diff --git a/.reuse/dep5 b/.reuse/dep5 index bba3f46..0befa16 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -5,5 +5,6 @@ Source: https://github.com/anjos/pelican-pybtex Files: pixi.lock + tests/data/biblio-override/content/article.md Copyright: Copyright © 2024 Andre Anjos License: MIT diff --git a/README.md b/README.md index ff21912..4a2a3cf 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,9 @@ documentation. This plugin reads a user-specified [pybtex supported file](https://docs.pybtex.org/formats.html#bibliography-formats) and populates the -global Jinja2 context used by Pelican with a `publications` entry. The `publications` -entry is a list of dictionaries, each containing the following fields: +global Jinja2 context used by Pelican with a `publications` variable. The `publications` +variable is a list of dictionaries itself, each containing the following fields, +corresponding to individual entries in the provided compatible files: * `label`: The formatted label (depends on the used style, but usually something like `3`, `Ein05`, or `Einstein, 1905`). @@ -41,10 +42,11 @@ entry is a list of dictionaries, each containing the following fields: * `year`: The year of the entry * `html`: An HTML-formatted version of the entry * `bibtex`: A BibTeX-formatted version of the entry, wrapped in a [pygments -HTML-formatted](https://pygments.org/docs/quickstart/) version. +HTML-formatted](https://pygments.org/docs/quickstart/) version. The pygments output +respects the settings for `PYGMENTS_RST_OPTIONS` in Pelican. -Use the following configuration key to list sources to be parsed and populate the -`publications` context: +Use the following Pelican configuration key to list sources to be parsed and populate +the `publications` context: ```python # any pybtex supported input format is accepted @@ -56,15 +58,15 @@ reported, but ignored during generation. Check Pelican logs for details while b your site. Note that relative paths are considered with respect to the location of the setting of -`PATH` in `pelicanconf.py`. If `PATH` itself is relative, it is considered relative to -the location of `pelicanconf.py` itself. +`PATH` in `pelicanconf.py` (typically the `content` directory). If `PATH` itself is +relative, it is considered relative to the location of `pelicanconf.py` itself. ### Extra fields -If you also set `PYBTEX_ADD_ENTRY_FIELDS`, then if any other field in each matching -values in this setting will also be included in the dictionary of each entry. This -feature can be used, e.g., to include more URLs in a work, and then display those using -a template override as explained next. +If you also set `PYBTEX_ADD_ENTRY_FIELDS`, then if any other field listed in this +setting will also be included *verbatim* in the dictionary of each entry. This feature +can be used, e.g., to include more URLs in a work, and then display those using a +custom template as explained later. ```python PYBTEX_ADD_ENTRY_FIELDS = ["url", "pdf", "slides", "poster"] @@ -75,8 +77,10 @@ PYBTEX_ADD_ENTRY_FIELDS = ["url", "pdf", "slides", "poster"] By default, `PYBTEX_FORMAT_STYLE` is set to `plain`. You may further customize this setting to one of the biobliography formatting styles supported by pybtex (currently "plain", "alpha", "unsrt", and "unsrtalpha"). You may check the formatting style of -some of these BibTeX styles [on this -reference](https://www.overleaf.com/learn/latex/Bibtex_bibliography_styles). +these BibTeX styles [on this +reference](https://www.overleaf.com/learn/latex/Bibtex_bibliography_styles). We +currently do not support custom bibliographic styles. Create an issue if you would like +to work on this. ### Publications page @@ -140,23 +144,35 @@ restructuredtext or markdown formats, to refer to bibliography entries from the `PYBTEX_SOURCES`. This process is similar to using BibTeX database entries in your LaTeX sources by using the `\cite{bibkey}` command. In this case, this plugin will replace these citations with links to a bibliography database *injected* at the end of -the article or post. The global `PYBTEX_FORMAT_STYLE` is respected while formatting -bibliographies. +the article or post. + +The global `PYBTEX_FORMAT_STYLE` is respected while formatting bibliographies. You may +override the style for the current article or page using the metadata entry +`pybtex_format_style`. The same mechanism is available for `PYBTEX_ADD_ENTRY_FIELDS`, +which can be locally overriden by `pybtex_add_entry_fields` metadata entry. You may also add further enrich article or page metadata defining a specific `pybtex_sources`. In such a case, these files will be loaded respecting the same rules -as for `PYBTEX_SOURCES`. Specifically, relative paths are considered w.r.t. the location -of `PATH` in `pelicanconf.py`. Article and page bibliography markers will then be -resolved by first looking at entries in the local `pybtex_sources`, and then on the -global `PYBTEX_SOURCES` entry in `pelicanconf.py`. +as for `PYBTEX_SOURCES`. Specifically, relative paths are first searched at the location +of the article or page, and then default on using the `PATH` setting in +`pelicanconf.py`. Article and page bibliography markers will then be resolved by first +looking at entries in the local `pybtex_sources`, and then on the global +`PYBTEX_SOURCES` entry in `pelicanconf.py`. Be aware that in case repeated citation keys are found across all bibliography databases, **the last occurence is used** while resolving local bibliography for articles an pages. +Finally, local bibliography formatting is controlled by the [default +`bibliography.html`](src/pelican/plugins/pybtex/templates/biobliography.html) template +that is shipped with this package. This templates defines the contents of the +*injected* bibliography section on articles and pages. You may override this template in +a similar way to what was explained above for the global `publications.html` template, +by setting the `THEME_TEMPLATES_OVERRIDES` Pelican variable. + ## Contributing -Contributions are welcome and much appreciated. Every little bit helps. You can +Contributions are welcome and appreciated. Every little bit helps. You can contribute by improving the documentation, adding missing features, and fixing bugs. You can also help out by reviewing and commenting on [existing issues](https://github.com/anjos/pelican-pybtex/issues). diff --git a/pixi.lock b/pixi.lock index b429f97..ece3c20 100644 --- a/pixi.lock +++ b/pixi.lock @@ -10,6 +10,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.6.2.post1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/argcomplete-3.5.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-24.2.0-pyh71513ae_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/backports-1.0-pyhd8ed1ab_4.conda - conda: https://conda.anaconda.org/conda-forge/noarch/backports.tarfile-1.0.0-pyhd8ed1ab_1.conda @@ -77,6 +78,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/license-expression-30.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-3.6-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py312h178313f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_0.conda @@ -138,7 +140,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/unidecode-1.3.8-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.2.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/userpath-1.7.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/uv-0.4.29-h0f3a69f_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/uv-0.4.30-h0f3a69f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/versioningit-3.1.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.27.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/watchfiles-0.24.0-py312h12e396e_1.conda @@ -161,6 +163,7 @@ environments: - pypi: . osx-arm64: - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.6.2.post1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/argcomplete-3.5.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-24.2.0-pyh71513ae_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/backports-1.0-pyhd8ed1ab_4.conda - conda: https://conda.anaconda.org/conda-forge/noarch/backports.tarfile-1.0.0-pyhd8ed1ab_1.conda @@ -217,6 +220,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxcb-1.17.0-hdb1d25a_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/license-expression-30.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-3.6-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.2-py312ha0ccf2a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_0.conda @@ -277,7 +281,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/unidecode-1.3.8-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.2.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/userpath-1.7.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/uv-0.4.29-h668ec48_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/uv-0.4.30-h668ec48_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/versioningit-3.1.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.27.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/watchfiles-0.24.0-py312he431725_1.conda @@ -309,13 +313,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/binaryornot-0.4.4-py_1.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/blinker-1.8.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/boolean.py-4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py312h2ec8cdc_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py313h46c70d0_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.8.30-hbcca054_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2024.8.30-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py312h06ac9bb_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py313hfab6e84_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/chardet-5.2.0-py312h7900ff3_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/chardet-5.2.0-py313h78bf25f_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.9-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/docutils-0.21.2-pyhd8ed1ab_0.conda @@ -335,16 +339,15 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.2.0-h77fa898_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.2.0-h69a702a_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-14.2.0-h77fa898_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-h4bc722e_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.47.0-hadc24fc_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-14.2.0-hc0a3c3a_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.2.0-h4852527_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/license-expression-30.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py312h178313f_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py313h8060acc_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.9.1-pyhd8ed1ab_0.conda @@ -357,31 +360,31 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.18.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.7-hc5c86c4_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.0-h9ebbce0_100_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-debian-0.1.49-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.12-5_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.13-5_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2024.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.2-py312h66e93f0_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.2-py313h536fd9c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/reuse-3.0.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rich-13.9.4-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ruff-0.7.2-py312h2156523_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ruff-0.7.2-py313he87ea70_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py312h68727a3_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py313h33d0bda_5.conda - conda: https://conda.anaconda.org/conda-forge/noarch/unidecode-1.3.8-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.2.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.27.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/watchfiles-0.24.0-py312h12e396e_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/watchfiles-0.24.0-py313h920b4c0_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312hef9b889_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py313h80202fe_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.6-ha6fb4c9_0.conda osx-arm64: - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.6.2.post1-pyhd8ed1ab_0.conda @@ -465,6 +468,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.6.2.post1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/argcomplete-3.5.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.12.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/blinker-1.8.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda @@ -475,6 +479,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/feedgenerator-2.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.5.0-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/latexcodec-2.0.1-pyh9f0ad1d_0.tar.bz2 @@ -488,6 +493,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-3.6-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py310h89163eb_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_0.conda @@ -521,9 +527,11 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/watchfiles-0.24.0-py310h505e2c1_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.20.2-pyhd8ed1ab_0.conda - pypi: . osx-arm64: - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.6.2.post1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/argcomplete-3.5.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.12.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/blinker-1.8.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-h99b78c6_7.conda @@ -534,12 +542,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/feedgenerator-2.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.5.0-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/latexcodec-2.0.1-pyh9f0ad1d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.4.2-h3422bc3_5.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.47.0-hbaaea75_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-3.6-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.2-py310h5799be4_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_0.conda @@ -573,6 +583,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/watchfiles-0.24.0-py310h7a930dc_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xz-5.2.6-h57fd34a_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h3422bc3_2.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.20.2-pyhd8ed1ab_0.conda - pypi: . test311: channels: @@ -584,6 +595,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.6.2.post1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/argcomplete-3.5.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.12.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/blinker-1.8.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda @@ -594,6 +606,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/feedgenerator-2.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.5.0-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/latexcodec-2.0.1-pyh9f0ad1d_0.tar.bz2 @@ -608,6 +621,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-3.6-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py311h2dc5d0c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_0.conda @@ -641,9 +655,11 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/watchfiles-0.24.0-py311h9e33e62_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.20.2-pyhd8ed1ab_0.conda - pypi: . osx-arm64: - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.6.2.post1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/argcomplete-3.5.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.12.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/blinker-1.8.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-h99b78c6_7.conda @@ -654,6 +670,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/feedgenerator-2.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.5.0-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/latexcodec-2.0.1-pyh9f0ad1d_0.tar.bz2 @@ -661,6 +678,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.4.2-h3422bc3_5.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.47.0-hbaaea75_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-3.6-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.2-py311h56c23cb_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_0.conda @@ -694,6 +712,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/watchfiles-0.24.0-py311h481aa64_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xz-5.2.6-h57fd34a_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h3422bc3_2.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.20.2-pyhd8ed1ab_0.conda - pypi: . test312: channels: @@ -705,6 +724,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.6.2.post1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/argcomplete-3.5.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.12.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/blinker-1.8.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda @@ -715,6 +735,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/feedgenerator-2.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.5.0-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/latexcodec-2.0.1-pyh9f0ad1d_0.tar.bz2 @@ -729,6 +750,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-3.6-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py312h178313f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_0.conda @@ -762,9 +784,11 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/watchfiles-0.24.0-py312h12e396e_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.20.2-pyhd8ed1ab_0.conda - pypi: . osx-arm64: - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.6.2.post1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/argcomplete-3.5.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.12.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/blinker-1.8.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-h99b78c6_7.conda @@ -775,6 +799,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/feedgenerator-2.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.5.0-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/latexcodec-2.0.1-pyh9f0ad1d_0.tar.bz2 @@ -782,6 +807,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.4.2-h3422bc3_5.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.47.0-hbaaea75_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-3.6-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.2-py312ha0ccf2a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_0.conda @@ -815,6 +841,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/watchfiles-0.24.0-py312he431725_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xz-5.2.6-h57fd34a_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h3422bc3_2.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.20.2-pyhd8ed1ab_0.conda - pypi: . test313: channels: @@ -826,6 +853,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.6.2.post1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/argcomplete-3.5.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.12.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/blinker-1.8.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda @@ -836,6 +864,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/feedgenerator-2.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.5.0-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/latexcodec-2.0.1-pyh9f0ad1d_0.tar.bz2 @@ -849,6 +878,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.47.0-hadc24fc_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-3.6-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py313h8060acc_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_0.conda @@ -882,9 +912,11 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/watchfiles-0.24.0-py313h920b4c0_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.20.2-pyhd8ed1ab_0.conda - pypi: . osx-arm64: - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.6.2.post1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/argcomplete-3.5.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.12.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/blinker-1.8.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-h99b78c6_7.conda @@ -895,6 +927,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/feedgenerator-2.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.5.0-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/latexcodec-2.0.1-pyh9f0ad1d_0.tar.bz2 @@ -903,6 +936,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h99b78c6_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.47.0-hbaaea75_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-3.6-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.2-py313heb2b014_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_0.conda @@ -936,6 +970,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/watchfiles-0.24.0-py313h849cdff_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xz-5.2.6-h57fd34a_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h3422bc3_2.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.20.2-pyhd8ed1ab_0.conda - pypi: . test39: channels: @@ -947,6 +982,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.6.2.post1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/argcomplete-3.5.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.12.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/blinker-1.8.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda @@ -957,6 +993,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/feedgenerator-2.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.5.0-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/latexcodec-2.0.1-pyh9f0ad1d_0.tar.bz2 @@ -970,6 +1007,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-3.6-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py39h9399b63_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_0.conda @@ -1003,9 +1041,11 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/watchfiles-0.24.0-py39he612d8f_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.20.2-pyhd8ed1ab_0.conda - pypi: . osx-arm64: - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.6.2.post1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/argcomplete-3.5.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.12.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/blinker-1.8.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-h99b78c6_7.conda @@ -1016,12 +1056,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/feedgenerator-2.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.5.0-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/latexcodec-2.0.1-pyh9f0ad1d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.4.2-h3422bc3_5.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.47.0-hbaaea75_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-3.6-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.2-py39h66d85bf_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_0.conda @@ -1055,6 +1097,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/watchfiles-0.24.0-py39h9c3e640_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xz-5.2.6-h57fd34a_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h3422bc3_2.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.20.2-pyhd8ed1ab_0.conda - pypi: . packages: - kind: conda @@ -1112,6 +1155,23 @@ packages: - pkg:pypi/anyio?source=hash-mapping size: 109864 timestamp: 1728935803440 +- kind: conda + name: argcomplete + version: 3.5.1 + build: pyhd8ed1ab_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/argcomplete-3.5.1-pyhd8ed1ab_0.conda + sha256: b2c1cb869915a96d5e2d922719edf2fc6824a15ecf666ecc18fc281d2177d224 + md5: f1f7b435e0e99368020f21447e477b70 + depends: + - python >=3.8 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/argcomplete?source=hash-mapping + size: 41268 + timestamp: 1728339105769 - kind: conda name: attrs version: 24.2.0 @@ -1305,6 +1365,29 @@ packages: - pkg:pypi/brotli?source=hash-mapping size: 339067 timestamp: 1725268603536 +- kind: conda + name: brotli-python + version: 1.1.0 + build: py313h46c70d0_2 + build_number: 2 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py313h46c70d0_2.conda + sha256: da92e5e904465fce33a7a55658b13caa5963cc463c430356373deeda8b2dbc46 + md5: f6bb3742e17a4af0dc3c8ca942683ef6 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libstdcxx >=13 + - python >=3.13.0rc1,<3.14.0a0 + - python_abi 3.13.* *_cp313 + constrains: + - libbrotlicommon 1.1.0 hb9d3cd8_2 + license: MIT + license_family: MIT + purls: + - pkg:pypi/brotli?source=hash-mapping + size: 350424 + timestamp: 1725267803672 - kind: conda name: bzip2 version: 1.0.8 @@ -1441,6 +1524,27 @@ packages: - pkg:pypi/cffi?source=hash-mapping size: 282115 timestamp: 1725560759157 +- kind: conda + name: cffi + version: 1.17.1 + build: py313hfab6e84_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py313hfab6e84_0.conda + sha256: 73cd6199b143a8a6cbf733ce124ed57defc1b9a7eab9b10fd437448caf8eaa45 + md5: ce6386a5892ef686d6d680c345c40ad1 + depends: + - __glibc >=2.17,<3.0.a0 + - libffi >=3.4,<4.0a0 + - libgcc >=13 + - pycparser + - python >=3.13.0rc1,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT + purls: + - pkg:pypi/cffi?source=hash-mapping + size: 295514 + timestamp: 1725560706794 - kind: conda name: cfgv version: 3.3.1 @@ -1495,6 +1599,24 @@ packages: - pkg:pypi/chardet?source=hash-mapping size: 262471 timestamp: 1724954971996 +- kind: conda + name: chardet + version: 5.2.0 + build: py313h78bf25f_2 + build_number: 2 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/chardet-5.2.0-py313h78bf25f_2.conda + sha256: a176c9dfdd89018921f09cf9289a93267c5b53685a0161b7afa87917a8633aa9 + md5: e29347f148fdb82662fa8a97e3fb4bda + depends: + - python >=3.13.0rc1,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: LGPL-2.1-only + license_family: GPL + purls: + - pkg:pypi/chardet?source=hash-mapping + size: 260819 + timestamp: 1724954913874 - kind: conda name: chardet version: 5.2.0 @@ -2910,6 +3032,24 @@ packages: - pkg:pypi/license-expression?source=hash-mapping size: 97759 timestamp: 1728562513932 +- kind: conda + name: markdown + version: '3.6' + build: pyhd8ed1ab_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/markdown-3.6-pyhd8ed1ab_0.conda + sha256: fce1fde00359696983989699c00f9891194c4ebafea647a8d21b7e2e3329b56e + md5: 06e9bebf748a0dea03ecbe1f0e27e909 + depends: + - importlib-metadata >=4.4 + - python >=3.6 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/markdown?source=hash-mapping + size: 78331 + timestamp: 1710435316163 - kind: conda name: markdown-it-py version: 3.0.0 @@ -3435,15 +3575,16 @@ packages: timestamp: 1727688156572 - kind: pypi name: pelican-pybtex - version: 0.0.1.dev14+gac7ebeb + version: 0.0.1.dev15+gc566043.d20241105 path: . - sha256: e9c7c8325e820908f4409073ddd7229178bd8ff81255059f8193009431cc5fdc + sha256: d0d3b744d7ed102c88308ec68e68ead0756fa5faf1d733eb9fe440aa8d4af160 requires_dist: - pelican>=4.5 - pybtex - pygments>=2.2 - pre-commit ; extra == 'qa' - beautifulsoup4 ; extra == 'test' + - markdown ; extra == 'test' - pytest ; extra == 'test' - pytest-cov ; extra == 'test' requires_python: <4.0,>=3.9 @@ -4668,6 +4809,28 @@ packages: - pkg:pypi/ruff?source=hash-mapping size: 6851407 timestamp: 1730495660174 +- kind: conda + name: ruff + version: 0.7.2 + build: py313he87ea70_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/ruff-0.7.2-py313he87ea70_0.conda + sha256: 5833aa5c20110dc4f24d9fba1f34f1336a84dbf508f6cb77f60ca09202f73de3 + md5: 7245084735701a9e2cbeb86ca28d4646 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libstdcxx >=13 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + constrains: + - __glibc >=2.17 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ruff?source=hash-mapping + size: 7740447 + timestamp: 1730495301913 - kind: conda name: ruff version: 0.7.2 @@ -5015,6 +5178,28 @@ packages: - pkg:pypi/ukkonen?source=hash-mapping size: 13904 timestamp: 1725784191021 +- kind: conda + name: ukkonen + version: 1.0.1 + build: py313h33d0bda_5 + build_number: 5 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py313h33d0bda_5.conda + sha256: 4edcb6a933bb8c03099ab2136118d5e5c25285e3fd2b0ff0fa781916c53a1fb7 + md5: 5bcffe10a500755da4a71cc0fb62a420 + depends: + - __glibc >=2.17,<3.0.a0 + - cffi + - libgcc >=13 + - libstdcxx >=13 + - python >=3.13.0rc1,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ukkonen?source=hash-mapping + size: 13916 + timestamp: 1725784177558 - kind: conda name: ukkonen version: 1.0.1 @@ -5095,12 +5280,12 @@ packages: timestamp: 1632758637093 - kind: conda name: uv - version: 0.4.29 + version: 0.4.30 build: h0f3a69f_0 subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/uv-0.4.29-h0f3a69f_0.conda - sha256: bb5df024d49976d2436e0cb6db7b4bcb7d18eb782402dca0816381d08cad7e2d - md5: 3c9d4ca2e04ec5f72aa94b88a0440ed6 + url: https://conda.anaconda.org/conda-forge/linux-64/uv-0.4.30-h0f3a69f_0.conda + sha256: cc1b143394d8bc223cb4518634ad043798f0c804b0ee4bf689d440c785bbed76 + md5: 3b5a52d360a5abb658d477156b71edd5 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=13 @@ -5109,16 +5294,16 @@ packages: - __glibc >=2.17 license: Apache-2.0 OR MIT purls: [] - size: 9638406 - timestamp: 1730359945592 + size: 9788205 + timestamp: 1730790063014 - kind: conda name: uv - version: 0.4.29 + version: 0.4.30 build: h668ec48_0 subdir: osx-arm64 - url: https://conda.anaconda.org/conda-forge/osx-arm64/uv-0.4.29-h668ec48_0.conda - sha256: fb1f38e36442e7e79bb04b6f14eabff02277da2eba0323d117368d5960b547b0 - md5: b5c243a1a6417bec67fc373f40b6e71a + url: https://conda.anaconda.org/conda-forge/osx-arm64/uv-0.4.30-h668ec48_0.conda + sha256: c6e983728a6407fab435b697aefa4919ad3669f5e2f0eb1e8bbf240b130ece57 + md5: 3b7effc84a4942a2d69eaa554750d1e0 depends: - __osx >=11.0 - libcxx >=18 @@ -5126,8 +5311,8 @@ packages: - __osx >=11.0 license: Apache-2.0 OR MIT purls: [] - size: 8572870 - timestamp: 1730360733547 + size: 8639048 + timestamp: 1730790714546 - kind: conda name: versioningit version: 3.1.2 @@ -5888,6 +6073,29 @@ packages: - pkg:pypi/zstandard?source=hash-mapping size: 419552 timestamp: 1725305670210 +- kind: conda + name: zstandard + version: 0.23.0 + build: py313h80202fe_1 + build_number: 1 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py313h80202fe_1.conda + sha256: ea82f2b8964150a3aa7373b4697e48e64f2200fe68ae554ee85c641c692d1c97 + md5: c178558ff516cd507763ffee230c20b2 + depends: + - __glibc >=2.17,<3.0.a0 + - cffi >=1.11 + - libgcc >=13 + - python >=3.13.0rc1,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - zstd >=1.5.6,<1.5.7.0a0 + - zstd >=1.5.6,<1.6.0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/zstandard?source=hash-mapping + size: 424424 + timestamp: 1725305749031 - kind: conda name: zstandard version: 0.23.0 diff --git a/pyproject.toml b/pyproject.toml index e7c412c..f24ff77 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,4 @@ # SPDX-FileCopyrightText: Copyright © 2024 André Anjos -# # SPDX-License-Identifier: MIT [build-system] @@ -36,7 +35,7 @@ dependencies = ["pelican>=4.5", "pybtex", "pygments>=2.2"] [project.optional-dependencies] qa = ["pre-commit"] -test = ["pytest", "pytest-cov", "beautifulsoup4"] +test = ["pytest", "pytest-cov", "beautifulsoup4", "markdown"] [project.urls] Homepage = "https://github.com/anjos/pelican-pybtex" @@ -72,6 +71,8 @@ qa-ci = "pre-commit run --all-files --show-diff-on-failure --verbose" pytest = "*" pytest-cov = "*" beautifulsoup4 = "*" +argcomplete = "*" +markdown = "*" [tool.pixi.feature.test.tasks] test = "pytest -sv --no-cov tests/" diff --git a/src/pelican/plugins/pybtex/generator.py b/src/pelican/plugins/pybtex/generator.py index ea602ad..af6252d 100644 --- a/src/pelican/plugins/pybtex/generator.py +++ b/src/pelican/plugins/pybtex/generator.py @@ -1,5 +1,4 @@ # SPDX-FileCopyrightText: Copyright © 2024 André Anjos -# # SPDX-License-Identifier: MIT """Populate generation context with a list of formatted citations.""" @@ -49,25 +48,26 @@ def __init__(self, *args, **kwargs): # validates pybtex sources if not isinstance(kwargs["settings"].get("PYBTEX_SOURCES", []), (list, tuple)): - logger.fatal( + logger.error( f"Setting `PYBTEX_SOURCES` should be a list or tuple, not " f"{type(kwargs['settings']['PYBTEX_SOURCES'])}" ) - - self.bibdata = utils.load( - kwargs["settings"].get("PYBTEX_SOURCES", []), [kwargs["path"]] - ) - - if not self.bibdata: - logger.info("`pybtex` (generator) plugin detected no entries.") + self.bibdata = [] else: - sources = len(self.bibdata) - entries = sum([len(k.entries) for k in self.bibdata]) - logger.info( - f"`pybtex` plugin detected {entries} entries spread across " - f"{sources} source file(s)." + self.bibdata = utils.load( + kwargs["settings"].get("PYBTEX_SOURCES", []), [kwargs["path"]] ) + if not self.bibdata: + 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"`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 @@ -95,6 +95,7 @@ def generate_context(self): self.bibdata, self.settings.get("PYBTEX_FORMAT_STYLE", "plain"), self.settings.get("PYBTEX_ADD_ENTRY_FIELDS", []), + self.settings.get("PYGMENTS_RST_OPTIONS", {}), ) # get the right formatting for the date diff --git a/src/pelican/plugins/pybtex/injector.py b/src/pelican/plugins/pybtex/injector.py index 36defe0..109de78 100644 --- a/src/pelican/plugins/pybtex/injector.py +++ b/src/pelican/plugins/pybtex/injector.py @@ -1,5 +1,4 @@ # SPDX-FileCopyrightText: Copyright © 2024 André Anjos -# # SPDX-License-Identifier: MIT """Add references to a parsed content page.""" @@ -16,7 +15,10 @@ logger = logging.getLogger(__name__) -CITE_RE = re.compile(r"\[@(@)?\s*(\w.*?)\s*\]") +# acceptable bibtex key characters: +# https://tex.stackexchange.com/questions/408530/what-characters-are-allowed-to-use-as-delimiters-for-bibtex-keys +BIBTEX_KEY_RE = r"[!\"\$&'\(\)\*\+\-\.\/:;\<\=\>\?@\[\]\^\`\|\w]+" +CITE_RE = re.compile(rf"\[(@|@)(@|@)?\s*({BIBTEX_KEY_RE})\s*\]") class PybtexInjector: @@ -98,11 +100,24 @@ def resolve_bibliography(self, content: pelican.contents.Content): template_name = "bibliography" template = self.generator.get_template(template_name) database = pybtex.database.BibliographyData(entries=content_entries) + + style = self.generator.settings.get("PYBTEX_FORMAT_STYLE", "plain") + if "pybtex_format_style" in content.metadata: + style = content.metadata.pop("pybtex_format_style").strip() or style + + add_entry_fields = self.generator.settings.get("PYBTEX_ADD_ENTRY_FIELDS", []) + if "pybtex_add_entry_fields" in content.metadata: + add_entry_fields = ( + content.metadata.pop("pybtex_add_entry_fields").strip() + or add_entry_fields + ) + context = { "publications": utils.generate_context( [database], - self.generator.settings.get("PYBTEX_FORMAT_STYLE", "plain"), - self.generator.settings.get("PYBTEX_ADD_ENTRY_FIELDS", []), + style, + add_entry_fields, + self.generator.settings.get("PYGMENTS_RST_OPTIONS", {}), ) } content._content += template.render(context) # noqa: SLF001 @@ -118,6 +133,6 @@ def _re_repl(matchobj): f'[{lk[key]}]' ) - return f'[{key}?]' + return f'[{key}?]' content._content = CITE_RE.sub(_re_repl, content._content) # noqa: SLF001 diff --git a/src/pelican/plugins/pybtex/signals.py b/src/pelican/plugins/pybtex/signals.py index f76754c..a611948 100644 --- a/src/pelican/plugins/pybtex/signals.py +++ b/src/pelican/plugins/pybtex/signals.py @@ -1,5 +1,4 @@ # SPDX-FileCopyrightText: Copyright © 2024 André Anjos -# # SPDX-License-Identifier: MIT """Signals produced by this plugin.""" diff --git a/src/pelican/plugins/pybtex/templates/bibliography.html b/src/pelican/plugins/pybtex/templates/bibliography.html index c623fde..2cfc6e3 100644 --- a/src/pelican/plugins/pybtex/templates/bibliography.html +++ b/src/pelican/plugins/pybtex/templates/bibliography.html @@ -2,8 +2,6 @@ SPDX-FileCopyrightText: Copyright © 2024 André Anjos SPDX-License-Identifier: MIT --> -
-

Bibliography

{% block before_bibliography %} diff --git a/src/pelican/plugins/pybtex/utils.py b/src/pelican/plugins/pybtex/utils.py index e1b43a6..f69bd29 100644 --- a/src/pelican/plugins/pybtex/utils.py +++ b/src/pelican/plugins/pybtex/utils.py @@ -1,5 +1,4 @@ # SPDX-FileCopyrightText: Copyright © 2024 André Anjos -# # SPDX-License-Identifier: MIT """Common utilities to load and format bibliography entries.""" @@ -69,7 +68,10 @@ def load( p = _resolve(pathlib.Path(k), paths) if not p.exists(): - logger.error(f"`pybtex` file `{p}` does not exist") + logger.error( + f"`pybtex` file `{p}` cannot be found on path " + f"`{':'.join([str(k) for k in paths])}`" + ) else: try: retval.append(bibtex_parser.parse_file(p)) @@ -98,7 +100,9 @@ def format_bibtex(entry: pybtex.database.Entry) -> str: ) -def format_bibtex_pygments(entry: pybtex.database.Entry) -> str: +def format_bibtex_pygments( + entry: pybtex.database.Entry, html_formatter_options: dict[str, typing.Any] +) -> str: """Format a pybtex database entry into a highlight-able HTML/BibTeX representation. @@ -106,6 +110,9 @@ def format_bibtex_pygments(entry: pybtex.database.Entry) -> str: ---------- entry The entry to be formatted. + html_formatter_options + A dictionary containing HTML formatting options supported by + :py:class:`pygments.formatters.HtmlFormatter`. Returns ------- @@ -115,7 +122,7 @@ def format_bibtex_pygments(entry: pybtex.database.Entry) -> str: return pygments.highlight( format_bibtex(entry), pygments.lexers.BibTeXLexer(), - pygments.formatters.HtmlFormatter(), + pygments.formatters.HtmlFormatter(**html_formatter_options), ) @@ -123,6 +130,7 @@ 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]]: """Generate a list of dictionaries given a set of bibliography databases. @@ -135,6 +143,9 @@ def generate_context( "plain", "alpha", "unsrt", and "unsrtalpha"). extra_fields Extra fields to be preserved (verbatim) from each entry, if present. + html_formatter_options + A dictionary containing HTML formatting options supported by + :py:class:`pygments.formatters.HtmlFormatter`. Returns ------- @@ -189,7 +200,7 @@ def generate_context( "key": formatted_entry.key, "year": entry.fields.get("year"), "html": formatted_entry.text.render(backend), - "bibtex": format_bibtex_pygments(entry), + "bibtex": format_bibtex_pygments(entry, html_formatter_options), } ) diff --git a/tests/conftest.py b/tests/conftest.py index 23a4b21..7b1798e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -78,9 +78,10 @@ def setup_pelican( # pelican overrides the default logging class to `pelican.log.FatalLogger`, which # includes a de-duplication filter. Subsequent identical messages are # automoatically suppressed. The next line disables the suppression. - typing.cast( - FatalLogger, logging.getLogger("pelican.plugins.pybtex.generator") - ).disable_filter() + for mod in ["generator", "injector", "utils"]: + typing.cast( + FatalLogger, logging.getLogger(f"pelican.plugins.pybtex.{mod}") + ).disable_filter() if (data_path / "pelicanconf.py").exists(): pelican = Pelican( diff --git a/tests/data/biblio1/content/article.bib b/tests/data/biblio-alpha/content/article.bib similarity index 100% rename from tests/data/biblio1/content/article.bib rename to tests/data/biblio-alpha/content/article.bib diff --git a/tests/data/biblio1/content/article.rst b/tests/data/biblio-alpha/content/article.rst similarity index 100% rename from tests/data/biblio1/content/article.rst rename to tests/data/biblio-alpha/content/article.rst diff --git a/tests/data/biblio-alpha/pelicanconf.py b/tests/data/biblio-alpha/pelicanconf.py new file mode 100644 index 0000000..b18187f --- /dev/null +++ b/tests/data/biblio-alpha/pelicanconf.py @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: Copyright © 2024 André Anjos +# SPDX-License-Identifier: MIT +PATH = "content" +PYBTEX_FORMAT_STYLE = "alpha" diff --git a/tests/data/biblio-at-article/content/article.bib b/tests/data/biblio-at-article/content/article.bib new file mode 100644 index 0000000..969b3cf --- /dev/null +++ b/tests/data/biblio-at-article/content/article.bib @@ -0,0 +1,25 @@ +%% SPDX-FileCopyrightText: Copyright © 2024 André Anjos +%% SPDX-License-Identifier: MIT + +@article{art1, + author = "John Doe", + title = "This incredible title", + journal = "Journal of journals and periodicals", + year = 1901, + volume = "50", + number = "6", + pages = "1143--1148", + url = "https://example.com/test/path", + foo = "A foo is a bar without the bar and foo added.", + excluded = "This will not appear as an entry.", +} + +@article{art2, + author = "Joanna Doe", + title = "This is another incredible title", + journal = "Journal of journals and periodicals", + year = 1902, + volume = "50", + number = "6", + pages = "1143--1148", +} diff --git a/tests/data/biblio-at-article/content/article.rst b/tests/data/biblio-at-article/content/article.rst new file mode 100644 index 0000000..c737ba2 --- /dev/null +++ b/tests/data/biblio-at-article/content/article.rst @@ -0,0 +1,20 @@ +.. SPDX-FileCopyrightText: Copyright © 2024 André Anjos +.. 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 [@@art2]. + +This will be turned into another citation [@@art1]. + +This will be turned into yet another citation [@@art2]. diff --git a/tests/data/biblio1/pelicanconf.py b/tests/data/biblio-at-article/pelicanconf.py similarity index 100% rename from tests/data/biblio1/pelicanconf.py rename to tests/data/biblio-at-article/pelicanconf.py diff --git a/tests/data/biblio-at-page/content/pages/page.bib b/tests/data/biblio-at-page/content/pages/page.bib new file mode 100644 index 0000000..969b3cf --- /dev/null +++ b/tests/data/biblio-at-page/content/pages/page.bib @@ -0,0 +1,25 @@ +%% SPDX-FileCopyrightText: Copyright © 2024 André Anjos +%% SPDX-License-Identifier: MIT + +@article{art1, + author = "John Doe", + title = "This incredible title", + journal = "Journal of journals and periodicals", + year = 1901, + volume = "50", + number = "6", + pages = "1143--1148", + url = "https://example.com/test/path", + foo = "A foo is a bar without the bar and foo added.", + excluded = "This will not appear as an entry.", +} + +@article{art2, + author = "Joanna Doe", + title = "This is another incredible title", + journal = "Journal of journals and periodicals", + year = 1902, + volume = "50", + number = "6", + pages = "1143--1148", +} diff --git a/tests/data/biblio-at-page/content/pages/page.rst b/tests/data/biblio-at-page/content/pages/page.rst new file mode 100644 index 0000000..6a92267 --- /dev/null +++ b/tests/data/biblio-at-page/content/pages/page.rst @@ -0,0 +1,18 @@ +.. SPDX-FileCopyrightText: Copyright © 2024 André Anjos +.. SPDX-License-Identifier: MIT + +This is a page +############## + +:date: 2010-10-03 10:20 +:modified: 2010-10-04 18:40 +:tags: test, page +:category: bibliography +:slug: page +:authors: André Anjos +:summary: Short version for index and feeds +:pybtex_sources: page.bib + +This will be turned into another citation [@@art1]. + +This will be turned into a citation [@@art2]. diff --git a/tests/data/biblio-at-page/pelicanconf.py b/tests/data/biblio-at-page/pelicanconf.py new file mode 100644 index 0000000..6d08504 --- /dev/null +++ b/tests/data/biblio-at-page/pelicanconf.py @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: Copyright © 2024 André Anjos +# SPDX-License-Identifier: MIT +PATH = "content" diff --git a/tests/data/biblio-global/content/article.rst b/tests/data/biblio-global/content/article.rst new file mode 100644 index 0000000..6c50e61 --- /dev/null +++ b/tests/data/biblio-global/content/article.rst @@ -0,0 +1,19 @@ +.. SPDX-FileCopyrightText: Copyright © 2024 André Anjos +.. 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 + +This will be turned into a citation [@@art2]. + +This will be turned into another citation [@@art1]. + +This will be turned into yet another citation [@@art2]. diff --git a/tests/data/biblio-global/content/publications.bib b/tests/data/biblio-global/content/publications.bib new file mode 100644 index 0000000..969b3cf --- /dev/null +++ b/tests/data/biblio-global/content/publications.bib @@ -0,0 +1,25 @@ +%% SPDX-FileCopyrightText: Copyright © 2024 André Anjos +%% SPDX-License-Identifier: MIT + +@article{art1, + author = "John Doe", + title = "This incredible title", + journal = "Journal of journals and periodicals", + year = 1901, + volume = "50", + number = "6", + pages = "1143--1148", + url = "https://example.com/test/path", + foo = "A foo is a bar without the bar and foo added.", + excluded = "This will not appear as an entry.", +} + +@article{art2, + author = "Joanna Doe", + title = "This is another incredible title", + journal = "Journal of journals and periodicals", + year = 1902, + volume = "50", + number = "6", + pages = "1143--1148", +} diff --git a/tests/data/biblio-global/pelicanconf.py b/tests/data/biblio-global/pelicanconf.py new file mode 100644 index 0000000..8200eab --- /dev/null +++ b/tests/data/biblio-global/pelicanconf.py @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: Copyright © 2024 André Anjos +# SPDX-License-Identifier: MIT +PATH = "content" +PYBTEX_SOURCES = ["publications.bib"] diff --git a/tests/data/biblio-missing/content/article.bib b/tests/data/biblio-missing/content/article.bib new file mode 100644 index 0000000..969b3cf --- /dev/null +++ b/tests/data/biblio-missing/content/article.bib @@ -0,0 +1,25 @@ +%% SPDX-FileCopyrightText: Copyright © 2024 André Anjos +%% SPDX-License-Identifier: MIT + +@article{art1, + author = "John Doe", + title = "This incredible title", + journal = "Journal of journals and periodicals", + year = 1901, + volume = "50", + number = "6", + pages = "1143--1148", + url = "https://example.com/test/path", + foo = "A foo is a bar without the bar and foo added.", + excluded = "This will not appear as an entry.", +} + +@article{art2, + author = "Joanna Doe", + title = "This is another incredible title", + journal = "Journal of journals and periodicals", + year = 1902, + volume = "50", + number = "6", + pages = "1143--1148", +} diff --git a/tests/data/biblio-missing/content/article.rst b/tests/data/biblio-missing/content/article.rst new file mode 100644 index 0000000..b311581 --- /dev/null +++ b/tests/data/biblio-missing/content/article.rst @@ -0,0 +1,20 @@ +.. SPDX-FileCopyrightText: Copyright © 2024 André Anjos +.. 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 [@@art2]. + +This will be turned into another citation [@@art1]. + +Here is a missing citation [@@art3]. diff --git a/tests/data/biblio-missing/pelicanconf.py b/tests/data/biblio-missing/pelicanconf.py new file mode 100644 index 0000000..6d08504 --- /dev/null +++ b/tests/data/biblio-missing/pelicanconf.py @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: Copyright © 2024 André Anjos +# SPDX-License-Identifier: MIT +PATH = "content" diff --git a/tests/data/biblio-override/content/article.bib b/tests/data/biblio-override/content/article.bib new file mode 100644 index 0000000..969b3cf --- /dev/null +++ b/tests/data/biblio-override/content/article.bib @@ -0,0 +1,25 @@ +%% SPDX-FileCopyrightText: Copyright © 2024 André Anjos +%% SPDX-License-Identifier: MIT + +@article{art1, + author = "John Doe", + title = "This incredible title", + journal = "Journal of journals and periodicals", + year = 1901, + volume = "50", + number = "6", + pages = "1143--1148", + url = "https://example.com/test/path", + foo = "A foo is a bar without the bar and foo added.", + excluded = "This will not appear as an entry.", +} + +@article{art2, + author = "Joanna Doe", + title = "This is another incredible title", + journal = "Journal of journals and periodicals", + year = 1902, + volume = "50", + number = "6", + pages = "1143--1148", +} diff --git a/tests/data/biblio-override/content/article.md b/tests/data/biblio-override/content/article.md new file mode 100644 index 0000000..5806750 --- /dev/null +++ b/tests/data/biblio-override/content/article.md @@ -0,0 +1,18 @@ +--- +title: 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 +pybtex_format_style: alpha +--- + +This will be turned into a citation [@@art2]. + +This will be turned into another citation [@@art1]. + +This will be turned into yet another citation [@@art2]. diff --git a/tests/data/biblio-override/pelicanconf.py b/tests/data/biblio-override/pelicanconf.py new file mode 100644 index 0000000..920b800 --- /dev/null +++ b/tests/data/biblio-override/pelicanconf.py @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: Copyright © 2024 André Anjos +# SPDX-License-Identifier: MIT +import pathlib + +PATH = "content" +PYBTEX_FORMAT_STYLE = "plain" +THEME_TEMPLATES_OVERRIDES = [pathlib.Path(__file__).parent / "templates"] diff --git a/tests/data/biblio-override/templates/bibliography.html b/tests/data/biblio-override/templates/bibliography.html new file mode 100644 index 0000000..71d82c9 --- /dev/null +++ b/tests/data/biblio-override/templates/bibliography.html @@ -0,0 +1,13 @@ + +

References

+ +{% block bibliography_pybtex %} +
    +{% for item in publications %} +
  • [{{ item.label }}] {{ item.html }}
  • +{% endfor %} +
+{% endblock %} diff --git a/tests/data/missing-file/content/.gitkeep b/tests/data/missing-file/content/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/data/missing-file/pelicanconf.py b/tests/data/missing-file/pelicanconf.py new file mode 100644 index 0000000..14909ba --- /dev/null +++ b/tests/data/missing-file/pelicanconf.py @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: Copyright © 2024 André Anjos +# SPDX-License-Identifier: MIT +PATH = "content" +PYBTEX_SOURCES = ["does-not-exist.bib"] diff --git a/tests/data/no-biblio/content/article.rst b/tests/data/no-biblio/content/article.rst new file mode 100644 index 0000000..b8e00c4 --- /dev/null +++ b/tests/data/no-biblio/content/article.rst @@ -0,0 +1,15 @@ +.. SPDX-FileCopyrightText: Copyright © 2024 André Anjos +.. 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 + +This article contains no bibliography. diff --git a/tests/data/no-biblio/pelicanconf.py b/tests/data/no-biblio/pelicanconf.py new file mode 100644 index 0000000..6d08504 --- /dev/null +++ b/tests/data/no-biblio/pelicanconf.py @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: Copyright © 2024 André Anjos +# SPDX-License-Identifier: MIT +PATH = "content" diff --git a/tests/test_pybtex.py b/tests/test_pybtex.py index 2ba7bd7..14b15de 100644 --- a/tests/test_pybtex.py +++ b/tests/test_pybtex.py @@ -1,5 +1,4 @@ # SPDX-FileCopyrightText: Copyright © 2024 André Anjos -# # SPDX-License-Identifier: MIT import logging @@ -59,7 +58,9 @@ def _assert_log_no_errors( @pytest.mark.parametrize("subdir", ["empty"]) -def test_empty(setup_pelican: tuple[list[logging.LogRecord], pathlib.Path]): +def test_publications_empty( + setup_pelican: tuple[list[logging.LogRecord], pathlib.Path], +): records, pelican_output = setup_pelican publications_html = pelican_output / "publications.html" @@ -71,8 +72,30 @@ def test_empty(setup_pelican: tuple[list[logging.LogRecord], pathlib.Path]): ) +@pytest.mark.parametrize("subdir", ["missing-file"]) +def test_publications_missing_file( + setup_pelican: tuple[list[logging.LogRecord], pathlib.Path], +): + records, pelican_output = setup_pelican + + publications_html = pelican_output / "publications.html" + assert not publications_html.exists() + + _assert_log_contains( + records, + message="`pybtex` file `does-not-exist.bib` cannot be found on path", + level=logging.ERROR, + count=1, + ) + _assert_log_contains( + records, message="plugin detected no entries", level=logging.INFO, count=1 + ) + + @pytest.mark.parametrize("subdir", ["simple"]) -def test_simple(setup_pelican: tuple[list[logging.LogRecord], pathlib.Path]): +def test_publications_simple( + setup_pelican: tuple[list[logging.LogRecord], pathlib.Path], +): records, pelican_output = setup_pelican publications_html = pelican_output / "publications.html" @@ -126,7 +149,7 @@ def test_simple(setup_pelican: tuple[list[logging.LogRecord], pathlib.Path]): @pytest.mark.parametrize("subdir", ["urls"]) -def test_urls(setup_pelican: tuple[list[logging.LogRecord], pathlib.Path]): +def test_publications_urls(setup_pelican: tuple[list[logging.LogRecord], pathlib.Path]): records, pelican_output = setup_pelican publications_html = pelican_output / "publications.html" @@ -170,7 +193,9 @@ def test_urls(setup_pelican: tuple[list[logging.LogRecord], pathlib.Path]): @pytest.mark.parametrize("subdir", ["override"]) -def test_override(setup_pelican: tuple[list[logging.LogRecord], pathlib.Path]): +def test_publications_template_override( + setup_pelican: tuple[list[logging.LogRecord], pathlib.Path], +): records, pelican_output = setup_pelican publications_html = pelican_output / "publications.html" @@ -195,8 +220,342 @@ def test_override(setup_pelican: tuple[list[logging.LogRecord], pathlib.Path]): ) -@pytest.mark.parametrize("subdir", ["biblio1"]) -def test_biblio1(setup_pelican: tuple[list[logging.LogRecord], pathlib.Path]): +@pytest.mark.parametrize("subdir", ["biblio-at-article"]) +def test_biblio_at_article(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 = [ + "art2", + "art1", + ] + + para = soup.find_all("p") + + text_to_be_checked = ( + (publication_keys[0], "1", para[0]), + (publication_keys[1], "2", para[1]), + (publication_keys[0], "1", para[2]), + ) + + 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 details[1].attrs["id"].endswith(publication_keys[1]) + + _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 + + 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 = [ + "art2", + "art1", + ] + + para = soup.find_all("p") + + text_to_be_checked = ( + (publication_keys[0], "Doe02", para[0]), + (publication_keys[1], "Doe01", para[1]), + (publication_keys[0], "Doe02", para[2]), + ) + + 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 details[1].attrs["id"].endswith(publication_keys[1]) + + _assert_log_no_errors(records) + _assert_log_contains( + records, message="plugin detected no entries", level=logging.INFO, count=1 + ) + + +@pytest.mark.parametrize("subdir", ["biblio-at-page"]) +def test_biblio_at_page(setup_pelican: tuple[list[logging.LogRecord], pathlib.Path]): + records, pelican_output = setup_pelican + + page_html = pelican_output / "pages" / "page.html" + assert page_html.exists() + + publications_html = pelican_output / "publications.html" + assert not publications_html.exists() + + with page_html.open() as f: + soup = BeautifulSoup(f, "html.parser") + + publication_keys = [ + "art1", + "art2", + ] + + para = soup.find_all("p") + + text_to_be_checked = ( + (publication_keys[0], "1", para[0]), + (publication_keys[1], "2", para[1]), + ) + + 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 details[1].attrs["id"].endswith(publication_keys[1]) + + _assert_log_no_errors(records) + _assert_log_contains( + records, message="plugin detected no entries", level=logging.INFO, count=1 + ) + + +@pytest.mark.parametrize("subdir", ["biblio-missing"]) +def test_biblio_missing(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 = [ + "art2", + "art1", + ] + + para = soup.find_all("p") + + text_to_be_checked = ( + (publication_keys[0], "1", para[0]), + (publication_keys[1], "2", para[1]), + ) + + 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 + + # check for the missing item + span = para[2].find_all("span") + assert len(span) == 1 + assert span[0].attrs["title"] == "cannot find citation art3" + assert span[0].text == "[art3?]" + + 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 details[1].attrs["id"].endswith(publication_keys[1]) + + _assert_log_contains( + records, message="Cannot find pybtex key `art3`", level=logging.ERROR, count=1 + ) + _assert_log_contains( + records, message="plugin detected no entries", level=logging.INFO, count=1 + ) + + +@pytest.mark.parametrize("subdir", ["no-biblio"]) +def test_no_biblio(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") + + h2 = soup.find_all("h2") + assert len(h2) == 1 + + assert h2[0].text.strip() != "Bibliography" + + _assert_log_contains( + records, message="plugin detected no entries", level=logging.INFO, count=1 + ) + + +@pytest.mark.parametrize("subdir", ["biblio-global"]) +def test_biblio_global(setup_pelican: tuple[list[logging.LogRecord], pathlib.Path]): + records, pelican_output = setup_pelican + + article_html = pelican_output / "article.html" + assert article_html.exists() + + with article_html.open() as f: + soup = BeautifulSoup(f, "html.parser") + + publication_keys = [ + "art2", + "art1", + ] + + para = soup.find_all("p") + + text_to_be_checked = ( + (publication_keys[0], "1", para[0]), + (publication_keys[1], "2", para[1]), + (publication_keys[0], "1", para[2]), + ) + + 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 details[1].attrs["id"].endswith(publication_keys[1]) + + 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 + + details = div[0].find_all("details") + assert len(details) == len(publication_keys) + + assert details[0].attrs["id"].endswith(publication_keys[0]) + items = details[0].find_all("li") + assert len(items) == 0 + + assert details[1].attrs["id"].endswith(publication_keys[1]) + items = details[1].find_all("li") + assert len(items) == 0 + + _assert_log_no_errors(records) + _assert_log_contains( + records, + message="plugin detected 2 entries spread across 1 source file", + level=logging.INFO, + count=1, + ) + + +@pytest.mark.parametrize("subdir", ["biblio-override"]) +def test_biblio_override(setup_pelican: tuple[list[logging.LogRecord], pathlib.Path]): records, pelican_output = setup_pelican article_html = pelican_output / "article.html" @@ -205,8 +564,46 @@ def test_biblio1(setup_pelican: tuple[list[logging.LogRecord], pathlib.Path]): publications_html = pelican_output / "publications.html" assert not publications_html.exists() - # with article_html.open() as f: - # soup = BeautifulSoup(f, "html.parser") + with article_html.open() as f: + soup = BeautifulSoup(f, "html.parser") + + publication_keys = [ + "art2", + "art1", + ] + + para = soup.find_all("p") + + text_to_be_checked = ( + (publication_keys[0], "Doe02", para[0]), + (publication_keys[1], "Doe01", para[1]), + (publication_keys[0], "Doe02", para[2]), + ) + + 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 + + h3 = soup.find_all("h3") + assert len(h3) == 1 + + assert h3[0].text.strip() == "References" + + ul = soup.find_all("ul", id="pybtex") + assert len(ul) == 1 + + li = ul[0].find_all("li") + assert len(li) == len(publication_keys) + + assert li[0].attrs["id"].endswith(publication_keys[0]) + assert li[0].text.startswith("[Doe02]") + assert li[1].attrs["id"].endswith(publication_keys[1]) + assert li[1].text.startswith("[Doe01]") _assert_log_no_errors(records) _assert_log_contains(