summaryrefslogtreecommitdiffstats
path: root/common
diff options
context:
space:
mode:
authorChris Johns <chrisj@rtems.org>2017-08-07 21:58:52 +1000
committerChris Johns <chrisj@rtems.org>2017-08-07 21:58:52 +1000
commitaa4f8e2e436d6c49e1524a4a3fb164b28632d894 (patch)
tree5b7d3e70c2d5640495281b2665212f07bb41b251 /common
parentbbdf66cd585a99c8809b3a234a1fdb84ce986f58 (diff)
downloadrtems-docs-aa4f8e2e436d6c49e1524a4a3fb164b28632d894.tar.bz2
Add the sphinxcontrib.bibtex extension to the repo.
Diffstat (limited to 'common')
-rw-r--r--common/sphinxcontrib/__init__.py13
-rw-r--r--common/sphinxcontrib/bibtex/__init__.py148
-rw-r--r--common/sphinxcontrib/bibtex/cache.py406
-rw-r--r--common/sphinxcontrib/bibtex/directives.py221
-rw-r--r--common/sphinxcontrib/bibtex/nodes.py17
-rw-r--r--common/sphinxcontrib/bibtex/roles.py43
-rw-r--r--common/sphinxcontrib/bibtex/transforms.py127
-rw-r--r--common/waf.py1
8 files changed, 975 insertions, 1 deletions
diff --git a/common/sphinxcontrib/__init__.py b/common/sphinxcontrib/__init__.py
new file mode 100644
index 0000000..35d34fc
--- /dev/null
+++ b/common/sphinxcontrib/__init__.py
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+"""
+ sphinxcontrib
+ ~~~~~~~~~~~~~
+
+ This package is a namespace package that contains all extensions
+ distributed in the ``sphinx-contrib`` distribution.
+
+ :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+__import__('pkg_resources').declare_namespace(__name__)
diff --git a/common/sphinxcontrib/bibtex/__init__.py b/common/sphinxcontrib/bibtex/__init__.py
new file mode 100644
index 0000000..d79e688
--- /dev/null
+++ b/common/sphinxcontrib/bibtex/__init__.py
@@ -0,0 +1,148 @@
+# -*- coding: utf-8 -*-
+"""
+ Sphinx Interface
+ ~~~~~~~~~~~~~~~~
+
+ .. autofunction:: setup
+ .. autofunction:: init_bibtex_cache
+ .. autofunction:: purge_bibtex_cache
+ .. autofunction:: process_citations
+ .. autofunction:: process_citation_references
+ .. autofunction:: check_duplicate_labels
+"""
+
+import docutils.nodes
+import docutils.parsers.rst
+from sphinxcontrib.bibtex.cache import Cache
+from sphinxcontrib.bibtex.nodes import bibliography
+from sphinxcontrib.bibtex.roles import CiteRole
+from sphinxcontrib.bibtex.directives import BibliographyDirective
+from sphinxcontrib.bibtex.transforms import BibliographyTransform
+import six
+
+
+def init_bibtex_cache(app):
+ """Create ``app.env.bibtex_cache`` if it does not exist yet.
+
+ :param app: The sphinx application.
+ :type app: :class:`sphinx.application.Sphinx`
+ """
+ if not hasattr(app.env, "bibtex_cache"):
+ app.env.bibtex_cache = Cache()
+
+
+def purge_bibtex_cache(app, env, docname):
+ """Remove all information related to *docname* from the cache.
+
+ :param app: The sphinx application.
+ :type app: :class:`sphinx.application.Sphinx`
+ :param env: The sphinx build environment.
+ :type env: :class:`sphinx.environment.BuildEnvironment`
+ """
+ env.bibtex_cache.purge(docname)
+
+
+def process_citations(app, doctree, docname):
+ """Replace labels of citation nodes by actual labels.
+
+ :param app: The sphinx application.
+ :type app: :class:`sphinx.application.Sphinx`
+ :param doctree: The document tree.
+ :type doctree: :class:`docutils.nodes.document`
+ :param docname: The document name.
+ :type docname: :class:`str`
+ """
+ for node in doctree.traverse(docutils.nodes.citation):
+ key = node[0].astext()
+ try:
+ label = app.env.bibtex_cache.get_label_from_key(key)
+ except KeyError:
+ app.warn("could not relabel citation [%s]" % key)
+ else:
+ node[0] = docutils.nodes.label('', label)
+
+
+def process_citation_references(app, doctree, docname):
+ """Replace text of citation reference nodes by actual labels.
+
+ :param app: The sphinx application.
+ :type app: :class:`sphinx.application.Sphinx`
+ :param doctree: The document tree.
+ :type doctree: :class:`docutils.nodes.document`
+ :param docname: The document name.
+ :type docname: :class:`str`
+ """
+ # sphinx has already turned citation_reference nodes
+ # into reference nodes, so iterate over reference nodes
+ for node in doctree.traverse(docutils.nodes.reference):
+ # exclude sphinx [source] labels
+ if isinstance(node[0], docutils.nodes.Element):
+ if 'viewcode-link' in node[0]['classes']:
+ continue
+ text = node[0].astext()
+ if text.startswith('[') and text.endswith(']'):
+ key = text[1:-1]
+ try:
+ label = app.env.bibtex_cache.get_label_from_key(key)
+ except KeyError:
+ app.warn("could not relabel citation reference [%s]" % key)
+ else:
+ node[0] = docutils.nodes.Text('[' + label + ']')
+
+
+def check_duplicate_labels(app, env):
+ """Check and warn about duplicate citation labels.
+
+ :param app: The sphinx application.
+ :type app: :class:`sphinx.application.Sphinx`
+ :param env: The sphinx build environment.
+ :type env: :class:`sphinx.environment.BuildEnvironment`
+ """
+ label_to_key = {}
+ for info in env.bibtex_cache.get_all_bibliography_caches():
+ for key, label in six.iteritems(info.labels):
+ if label in label_to_key:
+ app.warn(
+ "duplicate label for keys %s and %s"
+ % (key, label_to_key[label]))
+ else:
+ label_to_key[label] = key
+
+
+def setup(app):
+ """Set up the bibtex extension:
+
+ * register config values
+ * register directives
+ * register nodes
+ * register roles
+ * register transforms
+ * connect events to functions
+
+ :param app: The sphinx application.
+ :type app: :class:`sphinx.application.Sphinx`
+ """
+
+ app.add_config_value("bibtex_default_style", "alpha", "html")
+ app.connect("builder-inited", init_bibtex_cache)
+ app.connect("doctree-resolved", process_citations)
+ app.connect("doctree-resolved", process_citation_references)
+ app.connect("env-purge-doc", purge_bibtex_cache)
+ app.connect("env-updated", check_duplicate_labels)
+
+ # docutils keeps state around during testing, so to avoid spurious
+ # warnings, we detect here whether the directives have already been
+ # registered... very ugly hack but no better solution so far
+ _directives = docutils.parsers.rst.directives._directives
+ if "bibliography" not in _directives:
+ app.add_directive("bibliography", BibliographyDirective)
+ app.add_role("cite", CiteRole())
+ app.add_node(bibliography)
+ app.add_transform(BibliographyTransform)
+ else:
+ assert _directives["bibliography"] is BibliographyDirective
+
+ # Parallel read is not safe at the moment: in the current design,
+ # the document that contains references must be read last for all
+ # references to be resolved.
+ return {'parallel_read_safe': False}
diff --git a/common/sphinxcontrib/bibtex/cache.py b/common/sphinxcontrib/bibtex/cache.py
new file mode 100644
index 0000000..aa9064f
--- /dev/null
+++ b/common/sphinxcontrib/bibtex/cache.py
@@ -0,0 +1,406 @@
+# -*- coding: utf-8 -*-
+"""
+ Cached Information
+ ~~~~~~~~~~~~~~~~~~
+
+ Classes and methods to maintain any information that is stored
+ outside the doctree.
+
+ .. autoclass:: Cache
+ :members:
+
+ .. autoclass:: BibfileCache
+ :members:
+
+ .. autoclass:: BibliographyCache
+ :members:
+"""
+
+import six
+try: # pragma: no cover
+ from collections import OrderedDict
+except ImportError: # pragma: no cover
+ from ordereddict import OrderedDict
+import ast
+import collections
+import copy
+from oset import oset
+import re
+
+
+def _raise_invalid_node(node):
+ """Helper method to raise an exception when an invalid node is
+ visited.
+ """
+ raise ValueError("invalid node %s in filter expression" % node)
+
+
+class _FilterVisitor(ast.NodeVisitor):
+
+ """Visit the abstract syntax tree of a parsed filter expression."""
+
+ entry = None
+ """The bibliographic entry to which the filter must be applied."""
+
+ cited_docnames = False
+ """The documents where the entry is cited (empty if not cited)."""
+
+ def __init__(self, entry, docname, cited_docnames):
+ self.entry = entry
+ self.docname = docname
+ self.cited_docnames = cited_docnames
+
+ def visit_Module(self, node):
+ if len(node.body) != 1:
+ raise ValueError(
+ "filter expression cannot contain multiple expressions")
+ return self.visit(node.body[0])
+
+ def visit_Expr(self, node):
+ return self.visit(node.value)
+
+ def visit_BoolOp(self, node):
+ outcomes = (self.visit(value) for value in node.values)
+ if isinstance(node.op, ast.And):
+ return all(outcomes)
+ elif isinstance(node.op, ast.Or):
+ return any(outcomes)
+ else: # pragma: no cover
+ # there are no other boolean operators
+ # so this code should never execute
+ assert False, "unexpected boolean operator %s" % node.op
+
+ def visit_UnaryOp(self, node):
+ if isinstance(node.op, ast.Not):
+ return not self.visit(node.operand)
+ else:
+ _raise_invalid_node(node)
+
+ def visit_BinOp(self, node):
+ left = self.visit(node.left)
+ op = node.op
+ right = self.visit(node.right)
+ if isinstance(op, ast.Mod):
+ # modulo operator is used for regular expression matching
+ if not isinstance(left, six.string_types):
+ raise ValueError(
+ "expected a string on left side of %s" % node.op)
+ if not isinstance(right, six.string_types):
+ raise ValueError(
+ "expected a string on right side of %s" % node.op)
+ return re.search(right, left, re.IGNORECASE)
+ elif isinstance(op, ast.BitOr):
+ return left | right
+ elif isinstance(op, ast.BitAnd):
+ return left & right
+ else:
+ _raise_invalid_node(node)
+
+ def visit_Compare(self, node):
+ # keep it simple: binary comparators only
+ if len(node.ops) != 1:
+ raise ValueError("syntax for multiple comparators not supported")
+ left = self.visit(node.left)
+ op = node.ops[0]
+ right = self.visit(node.comparators[0])
+ if isinstance(op, ast.Eq):
+ return left == right
+ elif isinstance(op, ast.NotEq):
+ return left != right
+ elif isinstance(op, ast.Lt):
+ return left < right
+ elif isinstance(op, ast.LtE):
+ return left <= right
+ elif isinstance(op, ast.Gt):
+ return left > right
+ elif isinstance(op, ast.GtE):
+ return left >= right
+ elif isinstance(op, ast.In):
+ return left in right
+ elif isinstance(op, ast.NotIn):
+ return left not in right
+ else:
+ # not used currently: ast.Is | ast.IsNot
+ _raise_invalid_node(op)
+
+ def visit_Name(self, node):
+ """Calculate the value of the given identifier."""
+ id_ = node.id
+ if id_ == 'type':
+ return self.entry.type.lower()
+ elif id_ == 'key':
+ return self.entry.key.lower()
+ elif id_ == 'cited':
+ return bool(self.cited_docnames)
+ elif id_ == 'docname':
+ return self.docname
+ elif id_ == 'docnames':
+ return self.cited_docnames
+ elif id_ == 'True':
+ return True
+ elif id_ == 'False':
+ return False
+ elif id_ == 'author' or id_ == 'editor':
+ if id_ in self.entry.persons:
+ return u' and '.join(
+ six.text_type(person) # XXX needs fix in pybtex?
+ for person in self.entry.persons[id_])
+ else:
+ return u''
+ else:
+ return self.entry.fields.get(id_, "")
+
+ def visit_Set(self, node):
+ return frozenset(self.visit(elt) for elt in node.elts)
+
+ def visit_Str(self, node):
+ return node.s
+
+ # NameConstant is Python 3.4 only so do not insist on coverage
+ def visit_NameConstant(self, node): # pragma: no cover
+ return node.value
+
+ def generic_visit(self, node):
+ _raise_invalid_node(node)
+
+
+class Cache:
+
+ """Global bibtex extension information cache. Stored in
+ ``app.env.bibtex_cache``, so must be picklable.
+ """
+
+ bibfiles = None
+ """A :class:`dict` mapping .bib file names (relative to the top
+ source folder) to :class:`BibfileCache` instances.
+ """
+
+ _bibliographies = None
+ """Each bibliography directive is assigned an id of the form
+ bibtex-bibliography-xxx. This :class:`dict` maps each docname
+ to another :class:`dict` which maps each id
+ to information about the bibliography directive,
+ :class:`BibliographyCache`. We need to store this extra
+ information separately because it cannot be stored in the
+ :class:`~sphinxcontrib.bibtex.nodes.bibliography` nodes
+ themselves.
+ """
+
+ _cited = None
+ """A :class:`dict` mapping each docname to a :class:`set` of
+ citation keys.
+ """
+
+ _enum_count = None
+ """A :class:`dict` mapping each docname to an :class:`int`
+ representing the current bibliography enumeration counter.
+ """
+
+ def __init__(self):
+
+ self.bibfiles = {}
+ self._bibliographies = collections.defaultdict(dict)
+ self._cited = collections.defaultdict(oset)
+ self._enum_count = {}
+
+ def purge(self, docname):
+ """Remove all information related to *docname*.
+
+ :param docname: The document name.
+ :type docname: :class:`str`
+ """
+ self._bibliographies.pop(docname, None)
+ self._cited.pop(docname, None)
+ self._enum_count.pop(docname, None)
+
+ def inc_enum_count(self, docname):
+ """Increment enumeration list counter for document *docname*."""
+ self._enum_count[docname] += 1
+
+ def set_enum_count(self, docname, value):
+ """Set enumeration list counter for document *docname* to *value*."""
+ self._enum_count[docname] = value
+
+ def get_enum_count(self, docname):
+ """Get enumeration list counter for document *docname*."""
+ return self._enum_count[docname]
+
+ def add_cited(self, key, docname):
+ """Add the given *key* to the set of cited keys for
+ *docname*.
+
+ :param key: The citation key.
+ :type key: :class:`str`
+ :param docname: The document name.
+ :type docname: :class:`str`
+ """
+ self._cited[docname].add(key)
+
+ def get_cited_docnames(self, key):
+ """Return the *docnames* from which the given *key* is cited.
+
+ :param key: The citation key.
+ :type key: :class:`str`
+ """
+ return frozenset([
+ docname for docname, keys in six.iteritems(self._cited)
+ if key in keys])
+
+ def get_label_from_key(self, key):
+ """Return label for the given key."""
+ for bibcache in self.get_all_bibliography_caches():
+ if key in bibcache.labels:
+ return bibcache.labels[key]
+ else:
+ raise KeyError("%s not found" % key)
+
+ def get_all_cited_keys(self):
+ """Yield all citation keys, sorted first by document
+ (alphabetical), then by citation order in the document.
+ """
+ for docname in sorted(self._cited):
+ for key in self._cited[docname]:
+ yield key
+
+ def set_bibliography_cache(self, docname, id_, bibcache):
+ """Register *bibcache* (:class:`BibliographyCache`)
+ with id *id_* for document *docname*.
+ """
+ assert id_ not in self._bibliographies[docname]
+ self._bibliographies[docname][id_] = bibcache
+
+ def get_bibliography_cache(self, docname, id_):
+ """Return :class:`BibliographyCache` with id *id_* in
+ document *docname*.
+ """
+ return self._bibliographies[docname][id_]
+
+ def get_all_bibliography_caches(self):
+ """Return all bibliography caches."""
+ for bibcaches in six.itervalues(self._bibliographies):
+ for bibcache in six.itervalues(bibcaches):
+ yield bibcache
+
+ def _get_bibliography_entries(self, docname, id_, warn):
+ """Return filtered bibliography entries, sorted by occurence
+ in the bib file.
+ """
+ # get the information of this bibliography node
+ bibcache = self.get_bibliography_cache(docname=docname, id_=id_)
+ # generate entries
+ for bibfile in bibcache.bibfiles:
+ data = self.bibfiles[bibfile].data
+ for entry in six.itervalues(data.entries):
+ # beware: the prefix is not stored in the data
+ # to allow reusing the data for multiple bibliographies
+ cited_docnames = self.get_cited_docnames(
+ bibcache.keyprefix + entry.key)
+ visitor = _FilterVisitor(
+ entry=entry,
+ docname=docname,
+ cited_docnames=cited_docnames)
+ try:
+ success = visitor.visit(bibcache.filter_)
+ except ValueError as err:
+ warn("syntax error in :filter: expression; %s" % err)
+ # recover by falling back to the default
+ success = bool(cited_docnames)
+ if success:
+ # entries are modified in an unpickable way
+ # when formatting, so fetch a deep copy
+ # and return this copy with prefixed key
+ # we do not deep copy entry.collection because that
+ # consumes enormous amounts of memory
+ entry.collection = None
+ entry2 = copy.deepcopy(entry)
+ entry2.key = bibcache.keyprefix + entry.key
+ entry2.collection = data
+ entry.collection = data
+ yield entry2
+
+ def get_bibliography_entries(self, docname, id_, warn):
+ """Return filtered bibliography entries, sorted by citation order."""
+ # get entries, ordered by bib file occurrence
+ entries = OrderedDict(
+ (entry.key, entry) for entry in
+ self._get_bibliography_entries(
+ docname=docname, id_=id_, warn=warn))
+ # order entries according to which were cited first
+ # first, we add all keys that were cited
+ # then, we add all remaining keys
+ sorted_entries = []
+ for key in self.get_all_cited_keys():
+ try:
+ entry = entries.pop(key)
+ except KeyError:
+ pass
+ else:
+ sorted_entries.append(entry)
+ sorted_entries += six.itervalues(entries)
+ return sorted_entries
+
+
+class BibfileCache(collections.namedtuple('BibfileCache', 'mtime data')):
+
+ """Contains information about a parsed .bib file.
+
+ .. attribute:: mtime
+
+ A :class:`float` representing the modification time of the .bib
+ file when it was last parsed.
+
+ .. attribute:: data
+
+ A :class:`pybtex.database.BibliographyData` containing the
+ parsed .bib file.
+
+ """
+
+
+class BibliographyCache(collections.namedtuple(
+ 'BibliographyCache',
+ """bibfiles style encoding
+list_ enumtype start labels labelprefix
+filter_ curly_bracket_strip keyprefix
+""")):
+
+ """Contains information about a bibliography directive.
+
+ .. attribute:: bibfiles
+
+ A :class:`list` of :class:`str`\\ s containing the .bib file
+ names (relative to the top source folder) that contain the
+ references.
+
+ .. attribute:: style
+
+ The bibtex style.
+
+ .. attribute:: list_
+
+ The list type.
+
+ .. attribute:: enumtype
+
+ The sequence type (only used for enumerated lists).
+
+ .. attribute:: start
+
+ The first ordinal of the sequence (only used for enumerated lists).
+
+ .. attribute:: labels
+
+ Maps citation keys to their final labels.
+
+ .. attribute:: labelprefix
+
+ This bibliography's string prefix for pybtex generated labels.
+
+ .. attribute:: keyprefix
+
+ This bibliography's string prefix for citation keys.
+
+ .. attribute:: filter_
+
+ An :class:`ast.AST` node, containing the parsed filter expression.
+ """
diff --git a/common/sphinxcontrib/bibtex/directives.py b/common/sphinxcontrib/bibtex/directives.py
new file mode 100644
index 0000000..af8e9db
--- /dev/null
+++ b/common/sphinxcontrib/bibtex/directives.py
@@ -0,0 +1,221 @@
+"""
+ New Doctree Directives
+ ~~~~~~~~~~~~~~~~~~~~~~
+
+ .. autoclass:: BibliographyDirective
+
+ .. automethod:: run
+ .. automethod:: process_bibfile
+ .. automethod:: update_bibfile_cache
+ .. automethod:: parse_bibfile
+
+ .. autofunction:: process_start_option
+"""
+
+import ast # parse(), used for filter
+import os.path # getmtime()
+
+from docutils.parsers.rst import directives # for Directive.option_spec
+from sphinx.util.compat import Directive
+from sphinx.util.console import bold, standout
+
+from pybtex.database.input import bibtex
+from pybtex.database import BibliographyData
+
+from sphinxcontrib.bibtex.cache import BibliographyCache, BibfileCache
+from sphinxcontrib.bibtex.nodes import bibliography
+
+# register the latex codec
+import latexcodec # noqa
+
+
+def process_start_option(value):
+ """Process and validate the start option value
+ of a :rst:dir:`bibliography` directive.
+ If *value* is ``continue`` then this function returns -1,
+ otherwise *value* is converted into a positive integer.
+ """
+ if value == "continue":
+ return -1
+ else:
+ return directives.positive_int(value)
+
+
+class BibliographyDirective(Directive):
+
+ """Class for processing the :rst:dir:`bibliography` directive.
+
+ Parses the bibliography files, and produces a
+ :class:`~sphinxcontrib.bibtex.nodes.bibliography` node.
+
+ .. seealso::
+
+ Further processing of the resulting
+ :class:`~sphinxcontrib.bibtex.nodes.bibliography` node is done
+ by
+ :class:`~sphinxcontrib.bibtex.transforms.BibliographyTransform`.
+ """
+
+ required_arguments = 1
+ optional_arguments = 0
+ final_argument_whitespace = True
+ has_content = False
+ option_spec = {
+ 'cited': directives.flag,
+ 'notcited': directives.flag,
+ 'all': directives.flag,
+ 'filter': directives.unchanged,
+ 'style': directives.unchanged,
+ 'list': directives.unchanged,
+ 'enumtype': directives.unchanged,
+ 'start': process_start_option,
+ 'encoding': directives.encoding,
+ 'disable-curly-bracket-strip': directives.flag,
+ 'labelprefix': directives.unchanged,
+ 'keyprefix': directives.unchanged,
+ }
+
+ def run(self):
+ """Process .bib files, set file dependencies, and create a
+ node that is to be transformed to the entries of the
+ bibliography.
+ """
+ env = self.state.document.settings.env
+ # create id and cache for this node
+ # this id will be stored with the node
+ # and is used to look up additional data in env.bibtex_cache
+ # (implementation note: new_serialno only guarantees unique
+ # ids within a single document, but we need the id to be
+ # unique across all documents, so we also include the docname
+ # in the id)
+ id_ = 'bibtex-bibliography-%s-%s' % (
+ env.docname, env.new_serialno('bibtex'))
+ if "filter" in self.options:
+ if "all" in self.options:
+ env.app.warn(standout(":filter: overrides :all:"))
+ if "notcited" in self.options:
+ env.app.warn(standout(":filter: overrides :notcited:"))
+ if "cited" in self.options:
+ env.app.warn(standout(":filter: overrides :cited:"))
+ try:
+ filter_ = ast.parse(self.options["filter"])
+ except SyntaxError:
+ env.app.warn(
+ standout("syntax error in :filter: expression") +
+ " (" + self.options["filter"] + "); "
+ "the option will be ignored"
+ )
+ filter_ = ast.parse("cited")
+ elif "all" in self.options:
+ filter_ = ast.parse("True")
+ elif "notcited" in self.options:
+ filter_ = ast.parse("not cited")
+ else:
+ # the default filter: include only cited entries
+ filter_ = ast.parse("cited")
+ bibcache = BibliographyCache(
+ list_=self.options.get("list", "citation"),
+ enumtype=self.options.get("enumtype", "arabic"),
+ start=self.options.get("start", 1),
+ style=self.options.get(
+ "style", env.app.config.bibtex_default_style),
+ filter_=filter_,
+ encoding=self.options.get(
+ 'encoding',
+ 'latex+' + self.state.document.settings.input_encoding),
+ curly_bracket_strip=(
+ 'disable-curly-bracket-strip' not in self.options),
+ labelprefix=self.options.get("labelprefix", ""),
+ keyprefix=self.options.get("keyprefix", ""),
+ labels={},
+ bibfiles=[],
+ )
+ if (bibcache.list_ not in set(["bullet", "enumerated", "citation"])):
+ env.app.warn(
+ "unknown bibliography list type '{0}'.".format(bibcache.list_))
+ for bibfile in self.arguments[0].split():
+ # convert to normalized absolute path to ensure that the same file
+ # only occurs once in the cache
+ bibfile = os.path.normpath(env.relfn2path(bibfile.strip())[1])
+ self.process_bibfile(bibfile, bibcache.encoding)
+ env.note_dependency(bibfile)
+ bibcache.bibfiles.append(bibfile)
+ env.bibtex_cache.set_bibliography_cache(env.docname, id_, bibcache)
+ return [bibliography('', ids=[id_])]
+
+ def parse_bibfile(self, bibfile, encoding):
+ """Parse *bibfile*, and return parsed data.
+
+ :param bibfile: The bib file name.
+ :type bibfile: ``str``
+ :return: The parsed bibliography data.
+ :rtype: :class:`pybtex.database.BibliographyData`
+ """
+ app = self.state.document.settings.env.app
+ parser = bibtex.Parser(encoding)
+ app.info(
+ bold("parsing bibtex file {0}... ".format(bibfile)), nonl=True)
+ parser.parse_file(bibfile)
+ app.info("parsed {0} entries"
+ .format(len(parser.data.entries)))
+ return parser.data
+
+ def update_bibfile_cache(self, bibfile, mtime, encoding):
+ """Parse *bibfile* (see :meth:`parse_bibfile`), and store the
+ parsed data, along with modification time *mtime*, in the
+ bibtex cache.
+
+ :param bibfile: The bib file name.
+ :type bibfile: ``str``
+ :param mtime: The bib file's modification time.
+ :type mtime: ``float``
+ :return: The parsed bibliography data.
+ :rtype: :class:`pybtex.database.BibliographyData`
+ """
+ data = self.parse_bibfile(bibfile, encoding)
+ env = self.state.document.settings.env
+ env.bibtex_cache.bibfiles[bibfile] = BibfileCache(
+ mtime=mtime,
+ data=data)
+ return data
+
+ def process_bibfile(self, bibfile, encoding):
+ """Check if ``env.bibtex_cache.bibfiles[bibfile]`` is still
+ up to date. If not, parse the *bibfile* (see
+ :meth:`update_bibfile_cache`), and store parsed data in the
+ bibtex cache.
+
+ :param bibfile: The bib file name.
+ :type bibfile: ``str``
+ :return: The parsed bibliography data.
+ :rtype: :class:`pybtex.database.BibliographyData`
+ """
+ env = self.state.document.settings.env
+ cache = env.bibtex_cache.bibfiles
+ # get modification time of bibfile
+ try:
+ mtime = os.path.getmtime(bibfile)
+ except OSError:
+ env.app.warn(
+ standout("could not open bibtex file {0}.".format(bibfile)))
+ cache[bibfile] = BibfileCache( # dummy cache
+ mtime=-float("inf"), data=BibliographyData())
+ return cache[bibfile].data
+ # get cache and check if it is still up to date
+ # if it is not up to date, parse the bibtex file
+ # and store it in the cache
+ env.app.info(
+ bold("checking for {0} in bibtex cache... ".format(bibfile)),
+ nonl=True)
+ try:
+ bibfile_cache = cache[bibfile]
+ except KeyError:
+ env.app.info("not found")
+ self.update_bibfile_cache(bibfile, mtime, encoding)
+ else:
+ if mtime != bibfile_cache.mtime:
+ env.app.info("out of date")
+ self.update_bibfile_cache(bibfile, mtime, encoding)
+ else:
+ env.app.info('up to date')
+ return cache[bibfile].data
diff --git a/common/sphinxcontrib/bibtex/nodes.py b/common/sphinxcontrib/bibtex/nodes.py
new file mode 100644
index 0000000..426aed9
--- /dev/null
+++ b/common/sphinxcontrib/bibtex/nodes.py
@@ -0,0 +1,17 @@
+"""
+ New Doctree Nodes
+ ~~~~~~~~~~~~~~~~~
+
+ .. autoclass:: bibliography
+"""
+
+from docutils import nodes
+
+
+class bibliography(nodes.General, nodes.Element):
+
+ """Node for representing a bibliography. Replaced by a list of
+ citations by
+ :class:`~sphinxcontrib.bibtex.transforms.BibliographyTransform`.
+ """
+ pass
diff --git a/common/sphinxcontrib/bibtex/roles.py b/common/sphinxcontrib/bibtex/roles.py
new file mode 100644
index 0000000..bbbd1f0
--- /dev/null
+++ b/common/sphinxcontrib/bibtex/roles.py
@@ -0,0 +1,43 @@
+"""
+ New Doctree Roles
+ ~~~~~~~~~~~~~~~~~
+
+ .. autoclass:: CiteRole
+ :show-inheritance:
+
+ .. automethod:: result_nodes
+"""
+
+from pybtex.plugin import find_plugin
+import pybtex.database
+from sphinx.roles import XRefRole # for :cite:
+
+
+class CiteRole(XRefRole):
+
+ """Class for processing the :rst:role:`cite` role."""
+ backend = find_plugin('pybtex.backends', 'docutils')()
+
+ def result_nodes(self, document, env, node, is_ref):
+ """Transform reference node into a citation reference,
+ and note that the reference was cited.
+ """
+ keys = node['reftarget'].split(',')
+ # Note that at this point, usually, env.bibtex_cache.bibfiles
+ # is still empty because the bibliography directive may not
+ # have been processed yet, so we cannot get the actual entry.
+ # Instead, we simply fake an entry with the desired key, and
+ # fix the label at doctree-resolved time. This happens in
+ # process_citation_references.
+ refnodes = [
+ self.backend.citation_reference(_fake_entry(key), document)
+ for key in keys]
+ for key in keys:
+ env.bibtex_cache.add_cited(key, env.docname)
+ return refnodes, []
+
+
+def _fake_entry(key):
+ entry = pybtex.database.Entry(type_="")
+ entry.key = key
+ return entry
diff --git a/common/sphinxcontrib/bibtex/transforms.py b/common/sphinxcontrib/bibtex/transforms.py
new file mode 100644
index 0000000..8e4cdcb
--- /dev/null
+++ b/common/sphinxcontrib/bibtex/transforms.py
@@ -0,0 +1,127 @@
+"""
+ New Doctree Transforms
+ ~~~~~~~~~~~~~~~~~~~~~~
+
+ .. autoclass:: BibliographyTransform
+ :show-inheritance:
+
+ .. autoattribute:: default_priority
+ .. automethod:: apply
+
+ .. autofunction:: node_text_transform
+
+ .. autofunction:: transform_curly_bracket_strip
+
+ .. autofunction:: transform_url_command
+"""
+
+import docutils.nodes
+import docutils.transforms
+
+from pybtex.plugin import find_plugin
+
+from sphinxcontrib.bibtex.nodes import bibliography
+
+
+def node_text_transform(node, transform):
+ """Apply transformation to all Text nodes within node."""
+ for child in node.children:
+ if isinstance(child, docutils.nodes.Text):
+ node.replace(child, transform(child))
+ else:
+ node_text_transform(child, transform)
+
+
+def transform_curly_bracket_strip(textnode):
+ """Strip curly brackets from text."""
+ text = textnode.astext()
+ if '{' in text or '}' in text:
+ text = text.replace('{', '').replace('}', '')
+ return docutils.nodes.Text(text)
+ else:
+ return textnode
+
+
+def transform_url_command(textnode):
+ """Convert '\\\\url{...}' into a proper docutils hyperlink."""
+ text = textnode.astext()
+ if '\\url' in text:
+ text1, _, text = text.partition('\\url')
+ text2, _, text3 = text.partition('}')
+ text2 = text2.lstrip(' {')
+ ref = docutils.nodes.reference(refuri=text2)
+ ref += docutils.nodes.Text(text2)
+ node = docutils.nodes.inline()
+ node += transform_url_command(docutils.nodes.Text(text1))
+ node += ref
+ node += transform_url_command(docutils.nodes.Text(text3))
+ return node
+ else:
+ return textnode
+
+
+class BibliographyTransform(docutils.transforms.Transform):
+
+ """A docutils transform to generate citation entries for
+ bibliography nodes.
+ """
+
+ # transform must be applied before references are resolved
+ default_priority = 10
+ """Priority of the transform. See
+ http://docutils.sourceforge.net/docs/ref/transforms.html
+ """
+
+ def apply(self):
+ """Transform each
+ :class:`~sphinxcontrib.bibtex.nodes.bibliography` node into a
+ list of citations.
+ """
+ env = self.document.settings.env
+ docname = env.docname
+ for bibnode in self.document.traverse(bibliography):
+ id_ = bibnode['ids'][0]
+ bibcache = env.bibtex_cache.get_bibliography_cache(
+ docname=docname, id_=id_)
+ entries = env.bibtex_cache.get_bibliography_entries(
+ docname=docname, id_=id_, warn=env.app.warn)
+ # locate and instantiate style and backend plugins
+ style = find_plugin('pybtex.style.formatting', bibcache.style)()
+ backend = find_plugin('pybtex.backends', 'docutils')()
+ # create citation nodes for all references
+ if bibcache.list_ == "enumerated":
+ nodes = docutils.nodes.enumerated_list()
+ nodes['enumtype'] = bibcache.enumtype
+ if bibcache.start >= 1:
+ nodes['start'] = bibcache.start
+ env.bibtex_cache.set_enum_count(
+ env.docname, bibcache.start)
+ else:
+ nodes['start'] = env.bibtex_cache.get_enum_count(
+ env.docname)
+ elif bibcache.list_ == "bullet":
+ nodes = docutils.nodes.bullet_list()
+ else: # "citation"
+ nodes = docutils.nodes.paragraph()
+ # remind: style.format_entries modifies entries in unpickable way
+ for entry in style.format_entries(entries):
+ if bibcache.list_ in ["enumerated", "bullet"]:
+ citation = docutils.nodes.list_item()
+ citation += backend.paragraph(entry)
+ else: # "citation"
+ citation = backend.citation(entry, self.document)
+ # backend.citation(...) uses entry.key as citation label
+ # we change it to entry.label later onwards
+ # but we must note the entry.label now;
+ # at this point, we also already prefix the label
+ key = citation[0].astext()
+ bibcache.labels[key] = bibcache.labelprefix + entry.label
+ node_text_transform(citation, transform_url_command)
+ if bibcache.curly_bracket_strip:
+ node_text_transform(
+ citation,
+ transform_curly_bracket_strip)
+ nodes += citation
+ if bibcache.list_ == "enumerated":
+ env.bibtex_cache.inc_enum_count(env.docname)
+ bibnode.replace_self(nodes)
diff --git a/common/waf.py b/common/waf.py
index 7947829..3ee4435 100644
--- a/common/waf.py
+++ b/common/waf.py
@@ -206,7 +206,6 @@ def cmd_configure(ctx):
check_sphinx_extension(ctx, 'sphinx.ext.graphviz')
check_sphinx_extension(ctx, 'sphinx.ext.intersphinx')
check_sphinx_extension(ctx, 'sphinx.ext.mathjax')
- check_sphinx_extension(ctx, 'sphinxcontrib.bibtex')
#
# Optional builds.