zensical 0.0.9__cp310-abi3-musllinux_1_2_i686.whl → 0.0.17__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 +6 -6
- zensical/__main__.py +2 -2
- zensical/bootstrap/.github/workflows/docs.yml +1 -0
- zensical/bootstrap/zensical.toml +6 -6
- zensical/config.py +223 -201
- zensical/extensions/__init__.py +2 -2
- zensical/extensions/emoji.py +22 -27
- zensical/extensions/links.py +21 -25
- zensical/extensions/preview.py +29 -41
- zensical/extensions/search.py +83 -83
- zensical/extensions/utilities/__init__.py +2 -2
- zensical/extensions/utilities/filter.py +5 -10
- zensical/main.py +35 -48
- zensical/markdown.py +82 -21
- zensical/templates/.icons/lucide/LICENSE +39 -0
- zensical/templates/.icons/lucide/ampersand.svg +1 -1
- zensical/templates/.icons/lucide/anchor.svg +1 -1
- zensical/templates/.icons/lucide/balloon.svg +1 -0
- zensical/templates/.icons/lucide/birdhouse.svg +1 -0
- zensical/templates/.icons/lucide/book-search.svg +1 -0
- zensical/templates/.icons/lucide/brush-cleaning.svg +1 -1
- zensical/templates/.icons/lucide/bubbles.svg +1 -1
- zensical/templates/.icons/lucide/calendar-fold.svg +1 -1
- zensical/templates/.icons/lucide/calendars.svg +1 -0
- zensical/templates/.icons/lucide/cannabis-off.svg +1 -0
- zensical/templates/.icons/lucide/chess-bishop.svg +1 -0
- zensical/templates/.icons/lucide/chess-king.svg +1 -0
- zensical/templates/.icons/lucide/chess-knight.svg +1 -0
- zensical/templates/.icons/lucide/chess-pawn.svg +1 -0
- zensical/templates/.icons/lucide/chess-queen.svg +1 -0
- zensical/templates/.icons/lucide/chess-rook.svg +1 -0
- zensical/templates/.icons/lucide/circle-pile.svg +1 -0
- zensical/templates/.icons/lucide/clock-check.svg +1 -0
- zensical/templates/.icons/lucide/cloud-backup.svg +1 -0
- zensical/templates/.icons/lucide/cloud-sync.svg +1 -0
- zensical/templates/.icons/lucide/file-archive.svg +1 -1
- zensical/templates/.icons/lucide/file-audio-2.svg +1 -1
- zensical/templates/.icons/lucide/file-audio.svg +1 -1
- zensical/templates/.icons/lucide/file-axis-3-d.svg +1 -1
- zensical/templates/.icons/lucide/file-axis-3d.svg +1 -1
- zensical/templates/.icons/lucide/file-badge-2.svg +1 -1
- zensical/templates/.icons/lucide/file-badge.svg +1 -1
- zensical/templates/.icons/lucide/file-bar-chart-2.svg +1 -1
- zensical/templates/.icons/lucide/file-bar-chart.svg +1 -1
- zensical/templates/.icons/lucide/file-box.svg +1 -1
- zensical/templates/.icons/lucide/file-braces-corner.svg +1 -0
- zensical/templates/.icons/lucide/file-braces.svg +1 -0
- zensical/templates/.icons/lucide/file-chart-column-increasing.svg +1 -1
- zensical/templates/.icons/lucide/file-chart-column.svg +1 -1
- zensical/templates/.icons/lucide/file-chart-line.svg +1 -1
- zensical/templates/.icons/lucide/file-chart-pie.svg +1 -1
- zensical/templates/.icons/lucide/file-check-2.svg +1 -1
- zensical/templates/.icons/lucide/file-check-corner.svg +1 -0
- zensical/templates/.icons/lucide/file-check.svg +1 -1
- zensical/templates/.icons/lucide/file-clock.svg +1 -1
- zensical/templates/.icons/lucide/file-code-2.svg +1 -1
- zensical/templates/.icons/lucide/file-code-corner.svg +1 -0
- zensical/templates/.icons/lucide/file-code.svg +1 -1
- zensical/templates/.icons/lucide/file-cog-2.svg +1 -1
- zensical/templates/.icons/lucide/file-cog.svg +1 -1
- zensical/templates/.icons/lucide/file-diff.svg +1 -1
- zensical/templates/.icons/lucide/file-digit.svg +1 -1
- zensical/templates/.icons/lucide/file-down.svg +1 -1
- zensical/templates/.icons/lucide/file-edit.svg +1 -1
- zensical/templates/.icons/lucide/file-exclamation-point.svg +1 -0
- zensical/templates/.icons/lucide/file-headphone.svg +1 -0
- zensical/templates/.icons/lucide/file-heart.svg +1 -1
- zensical/templates/.icons/lucide/file-image.svg +1 -1
- zensical/templates/.icons/lucide/file-input.svg +1 -1
- zensical/templates/.icons/lucide/file-json-2.svg +1 -1
- zensical/templates/.icons/lucide/file-json.svg +1 -1
- zensical/templates/.icons/lucide/file-key-2.svg +1 -1
- zensical/templates/.icons/lucide/file-key.svg +1 -1
- zensical/templates/.icons/lucide/file-line-chart.svg +1 -1
- zensical/templates/.icons/lucide/file-lock-2.svg +1 -1
- zensical/templates/.icons/lucide/file-lock.svg +1 -1
- zensical/templates/.icons/lucide/file-minus-2.svg +1 -1
- zensical/templates/.icons/lucide/file-minus-corner.svg +1 -0
- zensical/templates/.icons/lucide/file-minus.svg +1 -1
- zensical/templates/.icons/lucide/file-music.svg +1 -1
- zensical/templates/.icons/lucide/file-output.svg +1 -1
- zensical/templates/.icons/lucide/file-pen-line.svg +1 -1
- zensical/templates/.icons/lucide/file-pen.svg +1 -1
- zensical/templates/.icons/lucide/file-pie-chart.svg +1 -1
- zensical/templates/.icons/lucide/file-play.svg +1 -1
- zensical/templates/.icons/lucide/file-plus-2.svg +1 -1
- zensical/templates/.icons/lucide/file-plus-corner.svg +1 -0
- zensical/templates/.icons/lucide/file-plus.svg +1 -1
- zensical/templates/.icons/lucide/file-question-mark.svg +1 -1
- zensical/templates/.icons/lucide/file-question.svg +1 -1
- zensical/templates/.icons/lucide/file-scan.svg +1 -1
- zensical/templates/.icons/lucide/file-search-2.svg +1 -1
- zensical/templates/.icons/lucide/file-search-corner.svg +1 -0
- zensical/templates/.icons/lucide/file-search.svg +1 -1
- zensical/templates/.icons/lucide/file-signal.svg +1 -0
- zensical/templates/.icons/lucide/file-signature.svg +1 -1
- zensical/templates/.icons/lucide/file-sliders.svg +1 -1
- zensical/templates/.icons/lucide/file-spreadsheet.svg +1 -1
- zensical/templates/.icons/lucide/file-symlink.svg +1 -1
- zensical/templates/.icons/lucide/file-terminal.svg +1 -1
- zensical/templates/.icons/lucide/file-text.svg +1 -1
- zensical/templates/.icons/lucide/file-type-2.svg +1 -1
- zensical/templates/.icons/lucide/file-type-corner.svg +1 -0
- zensical/templates/.icons/lucide/file-type.svg +1 -1
- zensical/templates/.icons/lucide/file-up.svg +1 -1
- zensical/templates/.icons/lucide/file-user.svg +1 -1
- zensical/templates/.icons/lucide/file-video-2.svg +1 -1
- zensical/templates/.icons/lucide/file-video-camera.svg +1 -1
- zensical/templates/.icons/lucide/file-video.svg +1 -1
- zensical/templates/.icons/lucide/file-volume-2.svg +1 -1
- zensical/templates/.icons/lucide/file-volume.svg +1 -1
- zensical/templates/.icons/lucide/file-warning.svg +1 -1
- zensical/templates/.icons/lucide/file-x-2.svg +1 -1
- zensical/templates/.icons/lucide/file-x-corner.svg +1 -0
- zensical/templates/.icons/lucide/file-x.svg +1 -1
- zensical/templates/.icons/lucide/file.svg +1 -1
- zensical/templates/.icons/lucide/files.svg +1 -1
- zensical/templates/.icons/lucide/fingerprint-pattern.svg +1 -0
- zensical/templates/.icons/lucide/fishing-hook.svg +1 -0
- zensical/templates/.icons/lucide/flashlight-off.svg +1 -1
- zensical/templates/.icons/lucide/flashlight.svg +1 -1
- zensical/templates/.icons/lucide/folder-git-2.svg +1 -1
- zensical/templates/.icons/lucide/form.svg +1 -0
- zensical/templates/.icons/lucide/gamepad-directional.svg +1 -0
- zensical/templates/.icons/lucide/git-branch-minus.svg +1 -0
- zensical/templates/.icons/lucide/hd.svg +1 -0
- zensical/templates/.icons/lucide/helicopter.svg +1 -0
- zensical/templates/.icons/lucide/layers-plus.svg +1 -0
- zensical/templates/.icons/lucide/memory-stick.svg +1 -1
- zensical/templates/.icons/lucide/microchip.svg +1 -1
- zensical/templates/.icons/lucide/mouse-pointer-2-off.svg +1 -0
- zensical/templates/.icons/lucide/paint-bucket.svg +1 -1
- zensical/templates/.icons/lucide/plug.svg +1 -1
- zensical/templates/.icons/lucide/ruler-dimension-line.svg +1 -1
- zensical/templates/.icons/lucide/scale.svg +1 -1
- zensical/templates/.icons/lucide/scissors-square-dashed-bottom.svg +1 -1
- zensical/templates/.icons/lucide/scissors-square.svg +1 -1
- zensical/templates/.icons/lucide/scooter.svg +1 -0
- zensical/templates/.icons/lucide/search-alert.svg +1 -0
- zensical/templates/.icons/lucide/shredder.svg +1 -1
- zensical/templates/.icons/lucide/solar-panel.svg +1 -0
- zensical/templates/.icons/lucide/square-bottom-dashed-scissors.svg +1 -1
- zensical/templates/.icons/lucide/square-scissors.svg +1 -1
- zensical/templates/.icons/lucide/sticker.svg +1 -1
- zensical/templates/.icons/lucide/sticky-note.svg +1 -1
- zensical/templates/.icons/lucide/stone.svg +1 -0
- zensical/templates/.icons/lucide/thermometer-sun.svg +1 -1
- zensical/templates/.icons/lucide/thumbs-down.svg +1 -1
- zensical/templates/.icons/lucide/thumbs-up.svg +1 -1
- zensical/templates/.icons/lucide/tickets-plane.svg +1 -1
- zensical/templates/.icons/lucide/tickets.svg +1 -1
- zensical/templates/.icons/lucide/toolbox.svg +1 -0
- zensical/templates/.icons/lucide/van.svg +1 -0
- zensical/templates/.icons/lucide/waves-arrow-down.svg +1 -0
- zensical/templates/.icons/lucide/waves-arrow-up.svg +1 -0
- zensical/templates/.icons/lucide/weight-tilde.svg +1 -0
- zensical/templates/.icons/octicons/boolean-off-16.svg +1 -0
- zensical/templates/.icons/octicons/boolean-off-24.svg +1 -0
- zensical/templates/.icons/octicons/boolean-on-16.svg +1 -0
- zensical/templates/.icons/octicons/boolean-on-24.svg +1 -0
- zensical/templates/.icons/octicons/compose-16.svg +1 -0
- zensical/templates/.icons/octicons/compose-24.svg +1 -0
- zensical/templates/.icons/octicons/crosshairs-16.svg +1 -0
- zensical/templates/.icons/octicons/crosshairs-24.svg +1 -0
- zensical/templates/.icons/octicons/dice-16.svg +1 -0
- zensical/templates/.icons/octicons/dice-24.svg +1 -0
- zensical/templates/.icons/octicons/exclamation-16.svg +1 -0
- zensical/templates/.icons/octicons/exclamation-24.svg +1 -0
- zensical/templates/.icons/octicons/file-check-16.svg +1 -0
- zensical/templates/.icons/octicons/file-check-24.svg +1 -0
- zensical/templates/.icons/octicons/flowchart-16.svg +1 -0
- zensical/templates/.icons/octicons/flowchart-24.svg +1 -0
- zensical/templates/.icons/octicons/focus-center-16.svg +1 -0
- zensical/templates/.icons/octicons/focus-center-24.svg +1 -0
- zensical/templates/.icons/octicons/git-branch-check-16.svg +1 -0
- zensical/templates/.icons/octicons/git-branch-check-24.svg +1 -0
- zensical/templates/.icons/octicons/graph-bar-horizontal-16.svg +1 -0
- zensical/templates/.icons/octicons/graph-bar-horizontal-24.svg +1 -0
- zensical/templates/.icons/octicons/graph-bar-vertical-16.svg +1 -0
- zensical/templates/.icons/octicons/graph-bar-vertical-24.svg +1 -0
- zensical/templates/.icons/octicons/inbox-fill-16.svg +1 -0
- zensical/templates/.icons/octicons/inbox-fill-24.svg +1 -0
- zensical/templates/.icons/octicons/node-16.svg +1 -0
- zensical/templates/.icons/octicons/node-24.svg +1 -0
- zensical/templates/.icons/octicons/pencil-ai-16.svg +1 -0
- zensical/templates/.icons/octicons/pencil-ai-24.svg +1 -0
- zensical/templates/.icons/octicons/smiley-frown-16.svg +1 -0
- zensical/templates/.icons/octicons/smiley-frown-24.svg +1 -0
- zensical/templates/.icons/octicons/smiley-frustrated-16.svg +1 -0
- zensical/templates/.icons/octicons/smiley-frustrated-24.svg +1 -0
- zensical/templates/.icons/octicons/smiley-grin-16.svg +1 -0
- zensical/templates/.icons/octicons/smiley-grin-24.svg +1 -0
- zensical/templates/.icons/octicons/smiley-neutral-16.svg +1 -0
- zensical/templates/.icons/octicons/smiley-neutral-24.svg +1 -0
- zensical/templates/.icons/octicons/spacing-large-16.svg +1 -0
- zensical/templates/.icons/octicons/spacing-large-24.svg +1 -0
- zensical/templates/.icons/octicons/spacing-medium-16.svg +1 -0
- zensical/templates/.icons/octicons/spacing-medium-24.svg +1 -0
- zensical/templates/.icons/octicons/spacing-small-16.svg +1 -0
- zensical/templates/.icons/octicons/spacing-small-24.svg +1 -0
- zensical/templates/.icons/octicons/split-view-16.svg +1 -0
- zensical/templates/.icons/octicons/split-view-24.svg +1 -0
- zensical/templates/.icons/octicons/unwrap-16.svg +1 -0
- zensical/templates/.icons/octicons/unwrap-24.svg +1 -0
- zensical/templates/.icons/octicons/vscode-16.svg +1 -1
- zensical/templates/.icons/octicons/vscode-32.svg +1 -1
- zensical/templates/.icons/octicons/vscode-48.svg +1 -1
- zensical/templates/.icons/octicons/wrap-16.svg +1 -0
- zensical/templates/.icons/octicons/wrap-24.svg +1 -0
- zensical/templates/.icons/simple/acode.svg +1 -0
- zensical/templates/.icons/simple/apacheavro.svg +1 -0
- zensical/templates/.icons/simple/appimage.svg +1 -0
- zensical/templates/.icons/simple/appmanager.svg +1 -0
- zensical/templates/.icons/simple/autentique.svg +1 -0
- zensical/templates/.icons/simple/b4x.svg +1 -0
- zensical/templates/.icons/simple/bioconductor.svg +1 -0
- zensical/templates/.icons/simple/coolify.svg +1 -0
- zensical/templates/.icons/simple/cursor.svg +1 -0
- zensical/templates/.icons/simple/dash0.svg +1 -0
- zensical/templates/.icons/simple/dodopayments.svg +1 -0
- zensical/templates/.icons/simple/elk.svg +1 -0
- zensical/templates/.icons/simple/fishaudio.svg +1 -0
- zensical/templates/.icons/simple/ghostty.svg +1 -0
- zensical/templates/.icons/simple/glance.svg +1 -0
- zensical/templates/.icons/simple/hashcat.svg +1 -0
- zensical/templates/.icons/simple/kando.svg +1 -0
- zensical/templates/.icons/simple/labex.svg +1 -0
- zensical/templates/.icons/simple/listenhub.svg +1 -0
- zensical/templates/.icons/simple/luanti.svg +1 -0
- zensical/templates/.icons/simple/maas.svg +1 -1
- zensical/templates/.icons/simple/mailbox.svg +1 -0
- zensical/templates/.icons/simple/mangacollec.svg +1 -0
- zensical/templates/.icons/simple/mdblist.svg +1 -0
- zensical/templates/.icons/simple/minimax.svg +1 -0
- zensical/templates/.icons/simple/newgrounds.svg +1 -0
- zensical/templates/.icons/simple/nodegui.svg +1 -0
- zensical/templates/.icons/simple/openrouter.svg +1 -0
- zensical/templates/.icons/simple/passbolt.svg +1 -0
- zensical/templates/.icons/simple/plane.svg +1 -0
- zensical/templates/.icons/simple/postiz.svg +1 -0
- zensical/templates/.icons/simple/qlty.svg +1 -0
- zensical/templates/.icons/simple/rekaui.svg +1 -0
- zensical/templates/.icons/simple/retroachievements.svg +1 -0
- zensical/templates/.icons/simple/root.svg +1 -0
- zensical/templates/.icons/simple/setuptools.svg +1 -0
- zensical/templates/.icons/simple/tanstack.svg +1 -0
- zensical/templates/.icons/simple/textual.svg +1 -0
- zensical/templates/assets/javascripts/LICENSE +29 -0
- zensical/templates/assets/javascripts/bundle.8ffeb9c9.min.js +3 -0
- zensical/templates/assets/javascripts/workers/search.e2d2d235.min.js +1 -0
- zensical/templates/assets/stylesheets/classic/main.9a39631f.min.css +1 -0
- zensical/templates/assets/stylesheets/modern/main.d4922b3c.min.css +1 -0
- zensical/templates/base.html +4 -4
- zensical/zensical.abi3.so +0 -0
- zensical/zensical.pyi +7 -13
- {zensical-0.0.9.dist-info → zensical-0.0.17.dist-info}/METADATA +9 -5
- {zensical-0.0.9.dist-info → zensical-0.0.17.dist-info}/RECORD +262 -172
- {zensical-0.0.9.dist-info → zensical-0.0.17.dist-info}/WHEEL +1 -1
- {zensical-0.0.9.dist-info → zensical-0.0.17.dist-info}/licenses/LICENSE.md +1 -1
- zensical.libs/libgcc_s-f5fcfe20.so.1 +0 -0
- zensical/templates/.icons/simple/aerlingus.svg +0 -1
- zensical/templates/.icons/simple/aerospike.svg +0 -1
- zensical/templates/.icons/simple/aew.svg +0 -1
- zensical/templates/.icons/simple/affinity.svg +0 -1
- zensical/templates/.icons/simple/affinitydesigner.svg +0 -1
- zensical/templates/.icons/simple/affinityphoto.svg +0 -1
- zensical/templates/.icons/simple/affinitypublisher.svg +0 -1
- zensical/templates/.icons/simple/alfaromeo.svg +0 -1
- zensical/templates/.icons/simple/allocine.svg +0 -1
- zensical/templates/.icons/simple/alteryx.svg +0 -1
- zensical/templates/.icons/simple/altiumdesigner.svg +0 -1
- zensical/templates/.icons/simple/alx.svg +0 -1
- zensical/templates/.icons/simple/authy.svg +0 -1
- zensical/templates/.icons/simple/canva.svg +0 -1
- zensical/templates/.icons/simple/codepen.svg +0 -1
- zensical/templates/.icons/simple/cognizant.svg +0 -1
- zensical/templates/.icons/simple/dbt.svg +0 -1
- zensical/templates/.icons/simple/flipkart.svg +0 -1
- zensical/templates/.icons/simple/googlefit.svg +0 -1
- zensical/templates/.icons/simple/heroku.svg +0 -1
- zensical/templates/.icons/simple/informatica.svg +0 -1
- zensical/templates/.icons/simple/invision.svg +0 -1
- zensical/templates/.icons/simple/jaguar.svg +0 -1
- zensical/templates/.icons/simple/landrover.svg +0 -1
- zensical/templates/.icons/simple/logitech.svg +0 -1
- zensical/templates/.icons/simple/logitechg.svg +0 -1
- zensical/templates/.icons/simple/mailboxdotorg.svg +0 -1
- zensical/templates/.icons/simple/minetest.svg +0 -1
- zensical/templates/.icons/simple/mulesoft.svg +0 -1
- zensical/templates/.icons/simple/musescore.svg +0 -1
- zensical/templates/.icons/simple/nexusmods.svg +0 -1
- zensical/templates/.icons/simple/openai.svg +0 -1
- zensical/templates/.icons/simple/pocket.svg +0 -1
- zensical/templates/.icons/simple/quip.svg +0 -1
- zensical/templates/.icons/simple/salesforce.svg +0 -1
- zensical/templates/.icons/simple/scribd.svg +0 -1
- zensical/templates/.icons/simple/sendgrid.svg +0 -1
- zensical/templates/.icons/simple/shutterstock.svg +0 -1
- zensical/templates/.icons/simple/slack.svg +0 -1
- zensical/templates/.icons/simple/tunein.svg +0 -1
- zensical/templates/.icons/simple/twilio.svg +0 -1
- zensical/templates/.icons/simple/walmart.svg +0 -1
- zensical/templates/.icons/simple/warnerbros.svg +0 -1
- zensical/templates/.icons/simple/westerndigital.svg +0 -1
- zensical/templates/assets/javascripts/bundle.21aa498e.min.js +0 -3
- zensical/templates/assets/javascripts/workers/search.5e1f2129.min.js +0 -1
- 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.17.dist-info}/entry_points.txt +0 -0
zensical/config.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
# Copyright (c) 2025 Zensical and contributors
|
|
1
|
+
# Copyright (c) 2025-2026 Zensical and contributors
|
|
2
2
|
|
|
3
3
|
# SPDX-License-Identifier: MIT
|
|
4
|
-
#
|
|
4
|
+
# All contributions are certified under the DCO
|
|
5
5
|
|
|
6
6
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
7
|
# of this software and associated documentation files (the "Software"), to
|
|
@@ -26,22 +26,24 @@ from __future__ import annotations
|
|
|
26
26
|
import hashlib
|
|
27
27
|
import importlib
|
|
28
28
|
import os
|
|
29
|
+
from pathlib import Path
|
|
29
30
|
import pickle
|
|
30
|
-
import
|
|
31
|
-
|
|
32
|
-
try:
|
|
33
|
-
import tomllib
|
|
34
|
-
except ModuleNotFoundError:
|
|
35
|
-
import tomli as tomllib # type: ignore
|
|
31
|
+
from typing import IO, Any
|
|
32
|
+
from urllib.parse import urlparse
|
|
36
33
|
|
|
34
|
+
import yaml
|
|
37
35
|
from click import ClickException
|
|
38
36
|
from deepmerge import always_merger
|
|
39
|
-
from typing import Any, IO
|
|
40
37
|
from yaml import BaseLoader, Loader, YAMLError
|
|
41
38
|
from yaml.constructor import ConstructorError
|
|
42
|
-
from urllib.parse import urlparse
|
|
43
39
|
|
|
44
|
-
from .extensions.emoji import to_svg, twemoji
|
|
40
|
+
from zensical.extensions.emoji import to_svg, twemoji
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
import tomllib
|
|
44
|
+
except ModuleNotFoundError:
|
|
45
|
+
import tomli as tomllib # type: ignore[no-redef]
|
|
46
|
+
|
|
45
47
|
|
|
46
48
|
# ----------------------------------------------------------------------------
|
|
47
49
|
# Globals
|
|
@@ -64,9 +66,7 @@ side, and use it directly when needed. It's a hack but will do for now.
|
|
|
64
66
|
|
|
65
67
|
|
|
66
68
|
class ConfigurationError(ClickException):
|
|
67
|
-
"""
|
|
68
|
-
Configuration resolution or validation failed.
|
|
69
|
-
"""
|
|
69
|
+
"""Configuration resolution or validation failed."""
|
|
70
70
|
|
|
71
71
|
|
|
72
72
|
# ----------------------------------------------------------------------------
|
|
@@ -75,22 +75,17 @@ class ConfigurationError(ClickException):
|
|
|
75
75
|
|
|
76
76
|
|
|
77
77
|
def parse_config(path: str) -> dict:
|
|
78
|
-
"""
|
|
79
|
-
Parse configuration file.
|
|
80
|
-
"""
|
|
78
|
+
"""Parse configuration file."""
|
|
81
79
|
# Decide by extension; no need to convert to Path
|
|
82
80
|
_, ext = os.path.splitext(path)
|
|
83
81
|
if ext.lower() == ".toml":
|
|
84
82
|
return parse_zensical_config(path)
|
|
85
|
-
|
|
86
|
-
return parse_mkdocs_config(path)
|
|
83
|
+
return parse_mkdocs_config(path)
|
|
87
84
|
|
|
88
85
|
|
|
89
86
|
def parse_zensical_config(path: str) -> dict:
|
|
90
|
-
"""
|
|
91
|
-
|
|
92
|
-
"""
|
|
93
|
-
global _CONFIG
|
|
87
|
+
"""Parse zensical.toml configuration file."""
|
|
88
|
+
global _CONFIG # noqa: PLW0603
|
|
94
89
|
with open(path, "rb") as f:
|
|
95
90
|
config = tomllib.load(f)
|
|
96
91
|
if "project" in config:
|
|
@@ -102,11 +97,9 @@ def parse_zensical_config(path: str) -> dict:
|
|
|
102
97
|
|
|
103
98
|
|
|
104
99
|
def parse_mkdocs_config(path: str) -> dict:
|
|
105
|
-
"""
|
|
106
|
-
|
|
107
|
-
""
|
|
108
|
-
global _CONFIG
|
|
109
|
-
with open(path, "r") as f:
|
|
100
|
+
"""Parse mkdocs.yml configuration file."""
|
|
101
|
+
global _CONFIG # noqa: PLW0603
|
|
102
|
+
with open(path, encoding="utf-8") as f:
|
|
110
103
|
config = _yaml_load(f)
|
|
111
104
|
|
|
112
105
|
# Apply defaults and return parsed configuration
|
|
@@ -114,24 +107,30 @@ def parse_mkdocs_config(path: str) -> dict:
|
|
|
114
107
|
return _CONFIG
|
|
115
108
|
|
|
116
109
|
|
|
117
|
-
def get_config():
|
|
118
|
-
"""
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
return _CONFIG
|
|
110
|
+
def get_config() -> dict:
|
|
111
|
+
"""Return configuration."""
|
|
112
|
+
# We assume this function is only called after populating `_CONFIG`.
|
|
113
|
+
return _CONFIG # type: ignore[return-value]
|
|
122
114
|
|
|
123
115
|
|
|
124
116
|
def get_theme_dir() -> str:
|
|
125
|
-
"""
|
|
126
|
-
Return the theme directory.
|
|
127
|
-
"""
|
|
117
|
+
"""Return the theme directory."""
|
|
128
118
|
path = os.path.dirname(os.path.abspath(__file__))
|
|
129
119
|
return os.path.join(path, "templates")
|
|
130
120
|
|
|
131
121
|
|
|
122
|
+
def get_custom_theme_dir(config: dict) -> str | None:
|
|
123
|
+
"""Return the custom theme directory."""
|
|
124
|
+
path = os.path.dirname(os.path.abspath(__file__))
|
|
125
|
+
if config["theme"].get("custom_dir"):
|
|
126
|
+
return os.path.join(path, config["theme"].get("custom_dir"))
|
|
127
|
+
|
|
128
|
+
# Otherwise, return no path
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
|
|
132
132
|
def _apply_defaults(config: dict, path: str) -> dict:
|
|
133
|
-
"""
|
|
134
|
-
Apply default settings in configuration.
|
|
133
|
+
"""Apply default settings in configuration.
|
|
135
134
|
|
|
136
135
|
Note that this is loosely based on the defaults that MkDocs sets in its own
|
|
137
136
|
configuration system, which we won't port for compatibility right now, as
|
|
@@ -146,12 +145,12 @@ def _apply_defaults(config: dict, path: str) -> dict:
|
|
|
146
145
|
|
|
147
146
|
# Set site directory
|
|
148
147
|
set_default(config, "site_dir", "site", str)
|
|
149
|
-
if ".." in config.get("site_dir"):
|
|
148
|
+
if ".." in config.get("site_dir", ""):
|
|
150
149
|
raise ConfigurationError("site_dir must not contain '..'")
|
|
151
150
|
|
|
152
151
|
# Set docs directory
|
|
153
152
|
set_default(config, "docs_dir", "docs", str)
|
|
154
|
-
if ".." in config.get("docs_dir"):
|
|
153
|
+
if ".." in config.get("docs_dir", ""):
|
|
155
154
|
raise ConfigurationError("docs_dir must not contain '..'")
|
|
156
155
|
|
|
157
156
|
# Set defaults for core settings
|
|
@@ -169,21 +168,27 @@ def _apply_defaults(config: dict, path: str) -> dict:
|
|
|
169
168
|
set_default(config, "edit_uri", None, str)
|
|
170
169
|
|
|
171
170
|
# Set defaults for repository name settings
|
|
171
|
+
docs_dir = config.get("docs_dir")
|
|
172
|
+
repo_names = {
|
|
173
|
+
"github.com": "GitHub",
|
|
174
|
+
"gitlab.com": "Gitlab",
|
|
175
|
+
"bitbucket.org": "Bitbucket",
|
|
176
|
+
}
|
|
177
|
+
edit_uris = {
|
|
178
|
+
"github.com": f"edit/master/{docs_dir}",
|
|
179
|
+
"gitlab.com": f"edit/master/{docs_dir}",
|
|
180
|
+
"bitbucket.org": f"src/default/{docs_dir}",
|
|
181
|
+
}
|
|
172
182
|
repo_url = config.get("repo_url")
|
|
173
|
-
if repo_url
|
|
174
|
-
docs_dir = config.get("docs_dir")
|
|
183
|
+
if repo_url:
|
|
175
184
|
host = urlparse(repo_url).hostname or ""
|
|
176
|
-
if
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
set_default(config, "repo_name", "Bitbucket", str)
|
|
184
|
-
set_default(config, "edit_uri", f"src/default/{docs_dir}", str)
|
|
185
|
-
elif host:
|
|
186
|
-
config["repo_name"] = host.split(".")[0].title()
|
|
185
|
+
if not config.get("repo_name"):
|
|
186
|
+
if host in repo_names:
|
|
187
|
+
set_default(config, "repo_name", repo_names[host], str)
|
|
188
|
+
elif host:
|
|
189
|
+
config["repo_name"] = host.split(".")[0].title()
|
|
190
|
+
if host in edit_uris:
|
|
191
|
+
set_default(config, "edit_uri", edit_uris[host], str)
|
|
187
192
|
|
|
188
193
|
# Remove trailing slash from edit_uri if present
|
|
189
194
|
edit_uri = config.get("edit_uri")
|
|
@@ -258,18 +263,12 @@ def _apply_defaults(config: dict, path: str) -> dict:
|
|
|
258
263
|
|
|
259
264
|
# Set defaults for theme admonition icons
|
|
260
265
|
admonition = set_default(icon, "admonition", {}, dict)
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
set_default(admonition, "warning", None, str)
|
|
268
|
-
set_default(admonition, "failure", None, str)
|
|
269
|
-
set_default(admonition, "danger", None, str)
|
|
270
|
-
set_default(admonition, "bug", None, str)
|
|
271
|
-
set_default(admonition, "example", None, str)
|
|
272
|
-
set_default(admonition, "quote", None, str)
|
|
266
|
+
if isinstance(admonition, dict):
|
|
267
|
+
icon["admonition"] = {
|
|
268
|
+
str(key): str(value)
|
|
269
|
+
for key, value in admonition.items()
|
|
270
|
+
if value is not None
|
|
271
|
+
}
|
|
273
272
|
|
|
274
273
|
# Set defaults for theme palette settings and normalize to list
|
|
275
274
|
palette = theme.setdefault("palette", [])
|
|
@@ -292,73 +291,17 @@ def _apply_defaults(config: dict, path: str) -> dict:
|
|
|
292
291
|
set_default(toggle, "name", None, str)
|
|
293
292
|
|
|
294
293
|
# Set defaults for extra settings
|
|
294
|
+
if "extra" in config and not isinstance(config["extra"], dict):
|
|
295
|
+
raise ConfigurationError(
|
|
296
|
+
"The 'extra' setting must be a mapping/dictionary."
|
|
297
|
+
)
|
|
295
298
|
extra = set_default(config, "extra", {}, dict)
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
299
|
+
|
|
300
|
+
if "polyfills" in extra and not isinstance(extra["polyfills"], list):
|
|
301
|
+
raise ConfigurationError(
|
|
302
|
+
"The 'extra.polyfills' setting must be a list."
|
|
303
|
+
)
|
|
301
304
|
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
305
|
|
|
363
306
|
# Ensure all non-existent values are all empty strings (for now)
|
|
364
307
|
config["extra"] = _convert_extra(extra)
|
|
@@ -435,19 +378,38 @@ def _apply_defaults(config: dict, path: str) -> dict:
|
|
|
435
378
|
tabbed = config["mdx_configs"].get("pymdownx.tabbed", {})
|
|
436
379
|
if isinstance(tabbed.get("slugify"), dict):
|
|
437
380
|
object = tabbed["slugify"].get("object", "pymdownx.slugs.slugify")
|
|
438
|
-
tabbed["slugify"] = _resolve(object)(
|
|
381
|
+
tabbed["slugify"] = _resolve(object)(
|
|
382
|
+
**tabbed["slugify"].get("kwds", {})
|
|
383
|
+
)
|
|
439
384
|
|
|
440
385
|
# Table of contents extension configuration - resolve slugification function
|
|
441
386
|
toc = config["mdx_configs"]["toc"]
|
|
442
387
|
if isinstance(toc.get("slugify"), dict):
|
|
443
388
|
object = toc["slugify"].get("object", "pymdownx.slugs.slugify")
|
|
444
|
-
toc["slugify"] = _resolve(object)(**toc["slugify"].get("kwds"))
|
|
389
|
+
toc["slugify"] = _resolve(object)(**toc["slugify"].get("kwds", {}))
|
|
445
390
|
|
|
446
391
|
# Superfences extension configuration - resolve format function
|
|
447
392
|
superfences = config["mdx_configs"].get("pymdownx.superfences", {})
|
|
448
393
|
for fence in superfences.get("custom_fences", []):
|
|
449
394
|
if isinstance(fence.get("format"), str):
|
|
450
395
|
fence["format"] = _resolve(fence.get("format"))
|
|
396
|
+
elif isinstance(fence.get("format"), dict):
|
|
397
|
+
object = fence["format"].get(
|
|
398
|
+
"object", "pymdownx.superfences.fence_code_format"
|
|
399
|
+
)
|
|
400
|
+
fence["format"] = _resolve(object)(
|
|
401
|
+
**fence["format"].get("kwds", {})
|
|
402
|
+
)
|
|
403
|
+
if isinstance(fence.get("validator"), str):
|
|
404
|
+
fence["validator"] = _resolve(fence.get("validator"))
|
|
405
|
+
elif isinstance(fence.get("validator"), dict):
|
|
406
|
+
object = fence["validator"].get("object")
|
|
407
|
+
callable_object = (
|
|
408
|
+
_resolve(object) if object else lambda *args, **kwargs: True
|
|
409
|
+
)
|
|
410
|
+
fence["validator"] = callable_object(
|
|
411
|
+
**fence["validator"].get("kwds", {})
|
|
412
|
+
)
|
|
451
413
|
|
|
452
414
|
# Ensure the table of contents title is initialized, as it's used inside
|
|
453
415
|
# the template, and the table of contents extension is always defined
|
|
@@ -456,14 +418,31 @@ def _apply_defaults(config: dict, path: str) -> dict:
|
|
|
456
418
|
|
|
457
419
|
# Convert plugins configuration
|
|
458
420
|
config["plugins"] = _convert_plugins(config.get("plugins", []), config)
|
|
421
|
+
|
|
422
|
+
# Set up mkdocstrings, which touches plugins and Markdown extensions
|
|
423
|
+
if "mkdocstrings" in config["plugins"]:
|
|
424
|
+
mkdocstrings_config = config["plugins"]["mkdocstrings"]["config"]
|
|
425
|
+
if mkdocstrings_config.pop("enabled", True):
|
|
426
|
+
mkdocstrings_config["markdown_extensions"] = [
|
|
427
|
+
{ext: mdx_configs.get(ext, {})} for ext in markdown_extensions
|
|
428
|
+
]
|
|
429
|
+
config["markdown_extensions"].append("mkdocstrings")
|
|
430
|
+
config["mdx_configs"]["mkdocstrings"] = mkdocstrings_config
|
|
431
|
+
|
|
432
|
+
# List all source files for mkdocstrings
|
|
433
|
+
config["source_files"] = _list_sources(config, path)
|
|
434
|
+
|
|
435
|
+
# Hash all templates, so we rebuild if something changes
|
|
436
|
+
config["template_hash"] = _hash(_list_templates(config))
|
|
459
437
|
return config
|
|
460
438
|
|
|
461
439
|
|
|
462
440
|
def set_default(
|
|
463
|
-
entry: dict, key: str, default: Any, data_type: type = None
|
|
464
|
-
) ->
|
|
465
|
-
"""
|
|
466
|
-
|
|
441
|
+
entry: dict, key: str, default: Any, data_type: type | None = None
|
|
442
|
+
) -> Any:
|
|
443
|
+
"""Set a key to a default value if it isn't set.
|
|
444
|
+
|
|
445
|
+
Optionally cast it to the specified data type.
|
|
467
446
|
"""
|
|
468
447
|
if key in entry and entry[key] is None:
|
|
469
448
|
del entry[key]
|
|
@@ -476,24 +455,70 @@ def set_default(
|
|
|
476
455
|
try:
|
|
477
456
|
entry[key] = data_type(entry[key])
|
|
478
457
|
except (ValueError, TypeError) as e:
|
|
479
|
-
raise ValueError(
|
|
458
|
+
raise ValueError(
|
|
459
|
+
f"Failed to cast key '{key}' to {data_type}: {e}"
|
|
460
|
+
) from e
|
|
480
461
|
|
|
481
462
|
# Return the resulting value
|
|
482
463
|
return entry[key]
|
|
483
464
|
|
|
484
465
|
|
|
485
|
-
def _hash(data:
|
|
486
|
-
"""
|
|
487
|
-
|
|
488
|
-
"""
|
|
489
|
-
hash = hashlib.sha1(pickle.dumps(data))
|
|
466
|
+
def _hash(data: Any) -> int:
|
|
467
|
+
"""Compute a hash for the given data."""
|
|
468
|
+
hash = hashlib.sha1(pickle.dumps(data)) # noqa: S324
|
|
490
469
|
return int(hash.hexdigest(), 16) % (2**64)
|
|
491
470
|
|
|
492
471
|
|
|
472
|
+
def _list_sources(config: dict, config_file: str) -> list[tuple[str, int]]:
|
|
473
|
+
"""List all absolute links to source files for mkdocstrings."""
|
|
474
|
+
python_paths = (
|
|
475
|
+
config["plugins"]
|
|
476
|
+
.get("mkdocstrings", {})
|
|
477
|
+
.get("config", {})
|
|
478
|
+
.get("handlers", {})
|
|
479
|
+
.get("python", {})
|
|
480
|
+
.get("paths", ())
|
|
481
|
+
)
|
|
482
|
+
roots_with_hash = []
|
|
483
|
+
for python_path in python_paths:
|
|
484
|
+
path = Path(config_file).parent.joinpath(python_path).resolve()
|
|
485
|
+
|
|
486
|
+
# Collect all files under this root with modification times to detect
|
|
487
|
+
# changes. We'll replace this with proper dependency tracking later.
|
|
488
|
+
files = [(path, int(os.path.getmtime(path)))]
|
|
489
|
+
if path.is_dir():
|
|
490
|
+
for subpath in path.rglob("*"):
|
|
491
|
+
files.extend([(subpath, int(os.path.getmtime(subpath)))])
|
|
492
|
+
roots_with_hash.append((str(path), _hash(files)))
|
|
493
|
+
|
|
494
|
+
return sorted(roots_with_hash)
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
def _list_templates(config: dict) -> list[tuple[str, int]]:
|
|
498
|
+
"""List all template files in the theme directories."""
|
|
499
|
+
dirs = [get_theme_dir()]
|
|
500
|
+
if "custom_dir" in config["theme"]:
|
|
501
|
+
custom_dir = get_custom_theme_dir(config)
|
|
502
|
+
if custom_dir is not None:
|
|
503
|
+
dirs.append(custom_dir)
|
|
504
|
+
|
|
505
|
+
# Collect file paths and their mtimes
|
|
506
|
+
files_with_mtime = []
|
|
507
|
+
for directory in dirs:
|
|
508
|
+
for path, _, files in os.walk(directory):
|
|
509
|
+
if ".icons" in path:
|
|
510
|
+
continue
|
|
511
|
+
for file in files:
|
|
512
|
+
file_path = os.path.join(path, file)
|
|
513
|
+
mtime = int(os.path.getmtime(file_path))
|
|
514
|
+
files_with_mtime.append((file_path, mtime))
|
|
515
|
+
|
|
516
|
+
# Sort by file path for deterministic order
|
|
517
|
+
return sorted(files_with_mtime)
|
|
518
|
+
|
|
519
|
+
|
|
493
520
|
def _convert_extra(data: dict | list) -> dict | list:
|
|
494
|
-
"""
|
|
495
|
-
Recursively convert all None values in a dictionary or list to empty strings.
|
|
496
|
-
"""
|
|
521
|
+
"""Recursively convert None values in a dictionary/list to empty strings."""
|
|
497
522
|
if isinstance(data, dict):
|
|
498
523
|
# Process each key-value pair in the dictionary
|
|
499
524
|
return {
|
|
@@ -502,7 +527,7 @@ def _convert_extra(data: dict | list) -> dict | list:
|
|
|
502
527
|
else ("" if value is None else value)
|
|
503
528
|
for key, value in data.items()
|
|
504
529
|
}
|
|
505
|
-
|
|
530
|
+
if isinstance(data, list):
|
|
506
531
|
# Process each item in the list
|
|
507
532
|
return [
|
|
508
533
|
_convert_extra(item)
|
|
@@ -510,14 +535,11 @@ def _convert_extra(data: dict | list) -> dict | list:
|
|
|
510
535
|
else ("" if item is None else item)
|
|
511
536
|
for item in data
|
|
512
537
|
]
|
|
513
|
-
|
|
514
|
-
return data
|
|
538
|
+
return data
|
|
515
539
|
|
|
516
540
|
|
|
517
|
-
def _resolve(symbol: str):
|
|
518
|
-
"""
|
|
519
|
-
Resolve a symbol to its corresponding Python object.
|
|
520
|
-
"""
|
|
541
|
+
def _resolve(symbol: str) -> Any:
|
|
542
|
+
"""Resolve a symbol to its corresponding Python object."""
|
|
521
543
|
module_path, func_name = symbol.rsplit(".", 1)
|
|
522
544
|
module = importlib.import_module(module_path)
|
|
523
545
|
return getattr(module, func_name)
|
|
@@ -526,17 +548,15 @@ def _resolve(symbol: str):
|
|
|
526
548
|
# -----------------------------------------------------------------------------
|
|
527
549
|
|
|
528
550
|
|
|
529
|
-
def _convert_nav(nav:
|
|
530
|
-
"""
|
|
531
|
-
Convert MkDocs navigation
|
|
532
|
-
"""
|
|
551
|
+
def _convert_nav(nav: list) -> list:
|
|
552
|
+
"""Convert MkDocs navigation."""
|
|
533
553
|
return [_convert_nav_item(entry) for entry in nav]
|
|
534
554
|
|
|
535
555
|
|
|
536
|
-
def _convert_nav_item(item: str | dict | list) -> dict:
|
|
537
|
-
"""
|
|
538
|
-
|
|
539
|
-
|
|
556
|
+
def _convert_nav_item(item: str | dict | list) -> dict | list:
|
|
557
|
+
"""Convert MkDocs shorthand navigation structure into something manageable.
|
|
558
|
+
|
|
559
|
+
We need to annotate each item with a title, URL, icon, and children.
|
|
540
560
|
"""
|
|
541
561
|
if isinstance(item, str):
|
|
542
562
|
return {
|
|
@@ -550,19 +570,19 @@ def _convert_nav_item(item: str | dict | list) -> dict:
|
|
|
550
570
|
}
|
|
551
571
|
|
|
552
572
|
# Handle Title: URL
|
|
553
|
-
|
|
573
|
+
if isinstance(item, dict):
|
|
554
574
|
for title, value in item.items():
|
|
555
575
|
if isinstance(value, str):
|
|
556
576
|
return {
|
|
557
577
|
"title": str(title),
|
|
558
|
-
"url": value,
|
|
578
|
+
"url": value.strip(),
|
|
559
579
|
"canonical_url": None,
|
|
560
580
|
"meta": None,
|
|
561
581
|
"children": [],
|
|
562
|
-
"is_index": _is_index(value),
|
|
582
|
+
"is_index": _is_index(value.strip()),
|
|
563
583
|
"active": False,
|
|
564
584
|
}
|
|
565
|
-
|
|
585
|
+
if isinstance(value, list):
|
|
566
586
|
return {
|
|
567
587
|
"title": str(title),
|
|
568
588
|
"url": None,
|
|
@@ -572,28 +592,25 @@ def _convert_nav_item(item: str | dict | list) -> dict:
|
|
|
572
592
|
"is_index": False,
|
|
573
593
|
"active": False,
|
|
574
594
|
}
|
|
595
|
+
raise TypeError(f"Unknown nav item value type: {type(value)}")
|
|
575
596
|
|
|
576
597
|
# Handle a list of items
|
|
577
598
|
elif isinstance(item, list):
|
|
578
599
|
return [_convert_nav_item(child) for child in item]
|
|
579
|
-
|
|
580
|
-
|
|
600
|
+
|
|
601
|
+
raise TypeError(f"Unknown nav item type: {type(item)}")
|
|
581
602
|
|
|
582
603
|
|
|
583
604
|
def _is_index(path: str) -> bool:
|
|
584
|
-
"""
|
|
585
|
-
|
|
586
|
-
"""
|
|
587
|
-
return path.endswith(("index.md", "README.md"))
|
|
605
|
+
"""Returns, whether the given path points to a section index."""
|
|
606
|
+
return os.path.basename(path) in ("index.md", "README.md")
|
|
588
607
|
|
|
589
608
|
|
|
590
609
|
# -----------------------------------------------------------------------------
|
|
591
610
|
|
|
592
611
|
|
|
593
|
-
def _convert_extra_javascript(value: list
|
|
594
|
-
"""
|
|
595
|
-
Ensure extra_javascript uses a structured format.
|
|
596
|
-
"""
|
|
612
|
+
def _convert_extra_javascript(value: list) -> list:
|
|
613
|
+
"""Ensure extra_javascript uses a structured format."""
|
|
597
614
|
for i, item in enumerate(value):
|
|
598
615
|
if isinstance(item, str):
|
|
599
616
|
value[i] = {
|
|
@@ -608,9 +625,7 @@ def _convert_extra_javascript(value: list[any]) -> list:
|
|
|
608
625
|
item.setdefault("async", False)
|
|
609
626
|
item.setdefault("defer", False)
|
|
610
627
|
else:
|
|
611
|
-
raise
|
|
612
|
-
f"Unknown extra_javascript item type: {type(item)}"
|
|
613
|
-
)
|
|
628
|
+
raise TypeError(f"Unknown extra_javascript item type: {type(item)}")
|
|
614
629
|
|
|
615
630
|
# Return resulting value
|
|
616
631
|
return value
|
|
@@ -619,12 +634,10 @@ def _convert_extra_javascript(value: list[any]) -> list:
|
|
|
619
634
|
# -----------------------------------------------------------------------------
|
|
620
635
|
|
|
621
636
|
|
|
622
|
-
def _convert_markdown_extensions(value:
|
|
623
|
-
"""
|
|
624
|
-
Convert Markdown extensions configuration to what Python Markdown expects.
|
|
625
|
-
"""
|
|
637
|
+
def _convert_markdown_extensions(value: Any) -> tuple[list[str], dict]:
|
|
638
|
+
"""Convert Markdown extensions to what Python Markdown expects."""
|
|
626
639
|
markdown_extensions = ["toc", "tables"]
|
|
627
|
-
mdx_configs = {"toc": {}, "tables": {}}
|
|
640
|
+
mdx_configs: dict[str, dict[str, Any]] = {"toc": {}, "tables": {}}
|
|
628
641
|
|
|
629
642
|
# In case of Python Markdown Extensions, we allow to omit the necessary
|
|
630
643
|
# quotes around the extension names, so we need to hoist the extensions
|
|
@@ -632,14 +645,24 @@ def _convert_markdown_extensions(value: any):
|
|
|
632
645
|
# actually parse the configuration.
|
|
633
646
|
if "pymdownx" in value:
|
|
634
647
|
pymdownx = value.pop("pymdownx")
|
|
635
|
-
for ext,
|
|
648
|
+
for ext, conf in pymdownx.items():
|
|
636
649
|
# Special case for blocks extension, which has another level of
|
|
637
650
|
# nesting. This is the only extension that requires this.
|
|
638
651
|
if ext == "blocks":
|
|
639
|
-
for block, config in
|
|
652
|
+
for block, config in conf.items():
|
|
640
653
|
value[f"pymdownx.{ext}.{block}"] = config
|
|
641
654
|
else:
|
|
642
|
-
value[f"pymdownx.{ext}"] =
|
|
655
|
+
value[f"pymdownx.{ext}"] = conf
|
|
656
|
+
|
|
657
|
+
# Same as for Python Markdown extensions, see above
|
|
658
|
+
if "zensical" in value:
|
|
659
|
+
zensical = value.pop("zensical")
|
|
660
|
+
for ext, conf in zensical.items():
|
|
661
|
+
if ext == "extensions":
|
|
662
|
+
for key, config in conf.items():
|
|
663
|
+
value[f"zensical.{ext}.{key}"] = config
|
|
664
|
+
else:
|
|
665
|
+
value[f"zensical.{ext}"] = conf
|
|
643
666
|
|
|
644
667
|
# Extensions can be defined as a dict
|
|
645
668
|
if isinstance(value, dict):
|
|
@@ -664,16 +687,13 @@ def _convert_markdown_extensions(value: any):
|
|
|
664
687
|
# ----------------------------------------------------------------------------
|
|
665
688
|
|
|
666
689
|
|
|
667
|
-
def _convert_plugins(value:
|
|
668
|
-
"""
|
|
669
|
-
Convert plugins configuration to something we can work with.
|
|
670
|
-
"""
|
|
690
|
+
def _convert_plugins(value: Any, config: dict) -> dict:
|
|
691
|
+
"""Convert plugins configuration to something we can work with."""
|
|
671
692
|
plugins = {}
|
|
672
693
|
|
|
673
694
|
# Plugins can be defined as a dict
|
|
674
695
|
if isinstance(value, dict):
|
|
675
|
-
|
|
676
|
-
plugins[name] = data
|
|
696
|
+
plugins.update(value)
|
|
677
697
|
|
|
678
698
|
# Plugins can also be defined as a list
|
|
679
699
|
else:
|
|
@@ -730,8 +750,7 @@ def _convert_plugins(value: any, config: dict) -> list:
|
|
|
730
750
|
def _yaml_load(
|
|
731
751
|
source: IO, loader: type[BaseLoader] | None = None
|
|
732
752
|
) -> dict[str, Any]:
|
|
733
|
-
"""
|
|
734
|
-
Load configuration file and resolve environment variables and parent files.
|
|
753
|
+
"""Load configuration file, resolve environment variables and parent files.
|
|
735
754
|
|
|
736
755
|
Note that INHERIT is only a bandaid that was introduced to allow for some
|
|
737
756
|
degree of modularity, but with serious shortcomings. Zensical will use a
|
|
@@ -746,12 +765,12 @@ def _yaml_load(
|
|
|
746
765
|
source.read()
|
|
747
766
|
.replace("material.extensions", "zensical.extensions")
|
|
748
767
|
.replace("materialx", "zensical.extensions"),
|
|
749
|
-
Loader=Loader,
|
|
768
|
+
Loader=Loader, # noqa: S506
|
|
750
769
|
)
|
|
751
770
|
except YAMLError as e:
|
|
752
771
|
raise ConfigurationError(
|
|
753
772
|
f"Encountered an error parsing the configuration file: {e}"
|
|
754
|
-
)
|
|
773
|
+
) from e
|
|
755
774
|
if config is None:
|
|
756
775
|
return {}
|
|
757
776
|
|
|
@@ -763,9 +782,10 @@ def _yaml_load(
|
|
|
763
782
|
)
|
|
764
783
|
if not os.path.exists(abspath):
|
|
765
784
|
raise ConfigurationError(
|
|
766
|
-
f"Inherited config file '{relpath}'
|
|
785
|
+
f"Inherited config file '{relpath}' "
|
|
786
|
+
f"doesn't exist at '{abspath}'."
|
|
767
787
|
)
|
|
768
|
-
with open(abspath, "
|
|
788
|
+
with open(abspath, encoding="utf-8") as fd:
|
|
769
789
|
parent = _yaml_load(fd, loader)
|
|
770
790
|
config = always_merger.merge(parent, config)
|
|
771
791
|
|
|
@@ -773,9 +793,11 @@ def _yaml_load(
|
|
|
773
793
|
return config
|
|
774
794
|
|
|
775
795
|
|
|
776
|
-
def _construct_env_tag(
|
|
777
|
-
|
|
778
|
-
|
|
796
|
+
def _construct_env_tag(
|
|
797
|
+
loader: yaml.Loader,
|
|
798
|
+
node: yaml.ScalarNode | yaml.SequenceNode | yaml.MappingNode,
|
|
799
|
+
) -> Any:
|
|
800
|
+
"""Assign value of ENV variable referenced at node.
|
|
779
801
|
|
|
780
802
|
MkDocs supports the use of !ENV to reference environment variables in YAML
|
|
781
803
|
configuration files. We won't likely support this in Zensical, but for now
|
|
@@ -804,7 +826,7 @@ def _construct_env_tag(loader: yaml.Loader, node: yaml.Node):
|
|
|
804
826
|
else:
|
|
805
827
|
raise ConstructorError(
|
|
806
828
|
context=f"expected a scalar or sequence node, but found {node.id}",
|
|
807
|
-
|
|
829
|
+
context_mark=node.start_mark,
|
|
808
830
|
)
|
|
809
831
|
|
|
810
832
|
# Resolve environment variable
|