mirror of
https://github.com/zensical/zensical.git
synced 2026-08-23 07:36:52 +00:00
Merge pull request #180 from zensical/chore/linting-and-type-checking
zensical:chore - Setup Ruff and Mypy, lint code, check in CI
This commit is contained in:
@@ -48,6 +48,20 @@ jobs:
|
||||
- name: Run rustfmt
|
||||
run: find crates -name "*.rs" -exec rustfmt --check {} \;
|
||||
|
||||
- name: Set up Python
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install dependencies
|
||||
run: uv sync --all-groups
|
||||
|
||||
- name: Run ruff
|
||||
run: uv run ruff check --no-fix-only .
|
||||
|
||||
- name: Run mypy
|
||||
run: uv run mypy .
|
||||
|
||||
security:
|
||||
name: Security audit
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
[mypy]
|
||||
ignore_missing_imports = true
|
||||
warn_unused_ignores = true
|
||||
show_error_codes = true
|
||||
+79
@@ -21,5 +21,84 @@
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
target-version = "py310"
|
||||
line-length = 80
|
||||
fix = true
|
||||
|
||||
[lint]
|
||||
select = ["ALL"]
|
||||
ignore = [
|
||||
# Whole categories
|
||||
"AIR", # We're not using Airflow
|
||||
"ASYNC", # We're not using async code
|
||||
"DJ", # We're not using Django
|
||||
"EM", # We're fine with literal (f-)strings when raising exceptions
|
||||
"FAST", # We're not using FastAPI
|
||||
"FIX", # We allow ourselves to add TODO or HACK comments
|
||||
"ICN", # We're not using packages targeted by these rules
|
||||
"INT", # We're not using gettext
|
||||
"NPY", # We're not using NumPy
|
||||
"PD", # We're not using Pandas
|
||||
"PTH", # We're fine with using `os.path` for now
|
||||
# Individual rules
|
||||
"A001", # Variable is shadowing a Python builtin
|
||||
"ANN204", # Missing return type annotation for special method __str__
|
||||
"ANN401", # Dynamically typed expressions (typing.Any) are disallowed
|
||||
"ARG005", # Unused lambda argument
|
||||
"C901", # Too complex
|
||||
"COM812", # Conflicts with formatting
|
||||
"D100", # Missing docstring in public module
|
||||
"D102", # Missing docstring in public method
|
||||
"D104", # Missing docstring in public package
|
||||
"D105", # Missing docstring in magic method
|
||||
"D107", # Missing docstring in `__init__` method
|
||||
"D417", # Missing argument description in the docstring
|
||||
"E501", # Line too long
|
||||
"ERA001", # Commented out code
|
||||
"FBT001", # Boolean positional parameter in function signature
|
||||
"FBT003", # Boolean positional value in function call
|
||||
"G004", # Logging statement uses f-string
|
||||
"PLR0911", # Too many return statements
|
||||
"PLR0912", # Too many branches
|
||||
"PLR0913", # Too many arguments to function call
|
||||
"PLR0915", # Too many statements
|
||||
"RUF003", # Ambiguous emdash/endash
|
||||
"SIM108", # Consider ternary operator
|
||||
"SLF001", # Private member accessed
|
||||
"TRY003", # Avoid specifying long messages outside the exception class
|
||||
]
|
||||
|
||||
[lint.per-file-ignores]
|
||||
"python/**.py" = [
|
||||
"S110", # Consider logging exceptions
|
||||
]
|
||||
"python/zensical/main.py" = [
|
||||
"T201", # Print statements
|
||||
]
|
||||
"python/**.pyi" = [
|
||||
"PYI021", # Docstrings in stubs
|
||||
]
|
||||
"scripts/*.py" = [
|
||||
"PLR2004", # Magic value used in comparison
|
||||
"S603", # Possible untrusted input
|
||||
"S605", # Starting process with shell=True
|
||||
"S607", # Subprocess with partial executable path
|
||||
"T201", # Print statements
|
||||
]
|
||||
|
||||
[lint.flake8-quotes]
|
||||
docstring-quotes = "double"
|
||||
|
||||
[lint.flake8-tidy-imports]
|
||||
ban-relative-imports = "all"
|
||||
|
||||
[lint.isort]
|
||||
known-first-party = ["zensical"]
|
||||
|
||||
[lint.pydocstyle]
|
||||
convention = "google"
|
||||
|
||||
[format]
|
||||
docstring-code-format = true
|
||||
docstring-code-line-length = 80
|
||||
|
||||
|
||||
+3
-5
@@ -69,7 +69,10 @@ zensical = "zensical.main:cli"
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"maturin>=1.9.3",
|
||||
"mypy>=1.19.0",
|
||||
"ruff>=0.12.8",
|
||||
"types-markdown>=3.10.0",
|
||||
"types-pyyaml>=6.0.12",
|
||||
]
|
||||
|
||||
[tool.maturin]
|
||||
@@ -81,8 +84,3 @@ include = [
|
||||
"python/zensical/templates/**/*",
|
||||
"LICENSE.md",
|
||||
]
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 80
|
||||
fix = true
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
from .zensical import *
|
||||
from zensical.zensical import * # noqa: F403
|
||||
|
||||
__doc__ = zensical.__doc__
|
||||
if hasattr(zensical, "__all__"):
|
||||
__all__ = zensical.__all__
|
||||
__doc__ = zensical.__doc__ # type: ignore[name-defined] # noqa: F405
|
||||
if hasattr(zensical, "__all__"): # type: ignore[name-defined] # noqa: F405
|
||||
__all__ = zensical.__all__ # type: ignore[name-defined] # noqa: F405
|
||||
|
||||
+71
-101
@@ -27,21 +27,22 @@ import hashlib
|
||||
import importlib
|
||||
import os
|
||||
import pickle
|
||||
from typing import IO, Any
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import yaml
|
||||
from click import ClickException
|
||||
from deepmerge import always_merger
|
||||
from yaml import BaseLoader, Loader, YAMLError
|
||||
from yaml.constructor import ConstructorError
|
||||
|
||||
from zensical.extensions.emoji import to_svg, twemoji
|
||||
|
||||
try:
|
||||
import tomllib
|
||||
except ModuleNotFoundError:
|
||||
import tomli as tomllib # type: ignore
|
||||
import tomli as tomllib # type: ignore[no-redef]
|
||||
|
||||
from click import ClickException
|
||||
from deepmerge import always_merger
|
||||
from typing import Any, IO
|
||||
from yaml import BaseLoader, Loader, YAMLError
|
||||
from yaml.constructor import ConstructorError
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from .extensions.emoji import to_svg, twemoji
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Globals
|
||||
@@ -64,9 +65,7 @@ side, and use it directly when needed. It's a hack but will do for now.
|
||||
|
||||
|
||||
class ConfigurationError(ClickException):
|
||||
"""
|
||||
Configuration resolution or validation failed.
|
||||
"""
|
||||
"""Configuration resolution or validation failed."""
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
@@ -75,22 +74,17 @@ class ConfigurationError(ClickException):
|
||||
|
||||
|
||||
def parse_config(path: str) -> dict:
|
||||
"""
|
||||
Parse configuration file.
|
||||
"""
|
||||
"""Parse configuration file."""
|
||||
# Decide by extension; no need to convert to Path
|
||||
_, ext = os.path.splitext(path)
|
||||
if ext.lower() == ".toml":
|
||||
return parse_zensical_config(path)
|
||||
else:
|
||||
return parse_mkdocs_config(path)
|
||||
return parse_mkdocs_config(path)
|
||||
|
||||
|
||||
def parse_zensical_config(path: str) -> dict:
|
||||
"""
|
||||
Parse zensical.toml configuration file.
|
||||
"""
|
||||
global _CONFIG
|
||||
"""Parse zensical.toml configuration file."""
|
||||
global _CONFIG # noqa: PLW0603
|
||||
with open(path, "rb") as f:
|
||||
config = tomllib.load(f)
|
||||
if "project" in config:
|
||||
@@ -102,11 +96,9 @@ def parse_zensical_config(path: str) -> dict:
|
||||
|
||||
|
||||
def parse_mkdocs_config(path: str) -> dict:
|
||||
"""
|
||||
Parse mkdocs.yml configuration file.
|
||||
"""
|
||||
global _CONFIG
|
||||
with open(path, "r") as f:
|
||||
"""Parse mkdocs.yml configuration file."""
|
||||
global _CONFIG # noqa: PLW0603
|
||||
with open(path) as f:
|
||||
config = _yaml_load(f)
|
||||
|
||||
# Apply defaults and return parsed configuration
|
||||
@@ -114,24 +106,20 @@ def parse_mkdocs_config(path: str) -> dict:
|
||||
return _CONFIG
|
||||
|
||||
|
||||
def get_config():
|
||||
"""
|
||||
Return configuration.
|
||||
"""
|
||||
return _CONFIG
|
||||
def get_config() -> dict:
|
||||
"""Return configuration."""
|
||||
# We assume this function is only called after populating `_CONFIG`.
|
||||
return _CONFIG # type: ignore[return-value]
|
||||
|
||||
|
||||
def get_theme_dir() -> str:
|
||||
"""
|
||||
Return the theme directory.
|
||||
"""
|
||||
"""Return the theme directory."""
|
||||
path = os.path.dirname(os.path.abspath(__file__))
|
||||
return os.path.join(path, "templates")
|
||||
|
||||
|
||||
def _apply_defaults(config: dict, path: str) -> dict:
|
||||
"""
|
||||
Apply default settings in configuration.
|
||||
"""Apply default settings in configuration.
|
||||
|
||||
Note that this is loosely based on the defaults that MkDocs sets in its own
|
||||
configuration system, which we won't port for compatibility right now, as
|
||||
@@ -146,12 +134,12 @@ def _apply_defaults(config: dict, path: str) -> dict:
|
||||
|
||||
# Set site directory
|
||||
set_default(config, "site_dir", "site", str)
|
||||
if ".." in config.get("site_dir"):
|
||||
if ".." in config.get("site_dir", ""):
|
||||
raise ConfigurationError("site_dir must not contain '..'")
|
||||
|
||||
# Set docs directory
|
||||
set_default(config, "docs_dir", "docs", str)
|
||||
if ".." in config.get("docs_dir"):
|
||||
if ".." in config.get("docs_dir", ""):
|
||||
raise ConfigurationError("docs_dir must not contain '..'")
|
||||
|
||||
# Set defaults for core settings
|
||||
@@ -475,9 +463,7 @@ def _apply_defaults(config: dict, path: str) -> dict:
|
||||
def set_default(
|
||||
entry: dict, key: str, default: Any, data_type: type | None = None
|
||||
) -> Any:
|
||||
"""
|
||||
Set a key to a default value if it isn't set, and optionally cast it to the specified data type.
|
||||
"""
|
||||
"""Set a key to a default value if it isn't set, and optionally cast it to the specified data type."""
|
||||
if key in entry and entry[key] is None:
|
||||
del entry[key]
|
||||
|
||||
@@ -489,24 +475,22 @@ def set_default(
|
||||
try:
|
||||
entry[key] = data_type(entry[key])
|
||||
except (ValueError, TypeError) as e:
|
||||
raise ValueError(f"Failed to cast key '{key}' to {data_type}: {e}")
|
||||
raise ValueError(
|
||||
f"Failed to cast key '{key}' to {data_type}: {e}"
|
||||
) from e
|
||||
|
||||
# Return the resulting value
|
||||
return entry[key]
|
||||
|
||||
|
||||
def _hash(data: Any) -> int:
|
||||
"""
|
||||
Compute a hash for the given data.
|
||||
"""
|
||||
hash = hashlib.sha1(pickle.dumps(data))
|
||||
"""Compute a hash for the given data."""
|
||||
hash = hashlib.sha1(pickle.dumps(data)) # noqa: S324
|
||||
return int(hash.hexdigest(), 16) % (2**64)
|
||||
|
||||
|
||||
def _convert_extra(data: dict | list) -> dict | list:
|
||||
"""
|
||||
Recursively convert all None values in a dictionary or list to empty strings.
|
||||
"""
|
||||
"""Recursively convert all None values in a dictionary or list to empty strings."""
|
||||
if isinstance(data, dict):
|
||||
# Process each key-value pair in the dictionary
|
||||
return {
|
||||
@@ -515,7 +499,7 @@ def _convert_extra(data: dict | list) -> dict | list:
|
||||
else ("" if value is None else value)
|
||||
for key, value in data.items()
|
||||
}
|
||||
elif isinstance(data, list):
|
||||
if isinstance(data, list):
|
||||
# Process each item in the list
|
||||
return [
|
||||
_convert_extra(item)
|
||||
@@ -523,14 +507,11 @@ def _convert_extra(data: dict | list) -> dict | list:
|
||||
else ("" if item is None else item)
|
||||
for item in data
|
||||
]
|
||||
else:
|
||||
return data
|
||||
return data
|
||||
|
||||
|
||||
def _resolve(symbol: str):
|
||||
"""
|
||||
Resolve a symbol to its corresponding Python object.
|
||||
"""
|
||||
def _resolve(symbol: str) -> Any:
|
||||
"""Resolve a symbol to its corresponding Python object."""
|
||||
module_path, func_name = symbol.rsplit(".", 1)
|
||||
module = importlib.import_module(module_path)
|
||||
return getattr(module, func_name)
|
||||
@@ -540,16 +521,14 @@ def _resolve(symbol: str):
|
||||
|
||||
|
||||
def _convert_nav(nav: list) -> list:
|
||||
"""
|
||||
Convert MkDocs navigation
|
||||
"""
|
||||
"""Convert MkDocs navigation."""
|
||||
return [_convert_nav_item(entry) for entry in nav]
|
||||
|
||||
|
||||
def _convert_nav_item(item: str | dict | list) -> dict | list:
|
||||
"""
|
||||
Convert MkDocs shorthand navigation structure into something more manageable
|
||||
as we need to annotate each item with a title, URL, icon, and children.
|
||||
"""Convert MkDocs shorthand navigation structure into something more manageable.
|
||||
|
||||
We need to annotate each item with a title, URL, icon, and children.
|
||||
"""
|
||||
if isinstance(item, str):
|
||||
return {
|
||||
@@ -563,7 +542,7 @@ def _convert_nav_item(item: str | dict | list) -> dict | list:
|
||||
}
|
||||
|
||||
# Handle Title: URL
|
||||
elif isinstance(item, dict):
|
||||
if isinstance(item, dict):
|
||||
for title, value in item.items():
|
||||
if isinstance(value, str):
|
||||
return {
|
||||
@@ -575,7 +554,7 @@ def _convert_nav_item(item: str | dict | list) -> dict | list:
|
||||
"is_index": _is_index(value.strip()),
|
||||
"active": False,
|
||||
}
|
||||
elif isinstance(value, list):
|
||||
if isinstance(value, list):
|
||||
return {
|
||||
"title": str(title),
|
||||
"url": None,
|
||||
@@ -585,28 +564,25 @@ def _convert_nav_item(item: str | dict | list) -> dict | list:
|
||||
"is_index": False,
|
||||
"active": False,
|
||||
}
|
||||
raise TypeError(f"Unknown nav item value type: {type(value)}")
|
||||
|
||||
# Handle a list of items
|
||||
elif isinstance(item, list):
|
||||
return [_convert_nav_item(child) for child in item]
|
||||
else:
|
||||
raise ValueError(f"Unknown nav item type: {type(item)}")
|
||||
|
||||
raise TypeError(f"Unknown nav item type: {type(item)}")
|
||||
|
||||
|
||||
def _is_index(path: str) -> bool:
|
||||
"""
|
||||
Returns, whether the given path points to a section index.
|
||||
"""
|
||||
"""Returns, whether the given path points to a section index."""
|
||||
return os.path.basename(path) in ("index.md", "README.md")
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _convert_extra_javascript(value: list[Any]) -> list:
|
||||
"""
|
||||
Ensure extra_javascript uses a structured format.
|
||||
"""
|
||||
def _convert_extra_javascript(value: list) -> list:
|
||||
"""Ensure extra_javascript uses a structured format."""
|
||||
for i, item in enumerate(value):
|
||||
if isinstance(item, str):
|
||||
value[i] = {
|
||||
@@ -621,9 +597,7 @@ def _convert_extra_javascript(value: list[Any]) -> list:
|
||||
item.setdefault("async", False)
|
||||
item.setdefault("defer", False)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Unknown extra_javascript item type: {type(item)}"
|
||||
)
|
||||
raise TypeError(f"Unknown extra_javascript item type: {type(item)}")
|
||||
|
||||
# Return resulting value
|
||||
return value
|
||||
@@ -632,12 +606,10 @@ def _convert_extra_javascript(value: list[Any]) -> list:
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _convert_markdown_extensions(value: Any):
|
||||
"""
|
||||
Convert Markdown extensions configuration to what Python Markdown expects.
|
||||
"""
|
||||
def _convert_markdown_extensions(value: Any) -> tuple[list[str], dict]:
|
||||
"""Convert Markdown extensions configuration to what Python Markdown expects."""
|
||||
markdown_extensions = ["toc", "tables"]
|
||||
mdx_configs = {"toc": {}, "tables": {}}
|
||||
mdx_configs: dict[str, dict[str, Any]] = {"toc": {}, "tables": {}}
|
||||
|
||||
# In case of Python Markdown Extensions, we allow to omit the necessary
|
||||
# quotes around the extension names, so we need to hoist the extensions
|
||||
@@ -645,24 +617,24 @@ def _convert_markdown_extensions(value: Any):
|
||||
# actually parse the configuration.
|
||||
if "pymdownx" in value:
|
||||
pymdownx = value.pop("pymdownx")
|
||||
for ext, config in pymdownx.items():
|
||||
for ext, conf in pymdownx.items():
|
||||
# Special case for blocks extension, which has another level of
|
||||
# nesting. This is the only extension that requires this.
|
||||
if ext == "blocks":
|
||||
for block, config in config.items():
|
||||
for block, config in conf.items():
|
||||
value[f"pymdownx.{ext}.{block}"] = config
|
||||
else:
|
||||
value[f"pymdownx.{ext}"] = config
|
||||
value[f"pymdownx.{ext}"] = conf
|
||||
|
||||
# Same as for Python Markdown extensions, see above
|
||||
if "zensical" in value:
|
||||
zensical = value.pop("zensical")
|
||||
for ext, config in zensical.items():
|
||||
for ext, conf in zensical.items():
|
||||
if ext == "extensions":
|
||||
for key, config in config.items():
|
||||
for key, config in conf.items():
|
||||
value[f"zensical.{ext}.{key}"] = config
|
||||
else:
|
||||
value[f"zensical.{ext}"] = config
|
||||
value[f"zensical.{ext}"] = conf
|
||||
|
||||
# Extensions can be defined as a dict
|
||||
if isinstance(value, dict):
|
||||
@@ -688,15 +660,12 @@ def _convert_markdown_extensions(value: Any):
|
||||
|
||||
|
||||
def _convert_plugins(value: Any, config: dict) -> dict:
|
||||
"""
|
||||
Convert plugins configuration to something we can work with.
|
||||
"""
|
||||
"""Convert plugins configuration to something we can work with."""
|
||||
plugins = {}
|
||||
|
||||
# Plugins can be defined as a dict
|
||||
if isinstance(value, dict):
|
||||
for name, data in value.items():
|
||||
plugins[name] = data
|
||||
plugins.update(value)
|
||||
|
||||
# Plugins can also be defined as a list
|
||||
else:
|
||||
@@ -753,8 +722,7 @@ def _convert_plugins(value: Any, config: dict) -> dict:
|
||||
def _yaml_load(
|
||||
source: IO, loader: type[BaseLoader] | None = None
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Load configuration file and resolve environment variables and parent files.
|
||||
"""Load configuration file and resolve environment variables and parent files.
|
||||
|
||||
Note that INHERIT is only a bandaid that was introduced to allow for some
|
||||
degree of modularity, but with serious shortcomings. Zensical will use a
|
||||
@@ -769,12 +737,12 @@ def _yaml_load(
|
||||
source.read()
|
||||
.replace("material.extensions", "zensical.extensions")
|
||||
.replace("materialx", "zensical.extensions"),
|
||||
Loader=Loader,
|
||||
Loader=Loader, # noqa: S506
|
||||
)
|
||||
except YAMLError as e:
|
||||
raise ConfigurationError(
|
||||
f"Encountered an error parsing the configuration file: {e}"
|
||||
)
|
||||
) from e
|
||||
if config is None:
|
||||
return {}
|
||||
|
||||
@@ -788,7 +756,7 @@ def _yaml_load(
|
||||
raise ConfigurationError(
|
||||
f"Inherited config file '{relpath}' doesn't exist at '{abspath}'."
|
||||
)
|
||||
with open(abspath, "r") as fd:
|
||||
with open(abspath) as fd:
|
||||
parent = _yaml_load(fd, loader)
|
||||
config = always_merger.merge(parent, config)
|
||||
|
||||
@@ -796,9 +764,11 @@ def _yaml_load(
|
||||
return config
|
||||
|
||||
|
||||
def _construct_env_tag(loader: yaml.Loader, node: yaml.Node):
|
||||
"""
|
||||
Assign value of ENV variable referenced at node.
|
||||
def _construct_env_tag(
|
||||
loader: yaml.Loader,
|
||||
node: yaml.ScalarNode | yaml.SequenceNode | yaml.MappingNode,
|
||||
) -> Any:
|
||||
"""Assign value of ENV variable referenced at node.
|
||||
|
||||
MkDocs supports the use of !ENV to reference environment variables in YAML
|
||||
configuration files. We won't likely support this in Zensical, but for now
|
||||
@@ -827,7 +797,7 @@ def _construct_env_tag(loader: yaml.Loader, node: yaml.Node):
|
||||
else:
|
||||
raise ConstructorError(
|
||||
context=f"expected a scalar or sequence node, but found {node.id}",
|
||||
start_mark=node.start_mark,
|
||||
context_mark=node.start_mark,
|
||||
)
|
||||
|
||||
# Resolve environment variable
|
||||
|
||||
@@ -26,21 +26,22 @@ from __future__ import annotations
|
||||
import codecs
|
||||
import functools
|
||||
import os
|
||||
|
||||
from glob import iglob
|
||||
from markdown import Markdown
|
||||
from pymdownx import emoji, twemoji_db
|
||||
from typing import TYPE_CHECKING
|
||||
from xml.etree.ElementTree import Element
|
||||
|
||||
from pymdownx import emoji, twemoji_db
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from markdown import Markdown
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Functions
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
def twemoji(options: object, md: Markdown):
|
||||
"""
|
||||
Create twemoji index.
|
||||
"""
|
||||
def twemoji(options: dict, md: Markdown) -> dict: # noqa: ARG001
|
||||
"""Create twemoji index."""
|
||||
paths = options.get("custom_icons", [])[:]
|
||||
return _load_twemoji_index(tuple(paths))
|
||||
|
||||
@@ -53,14 +54,12 @@ def to_svg(
|
||||
alt: str,
|
||||
title: str,
|
||||
category: str,
|
||||
options: object,
|
||||
options: dict,
|
||||
md: Markdown,
|
||||
):
|
||||
"""
|
||||
Load icon.
|
||||
"""
|
||||
) -> Element[str]:
|
||||
"""Load icon."""
|
||||
if not uc:
|
||||
icons = md.inlinePatterns["emoji"].emoji_index["emoji"]
|
||||
icons = md.inlinePatterns["emoji"].emoji_index["emoji"] # type: ignore[attr-defined]
|
||||
|
||||
# Create and return element to host icon
|
||||
el = Element("span", {"class": options.get("classes", index)})
|
||||
@@ -78,20 +77,16 @@ def to_svg(
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def _load(file: str):
|
||||
"""
|
||||
Load icon from file.
|
||||
"""
|
||||
@functools.cache
|
||||
def _load(file: str) -> str:
|
||||
"""Load icon from file."""
|
||||
with codecs.open(file, encoding="utf-8") as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def _load_twemoji_index(paths):
|
||||
"""
|
||||
Load twemoji index and add icons.
|
||||
"""
|
||||
@functools.cache
|
||||
def _load_twemoji_index(paths: tuple[str, ...]) -> dict:
|
||||
"""Load twemoji index and add icons."""
|
||||
index = {
|
||||
"name": "twemoji",
|
||||
"emoji": twemoji_db.emoji,
|
||||
@@ -106,8 +101,8 @@ def _load_twemoji_index(paths):
|
||||
|
||||
# Index icons provided by the theme and via custom icons
|
||||
glob = os.path.join(base, "**", "*.svg")
|
||||
glob = iglob(os.path.normpath(glob), recursive=True)
|
||||
for file in glob:
|
||||
svgs = iglob(os.path.normpath(glob), recursive=True)
|
||||
for file in svgs:
|
||||
icon = file[len(base) + 1 : -4].replace(os.path.sep, "-")
|
||||
|
||||
# Add icon to index
|
||||
|
||||
@@ -23,12 +23,17 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import PurePosixPath
|
||||
from typing import TYPE_CHECKING
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from markdown import Extension, Markdown
|
||||
from markdown.treeprocessors import Treeprocessor
|
||||
from markdown.util import AMP_SUBSTITUTE
|
||||
from pathlib import PurePosixPath
|
||||
from xml.etree.ElementTree import Element
|
||||
from urllib.parse import urlparse
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from xml.etree.ElementTree import Element
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Classes
|
||||
@@ -36,8 +41,7 @@ from urllib.parse import urlparse
|
||||
|
||||
|
||||
class LinksProcessor(Treeprocessor):
|
||||
"""
|
||||
Tree processor to replace links in Markdown with URLs.
|
||||
"""Tree processor to replace links in Markdown with URLs.
|
||||
|
||||
Note that we view this as a bandaid until we can do processing on proper
|
||||
HTML ASTs in Rust. In the meantime, we just replace them as we find them.
|
||||
@@ -50,7 +54,7 @@ class LinksProcessor(Treeprocessor):
|
||||
self.path = path # Current page
|
||||
self.use_directory_urls = use_directory_urls
|
||||
|
||||
def run(self, root: Element):
|
||||
def run(self, root: Element) -> None:
|
||||
# Now, we determine whether the current page is an index page, as we
|
||||
# must apply slightly different handling in case of directory URLs
|
||||
current_is_index = get_name(self.path) in ("index.md", "README.md")
|
||||
@@ -64,7 +68,7 @@ class LinksProcessor(Treeprocessor):
|
||||
# Extract value - Python Markdown does some weird stuff where it
|
||||
# replaces mailto: links with double encoded entities. MkDocs just
|
||||
# skips if it detects that, so we do the same.
|
||||
value = el.get(key)
|
||||
value = el.get(key, "")
|
||||
if AMP_SUBSTITUTE in value:
|
||||
continue
|
||||
|
||||
@@ -101,21 +105,15 @@ class LinksProcessor(Treeprocessor):
|
||||
|
||||
|
||||
class LinksExtension(Extension):
|
||||
"""
|
||||
A Markdown extension to resolve links to other Markdown files.
|
||||
"""
|
||||
"""A Markdown extension to resolve links to other Markdown files."""
|
||||
|
||||
def __init__(self, path: str, use_directory_urls: bool):
|
||||
"""
|
||||
Initialize the extension.
|
||||
"""
|
||||
"""Initialize the extension."""
|
||||
self.path = path # Current page
|
||||
self.use_directory_urls = use_directory_urls
|
||||
|
||||
def extendMarkdown(self, md: Markdown):
|
||||
"""
|
||||
Register Markdown extension.
|
||||
"""
|
||||
def extendMarkdown(self, md: Markdown) -> None: # noqa: N802
|
||||
"""Register Markdown extension."""
|
||||
md.registerExtension(self)
|
||||
|
||||
# Create and register treeprocessor - we use the same priority as the
|
||||
@@ -132,8 +130,6 @@ class LinksExtension(Extension):
|
||||
|
||||
|
||||
def get_name(path: str) -> str:
|
||||
"""
|
||||
Get the name of a file from a given path.
|
||||
"""
|
||||
path = PurePosixPath(path)
|
||||
return path.name
|
||||
"""Get the name of a file from a given path."""
|
||||
pure_path = PurePosixPath(path)
|
||||
return pure_path.name
|
||||
|
||||
@@ -24,14 +24,17 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import posixpath
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from markdown import Extension, Markdown
|
||||
from markdown.treeprocessors import Treeprocessor
|
||||
from urllib.parse import urlparse
|
||||
from xml.etree.ElementTree import Element
|
||||
|
||||
from .links import LinksProcessor
|
||||
from .utilities.filter import Filter
|
||||
from zensical.extensions.links import LinksProcessor
|
||||
from zensical.extensions.utilities.filter import Filter
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from xml.etree.ElementTree import Element
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Classes
|
||||
@@ -39,24 +42,19 @@ from .utilities.filter import Filter
|
||||
|
||||
|
||||
class PreviewProcessor(Treeprocessor):
|
||||
"""
|
||||
A Markdown treeprocessor to enable instant previews on links.
|
||||
"""A Markdown treeprocessor to enable instant previews on links.
|
||||
|
||||
Note that this treeprocessor is dependent on the `links` treeprocessor
|
||||
registered programmatically before rendering a page.
|
||||
"""
|
||||
|
||||
def __init__(self, md: Markdown, config: dict):
|
||||
"""
|
||||
Initialize the treeprocessor.
|
||||
"""
|
||||
"""Initialize the treeprocessor."""
|
||||
super().__init__(md)
|
||||
self.config = config
|
||||
|
||||
def run(self, root: Element):
|
||||
"""
|
||||
Run the treeprocessor.
|
||||
"""
|
||||
def run(self, root: Element) -> None:
|
||||
"""Run the treeprocessor."""
|
||||
at = self.md.treeprocessors.get_index_for_name("zrelpath")
|
||||
|
||||
# Hack: Python Markdown has no notion of where it is, i.e., which file
|
||||
@@ -84,9 +82,10 @@ class PreviewProcessor(Treeprocessor):
|
||||
# Walk through all configurations - @todo refactor so that we don't
|
||||
# iterate multiple times over the same elements
|
||||
for configuration in configurations:
|
||||
if not configuration.get("sources"):
|
||||
if not configuration.get("targets"):
|
||||
continue
|
||||
if not configuration.get("sources") and not configuration.get(
|
||||
"targets"
|
||||
):
|
||||
continue
|
||||
|
||||
# Skip if page should not be considered
|
||||
filter = get_filter(configuration, "sources")
|
||||
@@ -123,8 +122,7 @@ class PreviewProcessor(Treeprocessor):
|
||||
|
||||
|
||||
class PreviewExtension(Extension):
|
||||
"""
|
||||
A Markdown extension to enable instant previews on links.
|
||||
"""A Markdown extension to enable instant previews on links.
|
||||
|
||||
This extensions allows to automatically add the `data-preview` attribute to
|
||||
internal links matching specific criteria, so Material for MkDocs renders a
|
||||
@@ -132,10 +130,8 @@ class PreviewExtension(Extension):
|
||||
add previews to links in a programmatic way.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
Initialize the extension.
|
||||
"""
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||
"""Initialize the extension."""
|
||||
self.config = {
|
||||
"configurations": [[], "Filter configurations"],
|
||||
"sources": [{}, "Link sources"],
|
||||
@@ -143,10 +139,8 @@ class PreviewExtension(Extension):
|
||||
}
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def extendMarkdown(self, md: Markdown):
|
||||
"""
|
||||
Register Markdown extension.
|
||||
"""
|
||||
def extendMarkdown(self, md: Markdown) -> None: # noqa: N802
|
||||
"""Register Markdown extension."""
|
||||
md.registerExtension(self)
|
||||
|
||||
# Create and register treeprocessor - we use the same priority as the
|
||||
@@ -162,17 +156,13 @@ class PreviewExtension(Extension):
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
def get_filter(settings: dict, key: str):
|
||||
"""
|
||||
Get file filter from settings.
|
||||
"""
|
||||
return Filter(config=settings.get(key, {})) # type: ignore
|
||||
def get_filter(settings: dict, key: str) -> Filter:
|
||||
"""Get file filter from settings."""
|
||||
return Filter(config=settings.get(key, {}))
|
||||
|
||||
|
||||
def resolve(processor_path: str, url_path: str) -> str:
|
||||
"""
|
||||
Resolve a relative URL path against the processor path.
|
||||
"""
|
||||
"""Resolve a relative URL path against the processor path."""
|
||||
# Remove the file name from the processor path to get the directory
|
||||
base_path = posixpath.dirname(processor_path)
|
||||
|
||||
@@ -194,8 +184,6 @@ def resolve(processor_path: str, url_path: str) -> str:
|
||||
return posixpath.join(*base_segments)
|
||||
|
||||
|
||||
def makeExtension(**kwargs):
|
||||
"""
|
||||
Register Markdown extension.
|
||||
"""
|
||||
def makeExtension(**kwargs: Any) -> PreviewExtension: # noqa: N802
|
||||
"""Register Markdown extension."""
|
||||
return PreviewExtension(**kwargs)
|
||||
|
||||
@@ -23,8 +23,9 @@
|
||||
|
||||
from html import escape
|
||||
from html.parser import HTMLParser
|
||||
from typing import Any
|
||||
|
||||
from markdown import Extension
|
||||
from markdown import Extension, Markdown
|
||||
from markdown.postprocessors import Postprocessor
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -33,17 +34,14 @@ from markdown.postprocessors import Postprocessor
|
||||
|
||||
|
||||
class SearchProcessor(Postprocessor):
|
||||
"""
|
||||
Post processor that extracts searchable content from the rendered HTML.
|
||||
"""
|
||||
"""Post processor that extracts searchable content from the rendered HTML."""
|
||||
|
||||
def __init__(self, md):
|
||||
def __init__(self, md: Markdown) -> None:
|
||||
super().__init__(md)
|
||||
self.data = []
|
||||
self.data: list[dict[str, Any]] = []
|
||||
|
||||
def run(self, html):
|
||||
def run(self, html: str) -> str:
|
||||
"""Process the rendered HTML and extract text length."""
|
||||
|
||||
# Divide page content into sections
|
||||
parser = Parser()
|
||||
parser.feed(html)
|
||||
@@ -76,17 +74,17 @@ class SearchProcessor(Postprocessor):
|
||||
class SearchExtension(Extension):
|
||||
"""Markdown extension for search indexing."""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
def __init__(self, **kwargs: Any) -> None:
|
||||
self.config = {"keep": [set(), "Set of HTML tags to keep in output"]}
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def extendMarkdown(self, md):
|
||||
def extendMarkdown(self, md: Markdown) -> None: # noqa: N802
|
||||
"""Register the PostProcessor with Markdown."""
|
||||
processor = SearchProcessor(md)
|
||||
md.postprocessors.register(processor, "search", 0)
|
||||
|
||||
|
||||
def makeExtension(**kwargs):
|
||||
def makeExtension(**kwargs: Any) -> SearchExtension: # noqa: N802
|
||||
"""Factory function for creating the extension."""
|
||||
return SearchExtension(**kwargs)
|
||||
|
||||
@@ -96,13 +94,16 @@ def makeExtension(**kwargs):
|
||||
|
||||
# HTML element
|
||||
class Element:
|
||||
"""
|
||||
"""HTML element.
|
||||
|
||||
An element with attributes, essentially a small wrapper object for the
|
||||
parser to access attributes in other callbacks than handle_starttag.
|
||||
"""
|
||||
|
||||
# Initialize HTML element
|
||||
def __init__(self, tag, attrs=None):
|
||||
def __init__(
|
||||
self, tag: str, attrs: dict[str, str | None] | None = None
|
||||
) -> None:
|
||||
self.tag = tag
|
||||
self.attrs = attrs or {}
|
||||
|
||||
@@ -111,18 +112,17 @@ class Element:
|
||||
return self.tag
|
||||
|
||||
# Support comparison (compare by tag only)
|
||||
def __eq__(self, other):
|
||||
if other is Element:
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if isinstance(other, Element):
|
||||
return self.tag == other.tag
|
||||
else:
|
||||
return self.tag == other
|
||||
return self.tag == other
|
||||
|
||||
# Support set operations
|
||||
def __hash__(self):
|
||||
return hash(self.tag)
|
||||
|
||||
# Check whether the element should be excluded
|
||||
def is_excluded(self):
|
||||
def is_excluded(self) -> bool:
|
||||
return "data-search-exclude" in self.attrs
|
||||
|
||||
|
||||
@@ -131,31 +131,31 @@ class Element:
|
||||
|
||||
# HTML section
|
||||
class Section:
|
||||
"""
|
||||
"""HTML section.
|
||||
|
||||
A block of text with markup, preceded by a title (with markup), i.e., a
|
||||
headline with a certain level (h1-h6). Internally used by the parser.
|
||||
"""
|
||||
|
||||
# Initialize HTML section
|
||||
def __init__(self, el, level, depth=0):
|
||||
def __init__(self, el: Element, level: int, depth: int = 0) -> None:
|
||||
self.el = el
|
||||
self.depth = depth
|
||||
self.depth: int | float = depth
|
||||
self.level = level
|
||||
|
||||
# Initialize section data
|
||||
self.text = []
|
||||
self.title = []
|
||||
self.id = None
|
||||
self.text: list[str] = []
|
||||
self.title: list[str] = []
|
||||
self.id: str | None = None
|
||||
|
||||
# String representation
|
||||
def __repr__(self):
|
||||
if self.id:
|
||||
return "#".join([self.el.tag, self.id])
|
||||
else:
|
||||
return self.el.tag
|
||||
return f"{self.el.tag}#{self.id}"
|
||||
return self.el.tag
|
||||
|
||||
# Check whether the section should be excluded
|
||||
def is_excluded(self):
|
||||
def is_excluded(self) -> bool:
|
||||
return self.el.is_excluded()
|
||||
|
||||
|
||||
@@ -164,7 +164,8 @@ class Section:
|
||||
|
||||
# HTML parser
|
||||
class Parser(HTMLParser):
|
||||
"""
|
||||
"""Section divider.
|
||||
|
||||
This parser divides the given string of HTML into a list of sections, each
|
||||
of which are preceded by a h1-h6 level heading. A white- and blacklist of
|
||||
tags dictates which tags should be preserved as part of the index, and
|
||||
@@ -172,31 +173,31 @@ class Parser(HTMLParser):
|
||||
"""
|
||||
|
||||
# Initialize HTML parser
|
||||
def __init__(self, *args, **kwargs):
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Tags to skip
|
||||
self.skip = set(
|
||||
[
|
||||
"object", # Objects
|
||||
"script", # Scripts
|
||||
"style", # Styles
|
||||
]
|
||||
)
|
||||
self.skip: set[str | Element] = {
|
||||
"object", # Objects
|
||||
"script", # Scripts
|
||||
"style", # Styles
|
||||
}
|
||||
|
||||
# Current context and section
|
||||
self.context = []
|
||||
self.section = None
|
||||
self.context: list[Element] = []
|
||||
self.section: Section | None = None
|
||||
|
||||
# All parsed sections
|
||||
self.data = []
|
||||
self.data: list[Section] = []
|
||||
|
||||
# Called at the start of every HTML tag
|
||||
def handle_starttag(self, tag, attrs):
|
||||
attrs = dict(attrs)
|
||||
def handle_starttag(
|
||||
self, tag: str, attrs: list[tuple[str, str | None]]
|
||||
) -> None:
|
||||
attrs_dict = dict(attrs)
|
||||
|
||||
# Ignore self-closing tags
|
||||
el = Element(tag, attrs)
|
||||
el = Element(tag, attrs_dict)
|
||||
if tag not in void:
|
||||
self.context.append(el)
|
||||
else:
|
||||
@@ -205,7 +206,7 @@ class Parser(HTMLParser):
|
||||
# Handle heading
|
||||
if tag in ([f"h{x}" for x in range(1, 7)]):
|
||||
depth = len(self.context)
|
||||
if "id" in attrs:
|
||||
if "id" in attrs_dict:
|
||||
# Ensure top-level section
|
||||
if tag != "h1" and not self.data:
|
||||
self.section = Section(Element("hx"), 1, depth)
|
||||
@@ -214,7 +215,7 @@ class Parser(HTMLParser):
|
||||
# Set identifier, if not first section
|
||||
self.section = Section(el, int(tag[1:2]), depth)
|
||||
if self.data:
|
||||
self.section.id = attrs["id"]
|
||||
self.section.id = attrs_dict["id"]
|
||||
|
||||
# Append section to list
|
||||
self.data.append(self.section)
|
||||
@@ -225,7 +226,7 @@ class Parser(HTMLParser):
|
||||
self.data.append(self.section)
|
||||
|
||||
# Handle special cases to skip
|
||||
for key, value in attrs.items():
|
||||
for key, value in attrs_dict.items():
|
||||
# Skip block if explicitly excluded from search
|
||||
if key == "data-search-exclude":
|
||||
self.skip.add(el)
|
||||
@@ -247,7 +248,7 @@ class Parser(HTMLParser):
|
||||
data.append(f"<{tag}>")
|
||||
|
||||
# Called at the end of every HTML tag
|
||||
def handle_endtag(self, tag):
|
||||
def handle_endtag(self, tag: str) -> None:
|
||||
if not self.context or self.context[-1] != tag:
|
||||
return
|
||||
|
||||
@@ -255,6 +256,7 @@ class Parser(HTMLParser):
|
||||
# a headline is nested in another element. In that case, we close the
|
||||
# current section, continuing to append data to the previous section,
|
||||
# which could also be a nested section – see https://bit.ly/3IxxIJZ
|
||||
assert self.section is not None # noqa: S101
|
||||
if self.section.depth > len(self.context):
|
||||
for section in reversed(self.data):
|
||||
if section.depth <= len(self.context):
|
||||
@@ -295,7 +297,7 @@ class Parser(HTMLParser):
|
||||
data.append(f"</{tag}>")
|
||||
|
||||
# Called for the text contents of each tag
|
||||
def handle_data(self, data):
|
||||
def handle_data(self, data: str) -> None:
|
||||
if self.skip.intersection(self.context):
|
||||
return
|
||||
|
||||
@@ -324,9 +326,11 @@ class Parser(HTMLParser):
|
||||
|
||||
# Collapse adjacent whitespace
|
||||
elif data.isspace():
|
||||
if not self.section.text or not self.section.text[-1].isspace():
|
||||
self.section.text.append(data)
|
||||
elif "pre" in self.context:
|
||||
if (
|
||||
not self.section.text
|
||||
or not self.section.text[-1].isspace()
|
||||
or "pre" in self.context
|
||||
):
|
||||
self.section.text.append(data)
|
||||
|
||||
# Handle everything else
|
||||
@@ -339,35 +343,31 @@ class Parser(HTMLParser):
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Tags to keep
|
||||
keep = set(
|
||||
[
|
||||
"p",
|
||||
"code",
|
||||
"pre",
|
||||
"li",
|
||||
"ol",
|
||||
"ul",
|
||||
"sub",
|
||||
"sup",
|
||||
]
|
||||
)
|
||||
keep = {
|
||||
"p",
|
||||
"code",
|
||||
"pre",
|
||||
"li",
|
||||
"ol",
|
||||
"ul",
|
||||
"sub",
|
||||
"sup",
|
||||
}
|
||||
|
||||
# Tags that are self-closing
|
||||
void = set(
|
||||
[
|
||||
"area",
|
||||
"base",
|
||||
"br",
|
||||
"col",
|
||||
"embed",
|
||||
"hr",
|
||||
"img",
|
||||
"input",
|
||||
"link",
|
||||
"meta",
|
||||
"param",
|
||||
"source",
|
||||
"track",
|
||||
"wbr",
|
||||
]
|
||||
)
|
||||
void = {
|
||||
"area",
|
||||
"base",
|
||||
"br",
|
||||
"col",
|
||||
"embed",
|
||||
"hr",
|
||||
"img",
|
||||
"input",
|
||||
"link",
|
||||
"meta",
|
||||
"param",
|
||||
"source",
|
||||
"track",
|
||||
"wbr",
|
||||
}
|
||||
|
||||
@@ -31,13 +31,10 @@ from fnmatch import fnmatch
|
||||
|
||||
|
||||
class Filter:
|
||||
"""
|
||||
A filter.
|
||||
"""
|
||||
"""A filter."""
|
||||
|
||||
def __init__(self, config: dict):
|
||||
"""
|
||||
Initialize the filter.
|
||||
"""Initialize the filter.
|
||||
|
||||
Arguments:
|
||||
config: The filter configuration.
|
||||
@@ -45,8 +42,7 @@ class Filter:
|
||||
self.config = config
|
||||
|
||||
def __call__(self, value: str) -> bool:
|
||||
"""
|
||||
Filter a value.
|
||||
"""Filter a value.
|
||||
|
||||
First, the inclusion patterns are checked. Regardless of whether they
|
||||
are present, the exclusion patterns are checked afterwards. This allows
|
||||
@@ -59,7 +55,6 @@ class Filter:
|
||||
Returns:
|
||||
Whether the value should be included.
|
||||
"""
|
||||
|
||||
# Check if value matches one of the inclusion patterns
|
||||
if "include" in self.config:
|
||||
for pattern in self.config["include"]:
|
||||
|
||||
+12
-17
@@ -23,14 +23,15 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import click
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import click
|
||||
from click import ClickException
|
||||
from zensical import build, serve, version
|
||||
|
||||
from zensical import build, serve, version
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Commands
|
||||
@@ -39,8 +40,8 @@ from zensical import build, serve, version
|
||||
|
||||
@click.version_option(version=version(), message="%(version)s")
|
||||
@click.group()
|
||||
def cli():
|
||||
"""Zensical - A modern static site generator"""
|
||||
def cli() -> None:
|
||||
"""Zensical - A modern static site generator."""
|
||||
|
||||
|
||||
@cli.command(name="build")
|
||||
@@ -65,10 +66,8 @@ def cli():
|
||||
is_flag=True,
|
||||
help="Strict mode (currently unsupported).",
|
||||
)
|
||||
def execute_build(config_file: str | None, **kwargs):
|
||||
"""
|
||||
Build a project.
|
||||
"""
|
||||
def execute_build(config_file: str | None, **kwargs: Any) -> None:
|
||||
"""Build a project."""
|
||||
if config_file is None:
|
||||
for file in ["zensical.toml", "mkdocs.yml", "mkdocs.yaml"]:
|
||||
if os.path.exists(file):
|
||||
@@ -81,7 +80,7 @@ def execute_build(config_file: str | None, **kwargs):
|
||||
|
||||
# Build project in Rust runtime, calling back into Python when necessary,
|
||||
# e.g., to parse MkDocs configuration format or render Markdown
|
||||
build(os.path.abspath(config_file), kwargs.get("clean"))
|
||||
build(os.path.abspath(config_file), kwargs.get("clean", False))
|
||||
|
||||
|
||||
@cli.command(name="serve")
|
||||
@@ -112,10 +111,8 @@ def execute_build(config_file: str | None, **kwargs):
|
||||
is_flag=True,
|
||||
help="Strict mode (currently unsupported).",
|
||||
)
|
||||
def execute_serve(config_file: str | None, **kwargs):
|
||||
"""
|
||||
Build and serve a project.
|
||||
"""
|
||||
def execute_serve(config_file: str | None, **kwargs: Any) -> None:
|
||||
"""Build and serve a project."""
|
||||
if config_file is None:
|
||||
for file in ["zensical.toml", "mkdocs.yml", "mkdocs.yaml"]:
|
||||
if os.path.exists(file):
|
||||
@@ -137,10 +134,8 @@ def execute_serve(config_file: str | None, **kwargs):
|
||||
type=click.Path(file_okay=False, dir_okay=True, writable=True),
|
||||
required=False,
|
||||
)
|
||||
def new_project(directory: str | None, **kwargs):
|
||||
"""
|
||||
Create a new template project in the current directory or in the given
|
||||
directory.
|
||||
def new_project(directory: str | None, **kwargs: Any) -> None: # noqa: ARG001
|
||||
"""Create a new template project in the current directory or in the given directory.
|
||||
|
||||
Raises:
|
||||
ClickException: if the directory already contains a zensical.toml or a
|
||||
|
||||
+18
-17
@@ -24,15 +24,19 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import yaml
|
||||
|
||||
from datetime import date, datetime
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import yaml
|
||||
from markdown import Markdown
|
||||
from yaml import SafeLoader
|
||||
|
||||
from .config import get_config
|
||||
from .extensions.links import LinksExtension
|
||||
from .extensions.search import SearchExtension
|
||||
from zensical.config import get_config
|
||||
from zensical.extensions.links import LinksExtension
|
||||
from zensical.extensions.search import SearchExtension
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from zensical.extensions.search import SearchProcessor
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Constants
|
||||
@@ -53,8 +57,7 @@ Regex pattern to extract front matter.
|
||||
|
||||
|
||||
def render(content: str, path: str) -> dict:
|
||||
"""
|
||||
Render Markdown and return HTML.
|
||||
"""Render Markdown and return HTML.
|
||||
|
||||
This function returns rendered HTML as well as the table of contents and
|
||||
metadata. Now, this is the part where Zensical needs to call into Python,
|
||||
@@ -77,8 +80,8 @@ def render(content: str, path: str) -> dict:
|
||||
links.extendMarkdown(md)
|
||||
|
||||
# Register search extension, which extracts text for search indexing
|
||||
search = SearchExtension()
|
||||
search.extendMarkdown(md)
|
||||
search_extension = SearchExtension()
|
||||
search_extension.extendMarkdown(md)
|
||||
|
||||
# First, extract metadata - the Python Markdown parser brings a metadata
|
||||
# extension, but the implementation is broken, as it does not support full
|
||||
@@ -91,7 +94,7 @@ def render(content: str, path: str) -> dict:
|
||||
content = content[match.end() :].lstrip("\n")
|
||||
else:
|
||||
meta = {}
|
||||
except Exception:
|
||||
except Exception: # noqa: BLE001
|
||||
pass
|
||||
|
||||
# Convert Markdown and set nullish metadata to empty string, since we
|
||||
@@ -106,24 +109,22 @@ def render(content: str, path: str) -> dict:
|
||||
meta[key] = value.isoformat()
|
||||
|
||||
# Obtain search index data, unless page is excluded
|
||||
search = md.postprocessors["search"]
|
||||
search_processor: SearchProcessor = md.postprocessors["search"] # type: ignore[assignment]
|
||||
if meta.get("search", {}).get("exclude", False):
|
||||
search.data = []
|
||||
search_processor.data = []
|
||||
|
||||
# Return Markdown with metadata
|
||||
return {
|
||||
"meta": meta,
|
||||
"content": content,
|
||||
"search": search.data,
|
||||
"search": search_processor.data,
|
||||
"title": "",
|
||||
"toc": [_convert_toc(item) for item in getattr(md, "toc_tokens", [])],
|
||||
}
|
||||
|
||||
|
||||
def _convert_toc(item: any):
|
||||
"""
|
||||
Convert a table of contents item to navigation item format.
|
||||
"""
|
||||
def _convert_toc(item: Any) -> dict:
|
||||
"""Convert a table of contents item to navigation item format."""
|
||||
toc_item = {
|
||||
"title": item["data-toc-label"] or item["name"],
|
||||
"id": item["id"],
|
||||
|
||||
@@ -25,20 +25,14 @@
|
||||
# Functions
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
def build(config_file: str, clean: bool):
|
||||
"""
|
||||
Builds the project.
|
||||
"""
|
||||
def build(config_file: str, clean: bool) -> None:
|
||||
"""Builds the project."""
|
||||
|
||||
def serve(config_file: str, dev_addr: str):
|
||||
"""
|
||||
Builds and serves the project.
|
||||
"""
|
||||
def serve(config_file: str, options: dict) -> None:
|
||||
"""Builds and serves the project."""
|
||||
|
||||
def version() -> str:
|
||||
"""
|
||||
Returns the current version.
|
||||
"""
|
||||
"""Returns the current version."""
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
Vendored
+34
-34
@@ -25,11 +25,14 @@
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
import os, re, sys, tomllib # noqa: E401
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from glob import glob
|
||||
|
||||
import tomllib
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Classes
|
||||
# ----------------------------------------------------------------------------
|
||||
@@ -48,8 +51,7 @@ class TypeError(ValueError):
|
||||
|
||||
@dataclass
|
||||
class Message:
|
||||
"""
|
||||
Commit message.
|
||||
"""Commit message.
|
||||
|
||||
This class represents a commit message with a scope, type, and description.
|
||||
It provides methods to parse and validate commit messages according to our
|
||||
@@ -59,9 +61,7 @@ class Message:
|
||||
|
||||
@classmethod
|
||||
def parse(cls, message: str) -> "Message":
|
||||
"""
|
||||
Parse a commit message string into an object.
|
||||
"""
|
||||
"""Parse a commit message string into an object."""
|
||||
match = re.match(r"^([^:]+):([^\s]+) - (.+)$", message)
|
||||
if not match:
|
||||
raise ValueError("Required format: <scope>:<type> - <description>")
|
||||
@@ -71,9 +71,7 @@ class Message:
|
||||
return cls(scope=scope, type=type, description=description)
|
||||
|
||||
def validate(self, scopes: dict[str, str]) -> None:
|
||||
"""
|
||||
Validate the commit message against the given scopes and types.
|
||||
"""
|
||||
"""Validate the commit message against the given scopes and types."""
|
||||
if self.scope not in scopes:
|
||||
raise ScopeError(f"Invalid scope: {self.scope}")
|
||||
|
||||
@@ -119,17 +117,16 @@ class Message:
|
||||
|
||||
|
||||
def resolve(directory: str) -> dict[str, str] | None:
|
||||
"""
|
||||
Return commit scopes for a cargo project.
|
||||
"""Return commit scopes for a cargo project.
|
||||
|
||||
This function checks, if the given directory contains a `Cargo.toml` file,
|
||||
This function checks if the given directory contains a `Cargo.toml` file,
|
||||
and if so, parses it to extract the workspace members. It then resolves the
|
||||
valid scopes, which are the names of the crates defined in the respective
|
||||
`Cargo.toml` files.
|
||||
"""
|
||||
path = os.path.join(directory, "Cargo.toml")
|
||||
if not os.path.isfile(path):
|
||||
return
|
||||
return None
|
||||
|
||||
# Open and parse the Cargo.toml file
|
||||
with open(path, "rb") as f:
|
||||
@@ -155,6 +152,8 @@ def resolve(directory: str) -> dict[str, str] | None:
|
||||
if package and "name" in package:
|
||||
return {package["name"]: directory}
|
||||
|
||||
return None
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Constants
|
||||
@@ -199,45 +198,46 @@ ANSI escape code to reset formatting.
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Commit message linter.
|
||||
"""
|
||||
def main() -> int:
|
||||
"""Commit message linter."""
|
||||
if len(sys.argv) < 2:
|
||||
print("No commit message provided.")
|
||||
sys.exit(1)
|
||||
return 1
|
||||
|
||||
# Commit message might be passed as string, or in a file
|
||||
commit = sys.argv[1]
|
||||
if os.path.isfile(commit):
|
||||
with open(sys.argv[1], "r") as f:
|
||||
with open(sys.argv[1]) as f:
|
||||
message = f.read().strip()
|
||||
else:
|
||||
message = commit.strip()
|
||||
|
||||
# Skip merge commits
|
||||
if message.startswith("Merge branch"):
|
||||
return sys.exit(0)
|
||||
return 0
|
||||
|
||||
# Resolve cargo workspace members and parse commit message
|
||||
scopes = resolve(os.path.curdir)
|
||||
scopes["workspace"] = "."
|
||||
try:
|
||||
msg = Message.parse(message)
|
||||
msg.validate(scopes)
|
||||
if scopes:
|
||||
scopes["workspace"] = "."
|
||||
try:
|
||||
msg = Message.parse(message)
|
||||
msg.validate(scopes)
|
||||
|
||||
# If an error happened, print it
|
||||
except ValueError as e:
|
||||
print(f"{FG_RED}✘{RESET} {BG_RED} Error {RESET} {e}")
|
||||
print("")
|
||||
print(" Commit rejected.")
|
||||
print("")
|
||||
# If an error happened, print it
|
||||
except ValueError as e:
|
||||
print(f"{FG_RED}✘{RESET} {BG_RED} Error {RESET} {e}")
|
||||
print()
|
||||
print(" Commit rejected.")
|
||||
print()
|
||||
|
||||
# Exit with error
|
||||
return sys.exit(1)
|
||||
# Exit with error
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
sys.exit(main())
|
||||
|
||||
Vendored
+8
-5
@@ -25,16 +25,17 @@
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
import os, shutil, subprocess # noqa: E401
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Program
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Set up development environment.
|
||||
def main() -> int:
|
||||
"""Set up development environment.
|
||||
|
||||
This script clones the Zensical UI repository, and symbolically links the
|
||||
build artifacts into the Python package directory for development use.
|
||||
@@ -71,8 +72,10 @@ def main():
|
||||
with open(path, "w") as f:
|
||||
f.write("templates\n")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
raise SystemExit(main())
|
||||
|
||||
Vendored
+7
-6
@@ -25,17 +25,16 @@
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
import os, subprocess # noqa: E401
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Program
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Prepare production build.
|
||||
"""
|
||||
def main() -> int:
|
||||
"""Prepare production build."""
|
||||
os.makedirs("tmp", exist_ok=True)
|
||||
|
||||
# Clone UI repository into tmp directory
|
||||
@@ -64,8 +63,10 @@ def main():
|
||||
subprocess.run(["rm", "-rf", path], check=True)
|
||||
subprocess.run(["cp", "-r", dist_dir, path], check=True)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
raise SystemExit(main())
|
||||
|
||||
@@ -32,6 +32,79 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/82/e5d2c1c67d19841e9edc74954c827444ae826978499bde3dfc1d007c8c11/deepmerge-2.0-py3-none-any.whl", hash = "sha256:6de9ce507115cff0bed95ff0ce9ecc31088ef50cbdf09bc90a09349a318b3d00", size = 13475, upload-time = "2024-08-30T05:31:48.659Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "librt"
|
||||
version = "0.7.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b3/d9/6f3d3fcf5e5543ed8a60cc70fa7d50508ed60b8a10e9af6d2058159ab54e/librt-0.7.3.tar.gz", hash = "sha256:3ec50cf65235ff5c02c5b747748d9222e564ad48597122a361269dd3aa808798", size = 144549, upload-time = "2025-12-06T19:04:45.553Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/66/79a14e672256ef58144a24eb49adb338ec02de67ff4b45320af6504682ab/librt-0.7.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2682162855a708e3270eba4b92026b93f8257c3e65278b456c77631faf0f4f7a", size = 54707, upload-time = "2025-12-06T19:03:10.881Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/fa/b709c65a9d5eab85f7bcfe0414504d9775aaad6e78727a0327e175474caa/librt-0.7.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:440c788f707c061d237c1e83edf6164ff19f5c0f823a3bf054e88804ebf971ec", size = 56670, upload-time = "2025-12-06T19:03:12.107Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/56/0685a0772ec89ddad4c00e6b584603274c3d818f9a68e2c43c4eb7b39ee9/librt-0.7.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:399938edbd3d78339f797d685142dd8a623dfaded023cf451033c85955e4838a", size = 161045, upload-time = "2025-12-06T19:03:13.444Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/d9/863ada0c5ce48aefb89df1555e392b2209fcb6daee4c153c031339b9a89b/librt-0.7.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1975eda520957c6e0eb52d12968dd3609ffb7eef05d4223d097893d6daf1d8a7", size = 169532, upload-time = "2025-12-06T19:03:14.699Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/a0/71da6c8724fd16c31749905ef1c9e11de206d9301b5be984bf2682b4efb3/librt-0.7.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f9da128d0edf990cf0d2ca011b02cd6f639e79286774bd5b0351245cbb5a6e51", size = 183277, upload-time = "2025-12-06T19:03:16.446Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/bf/9c97bf2f8338ba1914de233ea312bba2bbd7c59f43f807b3e119796bab18/librt-0.7.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e19acfde38cb532a560b98f473adc741c941b7a9bc90f7294bc273d08becb58b", size = 179045, upload-time = "2025-12-06T19:03:17.838Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/b1/ceea067f489e904cb4ddcca3c9b06ba20229bc3fa7458711e24a5811f162/librt-0.7.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:7b4f57f7a0c65821c5441d98c47ff7c01d359b1e12328219709bdd97fdd37f90", size = 173521, upload-time = "2025-12-06T19:03:19.17Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/41/6cb18f5da9c89ed087417abb0127a445a50ad4eaf1282ba5b52588187f47/librt-0.7.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:256793988bff98040de23c57cf36e1f4c2f2dc3dcd17537cdac031d3b681db71", size = 193592, upload-time = "2025-12-06T19:03:20.637Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/3c/fcef208746584e7c78584b7aedc617130c4a4742cb8273361bbda8b183b5/librt-0.7.3-cp310-cp310-win32.whl", hash = "sha256:fcb72249ac4ea81a7baefcbff74df7029c3cb1cf01a711113fa052d563639c9c", size = 47201, upload-time = "2025-12-06T19:03:21.764Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/bf/d8a6c35d1b2b789a4df9b3ddb1c8f535ea373fde2089698965a8f0d62138/librt-0.7.3-cp310-cp310-win_amd64.whl", hash = "sha256:4887c29cadbdc50640179e3861c276325ff2986791e6044f73136e6e798ff806", size = 54371, upload-time = "2025-12-06T19:03:23.231Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/e6/f6391f5c6f158d31ed9af6bd1b1bcd3ffafdea1d816bc4219d0d90175a7f/librt-0.7.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:687403cced6a29590e6be6964463835315905221d797bc5c934a98750fe1a9af", size = 54711, upload-time = "2025-12-06T19:03:24.6Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/1b/53c208188c178987c081560a0fcf36f5ca500d5e21769596c845ef2f40d4/librt-0.7.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:24d70810f6e2ea853ff79338001533716b373cc0f63e2a0be5bc96129edb5fb5", size = 56664, upload-time = "2025-12-06T19:03:25.969Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/5c/d9da832b9a1e5f8366e8a044ec80217945385b26cb89fd6f94bfdc7d80b0/librt-0.7.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bf8c7735fbfc0754111f00edda35cf9e98a8d478de6c47b04eaa9cef4300eaa7", size = 161701, upload-time = "2025-12-06T19:03:27.035Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/aa/1e0a7aba15e78529dd21f233076b876ee58c8b8711b1793315bdd3b263b0/librt-0.7.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32d43610dff472eab939f4d7fbdd240d1667794192690433672ae22d7af8445", size = 171040, upload-time = "2025-12-06T19:03:28.482Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/69/46/3cfa325c1c2bc25775ec6ec1718cfbec9cff4ac767d37d2d3a2d1cc6f02c/librt-0.7.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:adeaa886d607fb02563c1f625cf2ee58778a2567c0c109378da8f17ec3076ad7", size = 184720, upload-time = "2025-12-06T19:03:29.599Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/bb/e4553433d7ac47f4c75d0a7e59b13aee0e08e88ceadbee356527a9629b0a/librt-0.7.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:572a24fc5958c61431da456a0ef1eeea6b4989d81eeb18b8e5f1f3077592200b", size = 180731, upload-time = "2025-12-06T19:03:31.201Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/89/51cd73006232981a3106d4081fbaa584ac4e27b49bc02266468d3919db03/librt-0.7.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6488e69d408b492e08bfb68f20c4a899a354b4386a446ecd490baff8d0862720", size = 174565, upload-time = "2025-12-06T19:03:32.818Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/54/0578a78b587e5aa22486af34239a052c6366835b55fc307bc64380229e3f/librt-0.7.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ed028fc3d41adda916320712838aec289956c89b4f0a361ceadf83a53b4c047a", size = 195247, upload-time = "2025-12-06T19:03:34.434Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/0a/ee747cd999753dd9447e50b98fc36ee433b6c841a42dbf6d47b64b32a56e/librt-0.7.3-cp311-cp311-win32.whl", hash = "sha256:2cf9d73499486ce39eebbff5f42452518cc1f88d8b7ea4a711ab32962b176ee2", size = 47514, upload-time = "2025-12-06T19:03:35.959Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/af/8b13845178dec488e752878f8e290f8f89e7e34ae1528b70277aa1a6dd1e/librt-0.7.3-cp311-cp311-win_amd64.whl", hash = "sha256:35f1609e3484a649bb80431310ddbec81114cd86648f1d9482bc72a3b86ded2e", size = 54695, upload-time = "2025-12-06T19:03:36.956Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/7a/ae59578501b1a25850266778f59279f4f3e726acc5c44255bfcb07b4bc57/librt-0.7.3-cp311-cp311-win_arm64.whl", hash = "sha256:550fdbfbf5bba6a2960b27376ca76d6aaa2bd4b1a06c4255edd8520c306fcfc0", size = 48142, upload-time = "2025-12-06T19:03:38.263Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/90/ed8595fa4e35b6020317b5ea8d226a782dcbac7a997c19ae89fb07a41c66/librt-0.7.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0fa9ac2e49a6bee56e47573a6786cb635e128a7b12a0dc7851090037c0d397a3", size = 55687, upload-time = "2025-12-06T19:03:39.245Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/f6/6a20702a07b41006cb001a759440cb6b5362530920978f64a2b2ae2bf729/librt-0.7.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e980cf1ed1a2420a6424e2ed884629cdead291686f1048810a817de07b5eb18", size = 57127, upload-time = "2025-12-06T19:03:40.3Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/f3/b0c4703d5ffe9359b67bb2ccb86c42d4e930a363cfc72262ac3ba53cff3e/librt-0.7.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e094e445c37c57e9ec612847812c301840239d34ccc5d153a982fa9814478c60", size = 165336, upload-time = "2025-12-06T19:03:41.369Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/69/3ba05b73ab29ccbe003856232cea4049769be5942d799e628d1470ed1694/librt-0.7.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aca73d70c3f553552ba9133d4a09e767dcfeee352d8d8d3eb3f77e38a3beb3ed", size = 174237, upload-time = "2025-12-06T19:03:42.44Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/ad/d7c2671e7bf6c285ef408aa435e9cd3fdc06fd994601e1f2b242df12034f/librt-0.7.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c634a0a6db395fdaba0361aa78395597ee72c3aad651b9a307a3a7eaf5efd67e", size = 189017, upload-time = "2025-12-06T19:03:44.01Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/94/d13f57193148004592b618555f296b41d2d79b1dc814ff8b3273a0bf1546/librt-0.7.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a59a69deeb458c858b8fea6acf9e2acd5d755d76cd81a655256bc65c20dfff5b", size = 183983, upload-time = "2025-12-06T19:03:45.834Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/10/b612a9944ebd39fa143c7e2e2d33f2cb790205e025ddd903fb509a3a3bb3/librt-0.7.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d91e60ac44bbe3a77a67af4a4c13114cbe9f6d540337ce22f2c9eaf7454ca71f", size = 177602, upload-time = "2025-12-06T19:03:46.944Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/48/77bc05c4cc232efae6c5592c0095034390992edbd5bae8d6cf1263bb7157/librt-0.7.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:703456146dc2bf430f7832fd1341adac5c893ec3c1430194fdcefba00012555c", size = 199282, upload-time = "2025-12-06T19:03:48.069Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/aa/05916ccd864227db1ffec2a303ae34f385c6b22d4e7ce9f07054dbcf083c/librt-0.7.3-cp312-cp312-win32.whl", hash = "sha256:b7c1239b64b70be7759554ad1a86288220bbb04d68518b527783c4ad3fb4f80b", size = 47879, upload-time = "2025-12-06T19:03:49.289Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/92/7f41c42d31ea818b3c4b9cc1562e9714bac3c676dd18f6d5dd3d0f2aa179/librt-0.7.3-cp312-cp312-win_amd64.whl", hash = "sha256:ef59c938f72bdbc6ab52dc50f81d0637fde0f194b02d636987cea2ab30f8f55a", size = 54972, upload-time = "2025-12-06T19:03:50.335Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/dc/53582bbfb422311afcbc92adb75711f04e989cec052f08ec0152fbc36c9c/librt-0.7.3-cp312-cp312-win_arm64.whl", hash = "sha256:ff21c554304e8226bf80c3a7754be27c6c3549a9fec563a03c06ee8f494da8fc", size = 48338, upload-time = "2025-12-06T19:03:51.431Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/7d/e0ce1837dfb452427db556e6d4c5301ba3b22fe8de318379fbd0593759b9/librt-0.7.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56f2a47beda8409061bc1c865bef2d4bd9ff9255219402c0817e68ab5ad89aed", size = 55742, upload-time = "2025-12-06T19:03:52.459Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/c0/3564262301e507e1d5cf31c7d84cb12addf0d35e05ba53312494a2eba9a4/librt-0.7.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:14569ac5dd38cfccf0a14597a88038fb16811a6fede25c67b79c6d50fc2c8fdc", size = 57163, upload-time = "2025-12-06T19:03:53.516Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/ac/245e72b7e443d24a562f6047563c7f59833384053073ef9410476f68505b/librt-0.7.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6038ccbd5968325a5d6fd393cf6e00b622a8de545f0994b89dd0f748dcf3e19e", size = 165840, upload-time = "2025-12-06T19:03:54.918Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/af/587e4491f40adba066ba39a450c66bad794c8d92094f936a201bfc7c2b5f/librt-0.7.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d39079379a9a28e74f4d57dc6357fa310a1977b51ff12239d7271ec7e71d67f5", size = 174827, upload-time = "2025-12-06T19:03:56.082Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/21/5b8c60ea208bc83dd00421022a3874330685d7e856404128dc3728d5d1af/librt-0.7.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8837d5a52a2d7aa9f4c3220a8484013aed1d8ad75240d9a75ede63709ef89055", size = 189612, upload-time = "2025-12-06T19:03:57.507Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/2f/8b819169ef696421fb81cd04c6cdf225f6e96f197366001e9d45180d7e9e/librt-0.7.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:399bbd7bcc1633c3e356ae274a1deb8781c7bf84d9c7962cc1ae0c6e87837292", size = 184584, upload-time = "2025-12-06T19:03:58.686Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/fc/af9d225a9395b77bd7678362cb055d0b8139c2018c37665de110ca388022/librt-0.7.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8d8cf653e798ee4c4e654062b633db36984a1572f68c3aa25e364a0ddfbbb910", size = 178269, upload-time = "2025-12-06T19:03:59.769Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/d8/7b4fa1683b772966749d5683aa3fd605813defffe157833a8fa69cc89207/librt-0.7.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2f03484b54bf4ae80ab2e504a8d99d20d551bfe64a7ec91e218010b467d77093", size = 199852, upload-time = "2025-12-06T19:04:00.901Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/e8/4598413aece46ca38d9260ef6c51534bd5f34b5c21474fcf210ce3a02123/librt-0.7.3-cp313-cp313-win32.whl", hash = "sha256:44b3689b040df57f492e02cd4f0bacd1b42c5400e4b8048160c9d5e866de8abe", size = 47936, upload-time = "2025-12-06T19:04:02.054Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/80/ac0e92d5ef8c6791b3e2c62373863827a279265e0935acdf807901353b0e/librt-0.7.3-cp313-cp313-win_amd64.whl", hash = "sha256:6b407c23f16ccc36614c136251d6b32bf30de7a57f8e782378f1107be008ddb0", size = 54965, upload-time = "2025-12-06T19:04:03.224Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/fd/042f823fcbff25c1449bb4203a29919891ca74141b68d3a5f6612c4ce283/librt-0.7.3-cp313-cp313-win_arm64.whl", hash = "sha256:abfc57cab3c53c4546aee31859ef06753bfc136c9d208129bad23e2eca39155a", size = 48350, upload-time = "2025-12-06T19:04:04.234Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/ae/c6ecc7bb97134a71b5241e8855d39964c0e5f4d96558f0d60593892806d2/librt-0.7.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:120dd21d46ff875e849f1aae19346223cf15656be489242fe884036b23d39e93", size = 55175, upload-time = "2025-12-06T19:04:05.308Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/bc/2cc0cb0ab787b39aa5c7645cd792433c875982bdf12dccca558b89624594/librt-0.7.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1617bea5ab31266e152871208502ee943cb349c224846928a1173c864261375e", size = 56881, upload-time = "2025-12-06T19:04:06.674Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/87/397417a386190b70f5bf26fcedbaa1515f19dce33366e2684c6b7ee83086/librt-0.7.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93b2a1f325fefa1482516ced160c8c7b4b8d53226763fa6c93d151fa25164207", size = 163710, upload-time = "2025-12-06T19:04:08.437Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/37/7338f85b80e8a17525d941211451199845093ca242b32efbf01df8531e72/librt-0.7.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d4801db8354436fd3936531e7f0e4feb411f62433a6b6cb32bb416e20b529f", size = 172471, upload-time = "2025-12-06T19:04:10.124Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/e0/741704edabbfae2c852fedc1b40d9ed5a783c70ed3ed8e4fe98f84b25d13/librt-0.7.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11ad45122bbed42cfc8b0597450660126ef28fd2d9ae1a219bc5af8406f95678", size = 186804, upload-time = "2025-12-06T19:04:11.586Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/d1/0a82129d6ba242f3be9af34815be089f35051bc79619f5c27d2c449ecef6/librt-0.7.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:6b4e7bff1d76dd2b46443078519dc75df1b5e01562345f0bb740cea5266d8218", size = 181817, upload-time = "2025-12-06T19:04:12.802Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/32/704f80bcf9979c68d4357c46f2af788fbf9d5edda9e7de5786ed2255e911/librt-0.7.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:d86f94743a11873317094326456b23f8a5788bad9161fd2f0e52088c33564620", size = 175602, upload-time = "2025-12-06T19:04:14.004Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/6d/4355cfa0fae0c062ba72f541d13db5bc575770125a7ad3d4f46f4109d305/librt-0.7.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:754a0d09997095ad764ccef050dd5bf26cbf457aab9effcba5890dad081d879e", size = 196497, upload-time = "2025-12-06T19:04:15.487Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/eb/ac6d8517d44209e5a712fde46f26d0055e3e8969f24d715f70bd36056230/librt-0.7.3-cp314-cp314-win32.whl", hash = "sha256:fbd7351d43b80d9c64c3cfcb50008f786cc82cba0450e8599fdd64f264320bd3", size = 44678, upload-time = "2025-12-06T19:04:16.688Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/93/238f026d141faf9958da588c761a0812a1a21c98cc54a76f3608454e4e59/librt-0.7.3-cp314-cp314-win_amd64.whl", hash = "sha256:d376a35c6561e81d2590506804b428fc1075fcc6298fc5bb49b771534c0ba010", size = 51689, upload-time = "2025-12-06T19:04:17.726Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/44/43f462ad9dcf9ed7d3172fe2e30d77b980956250bd90e9889a9cca93df2a/librt-0.7.3-cp314-cp314-win_arm64.whl", hash = "sha256:cbdb3f337c88b43c3b49ca377731912c101178be91cb5071aac48faa898e6f8e", size = 44662, upload-time = "2025-12-06T19:04:18.771Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/35/fed6348915f96b7323241de97f26e2af481e95183b34991df12fd5ce31b1/librt-0.7.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9f0e0927efe87cd42ad600628e595a1a0aa1c64f6d0b55f7e6059079a428641a", size = 57347, upload-time = "2025-12-06T19:04:19.812Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/f2/045383ccc83e3fea4fba1b761796584bc26817b6b2efb6b8a6731431d16f/librt-0.7.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:020c6db391268bcc8ce75105cb572df8cb659a43fd347366aaa407c366e5117a", size = 59223, upload-time = "2025-12-06T19:04:20.862Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/3f/c081f8455ab1d7f4a10dbe58463ff97119272ff32494f21839c3b9029c2c/librt-0.7.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7af7785f5edd1f418da09a8cdb9ec84b0213e23d597413e06525340bcce1ea4f", size = 183861, upload-time = "2025-12-06T19:04:21.963Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/f5/73c5093c22c31fbeaebc25168837f05ebfd8bf26ce00855ef97a5308f36f/librt-0.7.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ccadf260bb46a61b9c7e89e2218f6efea9f3eeaaab4e3d1f58571890e54858e", size = 194594, upload-time = "2025-12-06T19:04:23.14Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/b8/d5f17d4afe16612a4a94abfded94c16c5a033f183074fb130dfe56fc1a42/librt-0.7.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9883b2d819ce83f87ba82a746c81d14ada78784db431e57cc9719179847376e", size = 206759, upload-time = "2025-12-06T19:04:24.328Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/2e/021765c1be85ee23ffd5b5b968bb4cba7526a4db2a0fc27dcafbdfc32da7/librt-0.7.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:59cb0470612d21fa1efddfa0dd710756b50d9c7fb6c1236bbf8ef8529331dc70", size = 203210, upload-time = "2025-12-06T19:04:25.544Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/f0/9923656e42da4fd18c594bd08cf6d7e152d4158f8b808e210d967f0dcceb/librt-0.7.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:1fe603877e1865b5fd047a5e40379509a4a60204aa7aa0f72b16f7a41c3f0712", size = 196708, upload-time = "2025-12-06T19:04:26.725Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/0b/0708b886ac760e64d6fbe7e16024e4be3ad1a3629d19489a97e9cf4c3431/librt-0.7.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5460d99ed30f043595bbdc888f542bad2caeb6226b01c33cda3ae444e8f82d42", size = 217212, upload-time = "2025-12-06T19:04:27.892Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/7f/12a73ff17bca4351e73d585dd9ebf46723c4a8622c4af7fe11a2e2d011ff/librt-0.7.3-cp314-cp314t-win32.whl", hash = "sha256:d09f677693328503c9e492e33e9601464297c01f9ebd966ea8fc5308f3069bfd", size = 45586, upload-time = "2025-12-06T19:04:29.116Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/df/8decd032ac9b995e4f5606cde783711a71094128d88d97a52e397daf2c89/librt-0.7.3-cp314-cp314t-win_amd64.whl", hash = "sha256:25711f364c64cab2c910a0247e90b51421e45dbc8910ceeb4eac97a9e132fc6f", size = 53002, upload-time = "2025-12-06T19:04:30.173Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/0c/6605b6199de8178afe7efc77ca1d8e6db00453bc1d3349d27605c0f42104/librt-0.7.3-cp314-cp314t-win_arm64.whl", hash = "sha256:a9f9b661f82693eb56beb0605156c7fca57f535704ab91837405913417d6990b", size = 45647, upload-time = "2025-12-06T19:04:31.302Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markdown"
|
||||
version = "3.9"
|
||||
@@ -65,6 +138,70 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/65/f2/e97aaba6d0d78c5871771bf9dd71d4eb8dac15df9109cf452748d2207412/maturin-1.9.6-py3-none-win_arm64.whl", hash = "sha256:ac02a30083553d2a781c10cd6f5480119bf6692fd177e743267406cad2ad198c", size = 6857006, upload-time = "2025-10-07T12:45:06.813Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mypy"
|
||||
version = "1.19.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "librt" },
|
||||
{ name = "mypy-extensions" },
|
||||
{ name = "pathspec" },
|
||||
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f9/b5/b58cdc25fadd424552804bf410855d52324183112aa004f0732c5f6324cf/mypy-1.19.0.tar.gz", hash = "sha256:f6b874ca77f733222641e5c46e4711648c4037ea13646fd0cdc814c2eaec2528", size = 3579025, upload-time = "2025-11-28T15:49:01.26Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/98/8f/55fb488c2b7dabd76e3f30c10f7ab0f6190c1fcbc3e97b1e588ec625bbe2/mypy-1.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6148ede033982a8c5ca1143de34c71836a09f105068aaa8b7d5edab2b053e6c8", size = 13093239, upload-time = "2025-11-28T15:45:11.342Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/1b/278beea978456c56b3262266274f335c3ba5ff2c8108b3b31bec1ffa4c1d/mypy-1.19.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a9ac09e52bb0f7fb912f5d2a783345c72441a08ef56ce3e17c1752af36340a39", size = 12156128, upload-time = "2025-11-28T15:46:02.566Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/f8/e06f951902e136ff74fd7a4dc4ef9d884faeb2f8eb9c49461235714f079f/mypy-1.19.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11f7254c15ab3f8ed68f8e8f5cbe88757848df793e31c36aaa4d4f9783fd08ab", size = 12753508, upload-time = "2025-11-28T15:44:47.538Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/5a/d035c534ad86e09cee274d53cf0fd769c0b29ca6ed5b32e205be3c06878c/mypy-1.19.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318ba74f75899b0e78b847d8c50821e4c9637c79d9a59680fc1259f29338cb3e", size = 13507553, upload-time = "2025-11-28T15:44:39.26Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/17/c4a5498e00071ef29e483a01558b285d086825b61cf1fb2629fbdd019d94/mypy-1.19.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cf7d84f497f78b682edd407f14a7b6e1a2212b433eedb054e2081380b7395aa3", size = 13792898, upload-time = "2025-11-28T15:44:31.102Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/f6/bb542422b3ee4399ae1cdc463300d2d91515ab834c6233f2fd1d52fa21e0/mypy-1.19.0-cp310-cp310-win_amd64.whl", hash = "sha256:c3385246593ac2b97f155a0e9639be906e73534630f663747c71908dfbf26134", size = 10048835, upload-time = "2025-11-28T15:48:15.744Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/d2/010fb171ae5ac4a01cc34fbacd7544531e5ace95c35ca166dd8fd1b901d0/mypy-1.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a31e4c28e8ddb042c84c5e977e28a21195d086aaffaf08b016b78e19c9ef8106", size = 13010563, upload-time = "2025-11-28T15:48:23.975Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/6b/63f095c9f1ce584fdeb595d663d49e0980c735a1d2004720ccec252c5d47/mypy-1.19.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34ec1ac66d31644f194b7c163d7f8b8434f1b49719d403a5d26c87fff7e913f7", size = 12077037, upload-time = "2025-11-28T15:47:51.582Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/83/6cb93d289038d809023ec20eb0b48bbb1d80af40511fa077da78af6ff7c7/mypy-1.19.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cb64b0ba5980466a0f3f9990d1c582bcab8db12e29815ecb57f1408d99b4bff7", size = 12680255, upload-time = "2025-11-28T15:46:57.628Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/db/d217815705987d2cbace2edd9100926196d6f85bcb9b5af05058d6e3c8ad/mypy-1.19.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:120cffe120cca5c23c03c77f84abc0c14c5d2e03736f6c312480020082f1994b", size = 13421472, upload-time = "2025-11-28T15:47:59.655Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/51/d2beaca7c497944b07594f3f8aad8d2f0e8fc53677059848ae5d6f4d193e/mypy-1.19.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7a500ab5c444268a70565e374fc803972bfd1f09545b13418a5174e29883dab7", size = 13651823, upload-time = "2025-11-28T15:45:29.318Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/d1/7883dcf7644db3b69490f37b51029e0870aac4a7ad34d09ceae709a3df44/mypy-1.19.0-cp311-cp311-win_amd64.whl", hash = "sha256:c14a98bc63fd867530e8ec82f217dae29d0550c86e70debc9667fff1ec83284e", size = 10049077, upload-time = "2025-11-28T15:45:39.818Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/7e/1afa8fb188b876abeaa14460dc4983f909aaacaa4bf5718c00b2c7e0b3d5/mypy-1.19.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0fb3115cb8fa7c5f887c8a8d81ccdcb94cff334684980d847e5a62e926910e1d", size = 13207728, upload-time = "2025-11-28T15:46:26.463Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/13/f103d04962bcbefb1644f5ccb235998b32c337d6c13145ea390b9da47f3e/mypy-1.19.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3e19e3b897562276bb331074d64c076dbdd3e79213f36eed4e592272dabd760", size = 12202945, upload-time = "2025-11-28T15:48:49.143Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/93/a86a5608f74a22284a8ccea8592f6e270b61f95b8588951110ad797c2ddd/mypy-1.19.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b9d491295825182fba01b6ffe2c6fe4e5a49dbf4e2bb4d1217b6ced3b4797bc6", size = 12718673, upload-time = "2025-11-28T15:47:37.193Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/58/cf08fff9ced0423b858f2a7495001fda28dc058136818ee9dffc31534ea9/mypy-1.19.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6016c52ab209919b46169651b362068f632efcd5eb8ef9d1735f6f86da7853b2", size = 13608336, upload-time = "2025-11-28T15:48:32.625Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/ed/9c509105c5a6d4b73bb08733102a3ea62c25bc02c51bca85e3134bf912d3/mypy-1.19.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f188dcf16483b3e59f9278c4ed939ec0254aa8a60e8fc100648d9ab5ee95a431", size = 13833174, upload-time = "2025-11-28T15:45:48.091Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/71/01939b66e35c6f8cb3e6fdf0b657f0fd24de2f8ba5e523625c8e72328208/mypy-1.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:0e3c3d1e1d62e678c339e7ade72746a9e0325de42cd2cccc51616c7b2ed1a018", size = 10112208, upload-time = "2025-11-28T15:46:41.702Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/0d/a1357e6bb49e37ce26fcf7e3cc55679ce9f4ebee0cd8b6ee3a0e301a9210/mypy-1.19.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7686ed65dbabd24d20066f3115018d2dce030d8fa9db01aa9f0a59b6813e9f9e", size = 13191993, upload-time = "2025-11-28T15:47:22.336Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/75/8e5d492a879ec4490e6ba664b5154e48c46c85b5ac9785792a5ec6a4d58f/mypy-1.19.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fd4a985b2e32f23bead72e2fb4bbe5d6aceee176be471243bd831d5b2644672d", size = 12174411, upload-time = "2025-11-28T15:44:55.492Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/31/ad5dcee9bfe226e8eaba777e9d9d251c292650130f0450a280aec3485370/mypy-1.19.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fc51a5b864f73a3a182584b1ac75c404396a17eced54341629d8bdcb644a5bba", size = 12727751, upload-time = "2025-11-28T15:44:14.169Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/06/b6b8994ce07405f6039701f4b66e9d23f499d0b41c6dd46ec28f96d57ec3/mypy-1.19.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:37af5166f9475872034b56c5efdcf65ee25394e9e1d172907b84577120714364", size = 13593323, upload-time = "2025-11-28T15:46:34.699Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/b1/126e274484cccdf099a8e328d4fda1c7bdb98a5e888fa6010b00e1bbf330/mypy-1.19.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:510c014b722308c9bd377993bcbf9a07d7e0692e5fa8fc70e639c1eb19fc6bee", size = 13818032, upload-time = "2025-11-28T15:46:18.286Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/56/53a8f70f562dfc466c766469133a8a4909f6c0012d83993143f2a9d48d2d/mypy-1.19.0-cp313-cp313-win_amd64.whl", hash = "sha256:cabbee74f29aa9cd3b444ec2f1e4fa5a9d0d746ce7567a6a609e224429781f53", size = 10120644, upload-time = "2025-11-28T15:47:43.99Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/f4/7751f32f56916f7f8c229fe902cbdba3e4dd3f3ea9e8b872be97e7fc546d/mypy-1.19.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f2e36bed3c6d9b5f35d28b63ca4b727cb0228e480826ffc8953d1892ddc8999d", size = 13185236, upload-time = "2025-11-28T15:45:20.696Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/31/871a9531f09e78e8d145032355890384f8a5b38c95a2c7732d226b93242e/mypy-1.19.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a18d8abdda14035c5718acb748faec09571432811af129bf0d9e7b2d6699bf18", size = 12213902, upload-time = "2025-11-28T15:46:10.117Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/b8/af221910dd40eeefa2077a59107e611550167b9994693fc5926a0b0f87c0/mypy-1.19.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f75e60aca3723a23511948539b0d7ed514dda194bc3755eae0bfc7a6b4887aa7", size = 12738600, upload-time = "2025-11-28T15:44:22.521Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/9f/c39e89a3e319c1d9c734dedec1183b2cc3aefbab066ec611619002abb932/mypy-1.19.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f44f2ae3c58421ee05fe609160343c25f70e3967f6e32792b5a78006a9d850f", size = 13592639, upload-time = "2025-11-28T15:48:08.55Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/6d/ffaf5f01f5e284d9033de1267e6c1b8f3783f2cf784465378a86122e884b/mypy-1.19.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:63ea6a00e4bd6822adbfc75b02ab3653a17c02c4347f5bb0cf1d5b9df3a05835", size = 13799132, upload-time = "2025-11-28T15:47:06.032Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/b0/c33921e73aaa0106224e5a34822411bea38046188eb781637f5a5b07e269/mypy-1.19.0-cp314-cp314-win_amd64.whl", hash = "sha256:3ad925b14a0bb99821ff6f734553294aa6a3440a8cb082fe1f5b84dfb662afb1", size = 10269832, upload-time = "2025-11-28T15:47:29.392Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/0e/fe228ed5aeab470c6f4eb82481837fadb642a5aa95cc8215fd2214822c10/mypy-1.19.0-py3-none-any.whl", hash = "sha256:0c01c99d626380752e527d5ce8e69ffbba2046eb8a060db0329690849cf9b6f9", size = 2469714, upload-time = "2025-11-28T15:45:33.22Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mypy-extensions"
|
||||
version = "1.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pathspec"
|
||||
version = "0.12.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.19.2"
|
||||
@@ -226,6 +363,33 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-markdown"
|
||||
version = "3.10.0.20251106"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/de/e4/060f0dadd9b551cae77d6407f2bc84b168f918d90650454aff219c1b3ed2/types_markdown-3.10.0.20251106.tar.gz", hash = "sha256:12836f7fcbd7221db8baeb0d3a2f820b95050d0824bfa9665c67b4d144a1afa1", size = 19486, upload-time = "2025-11-06T03:06:44.317Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/92/58/f666ca9391f2a8bd33bb0b0797cde6ac3e764866708d5f8aec6fab215320/types_markdown-3.10.0.20251106-py3-none-any.whl", hash = "sha256:2c39512a573899b59efae07e247ba088a75b70e3415e81277692718f430afd7e", size = 25862, upload-time = "2025-11-06T03:06:43.082Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-pyyaml"
|
||||
version = "6.0.12.20250915"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7e/69/3c51b36d04da19b92f9e815be12753125bd8bc247ba0470a982e6979e71c/types_pyyaml-6.0.12.20250915.tar.gz", hash = "sha256:0f8b54a528c303f0e6f7165687dd33fafa81c807fcac23f632b63aa624ced1d3", size = 17522, upload-time = "2025-09-15T03:01:00.728Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/e0/1eed384f02555dde685fff1a1ac805c1c7dcb6dd019c916fe659b1c1f9ec/types_pyyaml-6.0.12.20250915-py3-none-any.whl", hash = "sha256:e7d4d9e064e89a3b3cae120b4990cd370874d2bf12fa5f46c97018dd5d3c9ab6", size = 20338, upload-time = "2025-09-15T03:00:59.218Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.15.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zensical"
|
||||
source = { editable = "." }
|
||||
@@ -242,7 +406,10 @@ dependencies = [
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "maturin" },
|
||||
{ name = "mypy" },
|
||||
{ name = "ruff" },
|
||||
{ name = "types-markdown" },
|
||||
{ name = "types-pyyaml" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
@@ -259,5 +426,8 @@ requires-dist = [
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
{ name = "maturin", specifier = ">=1.9.3" },
|
||||
{ name = "mypy", specifier = ">=1.19.0" },
|
||||
{ name = "ruff", specifier = ">=0.12.8" },
|
||||
{ name = "types-markdown", specifier = ">=3.10.0" },
|
||||
{ name = "types-pyyaml", specifier = ">=6.0.12" },
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user