from abc import ABC
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Dict, Iterable, List, Optional, Tuple, Union
import pybtex.plugin
from pybtex.richtext import Tag, Text
from sphinxcontrib.bibtex.style.template import (
author_or_editor_or_title,
join,
names,
sentence,
)
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
from sphinxcontrib.bibtex.richtext import ReferenceInfo
[文档]
@dataclass
class BaseReferenceStyle(ABC):
"""Base class for citation reference styles.
For consistency, all subclasses of this class must be decorated
as a :class:`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:`~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
[文档]
def role_names(self) -> Iterable[str]:
"""Get list of role names supported by this style."""
raise NotImplementedError
[文档]
def outer(self, role_name: str, children: List["BaseText"]) -> "Node":
"""Returns outer template for formatting the references."""
raise NotImplementedError
[文档]
def inner(self, role_name: str) -> "Node":
"""Returns inner template for formatting the references."""
raise NotImplementedError
[文档]
@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
[文档]
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 "",
]
[文档]
@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" = 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]] = field(
default_factory=lambda: Text(" ", Tag("em", "et al."))
)
def __post_init__(self):
self.style_plugin = pybtex.plugin.find_plugin(
"pybtex.style.names", name=self.style
)()
[文档]
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,
)
[文档]
def author_or_editor_or_title(self, full: bool) -> "Node":
"""Returns a template formatting the author, falling back on editor
or title if author is not specified.
"""
return author_or_editor_or_title(
sep=self.sep,
sep2=self.sep2,
last_sep=self.last_sep,
other=None if full else self.other,
)
[文档]
@dataclass
class GroupReferenceStyle(BaseReferenceStyle):
"""Composes a group of reference styles into a single consistent style."""
#: List of style types.
styles: List[BaseReferenceStyle] = field(default_factory=list)
#: Dictionary from role names to styles.
#: Automatically initialized from :attr:`styles`.
role_style: Dict[str, BaseReferenceStyle] = 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()
)
[文档]
def role_names(self):
return self.role_style.keys()
[文档]
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)
[文档]
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)