markpublish 2.3.0__tar.gz → 2.3.2__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.
- {markpublish-2.3.0/src/markpublish.egg-info → markpublish-2.3.2}/PKG-INFO +1 -1
- {markpublish-2.3.0 → markpublish-2.3.2}/pyproject.toml +1 -1
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/__init__.py +1 -1
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/config/loader.py +75 -6
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/docs/cheatsheet/de/01_config.md +5 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/docs/cheatsheet/de/markpublish.yaml +1 -1
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/docs/cheatsheet/en/01_config.md +5 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/docs/cheatsheet/en/markpublish.yaml +1 -1
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/docs/manual/de/chapters/04_configuration_guide.md +71 -21
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/docs/manual/de/chapters/07_appendix_yaml_spec.md +1 -1
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/docs/manual/de/markpublish.yaml +1 -1
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/docs/manual/en/chapters/04_configuration_guide.md +71 -20
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/docs/manual/en/chapters/07_appendix_yaml_spec.md +1 -1
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/docs/manual/en/markpublish.yaml +1 -1
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/locale/de.yaml +10 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/locale/en.yaml +10 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/markdown/engine.py +7 -1
- markpublish-2.3.2/src/markpublish/markdown/variables.py +408 -0
- {markpublish-2.3.0 → markpublish-2.3.2/src/markpublish.egg-info}/PKG-INFO +1 -1
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish.egg-info/SOURCES.txt +1 -0
- markpublish-2.3.2/tests/test_file_ref.py +128 -0
- markpublish-2.3.2/tests/test_variables.py +243 -0
- markpublish-2.3.0/src/markpublish/markdown/variables.py +0 -122
- markpublish-2.3.0/tests/test_variables.py +0 -103
- {markpublish-2.3.0 → markpublish-2.3.2}/LICENSE +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/README.md +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/setup.cfg +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/cli.py +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/config/models.py +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/docs/cheatsheet/de/02_templates.md +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/docs/cheatsheet/en/02_templates.md +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/docs/init/de/kapitel_1.md +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/docs/init/de/kapitel_2.md +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/docs/init/de/markpublish.yaml +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/docs/init/en/chapter_1.md +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/docs/init/en/chapter_2.md +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/docs/init/en/markpublish.yaml +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/docs/manual/de/chapters/01_introduction.md +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/docs/manual/de/chapters/02_quickstart.md +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/docs/manual/de/chapters/03_cli_reference.md +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/docs/manual/de/chapters/05_template_system.md +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/docs/manual/de/chapters/06_markdown_features.md +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/docs/manual/de/chapters/08_appendix_troubleshooting.md +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/docs/manual/en/chapters/01_introduction.md +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/docs/manual/en/chapters/02_quickstart.md +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/docs/manual/en/chapters/03_cli_reference.md +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/docs/manual/en/chapters/05_template_system.md +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/docs/manual/en/chapters/06_markdown_features.md +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/docs/manual/en/chapters/08_appendix_troubleshooting.md +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/docs/manual/img/markpublish_logo.png +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/entry.py +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/i18n.py +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/i18n.yaml +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/markdown/__init__.py +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/markdown/alerts.py +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/markdown/pattern.py +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/markdown/toc.py +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/markdown/typst_serializer.py +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/renderers/__init__.py +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/renderers/base.py +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/renderers/pdf.py +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/syslang.py +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/templates/__init__.py +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/templates/contract.py +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/templates/default/i18n.yaml +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/templates/default/pdf/assets/icons/caution.svg +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/templates/default/pdf/assets/icons/checkbox-check.svg +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/templates/default/pdf/assets/icons/important.svg +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/templates/default/pdf/assets/icons/note.svg +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/templates/default/pdf/assets/icons/tip.svg +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/templates/default/pdf/assets/icons/warning.svg +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/templates/default/pdf/fonts/NotoSansMono-VariableFont_wdth,wght.ttf +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/templates/default/pdf/fonts/OFL-NotoSansMono.txt +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/templates/default/pdf/fonts/OFL.txt +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/templates/default/pdf/fonts/OpenSans-Italic-VariableFont_wdth,wght.ttf +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/templates/default/pdf/fonts/OpenSans-VariableFont_wdth,wght.ttf +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/templates/default/pdf/i18n.yaml +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/templates/default/pdf/template.typ +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/templates/resolver.py +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish/ui.py +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish.egg-info/dependency_links.txt +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish.egg-info/entry_points.txt +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish.egg-info/requires.txt +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/src/markpublish.egg-info/top_level.txt +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/tests/test_cli.py +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/tests/test_config.py +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/tests/test_defensive_review.py +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/tests/test_figures.py +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/tests/test_header_footer.py +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/tests/test_i18n.py +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/tests/test_i18n_cascade.py +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/tests/test_markdown.py +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/tests/test_packaging.py +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/tests/test_pattern.py +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/tests/test_regressions.py +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/tests/test_renderers.py +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/tests/test_templates.py +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/tests/test_theme_contract.py +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/tests/test_theme_layout.py +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/tests/test_toc.py +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/tests/test_typst_compile_cases.py +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/tests/test_typst_serializer.py +0 -0
- {markpublish-2.3.0 → markpublish-2.3.2}/tests/test_ui.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "markpublish"
|
|
7
|
-
version = "2.3.
|
|
7
|
+
version = "2.3.2"
|
|
8
8
|
description = "Modern, modular, and extensible Markdown-to-PDF publishing tool powered by Typst"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -6,8 +6,11 @@ from __future__ import annotations
|
|
|
6
6
|
|
|
7
7
|
import copy
|
|
8
8
|
import datetime
|
|
9
|
+
import urllib.error
|
|
10
|
+
import urllib.request
|
|
9
11
|
from pathlib import Path
|
|
10
|
-
from typing import Any, Dict, Optional, Union
|
|
12
|
+
from typing import Any, Dict, FrozenSet, Optional, Union
|
|
13
|
+
from urllib.parse import urljoin, urlparse
|
|
11
14
|
|
|
12
15
|
import yaml
|
|
13
16
|
|
|
@@ -25,6 +28,70 @@ def format_current_date(language: Optional[str] = None) -> str:
|
|
|
25
28
|
return now.strftime("%Y-%m-%d")
|
|
26
29
|
|
|
27
30
|
|
|
31
|
+
#: Woher eine Konfiguration stammt: Verzeichnis einer Datei oder eine URL.
|
|
32
|
+
#: Relative Verweise mit `!file` loesen sich dagegen auf.
|
|
33
|
+
Base = Union[Path, str]
|
|
34
|
+
|
|
35
|
+
FILE_TAG = "!file"
|
|
36
|
+
YAML_SUFFIXES = (".yaml", ".yml")
|
|
37
|
+
URL_TIMEOUT = 10
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _is_url(src: str) -> bool:
|
|
41
|
+
return src.startswith(("http://", "https://"))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _resolve_ref(src: str, base: Base) -> Union[Path, str]:
|
|
45
|
+
"""Absoluter Ort eines `!file`-Verweises, relativ zu der Datei, in der er steht."""
|
|
46
|
+
if _is_url(src):
|
|
47
|
+
return src
|
|
48
|
+
if isinstance(base, str):
|
|
49
|
+
return urljoin(base, src)
|
|
50
|
+
return (base / src).resolve()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _read_ref(target: Union[Path, str], src: str) -> str:
|
|
54
|
+
if isinstance(target, str):
|
|
55
|
+
try:
|
|
56
|
+
with urllib.request.urlopen(target, timeout=URL_TIMEOUT) as response:
|
|
57
|
+
return response.read().decode("utf-8")
|
|
58
|
+
except (urllib.error.URLError, OSError, UnicodeDecodeError) as exc:
|
|
59
|
+
raise ValueError(t("err.config.file_ref_url", src=src, reason=exc)) from exc
|
|
60
|
+
if not target.is_file():
|
|
61
|
+
raise FileNotFoundError(t("err.config.file_ref_missing", src=src, path=target))
|
|
62
|
+
return target.read_text(encoding="utf-8")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _load_yaml(text: Any, base: Base, seen: FrozenSet[Union[Path, str]] = frozenset()) -> Any:
|
|
66
|
+
"""
|
|
67
|
+
Wie `yaml.safe_load`, zusaetzlich mit `!file "pfad-oder-url"`.
|
|
68
|
+
|
|
69
|
+
Ein Verweis auf .yaml/.yml setzt deren Inhalt an seine Stelle, jede
|
|
70
|
+
andere Datei ihren Text. Nachgeladene YAML-Dateien duerfen selbst wieder
|
|
71
|
+
`!file` enthalten; deren Pfade gelten relativ zu ihnen. `seen` haelt die
|
|
72
|
+
Kette der offenen Dateien -- dieselbe Datei in zwei Zweigen ist erlaubt,
|
|
73
|
+
nur ein Verweis zurueck in die eigene Kette nicht.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
def construct(loader: yaml.SafeLoader, node: yaml.Node) -> Any:
|
|
77
|
+
src = loader.construct_scalar(node)
|
|
78
|
+
target = _resolve_ref(src, base)
|
|
79
|
+
if target in seen:
|
|
80
|
+
raise ValueError(t("err.config.file_ref_cycle", src=src, path=target))
|
|
81
|
+
content = _read_ref(target, src)
|
|
82
|
+
name = urlparse(target).path if isinstance(target, str) else target.name
|
|
83
|
+
if not name.lower().endswith(YAML_SUFFIXES):
|
|
84
|
+
return content.rstrip("\n")
|
|
85
|
+
child_base = target if isinstance(target, str) else target.parent
|
|
86
|
+
return _load_yaml(content, child_base, seen | {target})
|
|
87
|
+
|
|
88
|
+
# Eigene Loader-Klasse je Aufruf: der Konstruktor haengt an `base` und
|
|
89
|
+
# `seen`, und SafeLoader selbst soll unveraendert bleiben.
|
|
90
|
+
loader_cls = type("_FileLoader", (yaml.SafeLoader,), {})
|
|
91
|
+
loader_cls.add_constructor(FILE_TAG, construct)
|
|
92
|
+
return yaml.load(text, Loader=loader_cls) # noqa: S506 - SafeLoader-Unterklasse
|
|
93
|
+
|
|
94
|
+
|
|
28
95
|
def load_config(config_path_or_str: Union[str, Path, Dict[str, Any]]) -> MarkpublishConfig:
|
|
29
96
|
"""
|
|
30
97
|
Loads and validates a markpublish configuration.
|
|
@@ -44,8 +111,9 @@ def load_config(config_path_or_str: Union[str, Path, Dict[str, Any]]) -> Markpub
|
|
|
44
111
|
elif isinstance(config_path_or_str, Path):
|
|
45
112
|
if not config_path_or_str.is_file():
|
|
46
113
|
raise FileNotFoundError(t("err.config.not_found_file", path=config_path_or_str))
|
|
47
|
-
|
|
48
|
-
|
|
114
|
+
config_path = config_path_or_str.resolve()
|
|
115
|
+
with open(config_path, "r", encoding="utf-8") as f:
|
|
116
|
+
loaded = _load_yaml(f, config_path.parent, frozenset({config_path}))
|
|
49
117
|
if loaded is None:
|
|
50
118
|
raise ValueError(t("err.config.expected_mapping", type="empty"))
|
|
51
119
|
if not isinstance(loaded, dict):
|
|
@@ -56,8 +124,9 @@ def load_config(config_path_or_str: Union[str, Path, Dict[str, Any]]) -> Markpub
|
|
|
56
124
|
# YAML-Text mit Zeilenumbruch ist nie ein Pfad; ein langer liesse is_file()
|
|
57
125
|
# unter Linux/macOS vor Python 3.14 mit ENAMETOOLONG scheitern.
|
|
58
126
|
if "\n" not in config_path_or_str and candidate_path.is_file():
|
|
59
|
-
|
|
60
|
-
|
|
127
|
+
config_path = candidate_path.resolve()
|
|
128
|
+
with open(config_path, "r", encoding="utf-8") as f:
|
|
129
|
+
loaded = _load_yaml(f, config_path.parent, frozenset({config_path}))
|
|
61
130
|
if loaded is None:
|
|
62
131
|
raise ValueError(t("err.config.expected_mapping", type="empty"))
|
|
63
132
|
if not isinstance(loaded, dict):
|
|
@@ -78,7 +147,7 @@ def load_config(config_path_or_str: Union[str, Path, Dict[str, Any]]) -> Markpub
|
|
|
78
147
|
|
|
79
148
|
# Versuche als YAML-String zu parsen
|
|
80
149
|
try:
|
|
81
|
-
loaded =
|
|
150
|
+
loaded = _load_yaml(config_path_or_str, Path.cwd())
|
|
82
151
|
except yaml.YAMLError as exc:
|
|
83
152
|
raise ValueError(str(exc)) from exc
|
|
84
153
|
|
|
@@ -34,6 +34,7 @@ Metadaten und globale Layout-Schalter. Pflichtangabe ist allein `title`.
|
|
|
34
34
|
| `status` | – | Dokumentstatus (z. B. `Entwurf`, `Freigegeben`) |
|
|
35
35
|
| `copyright` | – | Copyright-Hinweis auf dem Deckblatt |
|
|
36
36
|
| `language` | Systemsprache | ISO-Sprachcode für statische Beschriftungen (`de`, `en`) |
|
|
37
|
+
| `custom` | – | Eigene Werte für Platzhalter im Text (`{{custom.a.b}}`), auch verschachtelt |
|
|
37
38
|
| `cover` | `false` | Deckblatt erzeugen |
|
|
38
39
|
| `header` / `footer` | `true` | Kopf- und Fußzeilen aktivieren |
|
|
39
40
|
| `document_toc` | `full` | Haupt-Inhaltsverzeichnis: `none`, `full` oder Tiefe als Zahl |
|
|
@@ -49,6 +50,10 @@ Besteht das Dokument aus **genau einem Kapitel**, lässt das Haupt-Inhaltsverzei
|
|
|
49
50
|
|
|
50
51
|
Eigene Zusatzfelder unter `document:` (`abteilung: "F&E"`) erreichen das Theme über das Wörterbuch `meta` (`meta.at("abteilung").value`). Statische Beschriftungen gehören in die `i18n.yaml`, nicht hierher.
|
|
51
52
|
|
|
53
|
+
Im Markdown setzt `{{name}}` eine Angabe aus `document:` ein (`{{author}}`, `{{custom.verfahren.name}}`). Werte unter `custom` erreichen das Theme nicht. Ein Platzhalter ohne Wert bricht den Bau ab; `\{{author}}` bleibt wörtlich stehen.
|
|
54
|
+
|
|
55
|
+
`schlüssel: !file "pfad-oder-url"` lädt einen Wert aus einer anderen Datei, überall in der Konfiguration. YAML-Dateien liefern einen verschachtelten Wert, jede andere Datei ihren Text. Relative Pfade gelten ab der verweisenden Datei.
|
|
56
|
+
|
|
52
57
|
## parts
|
|
53
58
|
|
|
54
59
|
Ein Part klammert Kapitel und gibt Einstellungen an sie weiter. Ohne `break_before` zeigt er sich selbst nicht.
|
|
@@ -34,6 +34,7 @@ Metadata and global layout toggles. Only `title` is required.
|
|
|
34
34
|
| `status` | – | Document status (e.g. `Draft`, `Approved`) |
|
|
35
35
|
| `copyright` | – | Copyright notice on the cover page |
|
|
36
36
|
| `language` | system language | ISO code for static labels (`de`, `en`) |
|
|
37
|
+
| `custom` | – | Own values for placeholders in the text (`{{custom.a.b}}`), may be nested |
|
|
37
38
|
| `cover` | `false` | Generate a cover page |
|
|
38
39
|
| `header` / `footer` | `true` | Running headers and footers |
|
|
39
40
|
| `document_toc` | `full` | Main table of contents: `none`, `full` or a depth |
|
|
@@ -49,6 +50,10 @@ If the document consists of **exactly one chapter**, the main table of contents
|
|
|
49
50
|
|
|
50
51
|
Custom fields under `document:` (`department: "R&D"`) reach the theme through the `meta` dictionary (`meta.at("department").value`). Static labels belong in `i18n.yaml`, not here.
|
|
51
52
|
|
|
53
|
+
In Markdown, `{{name}}` inserts a value from `document:` (`{{author}}`, `{{custom.procedure.name}}`). Values under `custom` do not reach the theme. A placeholder without a value stops the build; `\{{author}}` stays as written.
|
|
54
|
+
|
|
55
|
+
`key: !file "path-or-url"` loads a value from another file, anywhere in the configuration. YAML files supply a nested value, any other file its text. Relative paths resolve against the referencing file.
|
|
56
|
+
|
|
52
57
|
## parts
|
|
53
58
|
|
|
54
59
|
A part brackets chapters and passes settings down to them. Without `break_before` it does not show itself.
|
|
@@ -41,7 +41,35 @@ Globale Verzeichnisvorgaben
|
|
|
41
41
|
Nummerierungsregeln
|
|
42
42
|
: Vorgaben zur automatischen Nummerierung (`autonum_pattern`, `autonum_reset`).
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
### Parts (Abschnitte)
|
|
45
|
+
|
|
46
|
+
Ein Dokument wird durch `parts:` in übergeordnete Abschnitte gegliedert (z. B. „Hauptteil“, „Fallstudien“, „Anhänge“):
|
|
47
|
+
|
|
48
|
+
* Ein Dokument muss immer über `parts:` aufgebaut sein; ein flaches `chapters:` direkt auf oberster Ebene wird abgewiesen.
|
|
49
|
+
|
|
50
|
+
* Ein Abschnitt fasst logisch zusammengehörige Kapitel zusammen.
|
|
51
|
+
|
|
52
|
+
* Ohne Angabe tritt ein Abschnitt selbst nicht in Erscheinung (`break_before: "none"`): er klammert seine Kapitel und vererbt seine Einstellungen, belegt aber keine Seite und erscheint nicht im Inhaltsverzeichnis. Mit `break_before: "divider"` erhält er eine gestaltete Trennseite, mit `break_before: "page"` eine Überschrift auf einer neuen Seite.
|
|
53
|
+
|
|
54
|
+
* Auf Abschnittsebene gesetzte Einstellungen (z. B. Verzeichnistiefe oder ein `autonum_pattern`) vererben sich automatisch auf alle darin enthaltenen Kapitel.
|
|
55
|
+
|
|
56
|
+
* Auf `parts:` sind ausschließlich die deklarierten Konfigurationsschlüssel erlaubt. Unbekannte Schlüssel (wie `break_befor`) werden mit Ähnlichkeitsvorschlägen abgelehnt, um unbemerkte Fehlkonfigurationen auszuschließen.
|
|
57
|
+
|
|
58
|
+
### Chapters (Kapitel)
|
|
59
|
+
|
|
60
|
+
Die Liste `chapters:` innerhalb eines Abschnitts verweist auf die eigentlichen Markdown-Inhaltsdateien:
|
|
61
|
+
|
|
62
|
+
* Jedes Kapitel verweist auf seine Markdown-Datei (`file:`). Autoren können in ihren Markdown-Dateien ganz natürlich mit einer `#`-Überschrift beginnen.
|
|
63
|
+
|
|
64
|
+
* Über **`show_title:`** (`true` oder `false`, Standard: `true`) lässt sich steuern, ob die `#`-Überschrift der Markdown-Datei auf der Inhaltsseite gerendert werden soll. Wird vor dem Kapitel eine Trennseite erzeugt (`break_before: "divider"`), kann mit `show_title: false` verhindert werden, dass der Titel auf der Folgeseite doppelt erscheint – die Inhaltsseite beginnt dann direkt mit dem Fließtext oder der ersten Zwischenüberschrift.
|
|
65
|
+
|
|
66
|
+
* Über die optionalen Schlüssel **`toc_title:`** (für Inhaltsverzeichnisse und Kopfzeilen) und **`divider_title:`** (für Trennseiten) können gezielt abweichende Titel vergeben werden (z. B. eine prägnante Kurzform im Inhaltsverzeichnis gegenüber einer ausführlichen Überschrift auf der Textseite). Fehlen die Schlüssel, erben beide automatisch die `#`-Überschrift der Datei.
|
|
67
|
+
|
|
68
|
+
* Soll eine Trennseite erzeugt werden (`break_before: "divider"`) oder das Kapitel im Inhaltsverzeichnis gelistet werden, muss mindestens eine `#`-Überschrift oder der entsprechende Titelschlüssel (`divider_title` / `toc_title`) vorhanden sein, andernfalls bricht der Build mit einer klaren Fehlermeldung ab.
|
|
69
|
+
|
|
70
|
+
* Kapitel können über das Feld `break_before` steuern, ob vor ihnen eine Trennseite erzeugt wird, ein einfacher Seitenwechsel erfolgt oder der Text nahtlos anschließt.
|
|
71
|
+
|
|
72
|
+
## Eigene Metadatenfelder
|
|
45
73
|
|
|
46
74
|
Unter `document:` sind über die bekannten Schlüssel hinaus **beliebige eigene Felder** erlaubt. Sie erreichen das Theme, werden aber nicht von selbst gedruckt:
|
|
47
75
|
|
|
@@ -76,7 +104,7 @@ Drei Dinge sind dabei zu wissen:
|
|
|
76
104
|
|
|
77
105
|
* **Der Typ bleibt erhalten.** Ein `true` ist ein Wahrheitswert und kein Text: Das Deckblatt setzt `Ja` beziehungsweise `Yes`, je nach Dokumentsprache. Die beiden Wörter stehen als `bool_true` und `bool_false` in der i18n-Kaskade.
|
|
78
106
|
|
|
79
|
-
|
|
107
|
+
## Platzhalter im Text
|
|
80
108
|
|
|
81
109
|
Im Markdown setzt `{{name}}` eine Angabe aus `document:` ein, etwa `{{author}}`, `{{version}}` oder `{{date}}` (bei `"auto"` das errechnete Datum). Das gilt auch für eigene Metadatenfelder. Werte, die nur im Text gebraucht werden, gehören unter `custom:`. Dort dürfen sie verschachtelt sein und werden mit Punkten angesprochen:
|
|
82
110
|
|
|
@@ -94,36 +122,58 @@ Ansprechpartner: {{author}}, {{custom.support_mail}}.
|
|
|
94
122
|
Dieses Dokument beschreibt {{custom.verfahren.name}}.
|
|
95
123
|
```
|
|
96
124
|
|
|
97
|
-
Werte unter `custom` erreichen das Theme nicht. Ein Platzhalter ohne Wert, auch ein vertippter, bricht den Bau mit Angabe der Datei ab. In Code (`` `{{author}}` `` oder Codeblock) bleibt der Platzhalter wörtlich stehen, im Fließtext mit vorangestelltem Backslash: `\{{author}}` ergibt `{{author}}`.
|
|
98
|
-
|
|
99
|
-
### Parts (Abschnitte)
|
|
100
|
-
|
|
101
|
-
Ein Dokument wird durch `parts:` in übergeordnete Abschnitte gegliedert (z. B. „Hauptteil“, „Fallstudien“, „Anhänge“):
|
|
102
|
-
|
|
103
|
-
* Ein Dokument muss immer über `parts:` aufgebaut sein; ein flaches `chapters:` direkt auf oberster Ebene wird abgewiesen.
|
|
125
|
+
Ist ein eingesetzter Wert eine E-Mail-Adresse oder eine URL, wird er wie direkt geschriebener Text ein Link; steht der Platzhalter schon in einem Link, bleibt es bei diesem. Werte unter `custom` erreichen das Theme nicht. Ein Platzhalter ohne Wert, auch ein vertippter, bricht den Bau mit Angabe der Datei ab. In Code (`` `{{author}}` `` oder Codeblock) bleibt der Platzhalter wörtlich stehen, im Fließtext mit vorangestelltem Backslash: `\{{author}}` ergibt `{{author}}`.
|
|
104
126
|
|
|
105
|
-
|
|
127
|
+
### Schleifen und Kurznamen
|
|
106
128
|
|
|
107
|
-
|
|
129
|
+
`{% for %}` wiederholt die Zeilen bis `{% endfor %}` für jeden Eintrag einer Gruppe unter `custom`, in der Reihenfolge der YAML-Datei. So entsteht etwa eine Tabelle mit einer Zeile je Rolle:
|
|
108
130
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
131
|
+
```yaml
|
|
132
|
+
document:
|
|
133
|
+
custom:
|
|
134
|
+
app:
|
|
135
|
+
roles:
|
|
136
|
+
application_owner: {label: "Verfahrensverantwortliche/r", name: "A. Muster"}
|
|
137
|
+
technical_administrator: {label: "Technische Administration", name: "B. Beispiel"}
|
|
138
|
+
```
|
|
112
139
|
|
|
113
|
-
|
|
140
|
+
```markdown
|
|
141
|
+
| Rolle | Name | Schlüssel |
|
|
142
|
+
| :--- | :--- | :--- |
|
|
143
|
+
{% for (role, key) in custom.app.roles %}
|
|
144
|
+
| {{role.label}} | {{role.name}} | {{key}} |
|
|
145
|
+
{% endfor %}
|
|
146
|
+
```
|
|
114
147
|
|
|
115
|
-
|
|
148
|
+
`{% set %}` gibt einem langen Pfad einen Kurznamen, der bis zum Ende der Datei gilt (innerhalb einer Schleife bis zu deren Ende):
|
|
116
149
|
|
|
117
|
-
|
|
150
|
+
```markdown
|
|
151
|
+
{% set owner = custom.app.roles.application_owner %}
|
|
152
|
+
Verantwortlich ist {{owner.name}}.
|
|
153
|
+
```
|
|
118
154
|
|
|
119
|
-
*
|
|
155
|
+
* **Anweisungen stehen allein in ihrer Zeile**; die Zeile verschwindet beim Bau vollständig, eine Tabelle bleibt also zusammenhängend. In einem Zitat oder einer Hinweisbox darf `>` davorstehen.
|
|
156
|
+
* **`(role, key)`** liefert zusätzlich den Namen des Eintrags (`application_owner`); `for role in …` genügt, wenn er nicht gebraucht wird.
|
|
157
|
+
* **Schleifen lassen sich schachteln.** Der Name einer Schleife gilt nur in ihr; ein Name darf keine Angabe unter `document` verdecken (`author`, `custom` …).
|
|
158
|
+
* In Code und Codeblöcken bleiben Anweisungen wörtlich stehen, im Fließtext mit vorangestelltem Backslash: `\{% … %}`. Fehler wie ein fehlendes `{% endfor %}` brechen den Bau mit Datei und Zeile ab.
|
|
120
159
|
|
|
121
|
-
|
|
160
|
+
## Werte aus anderen Dateien (`!file`)
|
|
122
161
|
|
|
123
|
-
|
|
162
|
+
Statt einen Wert direkt hinzuschreiben, lädt `!file` ihn aus einer Datei oder von einer URL nach. Beides lässt sich beliebig mischen:
|
|
124
163
|
|
|
125
|
-
|
|
164
|
+
```yaml
|
|
165
|
+
document:
|
|
166
|
+
custom:
|
|
167
|
+
a: "xxx"
|
|
168
|
+
verfahren: !file "../gemeinsam/verfahren.yaml"
|
|
169
|
+
zweck: !file "zweck.txt"
|
|
170
|
+
betreiber: !file "https://intranet.example/betreiber.yaml"
|
|
171
|
+
```
|
|
126
172
|
|
|
173
|
+
* **YAML-Dateien** (`.yaml`, `.yml`) setzen ihren Inhalt an die Stelle des Verweises, `{{custom.verfahren.name}}` greift also in die nachgeladene Datei. **Jede andere Datei** liefert ihren Text als einfachen Wert.
|
|
174
|
+
* **Relative Pfade** gelten relativ zu der Datei, in der das `!file` steht, bei einer URL relativ zu dieser URL.
|
|
175
|
+
* **Verschachtelt:** Eine nachgeladene YAML-Datei darf selbst wieder `!file` enthalten. Dieselbe Datei an mehreren Stellen ist erlaubt; ein Verweis, der über die Kette zu sich selbst zurückführt, bricht den Bau ab.
|
|
176
|
+
* `!file` wirkt überall in der Konfiguration, nicht nur unter `custom`. Ein Abruf per URL wartet höchstens 10 Sekunden.
|
|
127
177
|
|
|
128
178
|
---
|
|
129
179
|
|
|
@@ -19,7 +19,7 @@ Die Sektion `document:` legt globale Metadaten, Layoutschalter, Verzeichnisvorga
|
|
|
19
19
|
| `date` | String | `"auto"` | Datum des Dokuments. Bei `"auto"` oder `"today"` wird das aktuelle Tagesdatum eingesetzt. |
|
|
20
20
|
| `version` | String | `None` | Versionskennung (z. B. `"2.0.0"`). Wird nur gedruckt, wenn explizit gesetzt. |
|
|
21
21
|
| `language` | String | *(Systemsprache)* | ISO-Sprachcode (z. B. `"de"` oder `"en"`). Steuert Silbentrennung und UI-Texte. |
|
|
22
|
-
| `custom` | Mapping | `{}` | Eigene Werte für Platzhalter im Text (`{{custom.a.b}}`), auch verschachtelt. Siehe *Platzhalter im Text*. |
|
|
22
|
+
| `custom` | Mapping | `{}` | Eigene Werte für Platzhalter im Text (`{{custom.a.b}}`), auch verschachtelt. Siehe *Platzhalter im Text*. Einzelne Werte oder Gruppen lassen sich mit `!file` nachladen. |
|
|
23
23
|
| `cover` | Boolean | `false` | Steuert, ob eine gestaltete Deckblattseite erzeugt wird. Ohne Angabe beginnt das Dokument mit dem ersten Inhalt. |
|
|
24
24
|
| `header` | Boolean | `true` | Aktiviert oder deaktiviert die laufende Kopfzeile im Dokument. |
|
|
25
25
|
| `footer` | Boolean | `true` | Aktiviert oder deaktiviert die laufende Fußzeile (inkl. Seitennummerierung). |
|
|
@@ -6,7 +6,7 @@ document:
|
|
|
6
6
|
summary: "Praxisorientierte Anleitung und Referenz zur Konfiguration, CLI-Bedienung, Theme-Gestaltung und Dokumentenerstellung mit markpublish."
|
|
7
7
|
author: "Frank Winter"
|
|
8
8
|
copyright: "© 2026 Frank Winter"
|
|
9
|
-
version: "2.3.
|
|
9
|
+
version: "2.3.2"
|
|
10
10
|
date: "auto"
|
|
11
11
|
language: "de"
|
|
12
12
|
|
|
@@ -41,7 +41,35 @@ Global Table of Contents Settings
|
|
|
41
41
|
Numbering Rules
|
|
42
42
|
: Settings for automatic numbering (`autonum_pattern`, `autonum_reset`).
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
### Parts (Sections)
|
|
45
|
+
|
|
46
|
+
A document is organized into high-level sections via `parts:` (e.g., "Main Part", "Case Studies", "Appendices"):
|
|
47
|
+
|
|
48
|
+
* A document must always be structured using `parts:`; a flat `chapters:` list at the top level is rejected.
|
|
49
|
+
|
|
50
|
+
* A part groups logically related chapters together.
|
|
51
|
+
|
|
52
|
+
* By default, a part produces no separate page (`break_before: "none"`): it groups its chapters and passes down its settings, but occupies no page and does not appear in the table of contents. With `break_before: "divider"`, it receives a styled divider page; with `break_before: "page"`, it receives a heading on a fresh page.
|
|
53
|
+
|
|
54
|
+
* Settings defined at the part level (such as TOC depth or an `autonum_pattern`) are automatically inherited by all chapters contained within.
|
|
55
|
+
|
|
56
|
+
* Under `parts:`, only declared configuration keys are allowed. Unknown keys (such as `break_befor`) are rejected with fuzzy suggestions to prevent unnoticed misconfigurations.
|
|
57
|
+
|
|
58
|
+
### Chapters (Chapters)
|
|
59
|
+
|
|
60
|
+
The `chapters:` list within a part references the actual Markdown content files:
|
|
61
|
+
|
|
62
|
+
* Each chapter points to its Markdown file (`file:`). Authors can write their Markdown files naturally starting with a `#` heading.
|
|
63
|
+
|
|
64
|
+
* **`show_title:`** (`true` or `false`, default: `true`) controls whether the file's `#` heading should be rendered on the content page. If a divider page precedes the chapter (`break_before: "divider"`), setting `show_title: false` prevents the title from repeating on the next page – the content page then starts directly with the introductory text or the first subheading.
|
|
65
|
+
|
|
66
|
+
* Optional keys **`toc_title:`** (for tables of contents and headers) and **`divider_title:`** (for divider pages) allow defining distinct alternative titles (e.g., a concise short form in the table of contents versus an extensive title on the chapter page). When omitted, both inherit the file's `#` heading.
|
|
67
|
+
|
|
68
|
+
* When generating a divider page (`break_before: "divider"`) or listing the chapter in the table of contents, at least one `#` heading or the corresponding title key (`divider_title` / `toc_title`) must exist; otherwise, the build aborts with an informative error message.
|
|
69
|
+
|
|
70
|
+
* Chapters can control via `break_before` whether a divider page is generated, a simple page break occurs, or content flows seamlessly.
|
|
71
|
+
|
|
72
|
+
## Custom Metadata Fields
|
|
45
73
|
|
|
46
74
|
Under `document:`, **arbitrary custom fields** are permitted beyond the standard keys. They are forwarded to the theme, but are not printed automatically:
|
|
47
75
|
|
|
@@ -76,7 +104,7 @@ Three essential things to keep in mind:
|
|
|
76
104
|
|
|
77
105
|
* **Types are preserved.** A `true` is a boolean value, not raw text: the cover page typesets `Yes` or `No` (or `Ja` / `Nein`) depending on the document language. These strings are looked up as `bool_true` and `bool_false` in the i18n cascade.
|
|
78
106
|
|
|
79
|
-
|
|
107
|
+
## Placeholders in the Text
|
|
80
108
|
|
|
81
109
|
In Markdown, `{{name}}` inserts a value from `document:`, e.g. `{{author}}`, `{{version}}` or `{{date}}` (the computed date for `"auto"`). This includes custom metadata fields. Values needed only in the text belong under `custom:`. They may be nested there and are addressed with dots:
|
|
82
110
|
|
|
@@ -94,35 +122,58 @@ Contact: {{author}}, {{custom.support_mail}}.
|
|
|
94
122
|
This document describes {{custom.procedure.name}}.
|
|
95
123
|
```
|
|
96
124
|
|
|
97
|
-
Values under `custom` do not reach the theme. A placeholder without a value, including a misspelled one, stops the build and names the file. In code (`` `{{author}}` `` or a code block) the placeholder stays as written; in running text, put a backslash in front: `\{{author}}` gives `{{author}}`.
|
|
125
|
+
If an inserted value is an email address or a URL, it becomes a link, just like text written directly; a placeholder already inside a link keeps that link. Values under `custom` do not reach the theme. A placeholder without a value, including a misspelled one, stops the build and names the file. In code (`` `{{author}}` `` or a code block) the placeholder stays as written; in running text, put a backslash in front: `\{{author}}` gives `{{author}}`.
|
|
98
126
|
|
|
99
|
-
###
|
|
127
|
+
### Loops and Short Names
|
|
100
128
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
* A document must always be structured using `parts:`; a flat `chapters:` list at the top level is rejected.
|
|
104
|
-
|
|
105
|
-
* A part groups logically related chapters together.
|
|
106
|
-
|
|
107
|
-
* By default, a part produces no separate page (`break_before: "none"`): it groups its chapters and passes down its settings, but occupies no page and does not appear in the table of contents. With `break_before: "divider"`, it receives a styled divider page; with `break_before: "page"`, it receives a heading on a fresh page.
|
|
129
|
+
`{% for %}` repeats the lines up to `{% endfor %}` for each entry of a group under `custom`, in the order of the YAML file. This gives, for example, a table with one row per role:
|
|
108
130
|
|
|
109
|
-
|
|
131
|
+
```yaml
|
|
132
|
+
document:
|
|
133
|
+
custom:
|
|
134
|
+
app:
|
|
135
|
+
roles:
|
|
136
|
+
application_owner: {label: "Application owner", name: "A. Sample"}
|
|
137
|
+
technical_administrator: {label: "Technical administration", name: "B. Example"}
|
|
138
|
+
```
|
|
110
139
|
|
|
111
|
-
|
|
140
|
+
```markdown
|
|
141
|
+
| Role | Name | Key |
|
|
142
|
+
| :--- | :--- | :--- |
|
|
143
|
+
{% for (role, key) in custom.app.roles %}
|
|
144
|
+
| {{role.label}} | {{role.name}} | {{key}} |
|
|
145
|
+
{% endfor %}
|
|
146
|
+
```
|
|
112
147
|
|
|
113
|
-
|
|
148
|
+
`{% set %}` gives a long path a short name, valid up to the end of the file (inside a loop, up to the end of that loop):
|
|
114
149
|
|
|
115
|
-
|
|
150
|
+
```markdown
|
|
151
|
+
{% set owner = custom.app.roles.application_owner %}
|
|
152
|
+
Responsible: {{owner.name}}.
|
|
153
|
+
```
|
|
116
154
|
|
|
117
|
-
*
|
|
155
|
+
* **Statements stand alone on their line**; the line disappears completely in the build, so a table stays in one piece. Inside a quote or callout, `>` may precede it.
|
|
156
|
+
* **`(role, key)`** also provides the entry's name (`application_owner`); `for role in …` is enough when it is not needed.
|
|
157
|
+
* **Loops can be nested.** A loop's name is valid only inside it; a name must not hide an entry under `document` (`author`, `custom` …).
|
|
158
|
+
* In code and code blocks statements stay as written; in running text, put a backslash in front: `\{% … %}`. Errors such as a missing `{% endfor %}` stop the build and name file and line.
|
|
118
159
|
|
|
119
|
-
|
|
160
|
+
## Values from Other Files (`!file`)
|
|
120
161
|
|
|
121
|
-
|
|
162
|
+
Instead of writing a value directly, `!file` loads it from a file or a URL. Both can be mixed freely:
|
|
122
163
|
|
|
123
|
-
|
|
164
|
+
```yaml
|
|
165
|
+
document:
|
|
166
|
+
custom:
|
|
167
|
+
a: "xxx"
|
|
168
|
+
procedure: !file "../shared/procedure.yaml"
|
|
169
|
+
purpose: !file "purpose.txt"
|
|
170
|
+
operator: !file "https://intranet.example/operator.yaml"
|
|
171
|
+
```
|
|
124
172
|
|
|
125
|
-
*
|
|
173
|
+
* **YAML files** (`.yaml`, `.yml`) put their content in place of the reference, so `{{custom.procedure.name}}` reaches into the loaded file. **Any other file** supplies its text as a simple value.
|
|
174
|
+
* **Relative paths** are relative to the file containing the `!file`, or to the URL it was loaded from.
|
|
175
|
+
* **Nested:** a loaded YAML file may itself contain `!file`. The same file may appear in several places; a reference leading back to itself through the chain stops the build.
|
|
176
|
+
* `!file` works anywhere in the configuration, not only under `custom`. A URL request waits at most 10 seconds.
|
|
126
177
|
|
|
127
178
|
---
|
|
128
179
|
|
|
@@ -19,7 +19,7 @@ The `document:` section defines global metadata, layout switches, table of conte
|
|
|
19
19
|
| `date` | String | `"auto"` | Document date. Set to `"auto"` or `"today"` to use current date. |
|
|
20
20
|
| `version` | String | `None` | Version identifier (e.g., `"2.0.0"`). Only printed when explicitly set. |
|
|
21
21
|
| `language` | String | *(System language)* | ISO language code (e.g., `"en"` or `"de"`). Governs hyphenation and UI strings. |
|
|
22
|
-
| `custom` | Mapping | `{}` | Own values for placeholders in the text (`{{custom.a.b}}`), may be nested. See *Placeholders in the Text*. |
|
|
22
|
+
| `custom` | Mapping | `{}` | Own values for placeholders in the text (`{{custom.a.b}}`), may be nested. See *Placeholders in the Text*. Single values or groups can be loaded with `!file`. |
|
|
23
23
|
| `cover` | Boolean | `false` | Controls whether a styled cover page is generated. |
|
|
24
24
|
| `header` | Boolean | `true` | Enables or disables running headers throughout the document. |
|
|
25
25
|
| `footer` | Boolean | `true` | Enables or disables running footers (including page numbering). |
|
|
@@ -6,7 +6,7 @@ document:
|
|
|
6
6
|
summary: "Practical guide and reference for configuration, CLI usage, theme design, and document publishing with markpublish."
|
|
7
7
|
author: "Frank Winter"
|
|
8
8
|
copyright: "© 2026 Frank Winter"
|
|
9
|
-
version: "2.3.
|
|
9
|
+
version: "2.3.2"
|
|
10
10
|
date: "auto"
|
|
11
11
|
language: "en"
|
|
12
12
|
|
|
@@ -167,8 +167,18 @@ err.config.toc_bool: "{key_path} nimmt keine Wahrheitswerte. Schreiben Sie 'none
|
|
|
167
167
|
err.config.toc_value: "{key_path} kennt {allowed} oder eine Tiefe als Zahl (war: {value})."
|
|
168
168
|
err.config.custom_value: "{key_path} muss ein einfacher Wert (Text, Zahl) oder eine Gruppe weiterer Werte sein."
|
|
169
169
|
err.config.custom_key: "{key_path}: Namen unter custom bestehen nur aus Buchstaben, Ziffern, '_' und '-'."
|
|
170
|
+
err.config.file_ref_missing: "!file \"{src}\": Datei nicht gefunden ({path})."
|
|
171
|
+
err.config.file_ref_cycle: "!file \"{src}\": Zyklischer Verweis, {path} lädt sich über diese Kette selbst nach."
|
|
172
|
+
err.config.file_ref_url: "!file \"{src}\": Abruf fehlgeschlagen ({reason})."
|
|
170
173
|
err.variable.unknown: "Platzhalter {{{{{name}}}}} in {file} hat keinen Wert. Verfügbar sind die Angaben unter document (z. B. {{{{author}}}}) und custom.* ."
|
|
171
174
|
err.variable.document_field: "Platzhalter {{{{{name}}}}} in {file} hat keinen Wert. In den Angaben unter document sind nur Werte aus custom.* erlaubt (z. B. {{{{custom.verfahren.name}}}})."
|
|
175
|
+
err.statement.syntax: "{file}, Zeile {line}: {{% {text} %}} ist keine bekannte Anweisung. Möglich sind {{% for name in custom.a %}}, {{% for (name, key) in custom.a %}}, {{% endfor %}} und {{% set name = custom.a.b %}}."
|
|
176
|
+
err.statement.inline: "{file}, Zeile {line}: Eine Anweisung {{% … %}} muss allein in ihrer Zeile stehen. Als Text schreiben Sie \\{{% … %}}."
|
|
177
|
+
err.statement.unclosed: "{file}, Zeile {line}: Zu diesem {{% for %}} fehlt das {{% endfor %}}."
|
|
178
|
+
err.statement.stray_end: "{file}, Zeile {line}: {{% endfor %}} ohne vorangehendes {{% for %}}."
|
|
179
|
+
err.statement.unknown_path: "{file}, Zeile {line}: {path} hat keinen Wert."
|
|
180
|
+
err.statement.not_group: "{file}, Zeile {line}: {path} ist ein einzelner Wert, keine Gruppe; {{% for %}} kann nicht darüber laufen."
|
|
181
|
+
err.statement.reserved: "{file}, Zeile {line}: Der Name {name} ist schon eine Angabe unter document. Wählen Sie einen anderen Namen."
|
|
172
182
|
err.config.toc_depth: "{key_path} braucht eine Tiefe ab 1 (war: {value}). Für 'kommt nicht vor' schreiben Sie 'none'."
|
|
173
183
|
err.config.toc_kind: "{key_path} kennt 'none', 'full' oder eine Tiefe als Zahl (war: {value})."
|
|
174
184
|
err.config.document_unknown: |
|
|
@@ -170,8 +170,18 @@ err.config.toc_bool: "{key_path} takes no boolean. Write 'none', 'full' or a dep
|
|
|
170
170
|
err.config.toc_value: "{key_path} takes {allowed} or a depth as a number (was: {value})."
|
|
171
171
|
err.config.custom_value: "{key_path} must be a simple value (text, number) or a group of further values."
|
|
172
172
|
err.config.custom_key: "{key_path}: names under custom consist only of letters, digits, '_' and '-'."
|
|
173
|
+
err.config.file_ref_missing: "!file \"{src}\": file not found ({path})."
|
|
174
|
+
err.config.file_ref_cycle: "!file \"{src}\": circular reference, {path} loads itself through this chain."
|
|
175
|
+
err.config.file_ref_url: "!file \"{src}\": retrieval failed ({reason})."
|
|
173
176
|
err.variable.unknown: "Placeholder {{{{{name}}}}} in {file} has no value. Available are the entries under document (e.g. {{{{author}}}}) and custom.* ."
|
|
174
177
|
err.variable.document_field: "Placeholder {{{{{name}}}}} in {file} has no value. The entries under document may only use values from custom.* (e.g. {{{{custom.verfahren.name}}}})."
|
|
178
|
+
err.statement.syntax: "{file}, line {line}: {{% {text} %}} is not a known statement. Available are {{% for name in custom.a %}}, {{% for (name, key) in custom.a %}}, {{% endfor %}} and {{% set name = custom.a.b %}}."
|
|
179
|
+
err.statement.inline: "{file}, line {line}: a statement {{% … %}} must stand alone on its line. To show it as text, write \\{{% … %}}."
|
|
180
|
+
err.statement.unclosed: "{file}, line {line}: this {{% for %}} has no {{% endfor %}}."
|
|
181
|
+
err.statement.stray_end: "{file}, line {line}: {{% endfor %}} without a preceding {{% for %}}."
|
|
182
|
+
err.statement.unknown_path: "{file}, line {line}: {path} has no value."
|
|
183
|
+
err.statement.not_group: "{file}, line {line}: {path} is a single value, not a group; {{% for %}} cannot loop over it."
|
|
184
|
+
err.statement.reserved: "{file}, line {line}: the name {name} is already an entry under document. Choose another name."
|
|
175
185
|
err.config.toc_depth: "{key_path} needs a depth of at least 1 (was: {value}). For 'does not appear' write 'none'."
|
|
176
186
|
err.config.toc_kind: "{key_path} takes 'none', 'full' or a depth as a number (was: {value})."
|
|
177
187
|
err.config.document_unknown: |
|
|
@@ -34,7 +34,12 @@ from markpublish.markdown.typst_serializer import (
|
|
|
34
34
|
html_to_tree,
|
|
35
35
|
process_tree_headings_and_toc,
|
|
36
36
|
)
|
|
37
|
-
from markpublish.markdown.variables import
|
|
37
|
+
from markpublish.markdown.variables import (
|
|
38
|
+
build_variables,
|
|
39
|
+
expand_statements,
|
|
40
|
+
protect_escapes,
|
|
41
|
+
substitute_tree,
|
|
42
|
+
)
|
|
38
43
|
from markpublish.ui import t
|
|
39
44
|
|
|
40
45
|
#: Namen der String-Extensions. Der GitHubAlertsExtension wird pro Sprache
|
|
@@ -555,6 +560,7 @@ class MarkdownPipeline:
|
|
|
555
560
|
# Convert markdown to HTML via python-markdown (all extensions active)
|
|
556
561
|
if raw_md and doc_cfg:
|
|
557
562
|
raw_md = protect_escapes(raw_md)
|
|
563
|
+
raw_md = expand_statements(raw_md, build_variables(doc_cfg), chapter_cfg.file or "")
|
|
558
564
|
raw_html = self.engine.convert(raw_md) if raw_md else ""
|
|
559
565
|
|
|
560
566
|
# Parse HTML into ElementTree and process headings / numbering directly in the AST
|