mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
#10: implement HTML section numbering.
This commit is contained in:
3
CHANGES
3
CHANGES
@@ -41,6 +41,9 @@ New features added
|
||||
line. Also, the current builder output format (e.g. "html" or
|
||||
"latex") is always a defined tag.
|
||||
|
||||
- #10: Added HTML section numbers, enabled by giving a
|
||||
``:numbered:`` flag to the ``toctree`` directive.
|
||||
|
||||
- The ``literalinclude`` directive now supports several more
|
||||
options, to include only parts of a file.
|
||||
|
||||
|
||||
@@ -77,6 +77,18 @@ tables of contents. The ``toctree`` directive is the central element.
|
||||
You can also add external links, by giving an HTTP URL instead of a document
|
||||
name.
|
||||
|
||||
If you want to have section numbers even in HTML output, give the toctree a
|
||||
``numbered`` flag option. For example::
|
||||
|
||||
.. toctree::
|
||||
:numbered:
|
||||
|
||||
foo
|
||||
bar
|
||||
|
||||
Numbering then starts at the heading of ``foo``. Sub-toctrees are
|
||||
automatically numbered (don't give the ``numbered`` flag to those).
|
||||
|
||||
You can use "globbing" in toctree directives, by giving the ``glob`` flag
|
||||
option. All entries are then matched against the list of available
|
||||
documents, and matches are inserted into the list alphabetically. Example::
|
||||
@@ -124,7 +136,8 @@ tables of contents. The ``toctree`` directive is the central element.
|
||||
Added "globbing" option.
|
||||
|
||||
.. versionchanged:: 0.6
|
||||
Added "hidden" option and external links, as well as support for "self".
|
||||
Added "numbered" and "hidden" options as well as external links and
|
||||
support for "self" references.
|
||||
|
||||
|
||||
Special names
|
||||
|
||||
@@ -9,10 +9,14 @@ suggest new entries!
|
||||
How do I...
|
||||
-----------
|
||||
|
||||
... get section numbers?
|
||||
They are automatic in LaTeX output; for HTML, give a ``:numbered:`` option to
|
||||
the :dir:`toctree` directive where you want to start numbering.
|
||||
|
||||
... customize the look of the built HTML files?
|
||||
Use themes, see :doc:`theming`.
|
||||
|
||||
... add global substitutions?
|
||||
... add global substitutions or includes?
|
||||
Add them in the :confval:`rst_epilog` config value.
|
||||
|
||||
... write my own extension?
|
||||
|
||||
@@ -246,7 +246,7 @@ class Builder(object):
|
||||
self.info(bold('building [%s]: ' % self.name), nonl=1)
|
||||
self.info(summary)
|
||||
|
||||
updated_docnames = []
|
||||
updated_docnames = set()
|
||||
# while reading, collect all warnings from docutils
|
||||
warnings = []
|
||||
self.env.set_warnfunc(warnings.append)
|
||||
@@ -257,7 +257,7 @@ class Builder(object):
|
||||
self.info(iterator.next())
|
||||
for docname in self.status_iterator(iterator, 'reading sources... ',
|
||||
purple):
|
||||
updated_docnames.append(docname)
|
||||
updated_docnames.add(docname)
|
||||
# nothing further to do, the environment has already
|
||||
# done the reading
|
||||
for warning in warnings:
|
||||
@@ -265,6 +265,16 @@ class Builder(object):
|
||||
self.warn(warning)
|
||||
self.env.set_warnfunc(self.warn)
|
||||
|
||||
doccount = len(updated_docnames)
|
||||
self.info(bold('looking for now-outdated files... '), nonl=1)
|
||||
for docname in self.env.check_dependents(updated_docnames):
|
||||
updated_docnames.add(docname)
|
||||
outdated = len(updated_docnames) - doccount
|
||||
if outdated:
|
||||
self.info('%d found' % outdated)
|
||||
else:
|
||||
self.info('none found')
|
||||
|
||||
if updated_docnames:
|
||||
# save the environment
|
||||
self.info(bold('pickling environment... '), nonl=True)
|
||||
@@ -282,7 +292,7 @@ class Builder(object):
|
||||
|
||||
# another indirection to support builders that don't build
|
||||
# files individually
|
||||
self.write(docnames, updated_docnames, method)
|
||||
self.write(docnames, list(updated_docnames), method)
|
||||
|
||||
# finish (write static files etc.)
|
||||
self.finish()
|
||||
|
||||
@@ -79,6 +79,9 @@ class StandaloneHTMLBuilder(Builder):
|
||||
# a hash of all config values that, if changed, cause a full rebuild
|
||||
self.config_hash = ''
|
||||
self.tags_hash = ''
|
||||
# section numbers for headings in the currently visited document
|
||||
self.secnumbers = {}
|
||||
|
||||
self.init_templates()
|
||||
self.init_highlighter()
|
||||
self.init_translator_class()
|
||||
@@ -336,6 +339,7 @@ class StandaloneHTMLBuilder(Builder):
|
||||
destination = StringOutput(encoding='utf-8')
|
||||
doctree.settings = self.docsettings
|
||||
|
||||
self.secnumbers = self.env.toc_secnumbers.get(docname, {})
|
||||
self.imgpath = relative_uri(self.get_target_uri(docname), '_images')
|
||||
self.post_process_images(doctree)
|
||||
self.dlpath = relative_uri(self.get_target_uri(docname), '_downloads')
|
||||
|
||||
@@ -32,6 +32,7 @@ class TocTree(Directive):
|
||||
'maxdepth': int,
|
||||
'glob': directives.flag,
|
||||
'hidden': directives.flag,
|
||||
'numbered': directives.flag,
|
||||
}
|
||||
|
||||
def run(self):
|
||||
@@ -93,6 +94,7 @@ class TocTree(Directive):
|
||||
subnode['maxdepth'] = self.options.get('maxdepth', -1)
|
||||
subnode['glob'] = glob
|
||||
subnode['hidden'] = 'hidden' in self.options
|
||||
subnode['numbered'] = 'numbered' in self.options
|
||||
ret.append(subnode)
|
||||
return ret
|
||||
|
||||
|
||||
@@ -277,11 +277,13 @@ class BuildEnvironment:
|
||||
self.toc_num_entries = {} # docname -> number of real entries
|
||||
# used to determine when to show the TOC
|
||||
# in a sidebar (don't show if it's only one item)
|
||||
self.toc_secnumbers = {} # docname -> dict of sectionid -> number
|
||||
|
||||
self.toctree_includes = {} # docname -> list of toctree includefiles
|
||||
self.files_to_rebuild = {} # docname -> set of files
|
||||
# (containing its TOCs) to rebuild too
|
||||
self.glob_toctrees = set() # docnames that have :glob: toctrees
|
||||
self.numbered_toctrees = set() # docnames that have :numbered: toctrees
|
||||
|
||||
# X-ref target inventory
|
||||
self.descrefs = {} # fullname -> docname, desctype
|
||||
@@ -341,11 +343,13 @@ class BuildEnvironment:
|
||||
self.dependencies.pop(docname, None)
|
||||
self.titles.pop(docname, None)
|
||||
self.tocs.pop(docname, None)
|
||||
self.toc_secnumbers.pop(docname, None)
|
||||
self.toc_num_entries.pop(docname, None)
|
||||
self.toctree_includes.pop(docname, None)
|
||||
self.filemodules.pop(docname, None)
|
||||
self.indexentries.pop(docname, None)
|
||||
self.glob_toctrees.discard(docname)
|
||||
self.numbered_toctrees.discard(docname)
|
||||
self.images.purge_doc(docname)
|
||||
self.dlfiles.purge_doc(docname)
|
||||
|
||||
@@ -502,7 +506,8 @@ class BuildEnvironment:
|
||||
self.clear_doc(docname)
|
||||
|
||||
# read all new and changed files
|
||||
for docname in sorted(added | changed):
|
||||
to_read = added | changed
|
||||
for docname in sorted(to_read):
|
||||
yield docname
|
||||
self.read_doc(docname, app=app)
|
||||
|
||||
@@ -514,6 +519,11 @@ class BuildEnvironment:
|
||||
if app:
|
||||
app.emit('env-updated', self)
|
||||
|
||||
def check_dependents(self, already):
|
||||
to_rewrite = self.assign_section_numbers()
|
||||
for docname in to_rewrite:
|
||||
if docname not in already:
|
||||
yield docname
|
||||
|
||||
# --------- SINGLE FILE READING --------------------------------------------
|
||||
|
||||
@@ -824,6 +834,8 @@ class BuildEnvironment:
|
||||
file relations from it."""
|
||||
if toctreenode['glob']:
|
||||
self.glob_toctrees.add(docname)
|
||||
if toctreenode['numbered']:
|
||||
self.numbered_toctrees.add(docname)
|
||||
includefiles = toctreenode['includefiles']
|
||||
for includefile in includefiles:
|
||||
# note that if the included file is rebuilt, this one must be
|
||||
@@ -1315,6 +1327,57 @@ class BuildEnvironment:
|
||||
# allow custom references to be resolved
|
||||
builder.app.emit('doctree-resolved', doctree, fromdocname)
|
||||
|
||||
def assign_section_numbers(self):
|
||||
"""Assign a section number to each heading under a numbered toctree."""
|
||||
# a list of all docnames whose section numbers changed
|
||||
rewrite_needed = []
|
||||
|
||||
old_secnumbers = self.toc_secnumbers
|
||||
self.toc_secnumbers = {}
|
||||
|
||||
def _walk_toc(node, secnums, titlenode=None):
|
||||
# titlenode is the title of the document, it will get assigned a
|
||||
# secnumber too, so that it shows up in next/prev/parent rellinks
|
||||
for subnode in node.children:
|
||||
if isinstance(subnode, nodes.bullet_list):
|
||||
numstack.append(0)
|
||||
_walk_toc(subnode, secnums, titlenode)
|
||||
numstack.pop()
|
||||
titlenode = None
|
||||
elif isinstance(subnode, nodes.list_item):
|
||||
_walk_toc(subnode, secnums, titlenode)
|
||||
titlenode = None
|
||||
elif isinstance(subnode, addnodes.compact_paragraph):
|
||||
numstack[-1] += 1
|
||||
secnums[subnode[0]['anchorname']] = \
|
||||
subnode[0]['secnumber'] = tuple(numstack)
|
||||
if titlenode:
|
||||
titlenode['secnumber'] = tuple(numstack)
|
||||
titlenode = None
|
||||
elif isinstance(subnode, addnodes.toctree):
|
||||
_walk_toctree(subnode)
|
||||
|
||||
def _walk_toctree(toctreenode):
|
||||
for (title, ref) in toctreenode['entries']:
|
||||
if url_re.match(ref) or ref == 'self':
|
||||
# don't mess with those
|
||||
continue
|
||||
if ref in self.tocs:
|
||||
secnums = self.toc_secnumbers[ref] = {}
|
||||
_walk_toc(self.tocs[ref], secnums, self.titles.get(ref))
|
||||
if secnums != old_secnumbers.get(ref):
|
||||
rewrite_needed.append(ref)
|
||||
|
||||
for docname in self.numbered_toctrees:
|
||||
doctree = self.get_doctree(docname)
|
||||
for toctreenode in doctree.traverse(addnodes.toctree):
|
||||
if toctreenode.get('numbered'):
|
||||
# every numbered toctree gets new numbering
|
||||
numstack = [0]
|
||||
_walk_toctree(toctreenode)
|
||||
|
||||
return rewrite_needed
|
||||
|
||||
def create_index(self, builder, _fixre=re.compile(r'(.*) ([(][^()]*[)])')):
|
||||
"""Create the real index from the collected index entries."""
|
||||
new = {}
|
||||
|
||||
@@ -158,6 +158,8 @@ class HTMLTranslator(BaseTranslator):
|
||||
return
|
||||
self.body[-1] = '<a title="%s"' % self.attval(node['reftitle']) + \
|
||||
starttag[2:]
|
||||
if node.hasattr('secnumber'):
|
||||
self.body.append('%s. ' % '.'.join(map(str, node['secnumber'])))
|
||||
|
||||
# overwritten -- we don't want source comments to show up in the HTML
|
||||
def visit_comment(self, node):
|
||||
@@ -176,6 +178,17 @@ class HTMLTranslator(BaseTranslator):
|
||||
def depart_seealso(self, node):
|
||||
self.depart_admonition(node)
|
||||
|
||||
def add_secnumber(self, node):
|
||||
if node.hasattr('secnumber'):
|
||||
self.body.append('.'.join(map(str, node['secnumber'])) + '. ')
|
||||
elif isinstance(node.parent, nodes.section):
|
||||
anchorname = '#' + node.parent['ids'][0]
|
||||
if anchorname not in self.builder.secnumbers:
|
||||
anchorname = '' # try first heading which has no anchor
|
||||
if anchorname in self.builder.secnumbers:
|
||||
numbers = self.builder.secnumbers[anchorname]
|
||||
self.body.append('.'.join(map(str, numbers)) + '. ')
|
||||
|
||||
# overwritten for docutils 0.4
|
||||
if hasattr(BaseTranslator, 'start_tag_with_title'):
|
||||
def visit_section(self, node):
|
||||
@@ -186,6 +199,11 @@ class HTMLTranslator(BaseTranslator):
|
||||
def visit_title(self, node):
|
||||
# don't move the id attribute inside the <h> tag
|
||||
BaseTranslator.visit_title(self, node, move_ids=0)
|
||||
self.add_secnumber(node)
|
||||
else:
|
||||
def visit_title(self, node):
|
||||
BaseTranslator.visit_title(self, node)
|
||||
self.add_secnumber(node)
|
||||
|
||||
# overwritten
|
||||
def visit_literal_block(self, node):
|
||||
|
||||
@@ -9,6 +9,7 @@ Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:numbered:
|
||||
|
||||
images
|
||||
subdir/images
|
||||
|
||||
@@ -76,19 +76,22 @@ def test_second_update():
|
||||
# delete, add and "edit" (change saved mtime) some files and update again
|
||||
env.all_docs['contents'] = 0
|
||||
root = path(app.srcdir)
|
||||
(root / 'images.txt').unlink()
|
||||
# important: using "autodoc" because it is the last one to be included in
|
||||
# the contents.txt toctree; otherwise section numbers would shift
|
||||
(root / 'autodoc.txt').unlink()
|
||||
(root / 'new.txt').write_text('New file\n========\n')
|
||||
it = env.update(app.config, app.srcdir, app.doctreedir, app)
|
||||
msg = it.next()
|
||||
assert '1 added, 2 changed, 1 removed' in msg
|
||||
assert '1 added, 3 changed, 1 removed' in msg
|
||||
docnames = set()
|
||||
for docname in it:
|
||||
docnames.add(docname)
|
||||
# "includes" is in there because it contains a reference to a nonexisting
|
||||
# downloadable file, which is given another chance to exist
|
||||
assert docnames == set(['contents', 'new', 'includes'])
|
||||
assert 'images' not in env.all_docs
|
||||
assert 'images' not in env.found_docs
|
||||
# "includes" and "images" are in there because they contain references
|
||||
# to nonexisting downloadable or image files, which are given another
|
||||
# chance to exist
|
||||
assert docnames == set(['contents', 'new', 'includes', 'images'])
|
||||
assert 'autodoc' not in env.all_docs
|
||||
assert 'autodoc' not in env.found_docs
|
||||
|
||||
def test_object_inventory():
|
||||
refs = env.descrefs
|
||||
|
||||
Reference in New Issue
Block a user