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,303 @@
|
|
|
1
|
+
"""Helpers for loading template instances from disk or entry points."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Iterable, Mapping
|
|
6
|
+
from importlib import metadata
|
|
7
|
+
import importlib.util
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
import shutil
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from texsmith.core.user_dir import get_user_dir
|
|
13
|
+
|
|
14
|
+
from .base import WrappableTemplate, load_specialised_template
|
|
15
|
+
from .builtins import iter_builtin_templates, load_builtin_template
|
|
16
|
+
from .manifest import TemplateError
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _looks_like_template_root(path: Path) -> bool:
|
|
20
|
+
"""Return True when ``path`` contains manifest or specialised template markers."""
|
|
21
|
+
def _safe_exists(candidate: Path) -> bool:
|
|
22
|
+
try:
|
|
23
|
+
return candidate.exists()
|
|
24
|
+
except OSError:
|
|
25
|
+
return False
|
|
26
|
+
|
|
27
|
+
if not _safe_exists(path) or path.is_file():
|
|
28
|
+
return False
|
|
29
|
+
if _safe_exists(path / "__init__.py"):
|
|
30
|
+
return True
|
|
31
|
+
manifest_candidates = (path / "manifest.toml", path / "template" / "manifest.toml")
|
|
32
|
+
return any(_safe_exists(candidate) for candidate in manifest_candidates)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def load_template(identifier: str) -> WrappableTemplate:
|
|
36
|
+
"""Load a template selected by name or filesystem path."""
|
|
37
|
+
path_candidate = Path(identifier).expanduser()
|
|
38
|
+
looks_like_path = (
|
|
39
|
+
path_candidate.is_absolute()
|
|
40
|
+
or "/" in identifier
|
|
41
|
+
or "\\" in identifier
|
|
42
|
+
or identifier.startswith(".")
|
|
43
|
+
)
|
|
44
|
+
if looks_like_path and path_candidate.exists():
|
|
45
|
+
return _load_path_template(path_candidate)
|
|
46
|
+
|
|
47
|
+
slug = _slug_from_identifier(identifier)
|
|
48
|
+
|
|
49
|
+
builtin = load_builtin_template(identifier)
|
|
50
|
+
if builtin is not None:
|
|
51
|
+
return builtin
|
|
52
|
+
|
|
53
|
+
packaged_root = _resolve_packaged_template_root(slug)
|
|
54
|
+
if packaged_root is not None:
|
|
55
|
+
return _load_path_template(packaged_root)
|
|
56
|
+
|
|
57
|
+
for candidate in _iter_local_candidates(slug):
|
|
58
|
+
if _looks_like_template_root(candidate):
|
|
59
|
+
return _load_path_template(candidate)
|
|
60
|
+
|
|
61
|
+
home_candidate = _home_template_candidate(slug)
|
|
62
|
+
if home_candidate is not None and _looks_like_template_root(home_candidate):
|
|
63
|
+
return _load_path_template(home_candidate)
|
|
64
|
+
|
|
65
|
+
raise TemplateError(
|
|
66
|
+
f"Unable to load template '{identifier}'. Provide a valid path or "
|
|
67
|
+
"install a package exposing a 'texsmith.templates' entry point."
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def copy_template_assets(
|
|
72
|
+
template: WrappableTemplate,
|
|
73
|
+
output_dir: Path,
|
|
74
|
+
*,
|
|
75
|
+
context: Mapping[str, Any] | None = None,
|
|
76
|
+
overrides: Mapping[str, Any] | None = None,
|
|
77
|
+
assets: Iterable["ResolvedAsset"] | None = None,
|
|
78
|
+
) -> list[Path]:
|
|
79
|
+
"""Copy the template declared assets into the selected output directory."""
|
|
80
|
+
output_dir = Path(output_dir).resolve()
|
|
81
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
82
|
+
|
|
83
|
+
if context is None:
|
|
84
|
+
render_context = template.prepare_context("", overrides=overrides)
|
|
85
|
+
else:
|
|
86
|
+
render_context = dict(context)
|
|
87
|
+
|
|
88
|
+
written: list[Path] = []
|
|
89
|
+
asset_entries = list(assets) if assets is not None else list(template.iter_assets())
|
|
90
|
+
for asset in asset_entries:
|
|
91
|
+
destination_path = (output_dir / asset.destination).resolve()
|
|
92
|
+
|
|
93
|
+
if asset.template:
|
|
94
|
+
if asset.template_name is None: # pragma: no cover - defensive
|
|
95
|
+
raise TemplateError(
|
|
96
|
+
f"Templated asset '{asset.source}' is missing template metadata."
|
|
97
|
+
)
|
|
98
|
+
destination_path.parent.mkdir(parents=True, exist_ok=True)
|
|
99
|
+
rendered = template.render_template(asset.template_name, **render_context)
|
|
100
|
+
destination_path.write_text(
|
|
101
|
+
rendered,
|
|
102
|
+
encoding=asset.encoding or "utf-8",
|
|
103
|
+
)
|
|
104
|
+
elif asset.source.is_dir():
|
|
105
|
+
shutil.copytree(asset.source, destination_path, dirs_exist_ok=True)
|
|
106
|
+
else:
|
|
107
|
+
destination_path.parent.mkdir(parents=True, exist_ok=True)
|
|
108
|
+
shutil.copy2(asset.source, destination_path)
|
|
109
|
+
|
|
110
|
+
written.append(destination_path)
|
|
111
|
+
|
|
112
|
+
return written
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _load_path_template(path: Path) -> WrappableTemplate:
|
|
116
|
+
specialised = load_specialised_template(path)
|
|
117
|
+
if specialised is not None:
|
|
118
|
+
return specialised
|
|
119
|
+
return WrappableTemplate(path)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _match_entry_points(
|
|
123
|
+
entry_points: Iterable[metadata.EntryPoint],
|
|
124
|
+
name: str,
|
|
125
|
+
) -> Iterable[metadata.EntryPoint]:
|
|
126
|
+
for entry_point in entry_points:
|
|
127
|
+
if entry_point.name == name:
|
|
128
|
+
yield entry_point
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _coerce_template(value: Any) -> WrappableTemplate | None:
|
|
132
|
+
"""Coerce an entry point payload into a ``WrappableTemplate`` instance."""
|
|
133
|
+
from .base import _coerce_template as base_coerce_template
|
|
134
|
+
|
|
135
|
+
return base_coerce_template(value)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _slug_from_identifier(identifier: str) -> str:
|
|
139
|
+
path = Path(identifier)
|
|
140
|
+
slug = path.name
|
|
141
|
+
if slug in {".", ""}:
|
|
142
|
+
slug = identifier.strip("./")
|
|
143
|
+
return slug
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _iter_local_candidates(slug: str | None) -> Iterable[Path]:
|
|
147
|
+
"""Yield template candidates from the current directory and its parents."""
|
|
148
|
+
cwd = Path.cwd().resolve()
|
|
149
|
+
visited_roots: set[Path] = set()
|
|
150
|
+
current = cwd
|
|
151
|
+
while current not in visited_roots:
|
|
152
|
+
visited_roots.add(current)
|
|
153
|
+
templates_root = current / "templates"
|
|
154
|
+
roots = (current, templates_root)
|
|
155
|
+
if not slug:
|
|
156
|
+
for root in roots:
|
|
157
|
+
if not root.exists():
|
|
158
|
+
continue
|
|
159
|
+
for child in root.iterdir():
|
|
160
|
+
if child.is_dir():
|
|
161
|
+
yield child
|
|
162
|
+
else:
|
|
163
|
+
distribution_name = f"texsmith-template-{slug}"
|
|
164
|
+
package_name = f"texsmith_template_{slug}"
|
|
165
|
+
for root in roots:
|
|
166
|
+
for candidate in (
|
|
167
|
+
root / slug,
|
|
168
|
+
root / package_name,
|
|
169
|
+
root / distribution_name / package_name,
|
|
170
|
+
root / distribution_name,
|
|
171
|
+
):
|
|
172
|
+
yield candidate
|
|
173
|
+
if current.parent == current:
|
|
174
|
+
break
|
|
175
|
+
current = current.parent
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _resolve_packaged_template_root(slug: str) -> Path | None:
|
|
179
|
+
"""Return a template root provided by an installed Python package."""
|
|
180
|
+
entry_points = metadata.entry_points()
|
|
181
|
+
group = entry_points.select(group="texsmith.templates")
|
|
182
|
+
for entry_point in _match_entry_points(group, slug):
|
|
183
|
+
loaded = entry_point.load()
|
|
184
|
+
template = _coerce_template(loaded)
|
|
185
|
+
if template is not None:
|
|
186
|
+
return template.root
|
|
187
|
+
|
|
188
|
+
candidate_modules = [
|
|
189
|
+
f"texsmith_template_{slug}",
|
|
190
|
+
f"texsmith-template-{slug}",
|
|
191
|
+
slug,
|
|
192
|
+
]
|
|
193
|
+
for module_name in candidate_modules:
|
|
194
|
+
spec = importlib.util.find_spec(module_name)
|
|
195
|
+
if spec is None or spec.origin is None:
|
|
196
|
+
continue
|
|
197
|
+
origin_path = Path(spec.origin).resolve()
|
|
198
|
+
module_root = origin_path.parent if origin_path.name == "__init__.py" else origin_path
|
|
199
|
+
if _looks_like_template_root(module_root):
|
|
200
|
+
return module_root
|
|
201
|
+
return None
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _home_template_candidate(slug: str) -> Path | None:
|
|
205
|
+
"""Return a template path under ~/.texsmith/templates if present."""
|
|
206
|
+
home_root = get_user_dir().data_dir("templates", create=False)
|
|
207
|
+
distribution_name = f"texsmith-template-{slug}"
|
|
208
|
+
package_name = f"texsmith_template_{slug}"
|
|
209
|
+
for candidate in (
|
|
210
|
+
home_root / slug,
|
|
211
|
+
home_root / package_name,
|
|
212
|
+
home_root / distribution_name / package_name,
|
|
213
|
+
home_root / distribution_name,
|
|
214
|
+
):
|
|
215
|
+
if candidate.exists():
|
|
216
|
+
return candidate
|
|
217
|
+
return None
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def discover_templates() -> list[dict[str, str]]:
|
|
221
|
+
"""Return available templates in discovery order."""
|
|
222
|
+
entries: list[dict[str, str]] = []
|
|
223
|
+
|
|
224
|
+
def _record(origin: str, name: str, root: Path) -> None:
|
|
225
|
+
entries.append({"name": name, "origin": origin, "root": str(root.resolve())})
|
|
226
|
+
|
|
227
|
+
for slug in iter_builtin_templates():
|
|
228
|
+
try:
|
|
229
|
+
template = load_builtin_template(slug)
|
|
230
|
+
except TemplateError:
|
|
231
|
+
continue
|
|
232
|
+
if template is not None:
|
|
233
|
+
_record("builtin", slug, template.root)
|
|
234
|
+
|
|
235
|
+
try:
|
|
236
|
+
for dist in metadata.distributions():
|
|
237
|
+
name = dist.metadata.get("Name", "")
|
|
238
|
+
if not name.lower().startswith("texsmith-template-"):
|
|
239
|
+
continue
|
|
240
|
+
slug = name[len("texsmith-template-") :]
|
|
241
|
+
root = _resolve_packaged_template_root(slug)
|
|
242
|
+
if root is None:
|
|
243
|
+
continue
|
|
244
|
+
_record("package", slug, root)
|
|
245
|
+
except Exception:
|
|
246
|
+
pass
|
|
247
|
+
|
|
248
|
+
seen_locals: set[str] = set()
|
|
249
|
+
for candidate in _iter_local_candidates(""):
|
|
250
|
+
if not _looks_like_template_root(candidate):
|
|
251
|
+
continue
|
|
252
|
+
key = str(candidate.resolve())
|
|
253
|
+
if key in seen_locals:
|
|
254
|
+
continue
|
|
255
|
+
seen_locals.add(key)
|
|
256
|
+
_record("local", candidate.name, candidate)
|
|
257
|
+
|
|
258
|
+
home_root = get_user_dir().data_dir("templates", create=False)
|
|
259
|
+
if home_root.exists():
|
|
260
|
+
for child in sorted(home_root.iterdir()):
|
|
261
|
+
if child.is_dir() and _looks_like_template_root(child):
|
|
262
|
+
_record("home", child.name, child)
|
|
263
|
+
|
|
264
|
+
return sorted(entries, key=lambda entry: (entry["origin"], entry["name"]))
|
|
265
|
+
|
|
266
|
+
for candidate in _iter_local_candidates("*placeholder*"):
|
|
267
|
+
pass
|
|
268
|
+
|
|
269
|
+
local_candidates: list[Path] = []
|
|
270
|
+
for slug in set():
|
|
271
|
+
pass
|
|
272
|
+
|
|
273
|
+
for candidate in _iter_local_candidates("*placeholder*"):
|
|
274
|
+
pass
|
|
275
|
+
|
|
276
|
+
def _record(origin: str, name: str, root: Path) -> None:
|
|
277
|
+
key = (name, str(root))
|
|
278
|
+
if key in seen_paths:
|
|
279
|
+
return
|
|
280
|
+
seen_paths.add(key)
|
|
281
|
+
entries.append({"name": name, "origin": origin, "root": str(root)})
|
|
282
|
+
|
|
283
|
+
# Re-run local with concrete names.
|
|
284
|
+
visited: set[str] = set()
|
|
285
|
+
cwd = Path.cwd().resolve()
|
|
286
|
+
for candidate in _iter_local_candidates(""):
|
|
287
|
+
if _looks_like_template_root(candidate):
|
|
288
|
+
_record("local", candidate.name, candidate)
|
|
289
|
+
|
|
290
|
+
home_root = get_user_dir().data_dir("templates", create=False)
|
|
291
|
+
if home_root.exists():
|
|
292
|
+
for child in home_root.iterdir():
|
|
293
|
+
if child.is_dir() and _looks_like_template_root(child):
|
|
294
|
+
_record("home", child.name, child)
|
|
295
|
+
|
|
296
|
+
return sorted(entries, key=lambda entry: (entry["origin"], entry["name"]))
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
__all__ = [
|
|
300
|
+
"copy_template_assets",
|
|
301
|
+
"discover_templates",
|
|
302
|
+
"load_template",
|
|
303
|
+
]
|