zensical 0.0.7__cp310-abi3-macosx_10_12_x86_64.whl → 0.0.12__cp310-abi3-macosx_10_12_x86_64.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 +151 -177
- zensical/extensions/emoji.py +20 -25
- zensical/extensions/links.py +31 -22
- zensical/extensions/preview.py +27 -39
- zensical/extensions/search.py +81 -81
- zensical/extensions/utilities/filter.py +3 -8
- zensical/main.py +34 -71
- zensical/markdown.py +19 -18
- zensical/templates/assets/javascripts/bundle.21aa498e.min.js +3 -0
- 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 +4 -4
- zensical/templates/partials/javascripts/base.html +1 -1
- zensical/templates/partials/nav-item.html +1 -1
- zensical/zensical.abi3.so +0 -0
- zensical/zensical.pyi +5 -11
- {zensical-0.0.7.dist-info → zensical-0.0.12.dist-info}/METADATA +6 -2
- {zensical-0.0.7.dist-info → zensical-0.0.12.dist-info}/RECORD +25 -25
- {zensical-0.0.7.dist-info → zensical-0.0.12.dist-info}/WHEEL +1 -1
- zensical/templates/assets/javascripts/bundle.6b62de02.min.js +0 -3
- zensical/templates/assets/stylesheets/classic/main.6eec86b3.min.css +0 -1
- zensical/templates/assets/stylesheets/modern/main.21338c02.min.css +0 -1
- {zensical-0.0.7.dist-info → zensical-0.0.12.dist-info}/entry_points.txt +0 -0
- {zensical-0.0.7.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
|
@@ -23,24 +23,26 @@
|
|
|
23
23
|
|
|
24
24
|
from __future__ import annotations
|
|
25
25
|
|
|
26
|
+
import hashlib
|
|
26
27
|
import importlib
|
|
27
28
|
import os
|
|
28
|
-
import
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
import tomllib
|
|
32
|
-
except ModuleNotFoundError:
|
|
33
|
-
import tomli as tomllib # type: ignore
|
|
29
|
+
import pickle
|
|
30
|
+
from typing import IO, Any
|
|
31
|
+
from urllib.parse import urlparse
|
|
34
32
|
|
|
33
|
+
import yaml
|
|
35
34
|
from click import ClickException
|
|
36
35
|
from deepmerge import always_merger
|
|
37
|
-
from functools import partial
|
|
38
|
-
from typing import Any, IO
|
|
39
36
|
from yaml import BaseLoader, Loader, YAMLError
|
|
40
37
|
from yaml.constructor import ConstructorError
|
|
41
|
-
from urllib.parse import urlparse
|
|
42
38
|
|
|
43
|
-
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
|
+
|
|
44
46
|
|
|
45
47
|
# ----------------------------------------------------------------------------
|
|
46
48
|
# Globals
|
|
@@ -63,9 +65,7 @@ side, and use it directly when needed. It's a hack but will do for now.
|
|
|
63
65
|
|
|
64
66
|
|
|
65
67
|
class ConfigurationError(ClickException):
|
|
66
|
-
"""
|
|
67
|
-
Configuration resolution or validation failed.
|
|
68
|
-
"""
|
|
68
|
+
"""Configuration resolution or validation failed."""
|
|
69
69
|
|
|
70
70
|
|
|
71
71
|
# ----------------------------------------------------------------------------
|
|
@@ -74,20 +74,17 @@ class ConfigurationError(ClickException):
|
|
|
74
74
|
|
|
75
75
|
|
|
76
76
|
def parse_config(path: str) -> dict:
|
|
77
|
-
"""
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if
|
|
77
|
+
"""Parse configuration file."""
|
|
78
|
+
# Decide by extension; no need to convert to Path
|
|
79
|
+
_, ext = os.path.splitext(path)
|
|
80
|
+
if ext.lower() == ".toml":
|
|
81
81
|
return parse_zensical_config(path)
|
|
82
|
-
|
|
83
|
-
return parse_mkdocs_config(path)
|
|
82
|
+
return parse_mkdocs_config(path)
|
|
84
83
|
|
|
85
84
|
|
|
86
85
|
def parse_zensical_config(path: str) -> dict:
|
|
87
|
-
"""
|
|
88
|
-
|
|
89
|
-
"""
|
|
90
|
-
global _CONFIG
|
|
86
|
+
"""Parse zensical.toml configuration file."""
|
|
87
|
+
global _CONFIG # noqa: PLW0603
|
|
91
88
|
with open(path, "rb") as f:
|
|
92
89
|
config = tomllib.load(f)
|
|
93
90
|
if "project" in config:
|
|
@@ -99,11 +96,9 @@ def parse_zensical_config(path: str) -> dict:
|
|
|
99
96
|
|
|
100
97
|
|
|
101
98
|
def parse_mkdocs_config(path: str) -> dict:
|
|
102
|
-
"""
|
|
103
|
-
|
|
104
|
-
""
|
|
105
|
-
global _CONFIG
|
|
106
|
-
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:
|
|
107
102
|
config = _yaml_load(f)
|
|
108
103
|
|
|
109
104
|
# Apply defaults and return parsed configuration
|
|
@@ -111,17 +106,20 @@ def parse_mkdocs_config(path: str) -> dict:
|
|
|
111
106
|
return _CONFIG
|
|
112
107
|
|
|
113
108
|
|
|
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]
|
|
113
|
+
|
|
114
|
+
|
|
114
115
|
def get_theme_dir() -> str:
|
|
115
|
-
"""
|
|
116
|
-
Return the theme directory.
|
|
117
|
-
"""
|
|
116
|
+
"""Return the theme directory."""
|
|
118
117
|
path = os.path.dirname(os.path.abspath(__file__))
|
|
119
118
|
return os.path.join(path, "templates")
|
|
120
119
|
|
|
121
120
|
|
|
122
121
|
def _apply_defaults(config: dict, path: str) -> dict:
|
|
123
|
-
"""
|
|
124
|
-
Apply default settings in configuration.
|
|
122
|
+
"""Apply default settings in configuration.
|
|
125
123
|
|
|
126
124
|
Note that this is loosely based on the defaults that MkDocs sets in its own
|
|
127
125
|
configuration system, which we won't port for compatibility right now, as
|
|
@@ -136,12 +134,12 @@ def _apply_defaults(config: dict, path: str) -> dict:
|
|
|
136
134
|
|
|
137
135
|
# Set site directory
|
|
138
136
|
set_default(config, "site_dir", "site", str)
|
|
139
|
-
if ".." in config.get("site_dir"):
|
|
137
|
+
if ".." in config.get("site_dir", ""):
|
|
140
138
|
raise ConfigurationError("site_dir must not contain '..'")
|
|
141
139
|
|
|
142
140
|
# Set docs directory
|
|
143
141
|
set_default(config, "docs_dir", "docs", str)
|
|
144
|
-
if ".." in config.get("docs_dir"):
|
|
142
|
+
if ".." in config.get("docs_dir", ""):
|
|
145
143
|
raise ConfigurationError("docs_dir must not contain '..'")
|
|
146
144
|
|
|
147
145
|
# Set defaults for core settings
|
|
@@ -159,21 +157,26 @@ def _apply_defaults(config: dict, path: str) -> dict:
|
|
|
159
157
|
set_default(config, "edit_uri", None, str)
|
|
160
158
|
|
|
161
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
|
+
}
|
|
162
171
|
repo_url = config.get("repo_url")
|
|
163
|
-
if repo_url
|
|
164
|
-
docs_dir = config.get("docs_dir")
|
|
172
|
+
if repo_url:
|
|
165
173
|
host = urlparse(repo_url).hostname or ""
|
|
166
|
-
if host
|
|
167
|
-
set_default(config, "repo_name",
|
|
168
|
-
set_default(config, "edit_uri", f"edit/master/{docs_dir}", str)
|
|
169
|
-
elif host == "gitlab.com":
|
|
170
|
-
set_default(config, "repo_name", "GitLab", str)
|
|
171
|
-
set_default(config, "edit_uri", f"edit/master/{docs_dir}", str)
|
|
172
|
-
elif host == "bitbucket.org":
|
|
173
|
-
set_default(config, "repo_name", "Bitbucket", str)
|
|
174
|
-
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)
|
|
175
176
|
elif host:
|
|
176
177
|
config["repo_name"] = host.split(".")[0].title()
|
|
178
|
+
if host in edit_uris:
|
|
179
|
+
set_default(config, "edit_uri", edit_uris[host], str)
|
|
177
180
|
|
|
178
181
|
# Remove trailing slash from edit_uri if present
|
|
179
182
|
edit_uri = config.get("edit_uri")
|
|
@@ -282,73 +285,13 @@ def _apply_defaults(config: dict, path: str) -> dict:
|
|
|
282
285
|
set_default(toggle, "name", None, str)
|
|
283
286
|
|
|
284
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.")
|
|
285
290
|
extra = set_default(config, "extra", {}, dict)
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
set_default(extra, "tags", {}, dict)
|
|
290
|
-
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.")
|
|
291
294
|
set_default(extra, "polyfills", [], list)
|
|
292
|
-
set_default(extra, "analytics", None, dict)
|
|
293
|
-
|
|
294
|
-
# Set defaults for extra analytics settings
|
|
295
|
-
analytics = extra.get("analytics")
|
|
296
|
-
if analytics:
|
|
297
|
-
set_default(analytics, "provider", None, str)
|
|
298
|
-
set_default(analytics, "property", None, str)
|
|
299
|
-
set_default(analytics, "feedback", None, dict)
|
|
300
|
-
|
|
301
|
-
# Set defaults for extra analytics feedback settings
|
|
302
|
-
feedback = analytics.get("feedback")
|
|
303
|
-
if feedback:
|
|
304
|
-
set_default(feedback, "title", None, str)
|
|
305
|
-
set_default(feedback, "ratings", [], list)
|
|
306
|
-
|
|
307
|
-
# Set defaults for each rating entry
|
|
308
|
-
ratings = feedback.setdefault("ratings", [])
|
|
309
|
-
for entry in ratings:
|
|
310
|
-
set_default(entry, "icon", None, str)
|
|
311
|
-
set_default(entry, "name", None, str)
|
|
312
|
-
set_default(entry, "data", None, str)
|
|
313
|
-
set_default(entry, "note", None, str)
|
|
314
|
-
|
|
315
|
-
# Set defaults for extra consent settings
|
|
316
|
-
consent = extra.setdefault("consent", None)
|
|
317
|
-
if consent:
|
|
318
|
-
set_default(consent, "title", None, str)
|
|
319
|
-
set_default(consent, "description", None, str)
|
|
320
|
-
set_default(consent, "actions", [], list)
|
|
321
|
-
|
|
322
|
-
# Set defaults for extra consent cookie settings
|
|
323
|
-
cookies = consent.setdefault("cookies", {})
|
|
324
|
-
for key, value in cookies.items():
|
|
325
|
-
if isinstance(value, str):
|
|
326
|
-
cookies[key] = {"name": value, "checked": False}
|
|
327
|
-
|
|
328
|
-
# Set defaults for each cookie entry
|
|
329
|
-
set_default(cookies[key], "name", None, str)
|
|
330
|
-
set_default(cookies[key], "checked", False, bool)
|
|
331
|
-
|
|
332
|
-
# Set defaults for extra social settings
|
|
333
|
-
social = extra.setdefault("social", [])
|
|
334
|
-
for entry in social:
|
|
335
|
-
set_default(entry, "icon", None, str)
|
|
336
|
-
set_default(entry, "name", None, str)
|
|
337
|
-
set_default(entry, "link", None, str)
|
|
338
|
-
|
|
339
|
-
# Set defaults for extra alternate settings
|
|
340
|
-
alternate = extra.setdefault("alternate", [])
|
|
341
|
-
for entry in alternate:
|
|
342
|
-
set_default(entry, "name", None, str)
|
|
343
|
-
set_default(entry, "link", None, str)
|
|
344
|
-
set_default(entry, "lang", None, str)
|
|
345
|
-
|
|
346
|
-
# Set defaults for extra version settings
|
|
347
|
-
version = extra.setdefault("version", None)
|
|
348
|
-
if version:
|
|
349
|
-
set_default(version, "provider", None, str)
|
|
350
|
-
set_default(version, "default", None, str)
|
|
351
|
-
set_default(version, "alias", False, bool)
|
|
352
295
|
|
|
353
296
|
# Ensure all non-existent values are all empty strings (for now)
|
|
354
297
|
config["extra"] = _convert_extra(extra)
|
|
@@ -425,31 +368,54 @@ def _apply_defaults(config: dict, path: str) -> dict:
|
|
|
425
368
|
tabbed = config["mdx_configs"].get("pymdownx.tabbed", {})
|
|
426
369
|
if isinstance(tabbed.get("slugify"), dict):
|
|
427
370
|
object = tabbed["slugify"].get("object", "pymdownx.slugs.slugify")
|
|
428
|
-
tabbed["slugify"] =
|
|
429
|
-
|
|
430
|
-
|
|
371
|
+
tabbed["slugify"] = _resolve(object)(**tabbed["slugify"].get("kwds", {}))
|
|
372
|
+
|
|
373
|
+
# Table of contents extension configuration - resolve slugification function
|
|
374
|
+
toc = config["mdx_configs"]["toc"]
|
|
375
|
+
if isinstance(toc.get("slugify"), dict):
|
|
376
|
+
object = toc["slugify"].get("object", "pymdownx.slugs.slugify")
|
|
377
|
+
toc["slugify"] = _resolve(object)(**toc["slugify"].get("kwds", {}))
|
|
431
378
|
|
|
432
379
|
# Superfences extension configuration - resolve format function
|
|
433
380
|
superfences = config["mdx_configs"].get("pymdownx.superfences", {})
|
|
434
381
|
for fence in superfences.get("custom_fences", []):
|
|
435
382
|
if isinstance(fence.get("format"), str):
|
|
436
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", {}))
|
|
437
393
|
|
|
438
394
|
# Ensure the table of contents title is initialized, as it's used inside
|
|
439
395
|
# the template, and the table of contents extension is always defined
|
|
440
396
|
config["mdx_configs"]["toc"].setdefault("title", None)
|
|
397
|
+
config["mdx_configs_hash"] = _hash(mdx_configs)
|
|
441
398
|
|
|
442
399
|
# Convert plugins configuration
|
|
443
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
|
+
|
|
444
412
|
return config
|
|
445
413
|
|
|
446
414
|
|
|
447
415
|
def set_default(
|
|
448
|
-
entry: dict, key: str, default: Any, data_type: type = None
|
|
449
|
-
) ->
|
|
450
|
-
"""
|
|
451
|
-
Set a key to a default value if it isn't set, and optionally cast it to the specified data type.
|
|
452
|
-
"""
|
|
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."""
|
|
453
419
|
if key in entry and entry[key] is None:
|
|
454
420
|
del entry[key]
|
|
455
421
|
|
|
@@ -461,16 +427,22 @@ def set_default(
|
|
|
461
427
|
try:
|
|
462
428
|
entry[key] = data_type(entry[key])
|
|
463
429
|
except (ValueError, TypeError) as e:
|
|
464
|
-
raise ValueError(
|
|
430
|
+
raise ValueError(
|
|
431
|
+
f"Failed to cast key '{key}' to {data_type}: {e}"
|
|
432
|
+
) from e
|
|
465
433
|
|
|
466
434
|
# Return the resulting value
|
|
467
435
|
return entry[key]
|
|
468
436
|
|
|
469
437
|
|
|
438
|
+
def _hash(data: Any) -> int:
|
|
439
|
+
"""Compute a hash for the given data."""
|
|
440
|
+
hash = hashlib.sha1(pickle.dumps(data)) # noqa: S324
|
|
441
|
+
return int(hash.hexdigest(), 16) % (2**64)
|
|
442
|
+
|
|
443
|
+
|
|
470
444
|
def _convert_extra(data: dict | list) -> dict | list:
|
|
471
|
-
"""
|
|
472
|
-
Recursively convert all None values in a dictionary or list to empty strings.
|
|
473
|
-
"""
|
|
445
|
+
"""Recursively convert all None values in a dictionary or list to empty strings."""
|
|
474
446
|
if isinstance(data, dict):
|
|
475
447
|
# Process each key-value pair in the dictionary
|
|
476
448
|
return {
|
|
@@ -479,7 +451,7 @@ def _convert_extra(data: dict | list) -> dict | list:
|
|
|
479
451
|
else ("" if value is None else value)
|
|
480
452
|
for key, value in data.items()
|
|
481
453
|
}
|
|
482
|
-
|
|
454
|
+
if isinstance(data, list):
|
|
483
455
|
# Process each item in the list
|
|
484
456
|
return [
|
|
485
457
|
_convert_extra(item)
|
|
@@ -487,14 +459,11 @@ def _convert_extra(data: dict | list) -> dict | list:
|
|
|
487
459
|
else ("" if item is None else item)
|
|
488
460
|
for item in data
|
|
489
461
|
]
|
|
490
|
-
|
|
491
|
-
return data
|
|
462
|
+
return data
|
|
492
463
|
|
|
493
464
|
|
|
494
|
-
def _resolve(symbol: str):
|
|
495
|
-
"""
|
|
496
|
-
Resolve a symbol to its corresponding Python object.
|
|
497
|
-
"""
|
|
465
|
+
def _resolve(symbol: str) -> Any:
|
|
466
|
+
"""Resolve a symbol to its corresponding Python object."""
|
|
498
467
|
module_path, func_name = symbol.rsplit(".", 1)
|
|
499
468
|
module = importlib.import_module(module_path)
|
|
500
469
|
return getattr(module, func_name)
|
|
@@ -503,17 +472,15 @@ def _resolve(symbol: str):
|
|
|
503
472
|
# -----------------------------------------------------------------------------
|
|
504
473
|
|
|
505
474
|
|
|
506
|
-
def _convert_nav(nav:
|
|
507
|
-
"""
|
|
508
|
-
Convert MkDocs navigation
|
|
509
|
-
"""
|
|
475
|
+
def _convert_nav(nav: list) -> list:
|
|
476
|
+
"""Convert MkDocs navigation."""
|
|
510
477
|
return [_convert_nav_item(entry) for entry in nav]
|
|
511
478
|
|
|
512
479
|
|
|
513
|
-
def _convert_nav_item(item: str | dict | list) -> dict:
|
|
514
|
-
"""
|
|
515
|
-
|
|
516
|
-
|
|
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.
|
|
517
484
|
"""
|
|
518
485
|
if isinstance(item, str):
|
|
519
486
|
return {
|
|
@@ -527,19 +494,19 @@ def _convert_nav_item(item: str | dict | list) -> dict:
|
|
|
527
494
|
}
|
|
528
495
|
|
|
529
496
|
# Handle Title: URL
|
|
530
|
-
|
|
497
|
+
if isinstance(item, dict):
|
|
531
498
|
for title, value in item.items():
|
|
532
499
|
if isinstance(value, str):
|
|
533
500
|
return {
|
|
534
501
|
"title": str(title),
|
|
535
|
-
"url": value,
|
|
502
|
+
"url": value.strip(),
|
|
536
503
|
"canonical_url": None,
|
|
537
504
|
"meta": None,
|
|
538
505
|
"children": [],
|
|
539
|
-
"is_index": _is_index(value),
|
|
506
|
+
"is_index": _is_index(value.strip()),
|
|
540
507
|
"active": False,
|
|
541
508
|
}
|
|
542
|
-
|
|
509
|
+
if isinstance(value, list):
|
|
543
510
|
return {
|
|
544
511
|
"title": str(title),
|
|
545
512
|
"url": None,
|
|
@@ -549,28 +516,25 @@ def _convert_nav_item(item: str | dict | list) -> dict:
|
|
|
549
516
|
"is_index": False,
|
|
550
517
|
"active": False,
|
|
551
518
|
}
|
|
519
|
+
raise TypeError(f"Unknown nav item value type: {type(value)}")
|
|
552
520
|
|
|
553
521
|
# Handle a list of items
|
|
554
522
|
elif isinstance(item, list):
|
|
555
523
|
return [_convert_nav_item(child) for child in item]
|
|
556
|
-
|
|
557
|
-
|
|
524
|
+
|
|
525
|
+
raise TypeError(f"Unknown nav item type: {type(item)}")
|
|
558
526
|
|
|
559
527
|
|
|
560
528
|
def _is_index(path: str) -> bool:
|
|
561
|
-
"""
|
|
562
|
-
|
|
563
|
-
"""
|
|
564
|
-
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")
|
|
565
531
|
|
|
566
532
|
|
|
567
533
|
# -----------------------------------------------------------------------------
|
|
568
534
|
|
|
569
535
|
|
|
570
|
-
def _convert_extra_javascript(value: list
|
|
571
|
-
"""
|
|
572
|
-
Ensure extra_javascript uses a structured format.
|
|
573
|
-
"""
|
|
536
|
+
def _convert_extra_javascript(value: list) -> list:
|
|
537
|
+
"""Ensure extra_javascript uses a structured format."""
|
|
574
538
|
for i, item in enumerate(value):
|
|
575
539
|
if isinstance(item, str):
|
|
576
540
|
value[i] = {
|
|
@@ -585,9 +549,7 @@ def _convert_extra_javascript(value: list[any]) -> list:
|
|
|
585
549
|
item.setdefault("async", False)
|
|
586
550
|
item.setdefault("defer", False)
|
|
587
551
|
else:
|
|
588
|
-
raise
|
|
589
|
-
f"Unknown extra_javascript item type: {type(item)}"
|
|
590
|
-
)
|
|
552
|
+
raise TypeError(f"Unknown extra_javascript item type: {type(item)}")
|
|
591
553
|
|
|
592
554
|
# Return resulting value
|
|
593
555
|
return value
|
|
@@ -596,12 +558,10 @@ def _convert_extra_javascript(value: list[any]) -> list:
|
|
|
596
558
|
# -----------------------------------------------------------------------------
|
|
597
559
|
|
|
598
560
|
|
|
599
|
-
def _convert_markdown_extensions(value:
|
|
600
|
-
"""
|
|
601
|
-
Convert Markdown extensions configuration to what Python Markdown expects.
|
|
602
|
-
"""
|
|
561
|
+
def _convert_markdown_extensions(value: Any) -> tuple[list[str], dict]:
|
|
562
|
+
"""Convert Markdown extensions configuration to what Python Markdown expects."""
|
|
603
563
|
markdown_extensions = ["toc", "tables"]
|
|
604
|
-
mdx_configs = {"toc": {}, "tables": {}}
|
|
564
|
+
mdx_configs: dict[str, dict[str, Any]] = {"toc": {}, "tables": {}}
|
|
605
565
|
|
|
606
566
|
# In case of Python Markdown Extensions, we allow to omit the necessary
|
|
607
567
|
# quotes around the extension names, so we need to hoist the extensions
|
|
@@ -609,8 +569,24 @@ def _convert_markdown_extensions(value: any):
|
|
|
609
569
|
# actually parse the configuration.
|
|
610
570
|
if "pymdownx" in value:
|
|
611
571
|
pymdownx = value.pop("pymdownx")
|
|
612
|
-
for ext,
|
|
613
|
-
|
|
572
|
+
for ext, conf in pymdownx.items():
|
|
573
|
+
# Special case for blocks extension, which has another level of
|
|
574
|
+
# nesting. This is the only extension that requires this.
|
|
575
|
+
if ext == "blocks":
|
|
576
|
+
for block, config in conf.items():
|
|
577
|
+
value[f"pymdownx.{ext}.{block}"] = config
|
|
578
|
+
else:
|
|
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
|
|
614
590
|
|
|
615
591
|
# Extensions can be defined as a dict
|
|
616
592
|
if isinstance(value, dict):
|
|
@@ -635,16 +611,13 @@ def _convert_markdown_extensions(value: any):
|
|
|
635
611
|
# ----------------------------------------------------------------------------
|
|
636
612
|
|
|
637
613
|
|
|
638
|
-
def _convert_plugins(value:
|
|
639
|
-
"""
|
|
640
|
-
Convert plugins configuration to something we can work with.
|
|
641
|
-
"""
|
|
614
|
+
def _convert_plugins(value: Any, config: dict) -> dict:
|
|
615
|
+
"""Convert plugins configuration to something we can work with."""
|
|
642
616
|
plugins = {}
|
|
643
617
|
|
|
644
618
|
# Plugins can be defined as a dict
|
|
645
619
|
if isinstance(value, dict):
|
|
646
|
-
|
|
647
|
-
plugins[name] = data
|
|
620
|
+
plugins.update(value)
|
|
648
621
|
|
|
649
622
|
# Plugins can also be defined as a list
|
|
650
623
|
else:
|
|
@@ -701,8 +674,7 @@ def _convert_plugins(value: any, config: dict) -> list:
|
|
|
701
674
|
def _yaml_load(
|
|
702
675
|
source: IO, loader: type[BaseLoader] | None = None
|
|
703
676
|
) -> dict[str, Any]:
|
|
704
|
-
"""
|
|
705
|
-
Load configuration file and resolve environment variables and parent files.
|
|
677
|
+
"""Load configuration file and resolve environment variables and parent files.
|
|
706
678
|
|
|
707
679
|
Note that INHERIT is only a bandaid that was introduced to allow for some
|
|
708
680
|
degree of modularity, but with serious shortcomings. Zensical will use a
|
|
@@ -717,12 +689,12 @@ def _yaml_load(
|
|
|
717
689
|
source.read()
|
|
718
690
|
.replace("material.extensions", "zensical.extensions")
|
|
719
691
|
.replace("materialx", "zensical.extensions"),
|
|
720
|
-
Loader=Loader,
|
|
692
|
+
Loader=Loader, # noqa: S506
|
|
721
693
|
)
|
|
722
694
|
except YAMLError as e:
|
|
723
695
|
raise ConfigurationError(
|
|
724
696
|
f"Encountered an error parsing the configuration file: {e}"
|
|
725
|
-
)
|
|
697
|
+
) from e
|
|
726
698
|
if config is None:
|
|
727
699
|
return {}
|
|
728
700
|
|
|
@@ -736,7 +708,7 @@ def _yaml_load(
|
|
|
736
708
|
raise ConfigurationError(
|
|
737
709
|
f"Inherited config file '{relpath}' doesn't exist at '{abspath}'."
|
|
738
710
|
)
|
|
739
|
-
with open(abspath, "
|
|
711
|
+
with open(abspath, encoding="utf-8") as fd:
|
|
740
712
|
parent = _yaml_load(fd, loader)
|
|
741
713
|
config = always_merger.merge(parent, config)
|
|
742
714
|
|
|
@@ -744,9 +716,11 @@ def _yaml_load(
|
|
|
744
716
|
return config
|
|
745
717
|
|
|
746
718
|
|
|
747
|
-
def _construct_env_tag(
|
|
748
|
-
|
|
749
|
-
|
|
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.
|
|
750
724
|
|
|
751
725
|
MkDocs supports the use of !ENV to reference environment variables in YAML
|
|
752
726
|
configuration files. We won't likely support this in Zensical, but for now
|
|
@@ -775,7 +749,7 @@ def _construct_env_tag(loader: yaml.Loader, node: yaml.Node):
|
|
|
775
749
|
else:
|
|
776
750
|
raise ConstructorError(
|
|
777
751
|
context=f"expected a scalar or sequence node, but found {node.id}",
|
|
778
|
-
|
|
752
|
+
context_mark=node.start_mark,
|
|
779
753
|
)
|
|
780
754
|
|
|
781
755
|
# Resolve environment variable
|