""" .. autofunction:: sphinxcontrib.bibtex.style.template.join(\ sep='', sep2=None, last_sep=None, other=None) .. autofunction:: sphinxcontrib.bibtex.style.template.sentence(\ capfirst=False, capitalize=False, add_period=True, \ sep=', ', sep2=None, last_sep=None, other=None) .. autofunction:: sphinxcontrib.bibtex.style.template.names(\ role, sep='', sep2=None, last_sep=None, other=None) .. autofunction:: sphinxcontrib.bibtex.style.template.entry_label() .. autofunction:: sphinxcontrib.bibtex.style.template.reference() .. autofunction:: sphinxcontrib.bibtex.style.template.footnote_reference() """ from typing import TYPE_CHECKING, Any, Dict, List, NamedTuple, cast import docutils.nodes import pybtex_docutils from pybtex.richtext import Text from pybtex.style.template import ( FieldIsMissing, Node, _format_list, field, first_of, optional, tag, ) from sphinx.util.nodes import make_refnode from sphinxcontrib.bibtex.nodes import raw_latex from sphinxcontrib.bibtex.richtext import BaseReferenceText if TYPE_CHECKING: from pybtex.backends import BaseBackend from pybtex.richtext import BaseText from pybtex.style import FormattedEntry from sphinx.builders import Builder # extended from pybtex: also copies the docstring into the wrapped object def node(f): n = Node(f.__name__, f) n.__doc__ = f.__doc__ return n # copied from pybtex join but extended to allow "et al" formatting @node def join(children, data, sep="", sep2=None, last_sep=None, other=None): """Join text fragments together.""" if sep2 is None: sep2 = sep if last_sep is None: last_sep = sep parts = [part for part in _format_list(children, data) if part] if len(parts) <= 1: return Text(*parts) elif len(parts) == 2: return Text(sep2).join(parts) elif other is None: return Text(last_sep).join([Text(sep).join(parts[:-1]), parts[-1]]) else: return Text(parts[0], other) @node def join2(children, data, sep1="", sep2=""): """Join text fragments together.""" if not children: return Text() else: return join(sep=sep1)[children[0], join(sep=sep2)[children[1:]]].format_data( data ) # copied from pybtex names but using the new join @node def sentence( children, data, capfirst=False, capitalize=False, add_period=True, sep=", ", sep2=None, last_sep=None, other=None, ): """Join text fragments, capitalize the first letter, and add a period to the end. """ text = join(sep=sep, sep2=sep2, last_sep=last_sep, other=other)[ children ].format_data(data) if capfirst: text = text.capfirst() if capitalize: text = text.capitalize() if add_period: text = text.add_period() return text # copied from pybtex names but using the new join allowing "et al" formatting @node def names(children, data, role, **kwargs): """Return formatted names.""" assert not children try: persons = data["entry"].persons[role] except KeyError: raise FieldIsMissing(role, data["entry"]) style = data["style"] formatted_names = [ style.person.style_plugin.format(person, style.person.abbreviate) for person in persons ] return join(**kwargs)[formatted_names].format_data(data) @node def entry_label(children, data) -> "BaseText": """Node for inserting the label of a formatted entry.""" assert not children entry = cast("FormattedEntry", data["formatted_entry"]) return Text(entry.label) class SphinxReferenceInfo(NamedTuple): """Tuple containing reference info to enable sphinx to resolve a reference to a citation. """ builder: "Builder" #: The Sphinx builder. fromdocname: str #: Document name of the citation reference. todocname: str #: Document name of the bibliography. citation_id: str #: Unique id of the citation within the bibliography. title: str #: Title attribute for reference node. pre_text: str #: Text to come before citation. post_text: str #: Text to come after citation. class SphinxReferenceText(BaseReferenceText[SphinxReferenceInfo]): """Pybtex rich text class generating a docutils reference node to a citation for use with :class:`SphinxReferenceInfo`. """ def render(self, backend: "BaseBackend") -> List[docutils.nodes.Element]: assert isinstance( backend, pybtex_docutils.Backend ), "SphinxReferenceText only supports the docutils backend" info = self.info[0] if info.builder.name == "latex": key = f"cite.{info.todocname}:{info.citation_id}" return ( [raw_latex(f"\\hyperlink{{{key}}}{{")] + super().render(backend) + [raw_latex("}")] ) elif info.builder.name == "rinoh": children = super().render(backend) refid = f"%{info.todocname}#{info.citation_id}" refnode = docutils.nodes.citation_reference( text=children[0], refid=refid, reftitle=info.title ) refnode.extend(children[1:]) return [refnode] else: children = super().render(backend) # make_refnode only takes a single child refnode2 = make_refnode( builder=info.builder, fromdocname=info.fromdocname, todocname=info.todocname, targetid=info.citation_id, child=children[0], title=info.title, ) refnode2.extend(children[1:]) # type: ignore return [refnode2] @node def reference(children, data: Dict[str, Any]): """Pybtex node for inserting a docutils reference node to a citation. The children of the node comprise the content of the reference, and any referencing information is stored in the *reference_info* key of the *data*. The data must also contain a *style* key pointing to the corresponding :class:`~sphinxcontrib.bibtex.style.referencing.BaseReferenceStyle`. """ parts = _format_list(children, data) info = data["reference_info"] assert isinstance(info, SphinxReferenceInfo) return SphinxReferenceText(info, *parts) @node def pre_text(children, data: Dict[str, Any]): assert not children info = data["reference_info"] assert isinstance(info, SphinxReferenceInfo) return Text(info.pre_text) @node def post_text(children, data: Dict[str, Any]): assert not children info = data["reference_info"] assert isinstance(info, SphinxReferenceInfo) return Text(info.post_text) class FootReferenceInfo(NamedTuple): """Tuple containing reference info to enable sphinx to resolve a footnote reference. """ key: str #: Citation key. document: "docutils.nodes.document" #: Current docutils document. refname: str #: Citation reference name. class FootReferenceText(BaseReferenceText[FootReferenceInfo]): """Pybtex rich text class generating a docutils footnote_reference node to a citation for use with :class:`FootReferenceInfo`. """ def render(self, backend: "BaseBackend"): assert isinstance( backend, pybtex_docutils.Backend ), "FootReferenceText only supports the docutils backend" info = self.info[0] # see docutils.parsers.rst.states.Body.footnote_reference() refnode = docutils.nodes.footnote_reference( "[#%s]_" % info.key, refname=info.refname, auto=1 ) info.document.note_autofootnote_ref(refnode) info.document.note_footnote_ref(refnode) return [refnode] @node def footnote_reference(children, data: Dict[str, Any]): """Pybtex node for inserting a footnote_reference docutils node. Any referencing information is stored in the *reference_info* key of the *data*. The data must also contain a *style* key pointing to the corresponding :class:`~sphinxcontrib.bibtex.style.referencing.BaseReferenceStyle`. """ assert not children info = data["reference_info"] assert isinstance(info, FootReferenceInfo) # we need to give the footnote text some fake content # otherwise pybtex richtext engine will mess things up return FootReferenceText(info, "#") @node def year(children, data: Dict[str, Any]) -> "BaseText": assert not children return first_of[optional[field("year")], "n.d."].format_data(data) @node def author_or_editor_or_title(children, data, **kwargs): assert not children return first_of[ optional[names("author", **kwargs)], optional[names("editor", **kwargs)], tag("em")[field("title")], ].format_data(data)