Source code for sphinxcontrib.bibtex.directives

"""
    .. autoclass:: BibliographyKey
        :members:

    .. autoclass:: BibliographyValue
        :members:

    .. autoclass:: BibliographyDirective

        .. automethod:: run
"""

import ast  # parse(), used for filter
from typing import TYPE_CHECKING, Dict, List, NamedTuple, cast

import docutils.nodes
import docutils.parsers.rst.directives as directives
import sphinx.util
from docutils.parsers.rst import Directive

from .bibfile import _make_ids, normpath_filename
from .nodes import bibliography as bibliography_node

if TYPE_CHECKING:
    from sphinx.environment import BuildEnvironment

    from .domain import BibtexDomain


logger = sphinx.util.logging.getLogger(__name__)


[docs]class BibliographyKey(NamedTuple): """Unique key for each bibliography directive.""" docname: str #: Name of the document where the bibliography resides. id_: str #: The id of the bibliography node in the document.
[docs]class BibliographyValue(NamedTuple): """Contains information about a bibliography directive.""" line: int #: Line number of the directive in the document. bibfiles: List[str] #: List of bib files for this directive. style: str #: The pybtex style. list_: str #: The list type. enumtype: str #: The sequence type (for enumerated lists). start: int #: The start of the sequence (for enumerated lists). labelprefix: str #: String prefix for pybtex generated labels. keyprefix: str #: String prefix for citation keys. filter_: ast.AST #: Parsed filter expression. citation_nodes: Dict[str, docutils.nodes.Element] #: key -> citation node keys: List[str] #: Keys listed as content of the directive.
[docs]class BibliographyDirective(Directive): """Class for processing the :rst:dir:`bibliography` directive. Produces a :class:`~sphinxcontrib.bibtex.nodes.bibliography` node, along with (empty) citation nodes that will be formatted later in the *env-updated* stage, and inserted into the document in a post-transform. We cannot insert the citation nodes here because we do not yet know which keys have been cited. .. seealso:: Further processing of the resulting :class:`~sphinxcontrib.bibtex.nodes.bibliography` node is done by :class:`~sphinxcontrib.bibtex.transforms.BibliographyTransform`. """ required_arguments = 0 optional_arguments = 1 final_argument_whitespace = True has_content = True option_spec = { "cited": directives.flag, "notcited": directives.flag, "all": directives.flag, "filter": directives.unchanged, "style": directives.unchanged, "list": directives.unchanged, "enumtype": directives.unchanged, "start": ( lambda value: directives.positive_int(value) if value != "continue" else -1 ), "labelprefix": directives.unchanged, "keyprefix": directives.unchanged, } def _get_filter(self): """Get parsed filter from options.""" env = cast("BuildEnvironment", self.state.document.settings.env) if "filter" in self.options: if "all" in self.options: logger.warning( ":filter: overrides :all:", location=(env.docname, self.lineno), type="bibtex", subtype="filter_overrides", ) if "notcited" in self.options: logger.warning( ":filter: overrides :notcited:", location=(env.docname, self.lineno), type="bibtex", subtype="filter_overrides", ) if "cited" in self.options: logger.warning( ":filter: overrides :cited:", location=(env.docname, self.lineno), type="bibtex", subtype="filter_overrides", ) try: return ast.parse(self.options["filter"]) except SyntaxError: logger.warning( "syntax error in :filter: expression" + " (" + self.options["filter"] + "); " "the option will be ignored", location=(env.docname, self.lineno), type="bibtex", subtype="filter_syntax_error", ) return ast.parse("cited") elif "all" in self.options: return ast.parse("True") elif "notcited" in self.options: return ast.parse("not cited") else: # the default filter: include only cited entries return ast.parse("cited")
[docs] 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 = cast("BuildEnvironment", self.state.document.settings.env) domain = cast("BibtexDomain", env.get_domain("cite")) filter_ = self._get_filter() if self.arguments: bibfiles = [] for bibfile in self.arguments[0].split(): normbibfile = normpath_filename(env, bibfile) if normbibfile not in domain.bibdata.bibfiles: logger.warning( "{0} not found or not configured" " in bibtex_bibfiles".format(bibfile), location=(env.docname, self.lineno), type="bibtex", subtype="bibfile_error", ) else: bibfiles.append(normbibfile) else: bibfiles = list(domain.bibdata.bibfiles.keys()) for bibfile in bibfiles: env.note_dependency(bibfile) # generate nodes and ids keyprefix: str = self.options.get("keyprefix", "") list_: str = self.options.get("list", "citation") if list_ not in {"bullet", "enumerated", "citation"}: logger.warning( "unknown bibliography list type '{0}'.".format(list_), location=(env.docname, self.lineno), type="bibtex", subtype="list_type_error", ) list_ = "citation" if list_ in {"bullet", "enumerated"}: citation_node_class = docutils.nodes.list_item else: citation_node_class = docutils.nodes.citation bibliography_count = env.temp_data["bibtex_bibliography_count"] = ( env.temp_data.get("bibtex_bibliography_count", 0) + 1 ) ids = set(self.state.document.ids.keys()) node = bibliography_node( "", docname=env.docname, ids=_make_ids( docname=env.docname, lineno=self.lineno, ids=ids, raw_id=env.app.config.bibtex_bibliography_id.format( bibliography_count=bibliography_count ), ), ) self.state.document.note_explicit_target(node, node) # we only know which citations to included at resolve stage # but we need to know their ids before resolve stage # so for now we generate a node, and thus, an id, for every entry citation_nodes: Dict[str, docutils.nodes.Element] = { keyprefix + entry.key: citation_node_class( ids=_make_ids( docname=env.docname, lineno=self.lineno, ids=ids, raw_id=env.app.config.bibtex_cite_id.format( bibliography_count=bibliography_count, key=keyprefix + entry.key ), ) ) for entry in domain.get_entries(bibfiles) } for citation_node in citation_nodes.values(): self.state.document.note_explicit_target(citation_node, citation_node) # check and get keys keys = [] for key in self.content: if keyprefix + key not in citation_nodes: logger.warning( 'could not find bibtex key "%s"' % key, location=(env.docname, self.lineno), type="bibtex", subtype="key_not_found", ) else: keys.append(key) # create bibliography object bibliography = BibliographyValue( line=self.lineno, list_=list_, 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_, labelprefix=self.options.get("labelprefix", ""), keyprefix=keyprefix, bibfiles=bibfiles, citation_nodes=citation_nodes, keys=keys, ) bib_key = BibliographyKey(docname=env.docname, id_=node["ids"][0]) assert bib_key not in domain.bibliographies domain.bibliographies[bib_key] = bibliography return [node]