antilibrary 0.1.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.
- antilibrary-0.1.0/.gitignore +8 -0
- antilibrary-0.1.0/PKG-INFO +91 -0
- antilibrary-0.1.0/README.md +61 -0
- antilibrary-0.1.0/pyproject.toml +53 -0
- antilibrary-0.1.0/src/antilibrary/__init__.py +3 -0
- antilibrary-0.1.0/src/antilibrary/bibfile.py +137 -0
- antilibrary-0.1.0/src/antilibrary/cli/__init__.py +0 -0
- antilibrary-0.1.0/src/antilibrary/cli/app.py +234 -0
- antilibrary-0.1.0/src/antilibrary/core.py +86 -0
- antilibrary-0.1.0/src/antilibrary/databases/__init__.py +1 -0
- antilibrary-0.1.0/src/antilibrary/entry.py +45 -0
- antilibrary-0.1.0/src/antilibrary/formatters.py +26 -0
- antilibrary-0.1.0/src/antilibrary/search.py +50 -0
- antilibrary-0.1.0/tests/__init__.py +0 -0
- antilibrary-0.1.0/tests/fixtures/sample.bib +40 -0
- antilibrary-0.1.0/tests/test_core.py +116 -0
- antilibrary-0.1.0/uv.lock +246 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: antilibrary
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Manage BibTeX libraries from the terminal
|
|
5
|
+
Project-URL: Homepage, https://github.com/vxvware/antilibrary
|
|
6
|
+
Project-URL: Repository, https://github.com/vxvware/antilibrary
|
|
7
|
+
Project-URL: Issues, https://github.com/vxvware/antilibrary/issues
|
|
8
|
+
Author-email: VxVware <trevor.j.vincent@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
Keywords: bibliography,bibtex,citations,cli,research
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Text Processing :: Markup :: LaTeX
|
|
21
|
+
Classifier: Topic :: Utilities
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Requires-Dist: bibtexparser>=2.0.0b7
|
|
24
|
+
Requires-Dist: rapidfuzz>=3.0
|
|
25
|
+
Requires-Dist: rich>=13.0
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# antilibrary
|
|
32
|
+
|
|
33
|
+
Manage a BibTeX file from the terminal.
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install antilibrary
|
|
39
|
+
# or
|
|
40
|
+
uv tool install antilibrary
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
45
|
+
Antilibrary operates on a single `.bib` file resolved in this order:
|
|
46
|
+
|
|
47
|
+
1. `-f / --file FILE` argument
|
|
48
|
+
2. `$ANTILIBRARY_BIB` environment variable
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
export ANTILIBRARY_BIB=~/refs/main.bib
|
|
52
|
+
|
|
53
|
+
antilibrary add # interactively add an entry
|
|
54
|
+
antilibrary get smith2024 # print citation for a key
|
|
55
|
+
antilibrary browse # list entries
|
|
56
|
+
antilibrary browse -q "attention" # fuzzy search
|
|
57
|
+
antilibrary browse -t "nlp,transformers" # filter by tags
|
|
58
|
+
antilibrary remove smith2024 # delete an entry
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Citation styles
|
|
62
|
+
|
|
63
|
+
`antilibrary get KEY --style {bibtex|bibtex-full|latex|org-cite|pandoc}` prints
|
|
64
|
+
a citation in the requested format:
|
|
65
|
+
|
|
66
|
+
| style | output |
|
|
67
|
+
|----------------|-------------------------|
|
|
68
|
+
| `bibtex` | `smith2024` |
|
|
69
|
+
| `bibtex-full` | the whole entry |
|
|
70
|
+
| `latex` | `\cite{smith2024}` |
|
|
71
|
+
| `org-cite` | `[cite:@smith2024]` |
|
|
72
|
+
| `pandoc` | `[@smith2024]` |
|
|
73
|
+
|
|
74
|
+
## Adding entries
|
|
75
|
+
|
|
76
|
+
`antilibrary add` is interactive — it prompts for type, title, author, year,
|
|
77
|
+
key, and any extra fields. Adding an entry that matches an existing one
|
|
78
|
+
(by DOI, arXiv ID, ISBN, or normalized title+author) silently fills any
|
|
79
|
+
empty fields on the existing entry rather than creating a duplicate. Pass
|
|
80
|
+
`--no-merge` to skip on duplicate, or `--force` to append anyway with an
|
|
81
|
+
auto-suffixed key.
|
|
82
|
+
|
|
83
|
+
Companion tools (planned):
|
|
84
|
+
|
|
85
|
+
- **bibzapper** — batch extraction from PDFs, EPUBs, URLs, DOIs, arXiv IDs, or
|
|
86
|
+
ISBNs into a `.bib` file.
|
|
87
|
+
- **bibsearcher** — online search (movies, TV, music, papers, books) → BibTeX.
|
|
88
|
+
|
|
89
|
+
## License
|
|
90
|
+
|
|
91
|
+
MIT
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# antilibrary
|
|
2
|
+
|
|
3
|
+
Manage a BibTeX file from the terminal.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install antilibrary
|
|
9
|
+
# or
|
|
10
|
+
uv tool install antilibrary
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
Antilibrary operates on a single `.bib` file resolved in this order:
|
|
16
|
+
|
|
17
|
+
1. `-f / --file FILE` argument
|
|
18
|
+
2. `$ANTILIBRARY_BIB` environment variable
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
export ANTILIBRARY_BIB=~/refs/main.bib
|
|
22
|
+
|
|
23
|
+
antilibrary add # interactively add an entry
|
|
24
|
+
antilibrary get smith2024 # print citation for a key
|
|
25
|
+
antilibrary browse # list entries
|
|
26
|
+
antilibrary browse -q "attention" # fuzzy search
|
|
27
|
+
antilibrary browse -t "nlp,transformers" # filter by tags
|
|
28
|
+
antilibrary remove smith2024 # delete an entry
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Citation styles
|
|
32
|
+
|
|
33
|
+
`antilibrary get KEY --style {bibtex|bibtex-full|latex|org-cite|pandoc}` prints
|
|
34
|
+
a citation in the requested format:
|
|
35
|
+
|
|
36
|
+
| style | output |
|
|
37
|
+
|----------------|-------------------------|
|
|
38
|
+
| `bibtex` | `smith2024` |
|
|
39
|
+
| `bibtex-full` | the whole entry |
|
|
40
|
+
| `latex` | `\cite{smith2024}` |
|
|
41
|
+
| `org-cite` | `[cite:@smith2024]` |
|
|
42
|
+
| `pandoc` | `[@smith2024]` |
|
|
43
|
+
|
|
44
|
+
## Adding entries
|
|
45
|
+
|
|
46
|
+
`antilibrary add` is interactive — it prompts for type, title, author, year,
|
|
47
|
+
key, and any extra fields. Adding an entry that matches an existing one
|
|
48
|
+
(by DOI, arXiv ID, ISBN, or normalized title+author) silently fills any
|
|
49
|
+
empty fields on the existing entry rather than creating a duplicate. Pass
|
|
50
|
+
`--no-merge` to skip on duplicate, or `--force` to append anyway with an
|
|
51
|
+
auto-suffixed key.
|
|
52
|
+
|
|
53
|
+
Companion tools (planned):
|
|
54
|
+
|
|
55
|
+
- **bibzapper** — batch extraction from PDFs, EPUBs, URLs, DOIs, arXiv IDs, or
|
|
56
|
+
ISBNs into a `.bib` file.
|
|
57
|
+
- **bibsearcher** — online search (movies, TV, music, papers, books) → BibTeX.
|
|
58
|
+
|
|
59
|
+
## License
|
|
60
|
+
|
|
61
|
+
MIT
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "antilibrary"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Manage BibTeX libraries from the terminal"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "VxVware", email = "trevor.j.vincent@gmail.com" },
|
|
10
|
+
]
|
|
11
|
+
keywords = ["bibtex", "bibliography", "citations", "cli", "research"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 3 - Alpha",
|
|
14
|
+
"Environment :: Console",
|
|
15
|
+
"Intended Audience :: Science/Research",
|
|
16
|
+
"Intended Audience :: End Users/Desktop",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Topic :: Text Processing :: Markup :: LaTeX",
|
|
23
|
+
"Topic :: Utilities",
|
|
24
|
+
]
|
|
25
|
+
dependencies = [
|
|
26
|
+
"bibtexparser>=2.0.0b7",
|
|
27
|
+
"rapidfuzz>=3.0",
|
|
28
|
+
"rich>=13.0",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.optional-dependencies]
|
|
32
|
+
dev = [
|
|
33
|
+
"pytest>=8.0",
|
|
34
|
+
"ruff>=0.4",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.urls]
|
|
38
|
+
Homepage = "https://github.com/vxvware/antilibrary"
|
|
39
|
+
Repository = "https://github.com/vxvware/antilibrary"
|
|
40
|
+
Issues = "https://github.com/vxvware/antilibrary/issues"
|
|
41
|
+
|
|
42
|
+
[project.scripts]
|
|
43
|
+
antilibrary = "antilibrary.cli.app:main"
|
|
44
|
+
|
|
45
|
+
[build-system]
|
|
46
|
+
requires = ["hatchling"]
|
|
47
|
+
build-backend = "hatchling.build"
|
|
48
|
+
|
|
49
|
+
[tool.hatch.build.targets.wheel]
|
|
50
|
+
packages = ["src/antilibrary"]
|
|
51
|
+
|
|
52
|
+
[tool.ruff]
|
|
53
|
+
line-length = 100
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Read, dedup, and append BibTeX entries to a .bib file."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import bibtexparser
|
|
8
|
+
|
|
9
|
+
from antilibrary.entry import Entry
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _btp_to_entry(e, source: Path | None = None) -> Entry:
|
|
13
|
+
fields = {f.key: f.value for f in e.fields}
|
|
14
|
+
return Entry(key=e.key, entry_type=e.entry_type, fields=fields, source_file=source)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def load(path: Path) -> list[Entry]:
|
|
18
|
+
"""Load all entries from a .bib file. Returns [] if missing/empty."""
|
|
19
|
+
if not path.exists():
|
|
20
|
+
return []
|
|
21
|
+
text = path.read_text()
|
|
22
|
+
if not text.strip():
|
|
23
|
+
return []
|
|
24
|
+
lib = bibtexparser.parse_string(text)
|
|
25
|
+
return [_btp_to_entry(e, path) for e in lib.entries]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _norm(s: str) -> str:
|
|
29
|
+
return s.strip().lower()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def find_duplicate(entry: Entry, existing: list[Entry]) -> Entry | None:
|
|
33
|
+
"""Return an existing entry that matches by identifier or title+author."""
|
|
34
|
+
new_doi = _norm(entry.fields.get("doi", ""))
|
|
35
|
+
new_arxiv = _norm(entry.fields.get("eprint", ""))
|
|
36
|
+
new_isbn = _norm(entry.fields.get("isbn", ""))
|
|
37
|
+
new_title = _norm(entry.fields.get("title", ""))
|
|
38
|
+
new_author = _norm(entry.fields.get("author", ""))
|
|
39
|
+
|
|
40
|
+
for e in existing:
|
|
41
|
+
if e.key == entry.key:
|
|
42
|
+
return e
|
|
43
|
+
if new_doi and _norm(e.fields.get("doi", "")) == new_doi:
|
|
44
|
+
return e
|
|
45
|
+
if new_arxiv and _norm(e.fields.get("eprint", "")) == new_arxiv:
|
|
46
|
+
return e
|
|
47
|
+
if new_isbn and _norm(e.fields.get("isbn", "")) == new_isbn:
|
|
48
|
+
return e
|
|
49
|
+
if (
|
|
50
|
+
new_title
|
|
51
|
+
and _norm(e.fields.get("title", "")) == new_title
|
|
52
|
+
and new_author
|
|
53
|
+
and _norm(e.fields.get("author", "")) == new_author
|
|
54
|
+
):
|
|
55
|
+
return e
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def make_unique_key(base: str, existing: list[Entry]) -> str:
|
|
60
|
+
"""Suffix a, b, c... if `base` collides with any key in existing."""
|
|
61
|
+
keys = {e.key for e in existing}
|
|
62
|
+
if base not in keys:
|
|
63
|
+
return base
|
|
64
|
+
for suffix in "abcdefghijklmnopqrstuvwxyz":
|
|
65
|
+
candidate = f"{base}{suffix}"
|
|
66
|
+
if candidate not in keys:
|
|
67
|
+
return candidate
|
|
68
|
+
n = 1
|
|
69
|
+
while f"{base}{n}" in keys:
|
|
70
|
+
n += 1
|
|
71
|
+
return f"{base}{n}"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _append_entry(path: Path, entry: Entry) -> None:
|
|
75
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
76
|
+
text = path.read_text() if path.exists() else ""
|
|
77
|
+
separator = "\n\n" if text.strip() else ""
|
|
78
|
+
with open(path, "a") as f:
|
|
79
|
+
f.write(separator + entry.to_bibtex() + "\n")
|
|
80
|
+
entry.source_file = path
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _rewrite(path: Path, entries: list[Entry]) -> None:
|
|
84
|
+
content = "\n\n".join(e.to_bibtex() for e in entries)
|
|
85
|
+
path.write_text(content + "\n" if content else "")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def merge_missing(target: Entry, source: Entry) -> bool:
|
|
89
|
+
"""Fill empty/absent fields on `target` from `source`. Returns True if changed."""
|
|
90
|
+
changed = False
|
|
91
|
+
for k, v in source.fields.items():
|
|
92
|
+
if not v:
|
|
93
|
+
continue
|
|
94
|
+
if not target.fields.get(k, "").strip():
|
|
95
|
+
target.fields[k] = v
|
|
96
|
+
changed = True
|
|
97
|
+
return changed
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class AppendResult:
|
|
101
|
+
__slots__ = ("status", "entry", "duplicate_of")
|
|
102
|
+
|
|
103
|
+
def __init__(self, status: str, entry: Entry, duplicate_of: Entry | None = None):
|
|
104
|
+
self.status = status # "added" | "skipped" | "merged"
|
|
105
|
+
self.entry = entry
|
|
106
|
+
self.duplicate_of = duplicate_of
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def append(
|
|
110
|
+
path: Path,
|
|
111
|
+
entry: Entry,
|
|
112
|
+
*,
|
|
113
|
+
merge: bool = True,
|
|
114
|
+
force: bool = False,
|
|
115
|
+
) -> AppendResult:
|
|
116
|
+
"""Append `entry` to the .bib at `path` with dedup.
|
|
117
|
+
|
|
118
|
+
- If a duplicate exists and `force` is False:
|
|
119
|
+
- If `merge` is True (default), fill missing/empty fields on the existing entry
|
|
120
|
+
and rewrite the file. Returns status="merged" if anything changed,
|
|
121
|
+
otherwise "skipped".
|
|
122
|
+
- If `merge` is False, returns status="skipped".
|
|
123
|
+
- Otherwise auto-suffixes the key on collision and appends.
|
|
124
|
+
"""
|
|
125
|
+
existing = load(path)
|
|
126
|
+
dup = find_duplicate(entry, existing) if not force else None
|
|
127
|
+
|
|
128
|
+
if dup is not None:
|
|
129
|
+
if merge:
|
|
130
|
+
if merge_missing(dup, entry):
|
|
131
|
+
_rewrite(path, existing)
|
|
132
|
+
return AppendResult("merged", dup, duplicate_of=dup)
|
|
133
|
+
return AppendResult("skipped", dup, duplicate_of=dup)
|
|
134
|
+
|
|
135
|
+
entry.key = make_unique_key(entry.key, existing)
|
|
136
|
+
_append_entry(path, entry)
|
|
137
|
+
return AppendResult("added", entry)
|
|
File without changes
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"""Antilibrary CLI — manage a BibTeX file.
|
|
2
|
+
|
|
3
|
+
Commands operate on a single .bib file resolved in this order:
|
|
4
|
+
1. -f/--file FILE argument
|
|
5
|
+
2. $ANTILIBRARY_BIB environment variable
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import os
|
|
12
|
+
import sys
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from rich import box
|
|
16
|
+
from rich.console import Console
|
|
17
|
+
from rich.syntax import Syntax
|
|
18
|
+
from rich.table import Table
|
|
19
|
+
|
|
20
|
+
from antilibrary.core import Library, make_entry
|
|
21
|
+
from antilibrary.formatters import format_citation
|
|
22
|
+
from antilibrary.search import fuzzy_search, filter_by_tags
|
|
23
|
+
|
|
24
|
+
console = Console()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _resolve_file(arg: str | None) -> Path:
|
|
28
|
+
if arg:
|
|
29
|
+
return Path(arg).expanduser()
|
|
30
|
+
env = os.environ.get("ANTILIBRARY_BIB")
|
|
31
|
+
if env:
|
|
32
|
+
return Path(env).expanduser()
|
|
33
|
+
console.print(
|
|
34
|
+
"[red]no .bib file specified[/] "
|
|
35
|
+
"[dim]pass -f FILE or set ANTILIBRARY_BIB[/]"
|
|
36
|
+
)
|
|
37
|
+
sys.exit(2)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _prompt(label: str, default: str = "") -> str:
|
|
41
|
+
suffix = f" [{default}]" if default else ""
|
|
42
|
+
try:
|
|
43
|
+
val = input(f"{label}{suffix}: ").strip()
|
|
44
|
+
except (EOFError, KeyboardInterrupt):
|
|
45
|
+
print()
|
|
46
|
+
sys.exit(130)
|
|
47
|
+
return val or default
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# ── add ─────────────────────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def cmd_add(args: argparse.Namespace) -> int:
|
|
54
|
+
path = _resolve_file(args.file)
|
|
55
|
+
lib = Library(path)
|
|
56
|
+
|
|
57
|
+
entry_type = args.type or _prompt("type", "article")
|
|
58
|
+
title = _prompt("title")
|
|
59
|
+
if not title:
|
|
60
|
+
console.print("[red]title required[/]")
|
|
61
|
+
return 2
|
|
62
|
+
author = _prompt("author")
|
|
63
|
+
year = _prompt("year")
|
|
64
|
+
key = args.key or _prompt("key (blank = auto)")
|
|
65
|
+
|
|
66
|
+
entry = make_entry(
|
|
67
|
+
entry_type=entry_type,
|
|
68
|
+
title=title,
|
|
69
|
+
author=author,
|
|
70
|
+
year=year or None,
|
|
71
|
+
key=key or None,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
while True:
|
|
75
|
+
extra = _prompt("extra field (blank to finish)")
|
|
76
|
+
if not extra:
|
|
77
|
+
break
|
|
78
|
+
val = _prompt(f" {extra}")
|
|
79
|
+
if val:
|
|
80
|
+
entry.fields[extra] = val
|
|
81
|
+
|
|
82
|
+
result = lib.add(entry, merge=not args.no_merge, force=args.force)
|
|
83
|
+
if result.status == "added":
|
|
84
|
+
console.print(f"[green]added[/] [bold]{result.entry.key}[/] → {path}")
|
|
85
|
+
elif result.status == "merged":
|
|
86
|
+
console.print(f"[green]merged[/] missing fields into [bold]{result.entry.key}[/]")
|
|
87
|
+
else:
|
|
88
|
+
console.print(f"[yellow]duplicate[/] of [bold]{result.duplicate_of.key}[/] — skipped")
|
|
89
|
+
return 0
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# ── get ─────────────────────────────────────────────────────────────────────
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def cmd_get(args: argparse.Namespace) -> int:
|
|
96
|
+
path = _resolve_file(args.file)
|
|
97
|
+
lib = Library(path)
|
|
98
|
+
entry = lib.find(args.key)
|
|
99
|
+
if entry is None:
|
|
100
|
+
console.print(f"[red]no entry with key {args.key!r}[/]")
|
|
101
|
+
return 1
|
|
102
|
+
|
|
103
|
+
if args.style == "bibtex-full":
|
|
104
|
+
console.print(
|
|
105
|
+
Syntax(entry.to_bibtex(), "bibtex", theme="monokai", line_numbers=False, padding=(0, 1))
|
|
106
|
+
)
|
|
107
|
+
else:
|
|
108
|
+
print(format_citation(entry, args.style))
|
|
109
|
+
return 0
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# ── browse ──────────────────────────────────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def cmd_browse(args: argparse.Namespace) -> int:
|
|
116
|
+
path = _resolve_file(args.file)
|
|
117
|
+
lib = Library(path)
|
|
118
|
+
entries = lib.entries()
|
|
119
|
+
|
|
120
|
+
if args.tags:
|
|
121
|
+
tags = [t.strip() for t in args.tags.split(",") if t.strip()]
|
|
122
|
+
entries = filter_by_tags(entries, tags)
|
|
123
|
+
|
|
124
|
+
if args.query:
|
|
125
|
+
entries = fuzzy_search(entries, args.query, limit=args.limit)
|
|
126
|
+
else:
|
|
127
|
+
entries = entries[: args.limit]
|
|
128
|
+
|
|
129
|
+
if not entries:
|
|
130
|
+
console.print("[dim]no matching entries[/]")
|
|
131
|
+
return 0
|
|
132
|
+
|
|
133
|
+
table = Table(box=box.SIMPLE_HEAVY, header_style="bold cyan", padding=(0, 1))
|
|
134
|
+
table.add_column("key", style="bold cyan", max_width=28)
|
|
135
|
+
table.add_column("title", ratio=3)
|
|
136
|
+
table.add_column("author", ratio=2)
|
|
137
|
+
table.add_column("year", width=6, justify="center")
|
|
138
|
+
table.add_column("tags", style="blue", max_width=20)
|
|
139
|
+
|
|
140
|
+
for e in entries:
|
|
141
|
+
table.add_row(
|
|
142
|
+
e.key,
|
|
143
|
+
e.fields.get("title", ""),
|
|
144
|
+
e.fields.get("author", "")[:40],
|
|
145
|
+
e.fields.get("year", ""),
|
|
146
|
+
", ".join(e.tags) if e.tags else "",
|
|
147
|
+
)
|
|
148
|
+
console.print(table)
|
|
149
|
+
console.print(f"[dim]{len(entries)} entries · {path}[/]")
|
|
150
|
+
return 0
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# ── remove ──────────────────────────────────────────────────────────────────
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def cmd_remove(args: argparse.Namespace) -> int:
|
|
157
|
+
path = _resolve_file(args.file)
|
|
158
|
+
lib = Library(path)
|
|
159
|
+
entry = lib.find(args.key)
|
|
160
|
+
if entry is None:
|
|
161
|
+
console.print(f"[red]no entry with key {args.key!r}[/]")
|
|
162
|
+
return 1
|
|
163
|
+
|
|
164
|
+
if not args.yes:
|
|
165
|
+
confirm = _prompt(f"remove {entry.display_string()!r}? (y/N)", "n")
|
|
166
|
+
if confirm.lower() not in ("y", "yes"):
|
|
167
|
+
console.print("[dim]cancelled[/]")
|
|
168
|
+
return 0
|
|
169
|
+
|
|
170
|
+
lib.remove(args.key)
|
|
171
|
+
console.print(f"[green]removed[/] {args.key}")
|
|
172
|
+
return 0
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
# ── argparse wiring ─────────────────────────────────────────────────────────
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
179
|
+
p = argparse.ArgumentParser(
|
|
180
|
+
prog="antilibrary",
|
|
181
|
+
description="Manage a BibTeX file.",
|
|
182
|
+
)
|
|
183
|
+
sub = p.add_subparsers(dest="command", required=True)
|
|
184
|
+
|
|
185
|
+
def add_file(sp: argparse.ArgumentParser) -> None:
|
|
186
|
+
sp.add_argument(
|
|
187
|
+
"-f", "--file",
|
|
188
|
+
help="Path to .bib file (default: $ANTILIBRARY_BIB)",
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
sp_add = sub.add_parser("add", help="Add a new entry interactively")
|
|
192
|
+
add_file(sp_add)
|
|
193
|
+
sp_add.add_argument("--type", help="Entry type (default: article)")
|
|
194
|
+
sp_add.add_argument("--key", help="Citation key (default: auto-generated)")
|
|
195
|
+
sp_add.add_argument("--no-merge", action="store_true",
|
|
196
|
+
help="On duplicate, skip without filling missing fields")
|
|
197
|
+
sp_add.add_argument("--force", action="store_true",
|
|
198
|
+
help="Append even if a duplicate exists")
|
|
199
|
+
sp_add.set_defaults(func=cmd_add)
|
|
200
|
+
|
|
201
|
+
sp_get = sub.add_parser("get", help="Fetch an entry by key")
|
|
202
|
+
add_file(sp_get)
|
|
203
|
+
sp_get.add_argument("key")
|
|
204
|
+
sp_get.add_argument(
|
|
205
|
+
"--style",
|
|
206
|
+
choices=("bibtex", "bibtex-full", "latex", "org-cite", "pandoc"),
|
|
207
|
+
default="bibtex-full",
|
|
208
|
+
help="Output format (default: bibtex-full — the whole entry)",
|
|
209
|
+
)
|
|
210
|
+
sp_get.set_defaults(func=cmd_get)
|
|
211
|
+
|
|
212
|
+
sp_br = sub.add_parser("browse", help="List / search entries")
|
|
213
|
+
add_file(sp_br)
|
|
214
|
+
sp_br.add_argument("--query", "-q", help="Fuzzy-match query")
|
|
215
|
+
sp_br.add_argument("--tags", "-t", help="Filter by tags (comma-separated)")
|
|
216
|
+
sp_br.add_argument("--limit", type=int, default=50)
|
|
217
|
+
sp_br.set_defaults(func=cmd_browse)
|
|
218
|
+
|
|
219
|
+
sp_rm = sub.add_parser("remove", help="Remove an entry by key")
|
|
220
|
+
add_file(sp_rm)
|
|
221
|
+
sp_rm.add_argument("key")
|
|
222
|
+
sp_rm.add_argument("-y", "--yes", action="store_true", help="Skip confirmation")
|
|
223
|
+
sp_rm.set_defaults(func=cmd_remove)
|
|
224
|
+
|
|
225
|
+
return p
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def main(argv: list[str] | None = None) -> int:
|
|
229
|
+
args = build_parser().parse_args(argv)
|
|
230
|
+
return args.func(args)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
if __name__ == "__main__":
|
|
234
|
+
sys.exit(main())
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Core BibTeX entry/library management.
|
|
2
|
+
|
|
3
|
+
`Entry` and `bibfile` provide the data class and low-level .bib I/O;
|
|
4
|
+
this module wraps them in a higher-level `Library` used across antilibrary's
|
|
5
|
+
CLI and search.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
|
|
13
|
+
from antilibrary import bibfile
|
|
14
|
+
from antilibrary.entry import Entry
|
|
15
|
+
|
|
16
|
+
__all__ = ["Entry", "Library", "make_entry"]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Library:
|
|
20
|
+
"""A BibTeX library backed by a .bib file."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, path: Path, label: str | None = None):
|
|
23
|
+
self.path = path
|
|
24
|
+
self.label = label or path.stem
|
|
25
|
+
self._entries: list[Entry] | None = None
|
|
26
|
+
|
|
27
|
+
def entries(self, *, reload: bool = False) -> list[Entry]:
|
|
28
|
+
if self._entries is not None and not reload:
|
|
29
|
+
return self._entries
|
|
30
|
+
self._entries = bibfile.load(self.path)
|
|
31
|
+
return self._entries
|
|
32
|
+
|
|
33
|
+
def find(self, key: str) -> Entry | None:
|
|
34
|
+
for e in self.entries():
|
|
35
|
+
if e.key == key:
|
|
36
|
+
return e
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
def has_duplicate(self, entry: Entry) -> Entry | None:
|
|
40
|
+
return bibfile.find_duplicate(entry, self.entries())
|
|
41
|
+
|
|
42
|
+
def add(self, entry: Entry, *, merge: bool = True, force: bool = False) -> bibfile.AppendResult:
|
|
43
|
+
"""Append an entry, deduplicating against existing ones."""
|
|
44
|
+
result = bibfile.append(self.path, entry, merge=merge, force=force)
|
|
45
|
+
self._entries = None
|
|
46
|
+
return result
|
|
47
|
+
|
|
48
|
+
def remove(self, key: str) -> bool:
|
|
49
|
+
"""Remove an entry by key. Rewrites the file."""
|
|
50
|
+
entries = self.entries(reload=True)
|
|
51
|
+
filtered = [e for e in entries if e.key != key]
|
|
52
|
+
if len(filtered) == len(entries):
|
|
53
|
+
return False
|
|
54
|
+
from antilibrary.bibfile import _rewrite
|
|
55
|
+
_rewrite(self.path, filtered)
|
|
56
|
+
self._entries = None
|
|
57
|
+
return True
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def count(self) -> int:
|
|
61
|
+
return len(self.entries())
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def make_entry(
|
|
65
|
+
entry_type: str = "misc",
|
|
66
|
+
title: str = "Untitled",
|
|
67
|
+
author: str = "",
|
|
68
|
+
year: str | None = None,
|
|
69
|
+
key: str | None = None,
|
|
70
|
+
**extra_fields: str,
|
|
71
|
+
) -> Entry:
|
|
72
|
+
"""Create an Entry with sensible defaults."""
|
|
73
|
+
if year is None:
|
|
74
|
+
year = str(datetime.now().year)
|
|
75
|
+
if key is None:
|
|
76
|
+
if author:
|
|
77
|
+
last = author.split(",")[0].split()[-1].lower() if author else "unknown"
|
|
78
|
+
else:
|
|
79
|
+
last = "unknown"
|
|
80
|
+
safe_title = "".join(c for c in title[:20] if c.isalnum())
|
|
81
|
+
key = f"{last}{year}{safe_title}".lower()
|
|
82
|
+
fields = {"title": title, "year": year}
|
|
83
|
+
if author:
|
|
84
|
+
fields["author"] = author
|
|
85
|
+
fields.update(extra_fields)
|
|
86
|
+
return Entry(key=key, entry_type=entry_type, fields=fields)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""External database integrations (OMDB, MusicBrainz, etc.) - to be implemented."""
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""BibTeX Entry data class."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class Entry:
|
|
11
|
+
"""A single BibTeX entry."""
|
|
12
|
+
|
|
13
|
+
key: str
|
|
14
|
+
entry_type: str
|
|
15
|
+
fields: dict[str, str] = field(default_factory=dict)
|
|
16
|
+
raw: str = ""
|
|
17
|
+
source_file: Path | None = None
|
|
18
|
+
|
|
19
|
+
def display_string(self) -> str:
|
|
20
|
+
author = self.fields.get("author", "Unknown")
|
|
21
|
+
if "," in author:
|
|
22
|
+
author = author.split(",")[0].strip()
|
|
23
|
+
elif " and " in author:
|
|
24
|
+
author = author.split(" and ")[0].strip()
|
|
25
|
+
year = self.fields.get("year", "n.d.")
|
|
26
|
+
title = self.fields.get("title", "Untitled")
|
|
27
|
+
return f"{author} ({year}) - {title}"
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def tags(self) -> list[str]:
|
|
31
|
+
kw = self.fields.get("keywords", "")
|
|
32
|
+
if not kw:
|
|
33
|
+
return []
|
|
34
|
+
return [t.strip() for t in kw.split(",") if t.strip()]
|
|
35
|
+
|
|
36
|
+
@tags.setter
|
|
37
|
+
def tags(self, value: list[str]) -> None:
|
|
38
|
+
self.fields["keywords"] = ", ".join(value)
|
|
39
|
+
|
|
40
|
+
def to_bibtex(self) -> str:
|
|
41
|
+
lines = [f"@{self.entry_type}{{{self.key},"]
|
|
42
|
+
for k, v in self.fields.items():
|
|
43
|
+
lines.append(f" {k} = {{{v}}},")
|
|
44
|
+
lines.append("}")
|
|
45
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Output formatting for BibTeX entries."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from antilibrary.core import Entry
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def format_citation(entry: Entry, style: str = "bibtex") -> str:
|
|
9
|
+
"""Format a citation reference in the given style."""
|
|
10
|
+
key = entry.key
|
|
11
|
+
match style:
|
|
12
|
+
case "bibtex":
|
|
13
|
+
return key
|
|
14
|
+
case "latex":
|
|
15
|
+
return f"\\cite{{{key}}}"
|
|
16
|
+
case "org-cite":
|
|
17
|
+
return f"[cite:@{key}]"
|
|
18
|
+
case "pandoc":
|
|
19
|
+
return f"[@{key}]"
|
|
20
|
+
case _:
|
|
21
|
+
return key
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def format_display(entry: Entry) -> str:
|
|
25
|
+
"""Format entry for display in search results."""
|
|
26
|
+
return entry.display_string()
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Fuzzy search over BibTeX entries."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from rapidfuzz import fuzz, process
|
|
6
|
+
|
|
7
|
+
from antilibrary.core import Entry
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def fuzzy_search(
|
|
11
|
+
entries: list[Entry],
|
|
12
|
+
query: str,
|
|
13
|
+
limit: int = 30,
|
|
14
|
+
score_cutoff: float = 30.0,
|
|
15
|
+
) -> list[Entry]:
|
|
16
|
+
"""Fuzzy search entries by their display string.
|
|
17
|
+
|
|
18
|
+
Returns entries sorted by match quality (best first).
|
|
19
|
+
"""
|
|
20
|
+
if not query.strip():
|
|
21
|
+
return entries[:limit]
|
|
22
|
+
|
|
23
|
+
display_map = {e.display_string(): e for e in entries}
|
|
24
|
+
results = process.extract(
|
|
25
|
+
query,
|
|
26
|
+
display_map.keys(),
|
|
27
|
+
scorer=fuzz.WRatio,
|
|
28
|
+
limit=limit,
|
|
29
|
+
score_cutoff=score_cutoff,
|
|
30
|
+
)
|
|
31
|
+
return [display_map[r[0]] for r in results]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def filter_by_tags(entries: list[Entry], tags: list[str]) -> list[Entry]:
|
|
35
|
+
"""Filter entries that have ALL of the specified tags."""
|
|
36
|
+
if not tags:
|
|
37
|
+
return entries
|
|
38
|
+
tag_set = {t.lower() for t in tags}
|
|
39
|
+
return [
|
|
40
|
+
e for e in entries
|
|
41
|
+
if tag_set.issubset({t.lower() for t in e.tags})
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def all_tags(entries: list[Entry]) -> list[str]:
|
|
46
|
+
"""Collect all unique tags across entries, sorted."""
|
|
47
|
+
tags: set[str] = set()
|
|
48
|
+
for e in entries:
|
|
49
|
+
tags.update(e.tags)
|
|
50
|
+
return sorted(tags)
|
|
File without changes
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
@article{smith2020neural,
|
|
2
|
+
title = {Neural Networks for Reference Management},
|
|
3
|
+
author = {Smith, John and Doe, Jane},
|
|
4
|
+
year = {2020},
|
|
5
|
+
journal = {Journal of Information Science},
|
|
6
|
+
doi = {10.1234/jis.2020.001},
|
|
7
|
+
keywords = {machine-learning, nlp, references},
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
@book{knuth1997art,
|
|
11
|
+
title = {The Art of Computer Programming},
|
|
12
|
+
author = {Knuth, Donald E.},
|
|
13
|
+
year = {1997},
|
|
14
|
+
publisher = {Addison-Wesley},
|
|
15
|
+
keywords = {algorithms, programming, classics},
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@inproceedings{vaswani2017attention,
|
|
19
|
+
title = {Attention Is All You Need},
|
|
20
|
+
author = {Vaswani, Ashish and Shazeer, Noam and Parmar, Niki},
|
|
21
|
+
year = {2017},
|
|
22
|
+
booktitle = {Advances in Neural Information Processing Systems},
|
|
23
|
+
keywords = {transformers, deep-learning, nlp},
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@misc{tarkovsky1979stalker,
|
|
27
|
+
title = {Stalker},
|
|
28
|
+
author = {Tarkovsky, Andrei},
|
|
29
|
+
year = {1979},
|
|
30
|
+
note = {Film},
|
|
31
|
+
keywords = {film, sci-fi, soviet},
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@misc{radiohead1997ok,
|
|
35
|
+
title = {OK Computer},
|
|
36
|
+
author = {Radiohead},
|
|
37
|
+
year = {1997},
|
|
38
|
+
note = {Album},
|
|
39
|
+
keywords = {music, rock, electronic},
|
|
40
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Tests for core BibTeX parsing and library management."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from antilibrary.core import Library, make_entry
|
|
6
|
+
from antilibrary.search import fuzzy_search, filter_by_tags, all_tags
|
|
7
|
+
from antilibrary.formatters import format_citation
|
|
8
|
+
|
|
9
|
+
FIXTURES = Path(__file__).parent / "fixtures"
|
|
10
|
+
SAMPLE_BIB = FIXTURES / "sample.bib"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestLibrary:
|
|
14
|
+
def test_parse_sample(self):
|
|
15
|
+
lib = Library(SAMPLE_BIB)
|
|
16
|
+
entries = lib.entries()
|
|
17
|
+
assert len(entries) == 5
|
|
18
|
+
|
|
19
|
+
def test_find_by_key(self):
|
|
20
|
+
lib = Library(SAMPLE_BIB)
|
|
21
|
+
entry = lib.find("knuth1997art")
|
|
22
|
+
assert entry is not None
|
|
23
|
+
assert entry.fields["title"] == "The Art of Computer Programming"
|
|
24
|
+
|
|
25
|
+
def test_find_missing(self):
|
|
26
|
+
lib = Library(SAMPLE_BIB)
|
|
27
|
+
assert lib.find("nonexistent") is None
|
|
28
|
+
|
|
29
|
+
def test_display_string(self):
|
|
30
|
+
lib = Library(SAMPLE_BIB)
|
|
31
|
+
entry = lib.find("knuth1997art")
|
|
32
|
+
assert "Knuth" in entry.display_string()
|
|
33
|
+
assert "1997" in entry.display_string()
|
|
34
|
+
|
|
35
|
+
def test_tags(self):
|
|
36
|
+
lib = Library(SAMPLE_BIB)
|
|
37
|
+
entry = lib.find("vaswani2017attention")
|
|
38
|
+
assert "transformers" in entry.tags
|
|
39
|
+
assert "deep-learning" in entry.tags
|
|
40
|
+
|
|
41
|
+
def test_add_entry(self, tmp_path):
|
|
42
|
+
bib = tmp_path / "test.bib"
|
|
43
|
+
bib.touch()
|
|
44
|
+
lib = Library(bib)
|
|
45
|
+
entry = make_entry(title="Test Paper", author="Author, A.", year="2024")
|
|
46
|
+
lib.add(entry)
|
|
47
|
+
lib2 = Library(bib)
|
|
48
|
+
assert lib2.count == 1
|
|
49
|
+
assert lib2.entries()[0].fields["title"] == "Test Paper"
|
|
50
|
+
|
|
51
|
+
def test_remove_entry(self, tmp_path):
|
|
52
|
+
bib = tmp_path / "test.bib"
|
|
53
|
+
bib.touch()
|
|
54
|
+
lib = Library(bib)
|
|
55
|
+
e1 = make_entry(title="Paper One", author="A", year="2024", key="one2024")
|
|
56
|
+
e2 = make_entry(title="Paper Two", author="B", year="2024", key="two2024")
|
|
57
|
+
lib.add(e1)
|
|
58
|
+
lib.add(e2)
|
|
59
|
+
assert lib.count == 2
|
|
60
|
+
assert lib.remove("one2024")
|
|
61
|
+
lib2 = Library(bib)
|
|
62
|
+
assert lib2.count == 1
|
|
63
|
+
assert lib2.entries()[0].key == "two2024"
|
|
64
|
+
|
|
65
|
+
def test_to_bibtex_roundtrip(self):
|
|
66
|
+
entry = make_entry(title="Roundtrip", author="Test, A.", year="2024", key="test2024")
|
|
67
|
+
bib_str = entry.to_bibtex()
|
|
68
|
+
assert "@misc{test2024," in bib_str
|
|
69
|
+
assert "Roundtrip" in bib_str
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class TestSearch:
|
|
73
|
+
def test_fuzzy_search(self):
|
|
74
|
+
lib = Library(SAMPLE_BIB)
|
|
75
|
+
results = fuzzy_search(lib.entries(), "attention")
|
|
76
|
+
assert len(results) > 0
|
|
77
|
+
assert results[0].key == "vaswani2017attention"
|
|
78
|
+
|
|
79
|
+
def test_fuzzy_search_partial(self):
|
|
80
|
+
lib = Library(SAMPLE_BIB)
|
|
81
|
+
results = fuzzy_search(lib.entries(), "knuth art prog")
|
|
82
|
+
assert any(e.key == "knuth1997art" for e in results)
|
|
83
|
+
|
|
84
|
+
def test_filter_by_tags(self):
|
|
85
|
+
lib = Library(SAMPLE_BIB)
|
|
86
|
+
results = filter_by_tags(lib.entries(), ["nlp"])
|
|
87
|
+
assert len(results) == 2
|
|
88
|
+
|
|
89
|
+
def test_all_tags(self):
|
|
90
|
+
lib = Library(SAMPLE_BIB)
|
|
91
|
+
tags = all_tags(lib.entries())
|
|
92
|
+
assert "transformers" in tags
|
|
93
|
+
assert "film" in tags
|
|
94
|
+
|
|
95
|
+
def test_empty_query_returns_all(self):
|
|
96
|
+
lib = Library(SAMPLE_BIB)
|
|
97
|
+
results = fuzzy_search(lib.entries(), "")
|
|
98
|
+
assert len(results) == 5
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class TestFormatters:
|
|
102
|
+
def test_bibtex_format(self):
|
|
103
|
+
entry = make_entry(title="T", key="k2024")
|
|
104
|
+
assert format_citation(entry, "bibtex") == "k2024"
|
|
105
|
+
|
|
106
|
+
def test_latex_format(self):
|
|
107
|
+
entry = make_entry(title="T", key="k2024")
|
|
108
|
+
assert format_citation(entry, "latex") == "\\cite{k2024}"
|
|
109
|
+
|
|
110
|
+
def test_org_cite_format(self):
|
|
111
|
+
entry = make_entry(title="T", key="k2024")
|
|
112
|
+
assert format_citation(entry, "org-cite") == "[cite:@k2024]"
|
|
113
|
+
|
|
114
|
+
def test_pandoc_format(self):
|
|
115
|
+
entry = make_entry(title="T", key="k2024")
|
|
116
|
+
assert format_citation(entry, "pandoc") == "[@k2024]"
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
version = 1
|
|
2
|
+
revision = 3
|
|
3
|
+
requires-python = ">=3.11"
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "antilibrary"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
source = { editable = "." }
|
|
9
|
+
dependencies = [
|
|
10
|
+
{ name = "bibtexparser" },
|
|
11
|
+
{ name = "rapidfuzz" },
|
|
12
|
+
{ name = "rich" },
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[package.optional-dependencies]
|
|
16
|
+
dev = [
|
|
17
|
+
{ name = "pytest" },
|
|
18
|
+
{ name = "ruff" },
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[package.metadata]
|
|
22
|
+
requires-dist = [
|
|
23
|
+
{ name = "bibtexparser", specifier = ">=2.0.0b7" },
|
|
24
|
+
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0" },
|
|
25
|
+
{ name = "rapidfuzz", specifier = ">=3.0" },
|
|
26
|
+
{ name = "rich", specifier = ">=13.0" },
|
|
27
|
+
{ name = "ruff", marker = "extra == 'dev'", specifier = ">=0.4" },
|
|
28
|
+
]
|
|
29
|
+
provides-extras = ["dev"]
|
|
30
|
+
|
|
31
|
+
[[package]]
|
|
32
|
+
name = "bibtexparser"
|
|
33
|
+
version = "2.0.0b9"
|
|
34
|
+
source = { registry = "https://pypi.org/simple" }
|
|
35
|
+
dependencies = [
|
|
36
|
+
{ name = "pylatexenc" },
|
|
37
|
+
]
|
|
38
|
+
sdist = { url = "https://files.pythonhosted.org/packages/f4/37/e21f79f5116ad1fb92a8eb0e8a005cad59eb24660003982840daa6b9fafd/bibtexparser-2.0.0b9.tar.gz", hash = "sha256:bcddf796cfe321bb92c17c844ca65d81731fb753a1dd0e82bc76a9947aa0f360", size = 40435, upload-time = "2026-01-29T19:47:52.605Z" }
|
|
39
|
+
wheels = [
|
|
40
|
+
{ url = "https://files.pythonhosted.org/packages/60/8e/5a3726bd421d4977262cee94a3660da4b9ca94397f951499c7d31ec0e863/bibtexparser-2.0.0b9-py3-none-any.whl", hash = "sha256:c30c73a9b4e2e8a4669c8c4d726e2d285990553f60612d8c952786af3cf766ca", size = 41407, upload-time = "2026-01-29T19:47:51.608Z" },
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[[package]]
|
|
44
|
+
name = "colorama"
|
|
45
|
+
version = "0.4.6"
|
|
46
|
+
source = { registry = "https://pypi.org/simple" }
|
|
47
|
+
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
|
48
|
+
wheels = [
|
|
49
|
+
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
[[package]]
|
|
53
|
+
name = "iniconfig"
|
|
54
|
+
version = "2.3.0"
|
|
55
|
+
source = { registry = "https://pypi.org/simple" }
|
|
56
|
+
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
|
|
57
|
+
wheels = [
|
|
58
|
+
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
[[package]]
|
|
62
|
+
name = "markdown-it-py"
|
|
63
|
+
version = "4.0.0"
|
|
64
|
+
source = { registry = "https://pypi.org/simple" }
|
|
65
|
+
dependencies = [
|
|
66
|
+
{ name = "mdurl" },
|
|
67
|
+
]
|
|
68
|
+
sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" }
|
|
69
|
+
wheels = [
|
|
70
|
+
{ url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" },
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
[[package]]
|
|
74
|
+
name = "mdurl"
|
|
75
|
+
version = "0.1.2"
|
|
76
|
+
source = { registry = "https://pypi.org/simple" }
|
|
77
|
+
sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
|
|
78
|
+
wheels = [
|
|
79
|
+
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
[[package]]
|
|
83
|
+
name = "packaging"
|
|
84
|
+
version = "26.2"
|
|
85
|
+
source = { registry = "https://pypi.org/simple" }
|
|
86
|
+
sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" }
|
|
87
|
+
wheels = [
|
|
88
|
+
{ url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" },
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
[[package]]
|
|
92
|
+
name = "pluggy"
|
|
93
|
+
version = "1.6.0"
|
|
94
|
+
source = { registry = "https://pypi.org/simple" }
|
|
95
|
+
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
|
|
96
|
+
wheels = [
|
|
97
|
+
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
[[package]]
|
|
101
|
+
name = "pygments"
|
|
102
|
+
version = "2.20.0"
|
|
103
|
+
source = { registry = "https://pypi.org/simple" }
|
|
104
|
+
sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" }
|
|
105
|
+
wheels = [
|
|
106
|
+
{ url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
[[package]]
|
|
110
|
+
name = "pylatexenc"
|
|
111
|
+
version = "2.10"
|
|
112
|
+
source = { registry = "https://pypi.org/simple" }
|
|
113
|
+
sdist = { url = "https://files.pythonhosted.org/packages/5d/ab/34ec41718af73c00119d0351b7a2531d2ebddb51833a36448fc7b862be60/pylatexenc-2.10.tar.gz", hash = "sha256:3dd8fd84eb46dc30bee1e23eaab8d8fb5a7f507347b23e5f38ad9675c84f40d3", size = 162597, upload-time = "2021-04-06T07:56:07.854Z" }
|
|
114
|
+
|
|
115
|
+
[[package]]
|
|
116
|
+
name = "pytest"
|
|
117
|
+
version = "9.0.3"
|
|
118
|
+
source = { registry = "https://pypi.org/simple" }
|
|
119
|
+
dependencies = [
|
|
120
|
+
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
|
121
|
+
{ name = "iniconfig" },
|
|
122
|
+
{ name = "packaging" },
|
|
123
|
+
{ name = "pluggy" },
|
|
124
|
+
{ name = "pygments" },
|
|
125
|
+
]
|
|
126
|
+
sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" }
|
|
127
|
+
wheels = [
|
|
128
|
+
{ url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" },
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
[[package]]
|
|
132
|
+
name = "rapidfuzz"
|
|
133
|
+
version = "3.14.5"
|
|
134
|
+
source = { registry = "https://pypi.org/simple" }
|
|
135
|
+
sdist = { url = "https://files.pythonhosted.org/packages/2c/21/ef6157213316e85790041254259907eb722e00b03480256c0545d98acd33/rapidfuzz-3.14.5.tar.gz", hash = "sha256:ba10ac57884ce82112f7ed910b67e7fb6072d8ef2c06e30dc63c0f604a112e0e", size = 57901753, upload-time = "2026-04-07T11:16:31.931Z" }
|
|
136
|
+
wheels = [
|
|
137
|
+
{ url = "https://files.pythonhosted.org/packages/e1/f9/3c41a7be8855803f4f6c713b472226a98d31d41869d98f64f4ca790510d6/rapidfuzz-3.14.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e251126d48615e1f02b4a178f2cd0cd4f0332b8a019c01a2e10480f7552554b4", size = 1952372, upload-time = "2026-04-07T11:13:58.32Z" },
|
|
138
|
+
{ url = "https://files.pythonhosted.org/packages/9e/89/c2557e37531d03465193bff0ab9de70b468420a807d71a26a65100635459/rapidfuzz-3.14.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ab449c9abd0d4e1f8145dce0798a4c822a1a1933d613c764a641bea88b8bdab", size = 1159782, upload-time = "2026-04-07T11:14:00.127Z" },
|
|
139
|
+
{ url = "https://files.pythonhosted.org/packages/1a/b2/ffeeb7eca1a897d51b998f4c0ef0281696c3b06abcca4f88f9def708ffe1/rapidfuzz-3.14.5-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cb2829fedd672dd7107267189dabe2bbe07972801d636014417c6861eb89e358", size = 1383677, upload-time = "2026-04-07T11:14:01.696Z" },
|
|
140
|
+
{ url = "https://files.pythonhosted.org/packages/6b/d0/4539e42a2d596e068f7738f279638a4a74edd1fbb6f8594e2458058979c6/rapidfuzz-3.14.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3d50e5861872935fece391351cbb5ba21d1bced277cf5e1143d207a0a35f1925", size = 3168906, upload-time = "2026-04-07T11:14:03.29Z" },
|
|
141
|
+
{ url = "https://files.pythonhosted.org/packages/5e/1c/3ec897eb9d8b05308aa8ef6ae4ed64b088ad521a3f9d8ff469e7e97bc2b0/rapidfuzz-3.14.5-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:7092a216728f80c960bd6b3807275d1ee318b168986bd5dc523349581d4890b8", size = 1478176, upload-time = "2026-04-07T11:14:04.94Z" },
|
|
142
|
+
{ url = "https://files.pythonhosted.org/packages/ab/ba/970c03a12ce20a5399e22afe9f8932fd4cd1265b8a8461d0e63b00eb4eae/rapidfuzz-3.14.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9669753caef7fdc6529f6adcc5883ed98d65976445d9322e7dbdb6b697feee13", size = 2402441, upload-time = "2026-04-07T11:14:07.228Z" },
|
|
143
|
+
{ url = "https://files.pythonhosted.org/packages/81/93/61d351cae60c1d0e21ba5ff1a1015ad045539ed215da9d6e302204ed887a/rapidfuzz-3.14.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:823b1b9d9230809d8edcc18872770764bfe8ef4357995e16744047c8ccf0e489", size = 2511628, upload-time = "2026-04-07T11:14:09.234Z" },
|
|
144
|
+
{ url = "https://files.pythonhosted.org/packages/87/52/374d2d4f60fd98155142a869323aa221e30868cfa1f15171a0f64070c247/rapidfuzz-3.14.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f0b2af76b7e7060c09e1a0dfa9410eb19369cbe6164509bff2ef94094b54d2b6", size = 4275480, upload-time = "2026-04-07T11:14:11.332Z" },
|
|
145
|
+
{ url = "https://files.pythonhosted.org/packages/d8/04/82e7989bc9ec20a15b720a335c5cb6b0724bf6582013898f90a3280cfccd/rapidfuzz-3.14.5-cp311-cp311-win32.whl", hash = "sha256:c5801a89604c65ab4cc9e91b23bc4076d0ca80efd8c976fb63843d7879a85d7f", size = 1725627, upload-time = "2026-04-07T11:14:13.217Z" },
|
|
146
|
+
{ url = "https://files.pythonhosted.org/packages/b9/b5/eca8ac5609bc9bcb02bb6ff87fa5983cc92b8772d66a431556ab8a8c178f/rapidfuzz-3.14.5-cp311-cp311-win_amd64.whl", hash = "sha256:d7ca16637c0ede8243f84074044bd0b2335a0341421f8227c85756de2d18c819", size = 1545977, upload-time = "2026-04-07T11:14:14.766Z" },
|
|
147
|
+
{ url = "https://files.pythonhosted.org/packages/ca/e1/dbf318de28f65fa2cdd0a9dfbdee380f8199eb83b19259bc4f8592551b4e/rapidfuzz-3.14.5-cp311-cp311-win_arm64.whl", hash = "sha256:8c90cdf8516d9057e502aa6003cea71cf5ec27cc44699ca52412b502a04761bb", size = 816827, upload-time = "2026-04-07T11:14:16.788Z" },
|
|
148
|
+
{ url = "https://files.pythonhosted.org/packages/d3/e3/574435c6aafb80254c191ef40d7aca2cb2bb97a095ec9395e9fa59ac307a/rapidfuzz-3.14.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0d3378f471ef440473a396ce2f8e97ee12f89a78b495540e0a5617bbfe895638", size = 1944601, upload-time = "2026-04-07T11:14:18.771Z" },
|
|
149
|
+
{ url = "https://files.pythonhosted.org/packages/d0/1f/fbad3102a255ecc112ce9a7e779bacab7fd14398217be8868dc9082ba363/rapidfuzz-3.14.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e910eebca9fd0eba245c0555e764597e8a0cccb673a92da2dc2397050725f48", size = 1164293, upload-time = "2026-04-07T11:14:20.534Z" },
|
|
150
|
+
{ url = "https://files.pythonhosted.org/packages/88/37/a3eb7ff6121ed3a5f199a8c38cc86c8e481816f879cb0e0b738b078c9a7e/rapidfuzz-3.14.5-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:01550fe5f60fd176aa66b7611289d46dc4aa4b1b904874c7b6d1d54e581c5ec1", size = 1371999, upload-time = "2026-04-07T11:14:22.63Z" },
|
|
151
|
+
{ url = "https://files.pythonhosted.org/packages/79/72/97a9728c711c7c1b06e107d3f0623880fb4ef90e147ed13c551a1730e7cc/rapidfuzz-3.14.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:48bee0b91bebfaec41e1081e351000659ab7570cc4598d617aa04d5bf827f9e6", size = 3145715, upload-time = "2026-04-07T11:14:24.508Z" },
|
|
152
|
+
{ url = "https://files.pythonhosted.org/packages/ed/54/d5caabbea233ac90c286c87c260e49d7641467e87438a18d858e41c82e91/rapidfuzz-3.14.5-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:7e580cb04ad849ae9b786fa21383c6b994b6e6c1444ad1cb9f22392759d72741", size = 1456304, upload-time = "2026-04-07T11:14:26.515Z" },
|
|
153
|
+
{ url = "https://files.pythonhosted.org/packages/fc/a7/2d1a81250ac8c01a0100c026018e76f0e7a097ff63e4c553e02a6938c6fb/rapidfuzz-3.14.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:09d6c9ba091854f07817055d795d604179c12a8f308ba4c7d56f3719dfea1646", size = 2389089, upload-time = "2026-04-07T11:14:28.635Z" },
|
|
154
|
+
{ url = "https://files.pythonhosted.org/packages/65/0d/c47c3872203ae88e6506997c0b576ad731f5261daa25d559be09c9756658/rapidfuzz-3.14.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1e989f86113be66574113b9c7bdf4793f3f863d248e47d911b355e05ca6b6b10", size = 2493404, upload-time = "2026-04-07T11:14:30.577Z" },
|
|
155
|
+
{ url = "https://files.pythonhosted.org/packages/8f/2f/71e0a5a3130792146c8a200a2dd1e52aa16f7c1074012e17f2601eea9a90/rapidfuzz-3.14.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ebd1a18e2e47bc0b292a07e6ed9c3642f8aaa672d12253885f599b50807a4f9", size = 4251709, upload-time = "2026-04-07T11:14:32.451Z" },
|
|
156
|
+
{ url = "https://files.pythonhosted.org/packages/86/45/d39874901abacef325adb5b34ae416817c8486dfb4fb87c7a9b74ec5b072/rapidfuzz-3.14.5-cp312-cp312-win32.whl", hash = "sha256:9981d38a703b86f0e315a3cd229fd1906fe1d91c989ed121fb975b3c849f89f5", size = 1710069, upload-time = "2026-04-07T11:14:34.37Z" },
|
|
157
|
+
{ url = "https://files.pythonhosted.org/packages/85/0b/f65572c53de8a1c704bda707f63a447b67bdbe95d7cdc70d18885e191df5/rapidfuzz-3.14.5-cp312-cp312-win_amd64.whl", hash = "sha256:d8375e3da319593389727c3187ccaf3e0e84199accc530866b8e0f2b79af05e9", size = 1540630, upload-time = "2026-04-07T11:14:36.287Z" },
|
|
158
|
+
{ url = "https://files.pythonhosted.org/packages/5e/c3/143be3a578f989758cae516f3270d5cbb49783a7bfdf57cc27a670e00456/rapidfuzz-3.14.5-cp312-cp312-win_arm64.whl", hash = "sha256:478b59bb018a6780d73f33e38d0b3ec5e968a6c1ed42876b993dd456b7aa20e8", size = 813137, upload-time = "2026-04-07T11:14:38.289Z" },
|
|
159
|
+
{ url = "https://files.pythonhosted.org/packages/11/66/252803f2010ba699618cdc048b6e1f7cc1f433c08b4a9a17579b92ab0142/rapidfuzz-3.14.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ebd8fd343bf8492a1e60bcb6dc99f90f74f65d98d8241a6b3e1fed225b76ecd6", size = 1940205, upload-time = "2026-04-07T11:14:40.319Z" },
|
|
160
|
+
{ url = "https://files.pythonhosted.org/packages/ea/59/b2afd98e41af9cd54554a4c1c423d84cdd60e6b1c0a09496f033b55f60ec/rapidfuzz-3.14.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6737b35d5af7479c5bf9710f7b17edd9d2c43128d974d25fb4ea653e42c64609", size = 1159639, upload-time = "2026-04-07T11:14:42.52Z" },
|
|
161
|
+
{ url = "https://files.pythonhosted.org/packages/a3/31/7aa7e62c4c516a7af322ed0c4f0774208b72d457d0cfec808bad0df12f4a/rapidfuzz-3.14.5-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b002c7994cc9f2bc9d9856f0fbaee6e8072c983873846c92f25cefba5b2a925f", size = 1367194, upload-time = "2026-04-07T11:14:44.25Z" },
|
|
162
|
+
{ url = "https://files.pythonhosted.org/packages/90/79/2fc252a63bc91d3c3b234d0a3a6ad4ebc460037a23cdcdaf9285f986e6c9/rapidfuzz-3.14.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:17a34330cd2a538c1ce5d400b61ba358c5b72c654b928ff87b362e88f8b864c7", size = 3151805, upload-time = "2026-04-07T11:14:46.21Z" },
|
|
163
|
+
{ url = "https://files.pythonhosted.org/packages/17/54/0c83508f2683ea70e2d05f8527eb07328acf7bb1e9d97a3bece5702378e7/rapidfuzz-3.14.5-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:95d937e74c1a7a1287dfb03b62a827be08ede10a155cf1af73bbf47f2b73ee6e", size = 1455667, upload-time = "2026-04-07T11:14:47.991Z" },
|
|
164
|
+
{ url = "https://files.pythonhosted.org/packages/71/1b/070175e873177814d58850a01ebe80e20ae11e93eb4da894d563988660fa/rapidfuzz-3.14.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:46b92a9970dcc34f0096901c792644094cab49554ac3547f35e3aebbdf0a3610", size = 2388246, upload-time = "2026-04-07T11:14:50.098Z" },
|
|
165
|
+
{ url = "https://files.pythonhosted.org/packages/c9/dd/77caf7aaf9c2be050ad1f128d7c24ff0f59079aa62c5f62f9df41c0af45e/rapidfuzz-3.14.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e012177c8e8a8a0754ae0d6027d63042aa5ff036d9f40f07cb3466a6082e21b8", size = 2494333, upload-time = "2026-04-07T11:14:52.303Z" },
|
|
166
|
+
{ url = "https://files.pythonhosted.org/packages/2c/e2/dd7e1f2aa31a8fbbfc16b0610af1d770ffaf1287490f3c8c5b1c52da264f/rapidfuzz-3.14.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a2ae6f53f99c9a0eca7a0afc5b4e45fc73bc1dd4ac74c00509031d76df80ed98", size = 4258579, upload-time = "2026-04-07T11:14:54.538Z" },
|
|
167
|
+
{ url = "https://files.pythonhosted.org/packages/9c/0a/ac99e1ba347ba0e85e0bb60b74231d55fb93c0eff43f2920ccb413d0be08/rapidfuzz-3.14.5-cp313-cp313-win32.whl", hash = "sha256:4a60f0057231188e3bd30216f7b4e0f279b11fa4ec818bb6c1d9f014d1562fbc", size = 1709231, upload-time = "2026-04-07T11:14:56.524Z" },
|
|
168
|
+
{ url = "https://files.pythonhosted.org/packages/cf/cb/0e251d731b3166378644238e8f0cf9e89858c024e19f75ca9f7e3ae83fd5/rapidfuzz-3.14.5-cp313-cp313-win_amd64.whl", hash = "sha256:11bfc2ed8fbe4ab86bd516fadefab126f90e6dcadffa761739fcb304707dfd35", size = 1538519, upload-time = "2026-04-07T11:14:58.635Z" },
|
|
169
|
+
{ url = "https://files.pythonhosted.org/packages/30/6f/4548132acc947db6d5346a248e44a8b3a22d608ef30e770fb578caaf2d00/rapidfuzz-3.14.5-cp313-cp313-win_arm64.whl", hash = "sha256:b486b5218808f6f4dc471b114b1054e63553db69705c97da0271f47bd706aedd", size = 812628, upload-time = "2026-04-07T11:15:00.552Z" },
|
|
170
|
+
{ url = "https://files.pythonhosted.org/packages/00/60/69b177577290c5eab892c6f75fe89c3aff3f9ae80298a78d9372b1cecb9a/rapidfuzz-3.14.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:39ef8658aaf67d51667e7bdaf7096f432333377d8302ac43c70b5df8a4cf89b8", size = 1970231, upload-time = "2026-04-07T11:15:02.603Z" },
|
|
171
|
+
{ url = "https://files.pythonhosted.org/packages/48/38/2fd790052659cc4e2907b63c25433f0987864b445c1aeec1a302ef5ad948/rapidfuzz-3.14.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9ad37a0be705b544af6296da8edddc260d10a8ae5462530fc9991f66498bb1f9", size = 1194394, upload-time = "2026-04-07T11:15:04.572Z" },
|
|
172
|
+
{ url = "https://files.pythonhosted.org/packages/80/f4/28430ad8472fc3536e8ebd51a864a226e979cfe924c6e3f83d111373aa74/rapidfuzz-3.14.5-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d45e06f60729e07d9b20c205f7e5cff90b6ef2584e852eecf46e045aea69627d", size = 1377051, upload-time = "2026-04-07T11:15:06.728Z" },
|
|
173
|
+
{ url = "https://files.pythonhosted.org/packages/77/7e/9aeacabcfd1e77397968362e5b98fe14248b8307011136b17daf99752a8e/rapidfuzz-3.14.5-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e52da10236aa6212de71b9e170bace65b64b129c0dea7fc243d6c9ce976f5074", size = 3160565, upload-time = "2026-04-07T11:15:08.667Z" },
|
|
174
|
+
{ url = "https://files.pythonhosted.org/packages/56/f4/db4dd7be0cd2f2022117ac5407d905f435d60e48baaea313a567ad27e865/rapidfuzz-3.14.5-cp313-cp313t-manylinux_2_39_riscv64.whl", hash = "sha256:440d30faaf682ca496170a7f0cc5453ec942e3e079f0fd802c9a7f938dfb50a3", size = 1442113, upload-time = "2026-04-07T11:15:11.138Z" },
|
|
175
|
+
{ url = "https://files.pythonhosted.org/packages/a4/99/0e9f6aa57f3e32a767216f797e56dc96b720fcecfb9d8ee907ecc82f8d66/rapidfuzz-3.14.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:56227a61fd3d17b0cd9793132431f3a3d07c8654be96794ba9f89fe0fc8b2d09", size = 2396618, upload-time = "2026-04-07T11:15:13.154Z" },
|
|
176
|
+
{ url = "https://files.pythonhosted.org/packages/60/94/44a78e39ffce17cbdd3e2b53b696acc751d5d153be0f499d052b07a4d904/rapidfuzz-3.14.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:2e83cd2e25bb4edd97b689d9979d9c3acccdaaf26ceac08212ceece202febcfa", size = 2478220, upload-time = "2026-04-07T11:15:15.193Z" },
|
|
177
|
+
{ url = "https://files.pythonhosted.org/packages/dd/df/454311469a09a507e9d784a35796742bec22e4cebe75551e2da4e0e290fd/rapidfuzz-3.14.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:af3b859726cd3374287e405e14b9634563c078c5531a4f62375508addebddad1", size = 4265027, upload-time = "2026-04-07T11:15:17.28Z" },
|
|
178
|
+
{ url = "https://files.pythonhosted.org/packages/fc/01/175465a9ab3e3b70ba669058372f009d1d49c1746e2dcd56b69df188d3a5/rapidfuzz-3.14.5-cp313-cp313t-win32.whl", hash = "sha256:8ce1d850b3c0178440efde9e884d98421b5e87ff925f364d6d79e23910d7593f", size = 1766814, upload-time = "2026-04-07T11:15:19.687Z" },
|
|
179
|
+
{ url = "https://files.pythonhosted.org/packages/1b/a0/a9b84a47af06ebed94a1439eb2f02adebfb8628bcd30af1fe3e02f5ef56c/rapidfuzz-3.14.5-cp313-cp313t-win_amd64.whl", hash = "sha256:c84af70bcf34e99aee894e46a0f1ac77f17d0ef828179c387407642e2466d28a", size = 1582448, upload-time = "2026-04-07T11:15:21.98Z" },
|
|
180
|
+
{ url = "https://files.pythonhosted.org/packages/1e/f1/5937800238b3f8248e70860d79f69ba8f73e764fff47e36bc9e2f26dbcc6/rapidfuzz-3.14.5-cp313-cp313t-win_arm64.whl", hash = "sha256:aac0ad28c686a5e72b81668b906c030ee28050b244544b8af68e12fb32543895", size = 832932, upload-time = "2026-04-07T11:15:24.358Z" },
|
|
181
|
+
{ url = "https://files.pythonhosted.org/packages/81/41/aa3ffb3355e62e1bf91f6599b3092e866bc88487a07c524004943c7676df/rapidfuzz-3.14.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1a31cc6d7d03e7318a0974c038959c59e19c752b81115f2e9138b3331cd64d45", size = 1943327, upload-time = "2026-04-07T11:15:26.266Z" },
|
|
182
|
+
{ url = "https://files.pythonhosted.org/packages/2d/e1/c2141f1840a41e07ad2db6f724945f8f8ff3065463899a22939152dd6e09/rapidfuzz-3.14.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0298d357e2bc59d572da4db0bc631009b6f8f6c9bc8c11e99a12b833f16b6575", size = 1161755, upload-time = "2026-04-07T11:15:28.659Z" },
|
|
183
|
+
{ url = "https://files.pythonhosted.org/packages/ca/07/66e753eeaa353161d1d331b7dd517bb349b0bacfebe8496d7b26be26f81f/rapidfuzz-3.14.5-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:59b3dba758661a318995655435c6ab20a04ade79fa51e75bc8dc107cac8df280", size = 1376571, upload-time = "2026-04-07T11:15:31.225Z" },
|
|
184
|
+
{ url = "https://files.pythonhosted.org/packages/c8/85/9535df0b78ba51f478c9ce7eb6d1f85535cc31fe356773b48fd9d3e563ca/rapidfuzz-3.14.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4900143d82071bdda533b00300c40b14b963ff826b3642cc463b6dd0f036585e", size = 3156468, upload-time = "2026-04-07T11:15:33.428Z" },
|
|
185
|
+
{ url = "https://files.pythonhosted.org/packages/81/ee/b667eb93bba6dc4e0de658edd778e1619dc4d6aab68fa5e5c7f075152735/rapidfuzz-3.14.5-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:feedf219672eef83ea6be6f3bb093bba396a8560fc75be85ba225f082903df0a", size = 1458311, upload-time = "2026-04-07T11:15:35.557Z" },
|
|
186
|
+
{ url = "https://files.pythonhosted.org/packages/7d/ce/479074f5624364a48df3403c538797ef22d3ac49c19dc76c3f79fcdcc70c/rapidfuzz-3.14.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:419e4397a36e2665ec992d8d64c20ba4b2a42500c76ecadeca78a4f19cb9cc32", size = 2398228, upload-time = "2026-04-07T11:15:37.669Z" },
|
|
187
|
+
{ url = "https://files.pythonhosted.org/packages/0b/15/a8982f649150fffbdcd6f17565974501f6ab33b2795267bffbd4a7ba905b/rapidfuzz-3.14.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:97131ab2be39043054ee28d99e09efe316e6d53449b7e962dfcf3c2de8b2b246", size = 2497226, upload-time = "2026-04-07T11:15:39.857Z" },
|
|
188
|
+
{ url = "https://files.pythonhosted.org/packages/19/52/5267c03ef6759831b7d4625a0c9c06e87baa2fae084b61ac9c388858317b/rapidfuzz-3.14.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:593c00dac4e30231c35bf3b4f1da8ec0998762e9e94425586a5d636fcd57f9d0", size = 4262283, upload-time = "2026-04-07T11:15:42.279Z" },
|
|
189
|
+
{ url = "https://files.pythonhosted.org/packages/71/c0/2579f343a97f5254c43bb5853baccc01488357dcb64a27bcb869b7888a4a/rapidfuzz-3.14.5-cp314-cp314-win32.whl", hash = "sha256:0084b687b02b4e569b46d8d6d4ad25659528e6081cd6d067ca453a69035f07e4", size = 1744614, upload-time = "2026-04-07T11:15:44.498Z" },
|
|
190
|
+
{ url = "https://files.pythonhosted.org/packages/17/eb/8edfed1e80119dc9c35b11df4bc701eea85622ad681fff0263b6961d3224/rapidfuzz-3.14.5-cp314-cp314-win_amd64.whl", hash = "sha256:5dfa89d78f22cd773054caff44827b846161a29f2dcf7e78b8f90d086621e502", size = 1588971, upload-time = "2026-04-07T11:15:46.86Z" },
|
|
191
|
+
{ url = "https://files.pythonhosted.org/packages/f6/04/5676df93c85cfa57a3045d8047318df9f3cd58c7b8a99340dd95f874795e/rapidfuzz-3.14.5-cp314-cp314-win_arm64.whl", hash = "sha256:67f3f9d2b444268ab53e47d31bab89954888d23c04c6789f2c727e51fe4b1d13", size = 834985, upload-time = "2026-04-07T11:15:49.411Z" },
|
|
192
|
+
{ url = "https://files.pythonhosted.org/packages/f7/0d/4a8988cea658fe335048ddef8c876addff1b6daa3c9ca8ad65a5a2196e69/rapidfuzz-3.14.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:77eac0526899b3c3ad1454bb2b03cdb491d67358ec8ef0c9c48bd61b632b431d", size = 1972517, upload-time = "2026-04-07T11:15:51.819Z" },
|
|
193
|
+
{ url = "https://files.pythonhosted.org/packages/1c/a3/f5cfd9965a9d9a9e32249159797c47b5d6299ea6d1629f9126b25f1c10a3/rapidfuzz-3.14.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b9c6bd754d11f6e78ac54e3d86b4b11dc1ba2f13e5fc958899574532897f5a99", size = 1196056, upload-time = "2026-04-07T11:15:54.292Z" },
|
|
194
|
+
{ url = "https://files.pythonhosted.org/packages/64/07/561c2e40cfd10e6630a7b0ac5a2a813aef50d944bcd1f3d260319d659d5b/rapidfuzz-3.14.5-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:738c96944d076deeaff70e92b65696ab4f7ecb8081d7791c5403a3257dfaf8ff", size = 1374732, upload-time = "2026-04-07T11:15:56.584Z" },
|
|
195
|
+
{ url = "https://files.pythonhosted.org/packages/c2/39/123bb94fee40e2fb3b7c49b80827c7ef42d838e18def3fc2fef5a3cf817a/rapidfuzz-3.14.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4c1bca487a17fe4226b4ffb2d30e799d2b274d692cffa76bd0746f56235fca3", size = 3166902, upload-time = "2026-04-07T11:15:58.768Z" },
|
|
196
|
+
{ url = "https://files.pythonhosted.org/packages/75/0a/45716fafc9fd2e028cf20b5ac5bc704887081cd312f84edb0e325599414b/rapidfuzz-3.14.5-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:af6a90a4ed2a48fa1a2d17e9d824e6c7c950bea5bad0b707c77fd55751e6bfef", size = 1452130, upload-time = "2026-04-07T11:16:01.453Z" },
|
|
197
|
+
{ url = "https://files.pythonhosted.org/packages/ca/49/4e96c413114398481c0a5b0086af32c364a18613c9a2ea578d17c4bea4ee/rapidfuzz-3.14.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bf5018938208d4597b2e679a4f8cff9fd252f1df53583130ae56281a21801b64", size = 2396308, upload-time = "2026-04-07T11:16:03.588Z" },
|
|
198
|
+
{ url = "https://files.pythonhosted.org/packages/89/b7/49fea9fc6878d59bd259d01dd1972d9b86117992b1c66d9b16f0a65273c3/rapidfuzz-3.14.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c0919d1f89ddf91129906705723118ea09754171e4116f5a5dbc667c7bc9b261", size = 2488210, upload-time = "2026-04-07T11:16:05.871Z" },
|
|
199
|
+
{ url = "https://files.pythonhosted.org/packages/0c/44/a1f732b93ffacbdad077b7c801149549b2938e1bece6addb5ad85ed74df8/rapidfuzz-3.14.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:93d8da883a35116d6813432177f35e570db5b0a5e30ecb0cbd7cb39c815735df", size = 4270621, upload-time = "2026-04-07T11:16:08.483Z" },
|
|
200
|
+
{ url = "https://files.pythonhosted.org/packages/bb/ce/ff942d19fce5385054650bb71a58495ddda299d94661ccc4e6e7fa44868b/rapidfuzz-3.14.5-cp314-cp314t-win32.whl", hash = "sha256:0f23e37019ec07712d58976b1ab2b889f8649a7f7c2f626a2f34ea9139e79279", size = 1803950, upload-time = "2026-04-07T11:16:10.873Z" },
|
|
201
|
+
{ url = "https://files.pythonhosted.org/packages/5c/0f/9aafc63f9661222b819b391c187eed29fc90ad5935f9690e5ecc2d2047a4/rapidfuzz-3.14.5-cp314-cp314t-win_amd64.whl", hash = "sha256:7d5ca9c7832e6879a707296d1463685f7c243a27846227044504741640caec66", size = 1632357, upload-time = "2026-04-07T11:16:13.1Z" },
|
|
202
|
+
{ url = "https://files.pythonhosted.org/packages/70/a6/51fc1b0e61e3326e1c68a61cfd0c6b3c34c843681c4b1eefbf0596f59162/rapidfuzz-3.14.5-cp314-cp314t-win_arm64.whl", hash = "sha256:3e91dcd2549b8f8d843f98ba03a17e01f3d8b72ce942adbbb6761bc58ffce813", size = 855409, upload-time = "2026-04-07T11:16:15.787Z" },
|
|
203
|
+
{ url = "https://files.pythonhosted.org/packages/d9/ee/e71853bf82846c5c2174b924b71d8e8099fb05ff87c958a720380b434ba3/rapidfuzz-3.14.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:578e6051f6d5e6200c259b47a103cf06bb875ab5814d17333fc0b5c290b22f4c", size = 1888603, upload-time = "2026-04-07T11:16:18.223Z" },
|
|
204
|
+
{ url = "https://files.pythonhosted.org/packages/36/82/40f67b730f32be2ebad9f62add1571c754f52249254b2e88af094b907eee/rapidfuzz-3.14.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fbf1b8bb2695415b347f3727da1addca2acb82c9b97ac86bebf8b1bead1eb12d", size = 1120599, upload-time = "2026-04-07T11:16:20.682Z" },
|
|
205
|
+
{ url = "https://files.pythonhosted.org/packages/ef/9f/a3635cc4ec8fc6e14b46e7db1f7f8763d8c4bef33dcc124eea2e6cb2c8f3/rapidfuzz-3.14.5-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f4a8f5cc84c7ad6bffa0e9947b33eb343ad66e6b53e94fe54378a5508c5ed53", size = 1348524, upload-time = "2026-04-07T11:16:23.451Z" },
|
|
206
|
+
{ url = "https://files.pythonhosted.org/packages/cc/1b/2b229520f0b48464cfcd7aa758f74551d12c9bc4ab544022a60210aab064/rapidfuzz-3.14.5-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:97c6d85283629646fa87acc22c66b30ea9d4de7f6fdf887daa2e30fa041829b5", size = 3099302, upload-time = "2026-04-07T11:16:25.858Z" },
|
|
207
|
+
{ url = "https://files.pythonhosted.org/packages/aa/b5/363906b1064fc6fe611783a61764927bbd91919aaaabe8cba82151ca93ef/rapidfuzz-3.14.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:dfef96543ced67d9513a422755db422ae1dc34dade0a1485e0b43e7342ed3ebf", size = 1509889, upload-time = "2026-04-07T11:16:28.487Z" },
|
|
208
|
+
]
|
|
209
|
+
|
|
210
|
+
[[package]]
|
|
211
|
+
name = "rich"
|
|
212
|
+
version = "15.0.0"
|
|
213
|
+
source = { registry = "https://pypi.org/simple" }
|
|
214
|
+
dependencies = [
|
|
215
|
+
{ name = "markdown-it-py" },
|
|
216
|
+
{ name = "pygments" },
|
|
217
|
+
]
|
|
218
|
+
sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" }
|
|
219
|
+
wheels = [
|
|
220
|
+
{ url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" },
|
|
221
|
+
]
|
|
222
|
+
|
|
223
|
+
[[package]]
|
|
224
|
+
name = "ruff"
|
|
225
|
+
version = "0.15.12"
|
|
226
|
+
source = { registry = "https://pypi.org/simple" }
|
|
227
|
+
sdist = { url = "https://files.pythonhosted.org/packages/99/43/3291f1cc9106f4c63bdce7a8d0df5047fe8422a75b091c16b5e9355e0b11/ruff-0.15.12.tar.gz", hash = "sha256:ecea26adb26b4232c0c2ca19ccbc0083a68344180bba2a600605538ce51a40a6", size = 4643852, upload-time = "2026-04-24T18:17:14.305Z" }
|
|
228
|
+
wheels = [
|
|
229
|
+
{ url = "https://files.pythonhosted.org/packages/c3/6e/e78ffb61d4686f3d96ba3df2c801161843746dcbcbb17a1e927d4829312b/ruff-0.15.12-py3-none-linux_armv6l.whl", hash = "sha256:f86f176e188e94d6bdbc09f09bfd9dc729059ad93d0e7390b5a73efe19f8861c", size = 10640713, upload-time = "2026-04-24T18:17:22.841Z" },
|
|
230
|
+
{ url = "https://files.pythonhosted.org/packages/ae/08/a317bc231fb9e7b93e4ef3089501e51922ff88d6936ce5cf870c4fe55419/ruff-0.15.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e3bcd123364c3770b8e1b7baaf343cc99a35f197c5c6e8af79015c666c423a6c", size = 11069267, upload-time = "2026-04-24T18:17:30.105Z" },
|
|
231
|
+
{ url = "https://files.pythonhosted.org/packages/aa/a4/f828e9718d3dce1f5f11c39c4f65afd32783c8b2aebb2e3d259e492c47bd/ruff-0.15.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fe87510d000220aa1ed530d4448a7c696a0cae1213e5ec30e5874287b66557b5", size = 10397182, upload-time = "2026-04-24T18:17:07.177Z" },
|
|
232
|
+
{ url = "https://files.pythonhosted.org/packages/71/e0/3310fc6d1b5e1fdea22bf3b1b807c7e187b581021b0d7d4514cccdb5fb71/ruff-0.15.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84a1630093121375a3e2a95b4a6dc7b59e2b4ee76216e32d81aae550a832d002", size = 10758012, upload-time = "2026-04-24T18:16:55.759Z" },
|
|
233
|
+
{ url = "https://files.pythonhosted.org/packages/11/c1/a606911aee04c324ddaa883ae418f3569792fd3c4a10c50e0dd0a2311e1e/ruff-0.15.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fb129f40f114f089ebe0ca56c0d251cf2061b17651d464bb6478dc01e69f11f5", size = 10447479, upload-time = "2026-04-24T18:16:51.677Z" },
|
|
234
|
+
{ url = "https://files.pythonhosted.org/packages/9d/68/4201e8444f0894f21ab4aeeaee68aa4f10b51613514a20d80bd628d57e88/ruff-0.15.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0c862b172d695db7598426b8af465e7e9ac00a3ea2a3630ee67eb82e366aaa6", size = 11234040, upload-time = "2026-04-24T18:17:16.529Z" },
|
|
235
|
+
{ url = "https://files.pythonhosted.org/packages/34/ff/8a6d6cf4ccc23fd67060874e832c18919d1557a0611ebef03fdb01fff11e/ruff-0.15.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2849ea9f3484c3aca43a82f484210370319e7170df4dfe4843395ddf6c57bc33", size = 12087377, upload-time = "2026-04-24T18:17:04.944Z" },
|
|
236
|
+
{ url = "https://files.pythonhosted.org/packages/85/f6/c669cf73f5152f623d34e69866a46d5e6185816b19fcd5b6dd8a2d299922/ruff-0.15.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e77c7e51c07fe396826d5969a5b846d9cd4c402535835fb6e21ce8b28fef847", size = 11367784, upload-time = "2026-04-24T18:17:25.409Z" },
|
|
237
|
+
{ url = "https://files.pythonhosted.org/packages/e8/39/c61d193b8a1daaa8977f7dea9e8d8ba866e02ea7b65d32f6861693aa4c12/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b2f4f2f3b1026b5fb449b467d9264bf22067b600f7b6f41fc5958909f449d0", size = 11344088, upload-time = "2026-04-24T18:17:12.258Z" },
|
|
238
|
+
{ url = "https://files.pythonhosted.org/packages/c2/8d/49afab3645e31e12c590acb6d3b5b69d7aab5b81926dbaf7461f9441f37a/ruff-0.15.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9ba3b8f1afd7e2e43d8943e55f249e13f9682fde09711644a6e7290eb4f3e339", size = 11271770, upload-time = "2026-04-24T18:17:02.457Z" },
|
|
239
|
+
{ url = "https://files.pythonhosted.org/packages/46/06/33f41fe94403e2b755481cdfb9b7ef3e4e0ed031c4581124658d935d52b4/ruff-0.15.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e852ba9fdc890655e1d78f2df1499efbe0e54126bd405362154a75e2bde159c5", size = 10719355, upload-time = "2026-04-24T18:17:27.648Z" },
|
|
240
|
+
{ url = "https://files.pythonhosted.org/packages/0d/59/18aa4e014debbf559670e4048e39260a85c7fcee84acfd761ac01e7b8d35/ruff-0.15.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dd8aed930da53780d22fc70bdf84452c843cf64f8cb4eb38984319c24c5cd5fd", size = 10462758, upload-time = "2026-04-24T18:17:32.347Z" },
|
|
241
|
+
{ url = "https://files.pythonhosted.org/packages/25/e7/cc9f16fd0f3b5fddcbd7ec3d6ae30c8f3fde1047f32a4093a98d633c6570/ruff-0.15.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:01da3988d225628b709493d7dc67c3b9b12c0210016b08690ef9bd27970b262b", size = 10953498, upload-time = "2026-04-24T18:17:20.674Z" },
|
|
242
|
+
{ url = "https://files.pythonhosted.org/packages/72/7a/a9ba7f98c7a575978698f4230c5e8cc54bbc761af34f560818f933dafa0c/ruff-0.15.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9cae0f92bd5700d1213188b31cd3bdd2b315361296d10b96b8e2337d3d11f53e", size = 11447765, upload-time = "2026-04-24T18:17:09.755Z" },
|
|
243
|
+
{ url = "https://files.pythonhosted.org/packages/ea/f9/0ae446942c846b8266059ad8a30702a35afae55f5cdc54c5adf8d7afdc27/ruff-0.15.12-py3-none-win32.whl", hash = "sha256:d0185894e038d7043ba8fd6aee7499ece6462dc0ea9f1e260c7451807c714c20", size = 10657277, upload-time = "2026-04-24T18:17:18.591Z" },
|
|
244
|
+
{ url = "https://files.pythonhosted.org/packages/33/f1/9614e03e1cdcbf9437570b5400ced8a720b5db22b28d8e0f1bda429f660d/ruff-0.15.12-py3-none-win_amd64.whl", hash = "sha256:c87a162d61ab3adca47c03f7f717c68672edec7d1b5499e652331780fe74950d", size = 11837758, upload-time = "2026-04-24T18:17:00.113Z" },
|
|
245
|
+
{ url = "https://files.pythonhosted.org/packages/c0/98/6beb4b351e472e5f4c4613f7c35a5290b8be2497e183825310c4c3a3984b/ruff-0.15.12-py3-none-win_arm64.whl", hash = "sha256:a538f7a82d061cee7be55542aca1d86d1393d55d81d4fcc314370f4340930d4f", size = 11120821, upload-time = "2026-04-24T18:16:57.979Z" },
|
|
246
|
+
]
|