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.

@@ -1,7 +1,9 @@
1
+ # noqa: A005
1
2
  # SPDX-FileCopyrightText: 2023-present Mark Hall <mark.hall@work.room3b.eu>
2
3
  #
3
4
  # SPDX-License-Identifier: MIT
4
5
  """TEI parsing extension for Sphinx."""
6
+
5
7
  import re
6
8
  from typing import Callable
7
9
 
@@ -10,8 +12,10 @@ from lxml import etree
10
12
  from sphinx import addnodes
11
13
  from sphinx.application import Sphinx
12
14
  from sphinx.parsers import Parser as SphinxParser
15
+ from sphinx.util import logging
13
16
  from sphinx.writers.html import HTMLWriter
14
17
 
18
+ logger = logging.getLogger(__name__)
15
19
  namespaces = {"tei": "http://www.tei-c.org/ns/1.0", "uedition": "https://uedition.readthedocs.org"}
16
20
 
17
21
 
@@ -33,18 +37,18 @@ class TeiElement(nodes.Element):
33
37
  def tei_element_html_enter(self: "HTMLWriter", node: TeiElement) -> None:
34
38
  """Visit a TeiElement and generate the correct HTML."""
35
39
  if node.get("html_tag") is not None:
36
- buffer = [f'<{node.get("html_tag")} data-tei-tag="{node.get("tei_tag")[29:]}"']
40
+ buffer = [f"<{node.get('html_tag')}"]
37
41
  if node.get("ids"):
38
42
  buffer.append(f' id="{node.get("ids")[0]}"')
39
43
  for key, value in node.get("tei_attributes").items():
40
44
  buffer.append(f' {key}="{value}"')
41
- self.body.append(f'{"".join(buffer)}>')
45
+ self.body.append(f"{''.join(buffer)}>")
42
46
 
43
47
 
44
48
  def tei_element_html_exit(self: "HTMLWriter", node: TeiElement) -> None:
45
49
  """Close the HTML tag."""
46
50
  if node.get("html_tag") is not None:
47
- self.body.append(f'</{node.get("html_tag")}>')
51
+ self.body.append(f"</{node.get('html_tag')}>")
48
52
 
49
53
 
50
54
  class TEIParser(SphinxParser):
@@ -54,7 +58,8 @@ class TEIParser(SphinxParser):
54
58
  """Specify that only .tei files are parsed"""
55
59
 
56
60
  def parse(self: "TEIParser", inputstring: str, document: nodes.document) -> None:
57
- """Parse source TEI text.
61
+ """
62
+ Parse source TEI text.
58
63
 
59
64
  This function creates the basic structure and then the :func:`~uEdition.extensions.tei.TEIParser._walk_tree`
60
65
  function is used to actually process the XML.
@@ -75,35 +80,49 @@ class TEIParser(SphinxParser):
75
80
  doc_title = nodes.title()
76
81
  doc_title.append(nodes.Text(title if title else "[Untitled]"))
77
82
  doc_section.append(doc_title)
78
- if "tei" in self.config.uEdition and "sections" in self.config.uEdition["tei"]:
79
- for conf_section in self.config.uEdition["tei"]["sections"]:
80
- section = nodes.section(ids=[nodes.make_id(conf_section["title"])])
83
+ for conf_section in self.config.tei["sections"]:
84
+ section = nodes.section(ids=[nodes.make_id(conf_section["name"])])
85
+ if conf_section["title"]:
81
86
  section_title = nodes.title()
82
87
  section_title.append(nodes.Text(conf_section["title"]))
83
88
  section.append(section_title)
84
- if conf_section["type"] == "text":
85
- # Process a text section
86
- source = root.xpath(conf_section["content"], namespaces=namespaces)
87
- if len(source) > 0:
88
- if conf_section["sort"]:
89
- source.sort(key=self._sort_key(conf_section["sort"]))
90
- doc_section.append(section)
91
- tmp = nodes.section()
89
+ if conf_section["type"] == "text":
90
+ # Process a text section
91
+ sources = root.xpath(conf_section["selector"], namespaces=namespaces)
92
+ if len(sources) > 0:
93
+ doc_section.append(section)
94
+ tmp = nodes.section()
95
+ for source in sources:
96
+ for child in source:
97
+ self._walk_tree(child, tmp)
98
+ self._wrap_sections(section, tmp)
99
+ elif conf_section["type"] == "textlist":
100
+ # Process a text section
101
+ sources = root.xpath(conf_section["selector"], namespaces=namespaces)
102
+ if len(sources) > 0:
103
+ if conf_section["sort"]:
104
+ source.sort(key=self._sort_key(conf_section["sort"]))
105
+ doc_section.append(section)
106
+ for source in sources:
107
+ tmp = nodes.section(ids=[source.attrib["id"]])
92
108
  for child in source:
93
- self._walk_tree(child, tmp, conf_section["mappings"])
94
- self._wrap_sections(section, tmp)
95
- elif conf_section["type"] == "fields":
96
- # Process a field or metadata section
109
+ self._walk_tree(child, tmp)
110
+ section.append(tmp)
111
+ # self._wrap_sections(section, tmp)
112
+ elif conf_section["type"] == "metadata":
113
+ # Process a field or metadata section
114
+ sources = root.xpath(conf_section["selector"], namespaces=namespaces)
115
+ if len(sources) > 0:
97
116
  doc_section.append(section)
98
117
  fields = nodes.definition_list()
99
118
  section.append(fields)
100
119
  for field in conf_section["fields"]:
101
120
  if field["type"] == "single":
102
- self._parse_single_field(fields, field, root)
121
+ self._parse_single_field(fields, field, sources[0])
103
122
  elif field["type"] == "list":
104
- self._parse_list_field(fields, field, root)
123
+ self._parse_list_field(fields, field, sources[0])
105
124
  elif field["type"] == "download":
106
- self._parse_download_field(fields, field, root)
125
+ self._parse_download_field(fields, field, sources[0])
107
126
  document.append(doc_section)
108
127
 
109
128
  def _sort_key(self: "TEIParser", xpath: str) -> Callable[[etree.Element], tuple[tuple[int, ...], ...]]:
@@ -130,70 +149,53 @@ class TEIParser(SphinxParser):
130
149
 
131
150
  return sorter
132
151
 
133
- def _walk_tree(self: "TEIParser", node: etree.Element, parent: nodes.Element, rules: list) -> None:
134
- """Walk the XML tree and create the appropriate AST nodes.
152
+ def _walk_tree(self: "TEIParser", node: etree.Element, parent: nodes.Element) -> None:
153
+ """Walk the XML tree and create the appropriate AST nodes."""
154
+ for conf in self.config.tei["blocks"]:
155
+ if len(node.xpath(f"self::{conf['selector']}", namespaces=namespaces)) > 0:
156
+ attrs = self._parse_attributes(node, conf["attributes"])
157
+ attrs.update({f"data-tei-block-{conf['name']}": ""})
158
+ element = TeiElement(
159
+ html_tag=conf["tag"] if conf["tag"] else "div", tei_tag=node.tag, tei_attributes=attrs
160
+ )
161
+ for child in node:
162
+ self._walk_tree(child, element)
163
+ parent.append(element)
164
+ return
165
+ for conf in self.config.tei["marks"]:
166
+ if len(node.xpath(f"self::{conf['selector']}", namespaces=namespaces)) > 0:
167
+ attrs = self._parse_attributes(node, conf["attributes"])
168
+ attrs.update({f"data-tei-mark-{conf['name']}": ""})
169
+ element = TeiElement(
170
+ html_tag=conf["tag"] if conf["tag"] else "span", tei_tag=node.tag, tei_attributes=attrs
171
+ )
172
+ if len(node) == 0 and node.text:
173
+ element.append(nodes.Text(node.text))
174
+ else:
175
+ for child in node:
176
+ self._walk_tree(child, element)
177
+ parent.append(element)
178
+ return
179
+ if len(node) == 0:
180
+ parent.append(nodes.Text(node.text))
181
+ else:
182
+ logger.warning(f"No block or mark configured for {node.tag}")
135
183
 
136
- Uses the mapping rules defined in :mod:`~uEdition.extensions.config` to determine what to
137
- map the XML to.
138
- """
139
- is_leaf = len(node) == 0
140
- text_only_in_leaf_nodes = (
141
- self.config.uEdition["tei"]["text_only_in_leaf_nodes"] if "tei" in self.config.uEdition else False
142
- )
143
- attributes = {}
144
- # Get the first matching rule for the current node
145
- rule = self._rule_for_node(node, rules)
146
- # Loop over the XML node attributes and apply any attribute transforms defined in the matching rule
147
- for key, value in node.attrib.items():
148
- # Always strip the namespace from the `id` attribute
149
- if key == "{http://www.w3.org/XML/1998/namespace}id":
150
- key = "id" # noqa: PLW2901
151
- if rule and "attributes" in rule:
152
- processed = False
153
- for attr_rule in rule["attributes"]:
154
- if attr_rule["action"] == "copy":
155
- if key == attr_rule["source"]:
156
- # Copied attributes are added without a `data-` prefix
157
- attributes[attr_rule["attr"]] = value
158
- elif attr_rule["action"] == "delete":
159
- if key == attr_rule["attr"]:
160
- processed = True
161
- elif attr_rule["action"] == "set":
162
- if key == attr_rule["attr"]:
163
- value = attr_rule["value"] # noqa: PLW2901
164
- # if the attribute did not match any attribute transform
165
- if not processed:
166
- # The id attribute is always output as is, all other attributes are prefixed with `data-`
167
- if key == "id":
168
- attributes["id"] = value
169
- else:
170
- attributes[f"data-{key}"] = value
171
- else: # noqa: PLR5501
172
- # The id attribute is always output as is, all other attributes are prefixed with `data-`
173
- if key == "id":
174
- attributes["id"] = value
184
+ def _parse_attributes(self, node: etree.Element, attribute_configs: list) -> dict:
185
+ attrs = {}
186
+ for conf in attribute_configs:
187
+ if conf["name"] in node.attrib:
188
+ if conf["type"] == "id-ref" and node.attrib[conf["name"]].startswith("#"):
189
+ attrs[f"data-tei-attribute-{conf['name']}"] = node.attrib[conf["name"]][1:]
190
+ elif conf["type"] == "static":
191
+ attrs[f"data-tei-attribute-{conf['name']}"] = conf["value"]
192
+ elif conf["type"] == "html-attribute":
193
+ attrs[conf["target"]] = node.attrib[conf["name"]]
175
194
  else:
176
- attributes[f"data-{key}"] = value
177
- # Create the docutils AST element
178
- new_element = TeiElement(
179
- html_tag=rule["tag"] if rule is not None and "tag" in rule else "div",
180
- tei_tag=node.tag,
181
- tei_attributes=attributes,
182
- )
183
- parent.append(new_element)
184
- if rule is not None and "text" in rule and rule["text"]:
185
- # If there is a `text` key in the rule, use that to set the text
186
- if rule["text"]["action"] == "from-attribute" and rule["text"]["attr"] in node.attrib:
187
- new_element.append(nodes.Text(node.attrib[rule["text"]["attr"]]))
188
- elif node.text and (is_leaf or not text_only_in_leaf_nodes):
189
- # Only create text content if there is text and we either are in a leaf node or are adding all text
190
- new_element.append(nodes.Text(node.text))
191
- # Process any children
192
- for child in node:
193
- self._walk_tree(child, new_element, rules)
194
- # If there is text after this XML node and we are adding all text, then add text content to the parent
195
- if node.tail and not text_only_in_leaf_nodes:
196
- parent.append(nodes.Text(node.tail))
195
+ attrs[f"data-tei-attribute-{conf['name']}"] = node.attrib[conf["name"]]
196
+ elif conf["default"]:
197
+ attrs[f"data-tei-attribute-{conf['name']}"] = conf["default"]
198
+ return attrs
197
199
 
198
200
  def _wrap_sections(self: "TEIParser", section: nodes.Element, tmp: nodes.Element) -> None:
199
201
  """Ensure that sections are correctly wrapped."""
@@ -211,7 +213,7 @@ class TEIParser(SphinxParser):
211
213
  while section_level <= section_stack[-1][0]:
212
214
  section_stack.pop()
213
215
  new_section = nodes.section(ids=[nodes.make_id(node.astext())])
214
- title = nodes.title()
216
+ title = nodes.title(attributes={"data-test": ""})
215
217
  title.children = node.children
216
218
  new_section.append(title)
217
219
  section_stack[-1][1].append(new_section)
@@ -237,25 +239,9 @@ class TEIParser(SphinxParser):
237
239
  if not in_heading:
238
240
  section_stack[-1][1].append(node)
239
241
 
240
- def _rule_for_node(self: "TEIParser", node: etree.Element, rules: list[dict]) -> dict:
241
- """Determine the first matching mapping rule for the node from the configured rules."""
242
- tei_tag = node.tag
243
- for rule in rules:
244
- if rule["selector"]["tag"] == tei_tag:
245
- if "attributes" in rule["selector"]:
246
- attr_match = True
247
- for attr_rule in rule["selector"]["attributes"]:
248
- if attr_rule["attr"] not in node.attrib or node.attrib[attr_rule["attr"]] != attr_rule["value"]:
249
- attr_match = False
250
- break
251
- if not attr_match:
252
- continue
253
- return rule
254
- return None
255
-
256
242
  def _parse_single_field(self: "TEIParser", parent: etree.Element, field: dict, root: etree.Element) -> None:
257
243
  """Parse a single metadata field."""
258
- content = root.xpath(field["content"], namespaces=namespaces)
244
+ content = root.xpath(field["selector"], namespaces=namespaces)
259
245
  if len(content) > 0:
260
246
  if isinstance(content, list):
261
247
  content = content[0]
@@ -270,7 +256,7 @@ class TEIParser(SphinxParser):
270
256
 
271
257
  def _parse_list_field(self: "TEIParser", parent: etree.Element, field: dict, root: etree.Element) -> None:
272
258
  """Parse a list of metadata fields."""
273
- content = root.xpath(field["content"], namespaces=namespaces)
259
+ content = root.xpath(field["selector"], namespaces=namespaces)
274
260
  if len(content) > 0:
275
261
  li = nodes.definition_list_item()
276
262
  dt = nodes.term()
@@ -288,7 +274,7 @@ class TEIParser(SphinxParser):
288
274
 
289
275
  def _parse_download_field(self: "TEIParser", parent: etree.Element, field: dict, root: etree.Element) -> None:
290
276
  """Parse a download metadata field."""
291
- content = root.xpath(field["content"], namespaces=namespaces)
277
+ content = root.xpath(field["selector"], namespaces=namespaces)
292
278
  target = root.xpath(field["target"], namespaces=namespaces)
293
279
  if len(content) > 0 and len(target) > 0:
294
280
  if isinstance(content, list):
uedition/settings.py CHANGED
@@ -5,6 +5,7 @@
5
5
 
6
6
  All application settings are accessed via the `settings` dictionary.
7
7
  """
8
+
8
9
  import os
9
10
  from typing import Annotated, Any, Dict, Tuple, Type
10
11
 
@@ -15,11 +16,21 @@ from pydantic_settings import BaseSettings, PydanticBaseSettingsSource
15
16
  from yaml import safe_load
16
17
 
17
18
 
19
+ class NoConfigError(Exception):
20
+ """Exception to signal that no configuration file was found in the current directory."""
21
+
22
+ def __init__(self) -> None:
23
+ """Initialise the Exception with the default error message."""
24
+ super().__init__("No uEdition.yml or uEdition.yaml was found in the current directory")
25
+
26
+
18
27
  class YAMLConfigSettingsSource(PydanticBaseSettingsSource):
19
28
  """Loads the configuration settings from a YAML file."""
20
29
 
21
30
  def get_field_value(
22
- self: "YAMLConfigSettingsSource", field: FieldInfo, field_name: str # noqa: ARG002
31
+ self: "YAMLConfigSettingsSource",
32
+ field: FieldInfo, # noqa: ARG002
33
+ field_name: str,
23
34
  ) -> Tuple[Any, str, bool]:
24
35
  """Get the value of a specific field."""
25
36
  encoding = self.config.get("env_file_encoding")
@@ -75,8 +86,6 @@ class RepositorySettings(BaseModel):
75
86
 
76
87
  url: str | None = None
77
88
  """The repository's URL."""
78
- branch: str | None = None
79
- """The repository's branch."""
80
89
 
81
90
 
82
91
  class AuthorSettings(BaseModel):
@@ -107,7 +116,7 @@ def convert_output_str_to_dict(value: str | dict) -> dict:
107
116
  class Settings(BaseSettings):
108
117
  """Application settings."""
109
118
 
110
- version: str = "1"
119
+ version: str = "2"
111
120
  """The configuration file version."""
112
121
  author: AuthorSettings = AuthorSettings()
113
122
  """The author settings."""
@@ -119,8 +128,8 @@ class Settings(BaseSettings):
119
128
  """The repository settings."""
120
129
  title: dict = {}
121
130
  """The titles for the individual languages."""
122
- jb_config: dict = {}
123
- """Additional JupyterBook configuration."""
131
+ sphinx_config: dict = {}
132
+ """Sphinx configuration."""
124
133
 
125
134
  @classmethod
126
135
  def settings_customise_sources(
@@ -147,4 +156,4 @@ settings = Settings().model_dump()
147
156
  def reload_settings() -> None:
148
157
  """Reload the settings."""
149
158
  settings.clear()
150
- settings.update(Settings().dict())
159
+ settings.update(Settings().model_dump())
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: uEdition
3
- Version: 1.3.2
3
+ Version: 2.0.0a2
4
4
  Project-URL: Documentation, https://github.com/uEdition/uEdition#readme
5
5
  Project-URL: Issues, https://github.com/uEdition/uEdition/issues
6
6
  Project-URL: Source, https://github.com/uEdition/uEdition
@@ -11,17 +11,22 @@ Classifier: Development Status :: 4 - Beta
11
11
  Classifier: Programming Language :: Python
12
12
  Classifier: Programming Language :: Python :: 3.10
13
13
  Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
14
16
  Classifier: Programming Language :: Python :: Implementation :: CPython
15
17
  Classifier: Programming Language :: Python :: Implementation :: PyPy
16
18
  Requires-Python: >=3.10
17
19
  Requires-Dist: copier<10.0.0,>=9.0.0
18
- Requires-Dist: jupyter-book<2.0.0,>=1.0.0
19
20
  Requires-Dist: livereload
20
21
  Requires-Dist: lxml<6.0.0,>=4.9.2
22
+ Requires-Dist: myst-parser<5,>=4.0.1
21
23
  Requires-Dist: pydantic-settings<3.0.0,>=2.0.0
22
24
  Requires-Dist: pydantic<3.0.0,>=2.0.0
23
25
  Requires-Dist: pyyaml<7.0.0,>=6.0.0
24
- Requires-Dist: typer[all]<1.0.0
26
+ Requires-Dist: sphinx-book-theme<2,>=1.1.4
27
+ Requires-Dist: sphinx-external-toc<2,>=1.0.1
28
+ Requires-Dist: sphinx<9,>=8.2.3
29
+ Requires-Dist: typer<1.0.0
25
30
  Description-Content-Type: text/markdown
26
31
 
27
32
  # μEdition
@@ -0,0 +1,25 @@
1
+ uedition/__about__.py,sha256=k24sMTNyGf4Oe8FCxCgoNYzCS3u7JLwlCB1o_xfqyNY,160
2
+ uedition/__init__.py,sha256=xDDK2i5l1NLhxq6_LJH5g7UJDrFEjIJNmT2tcg7xNWI,301
3
+ uedition/__main__.py,sha256=Pg_yGV-ndR2iiImDC3f017llSX6pSYMslIwZlw8vEpQ,189
4
+ uedition/settings.py,sha256=G73r6muJmRzuENZ-2n51bBf88qS3tRv0kdxr2SRt1j4,5048
5
+ uedition/cli/__init__.py,sha256=gnpl_N8uaw-4uF5ByWV3jXveJBvjLb_uay5YkYCUQWw,1478
6
+ uedition/cli/build.py,sha256=L_-OsIEFBXQh-aTHLwLhOXJzXqRH61X2SgVIPxmUgQ0,8169
7
+ uedition/cli/create.py,sha256=Q-SvDq9VtmUP4DQhuuvt1eZ_72sX8_tcFOj2Bt_T6J8,371
8
+ uedition/cli/language.py,sha256=JAyUwNa4gwqMvrJDPPKGkMLm5Cx9sHJkU5r6xTAMb7M,2214
9
+ uedition/cli/serve.py,sha256=FcKp0IbjcyCgn1XjU8VdhI59pGRMNCSXa5jbAS23nxs,1513
10
+ uedition/cli/update.py,sha256=SmAcczHxj3j3X0QYplvXPoIHS8XEfXYVqgXBney0v9c,550
11
+ uedition/ext/__init__.py,sha256=hAK3MB5il4tAkfWnZNVxIJhfJ5vN0Fdmmtz0ZAYsvo4,406
12
+ uedition/ext/config.py,sha256=zu0XSH3Ca09n--umhUJ7k6611lVCecOTVZCWAFn4TRU,994
13
+ uedition/ext/language_switcher.css,sha256=y4LzkCgCm6E_nHt15I4Ku5QzBNpjwda9bt9FsqD1ybM,132
14
+ uedition/ext/language_switcher.js,sha256=HIgQiLg0WGS_G_VjpfEpTDLqb1HwHxcL3r6mdoSUix4,2697
15
+ uedition/ext/language_switcher.py,sha256=tHpf4HsvMtatVn5dQ3EFlrk5urFaMzsZZY755cvgCu8,1425
16
+ uedition/ext/settings.py,sha256=CCbvwlWjhikhoeRZ5E_SuA4zUIqDBMkRes_hjOUxjWk,3735
17
+ uedition/ext/tei/__init__.py,sha256=8KgVi31bz8nI65m6u4EdT_f1qNCP45HrU0V7MSGlZxA,1074
18
+ uedition/ext/tei/builder.py,sha256=LkLR3cu1M6lHHdcjhH5esZ9Qn6eFBXCZWkxVxdXv69E,11719
19
+ uedition/ext/tei/parser.py,sha256=1-ugV44M4TmgRHZBYWnvuckTimIx_dHaKaSBL6hxdQA,13218
20
+ uedition/ext/tei/tei_download.js,sha256=5_IPCuamZGPXWriPPPz5wA-zo0Y0Oy1858S6ltxSdQ8,2151
21
+ uedition-2.0.0a2.dist-info/METADATA,sha256=nOJXvW8UPoo3jwQnQSDkkPEFrQeMbSKV7o7RuQZ4zXU,2572
22
+ uedition-2.0.0a2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
23
+ uedition-2.0.0a2.dist-info/entry_points.txt,sha256=cDOOVBb1SZ072ZkY1hW4Y7I_WKKGCtCJtDY1XST1Hr4,96
24
+ uedition-2.0.0a2.dist-info/licenses/LICENSE.txt,sha256=MhLJl8GE8mnuO5i_pvKaobpIGnhiAEdkY-a6LKiuwCE,1101
25
+ uedition-2.0.0a2.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.21.1
2
+ Generator: hatchling 1.27.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
uedition/cli/check.py DELETED
@@ -1,183 +0,0 @@
1
- # SPDX-FileCopyrightText: 2023-present Mark Hall <mark.hall@work.room3b.eu>
2
- #
3
- # SPDX-License-Identifier: MIT
4
- """The uEdition check functionality for validating a uEdition and its files."""
5
- import os
6
- from threading import Thread
7
-
8
- import typer
9
- from rich import print as print_cli
10
- from rich.progress import Progress
11
- from yaml import safe_load
12
-
13
- from uedition.settings import settings
14
-
15
-
16
- def collect_files(toc: dict) -> list[str]:
17
- """Collect all files from a TOC."""
18
- files = []
19
- if "parts" in toc:
20
- for part in toc["parts"]:
21
- files.extend(collect_files(part))
22
- elif "chapters" in toc:
23
- for chapter in toc["chapters"]:
24
- files.append(chapter["file"])
25
- files.extend(collect_files(chapter))
26
- elif "sections" in toc:
27
- for sections in toc["sections"]:
28
- files.append(sections["file"])
29
- return files
30
-
31
-
32
- def compare_tocs(prefix: str, toc_a: dict, toc_b: dict) -> list[tuple[str | None, str | None]]:
33
- """Recursively compare the structure of two TOCs."""
34
- mismatches = []
35
- if "parts" in toc_a and "parts" in toc_b:
36
- pass
37
- elif "chapters" in toc_a and "chapters" in toc_b:
38
- pass
39
- elif "sections" in toc_a and "sections" in toc_b:
40
- pass
41
- elif "parts" in toc_a:
42
- mismatches.append(f"{prefix}has parts, which are missing from")
43
- elif "chapters" in toc_a:
44
- mismatches.append(f"{prefix}has chapters, which are missing from")
45
- elif "sections" in toc_b:
46
- mismatches.append(f"{prefix}has sections, which are missing from")
47
- return mismatches
48
-
49
-
50
- class ConfigurationFileChecks(Thread):
51
- """Basic configuration file checks."""
52
-
53
- def __init__(self: "ConfigurationFileChecks", progress: Progress, task: int) -> None:
54
- """Initialise the thread."""
55
- super().__init__(group=None)
56
- self._progress = progress
57
- self._task = task
58
- self.errors = []
59
-
60
- def run(self: "ConfigurationFileChecks") -> None:
61
- """Run the checks."""
62
- for lang in settings["languages"]:
63
- yaml_path = os.path.join(lang["path"], "_config.yml")
64
- if os.path.exists(yaml_path):
65
- with open(yaml_path) as in_f:
66
- try:
67
- safe_load(in_f)
68
- except Exception as e:
69
- self.errors.append(str(e))
70
- else:
71
- self.errors.append(f"Missing configuration file {yaml_path}")
72
- self._progress.update(self._task, advance=1)
73
-
74
-
75
- class TocFileChecks(Thread):
76
- """TOC file checks."""
77
-
78
- def __init__(self: "TocFileChecks", progress: Progress, task: int) -> None:
79
- """Initialise the thread."""
80
- super().__init__(group=None)
81
- self._progress = progress
82
- self._task = task
83
- self.errors = []
84
-
85
- def run(self: "TocFileChecks") -> None:
86
- """Run the checks."""
87
- for lang in settings["languages"]:
88
- yaml_path = os.path.join(lang["path"], "_toc.yml")
89
- if os.path.exists(yaml_path):
90
- with open(yaml_path) as in_f:
91
- try:
92
- toc = safe_load(in_f)
93
- if "root" in toc:
94
- root_path = os.path.join(lang["path"], f'{toc["root"]}.md')
95
- if not os.path.exists(root_path):
96
- self.errors.append(f'Root in {yaml_path} points to missing file {toc["root"]}.md')
97
- root_base = os.path.dirname(root_path)
98
- for filename in collect_files(toc):
99
- if not os.path.exists(os.path.join(root_base, f"{filename}.md")):
100
- self.errors.append(f"File {filename}.md missing in {yaml_path}")
101
- else:
102
- self.errors.append(f"No root in {yaml_path}")
103
- except Exception as e:
104
- self.errors.append(str(e))
105
- else:
106
- self.errors.append(f"Missing toc file {yaml_path}")
107
- self._progress.update(self._task, advance=1)
108
-
109
-
110
- class LanguageConsistencyChecks(Thread):
111
- """Multi-language consistency checks."""
112
-
113
- def __init__(self: "LanguageConsistencyChecks", progress: Progress, task: int) -> None:
114
- """Initialise the thread."""
115
- super().__init__(group=None)
116
- self._progress = progress
117
- self._task = task
118
- self.errors = []
119
-
120
- def run(self: "LanguageConsistencyChecks") -> None:
121
- """Run the checks."""
122
- if len(settings["languages"]) == 0:
123
- return
124
- base_toc_path = os.path.join(settings["languages"][0]["path"], "_toc.yml")
125
- if not os.path.exists(base_toc_path):
126
- return
127
- with open(base_toc_path) as in_f:
128
- try:
129
- base_toc = safe_load(in_f)
130
- except Exception:
131
- return
132
- for lang in settings["languages"][1:]:
133
- lang_toc_path = os.path.join(lang["path"], "_toc.yml")
134
- if not os.path.exists(lang_toc_path):
135
- continue
136
- try:
137
- with open(lang_toc_path) as in_f:
138
- lang_toc = safe_load(in_f)
139
- missmatches = compare_tocs("", base_toc, lang_toc)
140
- if len(missmatches) > 0:
141
- for mismatch in missmatches:
142
- if mismatch[0] is None:
143
- self.errors.append(f"{base_toc_path} {mismatch[1]} {lang_toc_path}")
144
- elif mismatch[0] is None:
145
- self.errors.append(f"{lang_toc_path} {mismatch[0]} {base_toc_path}")
146
- self._progress.update(self._task, advance=1)
147
- except Exception as e:
148
- self.errors.append(f"Fail to check langauge {lang}: {e!s}")
149
-
150
-
151
- def run() -> None:
152
- """Check that the μEdition is correctly set up."""
153
- errors = []
154
- with Progress() as progress:
155
- threads = [
156
- ConfigurationFileChecks(
157
- progress,
158
- progress.add_task("[green]Configuration file checks", total=len(settings["languages"])),
159
- ),
160
- TocFileChecks(
161
- progress,
162
- progress.add_task("[green]TOC file checks", total=len(settings["languages"])),
163
- ),
164
- LanguageConsistencyChecks(
165
- progress,
166
- progress.add_task(
167
- "[green]Language consistency checks",
168
- total=len(settings["languages"]) - 1,
169
- ),
170
- ),
171
- ]
172
- for thread in threads:
173
- thread.start()
174
- for thread in threads:
175
- thread.join()
176
- errors.extend(thread.errors)
177
- if len(errors) > 0:
178
- print_cli("[red]:bug: The following errors were found:")
179
- for error in errors:
180
- print_cli(f"[red]* {error}")
181
- raise typer.Exit(code=1)
182
- else:
183
- print_cli("[green]:+1: All checks successfully passed")
@@ -1,25 +0,0 @@
1
- uedition/__about__.py,sha256=qc1tq_AqKfnOrx88PfqDhfox8UD5GLclkXNzDTGwgaQ,157
2
- uedition/__init__.py,sha256=xDDK2i5l1NLhxq6_LJH5g7UJDrFEjIJNmT2tcg7xNWI,301
3
- uedition/__main__.py,sha256=Pg_yGV-ndR2iiImDC3f017llSX6pSYMslIwZlw8vEpQ,189
4
- uedition/settings.py,sha256=4jWfjD1KmYTwzeD6DhP9taWS-zFp9XHxo1QqUqsSlrA,4774
5
- uedition/cli/__init__.py,sha256=h3N4ZwgRC72IlkCsTbke4PLTFv-_pn1-npXXPC7S78U,1642
6
- uedition/cli/build.py,sha256=qrpNP227DlTqa6pHX3NrDw1cohc1dHPi5J6pMFgxKsk,8135
7
- uedition/cli/check.py,sha256=Jlkyw6mgcz3HM-wJpmzCtKv054MFolCe7m6bSUDtsZA,7005
8
- uedition/cli/create.py,sha256=Q-SvDq9VtmUP4DQhuuvt1eZ_72sX8_tcFOj2Bt_T6J8,371
9
- uedition/cli/language.py,sha256=IqSJrZbrQzU-7TJqnCBeC2HUs1N01EAa7jFMvfXTsoA,1943
10
- uedition/cli/serve.py,sha256=UfVsY26OW9yAz7rnjjatz1gEDGIi1kaTYlkUAiVCuRw,1391
11
- uedition/cli/update.py,sha256=XKHnvorHqizsB5zP-8ifMrgnQuq6zRk6Tb03dBz_MI4,377
12
- uedition/ext/__init__.py,sha256=hAK3MB5il4tAkfWnZNVxIJhfJ5vN0Fdmmtz0ZAYsvo4,406
13
- uedition/ext/config.py,sha256=eEkBkAOaDQPzO_2Uj5OJxyAZiCG4I-TcOEqUyIY8IFY,6023
14
- uedition/ext/language_switcher.css,sha256=y4LzkCgCm6E_nHt15I4Ku5QzBNpjwda9bt9FsqD1ybM,132
15
- uedition/ext/language_switcher.js,sha256=HIgQiLg0WGS_G_VjpfEpTDLqb1HwHxcL3r6mdoSUix4,2697
16
- uedition/ext/language_switcher.py,sha256=tHpf4HsvMtatVn5dQ3EFlrk5urFaMzsZZY755cvgCu8,1425
17
- uedition/ext/tei/__init__.py,sha256=8KgVi31bz8nI65m6u4EdT_f1qNCP45HrU0V7MSGlZxA,1074
18
- uedition/ext/tei/builder.py,sha256=XLFM11Gnt_4G4H-2gaJyjOgko_aMmvgZnKftMsSk8qw,11743
19
- uedition/ext/tei/parser.py,sha256=QpDZ0D9ai4JCvtpBzfimI83rCdDQsD2ey4-_cW9Llv0,14291
20
- uedition/ext/tei/tei_download.js,sha256=5_IPCuamZGPXWriPPPz5wA-zo0Y0Oy1858S6ltxSdQ8,2151
21
- uedition-1.3.2.dist-info/METADATA,sha256=WlCa6UH0dUY63Mwwk6HPUFr5ltvei2KwXmObxB883YM,2358
22
- uedition-1.3.2.dist-info/WHEEL,sha256=TJPnKdtrSue7xZ_AVGkp9YXcvDrobsjBds1du3Nx6dc,87
23
- uedition-1.3.2.dist-info/entry_points.txt,sha256=cDOOVBb1SZ072ZkY1hW4Y7I_WKKGCtCJtDY1XST1Hr4,96
24
- uedition-1.3.2.dist-info/licenses/LICENSE.txt,sha256=MhLJl8GE8mnuO5i_pvKaobpIGnhiAEdkY-a6LKiuwCE,1101
25
- uedition-1.3.2.dist-info/RECORD,,