texsmith 0.0.2.dev0__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.
- texsmith/__init__.py +107 -0
- texsmith/_alias.py +59 -0
- texsmith/adapters/__init__.py +6 -0
- texsmith/adapters/docker.py +258 -0
- texsmith/adapters/handlers/__init__.py +3 -0
- texsmith/adapters/handlers/_assets.py +363 -0
- texsmith/adapters/handlers/_helpers.py +62 -0
- texsmith/adapters/handlers/_mermaid.py +122 -0
- texsmith/adapters/handlers/admonitions.py +267 -0
- texsmith/adapters/handlers/basic.py +228 -0
- texsmith/adapters/handlers/blocks.py +851 -0
- texsmith/adapters/handlers/code.py +309 -0
- texsmith/adapters/handlers/inline.py +937 -0
- texsmith/adapters/handlers/links.py +239 -0
- texsmith/adapters/handlers/media.py +380 -0
- texsmith/adapters/latex/__init__.py +10 -0
- texsmith/adapters/latex/engines/__init__.py +726 -0
- texsmith/adapters/latex/engines/latex/__init__.py +26 -0
- texsmith/adapters/latex/engines/latex/log.py +720 -0
- texsmith/adapters/latex/engines/latex/runner.py +28 -0
- texsmith/adapters/latex/engines/tectonic/__init__.py +38 -0
- texsmith/adapters/latex/formatter.py +297 -0
- texsmith/adapters/latex/latexmk.py +148 -0
- texsmith/adapters/latex/partials/acronym.tex +1 -0
- texsmith/adapters/latex/partials/add.tex +1 -0
- texsmith/adapters/latex/partials/addition.tex +1 -0
- texsmith/adapters/latex/partials/blockquote.tex +3 -0
- texsmith/adapters/latex/partials/callout.tex +3 -0
- texsmith/adapters/latex/partials/choices.tex +5 -0
- texsmith/adapters/latex/partials/citation.tex +1 -0
- texsmith/adapters/latex/partials/codeblock.tex +7 -0
- texsmith/adapters/latex/partials/codeblock_listings.tex +5 -0
- texsmith/adapters/latex/partials/codeblock_pygments.tex +5 -0
- texsmith/adapters/latex/partials/codeblock_verbatim.tex +12 -0
- texsmith/adapters/latex/partials/codeinline.tex +1 -0
- texsmith/adapters/latex/partials/codeinlinett.tex +1 -0
- texsmith/adapters/latex/partials/comment.tex +1 -0
- texsmith/adapters/latex/partials/del.tex +1 -0
- texsmith/adapters/latex/partials/deletion.tex +1 -0
- texsmith/adapters/latex/partials/description_list.tex +5 -0
- texsmith/adapters/latex/partials/enquote.tex +1 -0
- texsmith/adapters/latex/partials/epigraph.tex +1 -0
- texsmith/adapters/latex/partials/exercises_solutions.tex +7 -0
- texsmith/adapters/latex/partials/figure.tex +33 -0
- texsmith/adapters/latex/partials/figure_tcolorbox.tex +15 -0
- texsmith/adapters/latex/partials/footnote.tex +3 -0
- texsmith/adapters/latex/partials/glossary.tex +1 -0
- texsmith/adapters/latex/partials/heading.tex +14 -0
- texsmith/adapters/latex/partials/highlight.tex +25 -0
- texsmith/adapters/latex/partials/horizontal_rule.tex +1 -0
- texsmith/adapters/latex/partials/href.tex +1 -0
- texsmith/adapters/latex/partials/icon.tex +1 -0
- texsmith/adapters/latex/partials/include.tex +1 -0
- texsmith/adapters/latex/partials/index.tex +1 -0
- texsmith/adapters/latex/partials/italic.tex +1 -0
- texsmith/adapters/latex/partials/keystroke.tex +46 -0
- texsmith/adapters/latex/partials/label.tex +1 -0
- texsmith/adapters/latex/partials/list_acronyms.tex +5 -0
- texsmith/adapters/latex/partials/list_glossary.tex +8 -0
- texsmith/adapters/latex/partials/multicolumn.tex +3 -0
- texsmith/adapters/latex/partials/ordered_list.tex +5 -0
- texsmith/adapters/latex/partials/pagestyle.tex +1 -0
- texsmith/adapters/latex/partials/ref.tex +1 -0
- texsmith/adapters/latex/partials/regex.tex +1 -0
- texsmith/adapters/latex/partials/smallcaps.tex +1 -0
- texsmith/adapters/latex/partials/strikethrough.tex +1 -0
- texsmith/adapters/latex/partials/strong.tex +1 -0
- texsmith/adapters/latex/partials/subscript.tex +1 -0
- texsmith/adapters/latex/partials/substitution.tex +1 -0
- texsmith/adapters/latex/partials/superscript.tex +1 -0
- texsmith/adapters/latex/partials/tabbed.tex +3 -0
- texsmith/adapters/latex/partials/table.tex +48 -0
- texsmith/adapters/latex/partials/underline.tex +1 -0
- texsmith/adapters/latex/partials/unordered_list.tex +5 -0
- texsmith/adapters/latex/partials/url.tex +1 -0
- texsmith/adapters/latex/pygments.py +98 -0
- texsmith/adapters/latex/pyxindy.py +64 -0
- texsmith/adapters/latex/renderer.py +220 -0
- texsmith/adapters/latex/tectonic.py +366 -0
- texsmith/adapters/latex/utils.py +86 -0
- texsmith/adapters/markdown/__init__.py +342 -0
- texsmith/adapters/plugins/__init__.py +8 -0
- texsmith/adapters/plugins/material.py +174 -0
- texsmith/adapters/plugins/snippet.py +1734 -0
- texsmith/adapters/transformers/__init__.py +122 -0
- texsmith/adapters/transformers/base.py +140 -0
- texsmith/adapters/transformers/strategies.py +1456 -0
- texsmith/adapters/transformers/utils.py +59 -0
- texsmith/api/__init__.py +80 -0
- texsmith/api/_utils.py +56 -0
- texsmith/api/document.py +645 -0
- texsmith/api/pipeline.py +229 -0
- texsmith/api/service.py +648 -0
- texsmith/api/templates.py +287 -0
- texsmith/core/__init__.py +6 -0
- texsmith/core/bibliography/__init__.py +57 -0
- texsmith/core/bibliography/collection.py +354 -0
- texsmith/core/bibliography/doi.py +194 -0
- texsmith/core/bibliography/issues.py +15 -0
- texsmith/core/bibliography/parsing.py +45 -0
- texsmith/core/callouts.py +99 -0
- texsmith/core/config.py +191 -0
- texsmith/core/context.py +242 -0
- texsmith/core/conversion/__init__.py +103 -0
- texsmith/core/conversion/core.py +780 -0
- texsmith/core/conversion/debug.py +87 -0
- texsmith/core/conversion/inputs.py +474 -0
- texsmith/core/conversion/renderer.py +578 -0
- texsmith/core/conversion/templates.py +646 -0
- texsmith/core/conversion_contexts.py +95 -0
- texsmith/core/diagnostics.py +118 -0
- texsmith/core/exceptions.py +41 -0
- texsmith/core/fonts/__init__.py +3 -0
- texsmith/core/fragments/__init__.py +716 -0
- texsmith/core/fragments/base.py +117 -0
- texsmith/core/metadata.py +280 -0
- texsmith/core/mustache.py +86 -0
- texsmith/core/partials.py +20 -0
- texsmith/core/rules.py +394 -0
- texsmith/core/templates/__init__.py +58 -0
- texsmith/core/templates/base.py +343 -0
- texsmith/core/templates/builtins.py +68 -0
- texsmith/core/templates/context_usage.py +137 -0
- texsmith/core/templates/loader.py +303 -0
- texsmith/core/templates/manifest.py +861 -0
- texsmith/core/templates/runtime.py +347 -0
- texsmith/core/templates/text.py +14 -0
- texsmith/core/templates/wrapper.py +340 -0
- texsmith/core/user_dir.py +179 -0
- texsmith/devtools.py +28 -0
- texsmith/extensions/__init__.py +162 -0
- texsmith/extensions/index/__init__.py +21 -0
- texsmith/extensions/index/markdown.py +94 -0
- texsmith/extensions/index/mkdocs_plugin.py +136 -0
- texsmith/extensions/index/registry.py +57 -0
- texsmith/extensions/index/renderer.py +183 -0
- texsmith/extensions/index/templates/index.tex +1 -0
- texsmith/extensions/latex_raw.py +115 -0
- texsmith/extensions/latex_text.py +117 -0
- texsmith/extensions/mermaid.py +267 -0
- texsmith/extensions/missing_footnotes.py +125 -0
- texsmith/extensions/multi_citations.py +54 -0
- texsmith/extensions/progressbar/__init__.py +9 -0
- texsmith/extensions/progressbar/markdown.py +212 -0
- texsmith/extensions/progressbar/renderer.py +117 -0
- texsmith/extensions/smallcaps.py +48 -0
- texsmith/extensions/texlogos/__init__.py +10 -0
- texsmith/extensions/texlogos/markdown.py +236 -0
- texsmith/extensions/texlogos/renderer.py +81 -0
- texsmith/extensions/texlogos/specs.py +66 -0
- texsmith/fonts/__init__.py +57 -0
- texsmith/fonts/cache.py +46 -0
- texsmith/fonts/constants.py +60 -0
- texsmith/fonts/coverage.py +275 -0
- texsmith/fonts/downloader.py +103 -0
- texsmith/fonts/fallback.py +438 -0
- texsmith/fonts/html_scripts.py +168 -0
- texsmith/fonts/logging.py +127 -0
- texsmith/fonts/pipeline.py +338 -0
- texsmith/fonts/scripts.py +493 -0
- texsmith/fonts/ucharclasses.py +164 -0
- texsmith/fragments/__init__.py +11 -0
- texsmith/fragments/bibliography/__init__.py +65 -0
- texsmith/fragments/bibliography/fragment.toml +3 -0
- texsmith/fragments/bibliography/ts-bibliography-backmatter.jinja.tex +35 -0
- texsmith/fragments/bibliography/ts-bibliography.jinja.tex +10 -0
- texsmith/fragments/callouts/__init__.py +88 -0
- texsmith/fragments/callouts/fragment.toml +3 -0
- texsmith/fragments/callouts/ts-callouts.jinja.sty +129 -0
- texsmith/fragments/code/__init__.py +82 -0
- texsmith/fragments/code/fragment.toml +3 -0
- texsmith/fragments/code/ts-code.jinja.sty +140 -0
- texsmith/fragments/extra/__init__.py +180 -0
- texsmith/fragments/extra/fragment.toml +3 -0
- texsmith/fragments/extra/ts-extra.jinja.tex +13 -0
- texsmith/fragments/fonts/__init__.py +880 -0
- texsmith/fragments/fonts/fragment.toml +3 -0
- texsmith/fragments/fonts/ts-fonts.jinja.sty +292 -0
- texsmith/fragments/frame/__init__.py +170 -0
- texsmith/fragments/frame/fragment.toml +3 -0
- texsmith/fragments/frame/ts-frame.tex.jinja +49 -0
- texsmith/fragments/geometry/__init__.py +169 -0
- texsmith/fragments/geometry/fragment.toml +3 -0
- texsmith/fragments/geometry/paper.py +526 -0
- texsmith/fragments/geometry/ts_geometry.tex.jinja +53 -0
- texsmith/fragments/glossary/__init__.py +67 -0
- texsmith/fragments/glossary/fragment.toml +3 -0
- texsmith/fragments/glossary/ts-glossary-backmatter.jinja.tex +5 -0
- texsmith/fragments/glossary/ts-glossary.jinja.sty +28 -0
- texsmith/fragments/index/__init__.py +66 -0
- texsmith/fragments/index/fragment.toml +3 -0
- texsmith/fragments/index/ts-index-backmatter.jinja.tex +3 -0
- texsmith/fragments/index/ts-index.jinja.sty +12 -0
- texsmith/fragments/keystrokes/__init__.py +69 -0
- texsmith/fragments/keystrokes/fragment.toml +3 -0
- texsmith/fragments/keystrokes/ts-keystrokes.jinja.sty +20 -0
- texsmith/fragments/todolist/__init__.py +71 -0
- texsmith/fragments/todolist/fragment.toml +3 -0
- texsmith/fragments/todolist/ts-todolist.jinja.sty +21 -0
- texsmith/fragments/typesetting/__init__.py +214 -0
- texsmith/fragments/typesetting/fragment.toml +3 -0
- texsmith/fragments/typesetting/ts-typesetting.tex.jinja +81 -0
- texsmith/index.py +26 -0
- texsmith/plugins/__init__.py +14 -0
- texsmith/progressbar.py +8 -0
- texsmith/quotes.py +40 -0
- texsmith/smart_dashes.py +66 -0
- texsmith/templates/__init__.py +3 -0
- texsmith/templates/article/README.md +33 -0
- texsmith/templates/article/__init__.py +281 -0
- texsmith/templates/article/template/manifest.toml +125 -0
- texsmith/templates/article/template/mermaid-config.json +18 -0
- texsmith/templates/article/template/template.tex +74 -0
- texsmith/templates/book/README.md +26 -0
- texsmith/templates/book/__init__.py +75 -0
- texsmith/templates/book/overrides/codeblock.tex +7 -0
- texsmith/templates/book/overrides/codeinline.tex +1 -0
- texsmith/templates/book/template/fixtoc.sty +81 -0
- texsmith/templates/book/template/manifest.toml +198 -0
- texsmith/templates/book/template/template.tex +314 -0
- texsmith/templates/common/__init__.py +1 -0
- texsmith/templates/common/latexmkrc +46 -0
- texsmith/templates/letter/README.md +59 -0
- texsmith/templates/letter/__init__.py +495 -0
- texsmith/templates/letter/demo.md +31 -0
- texsmith/templates/letter/fonts/modernline bold.otf +0 -0
- texsmith/templates/letter/fonts/modernline.otf +0 -0
- texsmith/templates/letter/manifest.toml +197 -0
- texsmith/templates/letter/template/callouts.jinja.sty +290 -0
- texsmith/templates/letter/template/template.tex +123 -0
- texsmith/templates/snippet/README.md +21 -0
- texsmith/templates/snippet/__init__.py +80 -0
- texsmith/templates/snippet/template/manifest.toml +66 -0
- texsmith/templates/snippet/template/template.tex +47 -0
- texsmith/texlogos.py +15 -0
- texsmith/ui/__init__.py +6 -0
- texsmith/ui/cli/__init__.py +22 -0
- texsmith/ui/cli/_options.py +338 -0
- texsmith/ui/cli/app.py +65 -0
- texsmith/ui/cli/bibliography.py +300 -0
- texsmith/ui/cli/commands/__init__.py +14 -0
- texsmith/ui/cli/commands/render.py +1128 -0
- texsmith/ui/cli/commands/templates.py +397 -0
- texsmith/ui/cli/diagnostics.py +36 -0
- texsmith/ui/cli/presenter.py +663 -0
- texsmith/ui/cli/state.py +263 -0
- texsmith/ui/cli/utils.py +235 -0
- texsmith-0.0.2.dev0.dist-info/METADATA +187 -0
- texsmith-0.0.2.dev0.dist-info/RECORD +252 -0
- texsmith-0.0.2.dev0.dist-info/WHEEL +4 -0
- texsmith-0.0.2.dev0.dist-info/entry_points.txt +22 -0
- texsmith-0.0.2.dev0.dist-info/licenses/LICENSE.md +21 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
"""Bibliography-related CLI helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections import Counter
|
|
6
|
+
from collections.abc import Iterable, Mapping, Sequence
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
9
|
+
|
|
10
|
+
import typer
|
|
11
|
+
|
|
12
|
+
from texsmith.core.bibliography import BibliographyCollection
|
|
13
|
+
|
|
14
|
+
from .state import ensure_rich_compat, get_cli_state
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from rich.panel import Panel
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _iterable_items(value: object) -> Iterable[object]:
|
|
22
|
+
"""Normalize a value into an iterable, treating strings as scalars.
|
|
23
|
+
|
|
24
|
+
This helper ensures consistent processing of bibliography fields, which may be
|
|
25
|
+
single values (strings) or lists of values, by wrapping single items in a tuple.
|
|
26
|
+
"""
|
|
27
|
+
if isinstance(value, Iterable) and not isinstance(value, (str, bytes)):
|
|
28
|
+
return value
|
|
29
|
+
return ()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def format_bibliography_person(person: Mapping[str, object]) -> str:
|
|
33
|
+
"""Render a bibliography person dictionary into a readable string."""
|
|
34
|
+
parts: list[str] = []
|
|
35
|
+
for field in ("first", "middle", "prelast", "last", "lineage"):
|
|
36
|
+
value = person.get(field)
|
|
37
|
+
if isinstance(value, str):
|
|
38
|
+
text = value.strip()
|
|
39
|
+
if text:
|
|
40
|
+
parts.append(text)
|
|
41
|
+
continue
|
|
42
|
+
for segment in _iterable_items(value):
|
|
43
|
+
if segment:
|
|
44
|
+
parts.append(str(segment))
|
|
45
|
+
|
|
46
|
+
text = " ".join(part for part in parts if part)
|
|
47
|
+
if text:
|
|
48
|
+
return text
|
|
49
|
+
fallback = person.get("text")
|
|
50
|
+
return str(fallback).strip() if isinstance(fallback, str) else ""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def format_person_list(persons: Iterable[Mapping[str, object]]) -> str:
|
|
54
|
+
"""Join a sequence of person dictionaries into a comma-separated string."""
|
|
55
|
+
names = [format_bibliography_person(person) for person in persons]
|
|
56
|
+
return ", ".join(name for name in names if name)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def build_reference_panel(reference: Mapping[str, object]) -> Panel:
|
|
60
|
+
"""Create a Rich panel that visualises a single bibliography entry."""
|
|
61
|
+
from rich import box
|
|
62
|
+
from rich.panel import Panel
|
|
63
|
+
from rich.table import Table
|
|
64
|
+
|
|
65
|
+
raw_fields = reference.get("fields")
|
|
66
|
+
fields: dict[str, Any]
|
|
67
|
+
if isinstance(raw_fields, Mapping):
|
|
68
|
+
fields = {str(key): value for key, value in raw_fields.items()}
|
|
69
|
+
else:
|
|
70
|
+
fields = {}
|
|
71
|
+
grid = Table.grid(padding=(0, 1))
|
|
72
|
+
grid.add_column(style="bold green", no_wrap=True)
|
|
73
|
+
grid.add_column()
|
|
74
|
+
|
|
75
|
+
def _pop_field(*keys: str) -> str | None:
|
|
76
|
+
for key in keys:
|
|
77
|
+
value = fields.pop(key, None)
|
|
78
|
+
if isinstance(value, bytes):
|
|
79
|
+
text = value.decode("utf-8", errors="ignore").strip()
|
|
80
|
+
if text:
|
|
81
|
+
return text
|
|
82
|
+
elif isinstance(value, str):
|
|
83
|
+
text = value.strip()
|
|
84
|
+
if text:
|
|
85
|
+
return text
|
|
86
|
+
elif value is not None:
|
|
87
|
+
return str(value)
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
def _add_field(label: str, value: object) -> None:
|
|
91
|
+
if value is None:
|
|
92
|
+
return
|
|
93
|
+
if isinstance(value, str) and not value.strip():
|
|
94
|
+
return
|
|
95
|
+
grid.add_row(label, str(value))
|
|
96
|
+
|
|
97
|
+
title = _pop_field("title")
|
|
98
|
+
_add_field("Title", title)
|
|
99
|
+
|
|
100
|
+
year = _pop_field("year")
|
|
101
|
+
_add_field("Year", year)
|
|
102
|
+
|
|
103
|
+
journal = _pop_field("journal", "booktitle")
|
|
104
|
+
_add_field("Journal", journal)
|
|
105
|
+
|
|
106
|
+
persons_block = reference.get("persons")
|
|
107
|
+
if isinstance(persons_block, Mapping):
|
|
108
|
+
authors = persons_block.get("author")
|
|
109
|
+
if isinstance(authors, Iterable):
|
|
110
|
+
cast_authors = cast(Sequence[Mapping[str, object]], tuple(authors))
|
|
111
|
+
_add_field("Authors", format_person_list(cast_authors))
|
|
112
|
+
|
|
113
|
+
sources = [
|
|
114
|
+
str(Path(str(path))) for path in _iterable_items(reference.get("source_files")) if path
|
|
115
|
+
]
|
|
116
|
+
if sources:
|
|
117
|
+
formatted_sources = ", ".join(sources)
|
|
118
|
+
_add_field("Sources", formatted_sources)
|
|
119
|
+
|
|
120
|
+
for key, value in sorted(fields.items()):
|
|
121
|
+
_add_field(key.title(), value)
|
|
122
|
+
|
|
123
|
+
key = str(reference.get("key", "Reference"))
|
|
124
|
+
entry_type = str(reference.get("type", "reference"))
|
|
125
|
+
title = f"{key} ({entry_type})"
|
|
126
|
+
return Panel(grid, title=title, box=box.SIMPLE)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def print_bibliography_overview(collection: BibliographyCollection) -> None:
|
|
130
|
+
"""Render a formatted summary of bibliography files, issues, and entries."""
|
|
131
|
+
ensure_rich_compat()
|
|
132
|
+
try:
|
|
133
|
+
from rich import box
|
|
134
|
+
from rich.table import Table
|
|
135
|
+
from rich.text import Text
|
|
136
|
+
except ImportError: # pragma: no cover - fallback when Rich is unavailable
|
|
137
|
+
_print_bibliography_plain(collection)
|
|
138
|
+
return
|
|
139
|
+
|
|
140
|
+
console = get_cli_state().console
|
|
141
|
+
references = collection.list_references()
|
|
142
|
+
|
|
143
|
+
def _summarise_sources() -> tuple[Counter[Path], int, int, int]:
|
|
144
|
+
per_file: Counter[Path] = Counter()
|
|
145
|
+
frontmatter = 0
|
|
146
|
+
doi = 0
|
|
147
|
+
for reference in references:
|
|
148
|
+
raw_sources = reference.get("source_files") or []
|
|
149
|
+
sources = {Path(str(src)) for src in raw_sources if src}
|
|
150
|
+
if not sources:
|
|
151
|
+
continue
|
|
152
|
+
for src in sources:
|
|
153
|
+
per_file[src] += 1
|
|
154
|
+
names = {src.name.lower() for src in sources}
|
|
155
|
+
if any("frontmatter" in name for name in names):
|
|
156
|
+
frontmatter += 1
|
|
157
|
+
if any("doi" in name for name in names):
|
|
158
|
+
doi += 1
|
|
159
|
+
total = len(references)
|
|
160
|
+
return per_file, total, frontmatter, doi
|
|
161
|
+
|
|
162
|
+
per_file_counts, total_entries, frontmatter_entries, doi_entries = _summarise_sources()
|
|
163
|
+
|
|
164
|
+
stats = collection.file_stats
|
|
165
|
+
if stats:
|
|
166
|
+
stats_table = Table(
|
|
167
|
+
title="Bibliography Files",
|
|
168
|
+
box=box.SQUARE,
|
|
169
|
+
show_edge=True,
|
|
170
|
+
header_style="bold cyan",
|
|
171
|
+
)
|
|
172
|
+
stats_table.add_column("File", overflow="fold")
|
|
173
|
+
stats_table.add_column("Entries", justify="right")
|
|
174
|
+
for file_path, entry_count in stats:
|
|
175
|
+
stats_table.add_row(str(file_path), str(entry_count))
|
|
176
|
+
stats_table.add_row(
|
|
177
|
+
Text("Total", style="bold"), Text(str(sum(count for _, count in stats)))
|
|
178
|
+
)
|
|
179
|
+
console.print(stats_table)
|
|
180
|
+
|
|
181
|
+
if collection.issues:
|
|
182
|
+
issue_table = Table(
|
|
183
|
+
title="Warnings",
|
|
184
|
+
box=box.SQUARE,
|
|
185
|
+
header_style="bold cyan",
|
|
186
|
+
show_edge=True,
|
|
187
|
+
)
|
|
188
|
+
issue_table.add_column("Key", style="yellow", no_wrap=True)
|
|
189
|
+
issue_table.add_column("Message", style="yellow")
|
|
190
|
+
issue_table.add_column("Sources", style="yellow")
|
|
191
|
+
for issue in collection.issues:
|
|
192
|
+
issue_table.add_row(
|
|
193
|
+
issue.key or "—",
|
|
194
|
+
issue.message,
|
|
195
|
+
str(issue.source) if issue.source else "—",
|
|
196
|
+
)
|
|
197
|
+
console.print(issue_table)
|
|
198
|
+
|
|
199
|
+
if not references:
|
|
200
|
+
console.print("[dim]No references found.[/]")
|
|
201
|
+
summary_table = Table(
|
|
202
|
+
title="Bibliography Summary",
|
|
203
|
+
box=box.SQUARE,
|
|
204
|
+
header_style="bold cyan",
|
|
205
|
+
show_edge=True,
|
|
206
|
+
)
|
|
207
|
+
summary_table.add_column("Category", style="bold")
|
|
208
|
+
summary_table.add_column("Count", justify="right")
|
|
209
|
+
summary_table.add_row("Total entries", str(total_entries))
|
|
210
|
+
if per_file_counts:
|
|
211
|
+
for path, count in per_file_counts.most_common():
|
|
212
|
+
summary_table.add_row(f"From {path.name}", str(count))
|
|
213
|
+
summary_table.add_row("From front matter", str(frontmatter_entries))
|
|
214
|
+
summary_table.add_row("From DOI fetches", str(doi_entries))
|
|
215
|
+
console.print(summary_table)
|
|
216
|
+
return
|
|
217
|
+
|
|
218
|
+
for reference in references:
|
|
219
|
+
panel = build_reference_panel(reference)
|
|
220
|
+
console.print(panel)
|
|
221
|
+
console.print()
|
|
222
|
+
|
|
223
|
+
summary_table = Table(
|
|
224
|
+
title="Bibliography Summary",
|
|
225
|
+
box=box.SQUARE,
|
|
226
|
+
header_style="bold cyan",
|
|
227
|
+
show_edge=True,
|
|
228
|
+
)
|
|
229
|
+
summary_table.add_column("Category", style="bold")
|
|
230
|
+
summary_table.add_column("Count", justify="right")
|
|
231
|
+
summary_table.add_row("Total entries", str(total_entries))
|
|
232
|
+
if per_file_counts:
|
|
233
|
+
for path, count in per_file_counts.most_common():
|
|
234
|
+
summary_table.add_row(f"From {path.name}", str(count))
|
|
235
|
+
summary_table.add_row("From front matter", str(frontmatter_entries))
|
|
236
|
+
summary_table.add_row("From DOI fetches", str(doi_entries))
|
|
237
|
+
console.print(summary_table)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _print_bibliography_plain(collection: BibliographyCollection) -> None:
|
|
241
|
+
"""Simple text fallback when Rich is not available."""
|
|
242
|
+
typer.echo("Bibliography Files:")
|
|
243
|
+
stats = collection.file_stats
|
|
244
|
+
if stats:
|
|
245
|
+
for file_path, entry_count in stats:
|
|
246
|
+
typer.echo(f" - {file_path} ({entry_count} entries)")
|
|
247
|
+
else:
|
|
248
|
+
typer.echo(" - (none)")
|
|
249
|
+
|
|
250
|
+
if collection.issues:
|
|
251
|
+
typer.echo("Warnings:")
|
|
252
|
+
for issue in collection.issues:
|
|
253
|
+
key = issue.key or "-"
|
|
254
|
+
source = str(issue.source) if issue.source else "-"
|
|
255
|
+
typer.echo(f" - [{key}] {issue.message} (source: {source})")
|
|
256
|
+
|
|
257
|
+
references = collection.list_references()
|
|
258
|
+
total = len(references)
|
|
259
|
+
per_file: Counter[Path] = Counter()
|
|
260
|
+
frontmatter = 0
|
|
261
|
+
doi = 0
|
|
262
|
+
for reference in references:
|
|
263
|
+
sources = {Path(str(src)) for src in reference.get("source_files") or [] if src}
|
|
264
|
+
for src in sources:
|
|
265
|
+
per_file[src] += 1
|
|
266
|
+
names = {src.name.lower() for src in sources}
|
|
267
|
+
if any("frontmatter" in name for name in names):
|
|
268
|
+
frontmatter += 1
|
|
269
|
+
if any("doi" in name for name in names):
|
|
270
|
+
doi += 1
|
|
271
|
+
|
|
272
|
+
typer.echo("Entries:")
|
|
273
|
+
for reference in references:
|
|
274
|
+
key = reference.get("key", "Reference")
|
|
275
|
+
entry_type = reference.get("type", "reference")
|
|
276
|
+
typer.echo(f"- {key} ({entry_type})")
|
|
277
|
+
persons_block = reference.get("persons")
|
|
278
|
+
if isinstance(persons_block, Mapping):
|
|
279
|
+
authors = persons_block.get("author")
|
|
280
|
+
if isinstance(authors, Iterable):
|
|
281
|
+
cast_authors = cast(Sequence[Mapping[str, object]], tuple(authors))
|
|
282
|
+
typer.echo(f" Authors: {format_person_list(cast_authors)}")
|
|
283
|
+
fields = reference.get("fields")
|
|
284
|
+
if isinstance(fields, Mapping):
|
|
285
|
+
for field_key, field_value in sorted(fields.items()):
|
|
286
|
+
typer.echo(f" {field_key.title()}: {field_value}")
|
|
287
|
+
sources = reference.get("source_files")
|
|
288
|
+
entries = [str(Path(str(path))) for path in _iterable_items(sources) if path]
|
|
289
|
+
if entries:
|
|
290
|
+
typer.echo(f" Sources: {', '.join(entries)}")
|
|
291
|
+
typer.echo()
|
|
292
|
+
|
|
293
|
+
typer.echo("Summary:")
|
|
294
|
+
typer.echo(f" Total entries: {total}")
|
|
295
|
+
if per_file:
|
|
296
|
+
typer.echo(" Per-source counts:")
|
|
297
|
+
for path, count in per_file.most_common():
|
|
298
|
+
typer.echo(f" - {path}: {count}")
|
|
299
|
+
typer.echo(f" From front matter: {frontmatter}")
|
|
300
|
+
typer.echo(f" From DOI fetches: {doi}")
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""CLI command implementations exposed via `texsmith.ui.cli`.
|
|
2
|
+
|
|
3
|
+
This module exists primarily to make the `texsmith.ui.cli.commands` package
|
|
4
|
+
importable for documentation tools such as mkdocstrings. It re-exports the
|
|
5
|
+
Typer command factories defined in the sibling modules so downstream code can
|
|
6
|
+
import them using dotted paths (e.g. ``texsmith.ui.cli.commands.render``).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from .render import render
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
__all__ = ["render"]
|