# python-bibtex-linter
A Python tool to parse BibTeX entries and run (custom) checks on them.

```commandline
> bibtex_linter refs.bib

Entry 'SomeBook' of type 'BOOK' failed verification:
  ❌ Invariant Violations:
    - Entry 'SomeBook' misses the following required fields: [publisher]
    - Entry 'SomeBook' has fields present that would be omitted in the compiled document: [url]. This could lead to a loss of information.
    
Found 2 invariant violations in 17 entries.
```

## Motivation
I've always assumed that I just needed to take care that my `references.bib` file was in order, that as many of the
fields of the entries in there were filled, and then I could easily use them to create standard-conforming citations in
my LaTeX documents. 
As it turns out, a lot of different citation styles omit various fields, and it's an overall mess. 
Therefore, I created this tool (in Python, since that's what I know best), that can parse the entries and then performs
arbitrary (self-defined) invariant checks on them.

In my field the most used citation style is `IEEEtran` so this is how I've defined the default rules of the script.
I've written down the observations on which the rules are based [here](test/test_template/IEEEtran_observations.md).

It is however relatively easy to define your own [custom ruleset](#advanced-custom-rulesets), should the need arise.

## How to use:
First we need to install the tool, I recommend to use [pipx](https://github.com/pypa/pipx) for that:
```commandline
pipx install .
```

### Basic Usage
Then you can call the script the following way:
```commandline
bibtex_linter path/to/refs.bib
```

The script will parse the file, perform the checks and print out the results. 

> [!note]
> As the `bibtex_linter` returns exit code `0`, if all checks have passed and `1`, if violations were found, 
> you could also use it in the CI of your LaTeX projects. 

### Advanced: Custom Rulesets

> [!warning]
> Custom rulesets are plain Python code and will be executed on your machine.
> If you wouldn't trust running `python3 my_rules.py`, you shouldn't use it with `bibtex_linter`.
> **Only use rulesets from sources you trust!**

It is also possible to define your own rules inside a Python file. 
Let's call it `my_own_rules.py`.
Creating your own rule is as simple as:

```Python
from typing import List

from bibtex_linter.parser import BibTeXEntry, EntryType
from bibtex_linter.verification import linter_rule


@linter_rule(entry_type=EntryType.ARTICLE)
def check_article(entry: BibTeXEntry) -> List[str]:
    """
    Check that a `BibTeXEntry` has a nonempty `author` field.

    :param entry: The BibTeXEntry
    :return: A list of string descriptions of rule violations for this entry.
    """
    if not entry.fields.get("author"):
        return [f"Entry '{entry.name}' misses the required field author!"]
    return []
```

As you can see, we created a method and designated that it is a linter rule by using the `@linter_rule` decorator.
The method needs to have a specific interface: 
It needs to take the `BibTeXEntry` to be checked as input argument, and it needs to return a List of strings explaining
the rule violations for that `BibTeXEntry`. 
If there are no rule violations, it should return an empty list. 

This rule only gets executed for entries of the `ARTICLE` type, as specified by the decorator argument.
If we left the `entry_type` argument empty, this check would be executed on all entries.

For more inspiration on what you could define as your custom rules, have a look into `bibtex_linter/default_rules.py`.
After defining the rules in `my_own_rules.py`, we can execute them on a BibTeX file like this: 

```commandline
bibtex_linter path/to/refs.bib path/to/my_own_rules.py
```

Let's reiterate the warning from beforehand:

> [!warning]
> Custom rulesets are plain Python code and will be executed on your machine.
> If you wouldn't trust running `python3 my_rules.py`, you shouldn't use it with `bibtex_linter`.
> **Only use rulesets from sources you trust!**


## Definition of used Terms
If you're unfamiliar with BibTex, here's a short list of terms, so that you can better understand the output of the
`bibtex_linter`.

### Entry
An entry to the BibTeX file:
```LaTeX
@article{basic_case,
  author = {Test author},
  title = {Standard field format},
  year = {2020}
}
```

### Field
A field inside an entry, consists of a `key` and a `value`:
```LaTeX
author = {Test author},
```
In this case, `"author"` would be the `key` and `"Test author"` the `value`. 

> [!note]
> There are many different ways of wrapping the value text, from `{}` via `{{}}` or `""`.
> This tool removes these wrapping characters and only considers the text inside of them as `value`.

### Entry Type
The entry type specifies the available fields and is written behind the `@` and in front of the first `{` of an entry:
```LaTeX
@article{...}
@conference{...}
@online{...}
```
