Anonymous avatar Anonymous committed 28bcd32

More refactoring, this time allowing different file extensions
and a different master file. Also fix environment warning reporting
and improve handling of error conditions.

Comments (0)

Files changed (15)

 
 This document tries to give you a cursory overview of the doctools code.
 
+TODO: update this.
+
 
 Dependencies
 ------------
 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
 ---------------------
 
 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))

sphinx/addons/ifconfig.py

     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):

sphinx/application.py

 
 # 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):

sphinx/builder.py

 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
         self.freshenv = freshenv
 
         self.init()
+        self.load_env()
 
     # helper methods
 
         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):
         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
 
                 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
                        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)
         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))
 
         # 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):
     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')
             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,
             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
 
 
         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...'))
         # 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())
 
     # --------- 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:
         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
         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)
             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')
     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:
         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
             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')
     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 = []
             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)
         # 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(')', ')'))
         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 = {
         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'
                     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,
             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()
         # 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:
             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):
         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),
         html_index = ('', False),
         html_sidebars = ({}, False),
         html_additional_pages = ({}, False),
+        html_copy_source = (True, False),
 
         # HTML help options
         htmlhelp_basename = ('pydoc', False),

sphinx/directives.py

 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}

sphinx/environment.py

     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,
 
 # 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):
 
 
 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):
 
     # --------- 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()
         # 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
 
     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
         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')
             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)
         # 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
 
         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)
         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
                 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.
             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
                 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...
 
                     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
                 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)
             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):
                     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(
             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
 
     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
                 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 ''))
                         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:
                 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."""
                 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
                     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())
     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 -------------------------------------------------------
 
         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
                 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)
                    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

sphinx/htmlhelp.py

         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> ')

sphinx/latexwriter.py

                         '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,
             # 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
     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__)

sphinx/templates/changes/versionchanges.html

 {% 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"

sphinx/templates/layout.html

               <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 %}

sphinx/util/__init__.py

 # 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):
             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
             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):

sphinx/web/application.py

         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()
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.