import dataclasses
from abc import ABC
import pybtex.plugin
from pybtex.richtext import Text, Tag
from sphinxcontrib.bibtex.richtext import ReferenceInfo, BaseReferenceText
from sphinxcontrib.bibtex.style.template import names, sentence, join
from typing import (
TYPE_CHECKING, Tuple, List, Union, Iterable, Type, Optional, Dict
)
if TYPE_CHECKING:
from pybtex.database import Entry
from pybtex.richtext import BaseText
from pybtex.style import FormattedEntry
from pybtex.style.names import BaseNameStyle
from pybtex.style.template import Node
[docs]@dataclasses.dataclass
class BaseReferenceStyle(ABC):
"""Base class for citation reference styles.
For consistency, all subclasses of this class must be decorated
as a :class:`dataclasses.dataclass`,
and must provide a type annotation and default value for all attributes
(unless ``init=False`` is used, in which case they can be
initialized in :meth:`~dataclasses.dataclass.__post_init__`).).
This allows client code to instantiate any reference style
without needing to specify any arguments through the constructor.
"""
# see https://stackoverflow.com/a/59987363 as to why this is here
def __post_init__(self):
pass
[docs] def role_names(self) -> Iterable[str]:
"""Get list of role names supported by this style."""
raise NotImplementedError
[docs] def outer(
self, role_name: str, children: List["BaseText"]) -> "Node":
"""Returns outer template for formatting the references."""
raise NotImplementedError
[docs] def inner(self, role_name: str) -> "Node":
"""Returns inner template for formatting the references."""
raise NotImplementedError
[docs]@dataclasses.dataclass
class BracketStyle:
"""A class which provides brackets, as well as separators
and a function to facilitate formatting of the outer template.
"""
#: Left bracket.
left: Union["BaseText", str] = '['
#: Right bracket.
right: Union["BaseText", str] = ']'
#: Separators used for outer template (i.e. in between references
#: if multiple keys are referenced in a single citation).
sep: Union["BaseText", str] = ', '
#: Separator for outer template, if only two items.
sep2: Optional[Union["BaseText", str]] = None
#: Separator for outer template, for last item if three or more items.
last_sep: Optional[Union["BaseText", str]] = None
[docs] def outer(
self, children: List["BaseText"],
brackets=False, capfirst=False) -> "Node":
"""Creates an outer template with separators,
adding brackets if requested,
and capitalizing the first word if requested.
"""
return join[
self.left if brackets else '',
sentence(
capfirst=capfirst,
add_period=False,
sep=self.sep,
sep2=self.sep2,
last_sep=self.last_sep,
)[children],
self.right if brackets else '',
]
[docs]@dataclasses.dataclass
class PersonStyle:
"""A class providing additional data and helper functions
to facilitate formatting of person names.
"""
#: Plugin name of the style used for formatting person names.
style: str = 'last'
#: Plugin class instance used for formatting person names.
#: Automatically initialised from :attr:`style`.
style_plugin: "BaseNameStyle" = dataclasses.field(init=False)
#: Whether or not to abbreviate first names.
abbreviate: bool = True
#: Separator between persons.
sep: Union["BaseText", str] = ', '
#: Separator between persons, if only two persons.
sep2: Optional[Union["BaseText", str]] = ' and '
#: Separator between persons, for last person if three or more persons.
last_sep: Optional[Union["BaseText", str]] = ', and '
#: Abbreviation text if three or more persons.
other: Optional[Union["BaseText", str]] = \
Text(' ', Tag('em', 'et al.'))
def __post_init__(self):
self.style_plugin = pybtex.plugin.find_plugin(
'pybtex.style.names', name=self.style)()
[docs] def names(self, role: str, full: bool) -> "Node":
"""Returns a template formatting the persons with correct separators
and using the full person list if so requested.
"""
return names(
role=role,
sep=self.sep,
sep2=self.sep2,
last_sep=self.last_sep,
other=None if full else self.other,
)
[docs]@dataclasses.dataclass
class GroupReferenceStyle(BaseReferenceStyle):
"""Composes a group of reference styles into a single consistent style."""
#: List of style types.
styles: List[BaseReferenceStyle] \
= dataclasses.field(default_factory=list)
#: Dictionary from role names to styles.
#: Automatically initialized from :attr:`styles`.
role_style: Dict[str, BaseReferenceStyle] \
= dataclasses.field(default_factory=dict)
def __post_init__(self):
super().__post_init__()
self.role_style.update(
(role_name, style)
for style in self.styles
for role_name in style.role_names()
)
[docs] def role_names(self):
return self.role_style.keys()
[docs] def outer(self, role_name: str, children: List["BaseText"]) -> "Node":
"""Gets the outer template associated with *role_name*
in one of the :attr:`styles`.
"""
style = self.role_style[role_name]
return style.outer(role_name, children)
[docs] def inner(self, role_name: str) -> "Node":
"""Gets the inner template associated with *role_name*
in one of the :attr:`styles`.
"""
style = self.role_style[role_name]
return style.inner(role_name)