""" define the bibglossary directive
Originally adapted from: https://github.com/mcmtroffaes/sphinxcontrib-bibtex
"""
import ast # parse(), used for filter
import os.path # getmtime()
import sphinx.util
from docutils.parsers.rst import Directive, directives
from sphinx.util.console import bold, standout
from ipypublish.bib2glossary import BibGlossDB
from ipypublish.sphinx.gls.nodes import BibGlossaryNode
from ipypublish.sphinx.gls.cache import BibliographyCache, BibfileCache
logger = sphinx.util.logging.getLogger(__name__)
[docs]class BibGlossaryDirective(Directive):
"""Class for processing the ``bibglossary`` directive.
Parses the bibliography files, and produces a
:class:`~ipypublish.sphinx.gls.nodes.BibGlossaryNode` node.
.. seealso::
Further processing of the resulting
:class:`~ipypublish.sphinx.gls.nodes.BibGlossaryNode`
node is done by
:class:`~ipypublish.sphinx.gls.transforms.BibGlossaryTransform`.
"""
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = True
has_content = False
option_spec = {
"cited": directives.flag,
"notcited": directives.flag,
"all": directives.flag,
"unsorted": directives.flag,
"filter": directives.unchanged,
"style": directives.unchanged,
"encoding": directives.encoding,
"keyprefix": directives.unchanged,
}
_allowed_styles = "list"
_default_style = "list"
[docs] def run(self):
"""Process .bib files, set file dependencies, and create a
node that is to be transformed to the entries of the
glossary.
"""
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.bibgloss_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-bibglossary-%s-%s" % (env.docname, env.new_serialno("bibgloss"))
# set filter option
if "filter" in self.options:
if "all" in self.options:
logger.warning(standout(":filter: overrides :all:"))
if "notcited" in self.options:
logger.warning(standout(":filter: overrides :notcited:"))
if "cited" in self.options:
logger.warning(standout(":filter: overrides :cited:"))
try:
filter_ = ast.parse(self.options["filter"])
except SyntaxError:
logger.warning(
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")
style = self.options.get("style", env.app.config.bibgloss_default_style)
if style not in self._allowed_styles:
logger.warning(
"style '{}' not in allowed styles, defaulting to '{}'".format(
style, self._default_style
)
)
style = self._default_style
bibcache = BibliographyCache(
style=style,
unsorted=("unsorted" in self.options),
filter_=filter_,
encoding=self.options.get(
"encoding", self.state.document.settings.input_encoding
),
keyprefix=self.options.get("keyprefix", ""),
labels={},
plurals={},
bibfiles=[],
)
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])
# if the bibfile has been supplied with no extension, guess path
bibfile = BibGlossDB().guess_path(bibfile) or bibfile
self.process_bibfile(bibfile, bibcache.encoding)
env.note_dependency(bibfile)
bibcache.bibfiles.append(bibfile)
env.bibgloss_cache.set_bibliography_cache(env.docname, id_, bibcache)
return [BibGlossaryNode("", ids=[id_])]
[docs] def update_bibfile_cache(self, bibfile, mtime, encoding):
"""Parse *bibfile*, and store the
parsed data, along with modification time *mtime*, in the
bibtex cache.
Parameters
----------
bibfile: str
The bib file name.
mtime: float
The bib file's modification time.
"""
logger.info(bold("parsing bibtex file {0}... ".format(bibfile)), nonl=True)
bibglossdb = BibGlossDB()
bibglossdb.load(path=bibfile, encoding=encoding)
logger.info("parsed {0} entries".format(len(bibglossdb)))
env = self.state.document.settings.env
env.bibgloss_cache.bibfiles[bibfile] = BibfileCache(
mtime=mtime, data=bibglossdb
)
[docs] def process_bibfile(self, bibfile, encoding):
"""Check if ``env.bibgloss_cache.bibfiles[bibfile]`` is still
up to date. If not, parse the *bibfile*, and store parsed data in the
bibtex cache.
Parameters
----------
bibfile: str
The bib file name.
"""
env = self.state.document.settings.env
cache = env.bibgloss_cache.bibfiles
# get modification time of bibfile
try:
mtime = os.path.getmtime(bibfile)
except OSError:
logger.warning(standout("could not open bibtex file {0}.".format(bibfile)))
cache[bibfile] = BibfileCache( # dummy cache
mtime=-float("inf"), data=BibGlossDB()
)
return
# 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
logger.info(
bold("checking for {0} in bibtex cache... ".format(bibfile)), nonl=True
)
try:
bibfile_cache = cache[bibfile]
except KeyError:
logger.info("not found")
self.update_bibfile_cache(bibfile, mtime, encoding)
else:
if mtime != bibfile_cache.mtime:
logger.info("out of date")
self.update_bibfile_cache(bibfile, mtime, encoding)
else:
logger.info("up to date")