diff --git a/HACKING b/HACKING index 1bcf3fd66..5e4a0a62a 100644 --- a/HACKING +++ b/HACKING @@ -6,6 +6,8 @@ Coding overview This document tries to give you a cursory overview of the doctools code. +TODO: update this. + Dependencies ------------ diff --git a/README b/README index 96fde8ac1..beafe9c13 100644 --- a/README +++ b/README @@ -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 --------------------- diff --git a/sphinx-build.py b/sphinx-build.py index ecd6febe5..7c305e3d9 100644 --- a/sphinx-build.py +++ b/sphinx-build.py @@ -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)) diff --git a/sphinx/addons/ifconfig.py b/sphinx/addons/ifconfig.py index f6f3d5fb7..1f12b62e2 100644 --- a/sphinx/addons/ifconfig.py +++ b/sphinx/addons/ifconfig.py @@ -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): diff --git a/sphinx/application.py b/sphinx/application.py index b6fba62c3..b6676eb53 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -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): diff --git a/sphinx/builder.py b/sphinx/builder.py index 8fff38c13..0500cd7b8 100644 --- a/sphinx/builder.py +++ b/sphinx/builder.py @@ -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 = '%s: %s: %s' % (descname, ttext, context) else: entry = '%s: %s.' % (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 = '%s: %s: %s' % (descname, ttext, context) else: entry = '%s: %s.' % (descname, ttext) - libchanges.setdefault(module, []).append((entry, filename, lineno)) + libchanges.setdefault(module, []).append((entry, docname, lineno)) else: if not context: continue entry = '%s: %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): diff --git a/sphinx/config.py b/sphinx/config.py index d14108f55..5c40a29c6 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -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), diff --git a/sphinx/directives.py b/sphinx/directives.py index 2d9dac1eb..5a712c322 100644 --- a/sphinx/directives.py +++ b/sphinx/directives.py @@ -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} diff --git a/sphinx/environment.py b/sphinx/environment.py index 246c72c62..3226f617b 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -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 diff --git a/sphinx/htmlhelp.py b/sphinx/htmlhelp.py index 53604d87d..cee41ffe1 100644 --- a/sphinx/htmlhelp.py +++ b/sphinx/htmlhelp.py @@ -149,7 +149,7 @@ def build_hhx(builder, outdir, outname): f.write('