Skip to content

Commit

Permalink
Add support for patents; Add support for month ordering
Browse files Browse the repository at this point in the history
  • Loading branch information
anjos committed Nov 6, 2024
1 parent 42aebd2 commit 8316d69
Show file tree
Hide file tree
Showing 10 changed files with 284 additions and 9 deletions.
1 change: 1 addition & 0 deletions src/pelican/plugins/pybtex/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# SPDX-License-Identifier: MIT
"""Manage your academic publications page with Pelican and pybtex (BibTeX)."""

from . import style as _ # noqa: F401, monkey-patches to support extra entries
from .injector import PybtexInjector

_injector = PybtexInjector()
Expand Down
76 changes: 76 additions & 0 deletions src/pelican/plugins/pybtex/style.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# SPDX-FileCopyrightText: Copyright © 2024 André Anjos <[email protected]>
# SPDX-License-Identifier: MIT
"""Monkey-patch standard style from pybtex to support some extra entry
types.
"""

from pybtex.style.formatting import toplevel
import pybtex.style.formatting.unsrt
from pybtex.style.formatting.unsrt import date
from pybtex.style.template import field, node, optional, sentence, tag, words


def _monkeypatch_method(cls):
def decorator(func):
setattr(cls, func.__name__, func)
return func

return decorator


@_monkeypatch_method(pybtex.style.formatting.unsrt.Style)
def get_patent_template(self, e):
"""Format patent bibtex entry.
Parameters
----------
e
The entry to be formatted.
Returns
-------
The formatted entry object.
"""
return toplevel[
sentence[self.format_names("author")],
self.format_title(e, "title"),
sentence(capfirst=False)[tag("em")[field("number")], date],
optional[self.format_url(e), optional[" (visited on ", field("urldate"), ")"]],
]


# format month by converting integers to month name
_MONTH_NAMES = {
1: "January",
2: "February",
3: "March",
4: "April",
5: "May",
6: "June",
7: "July",
8: "August",
9: "September",
10: "October",
11: "November",
12: "December",
}


@node
def _month_field(children, data):
assert not children
m = data["entry"].fields.get("month")
try:
m = int(m)
# normalize
m = 1 if m < 1 else m
m = len(_MONTH_NAMES) if m > len(_MONTH_NAMES) else m
# reset
data["entry"].fields["month"] = _MONTH_NAMES[m]
except (TypeError, ValueError):
pass
return optional[field("month")].format_data(data)


# Ensures we always have the month correctly formatted
pybtex.style.formatting.unsrt.date = words[_month_field(), field("year")]
4 changes: 2 additions & 2 deletions src/pelican/plugins/pybtex/templates/publications.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ <h2>

{% block content_pybtex %}
<div id="pybtex">
{% for group in publications|groupby("year")|reverse %}
{% for group in publications|groupby(attribute="year")|reverse %}
<h3 id="{{ group.grouper }}">{{ group.grouper }}</h3>
{% for item in group.list %}
{% for item in group.list|sort(attribute="month")|reverse %}
<details id="pybtex-{{ item.key }}">
<summary>{{ item.html }}</summary>
{{ item.bibtex }}
Expand Down
49 changes: 42 additions & 7 deletions src/pelican/plugins/pybtex/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,44 @@ def format_bibtex_pygments(
)


_MONTH_NUMBERS: dict[str, int] = {
"unk": 0,
"jan": 1,
"feb": 2,
"mar": 3,
"apr": 4,
"may": 5,
"jun": 6,
"jul": 7,
"aug": 8,
"sep": 9,
"oct": 10,
"nov": 11,
"dec": 12,
}


def _get_month_number(m: str) -> int:
"""Get the month number or 0, if that is not known.
Parameters
----------
m
The name of the month.
Returns
-------
An integer, representing the month number.
"""
return _MONTH_NUMBERS[m.lower()[:3]]


def generate_context(
bibdata: typing.Sequence[pybtex.database.BibliographyData],
style_name: str,
extra_fields: typing.Sequence[str],
html_formatter_options: dict[str, typing.Any],
) -> list[dict[str, str]]:
) -> list[dict[str, str | int]]:
"""Generate a list of dictionaries given a set of bibliography databases.
Parameters
Expand All @@ -153,11 +185,13 @@ def generate_context(
order) from the input databases. Each entry in the list contains at least the
following keys:
* ``key``: The BibTeX database key
* ``year``: The year of the entry
* ``html``: An HTML-formatted version of the entry
* ``label``: The attribute label by the formatting style (str)
* ``key``: The BibTeX database key (str)
* ``year``: The year of the entry (int)
* ``month``: A string-fied version of the month number (int)
* ``html``: An HTML-formatted version of the entry (str)
* ``bibtex``: An HTML-ready (pygments-highlighted) BibTeX-formatted version of
the entry
the entry (str)
More keys as defined by ``extra_fiedls`` may also be present in case they are
found in the original database entry. These fields are copied verbatim to this
Expand Down Expand Up @@ -189,7 +223,7 @@ def generate_context(
]

backend = pybtex.backends.html.Backend()
retval: list[dict[str, str]] = []
retval: list[dict[str, str | int]] = []
for entry, formatted_entry in zip(all_entries, formatted_entries):
# make entry text, and then pass it through pygments for highlighting
assert entry.fields is not None
Expand All @@ -198,7 +232,8 @@ def generate_context(
{
"label": typing.cast(str, formatted_entry.label),
"key": formatted_entry.key,
"year": entry.fields.get("year"),
"year": int(entry.fields.get("year", 0)),
"month": _get_month_number(entry.fields.get("month", "unk")),
"html": formatted_entry.text.render(backend),
"bibtex": format_bibtex_pygments(entry, html_formatter_options),
}
Expand Down
13 changes: 13 additions & 0 deletions tests/data/biblio-patent/content/article.bib
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
%% SPDX-FileCopyrightText: Copyright © 2024 André Anjos <andre.dos.anjos-at-gmail.com>
%% SPDX-License-Identifier: MIT
@patent{pat,
author = "A. G. Bell",
title = "Improvement in telegraphy",
nationality = "United States",
number = "174465",
day = "7",
month = Mar,
year = "1876",
url = "https://www.google.com/patents/US174465",
}
16 changes: 16 additions & 0 deletions tests/data/biblio-patent/content/article.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.. SPDX-FileCopyrightText: Copyright © 2024 André Anjos <[email protected]>
.. SPDX-License-Identifier: MIT
This is an article
##################

:date: 2010-10-03 10:20
:modified: 2010-10-04 18:40
:tags: test, article
:category: bibliography
:slug: article
:authors: André Anjos
:summary: Short version for index and feeds
:pybtex_sources: article.bib

This will be turned into a citation [@@pat].
3 changes: 3 additions & 0 deletions tests/data/biblio-patent/pelicanconf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# SPDX-FileCopyrightText: Copyright © 2024 André Anjos <[email protected]>
# SPDX-License-Identifier: MIT
PATH = "content"
29 changes: 29 additions & 0 deletions tests/data/month-ordering/content/publications.bib
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
%% SPDX-FileCopyrightText: Copyright © 2024 André Anjos <andre.dos.anjos-at-gmail.com>
%% SPDX-License-Identifier: MIT
@misc{no-month,
title = "Misc title 1",
author = "Author Last",
year = 2015,
}

@misc{month-short,
title = "Misc with short month name",
author = "Author Last",
year = 2015,
month = jan,
}

@misc{month-long,
title = "Misc with short month name",
author = "Author Last",
year = 2015,
month = "June",
}

@misc{month-number,
title = "Misc with numbered month",
author = "Author Last",
year = 2015,
month = 3,
}
4 changes: 4 additions & 0 deletions tests/data/month-ordering/pelicanconf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# SPDX-FileCopyrightText: Copyright © 2024 André Anjos <[email protected]>
# SPDX-License-Identifier: MIT
PATH = "content"
PYBTEX_SOURCES = ["publications.bib"]
98 changes: 98 additions & 0 deletions tests/test_pybtex.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,53 @@ def test_publications_simple(
)


@pytest.mark.parametrize("subdir", ["month-ordering"])
def test_publications_month_ordering(
setup_pelican: tuple[list[logging.LogRecord], pathlib.Path],
):
records, pelican_output = setup_pelican

publications_html = pelican_output / "publications.html"
assert publications_html.exists()

with publications_html.open() as f:
soup = BeautifulSoup(f, "html.parser")

div = soup.find_all("div", id="pybtex")
assert len(div) == 1

publication_keys = [
"month-long",
"month-number",
"month-short",
"no-month",
]
details = div[0].find_all("details")
assert len(details) == len(publication_keys)

for k, detail in enumerate(details):
# should correspond to one of the expected entries
assert detail.attrs["id"][len("pybtex-") :] == publication_keys[k]

# it should contain the bibtex entry
pre = detail.find_all("pre")
assert len(pre) == 1

# check pygments filtered the BibTeX entry
assert "highlight" in pre[0].parent.attrs["class"]

# the bibtex entry should start with @
assert detail.pre.text.startswith("@")

_assert_log_no_errors(records)
_assert_log_contains(
records,
message="plugin detected 4 entries spread across 1 source file",
level=logging.INFO,
count=1,
)


@pytest.mark.parametrize("subdir", ["urls"])
def test_publications_urls(setup_pelican: tuple[list[logging.LogRecord], pathlib.Path]):
records, pelican_output = setup_pelican
Expand Down Expand Up @@ -277,6 +324,57 @@ def test_biblio_at_article(setup_pelican: tuple[list[logging.LogRecord], pathlib
)


@pytest.mark.parametrize("subdir", ["biblio-patent"])
def test_biblio_patent(setup_pelican: tuple[list[logging.LogRecord], pathlib.Path]):
records, pelican_output = setup_pelican

article_html = pelican_output / "article.html"
assert article_html.exists()

publications_html = pelican_output / "publications.html"
assert not publications_html.exists()

with article_html.open() as f:
soup = BeautifulSoup(f, "html.parser")

publication_keys = [
"pat",
]

para = soup.find_all("p")

text_to_be_checked = ((publication_keys[0], "1", para[0]),)

for key, label, paragraph in text_to_be_checked:
a = paragraph.find_all("a")
text_label = f"[{label}]"
assert len(a) == 1
assert text_label in a[0].attrs["title"]
assert a[0].attrs["href"].startswith("#") # internal
assert a[0].attrs["href"].endswith(key)
assert a[0].text == text_label

num_headers = 2
h2 = soup.find_all("h2")
assert len(h2) == num_headers

assert h2[1].text.strip() == "Bibliography"

div = soup.find_all("div", id="pybtex")
assert len(div) == 1

details = div[0].find_all("details")
assert len(details) == len(publication_keys)

# prefixed by "pybtex-"
assert details[0].attrs["id"].endswith(publication_keys[0])

_assert_log_no_errors(records)
_assert_log_contains(
records, message="plugin detected no entries", level=logging.INFO, count=1
)


@pytest.mark.parametrize("subdir", ["biblio-alpha"])
def test_biblio_alpha(setup_pelican: tuple[list[logging.LogRecord], pathlib.Path]):
records, pelican_output = setup_pelican
Expand Down

0 comments on commit 8316d69

Please sign in to comment.