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,95 @@
|
|
|
1
|
+
"""Context objects used during document conversion."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import TYPE_CHECKING, Any
|
|
9
|
+
|
|
10
|
+
from .config import BookConfig
|
|
11
|
+
from .templates import TemplateBinding
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING: # pragma: no cover - typing only
|
|
15
|
+
from .bibliography.collection import BibliographyCollection
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(slots=True)
|
|
19
|
+
class AssetMapping:
|
|
20
|
+
"""Describe how a source asset should be persisted for LaTeX generation."""
|
|
21
|
+
|
|
22
|
+
source: Path
|
|
23
|
+
target: Path
|
|
24
|
+
kind: str | None = None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(slots=True)
|
|
28
|
+
class GenerationStrategy:
|
|
29
|
+
"""Rendering strategy toggles shared across conversion workflows."""
|
|
30
|
+
|
|
31
|
+
copy_assets: bool = True
|
|
32
|
+
convert_assets: bool = False
|
|
33
|
+
hash_assets: bool = False
|
|
34
|
+
prefer_inputs: bool = False
|
|
35
|
+
persist_manifest: bool = False
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass(slots=True)
|
|
39
|
+
class SegmentContext:
|
|
40
|
+
"""Represent a fragment destined for insertion into a template slot."""
|
|
41
|
+
|
|
42
|
+
name: str
|
|
43
|
+
html: str
|
|
44
|
+
base_level: int
|
|
45
|
+
metadata: Mapping[str, Any] = field(default_factory=dict)
|
|
46
|
+
bibliography: Mapping[str, Any] = field(default_factory=dict)
|
|
47
|
+
assets: list[AssetMapping] = field(default_factory=list)
|
|
48
|
+
destination: Path | None = None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass(slots=True)
|
|
52
|
+
class DocumentContext:
|
|
53
|
+
"""Document-level context produced before rendering."""
|
|
54
|
+
|
|
55
|
+
name: str
|
|
56
|
+
source_path: Path
|
|
57
|
+
html: str
|
|
58
|
+
base_level: int
|
|
59
|
+
numbered: bool
|
|
60
|
+
drop_title: bool
|
|
61
|
+
title_from_heading: bool
|
|
62
|
+
extracted_title: str | None = None
|
|
63
|
+
front_matter: dict[str, Any] = field(default_factory=dict)
|
|
64
|
+
slot_requests: dict[str, str] = field(default_factory=dict)
|
|
65
|
+
slot_inclusions: set[str] = field(default_factory=set)
|
|
66
|
+
language: str | None = None
|
|
67
|
+
bibliography: dict[str, Any] = field(default_factory=dict)
|
|
68
|
+
assets: list[AssetMapping] = field(default_factory=list)
|
|
69
|
+
segments: dict[str, list[SegmentContext]] = field(default_factory=dict)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass(slots=True)
|
|
73
|
+
class BinderContext:
|
|
74
|
+
"""Binder-level context describing template binding and global state."""
|
|
75
|
+
|
|
76
|
+
output_dir: Path
|
|
77
|
+
config: BookConfig
|
|
78
|
+
strategy: GenerationStrategy
|
|
79
|
+
language: str
|
|
80
|
+
slot_requests: dict[str, str]
|
|
81
|
+
template_overrides: dict[str, Any]
|
|
82
|
+
bibliography_map: dict[str, dict[str, Any]] = field(default_factory=dict)
|
|
83
|
+
bibliography_collection: BibliographyCollection | None = None
|
|
84
|
+
template_binding: TemplateBinding | None = None
|
|
85
|
+
documents: list[DocumentContext] = field(default_factory=list)
|
|
86
|
+
bound_segments: dict[str, list[SegmentContext]] = field(default_factory=dict)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
__all__ = [
|
|
90
|
+
"AssetMapping",
|
|
91
|
+
"BinderContext",
|
|
92
|
+
"DocumentContext",
|
|
93
|
+
"GenerationStrategy",
|
|
94
|
+
"SegmentContext",
|
|
95
|
+
]
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""Diagnostic abstractions shared across the conversion pipeline."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
import logging
|
|
7
|
+
from typing import Any, Protocol, runtime_checkable
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@runtime_checkable
|
|
14
|
+
class DiagnosticEmitter(Protocol):
|
|
15
|
+
"""Interface used to surface warnings, errors, and structured events."""
|
|
16
|
+
|
|
17
|
+
debug_enabled: bool
|
|
18
|
+
|
|
19
|
+
def warning(self, message: str, exc: BaseException | None = None) -> None: ...
|
|
20
|
+
|
|
21
|
+
def error(self, message: str, exc: BaseException | None = None) -> None: ...
|
|
22
|
+
|
|
23
|
+
def event(self, name: str, payload: Mapping[str, Any]) -> None: ...
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class NullEmitter:
|
|
27
|
+
"""Emitter that ignores every diagnostic."""
|
|
28
|
+
|
|
29
|
+
debug_enabled: bool = False
|
|
30
|
+
|
|
31
|
+
def warning(self, message: str, exc: BaseException | None = None) -> None:
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
def error(self, message: str, exc: BaseException | None = None) -> None:
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
def event(self, name: str, payload: Mapping[str, Any]) -> None:
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class LoggingEmitter:
|
|
42
|
+
"""Emitter that forwards diagnostics to the standard logging module."""
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self, *, logger_obj: logging.Logger | None = None, debug_enabled: bool = False
|
|
46
|
+
) -> None:
|
|
47
|
+
self._logger = logger_obj or logger
|
|
48
|
+
self.debug_enabled = debug_enabled
|
|
49
|
+
|
|
50
|
+
def warning(self, message: str, exc: BaseException | None = None) -> None:
|
|
51
|
+
if exc is not None:
|
|
52
|
+
self._logger.warning(message, exc_info=exc)
|
|
53
|
+
else:
|
|
54
|
+
self._logger.warning(message)
|
|
55
|
+
|
|
56
|
+
def error(self, message: str, exc: BaseException | None = None) -> None:
|
|
57
|
+
if exc is not None:
|
|
58
|
+
self._logger.error(message, exc_info=exc)
|
|
59
|
+
else:
|
|
60
|
+
self._logger.error(message)
|
|
61
|
+
|
|
62
|
+
def event(self, name: str, payload: Mapping[str, Any]) -> None:
|
|
63
|
+
message = format_event_message(name, payload)
|
|
64
|
+
if message:
|
|
65
|
+
self._logger.info(message)
|
|
66
|
+
return
|
|
67
|
+
try:
|
|
68
|
+
self._logger.debug("diagnostic event %s: %s", name, dict(payload))
|
|
69
|
+
except Exception: # pragma: no cover - defensive
|
|
70
|
+
self._logger.debug("failed to log diagnostic event %s", name, exc_info=True)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def format_event_message(name: str, payload: Mapping[str, Any]) -> str | None:
|
|
74
|
+
"""Return a human-friendly summary for selected diagnostic events."""
|
|
75
|
+
try:
|
|
76
|
+
data = dict(payload)
|
|
77
|
+
except Exception: # pragma: no cover - defensive
|
|
78
|
+
data = {}
|
|
79
|
+
|
|
80
|
+
if name == "asset_fetch":
|
|
81
|
+
url = data.get("url") or "<unknown>"
|
|
82
|
+
convert = data.get("convert")
|
|
83
|
+
suffix_hint = data.get("suffix_hint")
|
|
84
|
+
details: list[str] = []
|
|
85
|
+
if convert:
|
|
86
|
+
details.append("convert")
|
|
87
|
+
if suffix_hint:
|
|
88
|
+
details.append(f"suffix={suffix_hint}")
|
|
89
|
+
suffix = f" ({', '.join(details)})" if details else ""
|
|
90
|
+
return f"Fetching: {url}{suffix}"
|
|
91
|
+
|
|
92
|
+
if name == "asset_fetch_cached":
|
|
93
|
+
url = data.get("url") or "<unknown>"
|
|
94
|
+
reason = data.get("reason") or "cache"
|
|
95
|
+
return f"Reusing cached remote image: {url} ({reason})"
|
|
96
|
+
|
|
97
|
+
if name == "doi_fetch":
|
|
98
|
+
doi_value = data.get("value") or data.get("doi") or "<unknown>"
|
|
99
|
+
key = data.get("key") or "<unknown>"
|
|
100
|
+
mode = data.get("mode")
|
|
101
|
+
source = data.get("source") or data.get("resolved_source")
|
|
102
|
+
details: list[str] = []
|
|
103
|
+
if mode:
|
|
104
|
+
details.append(str(mode))
|
|
105
|
+
if source:
|
|
106
|
+
details.append(str(source))
|
|
107
|
+
suffix = f" ({', '.join(details)})" if details else ""
|
|
108
|
+
return f"Resolved DOI {doi_value} for entry '{key}'{suffix}"
|
|
109
|
+
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
__all__ = [
|
|
114
|
+
"DiagnosticEmitter",
|
|
115
|
+
"LoggingEmitter",
|
|
116
|
+
"NullEmitter",
|
|
117
|
+
"format_event_message",
|
|
118
|
+
]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Custom exception hierarchy for the LaTeX rendering pipeline."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class LatexRenderingError(RuntimeError):
|
|
7
|
+
"""Base exception for LaTeX rendering failures."""
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AssetMissingError(LatexRenderingError):
|
|
11
|
+
"""Raised when an expected asset cannot be located or generated."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TransformerExecutionError(LatexRenderingError):
|
|
15
|
+
"""Raised when an external converter fails to execute properly."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class InvalidNodeError(LatexRenderingError):
|
|
19
|
+
"""Raised when a handler receives an unexpected DOM node shape."""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def exception_messages(exc: BaseException) -> list[str]:
|
|
23
|
+
"""Return the collected message chain for an exception and its causes."""
|
|
24
|
+
messages: list[str] = []
|
|
25
|
+
visited: set[int] = set()
|
|
26
|
+
current: BaseException | None = exc
|
|
27
|
+
while current is not None and id(current) not in visited:
|
|
28
|
+
visited.add(id(current))
|
|
29
|
+
text = str(current).strip()
|
|
30
|
+
if text:
|
|
31
|
+
first_line = text.splitlines()[0].strip()
|
|
32
|
+
if first_line:
|
|
33
|
+
messages.append(first_line)
|
|
34
|
+
current = current.__cause__ or current.__context__
|
|
35
|
+
return messages
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def exception_hint(exc: BaseException) -> str | None:
|
|
39
|
+
"""Return the most specific message available for an exception chain."""
|
|
40
|
+
messages = exception_messages(exc)
|
|
41
|
+
return messages[-1] if messages else None
|