uEdition 1.3.2__py3-none-any.whl → 2.0.0a2__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.
Potentially problematic release.
This version of uEdition might be problematic. Click here for more details.
- uedition/__about__.py +2 -1
- uedition/cli/__init__.py +0 -7
- uedition/cli/build.py +75 -68
- uedition/cli/language.py +6 -0
- uedition/cli/serve.py +3 -1
- uedition/cli/update.py +6 -0
- uedition/ext/config.py +9 -175
- uedition/ext/settings.py +131 -0
- uedition/ext/tei/builder.py +2 -3
- uedition/ext/tei/parser.py +90 -104
- uedition/settings.py +16 -7
- {uedition-1.3.2.dist-info → uedition-2.0.0a2.dist-info}/METADATA +9 -4
- uedition-2.0.0a2.dist-info/RECORD +25 -0
- {uedition-1.3.2.dist-info → uedition-2.0.0a2.dist-info}/WHEEL +1 -1
- uedition/cli/check.py +0 -183
- uedition-1.3.2.dist-info/RECORD +0 -25
- {uedition-1.3.2.dist-info → uedition-2.0.0a2.dist-info}/entry_points.txt +0 -0
- {uedition-1.3.2.dist-info → uedition-2.0.0a2.dist-info}/licenses/LICENSE.txt +0 -0
uedition/__about__.py
CHANGED
uedition/cli/__init__.py
CHANGED
|
@@ -7,7 +7,6 @@ from rich import print as print_cli
|
|
|
7
7
|
|
|
8
8
|
from uedition.__about__ import __version__
|
|
9
9
|
from uedition.cli import build as build_module
|
|
10
|
-
from uedition.cli import check as check_module
|
|
11
10
|
from uedition.cli import create as create_module
|
|
12
11
|
from uedition.cli import language as language_module
|
|
13
12
|
from uedition.cli import serve as serve_module
|
|
@@ -37,12 +36,6 @@ def serve() -> None:
|
|
|
37
36
|
serve_module.run()
|
|
38
37
|
|
|
39
38
|
|
|
40
|
-
@app.command()
|
|
41
|
-
def check() -> None:
|
|
42
|
-
"""Check that the μEdition is set up correctly."""
|
|
43
|
-
check_module.run()
|
|
44
|
-
|
|
45
|
-
|
|
46
39
|
@app.command()
|
|
47
40
|
def update() -> None:
|
|
48
41
|
"""Update the μEdition."""
|
uedition/cli/build.py
CHANGED
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
#
|
|
3
3
|
# SPDX-License-Identifier: MIT
|
|
4
4
|
"""Build functionality."""
|
|
5
|
+
|
|
5
6
|
import json
|
|
6
7
|
import subprocess
|
|
7
|
-
from copy import deepcopy
|
|
8
8
|
from os import makedirs, path
|
|
9
9
|
from shutil import copytree, ignore_patterns, rmtree
|
|
10
10
|
|
|
11
11
|
from yaml import safe_dump, safe_load
|
|
12
12
|
|
|
13
|
-
from uedition.settings import reload_settings, settings
|
|
13
|
+
from uedition.settings import NoConfigError, reload_settings, settings
|
|
14
14
|
|
|
15
15
|
LANDING_PAGE_TEMPLATE = """\
|
|
16
16
|
<!DOCTYPE html>
|
|
@@ -103,41 +103,50 @@ def toc_build(lang: dict) -> None:
|
|
|
103
103
|
|
|
104
104
|
|
|
105
105
|
def config_build(lang: dict) -> None:
|
|
106
|
-
"""Build the language-specific
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
#
|
|
106
|
+
"""Build the language-specific Sphinx config based on the main config."""
|
|
107
|
+
with open("toc.yml") as in_f:
|
|
108
|
+
toc = safe_load(in_f)
|
|
109
|
+
# Build the default configuration
|
|
110
|
+
config = {
|
|
111
|
+
"needs_sphinx": "8",
|
|
112
|
+
"language": lang["code"],
|
|
113
|
+
"root_doc": toc["root"],
|
|
114
|
+
"html_theme": "sphinx_book_theme",
|
|
115
|
+
"html_theme_options": {},
|
|
116
|
+
"extensions": ["myst_parser", "sphinx_external_toc", "uedition"],
|
|
117
|
+
"myst_enable_extensions": [
|
|
118
|
+
"amsmath",
|
|
119
|
+
"attrs_inline",
|
|
120
|
+
"colon_fence",
|
|
121
|
+
"deflist",
|
|
122
|
+
"dollarmath",
|
|
123
|
+
"fieldlist",
|
|
124
|
+
"html_admonition",
|
|
125
|
+
"html_image",
|
|
126
|
+
"replacements",
|
|
127
|
+
"smartquotes",
|
|
128
|
+
"strikethrough",
|
|
129
|
+
"substitution",
|
|
130
|
+
"tasklist",
|
|
131
|
+
],
|
|
132
|
+
}
|
|
133
|
+
# Load in any sphinx configuration
|
|
134
|
+
config.update(settings["sphinx_config"])
|
|
135
|
+
# Set settings-based
|
|
110
136
|
if lang["code"] in settings["title"]:
|
|
111
|
-
config["
|
|
137
|
+
config["project"] = settings["title"][lang["code"]]
|
|
112
138
|
elif len(settings["languages"]) > 0 and settings["languages"][0]["code"] in settings["title"]:
|
|
113
|
-
config["
|
|
139
|
+
config["project"] = settings["title"][settings["languages"][0]["code"]]
|
|
114
140
|
else:
|
|
115
|
-
config["
|
|
116
|
-
|
|
117
|
-
if settings["repository"]["url"]
|
|
118
|
-
config["
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
config["html"] = {}
|
|
125
|
-
if "use_repository_button" not in config["html"]:
|
|
126
|
-
config["html"]["use_repository_button"] = True
|
|
127
|
-
# Set the Sphinx language
|
|
128
|
-
if "sphinx" not in config:
|
|
129
|
-
config["sphinx"] = {}
|
|
130
|
-
if "config" not in config["sphinx"]:
|
|
131
|
-
config["sphinx"]["config"] = {}
|
|
132
|
-
config["sphinx"]["config"]["language"] = lang["code"]
|
|
133
|
-
# Add the uEdition extension
|
|
134
|
-
if "extra_extensions" not in config["sphinx"]:
|
|
135
|
-
config["sphinx"]["extra_extensions"] = []
|
|
136
|
-
if "uedition" not in config["sphinx"]["extra_extensions"]:
|
|
137
|
-
config["sphinx"]["extra_extensions"].append("uedition")
|
|
138
|
-
|
|
139
|
-
with open(path.join(lang["path"], "_config.yml"), "w") as out_f:
|
|
140
|
-
safe_dump(config, out_f, encoding="utf-8")
|
|
141
|
+
config["project"] = f"Missing title for {lang['label']}"
|
|
142
|
+
config["author"] = settings["author"]["name"]
|
|
143
|
+
if settings["repository"]["url"]:
|
|
144
|
+
config["html_theme_options"]["repository_url"] = f"{settings['repository']['url']}"
|
|
145
|
+
config["html_theme_options"]["use_repository_button"] = True
|
|
146
|
+
|
|
147
|
+
with open(path.join(lang["path"], "conf.py"), "w") as out_f:
|
|
148
|
+
for name, value in config.items():
|
|
149
|
+
out_f.write(f"{name} = {value!a}\n")
|
|
141
150
|
|
|
142
151
|
|
|
143
152
|
def static_build(lang: dict) -> None:
|
|
@@ -153,41 +162,39 @@ def full_build(lang: dict) -> None:
|
|
|
153
162
|
toc_build(lang)
|
|
154
163
|
config_build(lang)
|
|
155
164
|
static_build(lang)
|
|
156
|
-
subprocess.run(
|
|
157
|
-
[ # noqa:
|
|
158
|
-
"
|
|
159
|
-
"
|
|
160
|
-
"
|
|
161
|
-
"--
|
|
162
|
-
path.join("_build", lang["path"]),
|
|
165
|
+
subprocess.run( # noqa:S603
|
|
166
|
+
[ # noqa: S607
|
|
167
|
+
"sphinx-build",
|
|
168
|
+
"--builder",
|
|
169
|
+
"html",
|
|
170
|
+
"--fresh-env",
|
|
163
171
|
lang["path"],
|
|
172
|
+
path.join("_build", lang["path"], "html"),
|
|
164
173
|
],
|
|
165
174
|
check=False,
|
|
175
|
+
shell=False,
|
|
166
176
|
)
|
|
167
177
|
if settings["output"]["tei"]:
|
|
168
|
-
subprocess.run(
|
|
169
|
-
[ # noqa:
|
|
170
|
-
"
|
|
171
|
-
"build",
|
|
172
|
-
"--all",
|
|
173
|
-
"--path-output",
|
|
174
|
-
path.join("_build", lang["path"]),
|
|
178
|
+
subprocess.run( # noqa: S603
|
|
179
|
+
[ # noqa:S607
|
|
180
|
+
"sphinx-build",
|
|
175
181
|
"--builder",
|
|
176
|
-
"custom",
|
|
177
|
-
"--custom-builder",
|
|
178
182
|
"tei",
|
|
183
|
+
"--fresh-env",
|
|
179
184
|
lang["path"],
|
|
185
|
+
path.join("_build", lang["path"], "tei"),
|
|
180
186
|
],
|
|
181
187
|
check=False,
|
|
188
|
+
shell=False,
|
|
182
189
|
)
|
|
183
190
|
copytree(
|
|
184
|
-
path.join("_build", lang["path"], "
|
|
191
|
+
path.join("_build", lang["path"], "html"),
|
|
185
192
|
path.join(settings["output"]["path"], lang["path"]),
|
|
186
193
|
dirs_exist_ok=True,
|
|
187
194
|
)
|
|
188
195
|
if settings["output"]["tei"]:
|
|
189
196
|
copytree(
|
|
190
|
-
path.join("_build", lang["path"], "
|
|
197
|
+
path.join("_build", lang["path"], "tei"),
|
|
191
198
|
path.join(settings["output"]["path"], lang["path"]),
|
|
192
199
|
ignore=ignore_patterns("_sphinx_design_static"),
|
|
193
200
|
dirs_exist_ok=True,
|
|
@@ -197,39 +204,37 @@ def full_build(lang: dict) -> None:
|
|
|
197
204
|
def partial_build(lang: dict) -> None:
|
|
198
205
|
"""Run the as-needed build process for a single language."""
|
|
199
206
|
landing_build()
|
|
200
|
-
subprocess.run(
|
|
201
|
-
[ # noqa:
|
|
202
|
-
"
|
|
203
|
-
"
|
|
204
|
-
"
|
|
205
|
-
path.join("_build", lang["path"]),
|
|
207
|
+
subprocess.run( # noqa: S603
|
|
208
|
+
[ # noqa: S607
|
|
209
|
+
"sphinx-build",
|
|
210
|
+
"--builder",
|
|
211
|
+
"html",
|
|
206
212
|
lang["path"],
|
|
213
|
+
path.join("_build", lang["path"], "html"),
|
|
207
214
|
],
|
|
208
215
|
check=False,
|
|
216
|
+
shell=False,
|
|
209
217
|
)
|
|
210
218
|
if settings["output"]["tei"]:
|
|
211
|
-
subprocess.run(
|
|
212
|
-
[ # noqa:
|
|
213
|
-
"
|
|
214
|
-
"build",
|
|
215
|
-
"--path-output",
|
|
216
|
-
path.join("_build", lang["path"]),
|
|
219
|
+
subprocess.run( # noqa:S603
|
|
220
|
+
[ # noqa: S607
|
|
221
|
+
"sphinx-build",
|
|
217
222
|
"--builder",
|
|
218
|
-
"custom",
|
|
219
|
-
"--custom-builder",
|
|
220
223
|
"tei",
|
|
221
224
|
lang["path"],
|
|
225
|
+
path.join("_build", lang["path"], "tei"),
|
|
222
226
|
],
|
|
223
227
|
check=False,
|
|
228
|
+
shell=False,
|
|
224
229
|
)
|
|
225
230
|
copytree(
|
|
226
|
-
path.join("_build", lang["path"], "
|
|
231
|
+
path.join("_build", lang["path"], "html"),
|
|
227
232
|
path.join(settings["output"]["path"], lang["path"]),
|
|
228
233
|
dirs_exist_ok=True,
|
|
229
234
|
)
|
|
230
235
|
if settings["output"]["tei"]:
|
|
231
236
|
copytree(
|
|
232
|
-
path.join("_build", lang["path"], "
|
|
237
|
+
path.join("_build", lang["path"], "tei"),
|
|
233
238
|
path.join(settings["output"]["path"], lang["path"]),
|
|
234
239
|
ignore=ignore_patterns("_sphinx_design_static"),
|
|
235
240
|
dirs_exist_ok=True,
|
|
@@ -238,6 +243,8 @@ def partial_build(lang: dict) -> None:
|
|
|
238
243
|
|
|
239
244
|
def run() -> None:
|
|
240
245
|
"""Build the full uEdition."""
|
|
246
|
+
if not path.exists("uEdition.yml") and not path.exists("uEdition.yaml"):
|
|
247
|
+
raise NoConfigError()
|
|
241
248
|
if path.exists(settings["output"]["path"]):
|
|
242
249
|
rmtree(settings["output"]["path"])
|
|
243
250
|
for lang in settings["languages"]:
|
uedition/cli/language.py
CHANGED
|
@@ -7,9 +7,13 @@ import os
|
|
|
7
7
|
from copier import run_copy, run_update
|
|
8
8
|
from yaml import dump, safe_load
|
|
9
9
|
|
|
10
|
+
from uedition.settings import NoConfigError
|
|
11
|
+
|
|
10
12
|
|
|
11
13
|
def add(path: str) -> None:
|
|
12
14
|
"""Add a language to the μEdition using Copier."""
|
|
15
|
+
if not os.path.exists("uEdition.yml") and not os.path.exists("uEdition.yaml"):
|
|
16
|
+
raise NoConfigError()
|
|
13
17
|
run_copy("gh:uEdition/uEdition-language-template", path, data={"path": path})
|
|
14
18
|
with open(os.path.join(path, ".uEdition.answers")) as in_f:
|
|
15
19
|
answers = safe_load(in_f)
|
|
@@ -32,6 +36,8 @@ def add(path: str) -> None:
|
|
|
32
36
|
|
|
33
37
|
def update(path: str) -> None:
|
|
34
38
|
"""Update a language to the latest template."""
|
|
39
|
+
if not os.path.exists("uEdition.yml") and not os.path.exists("uEdition.yaml"):
|
|
40
|
+
raise NoConfigError()
|
|
35
41
|
run_update(path, answers_file=".uEdition.answers", overwrite=True, data={"path": path})
|
|
36
42
|
with open(os.path.join(path, ".uEdition.answers")) as in_f:
|
|
37
43
|
answers = safe_load(in_f)
|
uedition/cli/serve.py
CHANGED
|
@@ -8,7 +8,7 @@ from typing import Callable
|
|
|
8
8
|
from livereload import Server
|
|
9
9
|
|
|
10
10
|
from uedition.cli.build import full_build, partial_build
|
|
11
|
-
from uedition.settings import settings
|
|
11
|
+
from uedition.settings import NoConfigError, settings
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
def build_cmd(lang: dict, full: bool = True) -> Callable[[], None]: # noqa: FBT001, FBT002
|
|
@@ -29,6 +29,8 @@ def build_cmd(lang: dict, full: bool = True) -> Callable[[], None]: # noqa: FBT
|
|
|
29
29
|
|
|
30
30
|
def run() -> None:
|
|
31
31
|
"""Run the development server."""
|
|
32
|
+
if not path.exists("uEdition.yml") and not path.exists("uEdition.yaml"):
|
|
33
|
+
raise NoConfigError()
|
|
32
34
|
full_rebuilds = [build_cmd(lang, full=True) for lang in settings["languages"]]
|
|
33
35
|
partial_rebuilds = [build_cmd(lang, full=False) for lang in settings["languages"]]
|
|
34
36
|
for cmd in full_rebuilds:
|
uedition/cli/update.py
CHANGED
|
@@ -2,9 +2,15 @@
|
|
|
2
2
|
#
|
|
3
3
|
# SPDX-License-Identifier: MIT
|
|
4
4
|
"""The μEdition check functionality for validating a μEdition and its files."""
|
|
5
|
+
from os import path
|
|
6
|
+
|
|
5
7
|
from copier import run_update
|
|
6
8
|
|
|
9
|
+
from uedition.settings import NoConfigError
|
|
10
|
+
|
|
7
11
|
|
|
8
12
|
def run() -> None:
|
|
9
13
|
"""Update the μEdition using Copier."""
|
|
14
|
+
if not path.exists("uEdition.yml") and not path.exists("uEdition.yaml"):
|
|
15
|
+
raise NoConfigError()
|
|
10
16
|
run_update(".", answers_file=".uEdition.answers", overwrite=True, skip_answered=True)
|
uedition/ext/config.py
CHANGED
|
@@ -1,197 +1,31 @@
|
|
|
1
1
|
# SPDX-FileCopyrightText: 2023-present Mark Hall <mark.hall@work.room3b.eu>
|
|
2
2
|
#
|
|
3
3
|
# SPDX-License-Identifier: MIT
|
|
4
|
-
"""
|
|
4
|
+
"""
|
|
5
|
+
uEdition configuration handling.
|
|
5
6
|
|
|
6
7
|
This module handles reading the uEdition-specific configuration settings, validating them and
|
|
7
8
|
adding any required default values.
|
|
8
9
|
"""
|
|
9
|
-
from typing import Annotated, Any, Literal, Optional, Union
|
|
10
10
|
|
|
11
|
-
from pydantic import BaseModel, ValidationError
|
|
12
|
-
from pydantic.functional_validators import BeforeValidator
|
|
13
11
|
from sphinx.application import Sphinx
|
|
12
|
+
from sphinx.config import Config
|
|
14
13
|
from sphinx.util import logging
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class RuleSelectorAttribute(BaseModel):
|
|
20
|
-
"""Validation rule for selecting based on an attribute with a given value."""
|
|
21
|
-
|
|
22
|
-
attr: str
|
|
23
|
-
value: str
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def expand_tei_namespace(value: str) -> str:
|
|
27
|
-
"""Expand any ```tei:``` namespace prefixes."""
|
|
28
|
-
return value.replace("tei:", "{http://www.tei-c.org/ns/1.0}")
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def force_list(value: Any) -> list: # noqa: ANN401
|
|
32
|
-
"""Force the value into a list form."""
|
|
33
|
-
if isinstance(value, list):
|
|
34
|
-
return value
|
|
35
|
-
return [value]
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
class RuleSelector(BaseModel):
|
|
39
|
-
"""Validation rule for the selector for matching a TEI tag."""
|
|
40
|
-
|
|
41
|
-
tag: Annotated[str, BeforeValidator(expand_tei_namespace)]
|
|
42
|
-
attributes: Annotated[list[RuleSelectorAttribute], BeforeValidator(force_list)] = []
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
class RuleText(BaseModel):
|
|
46
|
-
"""Validation rule for retrieving the text content from an attribute."""
|
|
47
|
-
|
|
48
|
-
action: Literal["from-attribute"]
|
|
49
|
-
attr: str
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
class RuleAttributeCopy(BaseModel):
|
|
53
|
-
"""Validation rule for copying and attribute."""
|
|
54
|
-
|
|
55
|
-
action: Literal["copy"] = "copy"
|
|
56
|
-
attr: str
|
|
57
|
-
source: str
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
class RuleAttributeSet(BaseModel):
|
|
61
|
-
"""Validation rule for settings an attribute to a specific value."""
|
|
62
|
-
|
|
63
|
-
action: Literal["set"]
|
|
64
|
-
attr: str
|
|
65
|
-
value: str
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
class RuleAttributeDelete(BaseModel):
|
|
69
|
-
"""Validation rule for deleting an attribute."""
|
|
70
|
-
|
|
71
|
-
action: Literal["delete"]
|
|
72
|
-
attr: str
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def convert_string_to_selector_dict(value: str | dict) -> dict:
|
|
76
|
-
"""Convert a simple string selector into the dictionary representation."""
|
|
77
|
-
if isinstance(value, str):
|
|
78
|
-
return {"tag": value}
|
|
79
|
-
return value
|
|
15
|
+
from uedition.ext.settings import TEISettings
|
|
80
16
|
|
|
81
|
-
|
|
82
|
-
class Rule(BaseModel):
|
|
83
|
-
"""Validation model for a rule transforming a TEI tag into a HTML tag."""
|
|
84
|
-
|
|
85
|
-
selector: Annotated[RuleSelector, BeforeValidator(convert_string_to_selector_dict)]
|
|
86
|
-
tag: Union[str, None] = "div"
|
|
87
|
-
text: Union[RuleText, None] = None
|
|
88
|
-
attributes: list[Union[RuleAttributeCopy, RuleAttributeSet, RuleAttributeDelete]] = []
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
class TextSection(BaseModel):
|
|
92
|
-
"""Validation model for a TEI text section."""
|
|
93
|
-
|
|
94
|
-
title: str
|
|
95
|
-
type: Literal["text"] = "text"
|
|
96
|
-
content: str
|
|
97
|
-
sort: str | None = None
|
|
98
|
-
mappings: list[Rule] = []
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
class SingleFieldRule(BaseModel):
|
|
102
|
-
"""Validation model for a TEI field rule with a single value."""
|
|
103
|
-
|
|
104
|
-
title: str
|
|
105
|
-
type: Literal["single"] = "single"
|
|
106
|
-
content: str
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
class ListFieldRule(BaseModel):
|
|
110
|
-
"""Validation model for a TEI field rule with a list of values."""
|
|
111
|
-
|
|
112
|
-
title: str
|
|
113
|
-
type: Literal["list"]
|
|
114
|
-
content: str
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
class DownloadFieldRule(BaseModel):
|
|
118
|
-
"""Validation model for a TEI field rule that creates a download link."""
|
|
119
|
-
|
|
120
|
-
title: str
|
|
121
|
-
type: Literal["download"]
|
|
122
|
-
content: str
|
|
123
|
-
target: str
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
class FieldsSection(BaseModel):
|
|
127
|
-
"""Validation model for a TEI fields section."""
|
|
128
|
-
|
|
129
|
-
title: str
|
|
130
|
-
type: Literal["fields"]
|
|
131
|
-
fields: list[SingleFieldRule | ListFieldRule | DownloadFieldRule]
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
class TEIConfig(BaseModel):
|
|
135
|
-
"""Validation model for the TEI-specific settings."""
|
|
136
|
-
|
|
137
|
-
text_only_in_leaf_nodes: bool = False
|
|
138
|
-
mappings: list[Rule] = []
|
|
139
|
-
sections: list[TextSection | FieldsSection] = []
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
class Config(BaseModel):
|
|
143
|
-
"""Configuration validation model."""
|
|
144
|
-
|
|
145
|
-
tei: Optional[TEIConfig]
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
BASE_RULES = [
|
|
149
|
-
{"selector": "tei:body", "tag": "section"},
|
|
150
|
-
{"selector": "tei:head", "tag": "h1"},
|
|
151
|
-
{"selector": "tei:p", "tag": "p"},
|
|
152
|
-
{"selector": "tei:seg", "tag": "span"},
|
|
153
|
-
{"selector": "tei:pb", "tag": "span"},
|
|
154
|
-
{"selector": "tei:hi", "tag": "span"},
|
|
155
|
-
{
|
|
156
|
-
"selector": "tei:ref",
|
|
157
|
-
"tag": "a",
|
|
158
|
-
"attributes": [{"attr": "href", "source": "target"}],
|
|
159
|
-
},
|
|
160
|
-
{"selector": "tei:citedRange", "tag": "span"},
|
|
161
|
-
{"selector": "tei:q", "tag": "span"},
|
|
162
|
-
{"selector": "tei:hi", "tag": "span"},
|
|
163
|
-
{"selector": "tei:foreign", "tag": "span"},
|
|
164
|
-
{"selector": "tei:speaker", "tag": "span"},
|
|
165
|
-
{"selector": "tei:stage", "tag": "span"},
|
|
166
|
-
{"selector": "tei:lem", "tag": "span"},
|
|
167
|
-
{"selector": "tei:sic", "tag": "span"},
|
|
168
|
-
]
|
|
169
|
-
"""Base mapping rules for mapping TEI tags to default HTML elements."""
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
170
18
|
|
|
171
19
|
|
|
172
20
|
def validate_config(app: Sphinx, config: Config) -> None: # noqa: ARG001
|
|
173
21
|
"""Validate the configuration and add any default values."""
|
|
174
|
-
if config.
|
|
175
|
-
|
|
176
|
-
if "sections" in config.uEdition["tei"] and isinstance(config.uEdition["tei"]["sections"], list):
|
|
177
|
-
if "mappings" in config.uEdition["tei"] and isinstance(config.uEdition["tei"]["mappings"], list):
|
|
178
|
-
for section in config.uEdition["tei"]["sections"]:
|
|
179
|
-
if "mappings" in section and isinstance(section["mappings"], list):
|
|
180
|
-
section["mappings"] = section["mappings"] + config.uEdition["tei"]["mappings"] + BASE_RULES
|
|
181
|
-
else:
|
|
182
|
-
section["mappings"] = config.uEdition["tei"]["mappings"] + BASE_RULES
|
|
183
|
-
try:
|
|
184
|
-
config.uEdition = Config(**config.uEdition).dict()
|
|
185
|
-
except ValidationError as e:
|
|
186
|
-
for error in e.errors():
|
|
187
|
-
logger.error(" -> ".join([str(loc) for loc in error["loc"]]))
|
|
188
|
-
logger.error(f' {error["msg"]}')
|
|
189
|
-
config.uEdition = {}
|
|
22
|
+
if config.tei:
|
|
23
|
+
config.tei = TEISettings(**config.tei).model_dump()
|
|
190
24
|
else:
|
|
191
|
-
config.
|
|
25
|
+
config.tei = {"blocks": [], "marks": "", "sections": []}
|
|
192
26
|
|
|
193
27
|
|
|
194
28
|
def setup(app: Sphinx) -> None:
|
|
195
29
|
"""Set up the Sphinx configuration handling for the uEdition."""
|
|
196
|
-
app.add_config_value("
|
|
30
|
+
app.add_config_value("tei", default=None, rebuild="html", types=[dict])
|
|
197
31
|
app.connect("config-inited", validate_config)
|
uedition/ext/settings.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2023-present Mark Hall <mark.hall@work.room3b.eu>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: MIT
|
|
4
|
+
"""Additional settings validation."""
|
|
5
|
+
|
|
6
|
+
from typing import Literal
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ValueTitlePair(BaseModel):
|
|
12
|
+
"""A simple pair of value and title."""
|
|
13
|
+
|
|
14
|
+
value: str
|
|
15
|
+
"""The value for the pair."""
|
|
16
|
+
title: str
|
|
17
|
+
"""The title to show for the pair."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TEINodeAttribute(BaseModel):
|
|
21
|
+
"""Single attribute for a TEINode."""
|
|
22
|
+
|
|
23
|
+
name: str
|
|
24
|
+
"""The name of the attribute."""
|
|
25
|
+
value: str | None = None
|
|
26
|
+
"""A fixed value to use for the attribute."""
|
|
27
|
+
type: Literal["string"] | Literal["static"] | Literal["id-ref"] | Literal["text"] | Literal["html-attribute"] = (
|
|
28
|
+
"string"
|
|
29
|
+
)
|
|
30
|
+
"""The type of attribute this is."""
|
|
31
|
+
default: str = ""
|
|
32
|
+
"""The default value to use if none is set."""
|
|
33
|
+
target: str | None = None
|
|
34
|
+
"""The target HTML attribute."""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class TEINode(BaseModel):
|
|
38
|
+
"""A single node in a TEI document."""
|
|
39
|
+
|
|
40
|
+
name: str
|
|
41
|
+
"""The name to use to address this node."""
|
|
42
|
+
selector: str
|
|
43
|
+
"""The selector to identify this node."""
|
|
44
|
+
attributes: list[TEINodeAttribute] = []
|
|
45
|
+
"""A list of attributes that are used on this node."""
|
|
46
|
+
tag: str | None = None
|
|
47
|
+
"""The HTML tag to use to render the node."""
|
|
48
|
+
text: str | None = None
|
|
49
|
+
"""Where to get the text from."""
|
|
50
|
+
content: str | None = None
|
|
51
|
+
"""Allowed child nodes. Only relevant for block nodes."""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class TEIMetadataSectionSingleFieldRule(BaseModel):
|
|
55
|
+
"""Validation model for a TEI field rule with a single value."""
|
|
56
|
+
|
|
57
|
+
title: str
|
|
58
|
+
type: Literal["single"] = "single"
|
|
59
|
+
selector: str
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class TEIMetadataSectionListFieldRule(BaseModel):
|
|
63
|
+
"""Validation model for a TEI field rule with a list of values."""
|
|
64
|
+
|
|
65
|
+
title: str
|
|
66
|
+
type: Literal["list"]
|
|
67
|
+
selector: str
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class TEIMetadataSectionDownloadFieldRule(BaseModel):
|
|
71
|
+
"""Validation model for a TEI field rule that creates a download link."""
|
|
72
|
+
|
|
73
|
+
title: str
|
|
74
|
+
type: Literal["download"]
|
|
75
|
+
selector: str
|
|
76
|
+
target: str
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class TEIMetadataSection(BaseModel):
|
|
80
|
+
"""A metadata section in the TEI document."""
|
|
81
|
+
|
|
82
|
+
name: str
|
|
83
|
+
"""The name of the section."""
|
|
84
|
+
title: str | None = None
|
|
85
|
+
"""The title to show in the UI."""
|
|
86
|
+
type: Literal["metadata"]
|
|
87
|
+
"""The type must be set to metadata."""
|
|
88
|
+
selector: str
|
|
89
|
+
"""The XPath selector to retrieve this section."""
|
|
90
|
+
fields: list[
|
|
91
|
+
TEIMetadataSectionSingleFieldRule | TEIMetadataSectionListFieldRule | TEIMetadataSectionDownloadFieldRule
|
|
92
|
+
]
|
|
93
|
+
"""Fields to display."""
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class TEITextSection(BaseModel):
|
|
97
|
+
"""A section in the TEI document containing a single text."""
|
|
98
|
+
|
|
99
|
+
name: str
|
|
100
|
+
"""The name of the section."""
|
|
101
|
+
title: str | None = None
|
|
102
|
+
"""The title to show in the UI."""
|
|
103
|
+
type: Literal["text"]
|
|
104
|
+
"""The type must be set to text."""
|
|
105
|
+
selector: str
|
|
106
|
+
"""The XPath selector to retrieve this section."""
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class TEITextListSection(BaseModel):
|
|
110
|
+
"""A section in the TEI document containing multiple texts."""
|
|
111
|
+
|
|
112
|
+
name: str
|
|
113
|
+
"""The name of the section."""
|
|
114
|
+
title: str | None = None
|
|
115
|
+
"""The title to show in the UI."""
|
|
116
|
+
type: Literal["textlist"]
|
|
117
|
+
"""The type must be set to textlist."""
|
|
118
|
+
selector: str
|
|
119
|
+
"""The XPath selector to retrieve the texts in this section."""
|
|
120
|
+
sort: str | None = None
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class TEISettings(BaseModel):
|
|
124
|
+
"""Settings for the TEI processing."""
|
|
125
|
+
|
|
126
|
+
blocks: list[TEINode] = []
|
|
127
|
+
"""List of blocks supported in the TEI document."""
|
|
128
|
+
marks: list[TEINode] = []
|
|
129
|
+
"""List of inline marks supported in the TEI document."""
|
|
130
|
+
sections: list[TEIMetadataSection | TEITextSection | TEITextListSection] = []
|
|
131
|
+
"""List of sections within the TEI document."""
|
uedition/ext/tei/builder.py
CHANGED
|
@@ -6,7 +6,6 @@ import xml.sax.saxutils
|
|
|
6
6
|
from collections.abc import Iterator
|
|
7
7
|
|
|
8
8
|
import sphinx
|
|
9
|
-
import sphinx_jupyterbook_latex
|
|
10
9
|
from docutils import nodes
|
|
11
10
|
from docutils.io import StringOutput
|
|
12
11
|
from sphinx.application import Sphinx
|
|
@@ -37,6 +36,7 @@ MAPPINGS = [
|
|
|
37
36
|
"attrs": [{"target": "type", "value": "literal-block"}, {"source": "language", "target": "subtype"}],
|
|
38
37
|
},
|
|
39
38
|
{"cls": nodes.compound, "tagname": "div", "type": "block"},
|
|
39
|
+
{"cls": nodes.admonition, "tagname": "div", "type": "block"},
|
|
40
40
|
{"cls": sphinx.addnodes.toctree},
|
|
41
41
|
{
|
|
42
42
|
"cls": nodes.footnote,
|
|
@@ -47,7 +47,6 @@ MAPPINGS = [
|
|
|
47
47
|
{"target": "target", "source": "backrefs", "format": "#{value}", "join": " "},
|
|
48
48
|
],
|
|
49
49
|
},
|
|
50
|
-
{"cls": sphinx_jupyterbook_latex.nodes.HiddenCellNode},
|
|
51
50
|
{
|
|
52
51
|
"cls": nodes.transition,
|
|
53
52
|
"tagname": "div",
|
|
@@ -204,7 +203,7 @@ class TEITranslator(nodes.GenericNodeVisitor):
|
|
|
204
203
|
else:
|
|
205
204
|
self.output.append(text)
|
|
206
205
|
else:
|
|
207
|
-
self.output.append(f"{self.indent*self.level}<tei:span>{text}</tei:span>\n")
|
|
206
|
+
self.output.append(f"{self.indent * self.level}<tei:span>{text}</tei:span>\n")
|
|
208
207
|
|
|
209
208
|
def depart_Text(self, node: nodes.TextElement) -> None: # noqa: N802
|
|
210
209
|
"""Unused."""
|