bibdeskparser 0.3.0__tar.gz → 0.4.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.
- {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/PKG-INFO +1 -1
- {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/pyproject.toml +1 -1
- bibdeskparser-0.4.0/src/bibdeskparser/checks.py +149 -0
- {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/cli.py +498 -65
- {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/config.py +71 -0
- {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/editing.py +11 -28
- {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/entry.py +23 -9
- {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/entrytypes.py +18 -2
- bibdeskparser-0.4.0/src/bibdeskparser/exporting.py +691 -0
- {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/fetch.py +2 -1
- bibdeskparser-0.4.0/src/bibdeskparser/identifiers.py +202 -0
- {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/importing.py +126 -34
- {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/library.py +147 -22
- {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/macros.py +16 -16
- {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/names.py +8 -1
- {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/preprints.py +12 -4
- {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/render.py +279 -61
- bibdeskparser-0.3.0/src/bibdeskparser/exporting.py +0 -331
- bibdeskparser-0.3.0/src/bibdeskparser/identifiers.py +0 -51
- {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/README.md +0 -0
- {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/__init__.py +0 -0
- {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/abstracts.py +0 -0
- {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/abstracttext.py +0 -0
- {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/bdskfile.py +0 -0
- {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/groups.py +0 -0
- {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/header.py +0 -0
- {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/middleware.py +0 -0
- {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/search.py +0 -0
- {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/specifiers.py +0 -0
- {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/texmap.py +0 -0
- {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/writer.py +0 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""Standing audits backing the `check` CLI command.
|
|
2
|
+
|
|
3
|
+
Pure inspection of a loaded {class}`bibdeskparser.Library`: each
|
|
4
|
+
function returns {class}`Problem` records and never modifies the
|
|
5
|
+
library. The CLI command turns the records into a report and a
|
|
6
|
+
pass/fail exit code.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from collections import namedtuple
|
|
10
|
+
|
|
11
|
+
from bibtexparser.model import DuplicateBlockKeyBlock
|
|
12
|
+
|
|
13
|
+
from .config import active
|
|
14
|
+
from .identifiers import _entry_preprint, _preprint_journal
|
|
15
|
+
from .library import _bare_macro_fields, _field_state
|
|
16
|
+
from .macros import MacroString
|
|
17
|
+
|
|
18
|
+
__all__ = []
|
|
19
|
+
|
|
20
|
+
# All members whose name does not start with an underscore must be listed
|
|
21
|
+
# either in __all__ or in __private__
|
|
22
|
+
__private__ = ["Problem", "collect_problems"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
#: One audit finding. `check` names the audit (`"parse"`,
|
|
26
|
+
#: `"duplicate_keys"`, `"doi"`, `"journal"`, `"names"`, or
|
|
27
|
+
#: `"unused_strings"`), `key` is the citation key the problem is tied
|
|
28
|
+
#: to (`None` for a problem that concerns the file as a whole), and
|
|
29
|
+
#: `message` describes the problem.
|
|
30
|
+
Problem = namedtuple("Problem", ["check", "key", "message"])
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def collect_problems(library, keys=None):
|
|
34
|
+
"""Every standing-audit problem in `library`, as a `list` of
|
|
35
|
+
{class}`Problem`.
|
|
36
|
+
|
|
37
|
+
Without `keys`, all audits run over the entire library. With
|
|
38
|
+
`keys` (an iterable of citation keys, all of which must exist in
|
|
39
|
+
`library`), the doi, journal, and names audits cover only those
|
|
40
|
+
entries, the duplicate-keys audit reports only those keys, and
|
|
41
|
+
the unused-macros audit is skipped; problems parsing the file
|
|
42
|
+
itself are always included.
|
|
43
|
+
"""
|
|
44
|
+
problems = _parse_problems(library)
|
|
45
|
+
if keys is None:
|
|
46
|
+
duplicates = library.duplicate_keys
|
|
47
|
+
entries = library.entries
|
|
48
|
+
else:
|
|
49
|
+
keys = list(dict.fromkeys(keys))
|
|
50
|
+
duplicates = [key for key in library.duplicate_keys if key in keys]
|
|
51
|
+
entries = [library[key] for key in keys]
|
|
52
|
+
problems += [
|
|
53
|
+
Problem("duplicate_keys", key, "duplicate citation key")
|
|
54
|
+
for key in duplicates
|
|
55
|
+
]
|
|
56
|
+
for entry in entries:
|
|
57
|
+
problems += _entry_problems(entry, library)
|
|
58
|
+
if keys is None:
|
|
59
|
+
problems += _unused_string_problems(library)
|
|
60
|
+
return problems
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _parse_problems(library):
|
|
64
|
+
"""Problems for the blocks that were skipped when parsing the
|
|
65
|
+
`.bib` file, excluding duplicate-key blocks (which the
|
|
66
|
+
duplicate-keys audit reports per key instead)."""
|
|
67
|
+
return [
|
|
68
|
+
Problem(
|
|
69
|
+
"parse",
|
|
70
|
+
None,
|
|
71
|
+
f"block at line {block.start_line + 1} could not be "
|
|
72
|
+
f"parsed: {block.error}",
|
|
73
|
+
)
|
|
74
|
+
# pylint: disable-next=protected-access
|
|
75
|
+
for block in library._library.failed_blocks
|
|
76
|
+
if not isinstance(block, DuplicateBlockKeyBlock)
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _entry_problems(entry, library):
|
|
81
|
+
"""The doi, journal, and names problems of a single `entry`."""
|
|
82
|
+
problems = []
|
|
83
|
+
archives = active.preprint_archives
|
|
84
|
+
is_preprint = _entry_preprint(entry, archives) is not None
|
|
85
|
+
if (
|
|
86
|
+
entry.entry_type.lower() == "article"
|
|
87
|
+
and not is_preprint
|
|
88
|
+
and _field_state(entry, "doi") == "missing"
|
|
89
|
+
):
|
|
90
|
+
problems.append(Problem("doi", entry.key, "missing doi"))
|
|
91
|
+
if "journal" in entry:
|
|
92
|
+
problems += _journal_problems(entry, library, archives)
|
|
93
|
+
for field in ("author", "editor"):
|
|
94
|
+
if field in entry:
|
|
95
|
+
try:
|
|
96
|
+
getattr(entry, field)
|
|
97
|
+
except Exception as exc: # pylint: disable=broad-except
|
|
98
|
+
problems.append(
|
|
99
|
+
Problem(
|
|
100
|
+
"names",
|
|
101
|
+
entry.key,
|
|
102
|
+
f"{field} does not parse as names: {exc}",
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
return problems
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _journal_problems(entry, library, archives):
|
|
109
|
+
"""The problems with `entry`'s `journal` field: a reference to an
|
|
110
|
+
undefined `@string` macro, or a non-empty literal value that is
|
|
111
|
+
not a recognized preprint pseudo-journal."""
|
|
112
|
+
value = entry["journal"]
|
|
113
|
+
if isinstance(value, MacroString):
|
|
114
|
+
name = str(value)
|
|
115
|
+
# pylint: disable-next=protected-access
|
|
116
|
+
if name.lower() not in library._all_strings():
|
|
117
|
+
return [
|
|
118
|
+
Problem(
|
|
119
|
+
"journal",
|
|
120
|
+
entry.key,
|
|
121
|
+
f"journal references undefined @string macro {name!r}",
|
|
122
|
+
)
|
|
123
|
+
]
|
|
124
|
+
return []
|
|
125
|
+
text = str(value).strip()
|
|
126
|
+
if text and _preprint_journal(text, archives) is None:
|
|
127
|
+
return [
|
|
128
|
+
Problem(
|
|
129
|
+
"journal",
|
|
130
|
+
entry.key,
|
|
131
|
+
f"journal is the literal string {text!r}, not an "
|
|
132
|
+
"@string macro reference",
|
|
133
|
+
)
|
|
134
|
+
]
|
|
135
|
+
return []
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _unused_string_problems(library):
|
|
139
|
+
"""Problems for the `@string` macros defined in the `.bib` file
|
|
140
|
+
but not referenced by any entry."""
|
|
141
|
+
referenced = set()
|
|
142
|
+
for entry in library.entries:
|
|
143
|
+
for _, value in _bare_macro_fields(entry):
|
|
144
|
+
referenced.add(value.lower())
|
|
145
|
+
return [
|
|
146
|
+
Problem("unused_strings", None, f"unused @string macro {name!r}")
|
|
147
|
+
for name in library.strings
|
|
148
|
+
if name not in referenced
|
|
149
|
+
]
|