More refactoring, this time allowing different file extensions

and a different master file. Also fix environment warning reporting
and improve handling of error conditions.
This commit is contained in:
Georg Brandl 2008-02-01 20:44:17 +00:00
parent 8ca7c91443
commit b2ec05e690
15 changed files with 408 additions and 356 deletions

View File

@ -6,6 +6,8 @@ Coding overview
This document tries to give you a cursory overview of the doctools code.
TODO: update this.
Dependencies
------------

2
README
View File

@ -4,6 +4,8 @@ doctools README
FIXME: This is already outdated since the conversion has happened and the
reST sources are in the Python tree now.
TODO: update this.
What you need to know
---------------------

View File

@ -11,10 +11,4 @@ import sys
if __name__ == '__main__':
from sphinx import main
try:
sys.exit(main(sys.argv))
except Exception:
import traceback
traceback.print_exc()
import pdb
pdb.post_mortem(sys.exc_traceback)
sys.exit(main(sys.argv))

View File

@ -34,7 +34,7 @@ def ifconfig_directive(name, arguments, options, content, lineno,
return [node]
def process_ifconfig_nodes(app, doctree, docfilename):
def process_ifconfig_nodes(app, doctree, docname):
ns = app.config.__dict__.copy()
ns['builder'] = app.builder.name
for node in doctree.traverse(ifconfig):

View File

@ -42,9 +42,9 @@ class ExtensionError(Exception):
# List of all known events. Maps name to arguments description.
events = {
'builder-inited': 'builder instance',
'builder-inited': '',
'doctree-read' : 'the doctree before being pickled',
'doctree-resolved' : 'the doctree, the filename, the builder',
'doctree-resolved' : 'the doctree, the docname',
}
class Application(object):

View File

@ -27,13 +27,13 @@ from docutils.frontend import OptionParser
from docutils.readers.doctree import Reader as DoctreeReader
from sphinx import addnodes
from sphinx.util import (get_matching_files, ensuredir, relative_uri, os_path, SEP)
from sphinx.util import (get_matching_docs, ensuredir, relative_uri, SEP, os_path)
from sphinx.htmlhelp import build_hhx
from sphinx.htmlwriter import HTMLWriter, HTMLTranslator, SmartyPantsHTMLTranslator
from sphinx.latexwriter import LaTeXWriter
from sphinx.environment import BuildEnvironment, NoUri
from sphinx.highlighting import pygments, get_stylesheet
from sphinx.util.console import bold, purple, green, red, darkgreen
from sphinx.util.console import bold, purple, red, darkgreen
# side effect: registers roles and directives
from sphinx import roles
@ -64,6 +64,7 @@ class Builder(object):
self.freshenv = freshenv
self.init()
self.load_env()
# helper methods
@ -91,8 +92,11 @@ class Builder(object):
template = self.templates[name] = self.jinja_env.get_template(name)
return template
def get_target_uri(self, source_filename, typ=None):
"""Return the target URI for a source filename."""
def get_target_uri(self, docname, typ=None):
"""
Return the target URI for a document name (typ can be used to qualify
the link characteristic for individual builders).
"""
raise NotImplementedError
def get_relative_uri(self, from_, to, typ=None):
@ -102,7 +106,7 @@ class Builder(object):
return relative_uri(self.get_target_uri(from_),
self.get_target_uri(to, typ))
def get_outdated_files(self):
def get_outdated_docs(self):
"""Return a list of output files that are outdated."""
raise NotImplementedError
@ -132,29 +136,33 @@ class Builder(object):
self.info('done')
except Exception, err:
self.info('failed: %s' % err)
self.env = BuildEnvironment(self.srcdir, self.doctreedir)
self.env = BuildEnvironment(self.srcdir, self.doctreedir, self.config)
else:
self.env = BuildEnvironment(self.srcdir, self.doctreedir)
self.env = BuildEnvironment(self.srcdir, self.doctreedir, self.config)
self.env.set_warnfunc(self.warn)
def build_all(self):
"""Build all source files."""
self.load_env()
self.build(None, summary='all source files')
def build_specific(self, source_filenames):
def build_specific(self, filenames):
"""Only rebuild as much as needed for changes in the source_filenames."""
# bring the filenames to the canonical format, that is,
# relative to the source directory.
# relative to the source directory and without source_suffix.
dirlen = len(self.srcdir) + 1
to_write = [path.abspath(filename)[dirlen:] for filename in source_filenames]
self.load_env()
to_write = []
suffix = self.config.source_suffix
for filename in filenames:
filename = path.abspath(filename)[dirlen:]
if filename.endswith(suffix):
filename = filename[:-len(suffix)]
to_write.append(filename)
self.build(to_write,
summary='%d source files given on command line' % len(to_write))
def build_update(self):
"""Only rebuild files changed or added since last build."""
self.load_env()
to_build = self.get_outdated_files()
to_build = self.get_outdated_docs()
if not to_build:
self.info(bold('no target files are out of date, exiting.'))
return
@ -166,12 +174,12 @@ class Builder(object):
summary='targets for %d source files that are '
'out of date' % len(to_build))
def build(self, filenames, summary=None):
def build(self, docnames, summary=None):
if summary:
self.info(bold('building [%s]: ' % self.name), nonl=1)
self.info(summary)
updated_filenames = []
updated_docnames = []
# while reading, collect all warnings from docutils
warnings = []
self.env.set_warnfunc(warnings.append)
@ -179,14 +187,15 @@ class Builder(object):
iterator = self.env.update(self.config, self.app)
# the first item in the iterator is a summary message
self.info(iterator.next())
for filename in self.status_iterator(iterator, 'reading... ', purple):
updated_filenames.append(filename)
for docname in self.status_iterator(iterator, 'reading... ', purple):
updated_docnames.append(docname)
# nothing further to do, the environment has already done the reading
for warning in warnings:
self.warn(warning)
if warning.strip():
self.warn(warning)
self.env.set_warnfunc(self.warn)
if updated_filenames:
if updated_docnames:
# save the environment
self.info(bold('pickling the env... '), nonl=True)
self.env.topickle(path.join(self.doctreedir, ENV_PICKLE_FILENAME))
@ -198,43 +207,44 @@ class Builder(object):
# another indirection to support methods which don't build files
# individually
self.write(filenames, updated_filenames)
self.write(docnames, updated_docnames)
# finish (write style files etc.)
self.info(bold('finishing... '))
self.finish()
self.info(bold('build succeeded.'))
def write(self, build_filenames, updated_filenames):
if build_filenames is None: # build_all
build_filenames = self.env.all_files
filenames = set(build_filenames) | set(updated_filenames)
def write(self, build_docnames, updated_docnames):
if build_docnames is None: # build_all
build_docnames = self.env.all_docs
docnames = set(build_docnames) | set(updated_docnames)
# add all toctree-containing files that may have changed
for filename in list(filenames):
for tocfilename in self.env.files_to_rebuild.get(filename, []):
filenames.add(tocfilename)
filenames.add('contents.rst')
for docname in list(docnames):
for tocdocname in self.env.files_to_rebuild.get(docname, []):
docnames.add(tocdocname)
docnames.add(self.config.master_doc)
self.info(bold('creating index...'))
self.env.create_index(self)
self.prepare_writing(filenames)
self.prepare_writing(docnames)
# write target files
warnings = []
self.env.set_warnfunc(warnings.append)
for filename in self.status_iterator(sorted(filenames),
'writing output... ', green):
doctree = self.env.get_and_resolve_doctree(filename, self)
self.write_file(filename, doctree)
for docname in self.status_iterator(sorted(docnames),
'writing output... ', darkgreen):
doctree = self.env.get_and_resolve_doctree(docname, self)
self.write_doc(docname, doctree)
for warning in warnings:
self.warn(warning)
if warning.strip():
self.warn(warning)
self.env.set_warnfunc(self.warn)
def prepare_writing(self, filenames):
def prepare_writing(self, docnames):
raise NotImplementedError
def write_file(self, filename, doctree):
def write_doc(self, docname, doctree):
raise NotImplementedError
def finish(self):
@ -252,6 +262,9 @@ class StandaloneHTMLBuilder(Builder):
def init(self):
"""Load templates."""
self.init_templates()
self.init_translator_class()
def init_translator_class(self):
if self.config.html_translator_class:
self.translator_class = self.app.import_object(
self.config.html_translator_class, 'html_translator_class setting')
@ -272,10 +285,10 @@ class StandaloneHTMLBuilder(Builder):
settings_overrides={'output_encoding': 'unicode'}
)
def prepare_writing(self, filenames):
def prepare_writing(self, docnames):
from sphinx.search import IndexBuilder
self.indexer = IndexBuilder()
self.load_indexer(filenames)
self.load_indexer(docnames)
self.docwriter = HTMLWriter(self)
self.docsettings = OptionParser(
defaults=self.env.settings,
@ -301,8 +314,7 @@ class StandaloneHTMLBuilder(Builder):
len = len, # the built-in
)
def write_file(self, filename, doctree):
pagename = filename[:-4]
def write_doc(self, docname, doctree):
destination = StringOutput(encoding='utf-8')
doctree.settings = self.docsettings
@ -311,43 +323,43 @@ class StandaloneHTMLBuilder(Builder):
prev = next = None
parents = []
related = self.env.toctree_relations.get(filename)
related = self.env.toctree_relations.get(docname)
if related:
prev = {'link': self.get_relative_uri(filename, related[1]),
prev = {'link': self.get_relative_uri(docname, related[1]),
'title': self.render_partial(self.env.titles[related[1]])['title']}
next = {'link': self.get_relative_uri(filename, related[2]),
next = {'link': self.get_relative_uri(docname, related[2]),
'title': self.render_partial(self.env.titles[related[2]])['title']}
while related:
parents.append(
{'link': self.get_relative_uri(filename, related[0]),
{'link': self.get_relative_uri(docname, related[0]),
'title': self.render_partial(self.env.titles[related[0]])['title']})
related = self.env.toctree_relations.get(related[0])
if parents:
parents.pop() # remove link to "contents.rst"; we have a generic
parents.pop() # remove link to the master file; we have a generic
# "back to index" link already
parents.reverse()
title = self.env.titles.get(filename)
title = self.env.titles.get(docname)
if title:
title = self.render_partial(title)['title']
else:
title = ''
self.globalcontext['titles'][filename] = title
sourcename = pagename + '.txt'
context = dict(
self.globalcontext['titles'][docname] = title
sourcename = self.config.html_copy_source and docname + '.txt' or ''
ctx = dict(
title = title,
sourcename = sourcename,
body = self.docwriter.parts['fragment'],
toc = self.render_partial(self.env.get_toc_for(filename))['fragment'],
toc = self.render_partial(self.env.get_toc_for(docname))['fragment'],
# only display a TOC if there's more than one item to show
display_toc = (self.env.toc_num_entries[filename] > 1),
display_toc = (self.env.toc_num_entries[docname] > 1),
parents = parents,
prev = prev,
next = next,
)
self.index_page(pagename, doctree, title)
self.handle_page(pagename, context)
self.index_page(docname, doctree, title)
self.handle_page(docname, ctx)
def finish(self):
self.info(bold('writing additional files...'))
@ -369,7 +381,7 @@ class StandaloneHTMLBuilder(Builder):
# the global module index
# the sorted list of all modules, for the global module index
modules = sorted(((mn, (self.get_relative_uri('modindex.rst', fn) +
modules = sorted(((mn, (self.get_relative_uri('modindex', fn) +
'#module-' + mn, sy, pl, dep))
for (mn, (fn, sy, pl, dep)) in self.env.modules.iteritems()),
key=lambda x: x[0].lower())
@ -442,24 +454,25 @@ class StandaloneHTMLBuilder(Builder):
# --------- these are overwritten by the Web builder
def get_target_uri(self, source_filename, typ=None):
return source_filename[:-4] + '.html'
def get_target_uri(self, docname, typ=None):
return docname + '.html'
def get_outdated_files(self):
for filename in get_matching_files(
self.srcdir, '*.rst', exclude=set(self.config.unused_files)):
def get_outdated_docs(self):
for docname in get_matching_docs(
self.srcdir, self.config.source_suffix,
exclude=set(self.config.unused_files)):
targetname = self.env.doc2path(docname, self.outdir, '.html')
try:
rstname = path.join(self.outdir, os_path(filename))
targetmtime = path.getmtime(rstname[:-4] + '.html')
targetmtime = path.getmtime(targetname)
except:
targetmtime = 0
if filename not in self.env.all_files:
yield filename
elif path.getmtime(path.join(self.srcdir, os_path(filename))) > targetmtime:
yield filename
if docname not in self.env.all_docs:
yield docname
elif path.getmtime(self.env.doc2path(docname)) > targetmtime:
yield docname
def load_indexer(self, filenames):
def load_indexer(self, docnames):
try:
f = open(path.join(self.outdir, 'searchindex.json'), 'r')
try:
@ -469,7 +482,7 @@ class StandaloneHTMLBuilder(Builder):
except (IOError, OSError):
pass
# delete all entries for files that will be rebuilt
self.indexer.prune([fn[:-4] for fn in set(self.env.all_files) - set(filenames)])
self.indexer.prune(set(self.env.all_docs) - set(docnames))
def index_page(self, pagename, doctree, title):
# only index pages with title
@ -481,12 +494,12 @@ class StandaloneHTMLBuilder(Builder):
ctx['current_page_name'] = pagename
def pathto(otheruri, resource=False,
baseuri=self.get_target_uri(pagename+'.rst')):
baseuri=self.get_target_uri(pagename)):
if not resource:
otheruri = self.get_target_uri(otheruri+'.rst')
otheruri = self.get_target_uri(otheruri)
return relative_uri(baseuri, otheruri)
ctx['pathto'] = pathto
ctx['hasdoc'] = lambda name: name+'.rst' in self.env.all_files
ctx['hasdoc'] = lambda name: name in self.env.all_docs
sidebarfile = self.config.html_sidebars.get(pagename)
if sidebarfile:
ctx['customsidebar'] = path.join(self.srcdir, sidebarfile)
@ -505,12 +518,13 @@ class StandaloneHTMLBuilder(Builder):
self.warn("Error writing file %s: %s" % (outfilename, err))
if self.copysource and ctx.get('sourcename'):
# copy the source file for the "show source" link
shutil.copyfile(path.join(self.srcdir, os_path(pagename+'.rst')),
path.join(self.outdir, os_path(ctx['sourcename'])))
source_name = path.join(self.outdir, '_sources', os_path(ctx['sourcename']))
ensuredir(path.dirname(source_name))
shutil.copyfile(self.env.doc2path(pagename), source_name)
def handle_finish(self):
self.info(bold('dumping search index...'))
self.indexer.prune([fn[:-4] for fn in self.env.all_files])
self.indexer.prune(self.env.all_docs)
f = open(path.join(self.outdir, 'searchindex.json'), 'w')
try:
self.indexer.dump(f, 'json')
@ -525,29 +539,28 @@ class WebHTMLBuilder(StandaloneHTMLBuilder):
name = 'web'
def init(self):
# Nothing to do here.
pass
self.init_translator_class()
def get_outdated_files(self):
for filename in get_matching_files(
self.srcdir, '*.rst', exclude=set(self.config.unused_files)):
def get_outdated_docs(self):
for docname in get_matching_docs(
self.srcdir, self.config.source_suffix,
exclude=set(self.config.unused_files)):
targetname = self.env.doc2path(docname, self.outdir, '.fpickle')
try:
targetmtime = path.getmtime(
path.join(self.outdir, os_path(filename)[:-4] + '.fpickle'))
targetmtime = path.getmtime(targetname)
except:
targetmtime = 0
if path.getmtime(path.join(self.srcdir,
os_path(filename))) > targetmtime:
yield filename
if path.getmtime(self.env.doc2path(docname)) > targetmtime:
yield docname
def get_target_uri(self, source_filename, typ=None):
if source_filename == 'index.rst':
def get_target_uri(self, docname, typ=None):
if docname == 'index':
return ''
if source_filename.endswith(SEP+'index.rst'):
return source_filename[:-9] # up to sep
return source_filename[:-4] + SEP
if docname.endswith(SEP + 'index'):
return docname[:-5] # up to sep
return docname + SEP
def load_indexer(self, filenames):
def load_indexer(self, docnames):
try:
f = open(path.join(self.outdir, 'searchindex.pickle'), 'r')
try:
@ -557,32 +570,32 @@ class WebHTMLBuilder(StandaloneHTMLBuilder):
except (IOError, OSError):
pass
# delete all entries for files that will be rebuilt
self.indexer.prune(set(self.env.all_files) - set(filenames))
self.indexer.prune(set(self.env.all_docs) - set(docnames))
def index_page(self, pagename, doctree, title):
# only index pages with title
if self.indexer is not None and title:
self.indexer.feed(pagename+'.rst', title, doctree)
self.indexer.feed(pagename, title, doctree)
def handle_page(self, pagename, context, templatename='page.html'):
context['current_page_name'] = pagename
def handle_page(self, pagename, ctx, templatename='page.html'):
ctx['current_page_name'] = pagename
sidebarfile = self.config.html_sidebars.get(pagename, '')
if sidebarfile:
context['customsidebar'] = path.join(self.srcdir, sidebarfile)
ctx['customsidebar'] = path.join(self.srcdir, sidebarfile)
outfilename = path.join(self.outdir, os_path(pagename) + '.fpickle')
ensuredir(path.dirname(outfilename))
f = open(outfilename, 'wb')
try:
pickle.dump(context, f, 2)
pickle.dump(ctx, f, 2)
finally:
f.close()
# if there is a source file, copy the source file for the "show source" link
if context.get('sourcename'):
if ctx.get('sourcename'):
source_name = path.join(self.outdir, 'sources',
os_path(context['sourcename']))
os_path(ctx['sourcename']))
ensuredir(path.dirname(source_name))
shutil.copyfile(path.join(self.srcdir, os_path(pagename)+'.rst'), source_name)
shutil.copyfile(self.env.doc2path(pagename), source_name)
def handle_finish(self):
# dump the global context
@ -594,7 +607,7 @@ class WebHTMLBuilder(StandaloneHTMLBuilder):
f.close()
self.info(bold('dumping search index...'))
self.indexer.prune(self.env.all_files)
self.indexer.prune(self.env.all_docs)
f = open(path.join(self.outdir, 'searchindex.pickle'), 'wb')
try:
self.indexer.dump(f, 'pickle')
@ -636,31 +649,41 @@ class LaTeXBuilder(Builder):
name = 'latex'
def init(self):
self.filenames = []
self.document_data = map(list, self.config.latex_documents)
self.docnames = []
self.document_data = []
# assign subdirs to titles
self.titles = []
for entry in self.document_data:
# replace version with real version
sourcename = entry[0]
if sourcename.endswith('/index.rst'):
sourcename = sourcename[:-9]
self.titles.append((sourcename, entry[2]))
def get_outdated_files(self):
def get_outdated_docs(self):
return 'all documents' # for now
def get_target_uri(self, source_filename, typ=None):
def get_target_uri(self, docname, typ=None):
if typ == 'token':
# token references are always inside production lists and must be
# replaced by \token{} in LaTeX
return '@token'
if source_filename not in self.filenames:
if docname not in self.docnames:
raise NoUri
else:
return ''
def init_document_data(self):
preliminary_document_data = map(list, self.config.latex_documents)
if not preliminary_document_data:
self.warn('No "latex_documents" config value found; no documents '
'will be written.')
return
# assign subdirs to titles
self.titles = []
for entry in preliminary_document_data:
docname = entry[0]
if docname not in self.env.all_docs:
self.warn('"latex_documents" config value references unknown '
'document %s' % docname)
continue
self.document_data.append(entry)
if docname.endswith(SEP+'index'):
docname = docname[:-5]
self.titles.append((docname, entry[2]))
def write(self, *ignored):
# first, assemble the "appendix" docs that are in every PDF
appendices = []
@ -672,43 +695,40 @@ class LaTeXBuilder(Builder):
defaults=self.env.settings,
components=(docwriter,)).get_default_values()
if not self.document_data:
self.warn('No "latex_documents" config setting found; no documents '
'will be written.')
self.init_document_data()
for sourcename, targetname, title, author, docclass in self.document_data:
for docname, targetname, title, author, docclass in self.document_data:
destination = FileOutput(
destination_path=path.join(self.outdir, targetname),
encoding='utf-8')
self.info("processing " + targetname + "... ", nonl=1)
doctree = self.assemble_doctree(
sourcename, appendices=(docclass == 'manual') and appendices or [])
docname, appendices=(docclass == 'manual') and appendices or [])
self.info("writing... ", nonl=1)
doctree.settings = docsettings
doctree.settings.author = author
doctree.settings.filename = sourcename
doctree.settings.docname = docname
doctree.settings.docclass = docclass
docwriter.write(doctree, destination)
self.info("done")
def assemble_doctree(self, indexfile, appendices):
self.filenames = set([indexfile, 'glossary.rst', 'about.rst',
'license.rst', 'copyright.rst'])
self.info(green(indexfile) + " ", nonl=1)
def process_tree(filename, tree):
self.docnames = set([indexfile] + appendices)
self.info(darkgreen(indexfile) + " ", nonl=1)
def process_tree(docname, tree):
tree = tree.deepcopy()
for toctreenode in tree.traverse(addnodes.toctree):
newnodes = []
includefiles = map(str, toctreenode['includefiles'])
for includefile in includefiles:
try:
self.info(green(includefile) + " ", nonl=1)
self.info(darkgreen(includefile) + " ", nonl=1)
subtree = process_tree(includefile,
self.env.get_doctree(includefile))
self.filenames.add(includefile)
self.docnames.add(includefile)
except:
self.warn('%s: toctree contains ref to nonexisting file %r' %
(filename, includefile))
(docname, includefile))
else:
newnodes.extend(subtree.children)
toctreenode.parent.replace(toctreenode, newnodes)
@ -721,11 +741,11 @@ class LaTeXBuilder(Builder):
# resolve :ref:s to distant tex files -- we can't add a cross-reference,
# but append the document name
for pendingnode in largetree.traverse(addnodes.pending_xref):
filename = pendingnode['reffilename']
docname = pendingnode['refdocname']
sectname = pendingnode['refsectname']
newnodes = [nodes.emphasis(sectname, sectname)]
for subdir, title in self.titles:
if filename.startswith(subdir):
if docname.startswith(subdir):
newnodes.append(nodes.Text(' (in ', ' (in '))
newnodes.append(nodes.emphasis(title, title))
newnodes.append(nodes.Text(')', ')'))
@ -756,7 +776,7 @@ class ChangesBuilder(Builder):
self.vtemplate = self.get_template('changes/versionchanges.html')
self.stemplate = self.get_template('changes/rstsource.html')
def get_outdated_files(self):
def get_outdated_docs(self):
return self.outdir
typemap = {
@ -771,18 +791,18 @@ class ChangesBuilder(Builder):
apichanges = []
otherchanges = {}
self.info(bold('writing summary file...'))
for type, filename, lineno, module, descname, content in \
for type, docname, lineno, module, descname, content in \
self.env.versionchanges[version]:
ttext = self.typemap[type]
context = content.replace('\n', ' ')
if descname and filename.startswith('c-api'):
if descname and docname.startswith('c-api'):
if not descname:
continue
if context:
entry = '<b>%s</b>: <i>%s:</i> %s' % (descname, ttext, context)
else:
entry = '<b>%s</b>: <i>%s</i>.' % (descname, ttext)
apichanges.append((entry, filename, lineno))
apichanges.append((entry, docname, lineno))
elif descname or module:
if not module:
module = 'Builtins'
@ -792,14 +812,14 @@ class ChangesBuilder(Builder):
entry = '<b>%s</b>: <i>%s:</i> %s' % (descname, ttext, context)
else:
entry = '<b>%s</b>: <i>%s</i>.' % (descname, ttext)
libchanges.setdefault(module, []).append((entry, filename, lineno))
libchanges.setdefault(module, []).append((entry, docname, lineno))
else:
if not context:
continue
entry = '<i>%s:</i> %s' % (ttext.capitalize(), context)
title = self.env.titles[filename].astext()
otherchanges.setdefault((filename, title), []).append(
(entry, filename, lineno))
title = self.env.titles[docname].astext()
otherchanges.setdefault((docname, title), []).append(
(entry, docname, lineno))
ctx = {
'project': self.config.project,
@ -832,15 +852,15 @@ class ChangesBuilder(Builder):
return line
self.info(bold('copying source files...'))
for filename in self.env.all_files:
f = open(path.join(self.srcdir, os_path(filename)))
for docname in self.env.all_docs:
f = open(self.env.doc2path(docname))
lines = f.readlines()
targetfn = path.join(self.outdir, 'rst', os_path(filename)) + '.html'
targetfn = path.join(self.outdir, 'rst', os_path(docname)) + '.html'
ensuredir(path.dirname(targetfn))
f = codecs.open(targetfn, 'w', 'utf8')
try:
text = ''.join(hl(i+1, line) for (i, line) in enumerate(lines))
ctx = {'filename': filename, 'text': text}
ctx = {'filename': self.env.doc2path(docname, None), 'text': text}
f.write(self.stemplate.render(ctx))
finally:
f.close()
@ -873,25 +893,25 @@ class CheckExternalLinksBuilder(Builder):
# create output file
open(path.join(self.outdir, 'output.txt'), 'w').close()
def get_target_uri(self, source_filename, typ=None):
def get_target_uri(self, docname, typ=None):
return ''
def get_outdated_files(self):
return self.env.all_files
def get_outdated_docs(self):
return self.env.all_docs
def prepare_writing(self, filenames):
def prepare_writing(self, docnames):
return
def write_file(self, filename, doctree):
def write_doc(self, docname, doctree):
self.info()
for node in doctree.traverse(nodes.reference):
try:
self.check(node, filename)
self.check(node, docname)
except KeyError:
continue
return
def check(self, node, filename):
def check(self, node, docname):
uri = node['refuri']
if '#' in uri:
@ -920,23 +940,24 @@ class CheckExternalLinksBuilder(Builder):
elif r == 2:
self.info(' - ' + red('broken: ') + s)
self.broken[uri] = (r, s)
self.write_entry('broken', filename, lineno, uri + ': ' + s)
self.write_entry('broken', docname, lineno, uri + ': ' + s)
else:
self.info(' - ' + purple('redirected') + ' to ' + s)
self.redirected[uri] = (r, s)
self.write_entry('redirected', filename, lineno, uri + ' to ' + s)
self.write_entry('redirected', docname, lineno, uri + ' to ' + s)
elif len(uri) == 0 or uri[0:7] == 'mailto:' or uri[0:4] == 'ftp:':
return
else:
self.info(uri + ' - ' + red('malformed!'))
self.write_entry('malformed', filename, lineno, uri)
self.write_entry('malformed', docname, lineno, uri)
return
def write_entry(self, what, filename, line, uri):
def write_entry(self, what, docname, line, uri):
output = open(path.join(self.outdir, 'output.txt'), 'a')
output.write("%s:%s [%s] %s\n" % (filename, line, what, uri))
output.write("%s:%s [%s] %s\n" % (self.env.doc2path(docname, None),
line, what, uri))
output.close()
def resolve(self, uri):

View File

@ -33,6 +33,8 @@ class Config(object):
extensions = ([], True),
# general reading options
master_doc = ('contents', True),
source_suffix = ('.rst', True),
unused_files = ([], True),
add_function_parentheses = (True, True),
add_module_names = (True, True),
@ -44,6 +46,7 @@ class Config(object):
html_index = ('', False),
html_sidebars = ({}, False),
html_additional_pages = ({}, False),
html_copy_source = (True, False),
# HTML help options
htmlhelp_basename = ('pydoc', False),

View File

@ -539,15 +539,28 @@ directives.register_directive('moduleauthor', author_directive)
def toctree_directive(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
env = state.document.settings.env
dirname = posixpath.dirname(env.filename)
suffix = env.config.source_suffix
dirname = posixpath.dirname(env.docname)
ret = []
subnode = addnodes.toctree()
includefiles = filter(None, content)
# absolutize filenames
includefiles = [posixpath.normpath(posixpath.join(dirname, x)) for x in includefiles]
includefiles = []
for docname in content:
if not docname:
continue
# absolutize filenames, remove suffixes
if docname.endswith(suffix):
docname = docname[:-len(suffix)]
docname = posixpath.normpath(posixpath.join(dirname, docname))
if docname not in env.found_docs:
ret.append(state.document.reporter.warning(
'toctree references unknown document %s' % docname, line=lineno))
else:
includefiles.append(docname)
subnode['includefiles'] = includefiles
subnode['maxdepth'] = options.get('maxdepth', -1)
return [subnode]
ret.append(subnode)
return ret
toctree_directive.content = 1
toctree_directive.options = {'maxdepth': int}

View File

@ -43,7 +43,7 @@ Body.enum.converters['loweralpha'] = \
Body.enum.converters['upperroman'] = lambda x: None
from sphinx import addnodes
from sphinx.util import get_matching_files, os_path, SEP
from sphinx.util import get_matching_docs, SEP
default_settings = {
'embed_stylesheet': False,
@ -56,7 +56,7 @@ default_settings = {
# This is increased every time a new environment attribute is added
# to properly invalidate pickle files.
ENV_VERSION = 15
ENV_VERSION = 16
def walk_depth(node, depth, maxdepth):
@ -79,8 +79,11 @@ default_substitutions = set([
class RedirStream(object):
def __init__(self, write):
self.write = write
def __init__(self, writefunc):
self.writefunc = writefunc
def write(self, text):
if text.strip():
self.writefunc(text)
class NoUri(Exception):
@ -183,10 +186,10 @@ class BuildEnvironment:
# --------- ENVIRONMENT INITIALIZATION -------------------------------------
def __init__(self, srcdir, doctreedir):
def __init__(self, srcdir, doctreedir, config):
self.doctreedir = doctreedir
self.srcdir = srcdir
self.config = None
self.config = config
# the docutils settings for building
self.settings = default_settings.copy()
@ -198,41 +201,42 @@ class BuildEnvironment:
# this is to invalidate old pickles
self.version = ENV_VERSION
# Build times -- to determine changed files
# Also use this as an inventory of all existing and built filenames.
# All "filenames" here are /-separated and relative and include '.rst'.
self.all_files = {} # filename -> (mtime, md5sum) at the time of build
# All "docnames" here are /-separated and relative and exclude the source suffix.
self.found_docs = set() # contains all existing docnames
self.all_docs = {} # docname -> (mtime, md5sum) at the time of build
# contains all built docnames
# File metadata
self.metadata = {} # filename -> dict of metadata items
self.metadata = {} # docname -> dict of metadata items
# TOC inventory
self.titles = {} # filename -> title node
self.tocs = {} # filename -> table of contents nodetree
self.toc_num_entries = {} # filename -> number of real entries
self.titles = {} # docname -> title node
self.tocs = {} # docname -> table of contents nodetree
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.toctree_relations = {} # filename -> ["parent", "previous", "next"] filename
self.toctree_relations = {} # docname -> ["parent", "previous", "next"] docname
# for navigating in the toctree
self.files_to_rebuild = {} # filename -> set of files (containing its TOCs)
self.files_to_rebuild = {} # docname -> set of files (containing its TOCs)
# to rebuild too
# X-ref target inventory
self.descrefs = {} # fullname -> filename, desctype
self.filemodules = {} # filename -> [modules]
self.modules = {} # modname -> filename, synopsis, platform, deprecated
self.labels = {} # labelname -> filename, labelid, sectionname
self.reftargets = {} # (type, name) -> filename, labelid
self.descrefs = {} # fullname -> docname, desctype
self.filemodules = {} # docname -> [modules]
self.modules = {} # modname -> docname, synopsis, platform, deprecated
self.labels = {} # labelname -> docname, labelid, sectionname
self.reftargets = {} # (type, name) -> docname, labelid
# where type is term, token, option, envvar
# Other inventories
self.indexentries = {} # filename -> list of
self.indexentries = {} # docname -> list of
# (type, string, target, aliasname)
self.versionchanges = {} # version -> list of
# (type, filename, lineno, module, descname, content)
# (type, docname, lineno, module, descname, content)
# These are set while parsing a file
self.filename = None # current file name
self.docname = None # current document name
self.currmodule = None # current module name
self.currclass = None # current class name
self.currdesc = None # current descref name
@ -241,78 +245,95 @@ class BuildEnvironment:
def set_warnfunc(self, func):
self._warnfunc = func
self.settings['warnfunc'] = func
self.settings['warning_stream'] = RedirStream(func)
def clear_file(self, filename):
def warn(self, docname, msg):
if docname:
self._warnfunc(self.doc2path(docname) + ':: ' + msg)
else:
self._warnfunc('GLOBAL:: ' + msg)
def clear_doc(self, docname):
"""Remove all traces of a source file in the inventory."""
if filename in self.all_files:
self.all_files.pop(filename, None)
self.metadata.pop(filename, None)
self.titles.pop(filename, None)
self.tocs.pop(filename, None)
self.toc_num_entries.pop(filename, None)
if docname in self.all_docs:
self.all_docs.pop(docname, None)
self.metadata.pop(docname, None)
self.titles.pop(docname, None)
self.tocs.pop(docname, None)
self.toc_num_entries.pop(docname, None)
for subfn, fnset in self.files_to_rebuild.iteritems():
fnset.discard(filename)
fnset.discard(docname)
for fullname, (fn, _) in self.descrefs.items():
if fn == filename:
if fn == docname:
del self.descrefs[fullname]
self.filemodules.pop(filename, None)
self.filemodules.pop(docname, None)
for modname, (fn, _, _, _) in self.modules.items():
if fn == filename:
if fn == docname:
del self.modules[modname]
for labelname, (fn, _, _) in self.labels.items():
if fn == filename:
if fn == docname:
del self.labels[labelname]
for key, (fn, _) in self.reftargets.items():
if fn == filename:
if fn == docname:
del self.reftargets[key]
self.indexentries.pop(filename, None)
self.indexentries.pop(docname, None)
for version, changes in self.versionchanges.items():
new = [change for change in changes if change[1] != filename]
new = [change for change in changes if change[1] != docname]
changes[:] = new
def doc2path(self, docname, base=True, suffix=None):
"""
Return the filename for the document name.
If base is True, return absolute path under self.srcdir.
If base is None, return relative path to self.srcdir.
If base is a path string, return absolute path under that.
If suffix is not None, add it instead of config.source_suffix.
"""
suffix = suffix or self.config.source_suffix
if base is True:
return path.join(self.srcdir, docname.replace(SEP, path.sep)) + suffix
elif base is None:
return docname.replace(SEP, path.sep) + suffix
else:
return path.join(base, docname.replace(SEP, path.sep)) + suffix
def get_outdated_files(self, config, config_changed):
"""
Return (added, changed, removed) iterables.
Return (added, changed, removed) sets.
"""
all_source_files = list(get_matching_files(
self.srcdir, '*.rst', exclude=set(config.unused_files)))
self.found_docs = set(get_matching_docs(self.srcdir, config.source_suffix,
exclude=set(config.unused_files)))
# clear all files no longer present
removed = set(self.all_files) - set(all_source_files)
removed = set(self.all_docs) - self.found_docs
added = []
changed = []
added = set()
changed = set()
if config_changed:
# config values affect e.g. substitutions
added = all_source_files
added = self.found_docs
else:
for filename in all_source_files:
if filename not in self.all_files:
added.append(filename)
for docname in self.found_docs:
if docname not in self.all_docs:
added.add(docname)
else:
# if the doctree file is not there, rebuild
if not path.isfile(path.join(self.doctreedir,
os_path(filename)[:-3] + 'doctree')):
changed.append(filename)
if not path.isfile(self.doc2path(docname, self.doctreedir, '.doctree')):
changed.add(docname)
continue
mtime, md5sum = self.all_files[filename]
newmtime = path.getmtime(path.join(self.srcdir, os_path(filename)))
mtime, md5sum = self.all_docs[docname]
newmtime = path.getmtime(self.doc2path(docname))
if newmtime == mtime:
continue
# check the MD5
#with file(path.join(self.srcdir, filename), 'rb') as f:
# newmd5sum = md5(f.read()).digest()
#if newmd5sum != md5sum:
changed.append(filename)
changed.add(docname)
return added, changed, removed
def update(self, config, app=None):
"""(Re-)read all files new or changed since last update. Yields a summary
and then filenames as it processes them. Store all environment filenames
and then docnames as it processes them. Store all environment docnames
in the canonical format (ie using SEP as a separator in place of
os.path.sep)."""
config_changed = False
@ -338,36 +359,36 @@ class BuildEnvironment:
self.config = config
# clear all files no longer present
for filename in removed:
self.clear_file(filename)
for docname in removed:
self.clear_doc(docname)
# read all new and changed files
for filename in added + changed:
yield filename
self.read_file(filename, app=app)
for docname in sorted(added | changed):
yield docname
self.read_doc(docname, app=app)
if 'contents.rst' not in self.all_files:
self._warnfunc('no master file contents.rst found')
if config.master_doc not in self.all_docs:
self.warn(None, 'no master file %s found' % self.doc2path(config.master_doc))
# --------- SINGLE FILE BUILDING -------------------------------------------
def read_file(self, filename, src_path=None, save_parsed=True, app=None):
def read_doc(self, docname, src_path=None, save_parsed=True, app=None):
"""Parse a file and add/update inventory entries for the doctree.
If srcpath is given, read from a different source file."""
# remove all inventory entries for that file
self.clear_file(filename)
self.clear_doc(docname)
if src_path is None:
src_path = path.join(self.srcdir, os_path(filename))
src_path = self.doc2path(docname)
self.filename = filename
self.docname = docname
doctree = publish_doctree(None, src_path, FileInput,
settings_overrides=self.settings,
reader=MyStandaloneReader())
self.process_metadata(filename, doctree)
self.create_title_from(filename, doctree)
self.note_labels_from(filename, doctree)
self.build_toc_from(filename, doctree)
self.process_metadata(docname, doctree)
self.create_title_from(docname, doctree)
self.note_labels_from(docname, doctree)
self.build_toc_from(docname, doctree)
# calculate the MD5 of the file at time of build
f = open(src_path, 'rb')
@ -375,7 +396,7 @@ class BuildEnvironment:
md5sum = md5(f.read()).digest()
finally:
f.close()
self.all_files[filename] = (path.getmtime(src_path), md5sum)
self.all_docs[docname] = (path.getmtime(src_path), md5sum)
if app:
app.emit('doctree-read', doctree)
@ -383,11 +404,11 @@ class BuildEnvironment:
# make it picklable
doctree.reporter = None
doctree.transformer = None
doctree.settings.warning_stream = None
doctree.settings.env = None
doctree.settings.warnfunc = None
# cleanup
self.filename = None
self.docname = None
self.currmodule = None
self.currclass = None
self.indexnum = 0
@ -395,8 +416,7 @@ class BuildEnvironment:
if save_parsed:
# save the parsed doctree
doctree_filename = path.join(self.doctreedir,
os_path(filename)[:-3] + 'doctree')
doctree_filename = self.doc2path(docname, self.doctreedir, '.doctree')
dirname = path.dirname(doctree_filename)
if not path.isdir(dirname):
os.makedirs(dirname)
@ -408,11 +428,11 @@ class BuildEnvironment:
else:
return doctree
def process_metadata(self, filename, doctree):
def process_metadata(self, docname, doctree):
"""
Process the docinfo part of the doctree as metadata.
"""
self.metadata[filename] = md = {}
self.metadata[docname] = md = {}
docinfo = doctree[0]
if docinfo.__class__ is not nodes.docinfo:
# nothing to see here
@ -426,7 +446,7 @@ class BuildEnvironment:
md[name.astext()] = body.astext()
del doctree[0]
def create_title_from(self, filename, document):
def create_title_from(self, docname, document):
"""
Add a title node to the document (just copy the first section title),
and store that title in the environment.
@ -436,10 +456,10 @@ class BuildEnvironment:
visitor = MyContentsFilter(document)
node[0].walkabout(visitor)
titlenode += visitor.get_entry_text()
self.titles[filename] = titlenode
self.titles[docname] = titlenode
return
def note_labels_from(self, filename, document):
def note_labels_from(self, docname, document):
for name, explicit in document.nametypes.iteritems():
if not explicit:
continue
@ -450,27 +470,27 @@ class BuildEnvironment:
continue
sectname = node[0].astext() # node[0] == title node
if name in self.labels:
self._warnfunc('duplicate label %s, ' % name +
'in %s and %s' % (self.labels[name][0], filename))
self.labels[name] = filename, labelid, sectname
self.warn(docname, 'duplicate label %s, ' % name +
'other instance in %s' % self.doc2path(self.labels[name][0]))
self.labels[name] = docname, labelid, sectname
def note_toctree(self, filename, toctreenode):
def note_toctree(self, docname, toctreenode):
"""Note a TOC tree directive in a document and gather information about
file relations from it."""
includefiles = toctreenode['includefiles']
includefiles_len = len(includefiles)
for i, includefile in enumerate(includefiles):
# the "previous" file for the first toctree item is the parent
previous = i > 0 and includefiles[i-1] or filename
previous = i > 0 and includefiles[i-1] or docname
# the "next" file for the last toctree item is the parent again
next = i < includefiles_len-1 and includefiles[i+1] or filename
self.toctree_relations[includefile] = [filename, previous, next]
next = i < includefiles_len-1 and includefiles[i+1] or docname
self.toctree_relations[includefile] = [docname, previous, next]
# note that if the included file is rebuilt, this one must be
# too (since the TOC of the included file could have changed)
self.files_to_rebuild.setdefault(includefile, set()).add(filename)
self.files_to_rebuild.setdefault(includefile, set()).add(docname)
def build_toc_from(self, filename, document):
def build_toc_from(self, docname, document):
"""Build a TOC from the doctree and store it in the inventory."""
numentries = [0] # nonlocal again...
@ -483,7 +503,7 @@ class BuildEnvironment:
item = subnode.copy()
entries.append(item)
# do the inventory stuff
self.note_toctree(filename, subnode)
self.note_toctree(docname, subnode)
continue
if not isinstance(subnode, nodes.section):
continue
@ -500,7 +520,7 @@ class BuildEnvironment:
else:
anchorname = '#' + subnode['ids'][0]
numentries[0] += 1
reference = nodes.reference('', '', refuri=filename,
reference = nodes.reference('', '', refuri=docname,
anchorname=anchorname,
*nodetext)
para = addnodes.compact_paragraph('', '', reference)
@ -512,64 +532,66 @@ class BuildEnvironment:
return []
toc = build_toc(document)
if toc:
self.tocs[filename] = toc
self.tocs[docname] = toc
else:
self.tocs[filename] = nodes.bullet_list('')
self.toc_num_entries[filename] = numentries[0]
self.tocs[docname] = nodes.bullet_list('')
self.toc_num_entries[docname] = numentries[0]
def get_toc_for(self, filename):
def get_toc_for(self, docname):
"""Return a TOC nodetree -- for use on the same page only!"""
toc = self.tocs[filename].deepcopy()
toc = self.tocs[docname].deepcopy()
for node in toc.traverse(nodes.reference):
node['refuri'] = node['anchorname']
return toc
# -------
# these are called from docutils directives and therefore use self.filename
# these are called from docutils directives and therefore use self.docname
#
def note_descref(self, fullname, desctype):
if fullname in self.descrefs:
self._warnfunc('duplicate canonical description name %s, ' % fullname +
'in %s and %s' % (self.descrefs[fullname][0], self.filename))
self.descrefs[fullname] = (self.filename, desctype)
self.warn(self.docname,
'duplicate canonical description name %s, ' % fullname +
'other instance in %s' % self.doc2path(self.descrefs[fullname][0]))
self.descrefs[fullname] = (self.docname, desctype)
def note_module(self, modname, synopsis, platform, deprecated):
self.modules[modname] = (self.filename, synopsis, platform, deprecated)
self.filemodules.setdefault(self.filename, []).append(modname)
self.modules[modname] = (self.docname, synopsis, platform, deprecated)
self.filemodules.setdefault(self.docname, []).append(modname)
def note_reftarget(self, type, name, labelid):
self.reftargets[type, name] = (self.filename, labelid)
self.reftargets[type, name] = (self.docname, labelid)
def note_index_entry(self, type, string, targetid, aliasname):
self.indexentries.setdefault(self.filename, []).append(
self.indexentries.setdefault(self.docname, []).append(
(type, string, targetid, aliasname))
def note_versionchange(self, type, version, node, lineno):
self.versionchanges.setdefault(version, []).append(
(type, self.filename, lineno, self.currmodule, self.currdesc, node.astext()))
(type, self.docname, lineno, self.currmodule, self.currdesc, node.astext()))
# -------
# --------- RESOLVING REFERENCES AND TOCTREES ------------------------------
def get_doctree(self, filename):
def get_doctree(self, docname):
"""Read the doctree for a file from the pickle and return it."""
doctree_filename = path.join(self.doctreedir, os_path(filename)[:-3] + 'doctree')
doctree_filename = self.doc2path(docname, self.doctreedir, '.doctree')
f = open(doctree_filename, 'rb')
try:
doctree = pickle.load(f)
finally:
f.close()
doctree.reporter = Reporter(filename, 2, 4, stream=RedirStream(self._warnfunc))
doctree.reporter = Reporter(self.doc2path(docname), 2, 4,
stream=RedirStream(self._warnfunc))
return doctree
def get_and_resolve_doctree(self, filename, builder, doctree=None):
def get_and_resolve_doctree(self, docname, builder, doctree=None):
"""Read the doctree from the pickle, resolve cross-references and
toctrees and return it."""
if doctree is None:
doctree = self.get_doctree(filename)
doctree = self.get_doctree(docname)
# resolve all pending cross-references
self.resolve_references(doctree, filename, builder)
self.resolve_references(doctree, docname, builder)
# now, resolve all toctree nodes
def _entries_from_toctree(toctreenode):
@ -582,8 +604,8 @@ class BuildEnvironment:
toc = self.tocs[includefile].deepcopy()
except KeyError:
# this is raised if the included file does not exist
self._warnfunc('%s: toctree contains ref to nonexisting '
'file %r' % (filename, includefile))
self.warn(docname, 'toctree contains ref to nonexisting '
'file %r' % includefile)
else:
for toctreenode in toc.traverse(addnodes.toctree):
toctreenode.parent.replace_self(
@ -607,7 +629,7 @@ class BuildEnvironment:
if node.hasattr('anchorname'):
# a TOC reference
node['refuri'] = builder.get_relative_uri(
filename, node['refuri']) + node['anchorname']
docname, node['refuri']) + node['anchorname']
return doctree
@ -615,7 +637,7 @@ class BuildEnvironment:
descroles = frozenset(('data', 'exc', 'func', 'class', 'const', 'attr',
'meth', 'cfunc', 'cdata', 'ctype', 'cmacro'))
def resolve_references(self, doctree, docfilename, builder):
def resolve_references(self, doctree, fromdocname, builder):
for node in doctree.traverse(addnodes.pending_xref):
contnode = node[0].deepcopy()
newnode = None
@ -627,70 +649,69 @@ class BuildEnvironment:
if typ == 'ref':
# reference to the named label; the final node will contain the
# section name after the label
filename, labelid, sectname = self.labels.get(target, ('','',''))
if not filename:
docname, labelid, sectname = self.labels.get(target, ('','',''))
if not docname:
newnode = doctree.reporter.system_message(
2, 'undefined label: %s' % target)
self._warnfunc('%s: undefined label: %s' % (docfilename, target))
#self.warn(fromdocname, 'undefined label: %s' % target)
else:
newnode = nodes.reference('', '')
innernode = nodes.emphasis(sectname, sectname)
if filename == docfilename:
if docname == fromdocname:
newnode['refid'] = labelid
else:
# set more info in contnode in case the following call
# raises NoUri, the builder will have to resolve these
contnode = addnodes.pending_xref('')
contnode['reffilename'] = filename
contnode['refdocname'] = docname
contnode['refsectname'] = sectname
newnode['refuri'] = builder.get_relative_uri(
docfilename, filename) + '#' + labelid
fromdocname, docname) + '#' + labelid
newnode.append(innernode)
elif typ == 'keyword':
# keywords are referenced by named labels
filename, labelid, _ = self.labels.get(target, ('','',''))
if not filename:
self._warnfunc('%s: unknown keyword: %s' % (docfilename, target))
docname, labelid, _ = self.labels.get(target, ('','',''))
if not docname:
self.warn(fromdocname, 'unknown keyword: %s' % target)
newnode = contnode
else:
newnode = nodes.reference('', '')
if filename == docfilename:
if docname == fromdocname:
newnode['refid'] = labelid
else:
newnode['refuri'] = builder.get_relative_uri(
docfilename, filename) + '#' + labelid
fromdocname, docname) + '#' + labelid
newnode.append(contnode)
elif typ in ('token', 'term', 'envvar', 'option'):
filename, labelid = self.reftargets.get((typ, target), ('', ''))
if not filename:
docname, labelid = self.reftargets.get((typ, target), ('', ''))
if not docname:
if typ == 'term':
self._warnfunc('%s: term not in glossary: %s' %
(docfilename, target))
self.warn(fromdocname, 'term not in glossary: %s' % target)
newnode = contnode
else:
newnode = nodes.reference('', '')
if filename == docfilename:
if docname == fromdocname:
newnode['refid'] = labelid
else:
newnode['refuri'] = builder.get_relative_uri(
docfilename, filename, typ) + '#' + labelid
fromdocname, docname, typ) + '#' + labelid
newnode.append(contnode)
elif typ == 'mod':
filename, synopsis, platform, deprecated = \
docname, synopsis, platform, deprecated = \
self.modules.get(target, ('','','', ''))
# just link to an anchor if there are multiple modules in one file
# because the anchor is generally below the heading which is ugly
# but can't be helped easily
anchor = ''
if not filename or filename == docfilename:
if not docname or docname == fromdocname:
# don't link to self
newnode = contnode
else:
if len(self.filemodules[filename]) > 1:
if len(self.filemodules[docname]) > 1:
anchor = '#' + 'module-' + target
newnode = nodes.reference('', '')
newnode['refuri'] = (
builder.get_relative_uri(docfilename, filename) + anchor)
builder.get_relative_uri(fromdocname, docname) + anchor)
newnode['reftitle'] = '%s%s%s' % (
(platform and '(%s) ' % platform),
synopsis, (deprecated and ' (deprecated)' or ''))
@ -706,11 +727,11 @@ class BuildEnvironment:
newnode = contnode
else:
newnode = nodes.reference('', '')
if desc[0] == docfilename:
if desc[0] == fromdocname:
newnode['refid'] = name
else:
newnode['refuri'] = (
builder.get_relative_uri(docfilename, desc[0])
builder.get_relative_uri(fromdocname, desc[0])
+ '#' + name)
newnode.append(contnode)
else:
@ -721,7 +742,7 @@ class BuildEnvironment:
node.replace_self(newnode)
# allow custom references to be resolved
builder.app.emit('doctree-resolved', doctree, docfilename)
builder.app.emit('doctree-resolved', doctree, fromdocname)
def create_index(self, builder, _fixre=re.compile(r'(.*) ([(][^()]*[)])')):
"""Create the real index from the collected index entries."""
@ -735,7 +756,7 @@ class BuildEnvironment:
add_entry(subword, '', dic=entry[1])
else:
try:
entry[0].append(builder.get_relative_uri('genindex.rst', fn)
entry[0].append(builder.get_relative_uri('genindex', fn)
+ '#' + tid)
except NoUri:
pass
@ -766,7 +787,7 @@ class BuildEnvironment:
add_entry(string, 'built-in function')
add_entry('built-in function', string)
else:
self._warnfunc("unknown index entry type %r in %s" % (type, fn))
self.warn(fn, "unknown index entry type %r" % type)
newlist = new.items()
newlist.sort(key=lambda t: t[0].lower())
@ -815,12 +836,12 @@ class BuildEnvironment:
def check_consistency(self):
"""Do consistency checks."""
for filename in self.all_files:
if filename not in self.toctree_relations:
if filename == 'contents.rst':
for docname in self.all_docs:
if docname not in self.toctree_relations:
if docname == self.config.master_doc:
# the master file is not included anywhere ;)
continue
self._warnfunc('%s isn\'t included in any toctree' % filename)
self.warn(docname, 'document isn\'t included in any toctree')
# --------- QUERYING -------------------------------------------------------
@ -879,26 +900,26 @@ class BuildEnvironment:
Keywords searched are: first modules, then descrefs.
Returns: None if nothing found
(type, filename, anchorname) if exact match found
list of (quality, type, filename, anchorname, description) if fuzzy
(type, docname, anchorname) if exact match found
list of (quality, type, docname, anchorname, description) if fuzzy
"""
if keyword in self.modules:
filename, title, system, deprecated = self.modules[keyword]
return 'module', filename, 'module-' + keyword
docname, title, system, deprecated = self.modules[keyword]
return 'module', docname, 'module-' + keyword
if keyword in self.descrefs:
filename, ref_type = self.descrefs[keyword]
return ref_type, filename, keyword
docname, ref_type = self.descrefs[keyword]
return ref_type, docname, keyword
# special cases
if '.' not in keyword:
# exceptions are documented in the exceptions module
if 'exceptions.'+keyword in self.descrefs:
filename, ref_type = self.descrefs['exceptions.'+keyword]
return ref_type, filename, 'exceptions.'+keyword
docname, ref_type = self.descrefs['exceptions.'+keyword]
return ref_type, docname, 'exceptions.'+keyword
# special methods are documented as object methods
if 'object.'+keyword in self.descrefs:
filename, ref_type = self.descrefs['object.'+keyword]
return ref_type, filename, 'object.'+keyword
docname, ref_type = self.descrefs['object.'+keyword]
return ref_type, docname, 'object.'+keyword
if avoid_fuzzy:
return
@ -919,7 +940,7 @@ class BuildEnvironment:
yield '.'.join(parts[idx:])
result = []
for type, filename, title, desc in possibilities():
for type, docname, title, desc in possibilities():
best_res = 0
for part in dotsearch(title):
s.set_seq1(part)
@ -929,16 +950,6 @@ class BuildEnvironment:
s.ratio() > best_res:
best_res = s.ratio()
if best_res:
result.append((best_res, type, filename, title, desc))
result.append((best_res, type, docname, title, desc))
return heapq.nlargest(n, result)
def get_real_filename(self, filename):
"""
Pass this function a filename without .rst extension to get the real
filename. This also resolves the special `index.rst` files. If the file
does not exist the return value will be `None`.
"""
for rstname in filename + '.rst', filename + SEP + 'index.rst':
if rstname in self.all_files:
return rstname

View File

@ -149,7 +149,7 @@ def build_hhx(builder, outdir, outname):
f.write('<LI> ' + object_sitemap % ('Main page', 'index.html'))
f.write('<LI> ' + object_sitemap % ('Global Module Index', 'modindex.html'))
# the TOC
toc = builder.env.get_and_resolve_doctree('contents.rst', builder)
toc = builder.env.get_and_resolve_doctree(builder.config.master_doc, builder)
def write_toc(node, ullevel=0):
if isinstance(node, nodes.list_item):
f.write('<LI> ')

View File

@ -100,7 +100,7 @@ class LaTeXTranslator(nodes.NodeVisitor):
'pointsize': builder.config.latex_font_size,
'preamble': builder.config.latex_preamble,
'author': document.settings.author,
'filename': document.settings.filename,
'docname': document.settings.docname,
'title': None, # is determined later
'release': builder.config.release,
'date': date,
@ -200,7 +200,7 @@ class LaTeXTranslator(nodes.NodeVisitor):
# the environment already handles this
raise nodes.SkipNode
elif self.this_is_the_title:
if len(node.children) != 1 and not isinstance(node.children[0], Text):
if len(node.children) != 1 and not isinstance(node.children[0], nodes.Text):
self.builder.warn('document title is not a single Text node')
self.options['title'] = node.astext()
self.this_is_the_title = 0
@ -731,5 +731,10 @@ class LaTeXTranslator(nodes.NodeVisitor):
def depart_Text(self, node):
pass
def visit_system_message(self, node):
pass
def depart_system_message(self, node):
self.body.append('\n')
def unknown_visit(self, node):
raise NotImplementedError("Unknown node: " + node.__class__.__name__)

View File

@ -1,6 +1,6 @@
{% macro entries changes %}
<ul>{% for entry, filename, lineno in changes %}
<li><a href="rst/{{ filename }}.html#L{{ lineno-10 }}" target="src">{{ entry }}</a></li>
<ul>{% for entry, docname, lineno in changes %}
<li><a href="rst/{{ docname }}.html#L{{ lineno-10 }}" target="src">{{ entry }}</a></li>
{% endfor %}</ul>
{% endmacro -%}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"

View File

@ -106,7 +106,7 @@
<li><a href="{{ pathto('@edit/' + sourcename)|e }}">Suggest Change</a></li>
<li><a href="{{ pathto('@source/' + sourcename)|e }}">Show Source</a></li>
{% elif builder == 'html' %}
<li><a href="{{ pathto(sourcename, true)|e }}">Show Source</a></li>
<li><a href="{{ pathto('_sources/' + sourcename, true)|e }}">Show Source</a></li>
{% endif %}
</ul>
{% endif %}

View File

@ -22,11 +22,8 @@ from os import path
# hangover from more *nix-oriented origins.
SEP = "/"
def canonical_path(ospath):
return ospath.replace(os.path.sep, SEP)
def os_path(canpath):
return canpath.replace(SEP, os.path.sep)
def os_path(canonicalpath):
return canonicalpath.replace(SEP, os.path.sep)
def relative_uri(base, to):
@ -51,8 +48,12 @@ def ensuredir(path):
raise
def get_matching_files(dirname, pattern, exclude=()):
"""Get all files matching a pattern in a directory, recursively."""
def get_matching_docs(dirname, suffix, exclude=()):
"""
Get all file names (without suffix) matching a suffix in a
directory, recursively.
"""
pattern = '*' + suffix
# dirname is a normalized absolute path.
dirname = path.normpath(path.abspath(dirname))
dirlen = len(dirname) + 1 # exclude slash
@ -65,7 +66,7 @@ def get_matching_files(dirname, pattern, exclude=()):
qualified_name = path.join(root[dirlen:], sfile)
if qualified_name in exclude:
continue
yield canonical_path(qualified_name)
yield qualified_name[:-len(suffix)].replace(os.path.sep, SEP)
def shorten_result(text='', keywords=[], maxlen=240, fuzz=60):

View File

@ -225,7 +225,7 @@ class DocumentationApplication(object):
builder = MockBuilder()
builder.config = env2.config
writer = HTMLWriter(builder)
doctree = env2.read_file(page_id+'.rst', pathname, save_parsed=False)
doctree = env2.read_doc(page_id, pathname, save_parsed=False)
doctree = env2.get_and_resolve_doctree(page_id+'.rst', builder, doctree)
doctree.settings = OptionParser(defaults=env2.settings,
components=(writer,)).get_default_values()