Dear Sphinx developers, ==================== I'm a great fan of the framework and would like to contribute.
Documentation
---------------------
Unfortunately my first problem point is, that in the documentation of the
project there is no real documentation on how to contribute. So I simply
decided to send my ideas on that way and hopefully get into discussion. On
one page it was suggested to send an email to the group, which is
impossible as a non member.
Another point is, that the interface for changing the plotting of class
documentation is not enough documented. I still don't know where and how to
change it. Since I am having up to 10 parameters in the init function of a
class and up to 10 classes in one module, the normal "highlighting" of the
class documentation "headline" is insufficient.
TODO extension bug
-----------------------------
The easier point is, that there is a bug or conflict between autosummary
and the `todo` extension.
In some of my docstrings, I was beginning with::
.. todo:: To document!!
This makes the whole compiling process crash, with a useless error message.
I tried to catch this error and give a better warning. I'm pretty sure the
format I used is wrong, but at least you will get the idea. The relevant
change is at line 127::
try:
newnode['refuri'] = app.builder.get_relative_uri(
fromdocname, todo_info['docname'])
newnode['refuri'] += '#' + todo_info['target']['refid']
except NoUri:
# ignore if no URI can be determined, e.g. for LaTeX output
pass
+ except:
+ print "Wrong todo entry:"
+ for key in todo_info.keys():
+ print key,' : ',todo_info[key]
newnode.append(innernode)
para += newnode
My error log file is also attached. Autosummary seems to produce no 'refid'
entry but an 'id' entry with different syntax.
autodoc file generation for each module!
-------------------------------------------------------
The most important change I made, and which I would like to have integrated
in the official software is to the autodoc.py. When the software is
generating rst-files automatically all modules of one package get one rst
file and so in my use case only one web page, which is nearly unreadable.
So I added the option to generate one file for each module and only have an
auto-summary of the modules.
I also preferred having an auto-summary of the sub packages instead of a
toctree, but this could be made optional or discussed. :)
Further improvement could be also one file per class, but maybe that is to
detailed.
I would be glad, if I could improve the software for everybody with my
changes. If you have any question, just ask. Probably I did not explain
some of my problems in an understandable way.
Thank you for your time
Mario Michael Krell
--
You received this message because you are subscribed to the Google Groups
"sphinx-dev" group.
To view this discussion on the web visit
https://groups.google.com/d/msg/sphinx-dev/-/eWbnNTe6F_IJ.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/sphinx-dev?hl=en.
# -*- coding: utf-8 -*-
"""
sphinx.apidoc
~~~~~~~~~~~~~
Parses a directory tree looking for Python modules and packages and creates
ReST files appropriately to create code documentation with Sphinx. It also
creates a modules index (named modules.<suffix>).
This is derived from the "sphinx-autopackage" script, which is:
Copyright 2008 Soci?t? des arts technologiques (SAT),
http://www.sat.qc.ca/
:copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import os
import sys
import optparse
from os import path
# automodule options
if 'SPHINX_APIDOC_OPTIONS' in os.environ:
OPTIONS = os.environ['SPHINX_APIDOC_OPTIONS'].split(',')
else:
OPTIONS = [
'members',
'undoc-members',
# 'inherited-members', # disabled because there's a bug in sphinx
'show-inheritance',
]
INITPY = '__init__.py'
def makename(package, module):
"""Join package and module with a dot."""
# Both package and module can be None/empty.
if package:
name = package
if module:
name += '.' + module
else:
name = module
return name
def write_file(name, text, opts):
"""Write the output file for module/package <name>."""
fname = path.join(opts.destdir, '%s.%s' % (name, opts.suffix))
if opts.dryrun:
print 'Would create file %s.' % fname
return
if not opts.force and path.isfile(fname):
print 'File %s already exists, skipping.' % fname
else:
print 'Creating file %s.' % fname
f = open(fname, 'w')
try:
f.write(text)
finally:
f.close()
def format_heading(level, text):
"""Create a heading of <level> [1, 2 or 3 supported]."""
underlining = ['=', '-', '~', ][level-1] * len(text)
return '%s\n%s\n\n' % (text, underlining)
def format_directive(module, package=None):
"""Create the automodule directive and add the options."""
directive = '.. automodule:: %s\n' % makename(package, module)
for option in OPTIONS:
directive += ' :%s:\n' % option
return directive
def create_module_file(package, module, opts):
"""Build the text of the file and write the file."""
text = format_heading(1, '%s Module' % module)
#text += format_heading(2, ':mod:`%s` Module' % module)
text += format_directive(module, package)
write_file(makename(package, module), text, opts)
def create_package_file(root, master_package, subroot, py_files, opts, subs):
"""Build the text of the file and write the file."""
package = path.split(root)[-1]
text = format_heading(1, '%s Package' % package)
module_text = format_heading(2, 'Modules')
module_text += '.. autosummary::\n'
module_text += ' :toctree:\n\n'
# add each module in the package
for py_file in py_files:
if shall_skip(path.join(root, py_file)):
continue
is_package = py_file == INITPY
py_file = path.splitext(py_file)[0]
py_path = makename(subroot, py_file)
if is_package:
heading = ':mod:`%s` Package' % package
else:
module = path.splitext(py_file)[0]
if len(py_files)>1 and opts.module:
module_root=makename(master_package, subroot)
create_module_file(module_root,module,opts)
module_text += ' %s.%s\n' % (module_root, module)
continue
heading = ':mod:`%s` Module' % py_file
text += format_heading(2, heading)
# Package documentation gets __init__ additionally to the package name
# though it is not needed. This causes problems in combination with
# autosummary.
if py_path=="__init__":
py_path=""
text += format_directive(is_package and subroot or py_path,
master_package)
text += '\n'
module_text+='\n'
if len(py_files)>1 and opts.module:
text += module_text
text += '\n'
# build a list of directories that are packages (contain an INITPY file)
subs = [sub for sub in subs if path.isfile(path.join(root, sub, INITPY))]
# if there are some package directories, add a TOC for theses subpackages
if subs:
text += format_heading(2, 'Subpackages')
# text += '.. toctree::\n\n'
text += '.. autosummary::\n'
text += ' :toctree:\n\n'
for sub in subs:
text += ' %s.%s\n' % (makename(master_package, subroot), sub)
text += '\n'
write_file(makename(master_package, subroot), text, opts)
def create_modules_toc_file(modules, opts, name='modules'):
"""Create the module's index."""
text = format_heading(1, '%s' % opts.header)
text += '.. toctree::\n'
text += ' :maxdepth: %s\n\n' % opts.maxdepth
modules.sort()
prev_module = ''
for module in modules:
# look if the module is a subpackage and, if yes, ignore it
if module.startswith(prev_module + '.'):
continue
prev_module = module
text += ' %s\n' % module
write_file(name, text, opts)
def shall_skip(module):
"""Check if we want to skip this module."""
# skip it if there is nothing (or just \n or \r\n) in the file
return path.getsize(module) <= 2
def recurse_tree(rootpath, excludes, opts):
"""
Look for every file in the directory tree and create the corresponding
ReST files.
"""
# use absolute path for root, as relative paths like '../../foo' cause
# 'if "/." in root ...' to filter out *all* modules otherwise
rootpath = path.normpath(path.abspath(rootpath))
# check if the base directory is a package and get its name
if INITPY in os.listdir(rootpath):
root_package = rootpath.split(path.sep)[-1]
else:
# otherwise, the base is a directory with packages
root_package = None
toplevels = []
for root, subs, files in os.walk(rootpath):
if is_excluded(root, excludes):
del subs[:]
continue
# document only Python module files
py_files = sorted([f for f in files if path.splitext(f)[1] == '.py'])
is_pkg = INITPY in py_files
if is_pkg:
py_files.remove(INITPY)
py_files.insert(0, INITPY)
elif root != rootpath:
# only accept non-package at toplevel
del subs[:]
continue
# remove hidden ('.') and private ('_') directories
subs[:] = sorted(sub for sub in subs if sub[0] not in ['.', '_'])
if is_pkg:
# we are in a package with something to document
if subs or len(py_files) > 1 or not \
shall_skip(path.join(root, INITPY)):
subpackage = root[len(rootpath):].lstrip(path.sep).\
replace(path.sep, '.')
create_package_file(root, root_package, subpackage,
py_files, opts, subs)
toplevels.append(makename(root_package, subpackage))
else:
# if we are at the root level, we don't require it to be a package
assert root == rootpath and root_package is None
for py_file in py_files:
if not shall_skip(path.join(root, py_file)):
module = path.splitext(py_file)[0]
create_module_file(root, module, opts)
toplevels.append(module)
return toplevels
def normalize_excludes(rootpath, excludes):
"""
Normalize the excluded directory list:
* must be either an absolute path or start with rootpath,
* otherwise it is joined with rootpath
* with trailing slash
"""
f_excludes = []
for exclude in excludes:
if not path.isabs(exclude) and not exclude.startswith(rootpath):
exclude = path.join(rootpath, exclude)
f_excludes.append(path.normpath(exclude) + path.sep)
return f_excludes
def is_excluded(root, excludes):
"""
Check if the directory is in the exclude list.
Note: by having trailing slashes, we avoid common prefix issues, like
e.g. an exlude "foo" also accidentally excluding "foobar".
"""
sep = path.sep
if not root.endswith(sep):
root += sep
for exclude in excludes:
if root.startswith(exclude):
return True
return False
def main(argv=sys.argv):
"""
Parse and check the command line arguments.
"""
parser = optparse.OptionParser(
usage="""\
usage: %prog [options] -o <output_path> <module_path> [exclude_paths, ...]
Look recursively in <module_path> for Python modules and packages and create
one reST file with automodule directives per package in the <output_path>.
Note: By default this script will not overwrite already created files.""")
parser.add_option('-o', '--output-dir', action='store', dest='destdir',
help='Directory to place all output', default='')
parser.add_option('-d', '--maxdepth', action='store', dest='maxdepth',
help='Maximum depth of submodules to show in the TOC '
'(default: 4)', type='int', default=4)
parser.add_option('-f', '--force', action='store_true', dest='force',
help='Overwrite all files')
parser.add_option('-n', '--dry-run', action='store_true', dest='dryrun',
help='Run the script without creating files')
parser.add_option('-T', '--no-toc', action='store_true', dest='notoc',
help='Don\'t create a table of contents file')
parser.add_option('-s', '--suffix', action='store', dest='suffix',
help='file suffix (default: rst)', default='rst')
parser.add_option('-F', '--full', action='store_true', dest='full',
help='Generate a full project with sphinx-quickstart')
parser.add_option('-H', '--doc-project', action='store', dest='header',
help='Project name (default: root module name)')
parser.add_option('-A', '--doc-author', action='store', dest='author',
type='str',
help='Project author(s), used when --full is given')
parser.add_option('-V', '--doc-version', action='store', dest='version',
help='Project version, used when --full is given')
parser.add_option('-R', '--doc-release', action='store', dest='release',
help='Project release, used when --full is given, '
'defaults to --doc-version')
parser.add_option('-M', '--single-module-page', action='store_true', dest='module',
help='One extra page for each module')
(opts, args) = parser.parse_args(argv[1:])
if not args:
parser.error('A package path is required.')
rootpath, excludes = args[0], args[1:]
if not opts.destdir:
parser.error('An output directory is required.')
if opts.header is None:
opts.header = path.normpath(rootpath).split(path.sep)[-1]
if opts.suffix.startswith('.'):
opts.suffix = opts.suffix[1:]
if not path.isdir(rootpath):
print >>sys.stderr, '%s is not a directory.' % rootpath
sys.exit(1)
if not path.isdir(opts.destdir):
if not opts.dryrun:
os.makedirs(opts.destdir)
if not path.isabs(rootpath):
rootpath=path.abspath(rootpath)
excludes = normalize_excludes(rootpath, excludes)
modules = recurse_tree(rootpath, excludes, opts)
if opts.full:
from sphinx import quickstart as qs
modules.sort()
prev_module = ''
text = ''
for module in modules:
if module.startswith(prev_module + '.'):
continue
prev_module = module
text += ' %s\n' % module
d = dict(
path = opts.destdir,
sep = False,
dot = '_',
project = opts.header,
author = opts.author or 'Author',
version = opts.version or '',
release = opts.release or opts.version or '',
suffix = '.' + opts.suffix,
master = 'index',
epub = True,
ext_autodoc = True,
ext_viewcode = True,
makefile = True,
batchfile = True,
mastertocmaxdepth = opts.maxdepth,
mastertoctree = text,
)
if not opts.dryrun:
qs.generate(d, silent=True, overwrite=opts.force)
elif not opts.notoc:
create_modules_toc_file(modules, opts)
error.log
Description: Binary data
# -*- coding: utf-8 -*-
"""
sphinx.ext.todo
~~~~~~~~~~~~~~~
Allow todos to be inserted into your documentation. Inclusion of todos can
be switched of by a configuration variable. The todolist directive collects
all todos of your project and lists them along with a backlink to the
original location.
:copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
from docutils import nodes
from sphinx.locale import _
from sphinx.environment import NoUri
from sphinx.util.nodes import set_source_info
from sphinx.util.compat import Directive, make_admonition
class todo_node(nodes.Admonition, nodes.Element): pass
class todolist(nodes.General, nodes.Element): pass
class Todo(Directive):
"""
A todo entry, displayed (if configured) in the form of an admonition.
"""
has_content = True
required_arguments = 0
optional_arguments = 0
final_argument_whitespace = False
option_spec = {}
def run(self):
env = self.state.document.settings.env
targetid = 'index-%s' % env.new_serialno('index')
targetnode = nodes.target('', '', ids=[targetid])
ad = make_admonition(todo_node, self.name, [_('Todo')], self.options,
self.content, self.lineno, self.content_offset,
self.block_text, self.state, self.state_machine)
set_source_info(self, ad[0])
return [targetnode] + ad
def process_todos(app, doctree):
# collect all todos in the environment
# this is not done in the directive itself because it some transformations
# must have already been run, e.g. substitutions
env = app.builder.env
if not hasattr(env, 'todo_all_todos'):
env.todo_all_todos = []
for node in doctree.traverse(todo_node):
try:
targetnode = node.parent[node.parent.index(node) - 1]
if not isinstance(targetnode, nodes.target):
raise IndexError
except IndexError:
targetnode = None
env.todo_all_todos.append({
'docname': env.docname,
'source': node.source or env.doc2path(env.docname),
'lineno': node.line,
'todo': node.deepcopy(),
'target': targetnode,
})
class TodoList(Directive):
"""
A list of all todo entries.
"""
has_content = False
required_arguments = 0
optional_arguments = 0
final_argument_whitespace = False
option_spec = {}
def run(self):
# Simply insert an empty todolist node which will be replaced later
# when process_todo_nodes is called
return [todolist('')]
def process_todo_nodes(app, doctree, fromdocname):
if not app.config['todo_include_todos']:
for node in doctree.traverse(todo_node):
node.parent.remove(node)
# Replace all todolist nodes with a list of the collected todos.
# Augment each todo with a backlink to the original location.
env = app.builder.env
if not hasattr(env, 'todo_all_todos'):
env.todo_all_todos = []
for node in doctree.traverse(todolist):
if not app.config['todo_include_todos']:
node.replace_self([])
continue
content = []
for todo_info in env.todo_all_todos:
para = nodes.paragraph(classes=['todo-source'])
description = _('(The <<original entry>> is located in '
' %s, line %d.)') % \
(todo_info['source'], todo_info['lineno'])
desc1 = description[:description.find('<<')]
desc2 = description[description.find('>>')+2:]
para += nodes.Text(desc1, desc1)
# Create a reference
newnode = nodes.reference('', '', internal=True)
innernode = nodes.emphasis(_('original entry'), _('original entry'))
try:
newnode['refuri'] = app.builder.get_relative_uri(
fromdocname, todo_info['docname'])
newnode['refuri'] += '#' + todo_info['target']['refid']
except NoUri:
# ignore if no URI can be determined, e.g. for LaTeX output
pass
except:
print "Wrong todo entry:"
for key in todo_info.keys():
print key,' : ',todo_info[key]
newnode.append(innernode)
para += newnode
para += nodes.Text(desc2, desc2)
# (Recursively) resolve references in the todo content
todo_entry = todo_info['todo']
env.resolve_references(todo_entry, todo_info['docname'],
app.builder)
# Insert into the todolist
content.append(todo_entry)
content.append(para)
node.replace_self(content)
def purge_todos(app, env, docname):
if not hasattr(env, 'todo_all_todos'):
return
env.todo_all_todos = [todo for todo in env.todo_all_todos
if todo['docname'] != docname]
def visit_todo_node(self, node):
self.visit_admonition(node)
def depart_todo_node(self, node):
self.depart_admonition(node)
def setup(app):
app.add_config_value('todo_include_todos', False, False)
app.add_node(todolist)
app.add_node(todo_node,
html=(visit_todo_node, depart_todo_node),
latex=(visit_todo_node, depart_todo_node),
text=(visit_todo_node, depart_todo_node),
man=(visit_todo_node, depart_todo_node),
texinfo=(visit_todo_node, depart_todo_node))
app.add_directive('todo', Todo)
app.add_directive('todolist', TodoList)
app.connect('doctree-read', process_todos)
app.connect('doctree-resolved', process_todo_nodes)
app.connect('env-purge-doc', purge_todos)
