zensical 0.0.9__cp310-abi3-musllinux_1_2_i686.whl → 0.0.12__cp310-abi3-musllinux_1_2_i686.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 zensical might be problematic. Click here for more details.
- zensical/__init__.py +4 -4
- zensical/bootstrap/.github/workflows/docs.yml +1 -0
- zensical/bootstrap/zensical.toml +3 -3
- zensical/config.py +130 -185
- zensical/extensions/emoji.py +20 -25
- zensical/extensions/links.py +19 -23
- zensical/extensions/preview.py +27 -39
- zensical/extensions/search.py +81 -81
- zensical/extensions/utilities/filter.py +3 -8
- zensical/main.py +33 -46
- zensical/markdown.py +18 -17
- zensical/templates/assets/javascripts/workers/{search.5e1f2129.min.js → search.5df7522c.min.js} +1 -1
- zensical/templates/assets/stylesheets/classic/main.6f483be1.min.css +1 -0
- zensical/templates/assets/stylesheets/modern/main.09f707be.min.css +1 -0
- zensical/templates/base.html +3 -3
- zensical/zensical.abi3.so +0 -0
- zensical/zensical.pyi +5 -11
- {zensical-0.0.9.dist-info → zensical-0.0.12.dist-info}/METADATA +6 -2
- {zensical-0.0.9.dist-info → zensical-0.0.12.dist-info}/RECORD +23 -23
- zensical.libs/libgcc_s-f5fcfe20.so.1 +0 -0
- zensical/templates/assets/stylesheets/classic/main.6eec86b3.min.css +0 -1
- zensical/templates/assets/stylesheets/modern/main.2644c6b7.min.css +0 -1
- zensical.libs/libgcc_s-27e5a392.so.1 +0 -0
- {zensical-0.0.9.dist-info → zensical-0.0.12.dist-info}/WHEEL +0 -0
- {zensical-0.0.9.dist-info → zensical-0.0.12.dist-info}/entry_points.txt +0 -0
- {zensical-0.0.9.dist-info → zensical-0.0.12.dist-info}/licenses/LICENSE.md +0 -0
zensical/__init__.py
CHANGED
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
22
22
|
# IN THE SOFTWARE.
|
|
23
23
|
|
|
24
|
-
from .zensical import *
|
|
24
|
+
from zensical.zensical import * # noqa: F403
|
|
25
25
|
|
|
26
|
-
__doc__ = zensical.__doc__
|
|
27
|
-
if hasattr(zensical, "__all__"):
|
|
28
|
-
__all__ = zensical.__all__
|
|
26
|
+
__doc__ = zensical.__doc__ # type: ignore[name-defined] # noqa: F405
|
|
27
|
+
if hasattr(zensical, "__all__"): # type: ignore[name-defined] # noqa: F405
|
|
28
|
+
__all__ = zensical.__all__ # type: ignore[name-defined] # noqa: F405
|
zensical/bootstrap/zensical.toml
CHANGED
|
@@ -57,7 +57,7 @@ Copyright © 2025 The authors
|
|
|
57
57
|
#
|
|
58
58
|
# Read more: https://zensical.org/docs/customization/#additional-css
|
|
59
59
|
#
|
|
60
|
-
#extra_css = ["
|
|
60
|
+
#extra_css = ["stylesheets/extra.css"]
|
|
61
61
|
|
|
62
62
|
# With the `extra_javascript` option you can add your own JavaScript to your
|
|
63
63
|
# project to customize the behavior according to your needs.
|
|
@@ -65,7 +65,7 @@ Copyright © 2025 The authors
|
|
|
65
65
|
# The path provided should be relative to the "docs_dir".
|
|
66
66
|
#
|
|
67
67
|
# Read more: https://zensical.org/docs/customization/#additional-javascript
|
|
68
|
-
#extra_javascript = ["
|
|
68
|
+
#extra_javascript = ["javascripts/extra.js"]
|
|
69
69
|
|
|
70
70
|
# ----------------------------------------------------------------------------
|
|
71
71
|
# Section for configuring theme options
|
|
@@ -93,7 +93,7 @@ Copyright © 2025 The authors
|
|
|
93
93
|
# - https://zensical.org/docs/setup/logo-and-icons/#favicon
|
|
94
94
|
# - https://developer.mozilla.org/en-US/docs/Glossary/Favicon
|
|
95
95
|
#
|
|
96
|
-
#favicon = "
|
|
96
|
+
#favicon = "images/favicon.png"
|
|
97
97
|
|
|
98
98
|
# Zensical supports more than 60 different languages. This means that the
|
|
99
99
|
# labels and tooltips that Zensical's templates produce are translated.
|
zensical/config.py
CHANGED
|
@@ -27,21 +27,22 @@ import hashlib
|
|
|
27
27
|
import importlib
|
|
28
28
|
import os
|
|
29
29
|
import pickle
|
|
30
|
-
import
|
|
31
|
-
|
|
32
|
-
try:
|
|
33
|
-
import tomllib
|
|
34
|
-
except ModuleNotFoundError:
|
|
35
|
-
import tomli as tomllib # type: ignore
|
|
30
|
+
from typing import IO, Any
|
|
31
|
+
from urllib.parse import urlparse
|
|
36
32
|
|
|
33
|
+
import yaml
|
|
37
34
|
from click import ClickException
|
|
38
35
|
from deepmerge import always_merger
|
|
39
|
-
from typing import Any, IO
|
|
40
36
|
from yaml import BaseLoader, Loader, YAMLError
|
|
41
37
|
from yaml.constructor import ConstructorError
|
|
42
|
-
from urllib.parse import urlparse
|
|
43
38
|
|
|
44
|
-
from .extensions.emoji import to_svg, twemoji
|
|
39
|
+
from zensical.extensions.emoji import to_svg, twemoji
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
import tomllib
|
|
43
|
+
except ModuleNotFoundError:
|
|
44
|
+
import tomli as tomllib # type: ignore[no-redef]
|
|
45
|
+
|
|
45
46
|
|
|
46
47
|
# ----------------------------------------------------------------------------
|
|
47
48
|
# Globals
|
|
@@ -64,9 +65,7 @@ side, and use it directly when needed. It's a hack but will do for now.
|
|
|
64
65
|
|
|
65
66
|
|
|
66
67
|
class ConfigurationError(ClickException):
|
|
67
|
-
"""
|
|
68
|
-
Configuration resolution or validation failed.
|
|
69
|
-
"""
|
|
68
|
+
"""Configuration resolution or validation failed."""
|
|
70
69
|
|
|
71
70
|
|
|
72
71
|
# ----------------------------------------------------------------------------
|
|
@@ -75,22 +74,17 @@ class ConfigurationError(ClickException):
|
|
|
75
74
|
|
|
76
75
|
|
|
77
76
|
def parse_config(path: str) -> dict:
|
|
78
|
-
"""
|
|
79
|
-
Parse configuration file.
|
|
80
|
-
"""
|
|
77
|
+
"""Parse configuration file."""
|
|
81
78
|
# Decide by extension; no need to convert to Path
|
|
82
79
|
_, ext = os.path.splitext(path)
|
|
83
80
|
if ext.lower() == ".toml":
|
|
84
81
|
return parse_zensical_config(path)
|
|
85
|
-
|
|
86
|
-
return parse_mkdocs_config(path)
|
|
82
|
+
return parse_mkdocs_config(path)
|
|
87
83
|
|
|
88
84
|
|
|
89
85
|
def parse_zensical_config(path: str) -> dict:
|
|
90
|
-
"""
|
|
91
|
-
|
|
92
|
-
"""
|
|
93
|
-
global _CONFIG
|
|
86
|
+
"""Parse zensical.toml configuration file."""
|
|
87
|
+
global _CONFIG # noqa: PLW0603
|
|
94
88
|
with open(path, "rb") as f:
|
|
95
89
|
config = tomllib.load(f)
|
|
96
90
|
if "project" in config:
|
|
@@ -102,11 +96,9 @@ def parse_zensical_config(path: str) -> dict:
|
|
|
102
96
|
|
|
103
97
|
|
|
104
98
|
def parse_mkdocs_config(path: str) -> dict:
|
|
105
|
-
"""
|
|
106
|
-
|
|
107
|
-
""
|
|
108
|
-
global _CONFIG
|
|
109
|
-
with open(path, "r") as f:
|
|
99
|
+
"""Parse mkdocs.yml configuration file."""
|
|
100
|
+
global _CONFIG # noqa: PLW0603
|
|
101
|
+
with open(path, encoding="utf-8") as f:
|
|
110
102
|
config = _yaml_load(f)
|
|
111
103
|
|
|
112
104
|
# Apply defaults and return parsed configuration
|
|
@@ -114,24 +106,20 @@ def parse_mkdocs_config(path: str) -> dict:
|
|
|
114
106
|
return _CONFIG
|
|
115
107
|
|
|
116
108
|
|
|
117
|
-
def get_config():
|
|
118
|
-
"""
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
return _CONFIG
|
|
109
|
+
def get_config() -> dict:
|
|
110
|
+
"""Return configuration."""
|
|
111
|
+
# We assume this function is only called after populating `_CONFIG`.
|
|
112
|
+
return _CONFIG # type: ignore[return-value]
|
|
122
113
|
|
|
123
114
|
|
|
124
115
|
def get_theme_dir() -> str:
|
|
125
|
-
"""
|
|
126
|
-
Return the theme directory.
|
|
127
|
-
"""
|
|
116
|
+
"""Return the theme directory."""
|
|
128
117
|
path = os.path.dirname(os.path.abspath(__file__))
|
|
129
118
|
return os.path.join(path, "templates")
|
|
130
119
|
|
|
131
120
|
|
|
132
121
|
def _apply_defaults(config: dict, path: str) -> dict:
|
|
133
|
-
"""
|
|
134
|
-
Apply default settings in configuration.
|
|
122
|
+
"""Apply default settings in configuration.
|
|
135
123
|
|
|
136
124
|
Note that this is loosely based on the defaults that MkDocs sets in its own
|
|
137
125
|
configuration system, which we won't port for compatibility right now, as
|
|
@@ -146,12 +134,12 @@ def _apply_defaults(config: dict, path: str) -> dict:
|
|
|
146
134
|
|
|
147
135
|
# Set site directory
|
|
148
136
|
set_default(config, "site_dir", "site", str)
|
|
149
|
-
if ".." in config.get("site_dir"):
|
|
137
|
+
if ".." in config.get("site_dir", ""):
|
|
150
138
|
raise ConfigurationError("site_dir must not contain '..'")
|
|
151
139
|
|
|
152
140
|
# Set docs directory
|
|
153
141
|
set_default(config, "docs_dir", "docs", str)
|
|
154
|
-
if ".." in config.get("docs_dir"):
|
|
142
|
+
if ".." in config.get("docs_dir", ""):
|
|
155
143
|
raise ConfigurationError("docs_dir must not contain '..'")
|
|
156
144
|
|
|
157
145
|
# Set defaults for core settings
|
|
@@ -169,21 +157,26 @@ def _apply_defaults(config: dict, path: str) -> dict:
|
|
|
169
157
|
set_default(config, "edit_uri", None, str)
|
|
170
158
|
|
|
171
159
|
# Set defaults for repository name settings
|
|
160
|
+
docs_dir = config.get("docs_dir")
|
|
161
|
+
repo_names = {
|
|
162
|
+
"github.com": "GitHub",
|
|
163
|
+
"gitlab.com": "Gitlab",
|
|
164
|
+
"bitbucket.org": "Bitbucket"
|
|
165
|
+
}
|
|
166
|
+
edit_uris = {
|
|
167
|
+
"github.com": f"edit/master/{docs_dir}",
|
|
168
|
+
"gitlab.com": f"edit/master/{docs_dir}",
|
|
169
|
+
"bitbucket.org": f"src/default/{docs_dir}"
|
|
170
|
+
}
|
|
172
171
|
repo_url = config.get("repo_url")
|
|
173
|
-
if repo_url
|
|
174
|
-
docs_dir = config.get("docs_dir")
|
|
172
|
+
if repo_url:
|
|
175
173
|
host = urlparse(repo_url).hostname or ""
|
|
176
|
-
if host
|
|
177
|
-
set_default(config, "repo_name",
|
|
178
|
-
set_default(config, "edit_uri", f"edit/master/{docs_dir}", str)
|
|
179
|
-
elif host == "gitlab.com":
|
|
180
|
-
set_default(config, "repo_name", "GitLab", str)
|
|
181
|
-
set_default(config, "edit_uri", f"edit/master/{docs_dir}", str)
|
|
182
|
-
elif host == "bitbucket.org":
|
|
183
|
-
set_default(config, "repo_name", "Bitbucket", str)
|
|
184
|
-
set_default(config, "edit_uri", f"src/default/{docs_dir}", str)
|
|
174
|
+
if not config.get("repo_name") and host in repo_names:
|
|
175
|
+
set_default(config, "repo_name", repo_names[host], str)
|
|
185
176
|
elif host:
|
|
186
177
|
config["repo_name"] = host.split(".")[0].title()
|
|
178
|
+
if host in edit_uris:
|
|
179
|
+
set_default(config, "edit_uri", edit_uris[host], str)
|
|
187
180
|
|
|
188
181
|
# Remove trailing slash from edit_uri if present
|
|
189
182
|
edit_uri = config.get("edit_uri")
|
|
@@ -292,73 +285,13 @@ def _apply_defaults(config: dict, path: str) -> dict:
|
|
|
292
285
|
set_default(toggle, "name", None, str)
|
|
293
286
|
|
|
294
287
|
# Set defaults for extra settings
|
|
288
|
+
if "extra" in config and not isinstance(config["extra"], dict):
|
|
289
|
+
raise ConfigurationError("The 'extra' setting must be a mapping/dictionary.")
|
|
295
290
|
extra = set_default(config, "extra", {}, dict)
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
set_default(extra, "tags", {}, dict)
|
|
300
|
-
set_default(extra, "generator", True, bool)
|
|
291
|
+
|
|
292
|
+
if "polyfills" in extra and not isinstance(extra["polyfills"], list):
|
|
293
|
+
raise ConfigurationError("The 'extra.polyfills' setting must be a list.")
|
|
301
294
|
set_default(extra, "polyfills", [], list)
|
|
302
|
-
set_default(extra, "analytics", None, dict)
|
|
303
|
-
|
|
304
|
-
# Set defaults for extra analytics settings
|
|
305
|
-
analytics = extra.get("analytics")
|
|
306
|
-
if analytics:
|
|
307
|
-
set_default(analytics, "provider", None, str)
|
|
308
|
-
set_default(analytics, "property", None, str)
|
|
309
|
-
set_default(analytics, "feedback", None, dict)
|
|
310
|
-
|
|
311
|
-
# Set defaults for extra analytics feedback settings
|
|
312
|
-
feedback = analytics.get("feedback")
|
|
313
|
-
if feedback:
|
|
314
|
-
set_default(feedback, "title", None, str)
|
|
315
|
-
set_default(feedback, "ratings", [], list)
|
|
316
|
-
|
|
317
|
-
# Set defaults for each rating entry
|
|
318
|
-
ratings = feedback.setdefault("ratings", [])
|
|
319
|
-
for entry in ratings:
|
|
320
|
-
set_default(entry, "icon", None, str)
|
|
321
|
-
set_default(entry, "name", None, str)
|
|
322
|
-
set_default(entry, "data", None, str)
|
|
323
|
-
set_default(entry, "note", None, str)
|
|
324
|
-
|
|
325
|
-
# Set defaults for extra consent settings
|
|
326
|
-
consent = extra.setdefault("consent", None)
|
|
327
|
-
if consent:
|
|
328
|
-
set_default(consent, "title", None, str)
|
|
329
|
-
set_default(consent, "description", None, str)
|
|
330
|
-
set_default(consent, "actions", [], list)
|
|
331
|
-
|
|
332
|
-
# Set defaults for extra consent cookie settings
|
|
333
|
-
cookies = consent.setdefault("cookies", {})
|
|
334
|
-
for key, value in cookies.items():
|
|
335
|
-
if isinstance(value, str):
|
|
336
|
-
cookies[key] = {"name": value, "checked": False}
|
|
337
|
-
|
|
338
|
-
# Set defaults for each cookie entry
|
|
339
|
-
set_default(cookies[key], "name", None, str)
|
|
340
|
-
set_default(cookies[key], "checked", False, bool)
|
|
341
|
-
|
|
342
|
-
# Set defaults for extra social settings
|
|
343
|
-
social = extra.setdefault("social", [])
|
|
344
|
-
for entry in social:
|
|
345
|
-
set_default(entry, "icon", None, str)
|
|
346
|
-
set_default(entry, "name", None, str)
|
|
347
|
-
set_default(entry, "link", None, str)
|
|
348
|
-
|
|
349
|
-
# Set defaults for extra alternate settings
|
|
350
|
-
alternate = extra.setdefault("alternate", [])
|
|
351
|
-
for entry in alternate:
|
|
352
|
-
set_default(entry, "name", None, str)
|
|
353
|
-
set_default(entry, "link", None, str)
|
|
354
|
-
set_default(entry, "lang", None, str)
|
|
355
|
-
|
|
356
|
-
# Set defaults for extra version settings
|
|
357
|
-
version = extra.setdefault("version", None)
|
|
358
|
-
if version:
|
|
359
|
-
set_default(version, "provider", None, str)
|
|
360
|
-
set_default(version, "default", None, str)
|
|
361
|
-
set_default(version, "alias", False, bool)
|
|
362
295
|
|
|
363
296
|
# Ensure all non-existent values are all empty strings (for now)
|
|
364
297
|
config["extra"] = _convert_extra(extra)
|
|
@@ -435,19 +368,28 @@ def _apply_defaults(config: dict, path: str) -> dict:
|
|
|
435
368
|
tabbed = config["mdx_configs"].get("pymdownx.tabbed", {})
|
|
436
369
|
if isinstance(tabbed.get("slugify"), dict):
|
|
437
370
|
object = tabbed["slugify"].get("object", "pymdownx.slugs.slugify")
|
|
438
|
-
tabbed["slugify"] = _resolve(object)(**tabbed["slugify"].get("kwds"))
|
|
371
|
+
tabbed["slugify"] = _resolve(object)(**tabbed["slugify"].get("kwds", {}))
|
|
439
372
|
|
|
440
373
|
# Table of contents extension configuration - resolve slugification function
|
|
441
374
|
toc = config["mdx_configs"]["toc"]
|
|
442
375
|
if isinstance(toc.get("slugify"), dict):
|
|
443
376
|
object = toc["slugify"].get("object", "pymdownx.slugs.slugify")
|
|
444
|
-
toc["slugify"] = _resolve(object)(**toc["slugify"].get("kwds"))
|
|
377
|
+
toc["slugify"] = _resolve(object)(**toc["slugify"].get("kwds", {}))
|
|
445
378
|
|
|
446
379
|
# Superfences extension configuration - resolve format function
|
|
447
380
|
superfences = config["mdx_configs"].get("pymdownx.superfences", {})
|
|
448
381
|
for fence in superfences.get("custom_fences", []):
|
|
449
382
|
if isinstance(fence.get("format"), str):
|
|
450
383
|
fence["format"] = _resolve(fence.get("format"))
|
|
384
|
+
elif isinstance(fence.get("format"), dict):
|
|
385
|
+
object = fence["format"].get("object", "pymdownx.superfences.fence_code_format")
|
|
386
|
+
fence["format"] = _resolve(object)(**fence["format"].get("kwds", {}))
|
|
387
|
+
if isinstance(fence.get("validator"), str):
|
|
388
|
+
fence["validator"] = _resolve(fence.get("validator"))
|
|
389
|
+
elif isinstance(fence.get("validator"), dict):
|
|
390
|
+
object = fence["validator"].get("object")
|
|
391
|
+
callable_object = _resolve(object) if object else lambda *args, **kwargs: True
|
|
392
|
+
fence["validator"] = callable_object(**fence["validator"].get("kwds", {}))
|
|
451
393
|
|
|
452
394
|
# Ensure the table of contents title is initialized, as it's used inside
|
|
453
395
|
# the template, and the table of contents extension is always defined
|
|
@@ -456,15 +398,24 @@ def _apply_defaults(config: dict, path: str) -> dict:
|
|
|
456
398
|
|
|
457
399
|
# Convert plugins configuration
|
|
458
400
|
config["plugins"] = _convert_plugins(config.get("plugins", []), config)
|
|
401
|
+
|
|
402
|
+
# mkdocstrings configuration
|
|
403
|
+
if "mkdocstrings" in config["plugins"]:
|
|
404
|
+
mkdocstrings_config = config["plugins"]["mkdocstrings"]["config"]
|
|
405
|
+
if mkdocstrings_config.pop("enabled", True):
|
|
406
|
+
mkdocstrings_config["markdown_extensions"] = [
|
|
407
|
+
{ext: mdx_configs.get(ext, {})} for ext in markdown_extensions
|
|
408
|
+
]
|
|
409
|
+
config["markdown_extensions"].append("mkdocstrings")
|
|
410
|
+
config["mdx_configs"]["mkdocstrings"] = mkdocstrings_config
|
|
411
|
+
|
|
459
412
|
return config
|
|
460
413
|
|
|
461
414
|
|
|
462
415
|
def set_default(
|
|
463
|
-
entry: dict, key: str, default: Any, data_type: type = None
|
|
464
|
-
) ->
|
|
465
|
-
"""
|
|
466
|
-
Set a key to a default value if it isn't set, and optionally cast it to the specified data type.
|
|
467
|
-
"""
|
|
416
|
+
entry: dict, key: str, default: Any, data_type: type | None = None
|
|
417
|
+
) -> Any:
|
|
418
|
+
"""Set a key to a default value if it isn't set, and optionally cast it to the specified data type."""
|
|
468
419
|
if key in entry and entry[key] is None:
|
|
469
420
|
del entry[key]
|
|
470
421
|
|
|
@@ -476,24 +427,22 @@ def set_default(
|
|
|
476
427
|
try:
|
|
477
428
|
entry[key] = data_type(entry[key])
|
|
478
429
|
except (ValueError, TypeError) as e:
|
|
479
|
-
raise ValueError(
|
|
430
|
+
raise ValueError(
|
|
431
|
+
f"Failed to cast key '{key}' to {data_type}: {e}"
|
|
432
|
+
) from e
|
|
480
433
|
|
|
481
434
|
# Return the resulting value
|
|
482
435
|
return entry[key]
|
|
483
436
|
|
|
484
437
|
|
|
485
|
-
def _hash(data:
|
|
486
|
-
"""
|
|
487
|
-
|
|
488
|
-
"""
|
|
489
|
-
hash = hashlib.sha1(pickle.dumps(data))
|
|
438
|
+
def _hash(data: Any) -> int:
|
|
439
|
+
"""Compute a hash for the given data."""
|
|
440
|
+
hash = hashlib.sha1(pickle.dumps(data)) # noqa: S324
|
|
490
441
|
return int(hash.hexdigest(), 16) % (2**64)
|
|
491
442
|
|
|
492
443
|
|
|
493
444
|
def _convert_extra(data: dict | list) -> dict | list:
|
|
494
|
-
"""
|
|
495
|
-
Recursively convert all None values in a dictionary or list to empty strings.
|
|
496
|
-
"""
|
|
445
|
+
"""Recursively convert all None values in a dictionary or list to empty strings."""
|
|
497
446
|
if isinstance(data, dict):
|
|
498
447
|
# Process each key-value pair in the dictionary
|
|
499
448
|
return {
|
|
@@ -502,7 +451,7 @@ def _convert_extra(data: dict | list) -> dict | list:
|
|
|
502
451
|
else ("" if value is None else value)
|
|
503
452
|
for key, value in data.items()
|
|
504
453
|
}
|
|
505
|
-
|
|
454
|
+
if isinstance(data, list):
|
|
506
455
|
# Process each item in the list
|
|
507
456
|
return [
|
|
508
457
|
_convert_extra(item)
|
|
@@ -510,14 +459,11 @@ def _convert_extra(data: dict | list) -> dict | list:
|
|
|
510
459
|
else ("" if item is None else item)
|
|
511
460
|
for item in data
|
|
512
461
|
]
|
|
513
|
-
|
|
514
|
-
return data
|
|
462
|
+
return data
|
|
515
463
|
|
|
516
464
|
|
|
517
|
-
def _resolve(symbol: str):
|
|
518
|
-
"""
|
|
519
|
-
Resolve a symbol to its corresponding Python object.
|
|
520
|
-
"""
|
|
465
|
+
def _resolve(symbol: str) -> Any:
|
|
466
|
+
"""Resolve a symbol to its corresponding Python object."""
|
|
521
467
|
module_path, func_name = symbol.rsplit(".", 1)
|
|
522
468
|
module = importlib.import_module(module_path)
|
|
523
469
|
return getattr(module, func_name)
|
|
@@ -526,17 +472,15 @@ def _resolve(symbol: str):
|
|
|
526
472
|
# -----------------------------------------------------------------------------
|
|
527
473
|
|
|
528
474
|
|
|
529
|
-
def _convert_nav(nav:
|
|
530
|
-
"""
|
|
531
|
-
Convert MkDocs navigation
|
|
532
|
-
"""
|
|
475
|
+
def _convert_nav(nav: list) -> list:
|
|
476
|
+
"""Convert MkDocs navigation."""
|
|
533
477
|
return [_convert_nav_item(entry) for entry in nav]
|
|
534
478
|
|
|
535
479
|
|
|
536
|
-
def _convert_nav_item(item: str | dict | list) -> dict:
|
|
537
|
-
"""
|
|
538
|
-
|
|
539
|
-
|
|
480
|
+
def _convert_nav_item(item: str | dict | list) -> dict | list:
|
|
481
|
+
"""Convert MkDocs shorthand navigation structure into something more manageable.
|
|
482
|
+
|
|
483
|
+
We need to annotate each item with a title, URL, icon, and children.
|
|
540
484
|
"""
|
|
541
485
|
if isinstance(item, str):
|
|
542
486
|
return {
|
|
@@ -550,19 +494,19 @@ def _convert_nav_item(item: str | dict | list) -> dict:
|
|
|
550
494
|
}
|
|
551
495
|
|
|
552
496
|
# Handle Title: URL
|
|
553
|
-
|
|
497
|
+
if isinstance(item, dict):
|
|
554
498
|
for title, value in item.items():
|
|
555
499
|
if isinstance(value, str):
|
|
556
500
|
return {
|
|
557
501
|
"title": str(title),
|
|
558
|
-
"url": value,
|
|
502
|
+
"url": value.strip(),
|
|
559
503
|
"canonical_url": None,
|
|
560
504
|
"meta": None,
|
|
561
505
|
"children": [],
|
|
562
|
-
"is_index": _is_index(value),
|
|
506
|
+
"is_index": _is_index(value.strip()),
|
|
563
507
|
"active": False,
|
|
564
508
|
}
|
|
565
|
-
|
|
509
|
+
if isinstance(value, list):
|
|
566
510
|
return {
|
|
567
511
|
"title": str(title),
|
|
568
512
|
"url": None,
|
|
@@ -572,28 +516,25 @@ def _convert_nav_item(item: str | dict | list) -> dict:
|
|
|
572
516
|
"is_index": False,
|
|
573
517
|
"active": False,
|
|
574
518
|
}
|
|
519
|
+
raise TypeError(f"Unknown nav item value type: {type(value)}")
|
|
575
520
|
|
|
576
521
|
# Handle a list of items
|
|
577
522
|
elif isinstance(item, list):
|
|
578
523
|
return [_convert_nav_item(child) for child in item]
|
|
579
|
-
|
|
580
|
-
|
|
524
|
+
|
|
525
|
+
raise TypeError(f"Unknown nav item type: {type(item)}")
|
|
581
526
|
|
|
582
527
|
|
|
583
528
|
def _is_index(path: str) -> bool:
|
|
584
|
-
"""
|
|
585
|
-
|
|
586
|
-
"""
|
|
587
|
-
return path.endswith(("index.md", "README.md"))
|
|
529
|
+
"""Returns, whether the given path points to a section index."""
|
|
530
|
+
return os.path.basename(path) in ("index.md", "README.md")
|
|
588
531
|
|
|
589
532
|
|
|
590
533
|
# -----------------------------------------------------------------------------
|
|
591
534
|
|
|
592
535
|
|
|
593
|
-
def _convert_extra_javascript(value: list
|
|
594
|
-
"""
|
|
595
|
-
Ensure extra_javascript uses a structured format.
|
|
596
|
-
"""
|
|
536
|
+
def _convert_extra_javascript(value: list) -> list:
|
|
537
|
+
"""Ensure extra_javascript uses a structured format."""
|
|
597
538
|
for i, item in enumerate(value):
|
|
598
539
|
if isinstance(item, str):
|
|
599
540
|
value[i] = {
|
|
@@ -608,9 +549,7 @@ def _convert_extra_javascript(value: list[any]) -> list:
|
|
|
608
549
|
item.setdefault("async", False)
|
|
609
550
|
item.setdefault("defer", False)
|
|
610
551
|
else:
|
|
611
|
-
raise
|
|
612
|
-
f"Unknown extra_javascript item type: {type(item)}"
|
|
613
|
-
)
|
|
552
|
+
raise TypeError(f"Unknown extra_javascript item type: {type(item)}")
|
|
614
553
|
|
|
615
554
|
# Return resulting value
|
|
616
555
|
return value
|
|
@@ -619,12 +558,10 @@ def _convert_extra_javascript(value: list[any]) -> list:
|
|
|
619
558
|
# -----------------------------------------------------------------------------
|
|
620
559
|
|
|
621
560
|
|
|
622
|
-
def _convert_markdown_extensions(value:
|
|
623
|
-
"""
|
|
624
|
-
Convert Markdown extensions configuration to what Python Markdown expects.
|
|
625
|
-
"""
|
|
561
|
+
def _convert_markdown_extensions(value: Any) -> tuple[list[str], dict]:
|
|
562
|
+
"""Convert Markdown extensions configuration to what Python Markdown expects."""
|
|
626
563
|
markdown_extensions = ["toc", "tables"]
|
|
627
|
-
mdx_configs = {"toc": {}, "tables": {}}
|
|
564
|
+
mdx_configs: dict[str, dict[str, Any]] = {"toc": {}, "tables": {}}
|
|
628
565
|
|
|
629
566
|
# In case of Python Markdown Extensions, we allow to omit the necessary
|
|
630
567
|
# quotes around the extension names, so we need to hoist the extensions
|
|
@@ -632,14 +569,24 @@ def _convert_markdown_extensions(value: any):
|
|
|
632
569
|
# actually parse the configuration.
|
|
633
570
|
if "pymdownx" in value:
|
|
634
571
|
pymdownx = value.pop("pymdownx")
|
|
635
|
-
for ext,
|
|
572
|
+
for ext, conf in pymdownx.items():
|
|
636
573
|
# Special case for blocks extension, which has another level of
|
|
637
574
|
# nesting. This is the only extension that requires this.
|
|
638
575
|
if ext == "blocks":
|
|
639
|
-
for block, config in
|
|
576
|
+
for block, config in conf.items():
|
|
640
577
|
value[f"pymdownx.{ext}.{block}"] = config
|
|
641
578
|
else:
|
|
642
|
-
value[f"pymdownx.{ext}"] =
|
|
579
|
+
value[f"pymdownx.{ext}"] = conf
|
|
580
|
+
|
|
581
|
+
# Same as for Python Markdown extensions, see above
|
|
582
|
+
if "zensical" in value:
|
|
583
|
+
zensical = value.pop("zensical")
|
|
584
|
+
for ext, conf in zensical.items():
|
|
585
|
+
if ext == "extensions":
|
|
586
|
+
for key, config in conf.items():
|
|
587
|
+
value[f"zensical.{ext}.{key}"] = config
|
|
588
|
+
else:
|
|
589
|
+
value[f"zensical.{ext}"] = conf
|
|
643
590
|
|
|
644
591
|
# Extensions can be defined as a dict
|
|
645
592
|
if isinstance(value, dict):
|
|
@@ -664,16 +611,13 @@ def _convert_markdown_extensions(value: any):
|
|
|
664
611
|
# ----------------------------------------------------------------------------
|
|
665
612
|
|
|
666
613
|
|
|
667
|
-
def _convert_plugins(value:
|
|
668
|
-
"""
|
|
669
|
-
Convert plugins configuration to something we can work with.
|
|
670
|
-
"""
|
|
614
|
+
def _convert_plugins(value: Any, config: dict) -> dict:
|
|
615
|
+
"""Convert plugins configuration to something we can work with."""
|
|
671
616
|
plugins = {}
|
|
672
617
|
|
|
673
618
|
# Plugins can be defined as a dict
|
|
674
619
|
if isinstance(value, dict):
|
|
675
|
-
|
|
676
|
-
plugins[name] = data
|
|
620
|
+
plugins.update(value)
|
|
677
621
|
|
|
678
622
|
# Plugins can also be defined as a list
|
|
679
623
|
else:
|
|
@@ -730,8 +674,7 @@ def _convert_plugins(value: any, config: dict) -> list:
|
|
|
730
674
|
def _yaml_load(
|
|
731
675
|
source: IO, loader: type[BaseLoader] | None = None
|
|
732
676
|
) -> dict[str, Any]:
|
|
733
|
-
"""
|
|
734
|
-
Load configuration file and resolve environment variables and parent files.
|
|
677
|
+
"""Load configuration file and resolve environment variables and parent files.
|
|
735
678
|
|
|
736
679
|
Note that INHERIT is only a bandaid that was introduced to allow for some
|
|
737
680
|
degree of modularity, but with serious shortcomings. Zensical will use a
|
|
@@ -746,12 +689,12 @@ def _yaml_load(
|
|
|
746
689
|
source.read()
|
|
747
690
|
.replace("material.extensions", "zensical.extensions")
|
|
748
691
|
.replace("materialx", "zensical.extensions"),
|
|
749
|
-
Loader=Loader,
|
|
692
|
+
Loader=Loader, # noqa: S506
|
|
750
693
|
)
|
|
751
694
|
except YAMLError as e:
|
|
752
695
|
raise ConfigurationError(
|
|
753
696
|
f"Encountered an error parsing the configuration file: {e}"
|
|
754
|
-
)
|
|
697
|
+
) from e
|
|
755
698
|
if config is None:
|
|
756
699
|
return {}
|
|
757
700
|
|
|
@@ -765,7 +708,7 @@ def _yaml_load(
|
|
|
765
708
|
raise ConfigurationError(
|
|
766
709
|
f"Inherited config file '{relpath}' doesn't exist at '{abspath}'."
|
|
767
710
|
)
|
|
768
|
-
with open(abspath, "
|
|
711
|
+
with open(abspath, encoding="utf-8") as fd:
|
|
769
712
|
parent = _yaml_load(fd, loader)
|
|
770
713
|
config = always_merger.merge(parent, config)
|
|
771
714
|
|
|
@@ -773,9 +716,11 @@ def _yaml_load(
|
|
|
773
716
|
return config
|
|
774
717
|
|
|
775
718
|
|
|
776
|
-
def _construct_env_tag(
|
|
777
|
-
|
|
778
|
-
|
|
719
|
+
def _construct_env_tag(
|
|
720
|
+
loader: yaml.Loader,
|
|
721
|
+
node: yaml.ScalarNode | yaml.SequenceNode | yaml.MappingNode,
|
|
722
|
+
) -> Any:
|
|
723
|
+
"""Assign value of ENV variable referenced at node.
|
|
779
724
|
|
|
780
725
|
MkDocs supports the use of !ENV to reference environment variables in YAML
|
|
781
726
|
configuration files. We won't likely support this in Zensical, but for now
|
|
@@ -804,7 +749,7 @@ def _construct_env_tag(loader: yaml.Loader, node: yaml.Node):
|
|
|
804
749
|
else:
|
|
805
750
|
raise ConstructorError(
|
|
806
751
|
context=f"expected a scalar or sequence node, but found {node.id}",
|
|
807
|
-
|
|
752
|
+
context_mark=node.start_mark,
|
|
808
753
|
)
|
|
809
754
|
|
|
810
755
|
# Resolve environment variable
|