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.
Files changed (31) hide show
  1. {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/PKG-INFO +1 -1
  2. {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/pyproject.toml +1 -1
  3. bibdeskparser-0.4.0/src/bibdeskparser/checks.py +149 -0
  4. {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/cli.py +498 -65
  5. {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/config.py +71 -0
  6. {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/editing.py +11 -28
  7. {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/entry.py +23 -9
  8. {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/entrytypes.py +18 -2
  9. bibdeskparser-0.4.0/src/bibdeskparser/exporting.py +691 -0
  10. {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/fetch.py +2 -1
  11. bibdeskparser-0.4.0/src/bibdeskparser/identifiers.py +202 -0
  12. {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/importing.py +126 -34
  13. {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/library.py +147 -22
  14. {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/macros.py +16 -16
  15. {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/names.py +8 -1
  16. {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/preprints.py +12 -4
  17. {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/render.py +279 -61
  18. bibdeskparser-0.3.0/src/bibdeskparser/exporting.py +0 -331
  19. bibdeskparser-0.3.0/src/bibdeskparser/identifiers.py +0 -51
  20. {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/README.md +0 -0
  21. {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/__init__.py +0 -0
  22. {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/abstracts.py +0 -0
  23. {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/abstracttext.py +0 -0
  24. {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/bdskfile.py +0 -0
  25. {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/groups.py +0 -0
  26. {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/header.py +0 -0
  27. {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/middleware.py +0 -0
  28. {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/search.py +0 -0
  29. {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/specifiers.py +0 -0
  30. {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/texmap.py +0 -0
  31. {bibdeskparser-0.3.0 → bibdeskparser-0.4.0}/src/bibdeskparser/writer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: bibdeskparser
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Parser for Bibdesk database files
5
5
  Author: Michael Goerz
6
6
  Author-email: Michael Goerz <mail@michaelgoerz.net>
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "bibdeskparser"
7
- version = "0.3.0"
7
+ version = "0.4.0"
8
8
  description = "Parser for Bibdesk database files"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -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
+ ]