-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement per-content bibliography (draft)
- Loading branch information
Showing
21 changed files
with
587 additions
and
123 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,31 @@ | ||
# SPDX-FileCopyrightText: Copyright © 2024 André Anjos <[email protected]> | ||
# | ||
# SPDX-License-Identifier: MIT | ||
"""Manage your academic publications page with Pelican and pybtex (BibTeX).""" | ||
|
||
from .injector import PybtexInjector | ||
|
||
def _connector(pelican_object): | ||
from .generator import PublicationGenerator | ||
_injector = PybtexInjector() | ||
|
||
return PublicationGenerator | ||
|
||
def _get_generators(pelican_object): | ||
del pelican_object # shuts-up linter | ||
|
||
from .generator import PybtexGenerator | ||
|
||
return PybtexGenerator | ||
|
||
|
||
def register(): | ||
"""Register this plugin to pelican.""" | ||
|
||
import pelican.plugins.signals | ||
|
||
pelican.plugins.signals.get_generators.connect(_connector) | ||
from . import signals | ||
|
||
# Global bibliography page | ||
pelican.plugins.signals.get_generators.connect(_get_generators) | ||
|
||
# Per-content (articles, pages) biobliography injector | ||
signals.pybtex_generator_init.connect(_injector.init) | ||
pelican.plugins.signals.content_object_init.connect(_injector.resolve_bibliography) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,33 +1,25 @@ | ||
# SPDX-FileCopyrightText: Copyright © 2024 André Anjos <[email protected]> | ||
# | ||
# SPDX-License-Identifier: MIT | ||
"""Populate the context with a list of formatted citations. | ||
The citations are loaded from external BibTeX files, at a configurable path. It can | ||
generate a ``Publications'' page for academic websites. | ||
""" | ||
"""Populate generation context with a list of formatted citations.""" | ||
|
||
import datetime | ||
import locale | ||
import logging | ||
import pathlib | ||
import typing | ||
|
||
import jinja2 | ||
import pygments | ||
import pygments.formatters | ||
import pygments.lexers | ||
|
||
import pelican.generators | ||
import pybtex.backends.html | ||
import pybtex.database | ||
import pybtex.database.input.bibtex | ||
import pybtex.database.output.bibtex | ||
import pybtex.richtext | ||
import pybtex.style.formatting.plain | ||
import pelican.utils | ||
|
||
from . import utils | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class PublicationGenerator(pelican.generators.Generator): | ||
class PybtexGenerator(pelican.generators.Generator): | ||
"""Populate context with a list of BibTeX publications. | ||
Parameters | ||
|
@@ -54,98 +46,69 @@ def __init__(self, *args, **kwargs): | |
] | ||
) | ||
) | ||
self.bibdata: list[pybtex.database.BibliographyData] = [] | ||
|
||
bibtex_parser = pybtex.database.input.bibtex.Parser() | ||
for k in kwargs["settings"].get("PYBTEX_SOURCES", []): | ||
p = pathlib.Path(k) | ||
|
||
if not p.is_absolute(): | ||
# make it relative to the site path | ||
p = kwargs["path"] / p | ||
# validates pybtex sources | ||
if not isinstance(kwargs["settings"].get("PYBTEX_SOURCES", []), list | tuple): | ||
logger.fatal( | ||
f"Setting `PYBTEX_SOURCES` should be a list or tuple, not " | ||
f"{type(kwargs['settings']['PYBTEX_SOURCES'])}" | ||
) | ||
|
||
if not p.exists(): | ||
logger.error(f"BibTeX file `{p}` does not exist") | ||
else: | ||
try: | ||
self.bibdata.append(bibtex_parser.parse_file(str(p))) | ||
except pybtex.database.PybtexError as e: | ||
logger.warning(f"`bibtex` plugin failed to parse file `{k}`: {e}") | ||
return | ||
self.bibdata = utils.load( | ||
kwargs["settings"].get("PYBTEX_SOURCES", []), [kwargs["path"]] | ||
) | ||
|
||
if not self.bibdata: | ||
logger.info("`bibtex` plugin detected no entries.") | ||
logger.info("`pybtex` (generator) plugin detected no entries.") | ||
else: | ||
sources = len(self.bibdata) | ||
entries = sum([len(k.entries) for k in self.bibdata]) | ||
logger.info( | ||
f"`bibtex` plugin detected {entries} entries spread across " | ||
f"`pybtex` plugin detected {entries} entries spread across " | ||
f"{sources} source file(s)." | ||
) | ||
|
||
# signals other interested parties on the same configuration | ||
from .signals import pybtex_generator_init | ||
|
||
pybtex_generator_init.send(self) | ||
|
||
def generate_context(self): | ||
"""Populate context with a list of BibTeX publications. | ||
The generator context is modified to add a ``publications`` entry containing a | ||
list dictionaries, each corresponding to a BibTeX entry (in the declared order), | ||
with the following keys: | ||
with at least the following keys: | ||
* ``key``: The BibTeX database key | ||
* ``year``: The year of the entry | ||
* ``html``: An HTML-formatted version of the entry | ||
* ``bibtex``: A BibTeX-formatted version of the entry | ||
* ``pdf``: set if present on the BibTeX entry verbatim | ||
* ``slides``: set if present on the BibTeX entry verbatim | ||
* ``poster``: set if present on the BibTeX entry verbatim | ||
""" | ||
* ``bibtex``: An HTML-ready (pygments-highlighted) BibTeX-formatted version of | ||
the entry | ||
# format entries | ||
plain_style = pybtex.style.formatting.plain.Style() | ||
html_backend = pybtex.backends.html.Backend() | ||
|
||
entries: list[ | ||
tuple[str, pybtex.database.Entry, pybtex.style.FormattedEntry] | ||
] = [] | ||
for k in self.bibdata: | ||
entries += zip( # noqa: B905 | ||
k.entries.keys(), | ||
k.entries.values(), | ||
plain_style.format_bibliography(k), | ||
) | ||
|
||
publications = [] | ||
for key, entry, text in entries: | ||
# make entry text, and then pass it through pygments for highlighting | ||
bibtex = pybtex.database.BibliographyData(entries={key: entry}).to_string( | ||
"bibtex" | ||
) | ||
bibtex_html = pygments.highlight( | ||
bibtex, | ||
pygments.lexers.BibTeXLexer(), | ||
pygments.formatters.HtmlFormatter(), | ||
) | ||
More keys as defined by ``PYBTEX_ADD_ENTRY_FIELDS`` may also be present in case | ||
they are found in the original database entry. These fields are copied | ||
verbatim to this dictionary. | ||
""" | ||
|
||
assert entry.fields is not None | ||
|
||
extra_fields = { | ||
k: v | ||
for k, v in entry.fields.items() | ||
if k in self.settings.get("PYBTEX_ADD_ENTRY_FIELDS", []) | ||
} | ||
|
||
publications.append( | ||
{ | ||
"key": key, | ||
"year": entry.fields.get("year"), | ||
"html": text.text.render(html_backend), | ||
"bibtex": bibtex_html, | ||
} | ||
) | ||
self.context["publications"] = utils.generate_context( | ||
self.bibdata, | ||
self.settings.get("PYBTEX_FORMAT_STYLE", "plain"), | ||
self.settings.get("PYBTEX_ADD_ENTRY_FIELDS", []), | ||
) | ||
|
||
publications[-1].update(extra_fields) | ||
# get the right formatting for the date | ||
default_timezone = self.settings.get("TIMEZONE", "UTC") | ||
timezone = getattr(self, "timezone", default_timezone) | ||
date = pelican.utils.set_date_tzinfo(datetime.datetime.now(), timezone) | ||
date_format = self.settings["DEFAULT_DATE_FORMAT"] | ||
if isinstance(date_format, tuple): | ||
locale_string = date_format[0] | ||
locale.setlocale(locale.LC_ALL, locale_string) | ||
date_format = date_format[1] | ||
locale_date = date.strftime(date_format) | ||
|
||
self.context["publications"] = publications | ||
self.context["now"] = __import__("datetime").datetime.now() | ||
self.context["locale_date"] = locale_date | ||
|
||
def generate_output(self, writer): | ||
"""Generate a publication list on the website. | ||
|
@@ -163,6 +126,7 @@ def generate_output(self, writer): | |
|
||
if not self.bibdata: | ||
logger.info(f"Not generating `{template}.html` (no entries)") | ||
return | ||
|
||
save_as = self.settings.get(f"{template.upper()}_SAVE_AS", f"{template}.html") | ||
url = self.settings.get(f"{template.upper()}_URL", f"{template}.html") | ||
|
Oops, something went wrong.