mirror of
https://git.rtems.org/rtems-docs/
synced 2025-05-17 04:41:58 +08:00
Add the sphinxcontrib.bibtex extension to the repo.
This commit is contained in:
parent
bbdf66cd58
commit
aa4f8e2e43
13
common/sphinxcontrib/__init__.py
Normal file
13
common/sphinxcontrib/__init__.py
Normal file
@ -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__)
|
148
common/sphinxcontrib/bibtex/__init__.py
Normal file
148
common/sphinxcontrib/bibtex/__init__.py
Normal file
@ -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}
|
406
common/sphinxcontrib/bibtex/cache.py
Normal file
406
common/sphinxcontrib/bibtex/cache.py
Normal file
@ -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.
|
||||
"""
|
221
common/sphinxcontrib/bibtex/directives.py
Normal file
221
common/sphinxcontrib/bibtex/directives.py
Normal file
@ -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
|
17
common/sphinxcontrib/bibtex/nodes.py
Normal file
17
common/sphinxcontrib/bibtex/nodes.py
Normal file
@ -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
|
43
common/sphinxcontrib/bibtex/roles.py
Normal file
43
common/sphinxcontrib/bibtex/roles.py
Normal file
@ -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
|
127
common/sphinxcontrib/bibtex/transforms.py
Normal file
127
common/sphinxcontrib/bibtex/transforms.py
Normal file
@ -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)
|
@ -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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user