bibtex-linter 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bibtex_linter-1.0.0/.github/workflows/ci.yaml +52 -0
- bibtex_linter-1.0.0/.github/workflows/release.yaml +36 -0
- bibtex_linter-1.0.0/.gitignore +183 -0
- bibtex_linter-1.0.0/LICENSE +21 -0
- bibtex_linter-1.0.0/PKG-INFO +147 -0
- bibtex_linter-1.0.0/README.md +132 -0
- bibtex_linter-1.0.0/bibtex_linter/__init__.py +0 -0
- bibtex_linter-1.0.0/bibtex_linter/default_rules.py +270 -0
- bibtex_linter-1.0.0/bibtex_linter/main.py +71 -0
- bibtex_linter-1.0.0/bibtex_linter/parser.py +200 -0
- bibtex_linter-1.0.0/bibtex_linter/py.typed +0 -0
- bibtex_linter-1.0.0/bibtex_linter/verification.py +71 -0
- bibtex_linter-1.0.0/bibtex_linter/version.py +21 -0
- bibtex_linter-1.0.0/bibtex_linter.egg-info/PKG-INFO +147 -0
- bibtex_linter-1.0.0/bibtex_linter.egg-info/SOURCES.txt +24 -0
- bibtex_linter-1.0.0/bibtex_linter.egg-info/dependency_links.txt +1 -0
- bibtex_linter-1.0.0/bibtex_linter.egg-info/entry_points.txt +2 -0
- bibtex_linter-1.0.0/bibtex_linter.egg-info/requires.txt +5 -0
- bibtex_linter-1.0.0/bibtex_linter.egg-info/top_level.txt +1 -0
- bibtex_linter-1.0.0/pyproject.toml +45 -0
- bibtex_linter-1.0.0/setup.cfg +4 -0
- bibtex_linter-1.0.0/test/__init__.py +0 -0
- bibtex_linter-1.0.0/test/test_parser.py +341 -0
- bibtex_linter-1.0.0/test/test_refs.bib +89 -0
- bibtex_linter-1.0.0/test/test_template/IEEEtran_observations.md +210 -0
- bibtex_linter-1.0.0/test/test_template/maximal_example_refs.bib +152 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
pull_request:
|
|
8
|
+
|
|
9
|
+
env:
|
|
10
|
+
X_PYTHON_MIN_VERSION: "3.11"
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
test:
|
|
14
|
+
# This job runs the unittests on the python versions specified down at the matrix
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
- name: Set up Python ${{ env.X_PYTHON_MIN_VERSION }}
|
|
19
|
+
uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: ${{env.X_PYTHON_MIN_VERSION }}
|
|
22
|
+
- name: Install Python dependencies
|
|
23
|
+
run: |
|
|
24
|
+
python -m pip install --upgrade pip
|
|
25
|
+
pip install .[dev]
|
|
26
|
+
- name: Test with coverage + unittest
|
|
27
|
+
run: |
|
|
28
|
+
coverage run --source=bibtex_linter -m unittest
|
|
29
|
+
- name: Report test coverage
|
|
30
|
+
if: ${{ always() }}
|
|
31
|
+
run: |
|
|
32
|
+
coverage report -m
|
|
33
|
+
|
|
34
|
+
static-analysis:
|
|
35
|
+
# This job runs static code analysis, namely pycodestyle and mypy
|
|
36
|
+
runs-on: ubuntu-latest
|
|
37
|
+
steps:
|
|
38
|
+
- uses: actions/checkout@v4
|
|
39
|
+
- name: Set up Python ${{ env.X_PYTHON_MIN_VERSION }}
|
|
40
|
+
uses: actions/setup-python@v5
|
|
41
|
+
with:
|
|
42
|
+
python-version: ${{ env.X_PYTHON_MIN_VERSION }}
|
|
43
|
+
- name: Install Python dependencies
|
|
44
|
+
run: |
|
|
45
|
+
python -m pip install --upgrade pip
|
|
46
|
+
pip install .[dev]
|
|
47
|
+
- name: Check typing with MyPy
|
|
48
|
+
run: |
|
|
49
|
+
mypy --strict bibtex_linter test
|
|
50
|
+
- name: Check code style with PyCodestyle
|
|
51
|
+
run: |
|
|
52
|
+
pycodestyle --count --max-line-length 120 bibtex_linter test
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
env:
|
|
8
|
+
X_PYTHON_MIN_VERSION: "3.11"
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
publish:
|
|
12
|
+
name: Upload release to PyPI
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
environment: release
|
|
15
|
+
permissions:
|
|
16
|
+
# IMPORTANT: this permission is mandatory for trusted publishing
|
|
17
|
+
id-token: write
|
|
18
|
+
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
- name: Set up Python ${{ env.X_PYTHON_MIN_VERSION }}
|
|
22
|
+
uses: actions/setup-python@v5
|
|
23
|
+
with:
|
|
24
|
+
python-version: ${{env.X_PYTHON_MIN_VERSION }}
|
|
25
|
+
- name: Install Python dependencies
|
|
26
|
+
run: |
|
|
27
|
+
python -m pip install --upgrade pip
|
|
28
|
+
pip install build
|
|
29
|
+
- name: Create source and wheel dist
|
|
30
|
+
# (2024-12-11, s-heppner)
|
|
31
|
+
# The PyPI Action expects the dist files in a toplevel `/dist` directory,
|
|
32
|
+
# so we have to specify this as output directory here.
|
|
33
|
+
run: |
|
|
34
|
+
python -m build --outdir dist
|
|
35
|
+
- name: Publish distribution to PyPI
|
|
36
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py,cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
#Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
#uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
#poetry.lock
|
|
109
|
+
|
|
110
|
+
# pdm
|
|
111
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
112
|
+
#pdm.lock
|
|
113
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
114
|
+
# in version control.
|
|
115
|
+
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
|
116
|
+
.pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
121
|
+
__pypackages__/
|
|
122
|
+
|
|
123
|
+
# Celery stuff
|
|
124
|
+
celerybeat-schedule
|
|
125
|
+
celerybeat.pid
|
|
126
|
+
|
|
127
|
+
# SageMath parsed files
|
|
128
|
+
*.sage.py
|
|
129
|
+
|
|
130
|
+
# Environments
|
|
131
|
+
.env
|
|
132
|
+
.venv
|
|
133
|
+
env/
|
|
134
|
+
venv/
|
|
135
|
+
ENV/
|
|
136
|
+
env.bak/
|
|
137
|
+
venv.bak/
|
|
138
|
+
|
|
139
|
+
# Spyder project settings
|
|
140
|
+
.spyderproject
|
|
141
|
+
.spyproject
|
|
142
|
+
|
|
143
|
+
# Rope project settings
|
|
144
|
+
.ropeproject
|
|
145
|
+
|
|
146
|
+
# mkdocs documentation
|
|
147
|
+
/site
|
|
148
|
+
|
|
149
|
+
# mypy
|
|
150
|
+
.mypy_cache/
|
|
151
|
+
.dmypy.json
|
|
152
|
+
dmypy.json
|
|
153
|
+
|
|
154
|
+
# Pyre type checker
|
|
155
|
+
.pyre/
|
|
156
|
+
|
|
157
|
+
# pytype static type analyzer
|
|
158
|
+
.pytype/
|
|
159
|
+
|
|
160
|
+
# Cython debug symbols
|
|
161
|
+
cython_debug/
|
|
162
|
+
|
|
163
|
+
# PyCharm
|
|
164
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
165
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
166
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
167
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
168
|
+
.idea/
|
|
169
|
+
|
|
170
|
+
# Ruff stuff:
|
|
171
|
+
.ruff_cache/
|
|
172
|
+
|
|
173
|
+
# PyPI configuration file
|
|
174
|
+
.pypirc
|
|
175
|
+
|
|
176
|
+
# Ignore the tex files in the `test_template` directory, since we do not want to accidentally publish an official
|
|
177
|
+
# template under a different license
|
|
178
|
+
test/test_template/*.tex
|
|
179
|
+
test/test_template/*.pdf
|
|
180
|
+
test/test_template/*.cls
|
|
181
|
+
|
|
182
|
+
# Ignore the version.py file
|
|
183
|
+
bibtex_linter/version.py
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 s-heppner
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bibtex_linter
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A Python tool to parse BibTeX entries and run (custom) checks on them.
|
|
5
|
+
Author-email: Sebastian Heppner <mail@s-heppner.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Provides-Extra: dev
|
|
11
|
+
Requires-Dist: mypy; extra == "dev"
|
|
12
|
+
Requires-Dist: pycodestyle; extra == "dev"
|
|
13
|
+
Requires-Dist: coverage; extra == "dev"
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# python-bibtex-linter
|
|
17
|
+
A Python tool to parse BibTeX entries and run (custom) checks on them.
|
|
18
|
+
|
|
19
|
+
```commandline
|
|
20
|
+
> bibtex_linter refs.bib
|
|
21
|
+
|
|
22
|
+
Entry 'SomeBook' of type 'BOOK' failed verification:
|
|
23
|
+
❌ Invariant Violations:
|
|
24
|
+
- Entry 'SomeBook' misses the following required fields: [publisher]
|
|
25
|
+
- Entry 'SomeBook' has fields present that would be omitted in the compiled document: [url]. This could lead to a loss of information.
|
|
26
|
+
|
|
27
|
+
Found 2 invariant violations in 17 entries.
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Motivation
|
|
31
|
+
I've always assumed that I just needed to take care that my `references.bib` file was in order, that as many of the
|
|
32
|
+
fields of the entries in there were filled, and then I could easily use them to create standard-conforming citations in
|
|
33
|
+
my LaTeX documents.
|
|
34
|
+
As it turns out, a lot of different citation styles omit various fields, and it's an overall mess.
|
|
35
|
+
Therefore, I created this tool (in Python, since that's what I know best), that can parse the entries and then performs
|
|
36
|
+
arbitrary (self-defined) invariant checks on them.
|
|
37
|
+
|
|
38
|
+
In my field the most used citation style is `IEEEtran` so this is how I've defined the default rules of the script.
|
|
39
|
+
I've written down the observations on which the rules are based [here](test/test_template/IEEEtran_observations.md).
|
|
40
|
+
|
|
41
|
+
It is however relatively easy to define your own [custom ruleset](#advanced-custom-rulesets), should the need arise.
|
|
42
|
+
|
|
43
|
+
## How to use:
|
|
44
|
+
First we need to install the tool, I recommend to use [pipx](https://github.com/pypa/pipx) for that:
|
|
45
|
+
```commandline
|
|
46
|
+
pipx install .
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Basic Usage
|
|
50
|
+
Then you can call the script the following way:
|
|
51
|
+
```commandline
|
|
52
|
+
bibtex_linter path/to/refs.bib
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The script will parse the file, perform the checks and print out the results.
|
|
56
|
+
|
|
57
|
+
> [!note]
|
|
58
|
+
> As the `bibtex_linter` returns exit code `0`, if all checks have passed and `1`, if violations were found,
|
|
59
|
+
> you could also use it in the CI of your LaTeX projects.
|
|
60
|
+
|
|
61
|
+
### Advanced: Custom Rulesets
|
|
62
|
+
|
|
63
|
+
> [!warning]
|
|
64
|
+
> Custom rulesets are plain Python code and will be executed on your machine.
|
|
65
|
+
> If you wouldn't trust running `python3 my_rules.py`, you shouldn't use it with `bibtex_linter`.
|
|
66
|
+
> **Only use rulesets from sources you trust!**
|
|
67
|
+
|
|
68
|
+
It is also possible to define your own rules inside a Python file.
|
|
69
|
+
Let's call it `my_own_rules.py`.
|
|
70
|
+
Creating your own rule is as simple as:
|
|
71
|
+
|
|
72
|
+
```Python
|
|
73
|
+
from typing import List
|
|
74
|
+
|
|
75
|
+
from bibtex_linter.parser import BibTeXEntry, EntryType
|
|
76
|
+
from bibtex_linter.verification import linter_rule
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@linter_rule(entry_type=EntryType.ARTICLE)
|
|
80
|
+
def check_article(entry: BibTeXEntry) -> List[str]:
|
|
81
|
+
"""
|
|
82
|
+
Check that a `BibTeXEntry` has a nonempty `author` field.
|
|
83
|
+
|
|
84
|
+
:param entry: The BibTeXEntry
|
|
85
|
+
:return: A list of string descriptions of rule violations for this entry.
|
|
86
|
+
"""
|
|
87
|
+
if not entry.fields.get("author"):
|
|
88
|
+
return [f"Entry '{entry.name}' misses the required field author!"]
|
|
89
|
+
return []
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
As you can see, we created a method and designated that it is a linter rule by using the `@linter_rule` decorator.
|
|
93
|
+
The method needs to have a specific interface:
|
|
94
|
+
It needs to take the `BibTeXEntry` to be checked as input argument, and it needs to return a List of strings explaining
|
|
95
|
+
the rule violations for that `BibTeXEntry`.
|
|
96
|
+
If there are no rule violations, it should return an empty list.
|
|
97
|
+
|
|
98
|
+
This rule only gets executed for entries of the `ARTICLE` type, as specified by the decorator argument.
|
|
99
|
+
If we left the `entry_type` argument empty, this check would be executed on all entries.
|
|
100
|
+
|
|
101
|
+
For more inspiration on what you could define as your custom rules, have a look into `bibtex_linter/default_rules.py`.
|
|
102
|
+
After defining the rules in `my_own_rules.py`, we can execute them on a BibTeX file like this:
|
|
103
|
+
|
|
104
|
+
```commandline
|
|
105
|
+
bibtex_linter path/to/refs.bib path/to/my_own_rules.py
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Let's reiterate the warning from beforehand:
|
|
109
|
+
|
|
110
|
+
> [!warning]
|
|
111
|
+
> Custom rulesets are plain Python code and will be executed on your machine.
|
|
112
|
+
> If you wouldn't trust running `python3 my_rules.py`, you shouldn't use it with `bibtex_linter`.
|
|
113
|
+
> **Only use rulesets from sources you trust!**
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
## Definition of used Terms
|
|
117
|
+
If you're unfamiliar with BibTex, here's a short list of terms, so that you can better understand the output of the
|
|
118
|
+
`bibtex_linter`.
|
|
119
|
+
|
|
120
|
+
### Entry
|
|
121
|
+
An entry to the BibTeX file:
|
|
122
|
+
```LaTeX
|
|
123
|
+
@article{basic_case,
|
|
124
|
+
author = {Test author},
|
|
125
|
+
title = {Standard field format},
|
|
126
|
+
year = {2020}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Field
|
|
131
|
+
A field inside an entry, consists of a `key` and a `value`:
|
|
132
|
+
```LaTeX
|
|
133
|
+
author = {Test author},
|
|
134
|
+
```
|
|
135
|
+
In this case, `"author"` would be the `key` and `"Test author"` the `value`.
|
|
136
|
+
|
|
137
|
+
> [!note]
|
|
138
|
+
> There are many different ways of wrapping the value text, from `{}` via `{{}}` or `""`.
|
|
139
|
+
> This tool removes these wrapping characters and only considers the text inside of them as `value`.
|
|
140
|
+
|
|
141
|
+
### Entry Type
|
|
142
|
+
The entry type specifies the available fields and is written behind the `@` and in front of the first `{` of an entry:
|
|
143
|
+
```LaTeX
|
|
144
|
+
@article{...}
|
|
145
|
+
@conference{...}
|
|
146
|
+
@online{...}
|
|
147
|
+
```
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# python-bibtex-linter
|
|
2
|
+
A Python tool to parse BibTeX entries and run (custom) checks on them.
|
|
3
|
+
|
|
4
|
+
```commandline
|
|
5
|
+
> bibtex_linter refs.bib
|
|
6
|
+
|
|
7
|
+
Entry 'SomeBook' of type 'BOOK' failed verification:
|
|
8
|
+
❌ Invariant Violations:
|
|
9
|
+
- Entry 'SomeBook' misses the following required fields: [publisher]
|
|
10
|
+
- Entry 'SomeBook' has fields present that would be omitted in the compiled document: [url]. This could lead to a loss of information.
|
|
11
|
+
|
|
12
|
+
Found 2 invariant violations in 17 entries.
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Motivation
|
|
16
|
+
I've always assumed that I just needed to take care that my `references.bib` file was in order, that as many of the
|
|
17
|
+
fields of the entries in there were filled, and then I could easily use them to create standard-conforming citations in
|
|
18
|
+
my LaTeX documents.
|
|
19
|
+
As it turns out, a lot of different citation styles omit various fields, and it's an overall mess.
|
|
20
|
+
Therefore, I created this tool (in Python, since that's what I know best), that can parse the entries and then performs
|
|
21
|
+
arbitrary (self-defined) invariant checks on them.
|
|
22
|
+
|
|
23
|
+
In my field the most used citation style is `IEEEtran` so this is how I've defined the default rules of the script.
|
|
24
|
+
I've written down the observations on which the rules are based [here](test/test_template/IEEEtran_observations.md).
|
|
25
|
+
|
|
26
|
+
It is however relatively easy to define your own [custom ruleset](#advanced-custom-rulesets), should the need arise.
|
|
27
|
+
|
|
28
|
+
## How to use:
|
|
29
|
+
First we need to install the tool, I recommend to use [pipx](https://github.com/pypa/pipx) for that:
|
|
30
|
+
```commandline
|
|
31
|
+
pipx install .
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Basic Usage
|
|
35
|
+
Then you can call the script the following way:
|
|
36
|
+
```commandline
|
|
37
|
+
bibtex_linter path/to/refs.bib
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The script will parse the file, perform the checks and print out the results.
|
|
41
|
+
|
|
42
|
+
> [!note]
|
|
43
|
+
> As the `bibtex_linter` returns exit code `0`, if all checks have passed and `1`, if violations were found,
|
|
44
|
+
> you could also use it in the CI of your LaTeX projects.
|
|
45
|
+
|
|
46
|
+
### Advanced: Custom Rulesets
|
|
47
|
+
|
|
48
|
+
> [!warning]
|
|
49
|
+
> Custom rulesets are plain Python code and will be executed on your machine.
|
|
50
|
+
> If you wouldn't trust running `python3 my_rules.py`, you shouldn't use it with `bibtex_linter`.
|
|
51
|
+
> **Only use rulesets from sources you trust!**
|
|
52
|
+
|
|
53
|
+
It is also possible to define your own rules inside a Python file.
|
|
54
|
+
Let's call it `my_own_rules.py`.
|
|
55
|
+
Creating your own rule is as simple as:
|
|
56
|
+
|
|
57
|
+
```Python
|
|
58
|
+
from typing import List
|
|
59
|
+
|
|
60
|
+
from bibtex_linter.parser import BibTeXEntry, EntryType
|
|
61
|
+
from bibtex_linter.verification import linter_rule
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@linter_rule(entry_type=EntryType.ARTICLE)
|
|
65
|
+
def check_article(entry: BibTeXEntry) -> List[str]:
|
|
66
|
+
"""
|
|
67
|
+
Check that a `BibTeXEntry` has a nonempty `author` field.
|
|
68
|
+
|
|
69
|
+
:param entry: The BibTeXEntry
|
|
70
|
+
:return: A list of string descriptions of rule violations for this entry.
|
|
71
|
+
"""
|
|
72
|
+
if not entry.fields.get("author"):
|
|
73
|
+
return [f"Entry '{entry.name}' misses the required field author!"]
|
|
74
|
+
return []
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
As you can see, we created a method and designated that it is a linter rule by using the `@linter_rule` decorator.
|
|
78
|
+
The method needs to have a specific interface:
|
|
79
|
+
It needs to take the `BibTeXEntry` to be checked as input argument, and it needs to return a List of strings explaining
|
|
80
|
+
the rule violations for that `BibTeXEntry`.
|
|
81
|
+
If there are no rule violations, it should return an empty list.
|
|
82
|
+
|
|
83
|
+
This rule only gets executed for entries of the `ARTICLE` type, as specified by the decorator argument.
|
|
84
|
+
If we left the `entry_type` argument empty, this check would be executed on all entries.
|
|
85
|
+
|
|
86
|
+
For more inspiration on what you could define as your custom rules, have a look into `bibtex_linter/default_rules.py`.
|
|
87
|
+
After defining the rules in `my_own_rules.py`, we can execute them on a BibTeX file like this:
|
|
88
|
+
|
|
89
|
+
```commandline
|
|
90
|
+
bibtex_linter path/to/refs.bib path/to/my_own_rules.py
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Let's reiterate the warning from beforehand:
|
|
94
|
+
|
|
95
|
+
> [!warning]
|
|
96
|
+
> Custom rulesets are plain Python code and will be executed on your machine.
|
|
97
|
+
> If you wouldn't trust running `python3 my_rules.py`, you shouldn't use it with `bibtex_linter`.
|
|
98
|
+
> **Only use rulesets from sources you trust!**
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
## Definition of used Terms
|
|
102
|
+
If you're unfamiliar with BibTex, here's a short list of terms, so that you can better understand the output of the
|
|
103
|
+
`bibtex_linter`.
|
|
104
|
+
|
|
105
|
+
### Entry
|
|
106
|
+
An entry to the BibTeX file:
|
|
107
|
+
```LaTeX
|
|
108
|
+
@article{basic_case,
|
|
109
|
+
author = {Test author},
|
|
110
|
+
title = {Standard field format},
|
|
111
|
+
year = {2020}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Field
|
|
116
|
+
A field inside an entry, consists of a `key` and a `value`:
|
|
117
|
+
```LaTeX
|
|
118
|
+
author = {Test author},
|
|
119
|
+
```
|
|
120
|
+
In this case, `"author"` would be the `key` and `"Test author"` the `value`.
|
|
121
|
+
|
|
122
|
+
> [!note]
|
|
123
|
+
> There are many different ways of wrapping the value text, from `{}` via `{{}}` or `""`.
|
|
124
|
+
> This tool removes these wrapping characters and only considers the text inside of them as `value`.
|
|
125
|
+
|
|
126
|
+
### Entry Type
|
|
127
|
+
The entry type specifies the available fields and is written behind the `@` and in front of the first `{` of an entry:
|
|
128
|
+
```LaTeX
|
|
129
|
+
@article{...}
|
|
130
|
+
@conference{...}
|
|
131
|
+
@online{...}
|
|
132
|
+
```
|
|
File without changes
|