buildframework/helium/external/python/lib/common/Sphinx-0.5.1-py2.5.egg/sphinx/environment.py
changeset 179 d8ac696cc51f
equal deleted inserted replaced
1:be27ed110b50 179:d8ac696cc51f
       
     1 # -*- coding: utf-8 -*-
       
     2 """
       
     3     sphinx.environment
       
     4     ~~~~~~~~~~~~~~~~~~
       
     5 
       
     6     Global creation environment.
       
     7 
       
     8     :copyright: 2007-2008 by Georg Brandl.
       
     9     :license: BSD.
       
    10 """
       
    11 
       
    12 import re
       
    13 import os
       
    14 import time
       
    15 import heapq
       
    16 import types
       
    17 import imghdr
       
    18 import difflib
       
    19 import cPickle as pickle
       
    20 from os import path
       
    21 from glob import glob
       
    22 from string import uppercase
       
    23 from itertools import izip, groupby
       
    24 try:
       
    25     import hashlib
       
    26     md5 = hashlib.md5
       
    27 except ImportError:
       
    28     # 2.4 compatibility
       
    29     import md5
       
    30     md5 = md5.new
       
    31 
       
    32 from docutils import nodes
       
    33 from docutils.io import FileInput, NullOutput
       
    34 from docutils.core import Publisher
       
    35 from docutils.utils import Reporter, relative_path
       
    36 from docutils.readers import standalone
       
    37 from docutils.parsers.rst import roles
       
    38 from docutils.parsers.rst.languages import en as english
       
    39 from docutils.parsers.rst.directives.html import MetaBody
       
    40 from docutils.writers import UnfilteredWriter
       
    41 from docutils.transforms import Transform
       
    42 from docutils.transforms.parts import ContentsFilter
       
    43 
       
    44 from sphinx import addnodes
       
    45 from sphinx.util import get_matching_docs, SEP, ustrftime
       
    46 from sphinx.directives import additional_xref_types
       
    47 
       
    48 default_settings = {
       
    49     'embed_stylesheet': False,
       
    50     'cloak_email_addresses': True,
       
    51     'pep_base_url': 'http://www.python.org/dev/peps/',
       
    52     'rfc_base_url': 'http://rfc.net/',
       
    53     'input_encoding': 'utf-8',
       
    54     'doctitle_xform': False,
       
    55     'sectsubtitle_xform': False,
       
    56 }
       
    57 
       
    58 # This is increased every time an environment attribute is added
       
    59 # or changed to properly invalidate pickle files.
       
    60 ENV_VERSION = 26
       
    61 
       
    62 
       
    63 default_substitutions = set([
       
    64     'version',
       
    65     'release',
       
    66     'today',
       
    67 ])
       
    68 
       
    69 dummy_reporter = Reporter('', 4, 4)
       
    70 
       
    71 
       
    72 class RedirStream(object):
       
    73     def __init__(self, writefunc):
       
    74         self.writefunc = writefunc
       
    75     def write(self, text):
       
    76         if text.strip():
       
    77             self.writefunc(text)
       
    78 
       
    79 
       
    80 class NoUri(Exception):
       
    81     """Raised by get_relative_uri if there is no URI available."""
       
    82     pass
       
    83 
       
    84 
       
    85 class DefaultSubstitutions(Transform):
       
    86     """
       
    87     Replace some substitutions if they aren't defined in the document.
       
    88     """
       
    89     # run before the default Substitutions
       
    90     default_priority = 210
       
    91 
       
    92     def apply(self):
       
    93         config = self.document.settings.env.config
       
    94         # only handle those not otherwise defined in the document
       
    95         to_handle = default_substitutions - set(self.document.substitution_defs)
       
    96         for ref in self.document.traverse(nodes.substitution_reference):
       
    97             refname = ref['refname']
       
    98             if refname in to_handle:
       
    99                 text = config[refname]
       
   100                 if refname == 'today' and not text:
       
   101                     # special handling: can also specify a strftime format
       
   102                     text = ustrftime(config.today_fmt or _('%B %d, %Y'))
       
   103                 ref.replace_self(nodes.Text(text, text))
       
   104 
       
   105 
       
   106 class MoveModuleTargets(Transform):
       
   107     """
       
   108     Move module targets to their nearest enclosing section title.
       
   109     """
       
   110     default_priority = 210
       
   111 
       
   112     def apply(self):
       
   113         for node in self.document.traverse(nodes.target):
       
   114             if not node['ids']:
       
   115                 continue
       
   116             if node['ids'][0].startswith('module-') and \
       
   117                    node.parent.__class__ is nodes.section:
       
   118                 node.parent['ids'] = node['ids']
       
   119                 node.parent.remove(node)
       
   120 
       
   121 
       
   122 class HandleCodeBlocks(Transform):
       
   123     """
       
   124     Move doctest blocks out of blockquotes.
       
   125     """
       
   126     default_priority = 210
       
   127 
       
   128     def apply(self):
       
   129         for node in self.document.traverse(nodes.block_quote):
       
   130             if len(node.children) == 1 and isinstance(node.children[0],
       
   131                                                       nodes.doctest_block):
       
   132                 node.replace_self(node.children[0])
       
   133 
       
   134 class CitationReferences(Transform):
       
   135     """
       
   136     Handle citation references before the default docutils transform does.
       
   137     """
       
   138     default_priority = 619
       
   139 
       
   140     def apply(self):
       
   141         for citnode in self.document.traverse(nodes.citation_reference):
       
   142             cittext = citnode.astext()
       
   143             refnode = addnodes.pending_xref(cittext, reftype='citation',
       
   144                                             reftarget=cittext)
       
   145             refnode += nodes.Text('[' + cittext + ']')
       
   146             citnode.parent.replace(citnode, refnode)
       
   147 
       
   148 
       
   149 class SphinxStandaloneReader(standalone.Reader):
       
   150     """
       
   151     Add our own transforms.
       
   152     """
       
   153     transforms = [CitationReferences, DefaultSubstitutions, MoveModuleTargets,
       
   154                   HandleCodeBlocks]
       
   155 
       
   156     def get_transforms(self):
       
   157         return standalone.Reader.get_transforms(self) + self.transforms
       
   158 
       
   159 
       
   160 class SphinxDummyWriter(UnfilteredWriter):
       
   161     supported = ('html',)  # needed to keep "meta" nodes
       
   162 
       
   163     def translate(self):
       
   164         pass
       
   165 
       
   166 
       
   167 
       
   168 class SphinxContentsFilter(ContentsFilter):
       
   169     """
       
   170     Used with BuildEnvironment.add_toc_from() to discard cross-file links
       
   171     within table-of-contents link nodes.
       
   172     """
       
   173     def visit_pending_xref(self, node):
       
   174         text = node.astext()
       
   175         self.parent.append(nodes.literal(text, text))
       
   176         raise nodes.SkipNode
       
   177 
       
   178 
       
   179 class BuildEnvironment:
       
   180     """
       
   181     The environment in which the ReST files are translated.
       
   182     Stores an inventory of cross-file targets and provides doctree
       
   183     transformations to resolve links to them.
       
   184     """
       
   185 
       
   186     # --------- ENVIRONMENT PERSISTENCE ----------------------------------------
       
   187 
       
   188     @staticmethod
       
   189     def frompickle(config, filename):
       
   190         picklefile = open(filename, 'rb')
       
   191         try:
       
   192             env = pickle.load(picklefile)
       
   193         finally:
       
   194             picklefile.close()
       
   195         env.config.values = config.values
       
   196         if env.version != ENV_VERSION:
       
   197             raise IOError('env version not current')
       
   198         return env
       
   199 
       
   200     def topickle(self, filename):
       
   201         # remove unpicklable attributes
       
   202         warnfunc = self._warnfunc
       
   203         self.set_warnfunc(None)
       
   204         values = self.config.values
       
   205         del self.config.values
       
   206         picklefile = open(filename, 'wb')
       
   207         # remove potentially pickling-problematic values from config
       
   208         for key, val in vars(self.config).items():
       
   209             if key.startswith('_') or \
       
   210                    isinstance(val, types.ModuleType) or \
       
   211                    isinstance(val, types.FunctionType) or \
       
   212                    isinstance(val, (type, types.ClassType)):
       
   213                 del self.config[key]
       
   214         try:
       
   215             pickle.dump(self, picklefile, pickle.HIGHEST_PROTOCOL)
       
   216         finally:
       
   217             picklefile.close()
       
   218         # reset attributes
       
   219         self.config.values = values
       
   220         self.set_warnfunc(warnfunc)
       
   221 
       
   222     # --------- ENVIRONMENT INITIALIZATION -------------------------------------
       
   223 
       
   224     def __init__(self, srcdir, doctreedir, config):
       
   225         self.doctreedir = doctreedir
       
   226         self.srcdir = srcdir
       
   227         self.config = config
       
   228 
       
   229         # the application object; only set while update() runs
       
   230         self.app = None
       
   231 
       
   232         # the docutils settings for building
       
   233         self.settings = default_settings.copy()
       
   234         self.settings['env'] = self
       
   235 
       
   236         # the function to write warning messages with
       
   237         self._warnfunc = None
       
   238 
       
   239         # this is to invalidate old pickles
       
   240         self.version = ENV_VERSION
       
   241 
       
   242         # All "docnames" here are /-separated and relative and exclude the source suffix.
       
   243 
       
   244         self.found_docs = set()     # contains all existing docnames
       
   245         self.all_docs = {}          # docname -> mtime at the time of build
       
   246                                     # contains all built docnames
       
   247         self.dependencies = {}      # docname -> set of dependent file names, relative to
       
   248                                     # documentation root
       
   249 
       
   250         # File metadata
       
   251         self.metadata = {}          # docname -> dict of metadata items
       
   252 
       
   253         # TOC inventory
       
   254         self.titles = {}            # docname -> title node
       
   255         self.tocs = {}              # docname -> table of contents nodetree
       
   256         self.toc_num_entries = {}   # docname -> number of real entries
       
   257                                     # used to determine when to show the TOC in a sidebar
       
   258                                     # (don't show if it's only one item)
       
   259         self.toctree_includes = {}  # docname -> list of toctree includefiles
       
   260         self.files_to_rebuild = {}  # docname -> set of files (containing its TOCs)
       
   261                                     # to rebuild too
       
   262         self.glob_toctrees = set()  # docnames that have :glob: toctrees
       
   263 
       
   264         # X-ref target inventory
       
   265         self.descrefs = {}          # fullname -> docname, desctype
       
   266         self.filemodules = {}       # docname -> [modules]
       
   267         self.modules = {}           # modname -> docname, synopsis, platform, deprecated
       
   268         self.labels = {}            # labelname -> docname, labelid, sectionname
       
   269         self.anonlabels = {}        # labelname -> docname, labelid
       
   270         self.progoptions = {}       # (program, name) -> docname, labelid
       
   271         self.reftargets = {}        # (type, name) -> docname, labelid
       
   272                                     # where type is term, token, envvar, citation
       
   273 
       
   274         # Other inventories
       
   275         self.indexentries = {}      # docname -> list of
       
   276                                     # (type, string, target, aliasname)
       
   277         self.versionchanges = {}    # version -> list of
       
   278                                     # (type, docname, lineno, module, descname, content)
       
   279         self.images = {}            # absolute path -> (docnames, unique filename)
       
   280 
       
   281         # These are set while parsing a file
       
   282         self.docname = None         # current document name
       
   283         self.currmodule = None      # current module name
       
   284         self.currclass = None       # current class name
       
   285         self.currdesc = None        # current descref name
       
   286         self.currprogram = None     # current program name
       
   287         self.index_num = 0          # autonumber for index targets
       
   288         self.gloss_entries = set()  # existing definition labels
       
   289 
       
   290         # Some magically present labels
       
   291         self.labels['genindex'] = ('genindex', '', _('Index'))
       
   292         self.labels['modindex'] = ('modindex', '', _('Module Index'))
       
   293         self.labels['search']   = ('search', '', _('Search Page'))
       
   294 
       
   295     def set_warnfunc(self, func):
       
   296         self._warnfunc = func
       
   297         self.settings['warning_stream'] = RedirStream(func)
       
   298 
       
   299     def warn(self, docname, msg, lineno=None):
       
   300         if docname:
       
   301             if lineno is None:
       
   302                 lineno = ''
       
   303             self._warnfunc('%s:%s: %s' % (self.doc2path(docname), lineno, msg))
       
   304         else:
       
   305             self._warnfunc('GLOBAL:: ' + msg)
       
   306 
       
   307     def clear_doc(self, docname):
       
   308         """Remove all traces of a source file in the inventory."""
       
   309         if docname in self.all_docs:
       
   310             self.all_docs.pop(docname, None)
       
   311             self.metadata.pop(docname, None)
       
   312             self.dependencies.pop(docname, None)
       
   313             self.titles.pop(docname, None)
       
   314             self.tocs.pop(docname, None)
       
   315             self.toc_num_entries.pop(docname, None)
       
   316             self.toctree_includes.pop(docname, None)
       
   317             self.filemodules.pop(docname, None)
       
   318             self.indexentries.pop(docname, None)
       
   319             self.glob_toctrees.discard(docname)
       
   320 
       
   321             for subfn, fnset in self.files_to_rebuild.items():
       
   322                 fnset.discard(docname)
       
   323                 if not fnset:
       
   324                     del self.files_to_rebuild[subfn]
       
   325             for fullname, (fn, _) in self.descrefs.items():
       
   326                 if fn == docname:
       
   327                     del self.descrefs[fullname]
       
   328             for modname, (fn, _, _, _) in self.modules.items():
       
   329                 if fn == docname:
       
   330                     del self.modules[modname]
       
   331             for labelname, (fn, _, _) in self.labels.items():
       
   332                 if fn == docname:
       
   333                     del self.labels[labelname]
       
   334             for key, (fn, _) in self.reftargets.items():
       
   335                 if fn == docname:
       
   336                     del self.reftargets[key]
       
   337             for key, (fn, _) in self.progoptions.items():
       
   338                 if fn == docname:
       
   339                     del self.progoptions[key]
       
   340             for version, changes in self.versionchanges.items():
       
   341                 new = [change for change in changes if change[1] != docname]
       
   342                 changes[:] = new
       
   343             for fullpath, (docs, _) in self.images.items():
       
   344                 docs.discard(docname)
       
   345                 if not docs:
       
   346                     del self.images[fullpath]
       
   347 
       
   348     def doc2path(self, docname, base=True, suffix=None):
       
   349         """
       
   350         Return the filename for the document name.
       
   351         If base is True, return absolute path under self.srcdir.
       
   352         If base is None, return relative path to self.srcdir.
       
   353         If base is a path string, return absolute path under that.
       
   354         If suffix is not None, add it instead of config.source_suffix.
       
   355         """
       
   356         suffix = suffix or self.config.source_suffix
       
   357         if base is True:
       
   358             return path.join(self.srcdir, docname.replace(SEP, path.sep)) + suffix
       
   359         elif base is None:
       
   360             return docname.replace(SEP, path.sep) + suffix
       
   361         else:
       
   362             return path.join(base, docname.replace(SEP, path.sep)) + suffix
       
   363 
       
   364     def find_files(self, config):
       
   365         """
       
   366         Find all source files in the source dir and put them in self.found_docs.
       
   367         """
       
   368         exclude_dirs  = [d.replace(SEP, path.sep) for d in config.exclude_dirs]
       
   369         exclude_trees = [d.replace(SEP, path.sep) for d in config.exclude_trees]
       
   370         self.found_docs = set(get_matching_docs(
       
   371             self.srcdir, config.source_suffix, exclude_docs=set(config.unused_docs),
       
   372             exclude_dirs=exclude_dirs, exclude_trees=exclude_trees,
       
   373             exclude_dirnames=['_sources'] + config.exclude_dirnames))
       
   374 
       
   375     def get_outdated_files(self, config_changed):
       
   376         """
       
   377         Return (added, changed, removed) sets.
       
   378         """
       
   379         # clear all files no longer present
       
   380         removed = set(self.all_docs) - self.found_docs
       
   381 
       
   382         added = set()
       
   383         changed = set()
       
   384 
       
   385         if config_changed:
       
   386             # config values affect e.g. substitutions
       
   387             added = self.found_docs
       
   388         else:
       
   389             for docname in self.found_docs:
       
   390                 if docname not in self.all_docs:
       
   391                     added.add(docname)
       
   392                     continue
       
   393                 # if the doctree file is not there, rebuild
       
   394                 if not path.isfile(self.doc2path(docname, self.doctreedir,
       
   395                                                  '.doctree')):
       
   396                     changed.add(docname)
       
   397                     continue
       
   398                 # check the mtime of the document
       
   399                 mtime = self.all_docs[docname]
       
   400                 newmtime = path.getmtime(self.doc2path(docname))
       
   401                 if newmtime > mtime:
       
   402                     changed.add(docname)
       
   403                     continue
       
   404                 # finally, check the mtime of dependencies
       
   405                 for dep in self.dependencies.get(docname, ()):
       
   406                     try:
       
   407                         # this will do the right thing when dep is absolute too
       
   408                         deppath = path.join(self.srcdir, dep)
       
   409                         if not path.isfile(deppath):
       
   410                             changed.add(docname)
       
   411                             break
       
   412                         depmtime = path.getmtime(deppath)
       
   413                         if depmtime > mtime:
       
   414                             changed.add(docname)
       
   415                             break
       
   416                     except EnvironmentError:
       
   417                         # give it another chance
       
   418                         changed.add(docname)
       
   419                         break
       
   420 
       
   421         return added, changed, removed
       
   422 
       
   423     def update(self, config, srcdir, doctreedir, app=None):
       
   424         """(Re-)read all files new or changed since last update.  Yields a summary
       
   425         and then docnames as it processes them.  Store all environment docnames
       
   426         in the canonical format (ie using SEP as a separator in place of
       
   427         os.path.sep)."""
       
   428         config_changed = False
       
   429         if self.config is None:
       
   430             msg = '[new config] '
       
   431             config_changed = True
       
   432         else:
       
   433             # check if a config value was changed that affects how doctrees are read
       
   434             for key, descr in config.config_values.iteritems():
       
   435                 if not descr[1]:
       
   436                     continue
       
   437                 if self.config[key] != config[key]:
       
   438                     msg = '[config changed] '
       
   439                     config_changed = True
       
   440                     break
       
   441             else:
       
   442                 msg = ''
       
   443             # this value is not covered by the above loop because it is handled
       
   444             # specially by the config class
       
   445             if self.config.extensions != config.extensions:
       
   446                 msg = '[extensions changed] '
       
   447                 config_changed = True
       
   448         # the source and doctree directories may have been relocated
       
   449         self.srcdir = srcdir
       
   450         self.doctreedir = doctreedir
       
   451         self.find_files(config)
       
   452 
       
   453         added, changed, removed = self.get_outdated_files(config_changed)
       
   454 
       
   455         # if files were added or removed, all documents with globbed toctrees
       
   456         # must be reread
       
   457         if added or removed:
       
   458             changed.update(self.glob_toctrees)
       
   459 
       
   460         msg += '%s added, %s changed, %s removed' % (len(added), len(changed),
       
   461                                                      len(removed))
       
   462         yield msg
       
   463 
       
   464         self.config = config
       
   465         self.app = app
       
   466 
       
   467         # clear all files no longer present
       
   468         for docname in removed:
       
   469             if app:
       
   470                 app.emit('env-purge-doc', self, docname)
       
   471             self.clear_doc(docname)
       
   472 
       
   473         # read all new and changed files
       
   474         for docname in sorted(added | changed):
       
   475             yield docname
       
   476             self.read_doc(docname, app=app)
       
   477 
       
   478         if config.master_doc not in self.all_docs:
       
   479             self.warn(None, 'master file %s not found' %
       
   480                       self.doc2path(config.master_doc))
       
   481 
       
   482         self.app = None
       
   483 
       
   484         # remove all non-existing images from inventory
       
   485         for imgsrc in self.images.keys():
       
   486             if not os.access(path.join(self.srcdir, imgsrc), os.R_OK):
       
   487                 del self.images[imgsrc]
       
   488 
       
   489         if app:
       
   490             app.emit('env-updated', self)
       
   491 
       
   492 
       
   493     # --------- SINGLE FILE READING --------------------------------------------
       
   494 
       
   495     def read_doc(self, docname, src_path=None, save_parsed=True, app=None):
       
   496         """
       
   497         Parse a file and add/update inventory entries for the doctree.
       
   498         If srcpath is given, read from a different source file.
       
   499         """
       
   500         # remove all inventory entries for that file
       
   501         if app:
       
   502             app.emit('env-purge-doc', self, docname)
       
   503         self.clear_doc(docname)
       
   504 
       
   505         if src_path is None:
       
   506             src_path = self.doc2path(docname)
       
   507 
       
   508         if self.config.default_role:
       
   509             role_fn, messages = roles.role(self.config.default_role, english,
       
   510                                            0, dummy_reporter)
       
   511             if role_fn:
       
   512                 roles._roles[''] = role_fn
       
   513             else:
       
   514                 self.warn(docname, 'default role %s not found' %
       
   515                           self.config.default_role)
       
   516 
       
   517         self.docname = docname
       
   518         self.settings['input_encoding'] = self.config.source_encoding
       
   519 
       
   520         class SphinxSourceClass(FileInput):
       
   521             def read(self):
       
   522                 data = FileInput.read(self)
       
   523                 if app:
       
   524                     arg = [data]
       
   525                     app.emit('source-read', docname, arg)
       
   526                     data = arg[0]
       
   527                 return data
       
   528 
       
   529         # publish manually
       
   530         pub = Publisher(reader=SphinxStandaloneReader(),
       
   531                         writer=SphinxDummyWriter(),
       
   532                         source_class=SphinxSourceClass,
       
   533                         destination_class=NullOutput)
       
   534         pub.set_components(None, 'restructuredtext', None)
       
   535         pub.process_programmatic_settings(None, self.settings, None)
       
   536         pub.set_source(None, src_path)
       
   537         pub.set_destination(None, None)
       
   538         try:
       
   539             pub.publish()
       
   540             doctree = pub.document
       
   541         except UnicodeError, err:
       
   542             from sphinx.application import SphinxError
       
   543             raise SphinxError(err.message)
       
   544         self.filter_messages(doctree)
       
   545         self.process_dependencies(docname, doctree)
       
   546         self.process_images(docname, doctree)
       
   547         self.process_metadata(docname, doctree)
       
   548         self.create_title_from(docname, doctree)
       
   549         self.note_labels_from(docname, doctree)
       
   550         self.note_indexentries_from(docname, doctree)
       
   551         self.note_citations_from(docname, doctree)
       
   552         self.build_toc_from(docname, doctree)
       
   553 
       
   554         # store time of reading, used to find outdated files
       
   555         self.all_docs[docname] = time.time()
       
   556 
       
   557         if app:
       
   558             app.emit('doctree-read', doctree)
       
   559 
       
   560         # make it picklable
       
   561         doctree.reporter = None
       
   562         doctree.transformer = None
       
   563         doctree.settings.warning_stream = None
       
   564         doctree.settings.env = None
       
   565         doctree.settings.record_dependencies = None
       
   566         for metanode in doctree.traverse(MetaBody.meta):
       
   567             # docutils' meta nodes aren't picklable because the class is nested
       
   568             metanode.__class__ = addnodes.meta
       
   569 
       
   570         # cleanup
       
   571         self.docname = None
       
   572         self.currmodule = None
       
   573         self.currclass = None
       
   574         self.gloss_entries = set()
       
   575 
       
   576         if save_parsed:
       
   577             # save the parsed doctree
       
   578             doctree_filename = self.doc2path(docname, self.doctreedir, '.doctree')
       
   579             dirname = path.dirname(doctree_filename)
       
   580             if not path.isdir(dirname):
       
   581                 os.makedirs(dirname)
       
   582             f = open(doctree_filename, 'wb')
       
   583             try:
       
   584                 pickle.dump(doctree, f, pickle.HIGHEST_PROTOCOL)
       
   585             finally:
       
   586                 f.close()
       
   587         else:
       
   588             return doctree
       
   589 
       
   590     def filter_messages(self, doctree):
       
   591         """
       
   592         Filter system messages from a doctree.
       
   593         """
       
   594         filterlevel = self.config.keep_warnings and 2 or 5
       
   595         for node in doctree.traverse(nodes.system_message):
       
   596             if node['level'] < filterlevel:
       
   597                 node.parent.remove(node)
       
   598 
       
   599     def process_dependencies(self, docname, doctree):
       
   600         """
       
   601         Process docutils-generated dependency info.
       
   602         """
       
   603         deps = doctree.settings.record_dependencies
       
   604         if not deps:
       
   605             return
       
   606         docdir = path.dirname(self.doc2path(docname, base=None))
       
   607         for dep in deps.list:
       
   608             dep = path.join(docdir, dep)
       
   609             self.dependencies.setdefault(docname, set()).add(dep)
       
   610 
       
   611     def process_images(self, docname, doctree):
       
   612         """
       
   613         Process and rewrite image URIs.
       
   614         """
       
   615         existing_names = set(v[1] for v in self.images.itervalues())
       
   616         docdir = path.dirname(self.doc2path(docname, base=None))
       
   617         for node in doctree.traverse(nodes.image):
       
   618             # Map the mimetype to the corresponding image.  The writer may
       
   619             # choose the best image from these candidates.  The special key * is
       
   620             # set if there is only single candiate to be used by a writer.
       
   621             # The special key ? is set for nonlocal URIs.
       
   622             node['candidates'] = candidates = {}
       
   623             imguri = node['uri']
       
   624             if imguri.find('://') != -1:
       
   625                 self.warn(docname, 'Nonlocal image URI found: %s' % imguri, node.line)
       
   626                 candidates['?'] = imguri
       
   627                 continue
       
   628             # imgpath is the image path *from srcdir*
       
   629             imgpath = path.normpath(path.join(docdir, imguri))
       
   630             # set imgpath as default URI
       
   631             node['uri'] = imgpath
       
   632             if imgpath.endswith(os.extsep + '*'):
       
   633                 for filename in glob(path.join(self.srcdir, imgpath)):
       
   634                     new_imgpath = relative_path(self.srcdir, filename)
       
   635                     if filename.lower().endswith('.pdf'):
       
   636                         candidates['application/pdf'] = new_imgpath
       
   637                     elif filename.lower().endswith('.svg'):
       
   638                         candidates['image/svg+xml'] = new_imgpath
       
   639                     else:
       
   640                         try:
       
   641                             f = open(filename, 'rb')
       
   642                             try:
       
   643                                 imgtype = imghdr.what(f)
       
   644                             finally:
       
   645                                 f.close()
       
   646                         except (OSError, IOError):
       
   647                             self.warn(docname, 'Image file %s not readable' % filename)
       
   648                         if imgtype:
       
   649                             candidates['image/' + imgtype] = new_imgpath
       
   650             else:
       
   651                 candidates['*'] = imgpath
       
   652             # map image paths to unique image names (so that they can be put
       
   653             # into a single directory)
       
   654             for imgpath in candidates.itervalues():
       
   655                 self.dependencies.setdefault(docname, set()).add(imgpath)
       
   656                 if not os.access(path.join(self.srcdir, imgpath), os.R_OK):
       
   657                     self.warn(docname, 'Image file not readable: %s' % imgpath,
       
   658                               node.line)
       
   659                 if imgpath in self.images:
       
   660                     self.images[imgpath][0].add(docname)
       
   661                     continue
       
   662                 uniquename = path.basename(imgpath)
       
   663                 base, ext = path.splitext(uniquename)
       
   664                 i = 0
       
   665                 while uniquename in existing_names:
       
   666                     i += 1
       
   667                     uniquename = '%s%s%s' % (base, i, ext)
       
   668                 self.images[imgpath] = (set([docname]), uniquename)
       
   669                 existing_names.add(uniquename)
       
   670 
       
   671     def process_metadata(self, docname, doctree):
       
   672         """
       
   673         Process the docinfo part of the doctree as metadata.
       
   674         """
       
   675         self.metadata[docname] = md = {}
       
   676         try:
       
   677             docinfo = doctree[0]
       
   678         except IndexError:
       
   679             # probably an empty document
       
   680             return
       
   681         if docinfo.__class__ is not nodes.docinfo:
       
   682             # nothing to see here
       
   683             return
       
   684         for node in docinfo:
       
   685             if node.__class__ is nodes.author:
       
   686                 # handled specially by docutils
       
   687                 md['author'] = node.astext()
       
   688             elif node.__class__ is nodes.field:
       
   689                 name, body = node
       
   690                 md[name.astext()] = body.astext()
       
   691         del doctree[0]
       
   692 
       
   693     def create_title_from(self, docname, document):
       
   694         """
       
   695         Add a title node to the document (just copy the first section title),
       
   696         and store that title in the environment.
       
   697         """
       
   698         for node in document.traverse(nodes.section):
       
   699             titlenode = nodes.title()
       
   700             visitor = SphinxContentsFilter(document)
       
   701             node[0].walkabout(visitor)
       
   702             titlenode += visitor.get_entry_text()
       
   703             self.titles[docname] = titlenode
       
   704             return
       
   705 
       
   706     def note_labels_from(self, docname, document):
       
   707         for name, explicit in document.nametypes.iteritems():
       
   708             if not explicit:
       
   709                 continue
       
   710             labelid = document.nameids[name]
       
   711             if labelid is None:
       
   712                 continue
       
   713             node = document.ids[labelid]
       
   714             if name.isdigit() or node.has_key('refuri') or \
       
   715                    node.tagname.startswith('desc_'):
       
   716                 # ignore footnote labels, labels automatically generated from a
       
   717                 # link and description units
       
   718                 continue
       
   719             if name in self.labels:
       
   720                 self.warn(docname, 'duplicate label %s, ' % name +
       
   721                           'other instance in %s' % self.doc2path(self.labels[name][0]),
       
   722                           node.line)
       
   723             self.anonlabels[name] = docname, labelid
       
   724             if node.tagname == 'section':
       
   725                 sectname = node[0].astext() # node[0] == title node
       
   726             elif node.tagname == 'figure':
       
   727                 for n in node:
       
   728                     if n.tagname == 'caption':
       
   729                         sectname = n.astext()
       
   730                         break
       
   731                 else:
       
   732                     continue
       
   733             else:
       
   734                 # anonymous-only labels
       
   735                 continue
       
   736             self.labels[name] = docname, labelid, sectname
       
   737 
       
   738     def note_indexentries_from(self, docname, document):
       
   739         entries = self.indexentries[docname] = []
       
   740         for node in document.traverse(addnodes.index):
       
   741             entries.extend(node['entries'])
       
   742 
       
   743     def note_citations_from(self, docname, document):
       
   744         for node in document.traverse(nodes.citation):
       
   745             label = node[0].astext()
       
   746             if ('citation', label) in self.reftargets:
       
   747                 self.warn(docname, 'duplicate citation %s, ' % label +
       
   748                           'other instance in %s' % self.doc2path(
       
   749                     self.reftargets['citation', label][0]), node.line)
       
   750             self.reftargets['citation', label] = (docname, node['ids'][0])
       
   751 
       
   752     def note_toctree(self, docname, toctreenode):
       
   753         """Note a TOC tree directive in a document and gather information about
       
   754            file relations from it."""
       
   755         if toctreenode['glob']:
       
   756             self.glob_toctrees.add(docname)
       
   757         includefiles = toctreenode['includefiles']
       
   758         for includefile in includefiles:
       
   759             # note that if the included file is rebuilt, this one must be
       
   760             # too (since the TOC of the included file could have changed)
       
   761             self.files_to_rebuild.setdefault(includefile, set()).add(docname)
       
   762         self.toctree_includes.setdefault(docname, []).extend(includefiles)
       
   763 
       
   764     def build_toc_from(self, docname, document):
       
   765         """Build a TOC from the doctree and store it in the inventory."""
       
   766         numentries = [0] # nonlocal again...
       
   767 
       
   768         try:
       
   769             maxdepth = int(self.metadata[docname].get('tocdepth', 0))
       
   770         except ValueError:
       
   771             maxdepth = 0
       
   772 
       
   773         def build_toc(node, depth=1):
       
   774             entries = []
       
   775             for subnode in node:
       
   776                 if isinstance(subnode, addnodes.toctree):
       
   777                     # just copy the toctree node which is then resolved
       
   778                     # in self.get_and_resolve_doctree
       
   779                     item = subnode.copy()
       
   780                     entries.append(item)
       
   781                     # do the inventory stuff
       
   782                     self.note_toctree(docname, subnode)
       
   783                     continue
       
   784                 if not isinstance(subnode, nodes.section):
       
   785                     continue
       
   786                 title = subnode[0]
       
   787                 # copy the contents of the section title, but without references
       
   788                 # and unnecessary stuff
       
   789                 visitor = SphinxContentsFilter(document)
       
   790                 title.walkabout(visitor)
       
   791                 nodetext = visitor.get_entry_text()
       
   792                 if not numentries[0]:
       
   793                     # for the very first toc entry, don't add an anchor
       
   794                     # as it is the file's title anyway
       
   795                     anchorname = ''
       
   796                 else:
       
   797                     anchorname = '#' + subnode['ids'][0]
       
   798                 numentries[0] += 1
       
   799                 reference = nodes.reference('', '', refuri=docname,
       
   800                                             anchorname=anchorname,
       
   801                                             *nodetext)
       
   802                 para = addnodes.compact_paragraph('', '', reference)
       
   803                 item = nodes.list_item('', para)
       
   804                 if maxdepth == 0 or depth < maxdepth:
       
   805                     item += build_toc(subnode, depth+1)
       
   806                 entries.append(item)
       
   807             if entries:
       
   808                 return nodes.bullet_list('', *entries)
       
   809             return []
       
   810         toc = build_toc(document)
       
   811         if toc:
       
   812             self.tocs[docname] = toc
       
   813         else:
       
   814             self.tocs[docname] = nodes.bullet_list('')
       
   815         self.toc_num_entries[docname] = numentries[0]
       
   816 
       
   817     def get_toc_for(self, docname):
       
   818         """Return a TOC nodetree -- for use on the same page only!"""
       
   819         toc = self.tocs[docname].deepcopy()
       
   820         for node in toc.traverse(nodes.reference):
       
   821             node['refuri'] = node['anchorname']
       
   822         return toc
       
   823 
       
   824     # -------
       
   825     # these are called from docutils directives and therefore use self.docname
       
   826     #
       
   827     def note_descref(self, fullname, desctype, line):
       
   828         if fullname in self.descrefs:
       
   829             self.warn(self.docname,
       
   830                       'duplicate canonical description name %s, ' % fullname +
       
   831                       'other instance in %s' % self.doc2path(self.descrefs[fullname][0]),
       
   832                       line)
       
   833         self.descrefs[fullname] = (self.docname, desctype)
       
   834 
       
   835     def note_module(self, modname, synopsis, platform, deprecated):
       
   836         self.modules[modname] = (self.docname, synopsis, platform, deprecated)
       
   837         self.filemodules.setdefault(self.docname, []).append(modname)
       
   838 
       
   839     def note_progoption(self, optname, labelid):
       
   840         self.progoptions[self.currprogram, optname] = (self.docname, labelid)
       
   841 
       
   842     def note_reftarget(self, type, name, labelid):
       
   843         self.reftargets[type, name] = (self.docname, labelid)
       
   844 
       
   845     def note_versionchange(self, type, version, node, lineno):
       
   846         self.versionchanges.setdefault(version, []).append(
       
   847             (type, self.docname, lineno, self.currmodule, self.currdesc, node.astext()))
       
   848 
       
   849     def note_dependency(self, filename):
       
   850         basename = path.dirname(self.doc2path(self.docname, base=None))
       
   851         # this will do the right thing when filename is absolute too
       
   852         filename = path.join(basename, filename)
       
   853         self.dependencies.setdefault(self.docname, set()).add(filename)
       
   854     # -------
       
   855 
       
   856     # --------- RESOLVING REFERENCES AND TOCTREES ------------------------------
       
   857 
       
   858     def get_doctree(self, docname):
       
   859         """Read the doctree for a file from the pickle and return it."""
       
   860         doctree_filename = self.doc2path(docname, self.doctreedir, '.doctree')
       
   861         f = open(doctree_filename, 'rb')
       
   862         try:
       
   863             doctree = pickle.load(f)
       
   864         finally:
       
   865             f.close()
       
   866         doctree.settings.env = self
       
   867         doctree.reporter = Reporter(self.doc2path(docname), 2, 4,
       
   868                                     stream=RedirStream(self._warnfunc))
       
   869         return doctree
       
   870 
       
   871 
       
   872     def get_and_resolve_doctree(self, docname, builder, doctree=None,
       
   873                                 prune_toctrees=True):
       
   874         """Read the doctree from the pickle, resolve cross-references and
       
   875            toctrees and return it."""
       
   876         if doctree is None:
       
   877             doctree = self.get_doctree(docname)
       
   878 
       
   879         # resolve all pending cross-references
       
   880         self.resolve_references(doctree, docname, builder)
       
   881 
       
   882         # now, resolve all toctree nodes
       
   883         for toctreenode in doctree.traverse(addnodes.toctree):
       
   884             result = self.resolve_toctree(docname, builder, toctreenode,
       
   885                                           prune=prune_toctrees)
       
   886             if result is None:
       
   887                 toctreenode.replace_self([])
       
   888             else:
       
   889                 toctreenode.replace_self(result)
       
   890 
       
   891         return doctree
       
   892 
       
   893     def resolve_toctree(self, docname, builder, toctree, prune=True, maxdepth=0,
       
   894                         titles_only=False):
       
   895         """
       
   896         Resolve a *toctree* node into individual bullet lists with titles
       
   897         as items, returning None (if no containing titles are found) or
       
   898         a new node.
       
   899 
       
   900         If *prune* is True, the tree is pruned to *maxdepth*, or if that is 0,
       
   901         to the value of the *maxdepth* option on the *toctree* node.
       
   902         If *titles_only* is True, only toplevel document titles will be in the
       
   903         resulting tree.
       
   904         """
       
   905 
       
   906         def _walk_depth(node, depth, maxdepth, titleoverrides):
       
   907             """Utility: Cut a TOC at a specified depth."""
       
   908             for subnode in node.children[:]:
       
   909                 if isinstance(subnode, (addnodes.compact_paragraph, nodes.list_item)):
       
   910                     subnode['classes'].append('toctree-l%d' % (depth-1))
       
   911                     _walk_depth(subnode, depth, maxdepth, titleoverrides)
       
   912                 elif isinstance(subnode, nodes.bullet_list):
       
   913                     if maxdepth > 0 and depth > maxdepth:
       
   914                         subnode.parent.replace(subnode, [])
       
   915                     else:
       
   916                         _walk_depth(subnode, depth+1, maxdepth, titleoverrides)
       
   917 
       
   918         def _entries_from_toctree(toctreenode, separate=False):
       
   919             """Return TOC entries for a toctree node."""
       
   920             includefiles = map(str, toctreenode['includefiles'])
       
   921 
       
   922             entries = []
       
   923             for includefile in includefiles:
       
   924                 try:
       
   925                     toc = self.tocs[includefile].deepcopy()
       
   926                     if not toc.children:
       
   927                         # empty toc means: no titles will show up in the toctree
       
   928                         self.warn(docname, 'toctree contains reference to document '
       
   929                                   '%r that doesn\'t have a title: no link will be '
       
   930                                   'generated' % includefile)
       
   931                 except KeyError:
       
   932                     # this is raised if the included file does not exist
       
   933                     self.warn(docname, 'toctree contains reference to nonexisting '
       
   934                               'document %r' % includefile)
       
   935                 else:
       
   936                     # if titles_only is given, only keep the main title and
       
   937                     # sub-toctrees
       
   938                     if titles_only:
       
   939                         # delete everything but the toplevel title(s) and toctrees
       
   940                         for toplevel in toc:
       
   941                             # nodes with length 1 don't have any children anyway
       
   942                             if len(toplevel) > 1:
       
   943                                 subtoctrees = toplevel.traverse(addnodes.toctree)
       
   944                                 toplevel[1][:] = subtoctrees
       
   945                     # resolve all sub-toctrees
       
   946                     for toctreenode in toc.traverse(addnodes.toctree):
       
   947                         i = toctreenode.parent.index(toctreenode) + 1
       
   948                         for item in _entries_from_toctree(toctreenode):
       
   949                             toctreenode.parent.insert(i, item)
       
   950                             i += 1
       
   951                         toctreenode.parent.remove(toctreenode)
       
   952                     if separate:
       
   953                         entries.append(toc)
       
   954                     else:
       
   955                         entries.extend(toc.children)
       
   956             return entries
       
   957 
       
   958         maxdepth = maxdepth or toctree.get('maxdepth', -1)
       
   959         titleoverrides = toctree.get('includetitles', {})
       
   960 
       
   961         tocentries = _entries_from_toctree(toctree, separate=True)
       
   962         if not tocentries:
       
   963             return None
       
   964 
       
   965         newnode = addnodes.compact_paragraph('', '', *tocentries)
       
   966         newnode['toctree'] = True
       
   967         # prune the tree to maxdepth and replace titles, also set level classes
       
   968         _walk_depth(newnode, 1, prune and maxdepth or 0, titleoverrides)
       
   969         # replace titles, if needed, and set the target paths in the
       
   970         # toctrees (they are not known at TOC generation time)
       
   971         for refnode in newnode.traverse(nodes.reference):
       
   972             refnode['refuri'] = builder.get_relative_uri(
       
   973                 docname, refnode['refuri']) + refnode['anchorname']
       
   974             if titleoverrides and not refnode['anchorname'] \
       
   975                    and refnode['refuri'] in titleoverrides:
       
   976                 newtitle = titleoverrides[refnode['refuri']]
       
   977                 refnode.children = [nodes.Text(newtitle)]
       
   978         return newnode
       
   979 
       
   980     descroles = frozenset(('data', 'exc', 'func', 'class', 'const', 'attr', 'obj',
       
   981                            'meth', 'cfunc', 'cmember', 'cdata', 'ctype', 'cmacro'))
       
   982 
       
   983     def resolve_references(self, doctree, fromdocname, builder):
       
   984         reftarget_roles = set(('token', 'term', 'citation'))
       
   985         # add all custom xref types too
       
   986         reftarget_roles.update(i[0] for i in additional_xref_types.values())
       
   987 
       
   988         for node in doctree.traverse(addnodes.pending_xref):
       
   989             contnode = node[0].deepcopy()
       
   990             newnode = None
       
   991 
       
   992             typ = node['reftype']
       
   993             target = node['reftarget']
       
   994 
       
   995             try:
       
   996                 if typ == 'ref':
       
   997                     if node['refcaption']:
       
   998                         # reference to anonymous label; the reference uses the supplied
       
   999                         # link caption
       
  1000                         docname, labelid = self.anonlabels.get(target, ('',''))
       
  1001                         sectname = node.astext()
       
  1002                         if not docname:
       
  1003                             newnode = doctree.reporter.system_message(
       
  1004                                 2, 'undefined label: %s' % target)
       
  1005                     else:
       
  1006                         # reference to the named label; the final node will contain the
       
  1007                         # section name after the label
       
  1008                         docname, labelid, sectname = self.labels.get(target, ('','',''))
       
  1009                         if not docname:
       
  1010                             newnode = doctree.reporter.system_message(
       
  1011                                 2, 'undefined label: %s -- if you don\'t ' % target +
       
  1012                                 'give a link caption the label must precede a section '
       
  1013                                 'header.')
       
  1014                     if docname:
       
  1015                         newnode = nodes.reference('', '')
       
  1016                         innernode = nodes.emphasis(sectname, sectname)
       
  1017                         if docname == fromdocname:
       
  1018                             newnode['refid'] = labelid
       
  1019                         else:
       
  1020                             # set more info in contnode in case the get_relative_uri call
       
  1021                             # raises NoUri, the builder will then have to resolve these
       
  1022                             contnode = addnodes.pending_xref('')
       
  1023                             contnode['refdocname'] = docname
       
  1024                             contnode['refsectname'] = sectname
       
  1025                             newnode['refuri'] = builder.get_relative_uri(
       
  1026                                 fromdocname, docname)
       
  1027                             if labelid:
       
  1028                                 newnode['refuri'] += '#' + labelid
       
  1029                         newnode.append(innernode)
       
  1030                 elif typ == 'keyword':
       
  1031                     # keywords are referenced by named labels
       
  1032                     docname, labelid, _ = self.labels.get(target, ('','',''))
       
  1033                     if not docname:
       
  1034                         #self.warn(fromdocname, 'unknown keyword: %s' % target)
       
  1035                         newnode = contnode
       
  1036                     else:
       
  1037                         newnode = nodes.reference('', '')
       
  1038                         if docname == fromdocname:
       
  1039                             newnode['refid'] = labelid
       
  1040                         else:
       
  1041                             newnode['refuri'] = builder.get_relative_uri(
       
  1042                                 fromdocname, docname) + '#' + labelid
       
  1043                         newnode.append(contnode)
       
  1044                 elif typ == 'option':
       
  1045                     progname = node['refprogram']
       
  1046                     docname, labelid = self.progoptions.get((progname, target), ('', ''))
       
  1047                     if not docname:
       
  1048                         newnode = contnode
       
  1049                     else:
       
  1050                         newnode = nodes.reference('', '')
       
  1051                         if docname == fromdocname:
       
  1052                             newnode['refid'] = labelid
       
  1053                         else:
       
  1054                             newnode['refuri'] = builder.get_relative_uri(
       
  1055                                 fromdocname, docname) + '#' + labelid
       
  1056                         newnode.append(contnode)
       
  1057                 elif typ in reftarget_roles:
       
  1058                     docname, labelid = self.reftargets.get((typ, target), ('', ''))
       
  1059                     if not docname:
       
  1060                         if typ == 'term':
       
  1061                             self.warn(fromdocname, 'term not in glossary: %s' % target,
       
  1062                                       node.line)
       
  1063                         elif typ == 'citation':
       
  1064                             self.warn(fromdocname, 'citation not found: %s' % target,
       
  1065                                       node.line)
       
  1066                         newnode = contnode
       
  1067                     else:
       
  1068                         newnode = nodes.reference('', '')
       
  1069                         if docname == fromdocname:
       
  1070                             newnode['refid'] = labelid
       
  1071                         else:
       
  1072                             newnode['refuri'] = builder.get_relative_uri(
       
  1073                                 fromdocname, docname, typ) + '#' + labelid
       
  1074                         newnode.append(contnode)
       
  1075                 elif typ == 'mod':
       
  1076                     docname, synopsis, platform, deprecated = \
       
  1077                         self.modules.get(target, ('','','', ''))
       
  1078                     if not docname:
       
  1079                         newnode = builder.app.emit_firstresult('missing-reference',
       
  1080                                                                self, node, contnode)
       
  1081                         if not newnode:
       
  1082                             newnode = contnode
       
  1083                     elif docname == fromdocname:
       
  1084                         # don't link to self
       
  1085                         newnode = contnode
       
  1086                     else:
       
  1087                         newnode = nodes.reference('', '')
       
  1088                         newnode['refuri'] = builder.get_relative_uri(
       
  1089                             fromdocname, docname) + '#module-' + target
       
  1090                         newnode['reftitle'] = '%s%s%s' % (
       
  1091                             (platform and '(%s) ' % platform),
       
  1092                             synopsis, (deprecated and ' (deprecated)' or ''))
       
  1093                         newnode.append(contnode)
       
  1094                 elif typ in self.descroles:
       
  1095                     # "descrefs"
       
  1096                     modname = node['modname']
       
  1097                     clsname = node['classname']
       
  1098                     searchorder = node.hasattr('refspecific') and 1 or 0
       
  1099                     name, desc = self.find_desc(modname, clsname,
       
  1100                                                 target, typ, searchorder)
       
  1101                     if not desc:
       
  1102                         newnode = builder.app.emit_firstresult('missing-reference',
       
  1103                                                                self, node, contnode)
       
  1104                         if not newnode:
       
  1105                             newnode = contnode
       
  1106                     else:
       
  1107                         newnode = nodes.reference('', '')
       
  1108                         if desc[0] == fromdocname:
       
  1109                             newnode['refid'] = name
       
  1110                         else:
       
  1111                             newnode['refuri'] = (
       
  1112                                 builder.get_relative_uri(fromdocname, desc[0])
       
  1113                                 + '#' + name)
       
  1114                         newnode['reftitle'] = name
       
  1115                         newnode.append(contnode)
       
  1116                 else:
       
  1117                     raise RuntimeError('unknown xfileref node encountered: %s' % node)
       
  1118             except NoUri:
       
  1119                 newnode = contnode
       
  1120             if newnode:
       
  1121                 node.replace_self(newnode)
       
  1122 
       
  1123         # allow custom references to be resolved
       
  1124         builder.app.emit('doctree-resolved', doctree, fromdocname)
       
  1125 
       
  1126     def create_index(self, builder, _fixre=re.compile(r'(.*) ([(][^()]*[)])')):
       
  1127         """Create the real index from the collected index entries."""
       
  1128         new = {}
       
  1129 
       
  1130         def add_entry(word, subword, dic=new):
       
  1131             entry = dic.get(word)
       
  1132             if not entry:
       
  1133                 dic[word] = entry = [[], {}]
       
  1134             if subword:
       
  1135                 add_entry(subword, '', dic=entry[1])
       
  1136             else:
       
  1137                 try:
       
  1138                     entry[0].append(builder.get_relative_uri('genindex', fn)
       
  1139                                     + '#' + tid)
       
  1140                 except NoUri:
       
  1141                     pass
       
  1142 
       
  1143         for fn, entries in self.indexentries.iteritems():
       
  1144             # new entry types must be listed in directives/other.py!
       
  1145             for type, string, tid, alias in entries:
       
  1146                 if type == 'single':
       
  1147                     try:
       
  1148                         entry, subentry = string.split(';', 1)
       
  1149                     except ValueError:
       
  1150                         entry, subentry = string, ''
       
  1151                     if not entry:
       
  1152                         self.warn(fn, 'invalid index entry %r' % string)
       
  1153                         continue
       
  1154                     add_entry(entry.strip(), subentry.strip())
       
  1155                 elif type == 'pair':
       
  1156                     try:
       
  1157                         first, second = map(lambda x: x.strip(),
       
  1158                                             string.split(';', 1))
       
  1159                         if not first or not second:
       
  1160                             raise ValueError
       
  1161                     except ValueError:
       
  1162                         self.warn(fn, 'invalid pair index entry %r' % string)
       
  1163                         continue
       
  1164                     add_entry(first, second)
       
  1165                     add_entry(second, first)
       
  1166                 elif type == 'triple':
       
  1167                     try:
       
  1168                         first, second, third = map(lambda x: x.strip(),
       
  1169                                                    string.split(';', 2))
       
  1170                         if not first or not second or not third:
       
  1171                             raise ValueError
       
  1172                     except ValueError:
       
  1173                         self.warn(fn, 'invalid triple index entry %r' % string)
       
  1174                         continue
       
  1175                     add_entry(first, second+' '+third)
       
  1176                     add_entry(second, third+', '+first)
       
  1177                     add_entry(third, first+' '+second)
       
  1178                 else:
       
  1179                     self.warn(fn, 'unknown index entry type %r' % type)
       
  1180 
       
  1181         newlist = new.items()
       
  1182         newlist.sort(key=lambda t: t[0].lower())
       
  1183 
       
  1184         # fixup entries: transform
       
  1185         #   func() (in module foo)
       
  1186         #   func() (in module bar)
       
  1187         # into
       
  1188         #   func()
       
  1189         #     (in module foo)
       
  1190         #     (in module bar)
       
  1191         oldkey = ''
       
  1192         oldsubitems = None
       
  1193         i = 0
       
  1194         while i < len(newlist):
       
  1195             key, (targets, subitems) = newlist[i]
       
  1196             # cannot move if it hassubitems; structure gets too complex
       
  1197             if not subitems:
       
  1198                 m = _fixre.match(key)
       
  1199                 if m:
       
  1200                     if oldkey == m.group(1):
       
  1201                         # prefixes match: add entry as subitem of the previous entry
       
  1202                         oldsubitems.setdefault(m.group(2), [[], {}])[0].extend(targets)
       
  1203                         del newlist[i]
       
  1204                         continue
       
  1205                     oldkey = m.group(1)
       
  1206                 else:
       
  1207                     oldkey = key
       
  1208             oldsubitems = subitems
       
  1209             i += 1
       
  1210 
       
  1211         # group the entries by letter
       
  1212         def keyfunc((k, v), ltrs=uppercase+'_'):
       
  1213             # hack: mutate the subitems dicts to a list in the keyfunc
       
  1214             v[1] = sorted((si, se) for (si, (se, void)) in v[1].iteritems())
       
  1215             # now calculate the key
       
  1216             letter = k[0].upper()
       
  1217             if letter in ltrs:
       
  1218                 return letter
       
  1219             else:
       
  1220                 # get all other symbols under one heading
       
  1221                 return 'Symbols'
       
  1222         return [(key, list(group)) for (key, group) in groupby(newlist, keyfunc)]
       
  1223 
       
  1224     def collect_relations(self):
       
  1225         relations = {}
       
  1226         getinc = self.toctree_includes.get
       
  1227         def collect(parents, docname, previous, next):
       
  1228             includes = getinc(docname)
       
  1229             # previous
       
  1230             if not previous:
       
  1231                 # if no previous sibling, go to parent
       
  1232                 previous = parents[0][0]
       
  1233             else:
       
  1234                 # else, go to previous sibling, or if it has children, to
       
  1235                 # the last of its children, or if that has children, to the
       
  1236                 # last of those, and so forth
       
  1237                 while 1:
       
  1238                     previncs = getinc(previous)
       
  1239                     if previncs:
       
  1240                         previous = previncs[-1]
       
  1241                     else:
       
  1242                         break
       
  1243             # next
       
  1244             if includes:
       
  1245                 # if it has children, go to first of them
       
  1246                 next = includes[0]
       
  1247             elif next:
       
  1248                 # else, if next sibling, go to it
       
  1249                 pass
       
  1250             else:
       
  1251                 # else, go to the next sibling of the parent, if present,
       
  1252                 # else the grandparent's sibling, if present, and so forth
       
  1253                 for parname, parindex in parents:
       
  1254                     parincs = getinc(parname)
       
  1255                     if parincs and parindex + 1 < len(parincs):
       
  1256                         next = parincs[parindex+1]
       
  1257                         break
       
  1258                 # else it will stay None
       
  1259             # same for children
       
  1260             if includes:
       
  1261                 for subindex, args in enumerate(izip(includes, [None] + includes,
       
  1262                                                      includes[1:] + [None])):
       
  1263                     collect([(docname, subindex)] + parents, *args)
       
  1264             relations[docname] = [parents[0][0], previous, next]
       
  1265         collect([(None, 0)], self.config.master_doc, None, None)
       
  1266         return relations
       
  1267 
       
  1268     def check_consistency(self):
       
  1269         """Do consistency checks."""
       
  1270 
       
  1271         for docname in sorted(self.all_docs):
       
  1272             if docname not in self.files_to_rebuild:
       
  1273                 if docname == self.config.master_doc:
       
  1274                     # the master file is not included anywhere ;)
       
  1275                     continue
       
  1276                 self.warn(docname, 'document isn\'t included in any toctree')
       
  1277 
       
  1278     # --------- QUERYING -------------------------------------------------------
       
  1279 
       
  1280     def find_desc(self, modname, classname, name, type, searchorder=0):
       
  1281         """Find a description node matching "name", perhaps using
       
  1282            the given module and/or classname."""
       
  1283         # skip parens
       
  1284         if name[-2:] == '()':
       
  1285             name = name[:-2]
       
  1286 
       
  1287         if not name:
       
  1288             return None, None
       
  1289 
       
  1290         # don't add module and class names for C things
       
  1291         if type[0] == 'c' and type not in ('class', 'const'):
       
  1292             # skip trailing star and whitespace
       
  1293             name = name.rstrip(' *')
       
  1294             if name in self.descrefs and self.descrefs[name][1][0] == 'c':
       
  1295                 return name, self.descrefs[name]
       
  1296             return None, None
       
  1297 
       
  1298         newname = None
       
  1299         if searchorder == 1:
       
  1300             if modname and classname and \
       
  1301                    modname + '.' + classname + '.' + name in self.descrefs:
       
  1302                 newname = modname + '.' + classname + '.' + name
       
  1303             elif modname and modname + '.' + name in self.descrefs:
       
  1304                 newname = modname + '.' + name
       
  1305             elif name in self.descrefs:
       
  1306                 newname = name
       
  1307         else:
       
  1308             if name in self.descrefs:
       
  1309                 newname = name
       
  1310             elif modname and modname + '.' + name in self.descrefs:
       
  1311                 newname = modname + '.' + name
       
  1312             elif modname and classname and \
       
  1313                      modname + '.' + classname + '.' + name in self.descrefs:
       
  1314                 newname = modname + '.' + classname + '.' + name
       
  1315             # special case: builtin exceptions have module "exceptions" set
       
  1316             elif type == 'exc' and '.' not in name and \
       
  1317                  'exceptions.' + name in self.descrefs:
       
  1318                 newname = 'exceptions.' + name
       
  1319             # special case: object methods
       
  1320             elif type in ('func', 'meth') and '.' not in name and \
       
  1321                  'object.' + name in self.descrefs:
       
  1322                 newname = 'object.' + name
       
  1323         if newname is None:
       
  1324             return None, None
       
  1325         return newname, self.descrefs[newname]
       
  1326 
       
  1327     def find_keyword(self, keyword, avoid_fuzzy=False, cutoff=0.6, n=20):
       
  1328         """
       
  1329         Find keyword matches for a keyword. If there's an exact match, just return
       
  1330         it, else return a list of fuzzy matches if avoid_fuzzy isn't True.
       
  1331 
       
  1332         Keywords searched are: first modules, then descrefs.
       
  1333 
       
  1334         Returns: None if nothing found
       
  1335                  (type, docname, anchorname) if exact match found
       
  1336                  list of (quality, type, docname, anchorname, description) if fuzzy
       
  1337         """
       
  1338 
       
  1339         if keyword in self.modules:
       
  1340             docname, title, system, deprecated = self.modules[keyword]
       
  1341             return 'module', docname, 'module-' + keyword
       
  1342         if keyword in self.descrefs:
       
  1343             docname, ref_type = self.descrefs[keyword]
       
  1344             return ref_type, docname, keyword
       
  1345         # special cases
       
  1346         if '.' not in keyword:
       
  1347             # exceptions are documented in the exceptions module
       
  1348             if 'exceptions.'+keyword in self.descrefs:
       
  1349                 docname, ref_type = self.descrefs['exceptions.'+keyword]
       
  1350                 return ref_type, docname, 'exceptions.'+keyword
       
  1351             # special methods are documented as object methods
       
  1352             if 'object.'+keyword in self.descrefs:
       
  1353                 docname, ref_type = self.descrefs['object.'+keyword]
       
  1354                 return ref_type, docname, 'object.'+keyword
       
  1355 
       
  1356         if avoid_fuzzy:
       
  1357             return
       
  1358 
       
  1359         # find fuzzy matches
       
  1360         s = difflib.SequenceMatcher()
       
  1361         s.set_seq2(keyword.lower())
       
  1362 
       
  1363         def possibilities():
       
  1364             for title, (fn, desc, _, _) in self.modules.iteritems():
       
  1365                 yield ('module', fn, 'module-'+title, desc)
       
  1366             for title, (fn, desctype) in self.descrefs.iteritems():
       
  1367                 yield (desctype, fn, title, '')
       
  1368 
       
  1369         def dotsearch(string):
       
  1370             parts = string.lower().split('.')
       
  1371             for idx in xrange(0, len(parts)):
       
  1372                 yield '.'.join(parts[idx:])
       
  1373 
       
  1374         result = []
       
  1375         for type, docname, title, desc in possibilities():
       
  1376             best_res = 0
       
  1377             for part in dotsearch(title):
       
  1378                 s.set_seq1(part)
       
  1379                 if s.real_quick_ratio() >= cutoff and \
       
  1380                    s.quick_ratio() >= cutoff and \
       
  1381                    s.ratio() >= cutoff and \
       
  1382                    s.ratio() > best_res:
       
  1383                     best_res = s.ratio()
       
  1384             if best_res:
       
  1385                 result.append((best_res, type, docname, title, desc))
       
  1386 
       
  1387         return heapq.nlargest(n, result)