summaryrefslogtreecommitdiffstats
path: root/common/sphinxcontrib/bibtex/directives.py
diff options
context:
space:
mode:
Diffstat (limited to 'common/sphinxcontrib/bibtex/directives.py')
-rw-r--r--common/sphinxcontrib/bibtex/directives.py221
1 files changed, 221 insertions, 0 deletions
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