pynakes 0.5.1__py3-none-any.whl

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 (109) hide show
  1. pynakes/__init__.py +8 -0
  2. pynakes/__main__.py +6 -0
  3. pynakes/_calendar.py +58 -0
  4. pynakes/_engine_groups.py +88 -0
  5. pynakes/_engine_helpers.py +741 -0
  6. pynakes/_engine_keys.py +92 -0
  7. pynakes/_engine_ops.py +564 -0
  8. pynakes/_identifiers.py +69 -0
  9. pynakes/_text_utils.py +163 -0
  10. pynakes/authors.py +270 -0
  11. pynakes/batch.py +165 -0
  12. pynakes/bibtex_parser.py +386 -0
  13. pynakes/bibtex_writer.py +182 -0
  14. pynakes/canonical.py +431 -0
  15. pynakes/capabilities.py +345 -0
  16. pynakes/cli.py +217 -0
  17. pynakes/cli_commands/__init__.py +1 -0
  18. pynakes/cli_commands/_fetch_report.py +39 -0
  19. pynakes/cli_commands/_reference.py +129 -0
  20. pynakes/cli_commands/add.py +150 -0
  21. pynakes/cli_commands/batch.py +87 -0
  22. pynakes/cli_commands/capabilities.py +34 -0
  23. pynakes/cli_commands/convert.py +185 -0
  24. pynakes/cli_commands/dedupe.py +116 -0
  25. pynakes/cli_commands/edit.py +105 -0
  26. pynakes/cli_commands/fetch.py +221 -0
  27. pynakes/cli_commands/fields.py +206 -0
  28. pynakes/cli_commands/files.py +110 -0
  29. pynakes/cli_commands/format.py +217 -0
  30. pynakes/cli_commands/groups.py +355 -0
  31. pynakes/cli_commands/import_ref.py +176 -0
  32. pynakes/cli_commands/init.py +302 -0
  33. pynakes/cli_commands/inspect.py +115 -0
  34. pynakes/cli_commands/integrity.py +173 -0
  35. pynakes/cli_commands/keys.py +457 -0
  36. pynakes/cli_commands/lint.py +68 -0
  37. pynakes/cli_commands/metadata.py +257 -0
  38. pynakes/cli_commands/normalize.py +206 -0
  39. pynakes/cli_commands/remove.py +86 -0
  40. pynakes/cli_commands/search.py +86 -0
  41. pynakes/cli_commands/setops.py +360 -0
  42. pynakes/cli_commands/show.py +48 -0
  43. pynakes/cli_commands/tex.py +277 -0
  44. pynakes/cli_commands/used.py +142 -0
  45. pynakes/cli_common.py +543 -0
  46. pynakes/cli_discovery.py +326 -0
  47. pynakes/convert.py +270 -0
  48. pynakes/dedupe.py +449 -0
  49. pynakes/diff.py +32 -0
  50. pynakes/editing.py +421 -0
  51. pynakes/engine.py +449 -0
  52. pynakes/fetch.py +695 -0
  53. pynakes/fetch_progress.py +35 -0
  54. pynakes/fields.py +280 -0
  55. pynakes/files.py +222 -0
  56. pynakes/filestore.py +913 -0
  57. pynakes/formatters/__init__.py +91 -0
  58. pynakes/formatters/_case.py +254 -0
  59. pynakes/formatters/_date.py +91 -0
  60. pynakes/formatters/_extra.py +130 -0
  61. pynakes/formatters/_latex.py +494 -0
  62. pynakes/formatters/_latex_tables.py +154 -0
  63. pynakes/formatters/_pages.py +32 -0
  64. pynakes/formatters/_registry.py +87 -0
  65. pynakes/formatters/_units.py +53 -0
  66. pynakes/group_tree.py +624 -0
  67. pynakes/groups.py +73 -0
  68. pynakes/importer.py +469 -0
  69. pynakes/inheritance.py +155 -0
  70. pynakes/initialize.py +264 -0
  71. pynakes/integrity.py +679 -0
  72. pynakes/interchange/__init__.py +75 -0
  73. pynakes/interchange/_shared.py +262 -0
  74. pynakes/interchange/csl.py +170 -0
  75. pynakes/interchange/csv.py +72 -0
  76. pynakes/interchange/endnote.py +171 -0
  77. pynakes/interchange/mods.py +328 -0
  78. pynakes/interchange/ris.py +152 -0
  79. pynakes/io.py +187 -0
  80. pynakes/journals.py +517 -0
  81. pynakes/keys.py +363 -0
  82. pynakes/lint.py +673 -0
  83. pynakes/metadata/__init__.py +196 -0
  84. pynakes/metadata/core.py +588 -0
  85. pynakes/metadata/jabref.py +888 -0
  86. pynakes/metadata/schema.py +190 -0
  87. pynakes/model.py +578 -0
  88. pynakes/normalize.py +540 -0
  89. pynakes/providers/__init__.py +1 -0
  90. pynakes/providers/_http.py +255 -0
  91. pynakes/providers/arxiv.py +133 -0
  92. pynakes/providers/crossref.py +78 -0
  93. pynakes/providers/doi.py +31 -0
  94. pynakes/providers/identity.py +48 -0
  95. pynakes/providers/openalex.py +122 -0
  96. pynakes/providers/pdf_overrides.json +106 -0
  97. pynakes/providers/pdf_overrides.py +61 -0
  98. pynakes/providers/publisher.py +111 -0
  99. pynakes/providers/semantic_scholar.py +45 -0
  100. pynakes/py.typed +0 -0
  101. pynakes/search.py +222 -0
  102. pynakes/setops.py +212 -0
  103. pynakes/usage.py +378 -0
  104. pynakes-0.5.1.dist-info/METADATA +242 -0
  105. pynakes-0.5.1.dist-info/RECORD +109 -0
  106. pynakes-0.5.1.dist-info/WHEEL +5 -0
  107. pynakes-0.5.1.dist-info/entry_points.txt +2 -0
  108. pynakes-0.5.1.dist-info/licenses/LICENSE +21 -0
  109. pynakes-0.5.1.dist-info/top_level.txt +1 -0
pynakes/__init__.py ADDED
@@ -0,0 +1,8 @@
1
+ """pynakes: Agent-friendly BibTeX library management."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ try:
6
+ __version__ = version("pynakes")
7
+ except PackageNotFoundError: # running from a source tree that isn't installed
8
+ __version__ = "0.0.0+unknown"
pynakes/__main__.py ADDED
@@ -0,0 +1,6 @@
1
+ """Entry point for running pynakes as a module."""
2
+
3
+ from pynakes.cli import app
4
+
5
+ if __name__ == "__main__":
6
+ app()
pynakes/_calendar.py ADDED
@@ -0,0 +1,58 @@
1
+ """Canonical month name/number tables shared across pynakes.
2
+
3
+ Consolidates month-related data that was previously defined independently in
4
+ :mod:`pynakes.model`, :mod:`pynakes.interchange._shared`, and
5
+ :mod:`pynakes.formatters._date`. All three now import from this module,
6
+ guaranteeing a single source of truth.
7
+ """
8
+
9
+ # Three-letter BibTeX month macros (lowercase) mapped to full month names.
10
+ # This is the same dict historically exposed as ``COMMON_STRINGS`` in model.py.
11
+ MONTH_ABBR_TO_NAME: dict[str, str] = {
12
+ "jan": "January",
13
+ "feb": "February",
14
+ "mar": "March",
15
+ "apr": "April",
16
+ "may": "May",
17
+ "jun": "June",
18
+ "jul": "July",
19
+ "aug": "August",
20
+ "sep": "September",
21
+ "oct": "October",
22
+ "nov": "November",
23
+ "dec": "December",
24
+ }
25
+
26
+ MONTH_ABBRS: list[str] = list(MONTH_ABBR_TO_NAME)
27
+
28
+ MONTH_NAMES: list[str] = [name.lower() for name in MONTH_ABBR_TO_NAME.values()]
29
+
30
+ # Three-letter macro → month number string (1-12, BibTeX-compatible).
31
+ # This is the same dict historically exposed as ``MONTH_NUMBERS`` in
32
+ # :mod:`pynakes.interchange._shared`.
33
+ MONTH_ABBR_TO_NUM: dict[str, str] = {
34
+ "jan": "1",
35
+ "feb": "2",
36
+ "mar": "3",
37
+ "apr": "4",
38
+ "may": "5",
39
+ "jun": "6",
40
+ "jul": "7",
41
+ "aug": "8",
42
+ "sep": "9",
43
+ "oct": "10",
44
+ "nov": "11",
45
+ "dec": "12",
46
+ }
47
+
48
+ # Any month name (three-letter abbreviation or full lowercase) → 1-based int.
49
+ _MONTH_TO_NUM: dict[str, int] = {name: i + 1 for i, name in enumerate(MONTH_ABBRS)}
50
+ _MONTH_TO_NUM.update({name: i + 1 for i, name in enumerate(MONTH_NAMES)})
51
+
52
+ # Two-digit month number string → three-letter abbreviation.
53
+ MONTH_NUM_TO_ABBR: dict[str, str] = {f"{i + 1:02d}": abbr for i, abbr in enumerate(MONTH_ABBRS)}
54
+
55
+
56
+ def month_name_to_int(token: str) -> int | None:
57
+ """Return 1-12 for a month name/abbreviation, or ``None``."""
58
+ return _MONTH_TO_NUM.get(token.strip().lower().rstrip("."))
@@ -0,0 +1,88 @@
1
+ """Group-operation mixin for :class:`~pynakes.engine.Bibliography`.
2
+
3
+ All methods here delegate to :mod:`pynakes.groups` / :mod:`pynakes.group_tree`;
4
+ the engine derives staged state from their model mutations. Do not import this
5
+ module directly; use ``pynakes.engine``.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from pynakes import group_tree as group_tree_ops
11
+ from pynakes import groups as group_ops
12
+
13
+
14
+ class BibliographyGroups:
15
+ """Mixin providing group-tree operations for :class:`~pynakes.engine.Bibliography`.
16
+
17
+ Consumers must not instantiate this class directly.
18
+ """
19
+
20
+ def list_groups(self) -> list[str]:
21
+ """Return all group names in first-seen order."""
22
+ return group_ops.list_groups(self.lib)
23
+
24
+ def list_entries_in_group(self, group: str) -> list[str]:
25
+ """Return entry keys that belong to ``group``."""
26
+ return group_ops.list_entries_in_group(self.lib, group)
27
+
28
+ def add_to_group(self, key: str, group: str) -> int:
29
+ """Add every entry with ``key`` to ``group``.
30
+
31
+ When the library has a group-tree, the group is also registered as a node
32
+ so the new membership is visible to ``groups tree``/``groups list`` and to
33
+ JabRef: an entry ``groups={X}`` with no ``X`` in the tree is an orphan
34
+ membership, invisible everywhere the tree drives the view. Flat, tree-less
35
+ libraries keep entry-field-only groups (surfaced by ``groups list``).
36
+ """
37
+ count = group_ops.add_to_group(self.lib, key, group)
38
+ if count and group_tree_ops.list_tree(self.lib) is not None:
39
+ group_tree_ops.add_node(self.lib, group) # no-op if already a node
40
+ return count
41
+
42
+ def remove_from_group(self, key: str, group: str) -> int:
43
+ """Remove every entry with ``key`` from ``group``."""
44
+ count = group_ops.remove_from_group(self.lib, key, group)
45
+ return count
46
+
47
+ def list_tree(self) -> list[group_tree_ops.GroupNode] | None:
48
+ """Return the group hierarchy tree, or ``None`` if none defined."""
49
+ return group_tree_ops.list_tree(self.lib)
50
+
51
+ def add_group_node(
52
+ self,
53
+ name: str,
54
+ *,
55
+ parent: str = "",
56
+ context: int = 2,
57
+ color: str = "",
58
+ expanded: bool = True,
59
+ ) -> bool:
60
+ """Add a group node to the tree."""
61
+ ok = group_tree_ops.add_node(
62
+ self.lib, name, parent=parent, context=context, color=color, expanded=expanded
63
+ )
64
+ return ok
65
+
66
+ def remove_group_node(self, name: str) -> int:
67
+ """Remove a group node and its descendants from the tree."""
68
+ count = group_tree_ops.remove_node(self.lib, name)
69
+ return count
70
+
71
+ def rename_group_node(self, old_name: str, new_name: str) -> bool:
72
+ """Rename a group node, updating parent references in children."""
73
+ ok = group_tree_ops.rename_node(self.lib, old_name, new_name)
74
+ return ok
75
+
76
+ def move_group_node(self, name: str, new_parent: str) -> bool:
77
+ """Move a group node to a new parent."""
78
+ ok = group_tree_ops.move_node(self.lib, name, new_parent)
79
+ return ok
80
+
81
+ def update_group_node(self, name: str, **kwargs) -> bool:
82
+ """Update properties of a group node."""
83
+ ok = group_tree_ops.update_node(self.lib, name, **kwargs)
84
+ return ok
85
+
86
+ def list_entries_in_group_tree(self, group: str, *, strict: bool = False) -> list[str]:
87
+ """Return entry keys in *group*, including descendants unless *strict*."""
88
+ return group_tree_ops.list_entries_in_group_tree(self.lib, group, strict=strict)