From 3dace8fbe81548a5503e29b2283314991bbd7d68 Mon Sep 17 00:00:00 2001 From: Stefan Schulz <5165104+Rubyfi@users.noreply.github.com> Date: Tue, 8 Sep 2026 07:11:39 +0000 Subject: [PATCH 01/19] feat: Set up sphinx-mounts --- AGENTS.md | 35 + components/auto_off/doc/index.md | 16 +- components/brightness_controller/doc/index.md | 4 +- components/light_controller/doc/index.md | 35 +- components/main_control_knob/doc/index.md | 4 +- components/power_button/doc/index.md | 4 +- .../power_signal_processing/doc/index.md | 42 +- conf.py | 90 +- doc/component_report.md | 29 + doc/components/index.md | 31 +- index.md | 44 +- ubproject.toml | 62 + uv.lock | 2529 ----------------- 13 files changed, 313 insertions(+), 2612 deletions(-) create mode 100644 doc/component_report.md delete mode 100644 uv.lock diff --git a/AGENTS.md b/AGENTS.md index 2a8d0696..3edd887b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -144,6 +144,41 @@ Edit feature config: `.\build.ps1 -command ".venv\Scripts\poetry run guiconfig"` Check feature values in source code via generated `autoconf.h` header. +## Variant-Dependent Documentation + +Documents must never use Jinja to select variant-dependent content. Two +mechanisms cover every case, and both read the same data: the feature values of +the variant being built, exposed as `var.features.*`, plus `var.build_config.*` +for the shape of the build (`variant` name, `target` of `docs` or `reports`, +and `scope` of `variant` or `component`). + +**Whole documents: `[[source.mounts]]` in `ubproject.toml`**, read by +sphinx-mounts. Each component's design document is mounted at its own real path +under an `if` condition, so it enters the build only when its feature is +enabled. `attach_to` appends it to the empty toctree in +`doc/components/index.md`, which is why that page needs no loop. Adding a +component to the report means adding one mount block, not editing a document. + +**Blocks inside a document: the `{if}` directive of Sphinx-Needs.** Wrap the +content in a four-backtick fence and give the condition as the argument, for +example ```` ````{if} var.features.BLINKING ````. Content behind a false +condition is never parsed, so its needs never enter the traceability data. + +Two differences between the engines are worth knowing. The `{if}` directive is +a real Python expression, so a bare `var.features.BLINKING` is enough, while a +mount condition uses a restricted grammar that needs `== True`. And a mount +condition that cannot be evaluated excludes what it gates, so a typo silently +shrinks the document set rather than failing loudly. + +`conf.py` defaults every boolean in the feature model to `False` before +overlaying the variant's own values, because KConfig omits a promptless boolean +from `autoconf.json` when it evaluates to n. Without that, a condition naming +such a feature would fail to evaluate for exactly the variants where it is off. + +The Jinja `source-read` hook in `conf.py` still runs, but only for the source +listings that spl-core generates under `__source_docs`, which wrap their code +blocks in `{% raw %}`. Do not reintroduce Jinja into a hand-written document. + ## Project-Specific Conventions 1. **No direct CMake invocation**: Always use `build.ps1` wrapper (handles variant selection, environment, Poetry, etc.) diff --git a/components/auto_off/doc/index.md b/components/auto_off/doc/index.md index 832a49e1..d7451528 100644 --- a/components/auto_off/doc/index.md +++ b/components/auto_off/doc/index.md @@ -1,4 +1,6 @@ -# Software Detailed Design +# Auto Off + +**Software Detailed Design** ```{toctree} :maxdepth: 2 @@ -30,16 +32,14 @@ The Auto Off Controller monitors three types of user input to determine system a Any of these inputs will reset the inactivity timer. ``` -{% if config.AUTO_OFF %} - +````{if} var.features.AUTO_OFF ```{spec} Configurable Timeout Period :id: SWDD_AO-102 :refines: SWARCH_001 The auto off timeout period is configurable through CONFIG_AUTO_OFF_PERIOD_SECONDS, with a valid range of 5 to 7200 seconds (5 seconds to 2 hours). ``` - -{% endif %} +```` ```{spec} Timer Countdown Behavior :id: SWDD_AO-103 @@ -92,16 +92,14 @@ The Auto Off Controller shall use the RTE interface `RteIsKeyPressed()` to monit Any activity detected from these monitored keys will reset the inactivity timer. ``` -{% if config.AUTO_OFF %} - +````{if} var.features.AUTO_OFF ```{spec} Auto Off State Output :id: SWDD_AO-205 :refines: SWARCH_001 The Auto Off Controller shall use the RTE interface `RteSetAutoOffState()` to communicate the current auto off state to other system components. The state is set to FALSE when the system is active and TRUE when the timeout period has elapsed. ``` - -{% endif %} +```` ## Timing Behavior diff --git a/components/brightness_controller/doc/index.md b/components/brightness_controller/doc/index.md index 36dd1db8..1bb44de8 100644 --- a/components/brightness_controller/doc/index.md +++ b/components/brightness_controller/doc/index.md @@ -1,4 +1,6 @@ -# Software Detailed Design +# Brightness Controller + +**Software Detailed Design** ```{toctree} :maxdepth: 2 diff --git a/components/light_controller/doc/index.md b/components/light_controller/doc/index.md index eea06dc0..19d157d7 100644 --- a/components/light_controller/doc/index.md +++ b/components/light_controller/doc/index.md @@ -1,4 +1,6 @@ -# Software Detailed Design +# Light Controller + +**Software Detailed Design** ```{toctree} :maxdepth: 2 @@ -18,16 +20,14 @@ The Light Controller is responsible for managing the behavior of the LED based o The light can be in one of two states: ON or OFF. The state transitions are triggered by changes in the system's power state. ``` -{% if config.BLINKING %} - +````{if} var.features.BLINKING ```{spec} Blinking Behavior :id: SWDD_LC-101 :refines: SWARCH_001 When the light is ON, it may exhibit a blinking behavior. The blinking rate is configurable and is determined based on an external input (main knob value). ``` - -{% endif %} +```` ```{spec} Color Management :id: SWDD_LC-102 @@ -67,27 +67,23 @@ The Light Controller uses the RTE interface `RteGetPowerState()` to get the curr The Light Controller uses the RTE interface `RteSetLightValue()` to set the light color. ``` -{% if config.BLINKING %} - +````{if} var.features.BLINKING ```{spec} Main Knob Input :id: SWDD_LC-203 :refines: SWARCH_001 The Light Controller uses the RTE interface `RteGetMainKnobValue()` to get the main knob value for controlling the blinking rate. ``` +```` -{% endif %} - -{% if config.BRIGHTNESS_ADJUSTMENT_ENABLED %} - +````{if} var.features.BRIGHTNESS_ADJUSTMENT_ENABLED ```{spec} Brightness Adjustment :id: SWDD_LC-204 :refines: SWARCH_001 The Light Controller uses the RTE interface `RteGetBrightnessValue()` to get the required brightness value for the light. ``` - -{% endif %} +```` ## Internal Behavior @@ -98,16 +94,25 @@ The Light Controller uses the RTE interface `RteGetBrightnessValue()` to get the The Light Controller is implemented as a state machine. The state machine is shown below. ``` +````{if} var.features.BLINKING ```{mermaid} stateDiagram-v2 [*] --> LIGHT_OFF: Initial State LIGHT_OFF --> LIGHT_ON : Power State != OFF LIGHT_ON --> LIGHT_OFF : Power State == OFF -{% if config.BLINKING %} LIGHT_ON --> BlinkON : Blink Counter >= Blink Period BlinkON --> BlinkOFF : Blink State == TRUE BlinkOFF --> BlinkON : Blink State == FALSE BlinkON --> LIGHT_ON : Reset Blink Counter BlinkOFF --> LIGHT_ON : Reset Blink Counter -{% endif %} ``` +```` + +````{if} not var.features.BLINKING +```{mermaid} +stateDiagram-v2 + [*] --> LIGHT_OFF: Initial State + LIGHT_OFF --> LIGHT_ON : Power State != OFF + LIGHT_ON --> LIGHT_OFF : Power State == OFF +``` +```` diff --git a/components/main_control_knob/doc/index.md b/components/main_control_knob/doc/index.md index 40b2b290..d35e2cd2 100644 --- a/components/main_control_knob/doc/index.md +++ b/components/main_control_knob/doc/index.md @@ -1,4 +1,6 @@ -# Software Detailed Design +# Main Control Knob + +**Software Detailed Design** ```{toctree} :maxdepth: 2 diff --git a/components/power_button/doc/index.md b/components/power_button/doc/index.md index 2359cc84..d0d52f4c 100644 --- a/components/power_button/doc/index.md +++ b/components/power_button/doc/index.md @@ -1,4 +1,6 @@ -# Software Detailed Design +# Power Button + +**Software Detailed Design** ```{toctree} :maxdepth: 2 diff --git a/components/power_signal_processing/doc/index.md b/components/power_signal_processing/doc/index.md index ccf1b8eb..1063860f 100644 --- a/components/power_signal_processing/doc/index.md +++ b/components/power_signal_processing/doc/index.md @@ -1,4 +1,6 @@ -# Software Detailed Design +# Power Signal Processing + +**Software Detailed Design** This module is responsible for processing power signals based on key presses. @@ -43,8 +45,7 @@ If the retrieved power state is POWER_STATE_OFF, the function shall set the powe If the retrieved power state is not POWER_STATE_OFF, the function shall set the power state to POWER_STATE_OFF. ``` -{% if config.AUTO_OFF %} - +````{if} var.features.AUTO_OFF ```{spec} Auto off event handling :id: SWDD_PSP-004 :refines: SWARCH_001 @@ -60,11 +61,35 @@ When no power key is pressed and the auto off state is TRUE, the function shall When no power key is pressed and the auto off state is FALSE, the function shall take no action regarding power state changes. ``` - -{% endif %} +```` ## Function Flow +````{if} var.features.AUTO_OFF +```{mermaid} +graph TD + Start[Start] + KeyCheck{Is 'P' key pressed?} + GetState{Get current power state} + IsOff{Is state OFF?} + TurnOn[Set state to ON] + TurnOff[Set state to OFF] + AutoOffCheck{Is auto off state TRUE?} + AutoOffPowerDown[Set state to OFF] + End[End] + + Start --> KeyCheck + KeyCheck -->|Yes| GetState + KeyCheck -->|No| AutoOffCheck + GetState --> IsOff + IsOff -->|Yes| TurnOn --> End + IsOff -->|No| TurnOff --> End + AutoOffCheck -->|Yes| AutoOffPowerDown --> End + AutoOffCheck -->|No| End +``` +```` + +````{if} not var.features.AUTO_OFF ```{mermaid} graph TD Start[Start] @@ -73,16 +98,13 @@ graph TD IsOff{Is state OFF?} TurnOn[Set state to ON] TurnOff[Set state to OFF] - {% if config.AUTO_OFF %}AutoOffCheck{Is auto off state TRUE?} - AutoOffPowerDown[Set state to OFF]{% endif %} End[End] Start --> KeyCheck KeyCheck -->|Yes| GetState - KeyCheck -->|No| {% if config.AUTO_OFF %}AutoOffCheck{% else %}End{% endif %} + KeyCheck -->|No| End GetState --> IsOff IsOff -->|Yes| TurnOn --> End IsOff -->|No| TurnOff --> End - {% if config.AUTO_OFF %}AutoOffCheck -->|Yes| AutoOffPowerDown --> End - AutoOffCheck -->|No| End{% endif %} ``` +```` diff --git a/conf.py b/conf.py index 5c304dd6..8d557b1c 100644 --- a/conf.py +++ b/conf.py @@ -1,8 +1,11 @@ # -*- coding: utf-8 -*- """Configuration""" import datetime +import json import os +from pathlib import Path + from importlib.resources import files from spl_core.report_generation.spl_sphinx import SplSphinx from spl_core.report_generation.spl_html_settings import html_theme, html_show_sourcelink, html_theme_options, html_sidebars, html_last_updated_fmt # noqa: F401 @@ -77,24 +80,97 @@ # Always write the merged needs.json (all needs, after import/resolution) to the build output dir. needs_build_json = True -# Expose KConfig feature values (e.g. CUSTOMER) as `var.features.*` for the {if} directive -if "AUTOCONF_JSON_FILE" in os.environ: - needs_variant_data_file = os.environ["AUTOCONF_JSON_FILE"] +# Expose KConfig feature values (e.g. CUSTOMER) as `var.features.*`. +# Used by the {if} directive in the documents and by the `if` conditions of +# [[source.mounts]] in ubproject.toml. CMake exports AUTOCONF_JSON_FILE for the +# variant it is building; the mirrored copy written by CMakeLists.txt is the +# fallback so a bare `sphinx-build` (or the IDE) resolves the same variant +# instead of silently dropping every variant-gated document. +_autoconf_json_file = os.environ.get("AUTOCONF_JSON_FILE", "build/autoconf.json") +if os.path.exists(_autoconf_json_file): + needs_variant_data_file = _autoconf_json_file + + +def _feature_defaults() -> dict: + """Every boolean the feature model declares, defaulted to False. + + KConfig writes a boolean into autoconf.json only when it has a prompt or + evaluates to y. A helper symbol such as BRIGHTNESS_ADJUSTMENT_ENABLED is + therefore simply absent from the variants where it is off, and a document + or a mount condition naming it would fail to evaluate for exactly those + variants. Reading the model once and defaulting its booleans keeps the + variant data complete without anyone having to maintain a list by hand. + """ + try: + import kconfiglib + import spl_core + + os.environ.setdefault("SPL_CORE_DIR", str(Path(spl_core.__file__).parent)) + os.environ.setdefault("srctree", str(Path(__file__).parent)) + model = kconfiglib.Kconfig("KConfig", warn=False) + except Exception: # noqa: BLE001 - the feature model is optional context here + return {} + return { + name: False + for name, symbol in model.syms.items() + if name and symbol.orig_type == kconfiglib.BOOL + } + + +_features = _feature_defaults() +if os.path.exists(_autoconf_json_file): + with open(_autoconf_json_file) as _handle: + _features.update(json.load(_handle)["features"]) -# Provide all config values to jinja html_context = SplSphinx.get_default_html_context() -include_patterns.extend(html_context["build_config"].get("include_patterns", [])) build_config = html_context["build_config"].copy() build_config.pop("components_info", None) +# Two shapes of build use this configuration: the variant-wide report, and the +# per-component report that spl-core builds for a single component. Normalize +# the difference into plain keys so a document or a mount condition can test it +# without having to know which keys CMake happens to write. CMake writes +# `target` only for the variant-wide builds; for a per-component build it is the +# name of the directory holding the configuration file CMake pointed us at. +_build_config_file = os.environ.get("SPHINX_BUILD_CONFIGURATION_FILE", "") +build_config.setdefault( + "target", "reports" if Path(_build_config_file).parent.name == "reports" else "docs" +) +build_config["scope"] = "component" if build_config.get("component_info") else "variant" + +_extra_include_patterns = html_context["build_config"].get("include_patterns", []) +if build_config["scope"] == "variant": + # Component design documents are mounted per feature by sphinx-mounts, see + # [[source.mounts]] in ubproject.toml. Reading them as ordinary sources as + # well would parse every need in them a second time under a second docname. + _extra_include_patterns = [ + pattern + for pattern in _extra_include_patterns + if not (pattern.startswith("components/") and pattern.endswith("/doc/**")) + ] + root_doc = "index" + exclude_patterns.append("doc/component_report.md") +else: + root_doc = "doc/component_report" + exclude_patterns.append("index.md") +include_patterns.extend(_extra_include_patterns) + needs_variant_data = { + "features": _features, "build_config": build_config, } + def rstjinja(app, docname, source): - """ - Render our pages as a jinja template for fancy templating goodness. + """Render every source file as a Jinja template before Sphinx parses it. + + No hand-written document in this project uses Jinja any more: variant + dependent content is selected by the {if} directive of Sphinx-Needs inside + a document, and by [[source.mounts]] conditions in ubproject.toml for whole + documents. The hook still has to run, because the source listings that + spl-core generates under `__source_docs` wrap their code blocks in + `{% raw %}` and depend on this pass to strip those markers. """ # Make sure we're outputting HTML if app.builder.format != "html": diff --git a/doc/component_report.md b/doc/component_report.md new file mode 100644 index 00000000..6fe1f6fa --- /dev/null +++ b/doc/component_report.md @@ -0,0 +1,29 @@ +# Software Component Report + +**Variant:** {variant}`build_config.variant`
+**Component:** {variant}`build_config.component_info.long_name` + +This is the root document of the per-component report that spl-core builds for +a single component. The build configuration restricts the source set to that +one component, so the glob below resolves to exactly its design document. + +```{toctree} +:maxdepth: 2 +:glob: + +/components/*/doc/index +``` + +````{if} var.build_config.target == "reports" + +```{toctree} +:caption: Unit test results +:maxdepth: 1 +:glob: + +/build/**/reports/unit_test_spec +/build/**/reports/unit_test_results +/build/**/reports/coverage +``` + +```` diff --git a/doc/components/index.md b/doc/components/index.md index a72d081b..e0ca0d4e 100644 --- a/doc/components/index.md +++ b/doc/components/index.md @@ -1,20 +1,27 @@ # Components -{% for component_info in build_config.components_info %} -{% if component_info.has_docs %} - -## {{ component_info.long_name or component_info.name }} +Only the components that the built variant actually contains appear below. +Each component's design document is mounted by sphinx-mounts under the KConfig +feature that owns it, so the table of contents follows the variant without any +templating in this file. The declarations live in `[[source.mounts]]` in +`ubproject.toml`, and sphinx-mounts appends each mounted entry to the empty +toctree below. ```{toctree} :maxdepth: 2 -/{{ component_info.path }}/doc/index -{% if (build_config.target == 'reports') and component_info.has_reports %} -/{{ component_info.reports_output_dir }}/unit_test_results -/{{ component_info.reports_output_dir }}/doxygen/html/index -/{{ component_info.reports_output_dir }}/coverage -{% endif %} ``` -{% endif %} -{% endfor %} +````{if} var.build_config.target == "reports" + +```{toctree} +:caption: Unit test results +:maxdepth: 1 +:glob: + +/build/**/components/*/reports/unit_test_spec +/build/**/components/*/reports/unit_test_results +/build/**/components/*/reports/coverage +``` + +```` diff --git a/index.md b/index.md index de302af9..dda4fb36 100644 --- a/index.md +++ b/index.md @@ -1,28 +1,6 @@ -{% if build_config.component_info %} - -# Software Component Report - -**Variant:** {{ build_config.variant }}
-**Component:** {{ build_config.component_info.long_name }}
-**Timestamp:** {{ timestamp }} - -```{toctree} -:maxdepth: 2 - -{{ build_config.component_info.path }}/doc/index -{% if build_config.component_info.has_reports %} -{{ build_config.component_info.reports_output_dir }}/unit_test_spec -{{ build_config.component_info.reports_output_dir }}/unit_test_results -{{ build_config.component_info.reports_output_dir }}/doxygen/html/index -{{ build_config.component_info.reports_output_dir }}/coverage -{% endif %} -``` - -{% else %} - # Variant Report -**Variant:** {variant}`build_config.variant`
+**Variant:** {variant}`build_config.variant` **Timestamp:** {{ timestamp }} ```{toctree} @@ -33,9 +11,21 @@ doc/customer_requirements/index doc/software_architecture/index doc/sw_requirements/index doc/components/index -{% if build_config.target == 'reports' %} -{{ build_config.reports_output_dir }}/coverage -{% endif %} ``` -{% endif %} +````{if} var.build_config.target == "reports" + +The glob below matches the variant-wide coverage page for any variant name and +any build type, and does not match a component's own coverage page, because the +directory in front of `reports` is the build type there and the component name +here. + +```{toctree} +:caption: Code Coverage +:maxdepth: 1 +:glob: + +/build/**/[A-Z]*/reports/coverage +``` + +```` diff --git a/ubproject.toml b/ubproject.toml index 5625b187..b71655b3 100644 --- a/ubproject.toml +++ b/ubproject.toml @@ -70,3 +70,65 @@ default = [] name = "fulfills" type = "list[str]" default = [] + +# --------------------------------------------------------------------------- +# Variant-dependent documentation, read by sphinx-mounts. +# +# Each component's design documentation is mounted at its own real path, so the +# docname is exactly what it would be if the file were an ordinary source. What +# the mount adds is the `if` condition: the bundle is in the build only when the +# KConfig feature that owns it is enabled for the variant being built. The +# feature values come from the variant's autoconf.json, see conf.py. +# +# `scope == "variant"` keeps these out of the per-component reports, which build +# a single component and stitch their own table of contents. +# +# This is the file-level half of variant handling. The block-level half is the +# {if} directive of Sphinx-Needs, used inside the component documents. +# +# `path_check = "off"` because these bundles are not self-contained by design: +# a component's design document legitimately reaches its own `src/` headers and +# the compilation database through sphinx-codelinks. +# --------------------------------------------------------------------------- + +[[source.mounts]] +if = 'var.build_config.scope == "variant"' +dir = "components/light_controller/doc" +mount_at = "components/light_controller/doc" +attach_to = "doc/components/index" +path_check = "off" + +[[source.mounts]] +if = 'var.build_config.scope == "variant"' +dir = "components/main_control_knob/doc" +mount_at = "components/main_control_knob/doc" +attach_to = "doc/components/index" +path_check = "off" + +[[source.mounts]] +if = 'var.build_config.scope == "variant"' +dir = "components/power_button/doc" +mount_at = "components/power_button/doc" +attach_to = "doc/components/index" +path_check = "off" + +[[source.mounts]] +if = 'var.build_config.scope == "variant"' +dir = "components/power_signal_processing/doc" +mount_at = "components/power_signal_processing/doc" +attach_to = "doc/components/index" +path_check = "off" + +[[source.mounts]] +if = 'var.build_config.scope == "variant" and var.features.BRIGHTNESS_ADJUSTMENT_ENABLED == True' +dir = "components/brightness_controller/doc" +mount_at = "components/brightness_controller/doc" +attach_to = "doc/components/index" +path_check = "off" + +[[source.mounts]] +if = 'var.build_config.scope == "variant" and var.features.AUTO_OFF == True' +dir = "components/auto_off/doc" +mount_at = "components/auto_off/doc" +attach_to = "doc/components/index" +path_check = "off" diff --git a/uv.lock b/uv.lock deleted file mode 100644 index 3f4ec3ff..00000000 --- a/uv.lock +++ /dev/null @@ -1,2529 +0,0 @@ -version = 1 -revision = 3 -requires-python = ">=3.12, <3.14" -resolution-markers = [ - "python_full_version >= '3.13'", - "python_full_version < '3.13'", -] - -[[package]] -name = "accessible-pygments" -version = "0.0.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pygments" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bc/c1/bbac6a50d02774f91572938964c582fff4270eee73ab822a4aeea4d8b11b/accessible_pygments-0.0.5.tar.gz", hash = "sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872", size = 1377899, upload-time = "2024-05-10T11:23:10.216Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/3f/95338030883d8c8b91223b4e21744b04d11b161a3ef117295d8241f50ab4/accessible_pygments-0.0.5-py3-none-any.whl", hash = "sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7", size = 1395903, upload-time = "2024-05-10T11:23:08.421Z" }, -] - -[[package]] -name = "alabaster" -version = "1.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, -] - -[[package]] -name = "annotated-doc" -version = "0.0.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5a/8e/38aa427ed5402449e226975b649c5dc73ccadfefeb95e6aecb8f8ea4b6b6/annotated_doc-0.0.5.tar.gz", hash = "sha256:c7e58ce09192557605d8bbd92836d7e1d520ac9580096042c0bfd197efacf1bb", size = 10758, upload-time = "2026-07-28T13:50:58.129Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3e/30/e900b21425a860e195f32e37657aa1f7c7f2b1bfb26f03ca209b90933c06/annotated_doc-0.0.5-py3-none-any.whl", hash = "sha256:117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101", size = 5302, upload-time = "2026-07-28T13:50:57.239Z" }, -] - -[[package]] -name = "annotated-types" -version = "0.8.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7", size = 15893, upload-time = "2026-07-23T20:16:13.995Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427, upload-time = "2026-07-23T20:16:12.938Z" }, -] - -[[package]] -name = "arrow" -version = "1.4.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "python-dateutil" }, - { name = "tzdata" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b9/33/032cdc44182491aa708d06a68b62434140d8c50820a087fac7af37703357/arrow-1.4.0.tar.gz", hash = "sha256:ed0cc050e98001b8779e84d461b0098c4ac597e88704a655582b21d116e526d7", size = 152931, upload-time = "2025-10-18T17:46:46.761Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl", hash = "sha256:749f0769958ebdc79c173ff0b0670d59051a535fa26e8eba02953dc19eb43205", size = 68797, upload-time = "2025-10-18T17:46:45.663Z" }, -] - -[[package]] -name = "attrs" -version = "26.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, -] - -[[package]] -name = "babel" -version = "2.18.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, -] - -[[package]] -name = "backports-zstd" -version = "1.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/75/f0/9ba1b05811aa5f5434f69768253129460a5744e1814f359efba39a01ce20/backports_zstd-1.7.0.tar.gz", hash = "sha256:1a967189c1822b6e85a2e550fdfc88a3272c17633ea0a4732dac5911a8034f2b", size = 1003722, upload-time = "2026-08-15T17:26:43.96Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/df/23/240495dec973dcfb34816248956ca8d05b32fb75936c226c1cf497b83b83/backports_zstd-1.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5548a857bb0fcc5449cc3687353547396c6b1ecd4bd882f1cd34fa8d29e70ca", size = 439047, upload-time = "2026-08-15T17:25:38.084Z" }, - { url = "https://files.pythonhosted.org/packages/6d/24/5556959c7d03bfee5ff14d7f07dd9bf8de737c69f81d823a32784ab39c34/backports_zstd-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bab192b934fdf5a03df4752556d9c8af2d058163fdfbafd4a253cdfe25449a6f", size = 367666, upload-time = "2026-08-15T17:25:39.233Z" }, - { url = "https://files.pythonhosted.org/packages/b2/cb/557db98001c4a7202beed19e8bd42603a2315b80fd5def7e21a0b048ec3b/backports_zstd-1.7.0-cp312-cp312-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:8344260bed9842c415a93d9bfe23ea834e5f27758827d56933d8c0d06db507a2", size = 508475, upload-time = "2026-08-15T17:25:40.367Z" }, - { url = "https://files.pythonhosted.org/packages/40/4b/820acbc2c1d1d945aedca0c0d22546a948630ffb186df523098fbd669a95/backports_zstd-1.7.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c55e55e1e9dee312bc5e186386e6aa5207482a6d2242bd7c14709ded254f87f", size = 478240, upload-time = "2026-08-15T17:25:41.806Z" }, - { url = "https://files.pythonhosted.org/packages/f9/76/77fa9b385e79d4c106ce15d66681978f39a844b0eb5db02682687246b716/backports_zstd-1.7.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cf609af3735c7e697ccd13f6b0c88da57c201b6ea63c6afbfe81d6f9b50e298c", size = 583611, upload-time = "2026-08-15T17:25:43.104Z" }, - { url = "https://files.pythonhosted.org/packages/95/a4/fbb7c73336f3279dad36da94382a59755100b656301ea836ebaa42736581/backports_zstd-1.7.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:676a37971f676830d4f90cee8fdf4e438781596fb2f2d1984ac76c9b3eb39a69", size = 642496, upload-time = "2026-08-15T17:25:44.322Z" }, - { url = "https://files.pythonhosted.org/packages/1e/40/121917bd2671bc3f1507c25503c0554f0b52483edcca4e6210e6d22228df/backports_zstd-1.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:470895d0bcddc850766e593d1b26764fb138c2feed149f515a2627ef9587d54c", size = 496195, upload-time = "2026-08-15T17:25:45.503Z" }, - { url = "https://files.pythonhosted.org/packages/de/c7/c6379a0d734bea1c7f14d07c23258108cc92b994654e25cfe3a3e88cd785/backports_zstd-1.7.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:02f2f6649a342d0901ddb35596ddadb7c3bb1cf6bb54d691e5e0285f1fa0674f", size = 570964, upload-time = "2026-08-15T17:25:46.648Z" }, - { url = "https://files.pythonhosted.org/packages/7a/a4/372c3dd3017c3f93cda0acbc282f8073b70efdc1b56d1fdeebe023660725/backports_zstd-1.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:132ba81fad59d44958b7d10da31545e7128c469cfbc2e268d0eaab96daa64175", size = 484276, upload-time = "2026-08-15T17:25:48.129Z" }, - { url = "https://files.pythonhosted.org/packages/f5/50/83fa7bdd5e1d808203b9143848fdf7e15de399b8119a0d4378b2aea9be78/backports_zstd-1.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a3e1c6ce0b232ee6703ed24ee126e8186107f5a4e56edbd21cd1aa5a8c6bfd12", size = 511963, upload-time = "2026-08-15T17:25:49.666Z" }, - { url = "https://files.pythonhosted.org/packages/56/d2/d4ed32c353148acc18f3b665ab24a677b9c49d3640244424c5d6046400c5/backports_zstd-1.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d7a7cb964eb8d1bb5d039970b16fe54802ea47dc935ae96d9874844a126bf8ff", size = 588025, upload-time = "2026-08-15T17:25:51.288Z" }, - { url = "https://files.pythonhosted.org/packages/ae/5a/df8b5b848e8dfdec6edca55f22067ffbafa081d81aec1313e28155c3fea3/backports_zstd-1.7.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:12a9842a2ec2854cbec7f252ab29d44c2b772788a9bbafded743ca4bf73b115f", size = 568223, upload-time = "2026-08-15T17:25:52.633Z" }, - { url = "https://files.pythonhosted.org/packages/43/8c/f970f15e7fdbf8a251f121c91364fa68bbc2dfab4d5eca058427dec63397/backports_zstd-1.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:138154eea8ced84394991bf0e819dba6b690306a178dd528c28eee724b7d4aec", size = 632937, upload-time = "2026-08-15T17:25:54.511Z" }, - { url = "https://files.pythonhosted.org/packages/6a/d0/e36c18c87a74421954502d123ff7027e0a63a7624dffa99ec0f7474deff9/backports_zstd-1.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:468b636ed365627b364c94be1c35a52858e13b5bc1fa3f068bbc71b1af65f3d7", size = 500735, upload-time = "2026-08-15T17:25:56.064Z" }, - { url = "https://files.pythonhosted.org/packages/1c/57/fc72280334d2aa94238c5882052263bd7796c1fa924044353c30d058e0c3/backports_zstd-1.7.0-cp312-cp312-win32.whl", hash = "sha256:f026fe2e89b7ff01ba6ebec6abaff34c6063919151a32afb68714cf139e17c50", size = 292394, upload-time = "2026-08-15T17:25:57.469Z" }, - { url = "https://files.pythonhosted.org/packages/71/89/6cea747bdeef34cd12482b17e604b832fdb0962987132b99496f1a6c3f82/backports_zstd-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:2ea62ba2f1a6e6c9e6dc108921f9ae881969ca72e073162fa488d0de3eb2713f", size = 329876, upload-time = "2026-08-15T17:25:58.798Z" }, - { url = "https://files.pythonhosted.org/packages/64/28/b4a17c07d5a50a45cb04592960d1593cdf3b3728968371f332aa3643b804/backports_zstd-1.7.0-cp312-cp312-win_arm64.whl", hash = "sha256:cefb983345c55ccaa20423a4eb96434730e6d640ffa2db9b60e5bedb0fbef94e", size = 292461, upload-time = "2026-08-15T17:25:59.928Z" }, - { url = "https://files.pythonhosted.org/packages/6f/24/32b3358ae3a4df0ebad85ebbce721818c6d76a836119bee76089d103e951/backports_zstd-1.7.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:a3fbcbf819bee2b06b8666b13742098d0f40663ee34e64a12bc360ec0f5e3d89", size = 400913, upload-time = "2026-08-15T17:26:01.089Z" }, - { url = "https://files.pythonhosted.org/packages/af/f3/39ef7dd75eb1e699e25a19212737a73d3c030a0c9fd1d0ed1572b5f8e493/backports_zstd-1.7.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:efee02f18e04c2e9e6d694c5cf9b7457c4bda3ea96f48b1ee69769e06bb9d89f", size = 454915, upload-time = "2026-08-15T17:26:02.294Z" }, - { url = "https://files.pythonhosted.org/packages/76/e8/8209081e094aa98b2f28bac388619c85b1a44aed813d6b3c54d1da79d19a/backports_zstd-1.7.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:ecc95fa0e91d92951d74468e7789afdf91d9e702f40af2d0fcbf0ded4d0f650a", size = 357992, upload-time = "2026-08-15T17:26:03.552Z" }, - { url = "https://files.pythonhosted.org/packages/b3/65/64025302bae4ba924d613e404c6120bf194b5636786960ece274622a4a3e/backports_zstd-1.7.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:34154d82fc0246738159084d146401073f9ac9cfd755b66bb8853ca06037810c", size = 366686, upload-time = "2026-08-15T17:26:04.812Z" }, - { url = "https://files.pythonhosted.org/packages/4a/b9/c4d24d113d28b774662152c462d38d28109741d6d45c1aea7834371741dc/backports_zstd-1.7.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:44b687b1c0be5cb279693d2682f91ff84c559d679b2ef2fbe501fe4b2db2c4bb", size = 447221, upload-time = "2026-08-15T17:26:05.979Z" }, - { url = "https://files.pythonhosted.org/packages/cb/9f/8db55c7f77aec60879844a879ac026065d8f03aab74080701acc060c4168/backports_zstd-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dcdbd368659f46b570114eeea36b75347716523870d71f6bc5d7801862aefd6e", size = 438571, upload-time = "2026-08-15T17:26:07.421Z" }, - { url = "https://files.pythonhosted.org/packages/cd/f8/72930ae4bb7bf6b9d6c7c31bce7b3e5751c062269a4ee718066e25f1973b/backports_zstd-1.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eda97fa535d4651a4ccdeed4ee7dde3978369046abc8a7456a7117d4271f9333", size = 367041, upload-time = "2026-08-15T17:26:08.537Z" }, - { url = "https://files.pythonhosted.org/packages/17/9b/7289dc191b34279d8f176bf5b181c3b26f8e049d14a2c0a2637650f034e5/backports_zstd-1.7.0-cp313-cp313-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:7e3999b5141d7f85171822d06112f70f7f317d162f0120530dd2c7a28dbf8add", size = 507676, upload-time = "2026-08-15T17:26:09.909Z" }, - { url = "https://files.pythonhosted.org/packages/7c/4d/6dd730b79ab96532e23fe851003545b4cc79e50c5b4c79ffcbe1b724eec4/backports_zstd-1.7.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69367726f4075c2574746f5883b0dc045805c5b02a81fdf8c829c26d33969de3", size = 477744, upload-time = "2026-08-15T17:26:11.038Z" }, - { url = "https://files.pythonhosted.org/packages/e1/53/11687e5019d56ea47893cf2ba59a6b4884a4e2d1496d0e653aed373b973f/backports_zstd-1.7.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:15e97edfd173ade365c01bac7d9d297fa906686015cdbcb5f32a0d410887826b", size = 583215, upload-time = "2026-08-15T17:26:12.379Z" }, - { url = "https://files.pythonhosted.org/packages/aa/c7/5a8c58542469ab31680c403b844770c119a976fd4cf1000fd7d53e7d0f77/backports_zstd-1.7.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:32a94cdcf16b44395cd55086ea38877395ca6bf3362cb507b0eb86db2a45a6a4", size = 644125, upload-time = "2026-08-15T17:26:13.651Z" }, - { url = "https://files.pythonhosted.org/packages/11/35/be5485e65df95b86c4981ad4a577b505cfeec6b700a46a86e2e3175ac718/backports_zstd-1.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3f4887a8a1fd1290017fe5a1d29a7d1dc5c57f9477fbd64f119316a7e3ae769", size = 492714, upload-time = "2026-08-15T17:26:14.838Z" }, - { url = "https://files.pythonhosted.org/packages/96/8b/a0603458ca08e4a56f09ae58588ce3c0453425e753df704d9aeaabb66ae5/backports_zstd-1.7.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e590313ce156f1d8986dff3107e8ed1651d6d106a56b3a95f965ff8d845ba979", size = 567953, upload-time = "2026-08-15T17:26:16.276Z" }, - { url = "https://files.pythonhosted.org/packages/02/87/2296db4c3c578947c35ccd8dcdf7992316d7e1f5f43cc829c062b3ed9319/backports_zstd-1.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:565270b0d6497970fa97a0df59593ae0d225e4176678bbce851d39e5f8aa422b", size = 483564, upload-time = "2026-08-15T17:26:17.493Z" }, - { url = "https://files.pythonhosted.org/packages/c0/d8/f53a79e6bf3cdb7ae08f95220c80bd0d606f3d6c3482995deaf21d024fb9/backports_zstd-1.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:37ef23c6c522fe935726c8fba6344350973c4a23b06d10194d90d0868b09ff7a", size = 511193, upload-time = "2026-08-15T17:26:18.7Z" }, - { url = "https://files.pythonhosted.org/packages/31/ea/d4e2eb159cd5813debd5a34d0644caff5fe7cf2e569bf5b02a82934aeee7/backports_zstd-1.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b3975330159f1efdd1fba76afe1c7b84f66f26e2bf209b32630fb148d647e0d5", size = 587748, upload-time = "2026-08-15T17:26:20.148Z" }, - { url = "https://files.pythonhosted.org/packages/81/d2/b5ec9709660fb1c193508215d9c30e781fac406183faac7c3c36b1c583a9/backports_zstd-1.7.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b40bc8cd0a86cbbe8263a9c3a2bf2e34897483516c6d799725412a19524c32e3", size = 565808, upload-time = "2026-08-15T17:26:21.349Z" }, - { url = "https://files.pythonhosted.org/packages/bd/13/004735cc4536483cbd973a60346a9dbc7bb977b13c28b55a11da14bb0a1e/backports_zstd-1.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f37e12ef10747f76901b1f20ef70d33221e861de177dba5ba08552242c6fd4bd", size = 634520, upload-time = "2026-08-15T17:26:22.944Z" }, - { url = "https://files.pythonhosted.org/packages/a3/28/05b11f7084d1100491cf7c60962aafd900c3dd01b1fc1ce85914476cdae0/backports_zstd-1.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5992143b2a8b71b4d17afed20cce2df50f8718228e31d6e716493b1fe9201712", size = 497098, upload-time = "2026-08-15T17:26:24.181Z" }, - { url = "https://files.pythonhosted.org/packages/f5/a5/bdc98d039ddbd5815fc1dd71912bbfb9f820a46ec12004ead51c8d60ea50/backports_zstd-1.7.0-cp313-cp313-win32.whl", hash = "sha256:31ae30d216ffae9243dfa607bcb995f94a70de5765bb8fae1e35ea1ad6497959", size = 291974, upload-time = "2026-08-15T17:26:25.512Z" }, - { url = "https://files.pythonhosted.org/packages/13/7d/fb0da7351e8b152d5149127594972922829281c316618df37a7e724f2eb9/backports_zstd-1.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:8086b4a7443bb2863f7ef8edb317b715d5f3ccec6c5512619bd23d57661ba1b7", size = 329550, upload-time = "2026-08-15T17:26:26.683Z" }, - { url = "https://files.pythonhosted.org/packages/37/f9/109ac272d650483fbdfa611c0040253a405f640604fbc90acc8076c6d37f/backports_zstd-1.7.0-cp313-cp313-win_arm64.whl", hash = "sha256:7eaceeec75e1dbdce40b81fb0ed1ffdb7ce492d970db7f8aabd6a95ccd6c3dd3", size = 292174, upload-time = "2026-08-15T17:26:27.819Z" }, -] - -[[package]] -name = "beautifulsoup4" -version = "4.15.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "soupsieve" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/43/65/318323f98dbee45d42dff61d8f047181bc6f2268a9068cfad035a46be5af/beautifulsoup4-4.15.0.tar.gz", hash = "sha256:288e3ca7d54b06f2ac191970bc275c1939cb46d450b255bf6718b04aa37ab4f7", size = 632571, upload-time = "2026-06-07T16:44:20.453Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/c6/92fcd42f1ba33e1184263f25bfabf3d27c383410470f169e4b8163bf9c17/beautifulsoup4-4.15.0-py3-none-any.whl", hash = "sha256:d6f88de62e1d4e38ecb1077eb9724cd0eff29d2a08ca16a401e9b9e93f117cf9", size = 109924, upload-time = "2026-06-07T16:44:21.566Z" }, -] - -[[package]] -name = "binaryornot" -version = "0.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/86/72/4755b85101f37707c71526a301c1203e413c715a0016ecb592de3d2dcfff/binaryornot-0.6.0.tar.gz", hash = "sha256:cc8d57cfa71d74ff8c28a7726734d53a851d02fad9e3a5581fb807f989f702f0", size = 478718, upload-time = "2026-03-08T16:26:28.804Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cd/0c/31cfaa6b56fe23488ecb993bc9fc526c0d84d89607decdf2a10776426c2e/binaryornot-0.6.0-py3-none-any.whl", hash = "sha256:900adfd5e1b821255ba7e63139b0396b14c88b9286e74e03b6f51e0200331337", size = 14185, upload-time = "2026-03-08T16:26:27.466Z" }, -] - -[[package]] -name = "brotli" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f7/16/c92ca344d646e71a43b8bb353f0a6490d7f6e06210f8554c8f874e454285/brotli-1.2.0.tar.gz", hash = "sha256:e310f77e41941c13340a95976fe66a8a95b01e783d430eeaf7a2f87e0a57dd0a", size = 7388632, upload-time = "2025-11-05T18:39:42.86Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/11/ee/b0a11ab2315c69bb9b45a2aaed022499c9c24a205c3a49c3513b541a7967/brotli-1.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:35d382625778834a7f3061b15423919aa03e4f5da34ac8e02c074e4b75ab4f84", size = 861543, upload-time = "2025-11-05T18:38:24.183Z" }, - { url = "https://files.pythonhosted.org/packages/e1/2f/29c1459513cd35828e25531ebfcbf3e92a5e49f560b1777a9af7203eb46e/brotli-1.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7a61c06b334bd99bc5ae84f1eeb36bfe01400264b3c352f968c6e30a10f9d08b", size = 444288, upload-time = "2025-11-05T18:38:25.139Z" }, - { url = "https://files.pythonhosted.org/packages/3d/6f/feba03130d5fceadfa3a1bb102cb14650798c848b1df2a808356f939bb16/brotli-1.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:acec55bb7c90f1dfc476126f9711a8e81c9af7fb617409a9ee2953115343f08d", size = 1528071, upload-time = "2025-11-05T18:38:26.081Z" }, - { url = "https://files.pythonhosted.org/packages/2b/38/f3abb554eee089bd15471057ba85f47e53a44a462cfce265d9bf7088eb09/brotli-1.2.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:260d3692396e1895c5034f204f0db022c056f9e2ac841593a4cf9426e2a3faca", size = 1626913, upload-time = "2025-11-05T18:38:27.284Z" }, - { url = "https://files.pythonhosted.org/packages/03/a7/03aa61fbc3c5cbf99b44d158665f9b0dd3d8059be16c460208d9e385c837/brotli-1.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:072e7624b1fc4d601036ab3f4f27942ef772887e876beff0301d261210bca97f", size = 1419762, upload-time = "2025-11-05T18:38:28.295Z" }, - { url = "https://files.pythonhosted.org/packages/21/1b/0374a89ee27d152a5069c356c96b93afd1b94eae83f1e004b57eb6ce2f10/brotli-1.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adedc4a67e15327dfdd04884873c6d5a01d3e3b6f61406f99b1ed4865a2f6d28", size = 1484494, upload-time = "2025-11-05T18:38:29.29Z" }, - { url = "https://files.pythonhosted.org/packages/cf/57/69d4fe84a67aef4f524dcd075c6eee868d7850e85bf01d778a857d8dbe0a/brotli-1.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7a47ce5c2288702e09dc22a44d0ee6152f2c7eda97b3c8482d826a1f3cfc7da7", size = 1593302, upload-time = "2025-11-05T18:38:30.639Z" }, - { url = "https://files.pythonhosted.org/packages/d5/3b/39e13ce78a8e9a621c5df3aeb5fd181fcc8caba8c48a194cd629771f6828/brotli-1.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:af43b8711a8264bb4e7d6d9a6d004c3a2019c04c01127a868709ec29962b6036", size = 1487913, upload-time = "2025-11-05T18:38:31.618Z" }, - { url = "https://files.pythonhosted.org/packages/62/28/4d00cb9bd76a6357a66fcd54b4b6d70288385584063f4b07884c1e7286ac/brotli-1.2.0-cp312-cp312-win32.whl", hash = "sha256:e99befa0b48f3cd293dafeacdd0d191804d105d279e0b387a32054c1180f3161", size = 334362, upload-time = "2025-11-05T18:38:32.939Z" }, - { url = "https://files.pythonhosted.org/packages/1c/4e/bc1dcac9498859d5e353c9b153627a3752868a9d5f05ce8dedd81a2354ab/brotli-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:b35c13ce241abdd44cb8ca70683f20c0c079728a36a996297adb5334adfc1c44", size = 369115, upload-time = "2025-11-05T18:38:33.765Z" }, - { url = "https://files.pythonhosted.org/packages/6c/d4/4ad5432ac98c73096159d9ce7ffeb82d151c2ac84adcc6168e476bb54674/brotli-1.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9e5825ba2c9998375530504578fd4d5d1059d09621a02065d1b6bfc41a8e05ab", size = 861523, upload-time = "2025-11-05T18:38:34.67Z" }, - { url = "https://files.pythonhosted.org/packages/91/9f/9cc5bd03ee68a85dc4bc89114f7067c056a3c14b3d95f171918c088bf88d/brotli-1.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0cf8c3b8ba93d496b2fae778039e2f5ecc7cff99df84df337ca31d8f2252896c", size = 444289, upload-time = "2025-11-05T18:38:35.6Z" }, - { url = "https://files.pythonhosted.org/packages/2e/b6/fe84227c56a865d16a6614e2c4722864b380cb14b13f3e6bef441e73a85a/brotli-1.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8565e3cdc1808b1a34714b553b262c5de5fbda202285782173ec137fd13709f", size = 1528076, upload-time = "2025-11-05T18:38:36.639Z" }, - { url = "https://files.pythonhosted.org/packages/55/de/de4ae0aaca06c790371cf6e7ee93a024f6b4bb0568727da8c3de112e726c/brotli-1.2.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:26e8d3ecb0ee458a9804f47f21b74845cc823fd1bb19f02272be70774f56e2a6", size = 1626880, upload-time = "2025-11-05T18:38:37.623Z" }, - { url = "https://files.pythonhosted.org/packages/5f/16/a1b22cbea436642e071adcaf8d4b350a2ad02f5e0ad0da879a1be16188a0/brotli-1.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67a91c5187e1eec76a61625c77a6c8c785650f5b576ca732bd33ef58b0dff49c", size = 1419737, upload-time = "2025-11-05T18:38:38.729Z" }, - { url = "https://files.pythonhosted.org/packages/46/63/c968a97cbb3bdbf7f974ef5a6ab467a2879b82afbc5ffb65b8acbb744f95/brotli-1.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4ecdb3b6dc36e6d6e14d3a1bdc6c1057c8cbf80db04031d566eb6080ce283a48", size = 1484440, upload-time = "2025-11-05T18:38:39.916Z" }, - { url = "https://files.pythonhosted.org/packages/06/9d/102c67ea5c9fc171f423e8399e585dabea29b5bc79b05572891e70013cdd/brotli-1.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3e1b35d56856f3ed326b140d3c6d9db91740f22e14b06e840fe4bb1923439a18", size = 1593313, upload-time = "2025-11-05T18:38:41.24Z" }, - { url = "https://files.pythonhosted.org/packages/9e/4a/9526d14fa6b87bc827ba1755a8440e214ff90de03095cacd78a64abe2b7d/brotli-1.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:54a50a9dad16b32136b2241ddea9e4df159b41247b2ce6aac0b3276a66a8f1e5", size = 1487945, upload-time = "2025-11-05T18:38:42.277Z" }, - { url = "https://files.pythonhosted.org/packages/5b/e8/3fe1ffed70cbef83c5236166acaed7bb9c766509b157854c80e2f766b38c/brotli-1.2.0-cp313-cp313-win32.whl", hash = "sha256:1b1d6a4efedd53671c793be6dd760fcf2107da3a52331ad9ea429edf0902f27a", size = 334368, upload-time = "2025-11-05T18:38:43.345Z" }, - { url = "https://files.pythonhosted.org/packages/ff/91/e739587be970a113b37b821eae8097aac5a48e5f0eca438c22e4c7dd8648/brotli-1.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:b63daa43d82f0cdabf98dee215b375b4058cce72871fd07934f179885aad16e8", size = 369116, upload-time = "2025-11-05T18:38:44.609Z" }, -] - -[[package]] -name = "brotlicffi" -version = "1.2.0.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/71/97/7845739a36828ffe751a1c6b240692f552fd7ecf65026c51326c0a4aa369/brotlicffi-1.2.0.2.tar.gz", hash = "sha256:5e0fbd13644cf1f6015e75fa5e0ad8fdce1048d9c9ff90b0ce826174b249ee35", size = 478755, upload-time = "2026-08-21T17:29:18.415Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/71/c27f24b8334f65f2492601c7764338f156cb904d2ffe0061e6004a76d9cc/brotlicffi-1.2.0.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:d5a8ffa154f16660ab818d78045b55fa6f9970f1ca4c38998766e99c672071cb", size = 438885, upload-time = "2026-08-21T17:29:04.113Z" }, - { url = "https://files.pythonhosted.org/packages/ef/22/d8fd1a4d09b7ab563b89380395e09151d2ef1344be31594df6a6987d4028/brotlicffi-1.2.0.2-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec6b1af7b7a8ce788354f2c603651ada0fba166ec31ab879e2eec462a3e6dbf4", size = 1534365, upload-time = "2026-08-21T17:29:05.878Z" }, - { url = "https://files.pythonhosted.org/packages/06/78/076419ed6c2c6aa3eaac6fd6b076502b4be89d50625fcdc513cd4aeca718/brotlicffi-1.2.0.2-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22916101de0e7ff535f2edf54b52a85591853b8ae9a98737643defdd3c063a3a", size = 1536851, upload-time = "2026-08-21T17:29:07.599Z" }, - { url = "https://files.pythonhosted.org/packages/35/dd/31ae9945cbd605339fb51c9a609f7dbb182cd361adeabc1d470142357206/brotlicffi-1.2.0.2-cp39-abi3-win32.whl", hash = "sha256:df1d34c4ad9adbf7f63a6b42f7d0e4dfd259c88141b85145b57abecc1abc3b24", size = 342379, upload-time = "2026-08-21T17:29:09.05Z" }, - { url = "https://files.pythonhosted.org/packages/95/ae/afd54e744df93b51cc29f6a19beccf9998b25743d7177697390de10479d1/brotlicffi-1.2.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:489ca4da3ee65926d72bf01584b61088a9da6bdd1bb01b2040901e1beaffa8f0", size = 379761, upload-time = "2026-08-21T17:29:10.687Z" }, -] - -[[package]] -name = "certifi" -version = "2026.7.22" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" }, -] - -[[package]] -name = "cffi" -version = "2.1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pycparser", marker = "implementation_name != 'PyPy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9e/ef/008a1939e372c06329a3fce4279c02f328488f3526744906eeec3da7ad5f/cffi-2.1.1.tar.gz", hash = "sha256:dd31f52ea1086513bb9df30f8fcee9b8918323ae067a3d5b78bc826a000712be", size = 530807, upload-time = "2026-08-03T21:21:18.939Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/10/69/43965eccfdead3b9220015fd1320e117be8c6ed01a62ffab76eeb752f5d5/cffi-2.1.1-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:c8c69575568085ba0b1b10c0249d779a214aea6f6522e949a0fc9fb0fcb449d0", size = 184821, upload-time = "2026-08-03T21:19:44.887Z" }, - { url = "https://files.pythonhosted.org/packages/54/7d/16e5a096677b5e313ca80cd5e5170efa3ea44624a82bb111925522da64b1/cffi-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f81b3b8f3d4e343550fa4baa0e479bba9f2d29ce9c2e9b51d1ce1718d7442fcf", size = 184719, upload-time = "2026-08-03T21:19:46.129Z" }, - { url = "https://files.pythonhosted.org/packages/56/e6/8941622732edec876dd17d0453dce07317ae96db34f2ec1436c9d3785986/cffi-2.1.1-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:811bd1e21d32de12efca32393a0ab3f5133b54fce9bd44b8bd77ab07da14bf6a", size = 214799, upload-time = "2026-08-03T21:19:47.218Z" }, - { url = "https://files.pythonhosted.org/packages/44/de/f98430906df1545ffde0d543dd124a7a439bc2cd32b36b9c53f805df7333/cffi-2.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:68e62fe11f30d5ca8289242866f0a5291402d8529ca2178ab8afc5c9694ae890", size = 222389, upload-time = "2026-08-03T21:19:48.331Z" }, - { url = "https://files.pythonhosted.org/packages/6a/5b/717f1526b9957b34456313c31645c5b82b8fb5c3fe9e4752999be7128bfc/cffi-2.1.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:4a7c934f7360e8cd64fe9efadcbd10c7c6364f531e432b9a4bf5ccbc9e0e8b50", size = 210249, upload-time = "2026-08-03T21:19:49.543Z" }, - { url = "https://files.pythonhosted.org/packages/64/b3/f8aa4f3e34986c7e4ec45072d1b1b9dd295b6b18007b45518d79726dd725/cffi-2.1.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:3143d81e29e1e20a9ce10901ec369012947876596f75a222235965f2b7ae832e", size = 208775, upload-time = "2026-08-03T21:19:50.918Z" }, - { url = "https://files.pythonhosted.org/packages/b1/db/dceb9dd5b231e1da801793f8acc9f3c52a7e1afe40bb1aae37e02b0faad5/cffi-2.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c1453022f490d2459a11819d83ad1d586e9ff65a12ac3e705ffebd46d3685dcf", size = 221822, upload-time = "2026-08-03T21:19:52.054Z" }, - { url = "https://files.pythonhosted.org/packages/a0/d2/6cd24ae3be000a634109c247d1475d62e5616d0dc78c82770942ec384248/cffi-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:208f941bb9d18e768138677f0a6d2ce01f590df56043dda1df1535ac57c88517", size = 225232, upload-time = "2026-08-03T21:19:53.109Z" }, - { url = "https://files.pythonhosted.org/packages/cb/52/3fa190537004dd7f0ab860a6dc7c0175b8667f68d1e618a46f5498d30250/cffi-2.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:210019b6c7cf07f081b4c54635c8cf744377001350e29cc0f81c4377b4797735", size = 223597, upload-time = "2026-08-03T21:19:54.515Z" }, - { url = "https://files.pythonhosted.org/packages/80/fb/0bb75b7039588c074b37ae99f40d9bfddf990ecb2fbc346ebccd2e56b9be/cffi-2.1.1-cp312-cp312-win32.whl", hash = "sha256:046bfc24911b37851ee1b51aab8bffe713d89c68c6a057b09484ce9fd5f69b4e", size = 175292, upload-time = "2026-08-03T21:19:55.566Z" }, - { url = "https://files.pythonhosted.org/packages/d9/79/615cc094e2fb508cade7de88d3b4f6c4ec2bab695c97bce9153dc65aadf5/cffi-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:f53e442b08449d42821fa4a4fba000095af9f62742a500f978a9f557ec44339a", size = 185919, upload-time = "2026-08-03T21:19:56.89Z" }, - { url = "https://files.pythonhosted.org/packages/70/c6/d0ea84713fe46b243a436a18fcd47d639732747e21635c8a27191b06dc30/cffi-2.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:7bde5e4cc5c10140859842b9d383af292b22639a4dffb725314baf45968cef80", size = 180093, upload-time = "2026-08-03T21:19:58.155Z" }, - { url = "https://files.pythonhosted.org/packages/9d/f4/035513d4117049066b4779dc3b7c0c0fdad175fa13731c9f4003f1cd1478/cffi-2.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:b5bdfd1c873d4e093aabc0ca84c4ca6dbc4f752afb5c86f146d9742580c9da2e", size = 194248, upload-time = "2026-08-03T21:19:59.399Z" }, - { url = "https://files.pythonhosted.org/packages/76/af/2aeb4dbb5fc41a04161ae9ff1518de7cec08e164f44a8ce6a4cf7fd2cd1d/cffi-2.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:31348097ff5bbe827ccc41795d4dd099d9f0625e7def00ee653c137a490c2a6c", size = 196908, upload-time = "2026-08-03T21:20:00.746Z" }, - { url = "https://files.pythonhosted.org/packages/a7/46/2e5fdde8555706dd98139a910ca11be02809f3f605ce956f655d0214e100/cffi-2.1.1-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:9d2055050ea716bd38b7f7f1579c275386646b4894c155a3e2f3cd62ed41b7c6", size = 184805, upload-time = "2026-08-03T21:20:02.02Z" }, - { url = "https://files.pythonhosted.org/packages/55/41/4c7042f317b9217502988f0873af87e16ad606dc20f84e546e3e6ce9764c/cffi-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19ee6127ee34de7d83ce3d371ebc5ed91addbdcc39f9ab15ce4eb35a4e534971", size = 184764, upload-time = "2026-08-03T21:20:03.141Z" }, - { url = "https://files.pythonhosted.org/packages/43/1f/1c3d90d91811c8f86ced9ed637956c54bfe5b79ca98fe976d7f8c8979f6b/cffi-2.1.1-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6a8dddef476fab96d066d578fc88526767b836ab5ab21754e1d5bf3879c31c7c", size = 214722, upload-time = "2026-08-03T21:20:04.377Z" }, - { url = "https://files.pythonhosted.org/packages/37/6f/3b5ce4c3b2192d250f04908f2bfd91ef34552ec8f7716a5d4abdb8d67bb2/cffi-2.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f16c709686a78c727bbbf059f92b0bf41c6fc60deec706d2dc19f529175a6125", size = 222369, upload-time = "2026-08-03T21:20:05.544Z" }, - { url = "https://files.pythonhosted.org/packages/02/10/4b3c75dde3d9663c9e02ba05c2668b954f671d4bbe346413ca8c696b295a/cffi-2.1.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:fcd22650c908d7b7da162bbfaab594a1227a15d1643a98c68b122ac642fa2264", size = 210175, upload-time = "2026-08-03T21:20:06.75Z" }, - { url = "https://files.pythonhosted.org/packages/df/62/14f74b9543e605d17701dc797b815958b8bb70b7624ce1b832ddad48ed6c/cffi-2.1.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:aa9511c62d14da7aacc9b4bf51f3f697a621e83b2d6919008243c3aad168eea3", size = 208670, upload-time = "2026-08-03T21:20:08.04Z" }, - { url = "https://files.pythonhosted.org/packages/95/95/86342356ff5953b3fb06f7ef7c5bee212d45e770abc7218d451b9148313c/cffi-2.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a931079504ecc49efed7744c476a5c343a92fabf66dec2db95edb1b2fdc770e2", size = 221824, upload-time = "2026-08-03T21:20:09.274Z" }, - { url = "https://files.pythonhosted.org/packages/eb/ff/7b3429ff53aafe931ed8a5fc69f481bbef7ba6de87ddcbb63d08f483f613/cffi-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a2d7755bef5a12ed488f4ef1f1b69ee9191d7396083b755a5d2295f6edb4768b", size = 225148, upload-time = "2026-08-03T21:20:10.7Z" }, - { url = "https://files.pythonhosted.org/packages/34/34/a95870b9221e09cf4f2ce3178b1a210abdfe63a1bd357da940418d7b8d15/cffi-2.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e0bcb7e0f677f543555d2adff3bf19c05f66cdb4796e5ff602442ab2fe3c4ef7", size = 223564, upload-time = "2026-08-03T21:20:12.165Z" }, - { url = "https://files.pythonhosted.org/packages/70/ea/839b50531021a647fb5e929f72cf97bc1ff702b5472166164b5b6e76b851/cffi-2.1.1-cp313-cp313-win32.whl", hash = "sha256:334644fbac4eff73d985a17a91226df55d0f394160c4cfb880e084c8f7161cac", size = 175263, upload-time = "2026-08-03T21:20:13.559Z" }, - { url = "https://files.pythonhosted.org/packages/60/a6/8b149b2c3f2e11aaa1618ef64500b45f50f22c57a977a4dff1aff1f91042/cffi-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:1aa5645c30469b09530c4ebca77ebf8f17618293c58f8549cb1a543a50236e7d", size = 185688, upload-time = "2026-08-03T21:20:14.69Z" }, - { url = "https://files.pythonhosted.org/packages/01/9a/11f687cb39d6a3504060d5242f04f48c735afb4d3d533958a20594890cb2/cffi-2.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:63bbfd5ded17c4840ac07cd8f1c21ba9d9708141f840b324f422f41b207e3973", size = 180078, upload-time = "2026-08-03T21:20:15.917Z" }, -] - -[[package]] -name = "charset-normalizer" -version = "3.5.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e5/3f/143b048436775b0f76ac3eec145c019e8173ccc2885c8f20319b996d5e83/charset_normalizer-3.5.1.tar.gz", hash = "sha256:6117b84ea48435e5356dc737f5121485c30920ba43375fa7b434fd753df0eac3", size = 171764, upload-time = "2026-08-15T08:20:44.807Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/30/27/78873dc8b6a56357517b74b6bb9568b80450e7bb4f6ef7e3fa9d22aa0bd7/charset_normalizer-3.5.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5b6d1386bf0096d26d3a863dc0a487a5b4eb9aa93cf5ba69683d29dde6b9d60f", size = 344456, upload-time = "2026-08-15T08:17:10.072Z" }, - { url = "https://files.pythonhosted.org/packages/9a/4c/be49ada26b1f0232d57aa89bbebf997a5cc2332a5616b6eca26ff680044d/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4582c27e8c889d64811987b5967fbd3ae0c823fe1fd933b543d55ac20bb475fa", size = 238530, upload-time = "2026-08-15T08:17:11.563Z" }, - { url = "https://files.pythonhosted.org/packages/76/84/6f1290fa07ae6978d3960caa3eb1b8019bf9284ab7c2297b00c099ef4250/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1d1c7a53a6c2103925cdd6d7229f8c567379f211c869793df679f2e9f738c369", size = 230200, upload-time = "2026-08-15T08:17:12.919Z" }, - { url = "https://files.pythonhosted.org/packages/e7/a0/47b18adeed31c8f16ba9700f32c1b18594cfa09f47eb672a488c273c22bf/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e6621fb2a4988d6e53eedc455e5903e2679f3967b8acb3d639f1b63c14a2e893", size = 262222, upload-time = "2026-08-15T08:17:14.571Z" }, - { url = "https://files.pythonhosted.org/packages/38/fe/341861ac118dae06f3ec0eb487488af52128f2ef2faf0b11003944d22259/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7c0c10730342b0c9b35dd1d619beb8214e520bd96a1f870f452680b238aab3e0", size = 258951, upload-time = "2026-08-15T08:17:16.158Z" }, - { url = "https://files.pythonhosted.org/packages/6f/89/bb5108dc6c3651dca963f2b0a3ba19bbcb370c94e1b6d3e0e844a58e6dca/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b9af956078716df40d985fb0dfeb2c2120c5ca92ba4ff4b388acfd01cdc14d08", size = 248801, upload-time = "2026-08-15T08:17:17.683Z" }, - { url = "https://files.pythonhosted.org/packages/b1/ba/ef83ae3aca816393decfa3530976f38a79812d707b80b580ac33b83f9877/charset_normalizer-3.5.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f9f8405c2c758532c74fed975dbee57be1f31a6e865c031870c79a6ed3212ada", size = 244070, upload-time = "2026-08-15T08:17:19.191Z" }, - { url = "https://files.pythonhosted.org/packages/f6/0b/c5292a2462d69b7378ea89793bbb5b2b6fcf6f7dd6d1667f9619094ad553/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:96fef3e886d6a9874b14f27fc193fbdc69d5d8035783d86aa4e1cea594e695f9", size = 240110, upload-time = "2026-08-15T08:17:20.547Z" }, - { url = "https://files.pythonhosted.org/packages/46/22/111e5be3b740d5c2a5bfcedb3d237b6591e5c2e82ae9d6ffcb121fe0909c/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5d8531a6569d025f68e2321e7638fb7978f23db58e5f69f56913837aae03816e", size = 232836, upload-time = "2026-08-15T08:17:21.895Z" }, - { url = "https://files.pythonhosted.org/packages/f9/d2/d2aad6fe0dbb44b194bf3becb60f5a0ac48446ade999a47fe7bb41eb09a7/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:aae2ee51122d3ae968a3837d97dc24a0aeebb0dea23694422cd172bd30017cd6", size = 262712, upload-time = "2026-08-15T08:17:23.727Z" }, - { url = "https://files.pythonhosted.org/packages/35/5a/337e4663a5eae6de99db940ee8066d4145caafb61327db62deda15313cce/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7235dc28fc6dd9d832ac7c7bce95367dedb85929f17368a0c2bee1e080b9acbf", size = 242977, upload-time = "2026-08-15T08:17:25.157Z" }, - { url = "https://files.pythonhosted.org/packages/ca/85/f82f8a92e31c7519410e2e1afdc630f28ec47490ce2c09a11c1a43cbb459/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4abdc5f9ad448c1ecbfae2974b820535d6bc6e7eef63babbab3d81cf46968c71", size = 260207, upload-time = "2026-08-15T08:17:26.602Z" }, - { url = "https://files.pythonhosted.org/packages/b7/52/643d11ffd60e9ac2fd1fb87e167a19285b9eefeff4a40e63c87cbfbeab36/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ba501e667c17d8411f98e67a022d9604ef179aff0e459b7e292c796837c13573", size = 250562, upload-time = "2026-08-15T08:17:27.971Z" }, - { url = "https://files.pythonhosted.org/packages/62/16/46556278c2168d12df9da7fede5dc6fc70e60301b26a82bbeec238c9cfe3/charset_normalizer-3.5.1-cp312-cp312-win32.whl", hash = "sha256:cfa1c0cc3a8f9f53f1243a5a99ac36fd003880199383b37672e86ddda9cb07e2", size = 178507, upload-time = "2026-08-15T08:17:29.277Z" }, - { url = "https://files.pythonhosted.org/packages/9d/7a/4c6c298171e6b3e745633180ff59350fc0ca0db1ffd28df1e369e0579f71/charset_normalizer-3.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:3617ac3cfd8b9888f145ad89dd6e692285834b0201c6074a5eeaad3fd4d668c2", size = 200551, upload-time = "2026-08-15T08:17:30.668Z" }, - { url = "https://files.pythonhosted.org/packages/cd/d7/eb95a042f0dd22e304b0b6472b154f3546a1a039a9ee89ccb2a7f61591fc/charset_normalizer-3.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:88e85ab89cb822c1e635f51d6d32e488f94e002e70e2f492bdb8b945543f345a", size = 180700, upload-time = "2026-08-15T08:17:32.028Z" }, - { url = "https://files.pythonhosted.org/packages/bc/61/2cb6ad133dbbb449fa2d37ccae973232f4827e799af258d15e589a3d1e9e/charset_normalizer-3.5.1-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:4f298bdadb8f0b9e5672877f647d1be9373ef5320c9e2f049795e26cad28b6a9", size = 211584, upload-time = "2026-08-15T08:17:33.597Z" }, - { url = "https://files.pythonhosted.org/packages/18/57/a305c968be1ca13f3dd1b32f445877e97addf55d80b65c7cb35fac82b777/charset_normalizer-3.5.1-cp313-cp313-android_24_x86_64.whl", hash = "sha256:88ca277405c2d3b71c4e1c2ee0e7966e807bcba86a69d11e19ba199d18ae4491", size = 223359, upload-time = "2026-08-15T08:17:35.022Z" }, - { url = "https://files.pythonhosted.org/packages/09/0a/d3646670292ce8d8f8cc11ac067d44885e697a5591f57a9221128da5e7b3/charset_normalizer-3.5.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9362dd90aa7dab48c0054a21187791ccf05473f7dba5d92b8033ae62164675e7", size = 194464, upload-time = "2026-08-15T08:17:36.452Z" }, - { url = "https://files.pythonhosted.org/packages/de/93/d51ec556e01042fed6f993ea859311bc7917b466684182fbbceb6ca24762/charset_normalizer-3.5.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:977cdbd483a9cff38179bea4fd754289a6f2195c7abd414aba85410b3e66cc5e", size = 197676, upload-time = "2026-08-15T08:17:37.819Z" }, - { url = "https://files.pythonhosted.org/packages/a4/a0/562247944386f7d4ef94467e84876600cc1e0f1b93239aaa9213d2bc3cbd/charset_normalizer-3.5.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e90251c0c7bdd54a100a0dce3c07b7e637278c93af29dbf78ebb89a58c4bac7d", size = 340473, upload-time = "2026-08-15T08:17:39.303Z" }, - { url = "https://files.pythonhosted.org/packages/31/e7/1d994be1b93d41e9502b8b0460eaa88a1dd8df335df415db87d6c3e91ab2/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94d78ecec2605a8d0398b0f365d5f12a63248438516f5dac536a5eff7337df4a", size = 240156, upload-time = "2026-08-15T08:17:40.66Z" }, - { url = "https://files.pythonhosted.org/packages/09/53/27923ce5cc6cbccb832037b27dca98882d9c53e9b69e866bbbef4aae7fc8/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d59b75732e9b6f27388e10c14b0259cc5f2e48c78627d185e6a177b58ad3cffe", size = 228246, upload-time = "2026-08-15T08:17:42.003Z" }, - { url = "https://files.pythonhosted.org/packages/ce/48/5a97e84d63af1d55c07439cb80e56d99a8efb4295700eb4e18c0d1615d2c/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0d929fc574b4d6fd9e7c0f5c2ede8716a41911923aa7fa5fce38e0818aa4a1ac", size = 263660, upload-time = "2026-08-15T08:17:43.627Z" }, - { url = "https://files.pythonhosted.org/packages/7a/c2/071575791dcc88316c0a9a65ce38897a82e4cfe4a325f0f7fe1b1ac47bcf/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:394fea06235c8543390050ed5f529187074b029fb027213f6c46ac11ab5d950e", size = 260354, upload-time = "2026-08-15T08:17:45.094Z" }, - { url = "https://files.pythonhosted.org/packages/fb/af/63240b0c0248c075c2535a1f1bd992821d8251b9f173abc13329661d09e4/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62b55f6722735a6c472f88361cde6640608773d9443cebdbb51abf436a1fcdd3", size = 250638, upload-time = "2026-08-15T08:17:46.496Z" }, - { url = "https://files.pythonhosted.org/packages/4d/66/70dfad64f15be09c15ccfee81330a7e515895dbe296dd23114e9a231268a/charset_normalizer-3.5.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa48b1b63d639f9483e0633e092f5851e2348c352f1f9bb6c8182f87884ef876", size = 244583, upload-time = "2026-08-15T08:17:47.963Z" }, - { url = "https://files.pythonhosted.org/packages/c0/24/ef36367d38b9ddd4bccbf72888c342e8de1f5ae506fa0b2dcf970e2732a1/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c71fb0d56c920c269cd3e2e3fe7c610e3f1fdb21a6ce60efa6430ff63676cea6", size = 242038, upload-time = "2026-08-15T08:17:49.481Z" }, - { url = "https://files.pythonhosted.org/packages/db/ab/55e683ba0fff2e43adafc10daa3001eac90fdaa419a97227d5a7067eedde/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:485a0d363cafefcd2538a73c7c838daa2035f09b2c9f9b5e3133f80c6aeb84c2", size = 233677, upload-time = "2026-08-15T08:17:50.845Z" }, - { url = "https://files.pythonhosted.org/packages/bd/67/0f40eaf8d1b6e7cf15e82382a2965efaca787fc1c2794b7021d37aaf5036/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c0ea61a470e070686aa30892fed79e297d2c8d0ab46b8bcdf027d38c51da591", size = 264491, upload-time = "2026-08-15T08:17:52.61Z" }, - { url = "https://files.pythonhosted.org/packages/5c/64/12b4c2a11ee8df4fcc518c78b0d93e3a92bd3d5253d1617ce74ff0e8c7ef/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:90b7481fb62fbe172c558bc6fd1c4c98d82004a54a7551f20e11ac9bf0b8708c", size = 245196, upload-time = "2026-08-15T08:17:54.023Z" }, - { url = "https://files.pythonhosted.org/packages/37/2e/651d910af6d0fba325eee1cda37ec5443462ed25360e666c144166eb6091/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:35fe081843b35aad20ffeccec3eeffbe637b15d14f3fb22cc1b59cd8ec17e93c", size = 261660, upload-time = "2026-08-15T08:17:55.491Z" }, - { url = "https://files.pythonhosted.org/packages/90/c6/b09e05e6db7f64338e0dc067c79577b1138da86c1e38369096851d96be88/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fd0350afdc3aabd5576f60ea109228bd5538139713c7b094c5cd27c73a98bc6f", size = 252618, upload-time = "2026-08-15T08:17:57.025Z" }, - { url = "https://files.pythonhosted.org/packages/76/4e/362d4f9fdcdf5556fb2aa3ce7d4a58ebce03ed1ff03aa1d9aca8d02f13f3/charset_normalizer-3.5.1-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:9d9a0dc7cbe9bec24c3f767c9122c41fe5a1bc43f47cd099d00d393e09769de4", size = 140362, upload-time = "2026-08-15T08:17:58.425Z" }, - { url = "https://files.pythonhosted.org/packages/b4/d4/703be739b26acce318bd29eb3b25b7209e1b1f527f9eae3d1f1f01fdde2b/charset_normalizer-3.5.1-cp313-cp313-win32.whl", hash = "sha256:d63600d620ad0064c3a748b950ac5ea38a80190e5498532efefa4b7b3f1da1f3", size = 177755, upload-time = "2026-08-15T08:18:00.037Z" }, - { url = "https://files.pythonhosted.org/packages/8a/33/56d97ade41c8db611e727168c52ae46c9224c362ec28d4b65d7e9869e8da/charset_normalizer-3.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:aea996a6aba25260827c9ea511d1addfde2da9eb686ac961838509086188b7e6", size = 199295, upload-time = "2026-08-15T08:18:01.506Z" }, - { url = "https://files.pythonhosted.org/packages/5b/75/5b20dd1e6573a01a08158fe104104fa2c8abf941745596954185726cd46c/charset_normalizer-3.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:fd0a274c0e5f9a21565cd9d3dd749b61f96b7aa1e20a93aa1ba4029518f2e5c0", size = 179856, upload-time = "2026-08-15T08:18:02.929Z" }, - { url = "https://files.pythonhosted.org/packages/5b/97/fb4e82231aba271ffd775a1b4993b0defc4e3059f286ae41d9433409fe85/charset_normalizer-3.5.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:41876ee62a3dddf48ff1121ad8f0798032aa03f2fd35f21f34a4cab14f18d8d2", size = 331467, upload-time = "2026-08-15T08:19:50.959Z" }, - { url = "https://files.pythonhosted.org/packages/9f/2f/fe3f187327aac18e2d54e9d2b08e15d27bf9b642d9e51c219f130fc34d1a/charset_normalizer-3.5.1-cp37-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6dac12ff6b846103483683f60c5f8fee205121adc58ffd87e90a90a3af69e99", size = 253057, upload-time = "2026-08-15T08:19:52.654Z" }, - { url = "https://files.pythonhosted.org/packages/d7/c7/9e48cee5c161fe24da823b61bf381921d77cb994a0a4de148e95018c1984/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cee5dd7c6fb5dd52a0fe2a740f9bc6e3593f5f8b1788bde49de02086f30182b2", size = 240930, upload-time = "2026-08-15T08:19:54.163Z" }, - { url = "https://files.pythonhosted.org/packages/49/e0/716601f3cc69be7b198951150c75ead1ece33c3c8036ff6ffa46029659a0/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:343fb4f2821043bd87095f7b08a1a181febc8e36ac64212143bbfd0a0e1bc235", size = 230822, upload-time = "2026-08-15T08:19:55.807Z" }, - { url = "https://files.pythonhosted.org/packages/d3/05/71bfc5caa0abcc45aea1f6a4d50ac68e59605ddc7666fe8494f4cd229665/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ae4a097991662cd4fff0ddc74e0fe7874f82e00042fa0ea00855645ed0c79598", size = 260037, upload-time = "2026-08-15T08:19:57.312Z" }, - { url = "https://files.pythonhosted.org/packages/c3/92/de7e32ed05341e7a9c4c877c318418197b7f2d66a3b68d561bf2ac57ca3e/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b599739b93b2cbeded49645ae3c8d1405c29ddfbceac1545c87a3f9580a9e96", size = 255097, upload-time = "2026-08-15T08:19:59.056Z" }, - { url = "https://files.pythonhosted.org/packages/f5/7b/ade0a122600319dfa0b1000ab0f9731c94a817904cf3c5de408c73a4ede7/charset_normalizer-3.5.1-cp37-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b39b69b347e5e47a3b5b8cfc005c68c1ba347474e3960236c4944a8ecd174962", size = 250166, upload-time = "2026-08-15T08:20:00.612Z" }, - { url = "https://files.pythonhosted.org/packages/75/9c/019fbb9f4834491a160951349b1a3714439376f66e5f7cf18b4f18f0c7aa/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a2028475ba855475b8b4d3cfeb4994269c967aea8b9892dfba907f4263a863a3", size = 241821, upload-time = "2026-08-15T08:20:02.321Z" }, - { url = "https://files.pythonhosted.org/packages/2b/b8/11d4840bfc99330cc7fbcc2681ee5a044553a6e77655508d8f9b2bff7b34/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:36047af20e17097c3bb9476c2b7655f2f7aa51322c0ba58c07695bedf755a950", size = 232529, upload-time = "2026-08-15T08:20:04.008Z" }, - { url = "https://files.pythonhosted.org/packages/18/96/2b3a21492d9f65171ac75d872f5018260013d00bfa0ff70ec9f179148cbd/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4c4fb141a727957c93edfe5c32a26ceb6b5f6461d67146e2d39f51e16170bea8", size = 260348, upload-time = "2026-08-15T08:20:05.877Z" }, - { url = "https://files.pythonhosted.org/packages/d6/aa/a69a2028e8bd052476c245460ab19d7de595de084dd968f2d75cd50c3e25/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:2f293479cce755c75f1697e87c409b7ae4c555c7dfecb6e988ad13abba943031", size = 247234, upload-time = "2026-08-15T08:20:07.487Z" }, - { url = "https://files.pythonhosted.org/packages/35/8a/3d130aeabcaf3d2466af76b7b141c08d9e89c9016ab4b7cdd0f7dc2d1c62/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_s390x.whl", hash = "sha256:3588e376b3ea2eea84976f67273d679f229e24c66dce7b82ae45aef04ff6e072", size = 256917, upload-time = "2026-08-15T08:20:09.142Z" }, - { url = "https://files.pythonhosted.org/packages/80/c2/a7379b840292d0c1ab9fbd17d1f3967aa81794dc95bc74be8999d7fedcf7/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e199fb99720074809a7720f1c0b4d919eea8b87e88713e0f8f602f7bef543d9d", size = 254846, upload-time = "2026-08-15T08:20:10.727Z" }, - { url = "https://files.pythonhosted.org/packages/01/65/d43b714731bb2f40d4053dfa00ecfc1c5a301f8e3316c5db3a09af59fe94/charset_normalizer-3.5.1-cp37-abi3-win32.whl", hash = "sha256:dd732602a7009217f658d5863d12d79d373a4de0eebc111094bcdd3bb8e0a6cc", size = 174216, upload-time = "2026-08-15T08:20:12.334Z" }, - { url = "https://files.pythonhosted.org/packages/35/4f/b911ed898b26a09789eba9c9200c999aff6c61b4bafaf4838e56d1a1e1a3/charset_normalizer-3.5.1-cp37-abi3-win_amd64.whl", hash = "sha256:70055ff39b97c99e7ae40ea3e393fb62aa2e44dbd9b29f8d14f42fb0025c3959", size = 199764, upload-time = "2026-08-15T08:20:13.908Z" }, - { url = "https://files.pythonhosted.org/packages/f0/a7/920baf467bfd9bf689f3b318340f37aee4572a71f162bd8db51da55ba4fa/charset_normalizer-3.5.1-cp37-abi3-win_arm64.whl", hash = "sha256:87e4f41d375c0b9be2fb5251aee4b8a689169e134535aed81bf085c3b647451e", size = 287318, upload-time = "2026-08-15T08:20:15.551Z" }, - { url = "https://files.pythonhosted.org/packages/cc/61/d01fc49b8dea277640b55a9e15960dbca9fdc8c9fde18e572d39c59f4019/charset_normalizer-3.5.1-py3-none-any.whl", hash = "sha256:6df0ec430f9a831772c23ca5a224cba36517a58a84bb32c32bb59a9fa67c47f6", size = 68658, upload-time = "2026-08-15T08:20:43.306Z" }, -] - -[[package]] -name = "clanguru" -version = "0.20.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jinja2" }, - { name = "libclang" }, - { name = "mashumaro" }, - { name = "openpyxl" }, - { name = "py-app-dev" }, - { name = "pyyaml" }, - { name = "typer" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0a/12/dfcc9cbfb8bfcbc9195f6656bce7a0d6dd3f0289162cdd28ed1c04be192e/clanguru-0.20.0.tar.gz", hash = "sha256:e3b662afee973b12ad41d65ad067b74f49f96d793df70b6b062bbda209bb1751", size = 41810, upload-time = "2026-05-08T13:30:02.742Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dd/e8/607231d01116e0cb2f36facd4d77e4800488d2226dad71ed7010e8e85982/clanguru-0.20.0-py3-none-any.whl", hash = "sha256:528cbde48f5bdf9b068745a44a253acef46ec121e49b683b1b00fb91debe6b68", size = 43361, upload-time = "2026-05-08T13:30:01.371Z" }, -] - -[[package]] -name = "click" -version = "8.1.8" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" }, -] - -[[package]] -name = "click-option-group" -version = "0.5.9" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ef/ff/d291d66595b30b83d1cb9e314b2c9be7cfc7327d4a0d40a15da2416ea97b/click_option_group-0.5.9.tar.gz", hash = "sha256:f94ed2bc4cf69052e0f29592bd1e771a1789bd7bfc482dd0bc482134aff95823", size = 22222, upload-time = "2025-10-09T09:38:01.474Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/75/45/54bb2d8d4138964a94bef6e9afe48b0be4705ba66ac442ae7d8a8dc4ffef/click_option_group-0.5.9-py3-none-any.whl", hash = "sha256:ad2599248bd373e2e19bec5407967c3eec1d0d4fc4a5e77b08a0481e75991080", size = 11553, upload-time = "2025-10-09T09:38:00.066Z" }, -] - -[[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, -] - -[[package]] -name = "colorlog" -version = "6.12.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8c/55/ba79756cb90c8d69d599d57785398ac87bba7b19c80e87f4e8a562197c93/colorlog-6.12.0.tar.gz", hash = "sha256:2a7924c1dadf18b22a0eb8b06d1c7b01d5341707ec1641eb6fcc4fde0c3e8e5f", size = 18151, upload-time = "2026-07-23T13:40:40.71Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/19/0b6647bf5e331521e55d2b63bfbdc210bd9cd605189273f03614a05f702d/colorlog-6.12.0-py3-none-any.whl", hash = "sha256:30d392604e9110045a2c2aeefc27d7a017abbab63f3a8aee594eac0801df784e", size = 12239, upload-time = "2026-07-23T13:40:39.562Z" }, -] - -[[package]] -name = "comment-parser" -version = "1.2.4" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.13'", -] -dependencies = [ - { name = "python-magic", version = "0.4.24", source = { registry = "https://pypi.org/simple" } }, -] -sdist = { url = "https://files.pythonhosted.org/packages/76/cd/6e4c79d4a25e3b6542d1a3728b3e2b2c4650e3f8c3cde950aaecc6f64764/comment_parser-1.2.4.tar.gz", hash = "sha256:4e03c421c07d146f5a4d248423820347f3589f60d17acea55f7fb5683faffa22", size = 8350, upload-time = "2022-01-26T04:50:40.828Z" } - -[[package]] -name = "comment-parser" -version = "1.2.5" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.13'", -] -dependencies = [ - { name = "python-magic", version = "0.4.27", source = { registry = "https://pypi.org/simple" } }, -] -sdist = { url = "https://files.pythonhosted.org/packages/37/6a/354d8e640b5f90996ac07c002189a552226a0ddaad85efd14863166aaa14/comment_parser-1.2.5.tar.gz", hash = "sha256:5606b769228cafce03d538e361472896581b386f3bc44bd62f4b61ff45ff05ec", size = 8852, upload-time = "2024-12-25T05:08:14.618Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/78/53/12ceba92ce55851a3d34123b2774a32689f745ce4c0c9bebca1a2a07e266/comment_parser-1.2.5-py3-none-any.whl", hash = "sha256:fb3dee8f351ebd1cfc22d3f19dabf93a4f7d4c9ea13b2092242de086a4449d9e", size = 13222, upload-time = "2026-03-16T03:30:00.77Z" }, -] - -[[package]] -name = "contourpy" -version = "1.3.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, - { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, - { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, - { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, - { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, - { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, - { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, - { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, - { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, - { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, - { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, - { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" }, - { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" }, - { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" }, - { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" }, - { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" }, - { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" }, - { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" }, - { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" }, - { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" }, - { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" }, - { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" }, - { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" }, - { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" }, - { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" }, - { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" }, - { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" }, - { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" }, - { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" }, - { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" }, - { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" }, - { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" }, - { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" }, -] - -[[package]] -name = "cookiecutter" -version = "2.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "arrow" }, - { name = "binaryornot" }, - { name = "click" }, - { name = "jinja2" }, - { name = "python-slugify" }, - { name = "pyyaml" }, - { name = "requests" }, - { name = "rich" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/92/03/f4c96d8fd4f5e8af0210bf896eb63927f35d3014a8e8f3bf9d2c43ad3332/cookiecutter-2.7.1.tar.gz", hash = "sha256:ca7bb7bc8c6ff441fbf53921b5537668000e38d56e28d763a1b73975c66c6138", size = 142854, upload-time = "2026-03-04T04:06:02.786Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/14/a9/8c855c14b401dc67d20739345295af5afce5e930a69600ab20f6cfa50b5c/cookiecutter-2.7.1-py3-none-any.whl", hash = "sha256:cee50defc1eaa7ad0071ee9b9893b746c1b3201b66bf4d3686d0f127c8ed6cf9", size = 41317, upload-time = "2026-03-04T04:06:01.221Z" }, -] - -[[package]] -name = "cycler" -version = "0.12.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, -] - -[[package]] -name = "defusedxml" -version = "0.7.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, -] - -[[package]] -name = "deprecated" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "wrapt" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/49/85/12f0a49a7c4ffb70572b6c2ef13c90c88fd190debda93b23f026b25f9634/deprecated-1.3.1.tar.gz", hash = "sha256:b1b50e0ff0c1fddaa5708a2c6b0a6588bb09b892825ab2b214ac9ea9d92a5223", size = 2932523, upload-time = "2025-10-30T08:19:02.757Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/84/d0/205d54408c08b13550c733c4b85429e7ead111c7f0014309637425520a9a/deprecated-1.3.1-py2.py3-none-any.whl", hash = "sha256:597bfef186b6f60181535a29fbe44865ce137a5079f295b479886c82729d5f3f", size = 11298, upload-time = "2025-10-30T08:19:00.758Z" }, -] - -[[package]] -name = "docopt" -version = "0.6.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/55/8f8cab2afd404cf578136ef2cc5dfb50baa1761b68c9da1fb1e4eed343c9/docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491", size = 25901, upload-time = "2014-06-16T11:18:57.406Z" } - -[[package]] -name = "docutils" -version = "0.21.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444, upload-time = "2024-04-23T18:57:18.24Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408, upload-time = "2024-04-23T18:57:14.835Z" }, -] - -[[package]] -name = "dotty-dict" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6a/ab/88d67f02024700b48cd8232579ad1316aa9df2272c63049c27cc094229d6/dotty_dict-1.3.1.tar.gz", hash = "sha256:4b016e03b8ae265539757a53eba24b9bfda506fb94fbce0bee843c6f05541a15", size = 7699, upload-time = "2022-07-09T18:50:57.727Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/91/e0d457ee03ec33d79ee2cd8d212debb1bc21dfb99728ae35efdb5832dc22/dotty_dict-1.3.1-py3-none-any.whl", hash = "sha256:5022d234d9922f13aa711b4950372a06a6d64cb6d6db9ba43d0ba133ebfce31f", size = 7014, upload-time = "2022-07-09T18:50:55.058Z" }, -] - -[[package]] -name = "et-xmlfile" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d3/38/af70d7ab1ae9d4da450eeec1fa3918940a5fafb9055e934af8d6eb0c2313/et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54", size = 17234, upload-time = "2024-10-25T17:25:40.039Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059, upload-time = "2024-10-25T17:25:39.051Z" }, -] - -[[package]] -name = "fonttools" -version = "4.64.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d4/41/0f072a712dc74496e03710e462a18a4cfd8a258ad055a4e22d28b43a7abd/fonttools-4.64.0.tar.gz", hash = "sha256:ecb2e59a7bc692fee64dda6010deb66222335693b30046f15cccf81233aa715f", size = 3664266, upload-time = "2026-08-31T15:44:33.685Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/82/23/4ea251977fef70ed14193785e1b2949355f1f5927dc0ef1dada675c0bbb2/fonttools-4.64.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9ecb2b206b5b2386f6968721a0770226b66bdd54adc4279bfff3ddf62873eed8", size = 3095501, upload-time = "2026-08-31T15:42:44.299Z" }, - { url = "https://files.pythonhosted.org/packages/c4/32/943e9034f49797e1a25dbbd60c8047ce0c38c3585c5d1b2db38ce64059c5/fonttools-4.64.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8c631303bb1fd7be3067c47536a30ff1fcb4846d6008c112bc52a03f7cd6965", size = 2583091, upload-time = "2026-08-31T15:42:46.57Z" }, - { url = "https://files.pythonhosted.org/packages/0a/4b/332cb3105d5d550cb5728e669151601c4f7698cbedafd134154eb806ef83/fonttools-4.64.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2763e452b025ee8e990f0462e76052de9bb094ebc21d296f62c6dfe958886b4", size = 5423449, upload-time = "2026-08-31T15:42:48.615Z" }, - { url = "https://files.pythonhosted.org/packages/dc/8f/e5f2906ea833d362916c64a2f6b1922a07b19442744fd3cd89563f429bff/fonttools-4.64.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:66a83f93579fb3493e458c4449d1d566a7b2a1c7b19915cd0fa3c9b8b5a8540b", size = 5400981, upload-time = "2026-08-31T15:42:50.862Z" }, - { url = "https://files.pythonhosted.org/packages/fe/46/1b325ebb20aef8bb05f803052ea65931d73638ae3e0d02a6658e3d14e0f8/fonttools-4.64.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cf67f96dc0bfe9607f5f2b734cedfbe2f6f995231adee4ccefa12872044d452d", size = 5359588, upload-time = "2026-08-31T15:42:53.341Z" }, - { url = "https://files.pythonhosted.org/packages/8c/d3/ac56fa880e01339aa6ad0bcb457cc30bb4d6c5ca79922e1bd650e4a3a396/fonttools-4.64.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6786bed88581e19bc4f28ea7a64ad531e8f54acf50327fddca942688824a60bd", size = 5520698, upload-time = "2026-08-31T15:42:55.919Z" }, - { url = "https://files.pythonhosted.org/packages/d7/40/1251fef04c308836a3a7db523703a7ca8ab010dc1e3b9a9bf08e0861c7a8/fonttools-4.64.0-cp312-cp312-win32.whl", hash = "sha256:da4c9bdeaf6b06c12d13d0addfc8ef15aa9695d26574a6dc10751258bef72f30", size = 2430689, upload-time = "2026-08-31T15:42:58.48Z" }, - { url = "https://files.pythonhosted.org/packages/6f/1c/ce0e89183c0235ff6cfbf0603d593023a5afa5333efb35ff569cc0be9ce2/fonttools-4.64.0-cp312-cp312-win_amd64.whl", hash = "sha256:06b6409b868494556a831ae33b2d9a090476c37516b38d70f45a9720b460d423", size = 2482071, upload-time = "2026-08-31T15:43:00.598Z" }, - { url = "https://files.pythonhosted.org/packages/44/7b/49b0054a79b9ed918c018e70e09b56eb5678ee8b44e59e126c79de4d8d73/fonttools-4.64.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9443eefff58aad558608f352092e1be6d278980e8c3b4e8621fcbfda97818500", size = 3092750, upload-time = "2026-08-31T15:43:02.705Z" }, - { url = "https://files.pythonhosted.org/packages/83/e2/08e73bd2f6e6248f071d3e9debe4c2b4cecda3a7dc057c56b44e255421c3/fonttools-4.64.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:09657817b75575822bcd6098ef0ebf0386f34430839ee53109e70fd40a7f6539", size = 2583001, upload-time = "2026-08-31T15:43:04.923Z" }, - { url = "https://files.pythonhosted.org/packages/2f/58/6dc0ff0963fc1e12f53bac59175617ec5927342ee7a4ffb871b44d56d81b/fonttools-4.64.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d7995b906666037d7114c20a5566a372902747452af7d5bd4cd6bca8f1a2550", size = 5392765, upload-time = "2026-08-31T15:43:07.096Z" }, - { url = "https://files.pythonhosted.org/packages/7a/dd/4d3911049680da7b3aecf97cb5094e3a05554f5d94829358fc7664640456/fonttools-4.64.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2c42237b7e8c6813643e57d3efed3be094d4c06339dc2166b626e2cc5c12ee93", size = 5373969, upload-time = "2026-08-31T15:43:09.617Z" }, - { url = "https://files.pythonhosted.org/packages/48/0a/a2cf94121fd3ca9166bdb4863f71d6db32a5606b223c81e2ba99832a0612/fonttools-4.64.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:498f02ea92c9ca18c0f9c581ea93184a9d56c25b0af14189b0767adaf34235d8", size = 5333854, upload-time = "2026-08-31T15:43:11.985Z" }, - { url = "https://files.pythonhosted.org/packages/44/e2/632f4a8b94e6d7eea41e6d82f0ed337f64f5fb96f9910e2ac6b1689910e2/fonttools-4.64.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8252f20108e557532f91d7d6dd9af87c16ed6fa930f65516aa480fa2cfed3363", size = 5492828, upload-time = "2026-08-31T15:43:14.288Z" }, - { url = "https://files.pythonhosted.org/packages/ff/25/7fee1978fdedc1a2d978b76dddbea88785416f5774e7222327b867712d71/fonttools-4.64.0-cp313-cp313-win32.whl", hash = "sha256:45e3ecc3888f1637094fd75cd8fc727f3a4b06d1ddf89181126c071e244fd2a5", size = 2429011, upload-time = "2026-08-31T15:43:16.463Z" }, - { url = "https://files.pythonhosted.org/packages/dd/ed/bb1c217c9e1fbfa59f42b266af57df937a703882a73e59a309773752228f/fonttools-4.64.0-cp313-cp313-win_amd64.whl", hash = "sha256:e4812f71c39d77ec5041348dafa400532adf7bf8f1fffa9aa6495fce5876d7b8", size = 2480199, upload-time = "2026-08-31T15:43:18.774Z" }, - { url = "https://files.pythonhosted.org/packages/82/f8/7188153c4b265c899cd035de6a062677d51f67118a4ba640902bd9683e90/fonttools-4.64.0-py3-none-any.whl", hash = "sha256:4a05783ff54ce4c7a28f18e5772efdf63c219374bd9ffc55452182e1cef8be60", size = 1195327, upload-time = "2026-08-31T15:44:31.741Z" }, -] - -[[package]] -name = "gcovr" -version = "8.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorlog" }, - { name = "jinja2" }, - { name = "lxml" }, - { name = "pygments" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/07/37/b4a87dff166dc0a5002e9d03fcb6ca8eeff048247b011b67f047e31122c9/gcovr-8.6.tar.gz", hash = "sha256:b2e7042abca9321cadbab8a06eb34d19f801b831557b28cdc30a029313de8b9e", size = 199997, upload-time = "2026-01-13T20:04:30.019Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/92/be/f722c843e7875c7cf92cf0e0c1604cddda55a70278c768c6327a78fdba79/gcovr-8.6-py3-none-any.whl", hash = "sha256:dbf9d87c38042752ad6f530aa8210427e22b526611bb7b7bfed0e81977d1f1ef", size = 254618, upload-time = "2026-01-13T20:04:28.15Z" }, -] - -[[package]] -name = "gitdb" -version = "4.0.12" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "smmap" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684, upload-time = "2025-01-02T07:20:46.413Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794, upload-time = "2025-01-02T07:20:43.624Z" }, -] - -[[package]] -name = "gitpython" -version = "3.1.62" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "gitdb" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e0/db/3ca813cbacb23ab6fe46ff38a9b5ef8e73e970c8051f2ce903aacafe0446/gitpython-3.1.62.tar.gz", hash = "sha256:1791de66309bc0c7cfca40bf8d2e3de7ca091cbf94e6051be1ad0722c61062af", size = 231728, upload-time = "2026-09-07T02:57:21.155Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d6/0b/29d7965215f8ef830a7ca1f42997fe13e5693d85e9edb18f938d063ef5f2/gitpython-3.1.62-py3-none-any.whl", hash = "sha256:7002251225e10e29d2e1f49e6532613fe5d5d9f0b6f1f02997a52b38fe56899e", size = 222753, upload-time = "2026-09-07T02:57:19.762Z" }, -] - -[[package]] -name = "giturlparse" -version = "0.15.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8c/8a/b70b84cc78f9059d627f09560c81a11e8f046570d220a24e169489cad6c9/giturlparse-0.15.0.tar.gz", hash = "sha256:9af3f1fd5c4a0cac94ddb283593635005646393ee0debbe330d1bdff8866bf2c", size = 16138, upload-time = "2026-06-16T07:28:56.021Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/96/147a2771ab655b9353781fb2f95c94eaf1d8576dccc991c1a61d0d355067/giturlparse-0.15.0-py2.py3-none-any.whl", hash = "sha256:76d2e6983b037356ab99b30683e533ac3db96409b68e2163a20fc3aff6446f10", size = 16683, upload-time = "2026-06-16T07:28:55.184Z" }, -] - -[[package]] -name = "hammocking" -version = "1.0.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jinja2" }, - { name = "libclang" }, - { name = "py-app-dev" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/fd/0e/b0c0ee876af8ad404c3c1e277d24da9bc9c82e747a93ea31283afe8a5825/hammocking-1.0.2.tar.gz", hash = "sha256:309a3fff0a1c4461390128c82abe7881599f018ba6be828b1cbc51860fe60f47", size = 406743, upload-time = "2026-04-28T20:55:58.345Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/96/86/70140c041f2e4210054e81bf26afca6dffe4527f8a7d915aa5da1769bf62/hammocking-1.0.2-py3-none-any.whl", hash = "sha256:7fa0124b268b8941794a83427a0aa9d3001c75a9f3b458ab03316d07febec7a9", size = 12563, upload-time = "2026-04-28T20:55:57.319Z" }, -] - -[[package]] -name = "idna" -version = "3.19" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5f/f7/abb373e5757eaec4b922b92f97ec8d6d7e057cf06778247604fbc4e7c3f3/idna-3.19.tar.gz", hash = "sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15", size = 215237, upload-time = "2026-08-18T05:14:24.27Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" }, -] - -[[package]] -name = "ignore-python" -version = "0.4.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7b/79/39bfb3743a176742c9075be3b7b12b4ec7100bb4cae13448b1839fb0d9df/ignore_python-0.4.1.tar.gz", hash = "sha256:dbd8a009d554b86c600f7b5cb180a126813ebf485c08d7ae05bcf9754fc25073", size = 12234, upload-time = "2026-07-01T11:59:59.307Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/96/0e/6f4e47c3c730066dc1ddd4d25eda3896d5e24ce51819693db3de87a30496/ignore_python-0.4.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7fa78c104e2cf751ca00491f6b8108153d40e07ea6731a5d0039ddbaf8a7ebb", size = 886147, upload-time = "2026-07-01T11:58:15.993Z" }, - { url = "https://files.pythonhosted.org/packages/1e/2d/6f552951f000cd10773b754042b16c5d8550022d5648234edcf80d3a0326/ignore_python-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88381ad01c24506933cb8d5cdf94417435f20cdf078ffca5bd06b1325c7fe5cb", size = 840284, upload-time = "2026-07-01T11:58:17.219Z" }, - { url = "https://files.pythonhosted.org/packages/e3/0b/d9a27ace2a7cbcc652c8431488933a1e9e54a6704ca536f4cb73436af840/ignore_python-0.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e87d887140602de486130da3f6deee5b03b82302488a92e388dda1f29bfeb30", size = 924643, upload-time = "2026-07-01T11:58:18.298Z" }, - { url = "https://files.pythonhosted.org/packages/3d/55/6d70979897a10e8b453a559cd3a3a1d170e475f20231f183fc945f13da71/ignore_python-0.4.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fe1511740bed936c5a82ceb7a570d6ebb4291dd13b37f8607a0e4072f72bd3e4", size = 894406, upload-time = "2026-07-01T11:58:19.589Z" }, - { url = "https://files.pythonhosted.org/packages/b3/1d/111e67c9be54c9fda510643dfe840cbd4e8e82c8c4677c8c37fb51c86e01/ignore_python-0.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96f3caf5db26f44fec5ce20d837f6e9bd391f5dd8559e8e08fe9d45d74927cde", size = 1177927, upload-time = "2026-07-01T11:58:20.873Z" }, - { url = "https://files.pythonhosted.org/packages/a1/1d/1ec7398522d437dcf5808f3d0c170100dbaff2cfb89886a2d837b87dd4f3/ignore_python-0.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7cf900d9e3ef608eee780a5146846bf9cac4ecfcdfb2a99ae59b2c08baf25c59", size = 953287, upload-time = "2026-07-01T11:58:22.158Z" }, - { url = "https://files.pythonhosted.org/packages/30/5f/ccf9faa05129b10571528867a565cf42cbc2dd58387bed5c297c8dff017f/ignore_python-0.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3718a8a2c17c7953812c473e035b97c697cd2fbd3c1d165450c37fe31f946d91", size = 966483, upload-time = "2026-07-01T11:58:23.508Z" }, - { url = "https://files.pythonhosted.org/packages/ca/f0/6fa4ce058b27017455ac6d2efb8e29bc729041b900af0ad4684f80453a23/ignore_python-0.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:90f7e52145073cf6d8a658e4e259345743a1483d078fc4b09431b9df336635e8", size = 1004278, upload-time = "2026-07-01T11:58:24.633Z" }, - { url = "https://files.pythonhosted.org/packages/46/ce/5fea86be6ebf40c4783974ddf942ffa3bdec390d7ae666d93ddfe151dc23/ignore_python-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0a62eb1130a62333e7d0e2d97fe7107004c6e1495cc934e1c57f979640204ce0", size = 1102070, upload-time = "2026-07-01T11:58:25.85Z" }, - { url = "https://files.pythonhosted.org/packages/e5/99/06c6c35902b15e1040bce8c0bc1804a08a1eef583b4acd84380e22ce011b/ignore_python-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:4dd65cec2aed8883fd8e82d522f00bf177e30412770b3ddac7599182b02c3f4c", size = 1167818, upload-time = "2026-07-01T11:58:27.008Z" }, - { url = "https://files.pythonhosted.org/packages/df/05/f5ad9dc01f18a879a6f47bbae05a40558c2ebf009a65733edeafc55318fc/ignore_python-0.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9dbbb14d4ec825f4279dc499bc6793884e0bc2dc29086a1bda6335bdc438835f", size = 1179445, upload-time = "2026-07-01T11:58:28.29Z" }, - { url = "https://files.pythonhosted.org/packages/ee/61/67e45639e141443830d7dfe8bfac014ba0ad215e04ece6540aa8c1cbeaa9/ignore_python-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:68a0c6754f237e5618a76343d236deee3372f9bc593842d1228a13fb870b1408", size = 1176735, upload-time = "2026-07-01T11:58:29.839Z" }, - { url = "https://files.pythonhosted.org/packages/1d/48/2fa7ebd1e52818379d6686d6242263c8ee3c0210d357c61c4de4a8efeab0/ignore_python-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:c174dc85629eabeed5b2a2a05d6480f70cb6c366e520ba6d1bbe7f540473408f", size = 749842, upload-time = "2026-07-01T11:58:31.047Z" }, - { url = "https://files.pythonhosted.org/packages/eb/51/6b2241f3f07aed3497e8f733c40dd5435fa58c6b0a860fd71bfd37d5bd8f/ignore_python-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:1dafbf90809a5ecd18851a0bd115cb5823d14df9bd84839a8bb14aa83db59721", size = 689895, upload-time = "2026-07-01T11:58:32.25Z" }, - { url = "https://files.pythonhosted.org/packages/ce/02/f351359f18ee87f770cb7126056baa3fe825d58563e2f3c0c0f46f97a442/ignore_python-0.4.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:fe7a1d6774a48df4e63761217fb14f83f7ee664dfd5aa7c43c68e52478fa47e8", size = 885345, upload-time = "2026-07-01T11:58:33.295Z" }, - { url = "https://files.pythonhosted.org/packages/64/44/9433f5dfafa68f98e6f24a2f5b3765d5d3fa0270ab3b1ba21530d49be14f/ignore_python-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbb867ecec784c3cc3491c8c42e55f1a4dcb4016e3855179a5125e1ec206dba5", size = 839369, upload-time = "2026-07-01T11:58:34.42Z" }, - { url = "https://files.pythonhosted.org/packages/ba/2e/bfa075c5b065a44a39c1e890ecb4031e97e73711129e23087ee58f32418f/ignore_python-0.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0dce0d63c67f33bbd0c2fd86db24607c1c92c24329b637b0ad0ecacd0e1c5f3a", size = 924307, upload-time = "2026-07-01T11:58:35.556Z" }, - { url = "https://files.pythonhosted.org/packages/13/f6/78505e82c84aee247f0fe53ceef3ccbe7de57b9826d307c80465dd357312/ignore_python-0.4.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:64093ddd989a262e0d8175ce6d62ac25bb578009d39db8f09b485ea4dbb298f3", size = 894260, upload-time = "2026-07-01T11:58:36.702Z" }, - { url = "https://files.pythonhosted.org/packages/bb/c3/89383366e9050b5030b30b860f2de190d092c390ba244d46a61252044963/ignore_python-0.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:88eb3af05b964ef8d5741a0c2fac3528734a9bcce6f63b8abb4a7121abfa1041", size = 1177366, upload-time = "2026-07-01T11:58:37.912Z" }, - { url = "https://files.pythonhosted.org/packages/25/33/df3b3146da1f9f4e36e4bea9e47cc51771b34b6676344c90876c2316a734/ignore_python-0.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:064092cf100d573543cd4f5c03abb69476a0bf2987a1f6ba756c8d524e86898a", size = 953720, upload-time = "2026-07-01T11:58:39.104Z" }, - { url = "https://files.pythonhosted.org/packages/9d/63/2b6cc85a92820926cdab55452f758bac2b60d6c2c15d1f122297950eae67/ignore_python-0.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19b6cbede28ba33116b3c88695375c40a5ebf658ad4afe28a1d54727edf44161", size = 965884, upload-time = "2026-07-01T11:58:40.347Z" }, - { url = "https://files.pythonhosted.org/packages/98/de/b0e11d32d74802f02377c6980b18b9d64498e36dc26d55304fd8e0387a7a/ignore_python-0.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:76068ce9bc1b53e9cdaf18eaf73a5f658accf326217f748ffdcb4723284fa7e8", size = 1003545, upload-time = "2026-07-01T11:58:41.544Z" }, - { url = "https://files.pythonhosted.org/packages/20/32/c2826b3c34c15b1324a303e1ba0b6fe5bfcac1e3f0a9f471df7728c724e1/ignore_python-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0891e2ac657b51a19fcedebe04c5dc0a432abd4650184086c7a88ce1e824b2f1", size = 1101743, upload-time = "2026-07-01T11:58:42.704Z" }, - { url = "https://files.pythonhosted.org/packages/40/8d/c4aa2bc153497ce125f7966f79a8637f00809b44f9a185d9538f5b69c1bf/ignore_python-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a303ebb4a89d58239e232076eee586e0b9020139044ba776066433d5fbafb925", size = 1167333, upload-time = "2026-07-01T11:58:44.412Z" }, - { url = "https://files.pythonhosted.org/packages/fc/70/022b0163e0e3f2fa596efa3fb245a43b1973a8de7a4ed3f97cccbf0f35df/ignore_python-0.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4fe4684917607a29b15f476b8623f19b31c3a831c17e15d20c4d02800e6527b2", size = 1178935, upload-time = "2026-07-01T11:58:45.783Z" }, - { url = "https://files.pythonhosted.org/packages/34/07/25484608155c064046da3b1411aab56c0d8983036e41ab6e39dc97a4a589/ignore_python-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:58987f4ae122212ac4e0971812c9293138d1326c609f1d94a691aff69f0b4137", size = 1175839, upload-time = "2026-07-01T11:58:47.028Z" }, - { url = "https://files.pythonhosted.org/packages/cf/64/2c075a6a0141bbce26dfc8b913e9bba5da7a428472cc395411391dbd16c3/ignore_python-0.4.1-cp313-cp313-win32.whl", hash = "sha256:0d42e50dd0ccefa515c42704d7fb5571b4ccebe91d33eba6261fad4b99b480d9", size = 657403, upload-time = "2026-07-01T11:58:48.403Z" }, - { url = "https://files.pythonhosted.org/packages/15/d6/21edb0a8145de5d371b8fa80aaea32d6d3bfc4e96e9de261288f9ac2d5af/ignore_python-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:a5487ddfccf518973e18ee2f43cd2b52505c55eb3e9fefc056774a10e30376cb", size = 749016, upload-time = "2026-07-01T11:58:49.515Z" }, - { url = "https://files.pythonhosted.org/packages/7b/a4/84e8e55c99fa0d9c924b09b0c0e1d20ef0d8edfc7562262195a1e78ac731/ignore_python-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:aee1c6043cd1b8d00983c7dec209d668eb1f0cc702d7eda886581b90cd5c274a", size = 689126, upload-time = "2026-07-01T11:58:50.606Z" }, -] - -[[package]] -name = "imagesize" -version = "2.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fb/5e/513ff06670c84e7b9887c1fdf61b2d42b4f574a831f2f1d2222023049d8a/imagesize-2.0.1.tar.gz", hash = "sha256:b2ba6a4dea487a7ebcd53248d3476aca449d30db12a2dde5e0c5ca9624fd77e5", size = 1883774, upload-time = "2026-08-24T12:35:19.13Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/01/f9/575c8d760eae1fc99651b7cc5efd96ad5379ca4d6b53750b0fb4fe983f34/imagesize-2.0.1-py3-none-any.whl", hash = "sha256:ea0c9a0384df69ed86a943a15cde37d0360b82491b3910dc2215e202e62b5b02", size = 14794, upload-time = "2026-08-24T12:35:12.548Z" }, -] - -[[package]] -name = "importlib-resources" -version = "6.5.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cf/8c/f834fbf984f691b4f7ff60f50b514cc3de5cc08abfc3295564dd89c5e2e7/importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c", size = 44693, upload-time = "2025-01-03T18:51:56.698Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461, upload-time = "2025-01-03T18:51:54.306Z" }, -] - -[[package]] -name = "inflate64" -version = "1.0.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3e/f3/41bb2901543abe7aad0b0b0284ae5854bb75f848cf406bf8a046bf525f67/inflate64-1.0.4.tar.gz", hash = "sha256:b398c686960c029777afc0ed281a86f66adb956cfc3fbf6667cc6453f7b407ce", size = 902542, upload-time = "2025-11-28T10:55:52.641Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/33/5cfa7468960de1be0833e7e41adf5b7804a0aef2fb46f3679df3876bf3ab/inflate64-1.0.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c8009e4a4918ee6c8cbc49e58fe159464895064cfdf0565fed3f49ca81e45272", size = 58619, upload-time = "2025-11-28T10:54:48.315Z" }, - { url = "https://files.pythonhosted.org/packages/cd/0a/583c7c2832da36e986c5758d0afb6f5944599e55c5b798b066a9ef63e581/inflate64-1.0.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0d173a7a0e865bb7d19685c5b1ad2994712b8361b24136d7e94abeff58505647", size = 35865, upload-time = "2025-11-28T10:54:49.389Z" }, - { url = "https://files.pythonhosted.org/packages/72/4b/9c6acfbe900e5c8698132244c68036b0455bd2169f46e356c83dc0366f11/inflate64-1.0.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8bad992f2d034f5f7e36208e54502d1b0829ce772c898e5dc59109833420148a", size = 36019, upload-time = "2025-11-28T10:54:50.813Z" }, - { url = "https://files.pythonhosted.org/packages/7f/66/c0c3d3b4b863aab2c2ce631d219a8eb3b95b78acd5f40d3212f071e693db/inflate64-1.0.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6bfcf806912ced77a21394f7363805ecacd626b79f93cba87d505a48e88ede78", size = 98765, upload-time = "2025-11-28T10:54:52.273Z" }, - { url = "https://files.pythonhosted.org/packages/37/00/1a2351a85d36b26c5b2b8cfbb37ad86084c98f592dd7590f8577d8b33993/inflate64-1.0.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62d1aac3aba094ae42e27ce7581b414c90f218248be0953b6aeb11a127225e5d", size = 100594, upload-time = "2025-11-28T10:54:53.684Z" }, - { url = "https://files.pythonhosted.org/packages/f7/3e/5d18ff5b86aaaf54117e1bb6ce15cb17163f56035f9c480e609d35f258ab/inflate64-1.0.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8065166f355122484f004225b379d403346bdae69ec624786a9334f025580675", size = 96745, upload-time = "2025-11-28T10:54:55.277Z" }, - { url = "https://files.pythonhosted.org/packages/84/ff/306d5d6aca1e04e596d2a504a59ff9a900623a6ec852f38aab99f384562d/inflate64-1.0.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:94a95f32087d223d2e119ff5c7c264109e8d4cb7e421e7a688a899a6fe021b38", size = 99795, upload-time = "2025-11-28T10:54:56.485Z" }, - { url = "https://files.pythonhosted.org/packages/cf/ec/d4caa4bf3c9e520c15e900fee5a00fc523953843e14aa378ca1abfb2b4ea/inflate64-1.0.4-cp312-cp312-win32.whl", hash = "sha256:ad4fa490bb7dc2a4640a3adaa2d5950f4a465ba034bbcf184c2103646e58ad97", size = 32956, upload-time = "2025-11-28T10:54:57.642Z" }, - { url = "https://files.pythonhosted.org/packages/33/c4/c0de4e9bdf12e449360b710e9ab5b5248804610f382b538773cbd07b72bb/inflate64-1.0.4-cp312-cp312-win_amd64.whl", hash = "sha256:2c6befdf83d088a6e0d10d0873a9d4bfde2ce00ad7a52c8189cf303306f98030", size = 35577, upload-time = "2025-11-28T10:54:59.14Z" }, - { url = "https://files.pythonhosted.org/packages/80/07/03a5ef74ad3869b3c5af3b09216321f5a1a5a45265f7bd6d5abc669c7622/inflate64-1.0.4-cp312-cp312-win_arm64.whl", hash = "sha256:2b263c619469f90a75f29c421c53d31b208ad494a078235a8f6db2bc96583fdc", size = 33465, upload-time = "2025-11-28T10:55:00.568Z" }, - { url = "https://files.pythonhosted.org/packages/c2/55/b7de7ae318a4f233f892c4f7c8b7e0e8643abe3fdcc53ed35020a9fe3f47/inflate64-1.0.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c3f37540d0e64884a935fd62a7d17e40ab69f05ec63e815483b6513675d01bef", size = 58633, upload-time = "2025-11-28T10:55:01.931Z" }, - { url = "https://files.pythonhosted.org/packages/b8/5f/6f89c8524503fd7a9ca2bd91fe60d7291b3f684e9d41edb38ef49e10fc3d/inflate64-1.0.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4d24112180c95d12f279cade9a1e21f8be7f4790c4109c293292edf87d061992", size = 35864, upload-time = "2025-11-28T10:55:03.075Z" }, - { url = "https://files.pythonhosted.org/packages/7d/14/9eadd59244b38cf85ccd0ca43d1296c50b3a33aa37be4fb68a1928efa58c/inflate64-1.0.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5c098dab17821f466fc6e6a3d78fc6e0295bb51458015f03416b1d58d6a8df4f", size = 36019, upload-time = "2025-11-28T10:55:04.126Z" }, - { url = "https://files.pythonhosted.org/packages/ca/04/399e82d8f5003dd92c8a0c5c1a9a8ce0919114710a496cbe88848bff3a72/inflate64-1.0.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a984b9287ff0fb596eb058d66a9e94530556afd2b7c054b44f2e0aeeff894e8f", size = 98973, upload-time = "2025-11-28T10:55:05.229Z" }, - { url = "https://files.pythonhosted.org/packages/c0/c5/038dc2593bbc4272d87eac8c9f75692267d47f834ced888f6d81995df606/inflate64-1.0.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f62a13d0327631778fa2a47c308ae2b07b2659b7bb8564783259ac65949f8c0c", size = 100777, upload-time = "2025-11-28T10:55:06.41Z" }, - { url = "https://files.pythonhosted.org/packages/74/4a/f6d3031dd3578510894a41bfe1ac149228970ce1629a43f20e0c5abbe8d1/inflate64-1.0.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:513201336fb3b0b7e2aee5dbbbe30a9f1b23291738b5ceb80076fc285f2ec2f1", size = 96967, upload-time = "2025-11-28T10:55:07.594Z" }, - { url = "https://files.pythonhosted.org/packages/26/ef/7ab3bde4e176609ae0e607a8a9bb38d201885275664a6d574299f5bf7850/inflate64-1.0.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:84ce3a97272ba745fce52b38363855c7201968f6402a794bbade774e64c657b9", size = 99997, upload-time = "2025-11-28T10:55:08.85Z" }, - { url = "https://files.pythonhosted.org/packages/39/59/8256b3e802e203c8645e0b32b25e6bd94508ad572593f0cdf8234db3879a/inflate64-1.0.4-cp313-cp313-win32.whl", hash = "sha256:332051a9d7e50579b90a3f555d68f53414b06f636c9ffe82e97c0baae3c8fbcc", size = 32958, upload-time = "2025-11-28T10:55:10.343Z" }, - { url = "https://files.pythonhosted.org/packages/e7/4f/5784ee1eb8260f2310e24ef2883f1f494f9332bcfde4ed14ee780372149e/inflate64-1.0.4-cp313-cp313-win_amd64.whl", hash = "sha256:3983f53b590ff7d0ba243f664ce852aca882482f30f7a8eab33e10d769336d0c", size = 35578, upload-time = "2025-11-28T10:55:11.389Z" }, - { url = "https://files.pythonhosted.org/packages/39/e8/8d927770ce25dc9764c8104207a80653d65471d0a6a8f9ead350016e4586/inflate64-1.0.4-cp313-cp313-win_arm64.whl", hash = "sha256:118d8286f085e99a14341c76ef9fbffd56619ccc80318a9a204aea3dbfa71470", size = 33465, upload-time = "2025-11-28T10:55:12.824Z" }, -] - -[[package]] -name = "iniconfig" -version = "2.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, -] - -[[package]] -name = "jinja2" -version = "3.1.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markupsafe" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, -] - -[[package]] -name = "jsonschema" -version = "4.26.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "attrs" }, - { name = "jsonschema-specifications" }, - { name = "referencing" }, - { name = "rpds-py" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, -] - -[[package]] -name = "jsonschema-rs" -version = "0.52.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e6/48/771a96c524eafadcd2366271c13bd6e5b79af9032821f11e22846d36da3f/jsonschema_rs-0.52.1.tar.gz", hash = "sha256:25ec1da52bc1c11c012e17a01b97e24d33a15d1d76c705fd5a7c276d1bbe7677", size = 2631333, upload-time = "2026-08-30T09:52:05.865Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/13/37/92f23a0e53b6448faaae98ea4896f549c104cea8c07b8d213a22b2fb84dd/jsonschema_rs-0.52.1-cp310-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:cf728999f2cf4fb4fba8abf8d5a110565f716f4a460f13157de59e836c03706d", size = 10119218, upload-time = "2026-08-30T09:51:25.959Z" }, - { url = "https://files.pythonhosted.org/packages/71/3e/52261a693a148d8746db1ff67591e9e9f4b56b155be3d48840f7e5cbab58/jsonschema_rs-0.52.1-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:d32934b6a3032e968e59bb172addb8e752d7fdbff742834390f87549889c86db", size = 5277560, upload-time = "2026-08-30T09:51:28.055Z" }, - { url = "https://files.pythonhosted.org/packages/82/07/94db20d60141955d1987b73bcae8fdd3db6cfdf7ef4ad68fb9b399cd436d/jsonschema_rs-0.52.1-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5eb2562cca5effc43134c4f8d2ba0c2b04b97c7cfcb6ac27997f0a5b2aab662f", size = 5179470, upload-time = "2026-08-30T09:51:29.328Z" }, - { url = "https://files.pythonhosted.org/packages/5f/5d/cd7ab77544d6a8fad152c0ec1c332de1506da1c753475357fbfbf5ed91a2/jsonschema_rs-0.52.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:150845b65c701d5db2a397c97beb98356123548b29c70095ec9a851a611cc736", size = 5418427, upload-time = "2026-08-30T09:51:30.682Z" }, - { url = "https://files.pythonhosted.org/packages/ac/30/8b3d95e7be67daa93996a1444ca2238bdfcb58debf8bccf6469b5f183dc8/jsonschema_rs-0.52.1-cp310-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:895e7ec4c6e7ff564786f86e197c95d15017a42177523319bd45ede20e4c4417", size = 4999697, upload-time = "2026-08-30T09:51:31.998Z" }, - { url = "https://files.pythonhosted.org/packages/c7/24/001bb2ee8ca4ba19a7e31daad391de8e10f554a4f8c8396f83775eb585fa/jsonschema_rs-0.52.1-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4b05b745f05859ca8b2d056e22a3fae036e922a61805680cbe77cac31141be2e", size = 5185040, upload-time = "2026-08-30T09:51:33.943Z" }, - { url = "https://files.pythonhosted.org/packages/f8/84/50cd77eb2d1763625e8b394d5da6ffebcb148f71f4350b7bf3872ba25996/jsonschema_rs-0.52.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f709d4666f7fc728829503ff653c8a578634ab1ed54e9f11097da89dd18baf75", size = 5658962, upload-time = "2026-08-30T09:51:35.305Z" }, - { url = "https://files.pythonhosted.org/packages/02/5d/a588c8fc4d3f9fc06e222d35003c9c60a28bbcc492c49f273e19fa205ef8/jsonschema_rs-0.52.1-cp310-abi3-win32.whl", hash = "sha256:9131e083499ea49157e559dff813215f3ed116ed3a2996b108fed2faf1b80c0a", size = 4613745, upload-time = "2026-08-30T09:51:36.776Z" }, - { url = "https://files.pythonhosted.org/packages/1d/64/6b08a726984c8a3eb648cc516efa5bf20d218f6c26b912ffc126746c8147/jsonschema_rs-0.52.1-cp310-abi3-win_amd64.whl", hash = "sha256:3c9152a9aaec6a656b89fa203ccc26de54065e26cf6785378d54b6390ad403f2", size = 5340066, upload-time = "2026-08-30T09:51:38.183Z" }, -] - -[[package]] -name = "jsonschema-specifications" -version = "2025.9.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "referencing" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, -] - -[[package]] -name = "junitparser" -version = "5.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6c/c9/edeb58b0565b74ba0ffedf822d63aa40368bb4ff2da49751768eea696f5a/junitparser-5.0.3.tar.gz", hash = "sha256:f824ec1b7afbf2fd3b6813b87967b02fcda750e135d27be17cae497147419a04", size = 12120, upload-time = "2026-09-01T06:13:00.32Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/36/dbb6f74621890e5daa831c5f474b81e8b773298adf5ea835c03215a9359c/junitparser-5.0.3-py3-none-any.whl", hash = "sha256:553535b8cd1672a9c8bd2c7326ae1ae69279267fbc562106b5aa44d3c5f769af", size = 15000, upload-time = "2026-09-01T06:12:58.01Z" }, -] - -[[package]] -name = "kconfiglib" -version = "14.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/59/29/d557718c84ef1a8f275faa4caf8e353778121beefbe9fadfa0055ca99aae/kconfiglib-14.1.0.tar.gz", hash = "sha256:bed2cc2216f538eca4255a83a4588d8823563cdd50114f86cf1a2674e602c93c", size = 162613, upload-time = "2020-01-31T06:40:50.563Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/f1/d98a89231e779b079b977590efcc31249d959c8f1d4b5858cad69695ff9c/kconfiglib-14.1.0-py2.py3-none-any.whl", hash = "sha256:edcd35a20e7e138a9a9e96149027f805f785e818d2eae400b6fa8b0c8845114a", size = 145875, upload-time = "2020-01-31T06:40:47.252Z" }, -] - -[[package]] -name = "kiwisolver" -version = "1.5.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/07/bd78e6a8fae171ea041ef5bba3ed21a003522fa088834b069b1909981f30/kiwisolver-1.5.1.tar.gz", hash = "sha256:f1303ef2eec81262a4b708c3e858afe58d7c75ad91c1c05266eda7673369859a", size = 104395, upload-time = "2026-08-28T10:28:27.153Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/9b/65b302742389c6f96f2956bef5decf26011309feb2fc5d79613af18adea4/kiwisolver-1.5.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:63fb7294b768f444eb4b068965f2662f28c2fd4161e23bd60fcf3ff27b74c046", size = 123876, upload-time = "2026-08-28T10:25:27.44Z" }, - { url = "https://files.pythonhosted.org/packages/71/74/c21f339956f6f691b2ed7e31d5f3ae767304df6c460192739fc830853051/kiwisolver-1.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0ebdef3eae5336568147c39a55be6a2036ffde53faa9ca2d978989ae7c2da12c", size = 66487, upload-time = "2026-08-28T10:25:28.728Z" }, - { url = "https://files.pythonhosted.org/packages/84/e5/bdb34e21523e01dceda064d63713f3bdec91388af24fba1eca7ea5e85864/kiwisolver-1.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1798e83840c3f627246104c4d8a9639c60fa068adf9ce92b61791781fa8a68c1", size = 64660, upload-time = "2026-08-28T10:25:30.071Z" }, - { url = "https://files.pythonhosted.org/packages/fc/f4/dadfec469313c7f428efa7e84b4aba9732f813c13ea7131a24b7b008ef57/kiwisolver-1.5.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:34633ecf50d16187ab8e5528b7a2530f2feb4e23f300db4672538b51cfc5cd38", size = 1477929, upload-time = "2026-08-28T10:25:31.495Z" }, - { url = "https://files.pythonhosted.org/packages/6f/35/09c58daac34e6f6ea5c6dee0094b422118e5a7c265586008a95fd135ac5f/kiwisolver-1.5.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d27c2123977cb9269c30a49ba45f03a4323017ef693e19db4ec9dbe1299a3002", size = 1278499, upload-time = "2026-08-28T10:25:33.375Z" }, - { url = "https://files.pythonhosted.org/packages/19/32/739765e24fbad29d13f83e546ea4abc215a78cea9d677ca09025b027724d/kiwisolver-1.5.1-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6a797a1cefc8b9c93170db580337e1fe3d011ad18b1299943231279406342048", size = 1296677, upload-time = "2026-08-28T10:25:35.059Z" }, - { url = "https://files.pythonhosted.org/packages/df/32/03304d1010e2cc45e5b3b52cef7e43fed3a2a5cd6c87a89b4a88e1d85b5d/kiwisolver-1.5.1-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2551cf9917af48ee7c4b29cc82320489508cf96fd26a51f6fc124de661cd44c7", size = 1346037, upload-time = "2026-08-28T10:25:36.705Z" }, - { url = "https://files.pythonhosted.org/packages/3e/57/4c49377bfd274450dd72ecaa13eaac32ea804a03363e4d1db0c5aa999ceb/kiwisolver-1.5.1-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:38f6e0deb4d0a4615efe0c4efc5990b06ae450ab50a0b321c0b078b6d238c083", size = 988248, upload-time = "2026-08-28T10:25:38.299Z" }, - { url = "https://files.pythonhosted.org/packages/cb/c3/38df144a08b6c5d75ca4504e5cc3141bb3bfef64c04f4ef48204f42711b6/kiwisolver-1.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bfd1de989b3330420e29de39352f5c049905c9e3ee67233a50d550e3d652c148", size = 2228722, upload-time = "2026-08-28T10:25:40.038Z" }, - { url = "https://files.pythonhosted.org/packages/e7/11/3221838a89cd64d9b386353e000cd8a296069a20fbe3584507fdfd5bebae/kiwisolver-1.5.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1209042a623ddfda5497e4066c7b77651dde8e1d3a9dd97599dc7e97f3b9b78c", size = 2325216, upload-time = "2026-08-28T10:25:41.699Z" }, - { url = "https://files.pythonhosted.org/packages/83/d4/075c219230697bb5db910d37262b9bacf880f92b4811a02ab81ed073a253/kiwisolver-1.5.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:26e8268480be5061d509e29669d59103c067a26377a56491630ece11762e3858", size = 1977689, upload-time = "2026-08-28T10:25:43.559Z" }, - { url = "https://files.pythonhosted.org/packages/bb/08/1d219c3c2dd960983d0d4da623d916e9de6385df2b0bab3d1af0e9b8fccc/kiwisolver-1.5.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d79308fa689fac89cbcfbd4dbfc80b5f95c54c5a7fd4d194be221f9d33d026e6", size = 2491443, upload-time = "2026-08-28T10:25:45.242Z" }, - { url = "https://files.pythonhosted.org/packages/ba/d3/024208ec1079d273f1047468d1bdffbf38bb75b7b268090fd3a0301b9d9a/kiwisolver-1.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b03af77d77e50edba2030fd5f7c352ff209314b09030a3cba7c14edf9a09a444", size = 2295200, upload-time = "2026-08-28T10:25:46.984Z" }, - { url = "https://files.pythonhosted.org/packages/6e/7c/7b210498f9f92e1cd7855f260fa69ef056881087b199ee20c208f0e4189a/kiwisolver-1.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:06a6917674de9e0fe3f66f5430787f59a9f2ddb64af9b714eaec547e29ef5c19", size = 70748, upload-time = "2026-08-28T10:25:48.444Z" }, - { url = "https://files.pythonhosted.org/packages/94/61/ef0daa157c8bb23672f7423e0d14c39db1dc6ef8ed47e6bc54c9c1bef3bf/kiwisolver-1.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:ad8b9671348d7c8716715652ae11f85ed0eb99e265a2df2ca490577d69860b2c", size = 68324, upload-time = "2026-08-28T10:25:49.81Z" }, - { url = "https://files.pythonhosted.org/packages/08/c1/88018321d976f53c421e379c43bc6993e70ce0c8a3ec5edc4bfe102257f6/kiwisolver-1.5.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:b6ae6a0328f0bc035741820fdeecdcd67bf4694eee03972e843663107122f450", size = 62272, upload-time = "2026-08-28T10:25:51.02Z" }, - { url = "https://files.pythonhosted.org/packages/85/d2/712bc17ea4f1d216034928069d612defbc6c95a471c55a7203a39faecb1a/kiwisolver-1.5.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:886fc26012f0e8b5f69d1cfe6d711f6b11f194621539bf8e6bb1c25c5dc82724", size = 64481, upload-time = "2026-08-28T10:25:52.22Z" }, - { url = "https://files.pythonhosted.org/packages/1b/e6/6c5380d676f43b6d918033962ea5e72360ca69e5a404154bc496b598ffdb/kiwisolver-1.5.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:aefe930d113798330e9462f7874542977869c0613cba3262e2de3a8d5dee8f3a", size = 66260, upload-time = "2026-08-28T10:25:53.387Z" }, - { url = "https://files.pythonhosted.org/packages/92/6a/7087f5822cc8bb272679641404b1966a42504dac9ee74e2b33840475a0aa/kiwisolver-1.5.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5ca5aebae78a0bc13c1943af4af615d4966c5b650b05d5aa83b50e427196fee", size = 123876, upload-time = "2026-08-28T10:25:54.644Z" }, - { url = "https://files.pythonhosted.org/packages/5c/4b/9f385087ca09ee5ab9c09c6832561a7d2f7c78d3e5661d511e669f70e439/kiwisolver-1.5.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1ed0f5e49d0ceff8b72190824d9e59c062fbbc02c231b853112c78474b3f5ec2", size = 66487, upload-time = "2026-08-28T10:25:55.899Z" }, - { url = "https://files.pythonhosted.org/packages/47/8b/40d33ffd2f378094ed462e9a9a0907e59d4de9845e65a59561272da350d4/kiwisolver-1.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:77a4c8187a5948d7f8795adb765a3c7b553d07d86d88e43038fc32fc1fb9a3f3", size = 64673, upload-time = "2026-08-28T10:25:57.048Z" }, - { url = "https://files.pythonhosted.org/packages/ab/43/86aacc027959108b4c66eeae8b73cedb057dfa6eb3a335d05ad65197081c/kiwisolver-1.5.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:74ad5c3dad54a4641b4c28cd15ded70899d04459c6c7aeacafea716be97cce6d", size = 1477992, upload-time = "2026-08-28T10:25:58.479Z" }, - { url = "https://files.pythonhosted.org/packages/ed/40/b1d0369048c79733a32c8abb0f2718532e6630641368e33a81384246e844/kiwisolver-1.5.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21e46b23a2da695c364124817bc01d970effd5483147f8d66a6a7167e3f6b851", size = 1278821, upload-time = "2026-08-28T10:26:00.121Z" }, - { url = "https://files.pythonhosted.org/packages/61/a1/fa71c1792272ff9461432715ae60ffb7e11a4d7ac3bf68961b9cab6c60cf/kiwisolver-1.5.1-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:75d9b1cf8258462dbdc1eeda718c96ea7f079324c09067f6daabfcf37712b7fe", size = 1296805, upload-time = "2026-08-28T10:26:01.868Z" }, - { url = "https://files.pythonhosted.org/packages/a3/49/3f0bd94af8e06ecc47eb834b195a06f05d48711ceb2352c56d6835160f0e/kiwisolver-1.5.1-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fca690b00c4c48f6c2a547b0160ed511357093a4e4c9b47e0fadf3128066d89", size = 1346109, upload-time = "2026-08-28T10:26:03.59Z" }, - { url = "https://files.pythonhosted.org/packages/a4/44/3afe6ef9cf06d61220953a8963e94eca978491be1d9547cb01d82a1efa08/kiwisolver-1.5.1-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:876bbfd276473d3daffe30e8c975df4ed9429967b41a6cb362dbb5155b6f13ad", size = 988252, upload-time = "2026-08-28T10:26:05.306Z" }, - { url = "https://files.pythonhosted.org/packages/e4/30/a12bd7a7285a211e1747c3eec77b8c614dbfcc1dad942f7611a1a6921ae5/kiwisolver-1.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f942903fde7363d1d879057ec5de01310efda2597161784d752fa9953a01a71a", size = 2228846, upload-time = "2026-08-28T10:26:07.312Z" }, - { url = "https://files.pythonhosted.org/packages/a1/6b/233e2958abf0dab7b18d07e52f286e02e519d7651bfbbe97af9347564109/kiwisolver-1.5.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c90d3022d8a94778939cda8638c6c8da8fa757b8958dad7ec868ce29c87681b8", size = 2325583, upload-time = "2026-08-28T10:26:09.093Z" }, - { url = "https://files.pythonhosted.org/packages/42/8e/7673060a27b01405b580058510adef34687069d229800239f5e44682d4d0/kiwisolver-1.5.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8a34616dc2521cc8dc1d7d081734da63539f021ac0450ce950908340c6e7aa2f", size = 1978221, upload-time = "2026-08-28T10:26:11.127Z" }, - { url = "https://files.pythonhosted.org/packages/3f/7b/1b882fc1a8b4a0bb8084e7d1d85004116d08c92c0705d31f2928dec607f2/kiwisolver-1.5.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:8bf4df63592c2a66b4f8edc5df2544998c288aa02f96ce0acd880cd1de8c8127", size = 2491819, upload-time = "2026-08-28T10:26:13.348Z" }, - { url = "https://files.pythonhosted.org/packages/39/9c/426deb49e62c5f69464b64bbeca064d3b758a7506b8913d986ef34f4619c/kiwisolver-1.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d09037ca068d784ebc4aec290ef952ca27ac15dd9c0b5801a88c6e1096b83e6b", size = 2295520, upload-time = "2026-08-28T10:26:15.042Z" }, - { url = "https://files.pythonhosted.org/packages/f5/22/deabbb3ad6d918d74b7831b2d8ae7151b09d21c87974e5ee8a456f58c94c/kiwisolver-1.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:dc23390afe9f4ef9ac3bcc72a03a56eebbde03f4c571a32cb38f859cff9a6524", size = 70758, upload-time = "2026-08-28T10:26:16.504Z" }, - { url = "https://files.pythonhosted.org/packages/b9/71/02fa5c2fd92068bb8952847e70ee6c5cb280e7febe11653d17812acc53dd/kiwisolver-1.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:186884a58486651e3c217b6acea0a53eaa9498fdd472057c46f2f0fb5c25aad5", size = 68329, upload-time = "2026-08-28T10:26:17.658Z" }, - { url = "https://files.pythonhosted.org/packages/a9/c4/1407df7512a5b36cc79840e01710dc575733c461b13ab866cae77eaf87f3/kiwisolver-1.5.1-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:482676e5bd48d70ac99d9fc78863469845421e01184fa83f1f9366dc49f7e974", size = 134002, upload-time = "2026-08-28T10:28:08.881Z" }, - { url = "https://files.pythonhosted.org/packages/16/45/c37a21ad5c0ab581a93c55ad544721aaa1f0ae94edb29c6a678a23d013e6/kiwisolver-1.5.1-graalpy312-graalpy250_312_native-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:072bdb15a3c19a5b5dbc8f8fb1f4e1884bf4f3507eeb4cc6334401274d37a5c0", size = 194292, upload-time = "2026-08-28T10:28:11.06Z" }, - { url = "https://files.pythonhosted.org/packages/a6/c1/69f00d627949580e43d57af0aa465df46868d7c29801c137a55374101294/kiwisolver-1.5.1-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:a5a00665d1a0e26763a7338d7e911d4598fbc1d50dd0d6b7919b7dc6c5d6569f", size = 73362, upload-time = "2026-08-28T10:28:12.449Z" }, -] - -[[package]] -name = "libclang" -version = "18.1.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6e/5c/ca35e19a4f142adffa27e3d652196b7362fa612243e2b916845d801454fc/libclang-18.1.1.tar.gz", hash = "sha256:a1214966d08d73d971287fc3ead8dfaf82eb07fb197680d8b3859dbbbbf78250", size = 39612, upload-time = "2024-03-17T16:04:37.434Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/49/f5e3e7e1419872b69f6f5e82ba56e33955a74bd537d8a1f5f1eff2f3668a/libclang-18.1.1-1-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:0b2e143f0fac830156feb56f9231ff8338c20aecfe72b4ffe96f19e5a1dbb69a", size = 25836045, upload-time = "2024-06-30T17:40:31.646Z" }, - { url = "https://files.pythonhosted.org/packages/e2/e5/fc61bbded91a8830ccce94c5294ecd6e88e496cc85f6704bf350c0634b70/libclang-18.1.1-py2.py3-none-macosx_10_9_x86_64.whl", hash = "sha256:6f14c3f194704e5d09769108f03185fce7acaf1d1ae4bbb2f30a72c2400cb7c5", size = 26502641, upload-time = "2024-03-18T15:52:26.722Z" }, - { url = "https://files.pythonhosted.org/packages/db/ed/1df62b44db2583375f6a8a5e2ca5432bbdc3edb477942b9b7c848c720055/libclang-18.1.1-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:83ce5045d101b669ac38e6da8e58765f12da2d3aafb3b9b98d88b286a60964d8", size = 26420207, upload-time = "2024-03-17T15:00:26.63Z" }, - { url = "https://files.pythonhosted.org/packages/1d/fc/716c1e62e512ef1c160e7984a73a5fc7df45166f2ff3f254e71c58076f7c/libclang-18.1.1-py2.py3-none-manylinux2010_x86_64.whl", hash = "sha256:c533091d8a3bbf7460a00cb6c1a71da93bffe148f172c7d03b1c31fbf8aa2a0b", size = 24515943, upload-time = "2024-03-17T16:03:45.942Z" }, - { url = "https://files.pythonhosted.org/packages/3c/3d/f0ac1150280d8d20d059608cf2d5ff61b7c3b7f7bcf9c0f425ab92df769a/libclang-18.1.1-py2.py3-none-manylinux2014_aarch64.whl", hash = "sha256:54dda940a4a0491a9d1532bf071ea3ef26e6dbaf03b5000ed94dd7174e8f9592", size = 23784972, upload-time = "2024-03-17T16:12:47.677Z" }, - { url = "https://files.pythonhosted.org/packages/fe/2f/d920822c2b1ce9326a4c78c0c2b4aa3fde610c7ee9f631b600acb5376c26/libclang-18.1.1-py2.py3-none-manylinux2014_armv7l.whl", hash = "sha256:cf4a99b05376513717ab5d82a0db832c56ccea4fd61a69dbb7bccf2dfb207dbe", size = 20259606, upload-time = "2024-03-17T16:17:42.437Z" }, - { url = "https://files.pythonhosted.org/packages/2d/c2/de1db8c6d413597076a4259cea409b83459b2db997c003578affdd32bf66/libclang-18.1.1-py2.py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:69f8eb8f65c279e765ffd28aaa7e9e364c776c17618af8bff22a8df58677ff4f", size = 24921494, upload-time = "2024-03-17T16:14:20.132Z" }, - { url = "https://files.pythonhosted.org/packages/0b/2d/3f480b1e1d31eb3d6de5e3ef641954e5c67430d5ac93b7fa7e07589576c7/libclang-18.1.1-py2.py3-none-win_amd64.whl", hash = "sha256:4dd2d3b82fab35e2bf9ca717d7b63ac990a3519c7e312f19fa8e86dcc712f7fb", size = 26415083, upload-time = "2024-03-17T16:42:21.703Z" }, - { url = "https://files.pythonhosted.org/packages/71/cf/e01dc4cc79779cd82d77888a88ae2fa424d93b445ad4f6c02bfc18335b70/libclang-18.1.1-py2.py3-none-win_arm64.whl", hash = "sha256:3f0e1f49f04d3cd198985fea0511576b0aee16f9ff0e0f0cad7f9c57ec3c20e8", size = 22361112, upload-time = "2024-03-17T16:42:59.565Z" }, -] - -[[package]] -name = "loguru" -version = "0.7.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "win32-setctime", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559, upload-time = "2024-12-06T11:20:56.608Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" }, -] - -[[package]] -name = "lxml" -version = "6.1.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/23/ad/28ecd7cb894d172f3c9c80a075eeeb2017ac62e3632cee05a5f9493547eb/lxml-6.1.3.tar.gz", hash = "sha256:45222d94ddd511536f3b2f7d9deae3b2339b4ce0f075f1ca25703b07cad9dd21", size = 4211198, upload-time = "2026-09-02T14:48:02.287Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dd/1f/a180b57d9eeabaab77f9d5aa30356898ea749c4795596a8f66d1eb6bef2e/lxml-6.1.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c0710ac085a157b593c38fbcacd950f15c4afa8e2057527185875ab302752bc", size = 8602094, upload-time = "2026-09-02T14:47:26.054Z" }, - { url = "https://files.pythonhosted.org/packages/a8/25/070c92013a1c029a602b03560d68772313d918268667fa993da7961759c9/lxml-6.1.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:623c8799c17128753c65699f1c3aa32402657393a9ad6db09ed8b98ddf76611d", size = 4638308, upload-time = "2026-09-02T14:47:29.587Z" }, - { url = "https://files.pythonhosted.org/packages/1e/1c/722e88883173097a1a375153e3c2447eba3060d0231522cf6596e99f4195/lxml-6.1.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f683dc6300317700025e41d89a43e0276692ded16113a3c43eab704d605c58e5", size = 4939696, upload-time = "2026-09-02T14:47:32.997Z" }, - { url = "https://files.pythonhosted.org/packages/db/36/aa413bc214dc4f785ad2b2ddd8cc99aae7062d49ab155e91e6011af00daf/lxml-6.1.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:379f8a75cf6eb7eef0af074b55f49ab73b868388a98de14646abcdfa4564bb11", size = 5105247, upload-time = "2026-09-02T14:47:36.734Z" }, - { url = "https://files.pythonhosted.org/packages/a3/a0/a1f7f1313795bfec67b77f01ef3b1128d49f2d7f66a8413fa55d47f4e25f/lxml-6.1.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b37772102d44bb6628186accca3a121b1fa3a6b3d97518a8c29a5229ca4c0d0a", size = 5011915, upload-time = "2026-09-02T14:47:39.846Z" }, - { url = "https://files.pythonhosted.org/packages/b9/78/840e7e3f1d0cc7a5cfac5d8505b97e25b6427fd774ac4bae672aaebfb4b5/lxml-6.1.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ddcf547bea2aee967d6a77779376a45e77e610e8465147a1f3d7e20d539d6e32", size = 5638175, upload-time = "2026-09-02T14:47:43.644Z" }, - { url = "https://files.pythonhosted.org/packages/0a/20/e022dbc6b4753a9bc9fc5fb28a27163430c1731b9913997f6544c1b2518c/lxml-6.1.3-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:909f4e927bb051f7740d6367285fc60cdcfdaf0258c2dba4ff5ba7eadadc250c", size = 5244675, upload-time = "2026-09-02T14:47:47.635Z" }, - { url = "https://files.pythonhosted.org/packages/99/83/82cde81d2b5eb38d1539fdfdf318abdd014a7e604f4df01c9cd3deb18f2a/lxml-6.1.3-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:a5c18810318303ce9afb3f95e2ddb54834f96fa699a8600433fd5a93dcf44c56", size = 5358205, upload-time = "2026-09-02T14:47:50.306Z" }, - { url = "https://files.pythonhosted.org/packages/d2/a1/f3b057371c8cb29f2a9c9c44ea320592446e40b74a4b0af68c3d8e65bc73/lxml-6.1.3-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:3e42265103fb385d8642a78672edf376c6f7e1d3598a7a4f9cb1278f2f6b5f6f", size = 4704495, upload-time = "2026-09-02T14:47:53.251Z" }, - { url = "https://files.pythonhosted.org/packages/1a/a4/230eb28be5d412152ffc3c679b51fe1aeede5a53f3a8eb6e9748f2f4754f/lxml-6.1.3-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:21402998e4b78e7cce237d2788841aaa21ac9a4d1574d04dc2d12ee41ae807b5", size = 5255117, upload-time = "2026-09-02T14:47:55.963Z" }, - { url = "https://files.pythonhosted.org/packages/a3/18/1969f56763af24ce42ea156007b0b2d73fddea552e283b2010416394f0f4/lxml-6.1.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:38fc4e4e4e084e0bd491949482527d406788045c546d4f8789e93fc527b91385", size = 5054424, upload-time = "2026-09-02T14:47:58.131Z" }, - { url = "https://files.pythonhosted.org/packages/f4/d4/2a90acc1f6fabaa3a8db9340437822bd8d041b205d626a4b3e8621aaa390/lxml-6.1.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5609efdb0d3c95499c00046bc53648b3482ec2175b5503d6e611b3f0555dc71d", size = 4785572, upload-time = "2026-09-02T14:48:01.029Z" }, - { url = "https://files.pythonhosted.org/packages/a5/1e/b90e845b1dcd0f2f3f26b98283d857f25909223aacd265eee032c34ab8b1/lxml-6.1.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:97ce49699d87ebf8aad631b55d65b33219a4f1bfefbbf5bff19dc9af160aeaf9", size = 5656516, upload-time = "2026-09-02T14:48:03.419Z" }, - { url = "https://files.pythonhosted.org/packages/eb/ab/0a1b802c57f3fba5c4efd77d5c6b78adaa8f7b681f0c90456b140fe8bf6c/lxml-6.1.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:48542c9acba9ff9450bd18d871d2c2c8787fdb283572b623d206f1b927cd7d9e", size = 5245982, upload-time = "2026-09-02T14:48:06.109Z" }, - { url = "https://files.pythonhosted.org/packages/da/ee/2c016fbceb3778137459292538d9dfa7e3ad9070fe409c15254ddd90d2cc/lxml-6.1.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c55e71a9b1db1f107efb60da49c093689b74c5c31a708e5379e2fd9439d4fbb5", size = 5267340, upload-time = "2026-09-02T14:48:08.374Z" }, - { url = "https://files.pythonhosted.org/packages/9c/b1/736d18fd6f0835761923b7bac1f0c27d60c1200384e9093f05d8c5100525/lxml-6.1.3-cp312-cp312-win32.whl", hash = "sha256:b3ff39654f0ce6ebd4db154211136dbe7e8157bcc3bed2344c87f32c7c6ecb6c", size = 3602606, upload-time = "2026-09-02T14:48:10.384Z" }, - { url = "https://files.pythonhosted.org/packages/3a/5b/6ed903e4e6278a020c8a6f0dbbe78030d041840a6b4a64ea441a1e414077/lxml-6.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:3e9a00d1c2c30936f7add097c41afc5da6556c580909104aafd382cac92a855c", size = 4005999, upload-time = "2026-09-02T14:48:12.51Z" }, - { url = "https://files.pythonhosted.org/packages/e4/1b/7bcebb7b6332cb3ae85e9c13b139adb6f23f75c71d84041c56a5005d9a29/lxml-6.1.3-cp312-cp312-win_arm64.whl", hash = "sha256:1aeca87830c4fe649dcf93fe2b059525b71c72587f21be4ae4af7103082a79fa", size = 3666631, upload-time = "2026-09-02T14:48:14.567Z" }, - { url = "https://files.pythonhosted.org/packages/52/05/3ef45db776baea068044c799bbba68f3ca00a440c0e930a17c572f3d9639/lxml-6.1.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:3a48093cdb058a93af842ede9703520e810b05dcd0fc6d7190a06376c3bfb6bd", size = 8590357, upload-time = "2026-09-02T14:48:17.413Z" }, - { url = "https://files.pythonhosted.org/packages/8c/a5/eee2fc77eee5ea68e4a4334b1def1781a3beaeefd3d98e81b4a38dc447b7/lxml-6.1.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:887c021d9a977cff89cb273047c1352997b772a8908a25c21836861f69b92be1", size = 4632616, upload-time = "2026-09-02T14:48:20.745Z" }, - { url = "https://files.pythonhosted.org/packages/35/42/df27b56848acd29d8a720acc28977911aab36f2a09df4208d5502e887415/lxml-6.1.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:611a51e61c92f62345a50b0035df6fc0d678f9299f33728826d831598862f59d", size = 4936186, upload-time = "2026-09-02T14:48:22.94Z" }, - { url = "https://files.pythonhosted.org/packages/ab/8d/8a7b91df0b54d09d25f5f44885d6b3e0a6d6643a8c070191580318d20c42/lxml-6.1.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b477912f42c5c33405a10c759d22f80cf5af043ae02d95b9d8e5e5bc555739ed", size = 5093324, upload-time = "2026-09-02T14:48:25.132Z" }, - { url = "https://files.pythonhosted.org/packages/c6/7e/8f340ddcd43790332fb0de8a26628d571a492da3300cd191821698407c96/lxml-6.1.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cffe18571ccc51d742cd08cbb3f8b756de9311d18c7ea98f5d92f37b8fb60c2", size = 4998850, upload-time = "2026-09-02T14:48:27.394Z" }, - { url = "https://files.pythonhosted.org/packages/c5/c1/9c5bb572f1f09ec9e4322bd4a4e9f4ad48347fc56ef94cf4df58a5279dc8/lxml-6.1.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:75cc6569e86be5785b6188ef1642670c6adbc984e81ec35e224842ecd9eefcc8", size = 5626813, upload-time = "2026-09-02T14:48:29.61Z" }, - { url = "https://files.pythonhosted.org/packages/ac/7d/8bf1fd8bae8247743968bb76d027a1ac5bd2c4b44495fba6a71b30d10706/lxml-6.1.3-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d85dfab42dd672f87a7f76e9de7172962aee69fa12044f0d6e1a23cbd53fb80e", size = 5232385, upload-time = "2026-09-02T14:48:31.969Z" }, - { url = "https://files.pythonhosted.org/packages/7b/2e/6cef69ed81cb7df0d03b0dd09d08e6e2cf5061a743ff6f42f0b741548e9b/lxml-6.1.3-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:42632b4024ab24a6b488f559ac851312509888b6b80ae2aa11cf29a646a0d245", size = 5347088, upload-time = "2026-09-02T14:48:34.13Z" }, - { url = "https://files.pythonhosted.org/packages/5f/e1/8e5fd8ddc8c7d685badb0f2db149e3c9da84eefc2827c01c658df2c4e3cb/lxml-6.1.3-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:febd35ef45f603c2d74b74655efdbf45e14f55fc0aef4ac82b663ca829b283e0", size = 4707227, upload-time = "2026-09-02T14:48:36.62Z" }, - { url = "https://files.pythonhosted.org/packages/7a/7e/00041382a11be40a88bf405ebff11c8efabd3de79f2691e1638b1c47a8a0/lxml-6.1.3-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a43b3bdf11e477dc7770609d3477316f974354dfc8425d596f64f471cc8daf6e", size = 5240208, upload-time = "2026-09-02T14:48:38.893Z" }, - { url = "https://files.pythonhosted.org/packages/fd/fe/316538b5cff0936fa63d45d421c655730fcbb5a28dcac728c175083002bc/lxml-6.1.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d582042c69857c364e8153de6e18e0da9b7b515a6a8113caf69a6ec8e0520f2", size = 5050271, upload-time = "2026-09-02T14:48:41.213Z" }, - { url = "https://files.pythonhosted.org/packages/c9/91/455bcccb3ac725373007344d351151810cd19762d1673b64b811f4359a42/lxml-6.1.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:8e49a646acfab83c68974f4aa1d0a2acca9e88d7d627ae0fc13201b14b76d310", size = 4780433, upload-time = "2026-09-02T14:48:43.779Z" }, - { url = "https://files.pythonhosted.org/packages/cb/f6/580440e2f52cf00bba5c5e1080bfa88cdfcde73be71a11d95170ddbb663f/lxml-6.1.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0dee106e9aa97fb00541b1ed7827070564d0549c3d3fba8920e6b20fd980f748", size = 5645928, upload-time = "2026-09-02T14:48:46.187Z" }, - { url = "https://files.pythonhosted.org/packages/f6/dc/d123c1f244306543d545f62443f794959e4f1ea709fe100f8740d514e74a/lxml-6.1.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:dd5e90f34cffcfed97f36cf066325773d2b6021c60c29942e53a18b028501b1d", size = 5231184, upload-time = "2026-09-02T14:48:48.691Z" }, - { url = "https://files.pythonhosted.org/packages/c3/3c/fe55b2bd5c6113c906511cd88f6a470195c5fbff1124f19970ab706c3477/lxml-6.1.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d9b3e7d71bf6acff341233417abbdface29c647e3113892d9aaedc02eb4aa2bc", size = 5255814, upload-time = "2026-09-02T14:48:50.948Z" }, - { url = "https://files.pythonhosted.org/packages/e7/a7/485df55acf55dc35e4ca89d2f48f03889e5a3241826b18b85102b32ce9d8/lxml-6.1.3-cp313-cp313-win32.whl", hash = "sha256:160fcf381f76c3aeac28a756bec44f48942a8f7245a87aa28e3a523b4d90cd87", size = 3602214, upload-time = "2026-09-02T14:48:53.236Z" }, - { url = "https://files.pythonhosted.org/packages/c0/28/e46a7702bd95e9043291f7c3539b6184cba66f96cea9936f20939b284eeb/lxml-6.1.3-cp313-cp313-win_amd64.whl", hash = "sha256:e477aca0bc0d19f3b4ae9e4f2a1cfd687c31bf772d78734910658186b40b2477", size = 4004091, upload-time = "2026-09-02T14:48:55.699Z" }, - { url = "https://files.pythonhosted.org/packages/8a/1d/154c78e20479a43916e63f19cb720d83f44f024b03228be44c92d9a97b24/lxml-6.1.3-cp313-cp313-win_arm64.whl", hash = "sha256:b1cc980905221a5d8b3c476330730b3adb40ff80add71ffbdb6215ba055656f1", size = 3665468, upload-time = "2026-09-02T14:48:57.703Z" }, -] - -[[package]] -name = "markdown-it-py" -version = "3.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mdurl" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, -] - -[[package]] -name = "markupsafe" -version = "3.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, - { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, - { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, - { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, - { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, - { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, - { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, - { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, - { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, - { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, - { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, - { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, - { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, - { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, - { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, - { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, - { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, - { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, - { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, - { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, - { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, - { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, - { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, - { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, - { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, - { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, - { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, - { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, - { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, - { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, - { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, - { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, - { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, -] - -[[package]] -name = "mashumaro" -version = "3.22" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d1/e3/a06dcd2e6df094c5e294721926f1f76da30f50823e2ac233a9198e804891/mashumaro-3.22.tar.gz", hash = "sha256:64538cc365204402a060ebde683a86505b5a4344acf6870d79021e9fbfe57360", size = 197845, upload-time = "2026-05-26T14:39:21.859Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/1c/92fd926c2e7763535454683250dbdd8d10aa2f2c62f58d6abbcce4d8b3fc/mashumaro-3.22-py3-none-any.whl", hash = "sha256:17dc4d7294c33ef380a8b929dda0608577aa2141988c00a0c4932310108fe71d", size = 95916, upload-time = "2026-05-26T14:39:20.233Z" }, -] - -[[package]] -name = "matplotlib" -version = "3.11.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "contourpy" }, - { name = "cycler" }, - { name = "fonttools" }, - { name = "kiwisolver" }, - { name = "numpy" }, - { name = "packaging" }, - { name = "pillow" }, - { name = "pyparsing" }, - { name = "python-dateutil" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/49/64/f9a391af28f518b11ad45a8a712353c94a0aefce09d3703200e5c54b610a/matplotlib-3.11.1.tar.gz", hash = "sha256:69647db5746941c793d6e445a4cd349323ffb87d9cc958c2ad84a659b4832d30", size = 32612045, upload-time = "2026-07-18T03:39:46.63Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/6c/7ef7ebcb2bd9739b2b66b18b076e077f44bb46fdbe28ca0506edb3c62c79/matplotlib-3.11.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e15ef41507f3d525f46154ac9e3ae785dacde9f20e593a25de8986267892ef74", size = 9453849, upload-time = "2026-07-18T03:38:19.593Z" }, - { url = "https://files.pythonhosted.org/packages/eb/f8/6d0c312c8d9738e7d9677f09fe5c986b3239e651a7b73a2deb38b65e4a71/matplotlib-3.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:21a67b961a6d597bca54fae826cd20695ba4a6e4d05424a08da6e13e3176fd6b", size = 9283113, upload-time = "2026-07-18T03:38:21.95Z" }, - { url = "https://files.pythonhosted.org/packages/c9/cf/b4ad2cc81b6672ea29ea04e64e350a9f9b493b0908ccd884c67eeff8f7b2/matplotlib-3.11.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ba8f811b8ddfac493734d6af0b2dff96919d0c28ca0d641858dab4262777c6ea", size = 10035615, upload-time = "2026-07-18T03:38:24.315Z" }, - { url = "https://files.pythonhosted.org/packages/88/90/4e10e033d9b66589d8ed98b84c95cdbb57033d57c1f41339d7393dbd2f2e/matplotlib-3.11.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c52f7ad20ef476806ed212380b1d54d20310c8b86bdc2c9a68b51f0024a44472", size = 10842559, upload-time = "2026-07-18T03:38:26.285Z" }, - { url = "https://files.pythonhosted.org/packages/88/eb/799612d0f8cd3e816a10fec59329fca52cd2353264df80378dfc541ae855/matplotlib-3.11.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8b14eb22961fe865efb0e4ff167e333e428908b00115a8d800ccb65ee108e481", size = 10927532, upload-time = "2026-07-18T03:38:28.532Z" }, - { url = "https://files.pythonhosted.org/packages/88/89/56649bbaa2fd12e20f3be03dbcc135b0c8676d88bac17977599e3eb442a0/matplotlib-3.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:88a2a27dd9691ae448dfae4b26f59036be90c3c28757edd3553a29559d00859f", size = 9333886, upload-time = "2026-07-18T03:38:30.477Z" }, - { url = "https://files.pythonhosted.org/packages/c1/11/4d124efbbad677b7b7552f6f85a3bd432d4232f95400cea98fcd2ae36ef3/matplotlib-3.11.1-cp312-cp312-win_arm64.whl", hash = "sha256:480194afceca4df2f137c2721227d3cba67121fbf4397b69cee7f83714b0a58a", size = 9007545, upload-time = "2026-07-18T03:38:32.833Z" }, - { url = "https://files.pythonhosted.org/packages/04/6c/4798363b7fb5644e309fe1fac30216e9146c9f70859d80d588c18caf5317/matplotlib-3.11.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6771b0cd7838c6a857a7209814158c0ad09bfef878db3033dd82d70ad101f191", size = 9454341, upload-time = "2026-07-18T03:38:35.001Z" }, - { url = "https://files.pythonhosted.org/packages/59/98/6acadbe7f98df19d274bc107ac58bb439fa75df82c33dc110d71a4a8501f/matplotlib-3.11.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2abdee5ffa2fe11b2d19f7a5c63b785fb7c28cc46c7bc1814156341d9d1a33e1", size = 9283627, upload-time = "2026-07-18T03:38:37.061Z" }, - { url = "https://files.pythonhosted.org/packages/24/ea/65cec46fe241390ccea1b1754207ee28eb71c5ab866bd5f22fe47e538fa4/matplotlib-3.11.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b0a19dcf73406d3746d25a5ed42d713604c9a3e024d129b102852b0d941cb9f3", size = 10035860, upload-time = "2026-07-18T03:38:39.663Z" }, - { url = "https://files.pythonhosted.org/packages/c7/10/63fdccccbabe002fb0960876baabc5e3f24d9c1bb4cfb25651457f74b3a0/matplotlib-3.11.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7389b77ed2ab0552f46d9a90b81b7b8e6dfcdc42adc36c37a0865799843e0e3e", size = 10843594, upload-time = "2026-07-18T03:38:42.144Z" }, - { url = "https://files.pythonhosted.org/packages/98/51/a1155945bff7b91381875022ac1522c5dfdac0d006be8e7df389b3134eae/matplotlib-3.11.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c90be0b73568da4f662afac580956a76e308437e641b4a45aa08925eeb67d95f", size = 10927962, upload-time = "2026-07-18T03:38:44.302Z" }, - { url = "https://files.pythonhosted.org/packages/0d/3a/3d5e1f42dc761bf53401a62a83ff93389b37de9d2c093b2a3aa49ac34f1b/matplotlib-3.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:68408341f2312836fbbdf6b3c78047f65b2d8752f5fd221c3e72d348f5b34f8b", size = 9334074, upload-time = "2026-07-18T03:38:46.616Z" }, - { url = "https://files.pythonhosted.org/packages/e2/db/3f5ea5a5b64060ef5e1ff60a19170423e41ce21b8497a6fe15a36e0b43e3/matplotlib-3.11.1-cp313-cp313-win_arm64.whl", hash = "sha256:0c1f44890d435c1b4ef52f701ad5828cb450ea97bcc83918fda6be74965d6cd2", size = 9007662, upload-time = "2026-07-18T03:38:49.112Z" }, - { url = "https://files.pythonhosted.org/packages/98/6e/c7ae5e0531425b69c0826b00ebbc264c85cab853f1cd6e096c9983c2cdc1/matplotlib-3.11.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:5e510088c27a89d53580a752f959146893563e63c330e161d159b0fee652af6f", size = 9503790, upload-time = "2026-07-18T03:38:51.527Z" }, - { url = "https://files.pythonhosted.org/packages/92/79/15be162e0a2ed546939674e2e97d0e33ec2447d86d4d4e611fa295bb178c/matplotlib-3.11.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:1524e2bdd48a93557aa47ddcfe9c225dfdd57d5a01a5c49128c20f0632980ee1", size = 9336148, upload-time = "2026-07-18T03:38:53.564Z" }, - { url = "https://files.pythonhosted.org/packages/6a/7f/36ffe144fc4aacfe0e3ed2318f72b6755d1e73b041d619b4d393e60f5a66/matplotlib-3.11.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:11664c551345553db92e61cae6cf1376f138f8c47cafdf13b64b18f3e3e9e464", size = 10049244, upload-time = "2026-07-18T03:38:55.911Z" }, - { url = "https://files.pythonhosted.org/packages/ab/5f/55812d68c0a840d3a463638f48c00ab1fe338518ec49a640cb6473b444af/matplotlib-3.11.1-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e1f8922ba31959cf6a9dfb51be64b7f7bc582801a3957dc0c2f3afcd3537adf", size = 10860798, upload-time = "2026-07-18T03:38:58.282Z" }, - { url = "https://files.pythonhosted.org/packages/7a/64/cca444b4eb5e6c768c44fc5e1f0b5211f20ca2b282778051996e996a2bdf/matplotlib-3.11.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83235693abde86e5e0129998f80ee39fc7f58e6d56a88fafb28a9278833e9d5f", size = 10943282, upload-time = "2026-07-18T03:39:00.465Z" }, - { url = "https://files.pythonhosted.org/packages/e5/0f/a49c329d394f2e9ef38506982107e8b04ecf94dd41a9d8423ff82cc737c7/matplotlib-3.11.1-cp313-cp313t-win_amd64.whl", hash = "sha256:9a076f4fc5cdc43fdf510f5981418d25c2db4973418d9f22d8bb3dc8045ada78", size = 9383532, upload-time = "2026-07-18T03:39:02.468Z" }, - { url = "https://files.pythonhosted.org/packages/e4/50/103e86afb806d8f64d04ede14e4cfc09dbfc25f512421ff85fdd6ebd59cf/matplotlib-3.11.1-cp313-cp313t-win_arm64.whl", hash = "sha256:216fbb93a74add02ddb4cb38ef5348f59ac00b3e84567eaf16598772d40e150a", size = 9059665, upload-time = "2026-07-18T03:39:04.607Z" }, -] - -[[package]] -name = "mdit-py-plugins" -version = "0.6.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markdown-it-py" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/59/fc/f8d0863f8862f25602c0404d75568e89fb6b4109804645e5cdfb1be5cf56/mdit_py_plugins-0.6.1.tar.gz", hash = "sha256:a2bca0f039f39dbd35fb74ae1b5f998608c437463371f0ff7f49a19a17a114d0", size = 56114, upload-time = "2026-05-13T09:03:38.91Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/69/6da5581c6a7fede7dc261bf4e67d6adca4196f176b43288b55b3db395b6e/mdit_py_plugins-0.6.1-py3-none-any.whl", hash = "sha256:214c82fb2ac524472ab6a5bcab1de80f73b50443e187f401bfd77efbc7c6481d", size = 66663, upload-time = "2026-05-13T09:03:37.76Z" }, -] - -[[package]] -name = "mdurl" -version = "0.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, -] - -[[package]] -name = "minijinja" -version = "2.24.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/18/2dbb84297175b77b5eb497e07e66e71c71b8073bdbe3d3cb1d3309823d66/minijinja-2.24.0.tar.gz", hash = "sha256:8e9dc11bd5c7a20869c42d6c7c0caa5cd437c62a00a042f65df20036f0def93c", size = 299288, upload-time = "2026-08-31T19:28:46.351Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/16/9170db3a85bf7fed14c5281e13927d310385e5fd945518dda80e2ec0a631/minijinja-2.24.0-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:f286106f9d2ffd73702a470cc4d9262b1588eb48916fc84b98d19df4e3064bd4", size = 2174596, upload-time = "2026-08-31T19:28:25.642Z" }, - { url = "https://files.pythonhosted.org/packages/82/ec/9a1e7513d7e18ff11907eb6ae92f060f2abd85e0df029625816b8f469eb4/minijinja-2.24.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e252fa4c59cff1794dfffb5349da6a4885de97aaaf2fbc6983becc1eb973e9", size = 1140515, upload-time = "2026-08-31T19:28:27.648Z" }, - { url = "https://files.pythonhosted.org/packages/66/0c/835223a549b924b1bf9a7a2e6723783342a05896c90cf49a10bbef72a253/minijinja-2.24.0-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:94964dca64c80b2a85b598037ab07f01a794d238c66d5556d01c4f400b630fd0", size = 1115064, upload-time = "2026-08-31T19:28:29.634Z" }, - { url = "https://files.pythonhosted.org/packages/ad/ed/14056592c0008a0945e96966c5e2b215579d6f3487032d7ba2d8ad2e9c12/minijinja-2.24.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5a4dbd73f2c02ab6d0ab0b62b68a7d2557e5d45723d3b4e423b7c3877f31cad", size = 1178991, upload-time = "2026-08-31T19:28:31.538Z" }, - { url = "https://files.pythonhosted.org/packages/4b/6b/71ca5f679c03a67af5d551b32e306c6bb87aa7157767c3a7a678323d748f/minijinja-2.24.0-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43d633999cb06509ba3de9a0e5798f97d5ba5bc0d337f9d8f8ea02f349050cbd", size = 1312790, upload-time = "2026-08-31T19:28:33.473Z" }, - { url = "https://files.pythonhosted.org/packages/56/13/3c7c6f34c41eece458f4f416dbab229024502173de780ee978ef38a4c605/minijinja-2.24.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:90cc9e1196d98e56fdc87b69a5949bc76ecbbc512c7654081483c5acabf8ca8f", size = 1308284, upload-time = "2026-08-31T19:28:36.272Z" }, - { url = "https://files.pythonhosted.org/packages/2e/c9/bbcf76a1bd02a503c2d05f996d6271358c77bab68893689d3bd14514fdab/minijinja-2.24.0-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:194364c0a060606e96ae635cd997c7e014b4ef026d12c02fe9f8912749622481", size = 1381830, upload-time = "2026-08-31T19:28:37.994Z" }, - { url = "https://files.pythonhosted.org/packages/20/91/980d6b2572a0985ec033c1b93bb8eea6be87f920a7cab2097ef740894c61/minijinja-2.24.0-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:6c2106a399eaff009ebf308ccebed397787ff4442159de9598543298f3eee522", size = 1463332, upload-time = "2026-08-31T19:28:39.749Z" }, - { url = "https://files.pythonhosted.org/packages/92/98/75d6a76499fd40f839a0a1a8fc46aa43cf6c47ea9680d4174289fd6fec3a/minijinja-2.24.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5e0cdc27520d2163ec8a2078e4ff20c3afff4fb89eb05d21679c3facd30369ab", size = 1388664, upload-time = "2026-08-31T19:28:41.421Z" }, - { url = "https://files.pythonhosted.org/packages/7d/f1/e8d82609d7aad5791d52fdec4c642fab46974094cef9102110efa6fc2713/minijinja-2.24.0-cp38-abi3-win32.whl", hash = "sha256:b68aa1bd72da3a6175afa721090592f19e637b7a02408f796298d342b709d06b", size = 1058094, upload-time = "2026-08-31T19:28:43.157Z" }, - { url = "https://files.pythonhosted.org/packages/5b/e5/d86c8c706b3820fc6f58baa0f7d322e78885843ddba7e9cba77985a3fbc7/minijinja-2.24.0-cp38-abi3-win_amd64.whl", hash = "sha256:541959f4529017e437b8822cdaabaaab9d4090c140138dec40dbd739b3b0f778", size = 1118117, upload-time = "2026-08-31T19:28:44.843Z" }, -] - -[[package]] -name = "mlx-traceability" -version = "11.8.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "docutils" }, - { name = "matplotlib" }, - { name = "natsort" }, - { name = "python-decouple" }, - { name = "requests" }, - { name = "sphinx" }, - { name = "sphinxcontrib-jquery" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cb/ea/110e5ef3b5656b1ebf7e74cee598e3270713a7e72db734b5a4d1f6b89c51/mlx_traceability-11.8.3.tar.gz", hash = "sha256:2ef56e6f2d4d1d32592f746b9d5bd194abe7610fd55ae55258d223bac108b5ab", size = 70594, upload-time = "2026-02-24T14:07:44.631Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/70/2dd58eefbfbeb578a33045aed7541bd15308f215925e9aecbe9b84a447a7/mlx_traceability-11.8.3-py3-none-any.whl", hash = "sha256:49507b2301e18b037037b7e893dc7a457cccf81e8fca656e482b6320933568e2", size = 81101, upload-time = "2026-02-24T14:07:43.359Z" }, -] - -[[package]] -name = "multivolumefile" -version = "0.2.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/50/f0/a7786212b5a4cb9ba05ae84a2bbd11d1d0279523aea0424b6d981d652a14/multivolumefile-0.2.3.tar.gz", hash = "sha256:a0648d0aafbc96e59198d5c17e9acad7eb531abea51035d08ce8060dcad709d6", size = 77984, upload-time = "2021-04-29T12:18:39.882Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/22/31/ec5f46fd4c83185b806aa9c736e228cb780f13990a9cf4da0beb70025fcc/multivolumefile-0.2.3-py3-none-any.whl", hash = "sha256:237f4353b60af1703087cf7725755a1f6fcaeeea48421e1896940cd1c920d678", size = 17037, upload-time = "2021-04-29T12:18:38.886Z" }, -] - -[[package]] -name = "myst-parser" -version = "4.0.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "docutils" }, - { name = "jinja2" }, - { name = "markdown-it-py" }, - { name = "mdit-py-plugins" }, - { name = "pyyaml" }, - { name = "sphinx" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/66/a5/9626ba4f73555b3735ad86247a8077d4603aa8628537687c839ab08bfe44/myst_parser-4.0.1.tar.gz", hash = "sha256:5cfea715e4f3574138aecbf7d54132296bfd72bb614d31168f48c477a830a7c4", size = 93985, upload-time = "2025-02-12T10:53:03.833Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/df/76d0321c3797b54b60fef9ec3bd6f4cfd124b9e422182156a1dd418722cf/myst_parser-4.0.1-py3-none-any.whl", hash = "sha256:9134e88959ec3b5780aedf8a99680ea242869d012e8821db3126d427edc9c95d", size = 84579, upload-time = "2025-02-12T10:53:02.078Z" }, -] - -[[package]] -name = "natsort" -version = "8.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e2/a9/a0c57aee75f77794adaf35322f8b6404cbd0f89ad45c87197a937764b7d0/natsort-8.4.0.tar.gz", hash = "sha256:45312c4a0e5507593da193dedd04abb1469253b601ecaf63445ad80f0a1ea581", size = 76575, upload-time = "2023-06-20T04:17:19.925Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/82/7a9d0550484a62c6da82858ee9419f3dd1ccc9aa1c26a1e43da3ecd20b0d/natsort-8.4.0-py3-none-any.whl", hash = "sha256:4732914fb471f56b5cce04d7bae6f164a592c7712e1c85f9ef585e197299521c", size = 38268, upload-time = "2023-06-20T04:17:17.522Z" }, -] - -[[package]] -name = "numpy" -version = "2.5.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/13/01/11703282db468b85f6f7b8c7f22d058de5970d5c7e60a3a8aaa313c3de36/numpy-2.5.3.tar.gz", hash = "sha256:df2d5874ff183595a4ba404edd04f6bd9b5505c1d7708573f6a6c17489a67563", size = 20791231, upload-time = "2026-09-06T16:27:47.073Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d6/50/8fdbb16af64895706a45f06a4068e29db732ec180f3c1375f14123359138/numpy-2.5.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cb189f09db39283b26bfd061ec16189e14f71c6755207f72a0f7540867afe5b9", size = 16994982, upload-time = "2026-09-06T16:24:29.244Z" }, - { url = "https://files.pythonhosted.org/packages/60/39/789131c1188c078dcb3a1692e72e1e050c68b88ffe72c9ccaac9bcd7a9cd/numpy-2.5.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f59a878c33d6b88122d80d239bb3b845d58708750b0cb06a09aebb9b18ec696c", size = 12009327, upload-time = "2026-09-06T16:24:32.491Z" }, - { url = "https://files.pythonhosted.org/packages/9c/59/a312e95696e5f601914dd8b6dd844692ba61670807417e24b68e337b5c70/numpy-2.5.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:a72f874bc9e10e4b8f80426fb49716d5141f64442a0c8418065093ec8017fbb0", size = 5445405, upload-time = "2026-09-06T16:24:35.071Z" }, - { url = "https://files.pythonhosted.org/packages/30/d0/5623a1707ed4fe16e3909fe3cf5ee3da004ae677ad23d83bbf3adf1a6faf/numpy-2.5.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:fc36dc566135b5eceec4cf89758fcb719266a019ef07dae1754ae7c9f617ef3e", size = 6783213, upload-time = "2026-09-06T16:24:37.253Z" }, - { url = "https://files.pythonhosted.org/packages/f1/32/84146fc020ad3c25f805f70ab60da46fe3c540a21369754a7e4369754b6f/numpy-2.5.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:76c2c1e6bfa5c84adc6434dfbf013aa92096a7985221762c8f11fedfd20fff58", size = 15687872, upload-time = "2026-09-06T16:24:39.751Z" }, - { url = "https://files.pythonhosted.org/packages/65/af/aa78d1a88805456e212b65461354cd943197fb9acecc4c90fd12295123a3/numpy-2.5.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7e18c623bb5c95acb3b3328861272816ba199fb531921c5d6d0b675f1fde9e3", size = 16717410, upload-time = "2026-09-06T16:24:42.745Z" }, - { url = "https://files.pythonhosted.org/packages/3b/24/faa79d865e69a97ba17473b23a1b74094b2259c03e820c70297293b9ea49/numpy-2.5.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4f8929ee6c96bfbd7b4ed2032e0c03af86fe1826740ab61ddabf9072d06e57ff", size = 17040975, upload-time = "2026-09-06T16:24:45.961Z" }, - { url = "https://files.pythonhosted.org/packages/62/4a/8877e629445a7176297dffcaf9c485faa96a95d81728a62521ad55bd4c0f/numpy-2.5.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b5d93cf48f687479941d12b69c873ad2cc76bbd487f0091c2200636497f34034", size = 18476479, upload-time = "2026-09-06T16:24:49.35Z" }, - { url = "https://files.pythonhosted.org/packages/c8/db/35e1c2d38b04cbd5b731f9d71495e055e813197669d22b612f11748d2ff9/numpy-2.5.3-cp312-cp312-win32.whl", hash = "sha256:bf63afbe037eb5d2fe87fbcc7778e61da53ebaf21d938a4515aa73b62532a5d4", size = 6133378, upload-time = "2026-09-06T16:24:51.915Z" }, - { url = "https://files.pythonhosted.org/packages/3c/a1/accf6d4f0c80c5d9ba9735d6b1550e444180599f34dec69ca01360f717ad/numpy-2.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:0a59a421a32580a009e8a1751345bf829631b990dc1794b80514ab722b435def", size = 12567828, upload-time = "2026-09-06T16:24:54.255Z" }, - { url = "https://files.pythonhosted.org/packages/22/43/1764aff32e4652526ae2f71fa8b3efd8d25c8a3d6926914454e47138ed1e/numpy-2.5.3-cp312-cp312-win_arm64.whl", hash = "sha256:ccb32e0525d29e8b0572eb84c9a57af0e7a4e615726927506f55063c62414034", size = 10485432, upload-time = "2026-09-06T16:24:57.278Z" }, - { url = "https://files.pythonhosted.org/packages/79/e5/8fb89cd46d14e35699d13bf943a5f5f441ecee8667120a1f6105ab89e349/numpy-2.5.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:66a78fe4556c60aceda5916f9eacd638b18e9e681016ec302dcb4682d6d4d034", size = 16991061, upload-time = "2026-09-06T16:25:00.411Z" }, - { url = "https://files.pythonhosted.org/packages/2f/06/9dc9e48b5e5e941c8b10350c5ff2d721da42a20517d911d15544246775ff/numpy-2.5.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92f30e89b8ee0ecf363033576c422b2f58fed6a80bed0aa48dff6d14c654663e", size = 12003676, upload-time = "2026-09-06T16:25:03.475Z" }, - { url = "https://files.pythonhosted.org/packages/ab/2a/98282aa5b8f58b1157d440bb6282eed47e3632a5de53a714fbab17e659fe/numpy-2.5.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f9a2353b37a1a9e78fd82b27ad7e2a32a2d036604d18f02b05e3136c62ca3b09", size = 5439695, upload-time = "2026-09-06T16:25:05.978Z" }, - { url = "https://files.pythonhosted.org/packages/a1/f9/b6533d777be9d6ffd29dc1be0867e563e6e8cc9a220ff1b716adc317f060/numpy-2.5.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:ccbc4665079665c3cf3bab4db9f6b095370cd6437d66be549b6c2a1fd19e1958", size = 6779395, upload-time = "2026-09-06T16:25:08.599Z" }, - { url = "https://files.pythonhosted.org/packages/73/85/735720d04ec197c5dcfacdfc9922667c7f1f5f496a279b7ba4d7c74c4cc7/numpy-2.5.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c76d5dde9f445058f83d0c02af00557a4db91de9a9a57c0df87d1535001d654b", size = 15681750, upload-time = "2026-09-06T16:25:11.173Z" }, - { url = "https://files.pythonhosted.org/packages/3a/1b/3b16a9bc514a440a7a0883684111dcb1ef1aee960af2ca95da8fc775f124/numpy-2.5.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5fa86b80fd24bcd1aff83ad23be44ea323de3f787be8f8b15d4a65621e25321", size = 16708577, upload-time = "2026-09-06T16:25:14.171Z" }, - { url = "https://files.pythonhosted.org/packages/69/c4/386f397831b07328b639c96c5b62719346cf4baf07c68d927239752b1534/numpy-2.5.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd4cb9ad3c7889b9b3fe0a9a9fb5d2ed26f9879bff2608d9f01aed147a20d231", size = 17042047, upload-time = "2026-09-06T16:25:17.582Z" }, - { url = "https://files.pythonhosted.org/packages/5f/3e/a700ecbf36e85ae8328fd3b0e12eeddc22ed6358a64cb2bd913e0d195d65/numpy-2.5.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1302b90c0e52281681b2975adfe8a860cb7b12216a27b4b0b4207c44bf7bccf0", size = 18465724, upload-time = "2026-09-06T16:25:20.949Z" }, - { url = "https://files.pythonhosted.org/packages/41/ee/38e785e88a4045f6ad1d1f2808dcdfafdca48c760260c0587bf171e29fc9/numpy-2.5.3-cp313-cp313-win32.whl", hash = "sha256:1c80eabb4035ecf4ca9cd49cde8a9fdd69a729e63e6474887d1523ade7aa277f", size = 6129003, upload-time = "2026-09-06T16:25:23.664Z" }, - { url = "https://files.pythonhosted.org/packages/f3/ec/100f2b1794ede74a9b3d7ec6b9736927f56713414c1dfe19ab6c383494bf/numpy-2.5.3-cp313-cp313-win_amd64.whl", hash = "sha256:71cad2b2a7451ab79d8f5e71b453485b6775963d5cf794179144a7463fe6e8ec", size = 12560965, upload-time = "2026-09-06T16:25:26.602Z" }, - { url = "https://files.pythonhosted.org/packages/80/b1/7dc825ca94c12acebbce4c37caa5e198695eb31424bc579679f32b1bb49d/numpy-2.5.3-cp313-cp313-win_arm64.whl", hash = "sha256:8e4dd766076855b5ff7ea52fa5f07ce26286726e0f8bff446b7739d02e6ea204", size = 10482343, upload-time = "2026-09-06T16:25:29.772Z" }, -] - -[[package]] -name = "object-deps-report" -version = "0.0.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "clanguru" }, - { name = "py-app-dev" }, - { name = "typer" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1f/de/cf9b2fc0a535aa3cacc3bd21bd5d93da96edd4f0211f34d201ad6236097d/object_deps_report-0.0.1.tar.gz", hash = "sha256:e97d349a385081503fd6254ed55c833e7f5677ecf923b5a53e0a69710a4c083e", size = 7457, upload-time = "2026-08-03T12:17:30.967Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/c6/fe5277fd033af3bd5d37c40a179703f440c0d33132aaf464fc5370d68c03/object_deps_report-0.0.1-py3-none-any.whl", hash = "sha256:45c559bd01432e54e8121c6eb227ab755e4b046af71e3ee8077ae3412f00bbe2", size = 9480, upload-time = "2026-08-03T12:17:29.871Z" }, -] - -[[package]] -name = "openpyxl" -version = "3.1.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "et-xmlfile" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3d/f9/88d94a75de065ea32619465d2f77b29a0469500e99012523b91cc4141cd1/openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050", size = 186464, upload-time = "2024-06-28T14:03:44.161Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/da/977ded879c29cbd04de313843e76868e6e13408a94ed6b987245dc7c8506/openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", size = 250910, upload-time = "2024-06-28T14:03:41.161Z" }, -] - -[[package]] -name = "packaging" -version = "26.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, -] - -[[package]] -name = "pillow" -version = "12.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1c/3d/bb7fca845737cf9d7dbde16ed1843984665ff2e0a518f5db43e77ec540b9/pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce", size = 47025035, upload-time = "2026-07-01T11:56:38.965Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/37/bf/fb3ebff8ddcb76aac5a01389251bbbb9519922a9b520d8247c1ca864a25d/pillow-12.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965", size = 5345969, upload-time = "2026-07-01T11:54:06.397Z" }, - { url = "https://files.pythonhosted.org/packages/d8/66/9a386a92561f402389a4fc70c18838bf6d35eb5eb5c6850b4b2dc64f5048/pillow-12.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7", size = 4780323, upload-time = "2026-07-01T11:54:09.351Z" }, - { url = "https://files.pythonhosted.org/packages/25/27/ac8f99618ffd3dde21db0f4d4b1d2ab00c0880595bfd17df103f7f39fd0c/pillow-12.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9c7f76c0673154f044e9d78c8655fb4213f6ca31a836df48b40fe5d187717b9", size = 6266838, upload-time = "2026-07-01T11:54:11.71Z" }, - { url = "https://files.pythonhosted.org/packages/84/21/a35af28dcc61f37ed850a2d64c65c701321dfbf25085e469d5559360cbbf/pillow-12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91", size = 6940830, upload-time = "2026-07-01T11:54:13.732Z" }, - { url = "https://files.pythonhosted.org/packages/eb/51/8b08617af3ad95e33ce6d7dd2c99ed6c8298f7fb131636303956be022e25/pillow-12.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c", size = 6344383, upload-time = "2026-07-01T11:54:15.756Z" }, - { url = "https://files.pythonhosted.org/packages/1d/72/cf78ac9780bb93c28328f408973845a309d4d145041665f734572ced1b52/pillow-12.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df", size = 7052934, upload-time = "2026-07-01T11:54:17.721Z" }, - { url = "https://files.pythonhosted.org/packages/20/20/25e0f4dc178a6bc0696793720055519a0de89e7661dae886992decbd2f81/pillow-12.3.0-cp312-cp312-win32.whl", hash = "sha256:dbce0b29841537a2fa4a214c2bbf14de3587c9680caa9b4e217568472490b28f", size = 6472684, upload-time = "2026-07-01T11:54:19.839Z" }, - { url = "https://files.pythonhosted.org/packages/45/89/da2f7971a317f83d807fdd4065c0af40208e59e692cc43d315a71a0e96d1/pillow-12.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09", size = 7227137, upload-time = "2026-07-01T11:54:22.025Z" }, - { url = "https://files.pythonhosted.org/packages/de/47/4845a0a6c0dbf1db8456bd9fc791f13c5ced7ced20606d08a0aacfd25b49/pillow-12.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510", size = 2568267, upload-time = "2026-07-01T11:54:24.051Z" }, - { url = "https://files.pythonhosted.org/packages/9d/ac/31fb64e1e7efb5a4b50cd3d92049ba89ac6e4d8d3bb6a74e15048ca3353e/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:21900ce7ba264168cd50defae43cd75d25c833ad4ad6e73ffc5596d12e25ac89", size = 4161684, upload-time = "2026-07-01T11:54:25.934Z" }, - { url = "https://files.pythonhosted.org/packages/87/b4/9805e23d2b4d77842b468513841fda254ee42f0289d25088340e4ff46e2d/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4e8c2a84d977f50b9daed6eeaf3baef67d00d5d74d932288f02cb94518ee3ace", size = 4255487, upload-time = "2026-07-01T11:54:27.935Z" }, - { url = "https://files.pythonhosted.org/packages/df/39/ecf519435a200c693fe053a6ee4d835b41cf963a4dfc2551c4e637cb2a71/pillow-12.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:ae26d61dfa7a47befdc7572b521024e8745f3d809bd95ca9505a7bba9ef849ec", size = 3696433, upload-time = "2026-07-01T11:54:29.813Z" }, - { url = "https://files.pythonhosted.org/packages/42/92/2fc3ffad878ae8dd5469ec1bc8eb83b71f48e13efdf68f02709003982a32/pillow-12.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7a743ff716f746fc19a9557f60dab1600d4613255f8a7aeb3cdde4db7eb15a66", size = 5345889, upload-time = "2026-07-01T11:54:31.97Z" }, - { url = "https://files.pythonhosted.org/packages/10/76/8803c13605b763d33d156c4678fc77f8443389c0c51c8aef707bb02015f4/pillow-12.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d69141514cc30b774ceea5e3ed3a6635c8d8a96edf664689b890f4089111fb35", size = 4780109, upload-time = "2026-07-01T11:54:34.026Z" }, - { url = "https://files.pythonhosted.org/packages/1f/01/e18aff37cb0b4aac47ac90f016d347a49aca667ef97f190b06ac2aabc928/pillow-12.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7401aebd7f581d7f83a439d87d474999317ee099218e5ad25d125290990ba65", size = 6263736, upload-time = "2026-07-01T11:54:36.131Z" }, - { url = "https://files.pythonhosted.org/packages/f7/62/de5bdd77d935331f4f802edc11e4d82950f642caad6cb2f949837b8560e2/pillow-12.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0847a763afefb695bc912d7c131e7e0632d4edc1d8698f58ddabec8e46b8b6d3", size = 6937129, upload-time = "2026-07-01T11:54:38.216Z" }, - { url = "https://files.pythonhosted.org/packages/70/4d/105627a13300c5e0df1d174230b32fd1273062c96f7745fd552b945d1e1d/pillow-12.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:571b9fcb07b97ef3a492028fb3d2dc0993ca23a06138b0315286566d29ef718a", size = 6339562, upload-time = "2026-07-01T11:54:40.354Z" }, - { url = "https://files.pythonhosted.org/packages/6b/1d/f13de01a553988ab895ba1c722e06cf3144d4f57656fd5b81b6d881f1179/pillow-12.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:756c768d0c9c2955feb7a56c37ea24aea2e369f8d36a88da270b6a9f19e62b5e", size = 7049439, upload-time = "2026-07-01T11:54:42.489Z" }, - { url = "https://files.pythonhosted.org/packages/c9/f9/066794cca041b969964f779ee5fa66a9498bbf34248ac39c5d7954e4198f/pillow-12.3.0-cp313-cp313-win32.whl", hash = "sha256:a876864214e136f0eb367788dbd7df045f4806801518e2cfe9e13229cfe06d8f", size = 6473287, upload-time = "2026-07-01T11:54:44.9Z" }, - { url = "https://files.pythonhosted.org/packages/a6/9b/7a58e61d62be561da3a356fe2384d4059a6345fc130e23ef1c36a5b81d24/pillow-12.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:1cca606cd25738df4ed873d5ad46bbdb3d83b5cbca291f6b4ff13a4df6b0bbe8", size = 7239691, upload-time = "2026-07-01T11:54:47.141Z" }, - { url = "https://files.pythonhosted.org/packages/aa/b0/c4ed4f0ef8f8fa5ee8351537db6650bb8189f7e118842978dd6589065692/pillow-12.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:b629de27fda84b42cde7edef0d85f13b958b47f6e9bbcbba9b673c562a89bd8b", size = 2568185, upload-time = "2026-07-01T11:54:49.137Z" }, -] - -[[package]] -name = "pip" -version = "26.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ae/15/4500e320e6b101ec3b719ae85b697d9940b6cda672bc555bd6016fc60c6f/pip-26.2.1.tar.gz", hash = "sha256:f6ad667e89a1fe78046c8f13232b247200f5258d7828f3f7883d660878e0813f", size = 1848877, upload-time = "2026-08-04T22:51:14.148Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/6e/1736e5b4ae2b778ef2f81c47d797de9f891d4d8acb047a24ca37a60294dd/pip-26.2.1-py3-none-any.whl", hash = "sha256:71138adf1f4ca900cdb7d289c21b7494329f2332b6d85f0e1c42108c0384ed3e", size = 1816632, upload-time = "2026-08-04T22:51:12.472Z" }, -] - -[[package]] -name = "pip-system-certs" -version = "5.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pip" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7d/6a/563b05a4f6c9ddc205c98bb413e74221368efb98b8fb9cca96b578b8930c/pip_system_certs-5.3.tar.gz", hash = "sha256:19c8bf9957bcce7d69c4dbc2d0b2ef13de1984d53f50a59012e6dbbad0af67c6", size = 6395, upload-time = "2025-10-16T06:14:55.217Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/57/752b63c609affae8f26ae0f1d1103d6ea7e707ad45943f62f7422936071d/pip_system_certs-5.3-py3-none-any.whl", hash = "sha256:3fbb5de62e374a99b688b1ad06e64ee5c4aeb633ef23e3a677d32e3e84fd863c", size = 6896, upload-time = "2025-10-16T06:14:54.072Z" }, -] - -[[package]] -name = "pluggy" -version = "1.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, -] - -[[package]] -name = "poks" -version = "0.11.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "gitpython" }, - { name = "mashumaro" }, - { name = "pip-system-certs" }, - { name = "py-app-dev" }, - { name = "py7zr" }, - { name = "requests" }, - { name = "typer" }, - { name = "zstandard" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a4/20/56ebe9b8b7607e775a5c8a1977ecb4555346a95f27212d329a642e1ce550/poks-0.11.1.tar.gz", hash = "sha256:1248c470d6bc7a97ea375e7afb774d3956c7c3ec51f6d11d6bfd7f3debd904af", size = 25655, upload-time = "2026-03-22T14:56:57.159Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/91/e8/9677a57ef26c79dad2056c79758170c085fc5387be5920387bc6420b1f4d/poks-0.11.1-py3-none-any.whl", hash = "sha256:d3ede0a3d449ba456510be03d91c554f96ff8cd55e08b5494e2d2942ab54070e", size = 27790, upload-time = "2026-03-22T14:56:56.022Z" }, -] - -[[package]] -name = "psutil" -version = "7.2.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, - { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, - { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, - { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, - { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, - { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, - { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, - { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, - { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, - { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, - { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, - { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, - { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, - { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, -] - -[[package]] -name = "py-app-dev" -version = "2.22.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "loguru" }, - { name = "mashumaro" }, - { name = "psutil" }, - { name = "pyyaml" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5e/8e/f8e12d7c0863dbf9ced1abe5ffdaa0917a0bb1b22b2f776222ae2d45d8ce/py_app_dev-2.22.0.tar.gz", hash = "sha256:1664b641d0690ebc8ad90033f8c8c123b9eb88a33429fa331b0f4e76329b8ae5", size = 24216, upload-time = "2026-06-12T19:36:02.184Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/72/2dd056161b8ddffb679559ffea28c2de098e1df129cbdd1a1d0b0de39411/py_app_dev-2.22.0-py3-none-any.whl", hash = "sha256:cfca68a270f119110060d9ad2095296db8697c87c88ada729e2cfadf369db432", size = 28161, upload-time = "2026-06-12T19:36:01.092Z" }, -] - -[[package]] -name = "py7zr" -version = "1.1.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "backports-zstd" }, - { name = "brotli", marker = "platform_python_implementation == 'CPython'" }, - { name = "brotlicffi", marker = "platform_python_implementation == 'PyPy'" }, - { name = "inflate64" }, - { name = "multivolumefile" }, - { name = "psutil", marker = "sys_platform != 'cygwin'" }, - { name = "pybcj" }, - { name = "pycryptodomex" }, - { name = "pyppmd" }, - { name = "texttable" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/59/3f/ac248f25f3901d3d6cf80ac99d5bac6fd1e5e6543c1812c79b02b6074c60/py7zr-1.1.3.tar.gz", hash = "sha256:8d51894abb38355bf14881088bb97f01fe4cb5b14ebe22f66d6297668c7e1a74", size = 71695, upload-time = "2026-06-19T09:28:29.339Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/40/dc7f804abd0ea167ea8d4d9b3f5455321aaf671d040fcef647e9e952e714/py7zr-1.1.3-py3-none-any.whl", hash = "sha256:17934a35089e026dec6c72ee275d9b841e646881ef822d618e805c3006661d9a", size = 72242, upload-time = "2026-06-19T09:28:27.512Z" }, -] - -[[package]] -name = "pybcj" -version = "1.0.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/14/c9/e2de4f98c21f2ff9e58047395b6a8e17701a0f2977f0e63f80948f61647f/pybcj-1.0.8.tar.gz", hash = "sha256:6818270692912d47e81ccd777b7d0ed2ad3a19ef7aaead3735b38048c80bf368", size = 31763, upload-time = "2026-07-16T22:47:24.705Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/09/32/63b96b702fc4848834ce552302fbdccad92f67886eaf3fbbed530569f4a1/pybcj-1.0.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:748af4545040da68dfae8cdf9f902ac7c29f209aaf6450a970abe0302d1b0f58", size = 32798, upload-time = "2026-07-16T22:46:40.083Z" }, - { url = "https://files.pythonhosted.org/packages/a2/21/0b501901021499e41afe7d0148c94ee26dcf045d3e1389bf1b42a61d11a6/pybcj-1.0.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:190e21c3bdb6b28c63999889668a3f80f5d7eb13b957f2dfda53c9ddeb3d946a", size = 24673, upload-time = "2026-07-16T22:46:41.288Z" }, - { url = "https://files.pythonhosted.org/packages/b3/97/c05d5800d742f5162bd06ef790dfcf73128431328f16abe7a0e2109d8ca5/pybcj-1.0.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:384b25dfe66868329a3872c6eae668033e009e4e86754eba3a70b656c9eb4208", size = 24958, upload-time = "2026-07-16T22:46:42.469Z" }, - { url = "https://files.pythonhosted.org/packages/05/0f/9db13432a6f8878f90c108a68fda46e832eea53585cb5dee8bdbc9472467/pybcj-1.0.8-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b480efcd66cfb4533ede6c3d67224ff477c8dd661613826edff9c95921a562d6", size = 53297, upload-time = "2026-07-16T22:46:43.93Z" }, - { url = "https://files.pythonhosted.org/packages/76/59/5629811698e6cfaea6cf159cf86fa471904ceb60448dc8b80233bba2e392/pybcj-1.0.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a50b58f9b9d9978c55e3219b6816a1021a778b028a810b36aa754407591cb5ae", size = 52332, upload-time = "2026-07-16T22:46:45.252Z" }, - { url = "https://files.pythonhosted.org/packages/b2/0a/c5bd982957b5fd785f02dffcb3f6f45a93143e1e8ccc8224e19b3915fc84/pybcj-1.0.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1997badf9e59221470f84e193ac8c1bab158acfe6b058ffdc28c331807967ff9", size = 51192, upload-time = "2026-07-16T22:46:46.674Z" }, - { url = "https://files.pythonhosted.org/packages/7a/79/1d421d2d45e565a0f8eab8ce6f795e31beca96f658c1e483dac64a466a28/pybcj-1.0.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4f0818cf48fbfddbae487536f3fcd69cfadfb7293630ab8658b66e89a0836d6a", size = 50849, upload-time = "2026-07-16T22:46:48.006Z" }, - { url = "https://files.pythonhosted.org/packages/7b/c3/daddd6edc01689b1cb75733bd3098231bae20c9e77d7022dc0b1c40e56fd/pybcj-1.0.8-cp312-cp312-win_amd64.whl", hash = "sha256:26e3e4d287f306996902289fc0e88b36d0fc817802f4fc4537b40a6518726585", size = 24956, upload-time = "2026-07-16T22:46:49.159Z" }, - { url = "https://files.pythonhosted.org/packages/a7/fc/9c668e2cd1432f7ede31bc131ab336c8602659374bc731bfaca6cf208bbe/pybcj-1.0.8-cp312-cp312-win_arm64.whl", hash = "sha256:82d2e46eff6c3fe8d16f6515f9ab0598eb960af9a2218907c5e0e9f993d7a666", size = 24251, upload-time = "2026-07-16T22:46:50.33Z" }, - { url = "https://files.pythonhosted.org/packages/25/c1/9d8822f1b97813f21b5907b351edb0f607c9736e21637812edf407a2dae6/pybcj-1.0.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9f0e496c49dce294fb41a2221e7b3f96156bb940d8cfe359d441909f1cf241ea", size = 32799, upload-time = "2026-07-16T22:46:51.625Z" }, - { url = "https://files.pythonhosted.org/packages/6f/8b/248154c815e683c0f991c471bedb7476d7c91fd8a56918f1ef66d2341ea5/pybcj-1.0.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0657c9534d480a43b11743cad4209d140d09d417adaf19213ff395686ed08812", size = 24673, upload-time = "2026-07-16T22:46:52.799Z" }, - { url = "https://files.pythonhosted.org/packages/e0/f7/f168c7b091fc41fc7ef6d9ed6cde2ec10bb4814f14ad21b493881476636e/pybcj-1.0.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:784443d0c075871689692679ef01cfe85eca789cc8d091f842fa02f62dead9d5", size = 24955, upload-time = "2026-07-16T22:46:54.069Z" }, - { url = "https://files.pythonhosted.org/packages/cc/a1/7b802ee7c7730521a102dadbdf944943795b3e9ca3ca665dbbdd1213f781/pybcj-1.0.8-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9195ce382f21e13df7ecc0df8fb42c5f391a7ac516066af16b023e6bd2f86046", size = 53344, upload-time = "2026-07-16T22:46:55.272Z" }, - { url = "https://files.pythonhosted.org/packages/4b/cf/c4b0248051cdb7af7f38223db63427175e755db1f1a7905e781e923f6d53/pybcj-1.0.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e5e67ec32176985bbf941185319a3fe385527b6826d457a8ec67dd43b5f0b5fa", size = 52410, upload-time = "2026-07-16T22:46:56.541Z" }, - { url = "https://files.pythonhosted.org/packages/52/77/9afed44c590a19940e04fdf38b7f55e7db3a8b35e3f6320340d99be25ca8/pybcj-1.0.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:20b657e4fe3e701a0f64a7c1224694820687334b17593d74a878f7124f10b24f", size = 51282, upload-time = "2026-07-16T22:46:57.748Z" }, - { url = "https://files.pythonhosted.org/packages/10/4b/7eaf8cf0ad598cc2035a24186a3e2bf45ec798279dad58484d0eb940d4b3/pybcj-1.0.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5b4c66b5358cd241b6ff81a8fafd5806e816d378c810a796ad51865be5533f7", size = 50935, upload-time = "2026-07-16T22:46:58.94Z" }, - { url = "https://files.pythonhosted.org/packages/39/f4/04d46f5059af00d2fae6086a7f47ebc23e553900ca75ea2a2e276d3a436c/pybcj-1.0.8-cp313-cp313-win_amd64.whl", hash = "sha256:e47d9c655d276200e5d08f7b550097dacc826364b44931f0fbdc0da2e4bdf448", size = 24956, upload-time = "2026-07-16T22:47:00.127Z" }, - { url = "https://files.pythonhosted.org/packages/63/cc/387af6844cdc53be20a9271998bc4a15a96360f06ce8829193d4a45f6b19/pybcj-1.0.8-cp313-cp313-win_arm64.whl", hash = "sha256:f561988aef99f07e991d0708ca6fe86e766d545c0a20e58bdbd38615a615e419", size = 24249, upload-time = "2026-07-16T22:47:01.28Z" }, -] - -[[package]] -name = "pycparser" -version = "3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, -] - -[[package]] -name = "pycryptodomex" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c9/85/e24bf90972a30b0fcd16c73009add1d7d7cd9140c2498a68252028899e41/pycryptodomex-3.23.0.tar.gz", hash = "sha256:71909758f010c82bc99b0abf4ea12012c98962fbf0583c2164f8b84533c2e4da", size = 4922157, upload-time = "2025-05-17T17:23:41.434Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/00/10edb04777069a42490a38c137099d4b17ba6e36a4e6e28bdc7470e9e853/pycryptodomex-3.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:7b37e08e3871efe2187bc1fd9320cc81d87caf19816c648f24443483005ff886", size = 2498764, upload-time = "2025-05-17T17:22:21.453Z" }, - { url = "https://files.pythonhosted.org/packages/6b/3f/2872a9c2d3a27eac094f9ceaa5a8a483b774ae69018040ea3240d5b11154/pycryptodomex-3.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:91979028227543010d7b2ba2471cf1d1e398b3f183cb105ac584df0c36dac28d", size = 1643012, upload-time = "2025-05-17T17:22:23.702Z" }, - { url = "https://files.pythonhosted.org/packages/70/af/774c2e2b4f6570fbf6a4972161adbb183aeeaa1863bde31e8706f123bf92/pycryptodomex-3.23.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b8962204c47464d5c1c4038abeadd4514a133b28748bcd9fa5b6d62e3cec6fa", size = 2187643, upload-time = "2025-05-17T17:22:26.37Z" }, - { url = "https://files.pythonhosted.org/packages/de/a3/71065b24cb889d537954cedc3ae5466af00a2cabcff8e29b73be047e9a19/pycryptodomex-3.23.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a33986a0066860f7fcf7c7bd2bc804fa90e434183645595ae7b33d01f3c91ed8", size = 2273762, upload-time = "2025-05-17T17:22:28.313Z" }, - { url = "https://files.pythonhosted.org/packages/c9/0b/ff6f43b7fbef4d302c8b981fe58467b8871902cdc3eb28896b52421422cc/pycryptodomex-3.23.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7947ab8d589e3178da3d7cdeabe14f841b391e17046954f2fbcd941705762b5", size = 2313012, upload-time = "2025-05-17T17:22:30.57Z" }, - { url = "https://files.pythonhosted.org/packages/02/de/9d4772c0506ab6da10b41159493657105d3f8bb5c53615d19452afc6b315/pycryptodomex-3.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c25e30a20e1b426e1f0fa00131c516f16e474204eee1139d1603e132acffc314", size = 2186856, upload-time = "2025-05-17T17:22:32.819Z" }, - { url = "https://files.pythonhosted.org/packages/28/ad/8b30efcd6341707a234e5eba5493700a17852ca1ac7a75daa7945fcf6427/pycryptodomex-3.23.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:da4fa650cef02db88c2b98acc5434461e027dce0ae8c22dd5a69013eaf510006", size = 2347523, upload-time = "2025-05-17T17:22:35.386Z" }, - { url = "https://files.pythonhosted.org/packages/0f/02/16868e9f655b7670dbb0ac4f2844145cbc42251f916fc35c414ad2359849/pycryptodomex-3.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:58b851b9effd0d072d4ca2e4542bf2a4abcf13c82a29fd2c93ce27ee2a2e9462", size = 2272825, upload-time = "2025-05-17T17:22:37.632Z" }, - { url = "https://files.pythonhosted.org/packages/ca/18/4ca89ac737230b52ac8ffaca42f9c6f1fd07c81a6cd821e91af79db60632/pycryptodomex-3.23.0-cp313-cp313t-win32.whl", hash = "sha256:a9d446e844f08299236780f2efa9898c818fe7e02f17263866b8550c7d5fb328", size = 1772078, upload-time = "2025-05-17T17:22:40Z" }, - { url = "https://files.pythonhosted.org/packages/73/34/13e01c322db027682e00986873eca803f11c56ade9ba5bbf3225841ea2d4/pycryptodomex-3.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:bc65bdd9fc8de7a35a74cab1c898cab391a4add33a8fe740bda00f5976ca4708", size = 1803656, upload-time = "2025-05-17T17:22:42.139Z" }, - { url = "https://files.pythonhosted.org/packages/54/68/9504c8796b1805d58f4425002bcca20f12880e6fa4dc2fc9a668705c7a08/pycryptodomex-3.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:c885da45e70139464f082018ac527fdaad26f1657a99ee13eecdce0f0ca24ab4", size = 1707172, upload-time = "2025-05-17T17:22:44.704Z" }, - { url = "https://files.pythonhosted.org/packages/dd/9c/1a8f35daa39784ed8adf93a694e7e5dc15c23c741bbda06e1d45f8979e9e/pycryptodomex-3.23.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:06698f957fe1ab229a99ba2defeeae1c09af185baa909a31a5d1f9d42b1aaed6", size = 2499240, upload-time = "2025-05-17T17:22:46.953Z" }, - { url = "https://files.pythonhosted.org/packages/7a/62/f5221a191a97157d240cf6643747558759126c76ee92f29a3f4aee3197a5/pycryptodomex-3.23.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b2c2537863eccef2d41061e82a881dcabb04944c5c06c5aa7110b577cc487545", size = 1644042, upload-time = "2025-05-17T17:22:49.098Z" }, - { url = "https://files.pythonhosted.org/packages/8c/fd/5a054543c8988d4ed7b612721d7e78a4b9bf36bc3c5ad45ef45c22d0060e/pycryptodomex-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43c446e2ba8df8889e0e16f02211c25b4934898384c1ec1ec04d7889c0333587", size = 2186227, upload-time = "2025-05-17T17:22:51.139Z" }, - { url = "https://files.pythonhosted.org/packages/c8/a9/8862616a85cf450d2822dbd4fff1fcaba90877907a6ff5bc2672cafe42f8/pycryptodomex-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f489c4765093fb60e2edafdf223397bc716491b2b69fe74367b70d6999257a5c", size = 2272578, upload-time = "2025-05-17T17:22:53.676Z" }, - { url = "https://files.pythonhosted.org/packages/46/9f/bda9c49a7c1842820de674ab36c79f4fbeeee03f8ff0e4f3546c3889076b/pycryptodomex-3.23.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdc69d0d3d989a1029df0eed67cc5e8e5d968f3724f4519bd03e0ec68df7543c", size = 2312166, upload-time = "2025-05-17T17:22:56.585Z" }, - { url = "https://files.pythonhosted.org/packages/03/cc/870b9bf8ca92866ca0186534801cf8d20554ad2a76ca959538041b7a7cf4/pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6bbcb1dd0f646484939e142462d9e532482bc74475cecf9c4903d4e1cd21f003", size = 2185467, upload-time = "2025-05-17T17:22:59.237Z" }, - { url = "https://files.pythonhosted.org/packages/96/e3/ce9348236d8e669fea5dd82a90e86be48b9c341210f44e25443162aba187/pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:8a4fcd42ccb04c31268d1efeecfccfd1249612b4de6374205376b8f280321744", size = 2346104, upload-time = "2025-05-17T17:23:02.112Z" }, - { url = "https://files.pythonhosted.org/packages/a5/e9/e869bcee87beb89040263c416a8a50204f7f7a83ac11897646c9e71e0daf/pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:55ccbe27f049743a4caf4f4221b166560d3438d0b1e5ab929e07ae1702a4d6fd", size = 2271038, upload-time = "2025-05-17T17:23:04.872Z" }, - { url = "https://files.pythonhosted.org/packages/8d/67/09ee8500dd22614af5fbaa51a4aee6e342b5fa8aecf0a6cb9cbf52fa6d45/pycryptodomex-3.23.0-cp37-abi3-win32.whl", hash = "sha256:189afbc87f0b9f158386bf051f720e20fa6145975f1e76369303d0f31d1a8d7c", size = 1771969, upload-time = "2025-05-17T17:23:07.115Z" }, - { url = "https://files.pythonhosted.org/packages/69/96/11f36f71a865dd6df03716d33bd07a67e9d20f6b8d39820470b766af323c/pycryptodomex-3.23.0-cp37-abi3-win_amd64.whl", hash = "sha256:52e5ca58c3a0b0bd5e100a9fbc8015059b05cffc6c66ce9d98b4b45e023443b9", size = 1803124, upload-time = "2025-05-17T17:23:09.267Z" }, - { url = "https://files.pythonhosted.org/packages/f9/93/45c1cdcbeb182ccd2e144c693eaa097763b08b38cded279f0053ed53c553/pycryptodomex-3.23.0-cp37-abi3-win_arm64.whl", hash = "sha256:02d87b80778c171445d67e23d1caef279bf4b25c3597050ccd2e13970b57fd51", size = 1707161, upload-time = "2025-05-17T17:23:11.414Z" }, -] - -[[package]] -name = "pydantic" -version = "2.13.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "annotated-types" }, - { name = "pydantic-core" }, - { name = "typing-extensions" }, - { name = "typing-inspection" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/53/ef/fc4f868f4e2cee79f863883abffceff107875f569b848507319842d2a681/pydantic-2.13.5.tar.gz", hash = "sha256:51a9c5f7b2f8e636f04c6cada605d9b6a3bf1348fdf945a3d8869b19bba0ee08", size = 845750, upload-time = "2026-08-28T14:04:00.916Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/47/c95ffc2009878c7aac0c5e08528022dcb885933252a88b5f170058014464/pydantic-2.13.5-py3-none-any.whl", hash = "sha256:346a034f080da3755d8e9cb5e00e8b07de1d39e4f6e2c87d8ab7cafa0b269a73", size = 472589, upload-time = "2026-08-28T14:03:59.136Z" }, -] - -[[package]] -name = "pydantic-core" -version = "2.46.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/af/f9/8a06bea35ef8daf588f707784c973a7046e0034c8d8cfb08828eeffb8b75/pydantic_core-2.46.5.tar.gz", hash = "sha256:10416c15b8839ecc4ef4d0885da76da6fd0f67333a0eb8aff6d93c4b8f2910fc", size = 472262, upload-time = "2026-08-28T10:01:31.677Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/82/3f/76358795aa7a8c6d4f36e2cb828ad1c90ee118e1393a9281664f5aade9d4/pydantic_core-2.46.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b9fe6fb92520e3fd61f2e49000b6911b188824f089b75973ea06d6267f0b476d", size = 2076516, upload-time = "2026-08-28T09:58:21.576Z" }, - { url = "https://files.pythonhosted.org/packages/db/50/26b091836076ce4cb2fac264186936acc069e0595772cfd02a563bc4761a/pydantic_core-2.46.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a39ac25a9a2fa4072efdb429833c4a4c8009a51ff9eea3eeae131713cd27991e", size = 1922874, upload-time = "2026-08-28T09:58:23.766Z" }, - { url = "https://files.pythonhosted.org/packages/09/f0/2a8ce3849e299d44e2d2c196b6082643a3235565a735cb51db7a6261f614/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fdc8b93a41521988916eeaa271173fcca7fa0803d62f87675aac8dcec1c8e29", size = 1951772, upload-time = "2026-08-28T09:58:25.435Z" }, - { url = "https://files.pythonhosted.org/packages/87/46/ac0dc8bdd9e6048183a14eb127764e7ad9240021c17513074a4711b0e31e/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b98134087d9de723658d17a42c7d0da8d6e2ef08015dee7dc93889047315f5e4", size = 2031832, upload-time = "2026-08-28T09:58:27.102Z" }, - { url = "https://files.pythonhosted.org/packages/c4/c2/339de5bef7be36301a2231eaa52e62163742c2281f11b5f4892bc79785cd/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e652ab17569c94bff5475520f907b7148b8c24036a8ebbe5cf7cf7493d28579a", size = 2208645, upload-time = "2026-08-28T09:58:28.948Z" }, - { url = "https://files.pythonhosted.org/packages/7b/a0/9ff22b797724262da14427abaed4dd1d864a139693fc5e7809114376a716/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d925f3d9afd05a8c0fb3a1031463a8d59ebe5e2afad297e29c78be19e13b4e62", size = 2265935, upload-time = "2026-08-28T09:58:30.625Z" }, - { url = "https://files.pythonhosted.org/packages/c0/a4/eb9409ec0736e50aa70a412f16c204ed149516846912f7e6724d4c73ee53/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fc5be0abd4a407e200d844b404e33639a554e7bd0d448e7b9ae181be4789ac2", size = 2066284, upload-time = "2026-08-28T09:58:32.289Z" }, - { url = "https://files.pythonhosted.org/packages/c0/02/7f6156ffc926857f1c37c07d9a388682865a81830ab6a1b637082c25e399/pydantic_core-2.46.5-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:816ff0a6550ffc06c098ccd2e0698600f9aa7da192a79eaa6f9af504a35db869", size = 2105889, upload-time = "2026-08-28T09:58:33.986Z" }, - { url = "https://files.pythonhosted.org/packages/92/b1/e781d357ebe09fc929f995700f1b3503e8897f1cece183ecb1300d4d67e9/pydantic_core-2.46.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c7ea57fc63aa7da93a1bd2d644e6577befae10c52c4e36377635eea1056a74f5", size = 2158006, upload-time = "2026-08-28T09:58:35.647Z" }, - { url = "https://files.pythonhosted.org/packages/70/0a/644597d84ab400e50609c192120b85c9681c22d3a20461b9060a79be0a7a/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:efd62a42486f1bda5d24cb4f63d15a3c7768375fe83d36f9417b4ad7a2fb20b3", size = 2158408, upload-time = "2026-08-28T09:58:37.38Z" }, - { url = "https://files.pythonhosted.org/packages/1e/ee/ca3b7b3a4b3769ffe9ce9432a7c9be755de9593a46d3b0d54d0409323e44/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:2bc9419666990c06d7397831f2126a1ecc3594aaa3ff7de5bf2d066802f4e07b", size = 2309609, upload-time = "2026-08-28T09:58:39.22Z" }, - { url = "https://files.pythonhosted.org/packages/ce/52/39fa1f451486019524ca685020390e7ca351832fd874530ba30c8628e6dc/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:18a09e1e1011b462f2e32774f25859ef1223d5c2b0546a633cf56654710721e0", size = 2342618, upload-time = "2026-08-28T09:58:40.89Z" }, - { url = "https://files.pythonhosted.org/packages/81/5e/468fc630568c61dcef3cd47ad32ffbeed9af643f49208d1ea86ab4f890c4/pydantic_core-2.46.5-cp312-cp312-win32.whl", hash = "sha256:5cb482e9e84c851f4e623fe4acc1ced89168cf1fe18f7089db4548c8f5bbb65b", size = 1939475, upload-time = "2026-08-28T09:58:42.591Z" }, - { url = "https://files.pythonhosted.org/packages/cf/c9/4c19f41b84cf6b622a72fbeed7665b25d47a187d68d47d0d430c07f23268/pydantic_core-2.46.5-cp312-cp312-win_amd64.whl", hash = "sha256:5e81740c09e310f5aa5cbd3e434a01c154d4bef93241c7877b39f211d2b78ba8", size = 2043140, upload-time = "2026-08-28T09:58:44.272Z" }, - { url = "https://files.pythonhosted.org/packages/af/dd/0c1a050299147c746e5256db16d645ab5efd4f78c59937d581a0524e74a2/pydantic_core-2.46.5-cp312-cp312-win_arm64.whl", hash = "sha256:f7b0ec93a2893de856652154d73b7ba622f26fa97726487dcac373de5f4c6084", size = 1997729, upload-time = "2026-08-28T09:58:46.13Z" }, - { url = "https://files.pythonhosted.org/packages/f5/37/5abe39a8372a61d3dc3c1338fc504281c01b32fdb3169cd7187153b56d3e/pydantic_core-2.46.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:b7ca9034437b6022f941f4857459562ee00a560b97e7cce8a0ec5a74fc6766e0", size = 2075885, upload-time = "2026-08-28T09:58:47.856Z" }, - { url = "https://files.pythonhosted.org/packages/21/43/6323b1f8b217780454c61304bcd2b38ae4762f50754414124603ccc90bb2/pydantic_core-2.46.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f332f0e72a5a0400141f830744e141bf9f97917878dbe968669e8a7fefea78ff", size = 1922768, upload-time = "2026-08-28T09:58:49.58Z" }, - { url = "https://files.pythonhosted.org/packages/0f/a3/c05ca796e1197618a774b01e596aeedfefc2f7d8c01ae3054e910b120e8a/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:193375f3548919d3f0b60936ca113ada3e38f264f91b9b8e0508efaad57be931", size = 1951241, upload-time = "2026-08-28T09:58:51.511Z" }, - { url = "https://files.pythonhosted.org/packages/68/32/33bc39ac705c52cffc908e8389f9754fdb208aea5c69cceddf4eb3ce99af/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:79bdfa52f843137045b2d081cc05c120ba6665d29b7559c2c47690906f39279f", size = 2031975, upload-time = "2026-08-28T09:58:53.166Z" }, - { url = "https://files.pythonhosted.org/packages/b0/70/2333e885c0f6a67bc105c5916965dac9b57f2718ee20d81d1a06a4ebdc13/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:24922243639cbdac66c75fcb6fd6495a9cb52b213d62f9a0d16f0310b1ff8038", size = 2208542, upload-time = "2026-08-28T09:58:55.017Z" }, - { url = "https://files.pythonhosted.org/packages/f7/ea/296debfb4264207bbda5936133892e027c0a58875ad53ebd512fba8ec3a2/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c76fe65e607be28c7fd4d56fc3c42b1583aa058ce3408b7ad0fd540171d31f9f", size = 2264692, upload-time = "2026-08-28T09:58:56.767Z" }, - { url = "https://files.pythonhosted.org/packages/d3/f2/9e4de77a6271e07a76d2d58b11c091a979c191ed2939bf80067568b369d2/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f7b393a8b3da82f5c1fc0751e6d01ac6c55b93c18226a60bdfba4a724efafd1", size = 2066633, upload-time = "2026-08-28T09:58:58.531Z" }, - { url = "https://files.pythonhosted.org/packages/8d/db/f9e9d0c97445987b2084823d5c240de88087338f04fc2cfaa2df186b8049/pydantic_core-2.46.5-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:7ac031912d54f3d83ef3b3eb98dfabc1608802e2202263d25957eeed40b94761", size = 2105235, upload-time = "2026-08-28T09:59:00.421Z" }, - { url = "https://files.pythonhosted.org/packages/07/c5/79169b047b3b2c3e99e04bc76372af9637e0bf6db638274fa927df96369e/pydantic_core-2.46.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:837b396ca3d7b74091ca623f6cbd8351bd42d670a79c2683e79fb089f06a2de5", size = 2157367, upload-time = "2026-08-28T09:59:02.442Z" }, - { url = "https://files.pythonhosted.org/packages/26/b5/ba6057afb7c291bd449f51b867f95aef2072941c4ce4e5c31d6ffd132d3b/pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:5ee239d575f80b08eca11f6e20f90c4c695de7825c67eefe6091fbf20dda648e", size = 2158420, upload-time = "2026-08-28T09:59:04.2Z" }, - { url = "https://files.pythonhosted.org/packages/6e/28/2057abecaafdc22912afa819603a51f0a62d40643b7c4871c51721fea9be/pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:e80675d75ae2cd14372cb65cad5400d9347a3d3f6c13000183f22dfd027283ed", size = 2309588, upload-time = "2026-08-28T09:59:06.048Z" }, - { url = "https://files.pythonhosted.org/packages/71/9d/881156dc404e27479c4246128d73538464cab4a239bec61995e227644c30/pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:9c4b71f10dd532fb7a5cbc8f58707779e64f03a258c2bf8bfbaecfcd9970b519", size = 2341866, upload-time = "2026-08-28T09:59:08.539Z" }, - { url = "https://files.pythonhosted.org/packages/5a/38/d66f443a259f84d13babdceae568e572b0ed26da17ca5d0a649ebb110a67/pydantic_core-2.46.5-cp313-cp313-win32.whl", hash = "sha256:97bf8de4d541598c94a59344eeb988a94c08ff76b5723c41f6567ec18c7892ea", size = 1938580, upload-time = "2026-08-28T09:59:10.402Z" }, - { url = "https://files.pythonhosted.org/packages/2c/1e/1d5371213f4cc9a7ed70c0bfcc7911de22311ee99a662a56077d7292d2ac/pydantic_core-2.46.5-cp313-cp313-win_amd64.whl", hash = "sha256:15f4a94963c95accac15b7b657bb177d3ad82bb90b0d0526d9a9b85079925db5", size = 2041980, upload-time = "2026-08-28T09:59:12.396Z" }, - { url = "https://files.pythonhosted.org/packages/5a/48/4222d90b1c67568bace4dec6dca6271449c66de3595d72b6d098f5fde597/pydantic_core-2.46.5-cp313-cp313-win_arm64.whl", hash = "sha256:d22a945598fb91236b4dd793a6e42e4f3dd7740bb5aace5ebd7d4c08d13bb575", size = 1997213, upload-time = "2026-08-28T09:59:14.245Z" }, - { url = "https://files.pythonhosted.org/packages/df/dd/053c2e4303f791f3b8f8a14ab0b22008e8eb21d868c0c90b4f9be705b76a/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:013d6f3483d81e02e7c328831808f336c8596ee33b4bd4026b9ffb1e960b8942", size = 2062540, upload-time = "2026-08-28T10:01:00.318Z" }, - { url = "https://files.pythonhosted.org/packages/d7/dd/a18df751a5e37dd51bfad7f68e766999125bebe68c9e1d10a493ad01bd63/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:e9c134bb666dd54b778b9fc0d2b50cbb7f979b9e3716f26a88c9ab3b6fc1dd0f", size = 1902040, upload-time = "2026-08-28T10:01:02.529Z" }, - { url = "https://files.pythonhosted.org/packages/b7/13/01d40f9d07ce8a779fd6e0bd8ad4fba91309500dd67b869e2e219d261a6d/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:347ec774390c87326a2e4929d58d3f7e8763a104d5d35f4cd595a4c952366433", size = 1967479, upload-time = "2026-08-28T10:01:05.004Z" }, - { url = "https://files.pythonhosted.org/packages/fa/04/c81d4841331c2178b6fb09ae225425e110ed72d990c9fe556c4ec03d1013/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e24d8f05fa2d28513d94e877e9c75ad66175376209b3977f916e240e623193c", size = 2111034, upload-time = "2026-08-28T10:01:07.345Z" }, -] - -[[package]] -name = "pydata-sphinx-theme" -version = "0.18.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "accessible-pygments" }, - { name = "babel" }, - { name = "beautifulsoup4" }, - { name = "docutils" }, - { name = "pygments" }, - { name = "sphinx" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ad/81/b3fdc8b74d0cfed9e623a0fef9932376800da5daa1a85d1224cac4c131a3/pydata_sphinx_theme-0.18.0.tar.gz", hash = "sha256:b4abc95ab02600872e060db07c79e056e87b7ea653ab1ffd0e0b1fa75a3003d4", size = 5004260, upload-time = "2026-05-20T08:32:28.897Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/cd/e0eda602060f9dc99068f8e54490812d9d34ebb134043ff0ae594cf721a4/pydata_sphinx_theme-0.18.0-py3-none-any.whl", hash = "sha256:fbe5401f26642d487e3c5b6dfcbf69b3b1d579e80dcc479a429632abe0a13929", size = 6200747, upload-time = "2026-05-20T08:32:26.646Z" }, -] - -[[package]] -name = "pygments" -version = "2.21.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" }, -] - -[[package]] -name = "pykwalify" -version = "1.8.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "docopt" }, - { name = "python-dateutil" }, - { name = "ruamel-yaml" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d5/77/2d6849510dbfce5f74f1f69768763630ad0385ad7bb0a4f39b55de3920c7/pykwalify-1.8.0.tar.gz", hash = "sha256:796b2ad3ed4cb99b88308b533fb2f559c30fa6efb4fa9fda11347f483d245884", size = 62462, upload-time = "2020-12-30T22:31:10.745Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/fd/ac2161cce19fd67a18c269073f8e86292b5511acec6f8ef6eab88615d032/pykwalify-1.8.0-py2.py3-none-any.whl", hash = "sha256:731dfa87338cca9f559d1fca2bdea37299116e3139b73f78ca90a543722d6651", size = 24860, upload-time = "2020-12-30T22:31:09.09Z" }, -] - -[[package]] -name = "pyparsing" -version = "3.3.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, -] - -[[package]] -name = "pypeline-runner" -version = "1.39.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "poks" }, - { name = "py-app-dev" }, - { name = "pyyaml" }, - { name = "typer" }, - { name = "west" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/95/41/bedb03c38505e88b7fbca3dc8fe6f2ec3c3733b08b0db7967fa2779087ac/pypeline_runner-1.39.0.tar.gz", hash = "sha256:fd387cf5a70f8b41b3363dc8064077ad28b903295913dc427acf06c73b7f2ad9", size = 40523, upload-time = "2026-09-04T15:10:55.246Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/80/1343cbc76ebe62a004576df9bca5ff31bf2765d70b9915f5d6288cd04b48/pypeline_runner-1.39.0-py3-none-any.whl", hash = "sha256:a6993ca2410e02c7e68004879f4b50fa64b1cc6f9c2fb6187837d96e81d89e01", size = 48884, upload-time = "2026-09-04T15:10:53.824Z" }, -] - -[[package]] -name = "pypeline-semantic-release" -version = "0.5.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "py-app-dev" }, - { name = "pypeline-runner" }, - { name = "python-semantic-release" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d2/f5/c54da09b40ebee03f0401778d4a83743e5807ce321e892dc4f96fe038b2a/pypeline_semantic_release-0.5.1.tar.gz", hash = "sha256:b1e5bd84143f9d0250da5e3b78c900587535d8a4419390898e93b2fdb0fee016", size = 16505, upload-time = "2026-04-21T05:35:29.442Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/50/584083a8dccd9010a977d5ea39fc9830bf5374421ee32459b5b821b66836/pypeline_semantic_release-0.5.1-py3-none-any.whl", hash = "sha256:9ef3602f4659f600b243a09d243cb3c861c5446eacbf6210bc79065efed0781b", size = 15443, upload-time = "2026-04-21T05:35:27.73Z" }, -] - -[[package]] -name = "pyppmd" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/81/d7/803232913cab9163a1a97ecf2236cd7135903c46ac8d49613448d88e8759/pyppmd-1.3.1.tar.gz", hash = "sha256:ced527f08ade4408c1bfc5264e9f97ffac8d221c9d13eca4f35ec1ec0c7b6b2e", size = 1351815, upload-time = "2025-11-27T22:08:44.175Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3e/89/696046e53c7aea98bb563aed3f15c3e2fa20c33e3d6c9de3c20992c586cc/pyppmd-1.3.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3faa58ab2ebe3b13ec23b1904639d687fb727270d2962fd2d239ca00fd6eb865", size = 77796, upload-time = "2025-11-27T22:07:50.362Z" }, - { url = "https://files.pythonhosted.org/packages/f1/c5/a708cdb58e76889bc65201eda12211486548ffe00ecddc5dfbdce6d4d252/pyppmd-1.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27703f041ee96912a5410fd3ce31c5cde32f9323bd67f72f100bd960ee67bf13", size = 48185, upload-time = "2025-11-27T22:07:51.736Z" }, - { url = "https://files.pythonhosted.org/packages/9e/5d/f299535c18009addb866d89a7044f1086e4eecf50542e57ff5bb15840195/pyppmd-1.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e773d8353b36f7e7973a43526993fb276b98a97839cb5dc8f4e6465ad873f41a", size = 48496, upload-time = "2025-11-27T22:07:52.866Z" }, - { url = "https://files.pythonhosted.org/packages/aa/9d/4a59b73ea8e305f9192ee26ceb7c3d57e17ccb9bcca0e99ef335db29fcf7/pyppmd-1.3.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:37b1883accf840cb0b711785d353f8548853a1401d381da007c0aec362f3ffac", size = 142620, upload-time = "2025-11-27T22:07:54.411Z" }, - { url = "https://files.pythonhosted.org/packages/88/d7/fe32c2a4f8539365e6292aed25545830a5e718a510cdb4caddd6fd8d8056/pyppmd-1.3.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1bd6d179ad39b6191ca0cbe62fb9592f33f49277b4384ad7bc5eb0e6ca27ebee", size = 144306, upload-time = "2025-11-27T22:07:55.727Z" }, - { url = "https://files.pythonhosted.org/packages/20/20/e3907cf263b58f4ed4c5315bc1ac91721c85332fd0d73a89e3e2752904ef/pyppmd-1.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:806cf8d33606e44bf5ff5786c57891f57993f1eef1c763da3c58ea97de3a13c8", size = 139522, upload-time = "2025-11-27T22:07:57.034Z" }, - { url = "https://files.pythonhosted.org/packages/0a/03/22d5f93251e481f23bf741dde7f59cfc3e315f60b32b63f215a2d7bb8944/pyppmd-1.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1826cbce9a2c944aa08df79310a7e6d4a61fd20636b6dff64a77ea4bc43da30f", size = 142340, upload-time = "2025-11-27T22:07:58.49Z" }, - { url = "https://files.pythonhosted.org/packages/34/88/38d36c1394d5e331db9708dae08863c87c059bd5b6e43b20234be02a0503/pyppmd-1.3.1-cp312-cp312-win32.whl", hash = "sha256:d3ff96671319318d941dd34300d641745048e8a3251b077bddf98652d6ddc513", size = 42102, upload-time = "2025-11-27T22:08:00.124Z" }, - { url = "https://files.pythonhosted.org/packages/ee/29/1398c6f67dfb66367babeed2980caee837d0a488705e3dff7ff159e017e7/pyppmd-1.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:c8c1ad39e7ebde71bf5a54cf61f489bf4790f1dd0beb70dc2e8f5ad3329d7ca7", size = 46957, upload-time = "2025-11-27T22:08:01.587Z" }, - { url = "https://files.pythonhosted.org/packages/d2/41/ee3193c16472a5a1c5f1965abb8fa87a7ad455c39688e9a0c2d87d6a7027/pyppmd-1.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:391b2bf76d7dc45b343781754d0b734dcbf539b92667986a343f5488c4bf9ca0", size = 45214, upload-time = "2025-11-27T22:08:02.721Z" }, - { url = "https://files.pythonhosted.org/packages/f0/01/7cc3854b0e1304b90d2435e946db5e1c42e103adf840a68400002fc838b4/pyppmd-1.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:4b4edb3e9619fd0bc39c1a07eb03e8731db833a93b23134f36c7ef581a94b37a", size = 77800, upload-time = "2025-11-27T22:08:03.856Z" }, - { url = "https://files.pythonhosted.org/packages/dd/02/caf6305224b9432ea7373670d101392f620c1ce741d6f95458b1fb9a16a4/pyppmd-1.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b8b5c813e462c91048b88e2adfbcc0c69f2c905f70097001d32066f86f675bd4", size = 48195, upload-time = "2025-11-27T22:08:05.323Z" }, - { url = "https://files.pythonhosted.org/packages/45/82/8c5d3f0f738a3cf5c209d1471efda105a16417c0aaa91a2ad39efb3d4efb/pyppmd-1.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e8d372d9fac382183e0371cf0c2d736b494b1857a1befe98d563342b1205265b", size = 48482, upload-time = "2025-11-27T22:08:06.441Z" }, - { url = "https://files.pythonhosted.org/packages/76/9c/09ab8b4f00473f210284dacf837cae8355ad900f3fd8fd98b2bba12de4fa/pyppmd-1.3.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3b765ae21f7ed2f4ea8f32bfd9e3a4a8d738e73fc8f8dcddec9cbe2c898d60be", size = 142828, upload-time = "2025-11-27T22:08:07.611Z" }, - { url = "https://files.pythonhosted.org/packages/64/fd/673d429df719affa1445611288aab6111c0bc87e51eb4677fe2421a1dd64/pyppmd-1.3.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd00522ddfcc292304577386b6c217758c0c10e1fb9ce7877ad7d3b7b821a808", size = 144502, upload-time = "2025-11-27T22:08:08.869Z" }, - { url = "https://files.pythonhosted.org/packages/cf/0f/64dad213f56eaf23210ac7d3a5dedb19ad8f186f8b8682f65e7d51d9fa4a/pyppmd-1.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3de62099ff2ca876c2d39bc547bcba6f7b878988663abd782a5bad4edac3bb44", size = 139718, upload-time = "2025-11-27T22:08:10.114Z" }, - { url = "https://files.pythonhosted.org/packages/74/61/bd5dbe8d7374748687d48f0bc44e6b930470e09552b1bbc65ef899ab673e/pyppmd-1.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:011f845de195d60fe973a635a1f4be981b7d80f357a8acb1b2d83bdf5087c808", size = 142578, upload-time = "2025-11-27T22:08:11.388Z" }, - { url = "https://files.pythonhosted.org/packages/42/ae/0af00a9b1cd59b6821eb182413033d5ddbe05dfcde7511e3a6b1a5b83f6d/pyppmd-1.3.1-cp313-cp313-win32.whl", hash = "sha256:7d61bd01f25289b6ae54832db4254602fb0c6d105f6e6bf0aee39b803b698b98", size = 42102, upload-time = "2025-11-27T22:08:13.297Z" }, - { url = "https://files.pythonhosted.org/packages/6c/6d/311d8faca142f39b4d158c94ed7eb237a197d41dd7eb67f53442a7904e04/pyppmd-1.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:df8d84ab72381058a964ba66e5e81ed52dbd0b5ad734a5ef8353452983506098", size = 46962, upload-time = "2025-11-27T22:08:14.759Z" }, - { url = "https://files.pythonhosted.org/packages/bb/42/24b59b6bb73a3fec96c7f81521d146c2ac89ffcb89cf9e848f62e0660381/pyppmd-1.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:124a04aab6936ba011f9ad57067798c7f052fdb1848b0cc4318606eea55475e6", size = 45209, upload-time = "2025-11-27T22:08:15.933Z" }, -] - -[[package]] -name = "pytest" -version = "8.4.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "iniconfig" }, - { name = "packaging" }, - { name = "pluggy" }, - { name = "pygments" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, -] - -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, -] - -[[package]] -name = "python-decouple" -version = "3.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e1/97/373dcd5844ec0ea5893e13c39a2c67e7537987ad8de3842fe078db4582fa/python-decouple-3.8.tar.gz", hash = "sha256:ba6e2657d4f376ecc46f77a3a615e058d93ba5e465c01bbe57289bfb7cce680f", size = 9612, upload-time = "2023-03-01T19:38:38.143Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/d4/9193206c4563ec771faf2ccf54815ca7918529fe81f6adb22ee6d0e06622/python_decouple-3.8-py3-none-any.whl", hash = "sha256:d0d45340815b25f4de59c974b855bb38d03151d81b037d9e3f463b0c9f8cbd66", size = 9947, upload-time = "2023-03-01T19:38:36.015Z" }, -] - -[[package]] -name = "python-gitlab" -version = "5.6.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "requests" }, - { name = "requests-toolbelt" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d6/08/d35d0f28549c43611e942f39b6321a7b35c02189d5badceefe601c0207ce/python_gitlab-5.6.0.tar.gz", hash = "sha256:bc531e8ba3e5641b60409445d4919ace68a2c18cb0ec6d48fbced6616b954166", size = 396148, upload-time = "2025-01-28T16:33:39.136Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/5e/2e1ed7145835afaad87664b2f675a1d7b6e1211ad6ecfe57e200ae45f0bd/python_gitlab-5.6.0-py3-none-any.whl", hash = "sha256:68980cd70929fc7f8f06d8a7b09bd046a6b79e1995c19d61249f046005099100", size = 148836, upload-time = "2025-01-28T16:33:36.818Z" }, -] - -[[package]] -name = "python-magic" -version = "0.4.24" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.13'", -] -sdist = { url = "https://files.pythonhosted.org/packages/3a/70/76b185393fecf78f81c12f9dc7b1df814df785f6acb545fc92b016e75a7e/python-magic-0.4.24.tar.gz", hash = "sha256:de800df9fb50f8ec5974761054a708af6e4246b03b4bdaee993f948947b0ebcf", size = 17295, upload-time = "2021-06-03T13:49:24.116Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/99/c89223c6547df268596899334ee77b3051f606077317023617b1c43162fb/python_magic-0.4.24-py2.py3-none-any.whl", hash = "sha256:4fec8ee805fea30c07afccd1592c0f17977089895bdfaae5fec870a84e997626", size = 12785, upload-time = "2021-06-03T13:49:22.828Z" }, -] - -[[package]] -name = "python-magic" -version = "0.4.27" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.13'", -] -sdist = { url = "https://files.pythonhosted.org/packages/da/db/0b3e28ac047452d079d375ec6798bf76a036a08182dbb39ed38116a49130/python-magic-0.4.27.tar.gz", hash = "sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b", size = 14677, upload-time = "2022-06-07T20:16:59.508Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/73/9f872cb81fc5c3bb48f7227872c28975f998f3e7c2b1c16e95e6432bbb90/python_magic-0.4.27-py2.py3-none-any.whl", hash = "sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3", size = 13840, upload-time = "2022-06-07T20:16:57.763Z" }, -] - -[[package]] -name = "python-semantic-release" -version = "9.21.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "click-option-group" }, - { name = "deprecated" }, - { name = "dotty-dict" }, - { name = "gitpython" }, - { name = "importlib-resources" }, - { name = "jinja2" }, - { name = "pydantic" }, - { name = "python-gitlab" }, - { name = "requests" }, - { name = "rich" }, - { name = "shellingham" }, - { name = "tomlkit" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1d/2b/8395fa3b7bd31fd4d0a4181ad660b2f8ee14961334945d3bfc6e73dec21a/python_semantic_release-9.21.2.tar.gz", hash = "sha256:84d4f90f77528bcc0cbdcf3bc262172e3a4eeb7670b82fa45b4c859455e9cdf2", size = 308976, upload-time = "2026-07-04T01:11:42.085Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/42/95/9059bf101c4147dd18c65f9323b57c2a3e1e152ff12a6a316ab7562d79bb/python_semantic_release-9.21.2-py3-none-any.whl", hash = "sha256:856b13c025aadf52180d5704477c2e3582fc0d5f6b2746641d3006b9eed4a0b4", size = 132839, upload-time = "2026-07-04T01:11:40.502Z" }, -] - -[[package]] -name = "python-slugify" -version = "8.0.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "text-unidecode" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/87/c7/5e1547c44e31da50a460df93af11a535ace568ef89d7a811069ead340c4a/python-slugify-8.0.4.tar.gz", hash = "sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856", size = 10921, upload-time = "2024-02-08T18:32:45.488Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/62/02da182e544a51a5c3ccf4b03ab79df279f9c60c5e82d5e8bec7ca26ac11/python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8", size = 10051, upload-time = "2024-02-08T18:32:43.911Z" }, -] - -[[package]] -name = "pyyaml" -version = "6.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, - { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, - { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, - { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, - { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, - { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, - { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, - { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, - { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, - { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, - { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, - { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, - { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, - { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, - { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, - { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, - { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, - { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, - { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, - { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, -] - -[[package]] -name = "referencing" -version = "0.37.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "attrs" }, - { name = "rpds-py" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, -] - -[[package]] -name = "requests" -version = "2.34.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "charset-normalizer" }, - { name = "idna" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, -] - -[[package]] -name = "requests-file" -version = "3.0.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3c/f8/5dc70102e4d337063452c82e1f0d95e39abfe67aa222ed8a5ddeb9df8de8/requests_file-3.0.1.tar.gz", hash = "sha256:f14243d7796c588f3521bd423c5dea2ee4cc730e54a3cac9574d78aca1272576", size = 6967, upload-time = "2025-10-20T18:56:42.279Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/d5/de8f089119205a09da657ed4784c584ede8381a0ce6821212a6d4ca47054/requests_file-3.0.1-py2.py3-none-any.whl", hash = "sha256:d0f5eb94353986d998f80ac63c7f146a307728be051d4d1cd390dbdb59c10fa2", size = 4514, upload-time = "2025-10-20T18:56:41.184Z" }, -] - -[[package]] -name = "requests-toolbelt" -version = "1.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" }, -] - -[[package]] -name = "rich" -version = "14.3.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markdown-it-py" }, - { name = "pygments" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e9/67/cae617f1351490c25a4b8ac3b8b63a4dda609295d8222bad12242dfdc629/rich-14.3.4.tar.gz", hash = "sha256:817e02727f2b25b40ef56f5aa2217f400c8489f79ca8f46ea2b70dd5e14558a9", size = 230524, upload-time = "2026-04-11T02:57:45.419Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/76/6d163cfac87b632216f71879e6b2cf17163f773ff59c00b5ff4900a80fa3/rich-14.3.4-py3-none-any.whl", hash = "sha256:07e7adb4690f68864777b1450859253bed81a99a31ac321ac1817b2313558952", size = 310480, upload-time = "2026-04-11T02:57:47.484Z" }, -] - -[[package]] -name = "roman-numerals" -version = "4.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ae/f9/41dc953bbeb056c17d5f7a519f50fdf010bd0553be2d630bc69d1e022703/roman_numerals-4.1.0.tar.gz", hash = "sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2", size = 9077, upload-time = "2025-12-17T18:25:34.381Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676, upload-time = "2025-12-17T18:25:33.098Z" }, -] - -[[package]] -name = "roman-numerals-py" -version = "4.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "roman-numerals" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cb/b5/de96fca640f4f656eb79bbee0e79aeec52e3e0e359f8a3e6a0d366378b64/roman_numerals_py-4.1.0.tar.gz", hash = "sha256:f5d7b2b4ca52dd855ef7ab8eb3590f428c0b1ea480736ce32b01fef2a5f8daf9", size = 4274, upload-time = "2025-12-17T18:25:41.153Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/2c/daca29684cbe9fd4bc711f8246da3c10adca1ccc4d24436b17572eb2590e/roman_numerals_py-4.1.0-py3-none-any.whl", hash = "sha256:553114c1167141c1283a51743759723ecd05604a1b6b507225e91dc1a6df0780", size = 4547, upload-time = "2025-12-17T18:25:40.136Z" }, -] - -[[package]] -name = "rpds-py" -version = "2026.6.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/2a/9618a122aeb2a169a28b03889a2995fe297588964333d4a7d67bdf46e147/rpds_py-2026.6.3.tar.gz", hash = "sha256:1cebd1337c242e4ec2293e541f712b2da849b29f48f0c293684b71c0632625d4", size = 64051, upload-time = "2026-06-30T07:17:53.009Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/be/2e8974163072e7bab7df1a5acd54c4498e75e35d6d18b864d3a9d5dadc92/rpds_py-2026.6.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a0811d33247c3d6128a3001d763f2aa056bb3425204335400ac54f89eec3a0d0", size = 343691, upload-time = "2026-06-30T07:15:14.96Z" }, - { url = "https://files.pythonhosted.org/packages/a4/73/319dfa745dd668efe89309141ded489126461fcecd2b8f3a3cda185129b6/rpds_py-2026.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:538949e262e46caa31ac01bdb3c1e8f642622922cacbabbae6a8445d9dc33eaf", size = 338542, upload-time = "2026-06-30T07:15:16.267Z" }, - { url = "https://files.pythonhosted.org/packages/21/63/4239893be1c4d09b709b1a8f6be4188f0870084ff547f46606b8a75f1b03/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55927d532399c2c646100ff7feb48eaa940ad70f42cd68e1328f3ded9f81ca24", size = 368180, upload-time = "2026-06-30T07:15:17.62Z" }, - { url = "https://files.pythonhosted.org/packages/1c/ca/9c5de382225234ceb37b1844ebdb140db12b2a278bb9efe2fcd19f6c82ce/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f56f1695bc5c0871cbc33dc0130fcf503aab0c57dcc5a6700a4f49eba4f2652e", size = 375067, upload-time = "2026-06-30T07:15:18.952Z" }, - { url = "https://files.pythonhosted.org/packages/87/dc/863f69d1bf04ade34b7fe0d59b9fdf6f0135fe2d7cbca74f1d665589559d/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:270b293dae9058fc9fcedab50f13cebf46fb8ed1d1d54e0521a9da5d6b211975", size = 490509, upload-time = "2026-06-30T07:15:20.434Z" }, - { url = "https://files.pythonhosted.org/packages/ce/ef/eac16a12048b45ec7c7fa94f2be3438a5f26bf9cc8580b18a1cfd609b7f6/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:127565fead0a10943b282957bd5447804ff3160ad79f2ad2635e6d249e380680", size = 382754, upload-time = "2026-06-30T07:15:21.831Z" }, - { url = "https://files.pythonhosted.org/packages/04/8f/d2f3f532616be4d06c316ef119683e832bd3d41e112bf3a88f4151c95b17/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecabd69db66de867690f9797f2f8fa27ba501bbc24540cbdbdc649cd15888ba6", size = 366189, upload-time = "2026-06-30T07:15:23.371Z" }, - { url = "https://files.pythonhosted.org/packages/e3/29/41a7b0e98a4b44cd676ab7598419623373eb43b20be68c084935c1a8cf88/rpds_py-2026.6.3-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:58eadac9cd119677b60e1cf8ac4052f35949d71b8a9e5556efccbe82533cf22a", size = 377750, upload-time = "2026-06-30T07:15:24.659Z" }, - { url = "https://files.pythonhosted.org/packages/2e/05/ecda0bec46f9a1565090bcdc941d023f6a25aff85fda28f89f8d19878152/rpds_py-2026.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7491ee23305ac3eb59e492b6945881f5cd77a6f731061a3f25b77fd40f9e99a4", size = 395576, upload-time = "2026-06-30T07:15:25.987Z" }, - { url = "https://files.pythonhosted.org/packages/68/a8/6ed52f03ee6cb854ce78785cc9a9a672eb880e83fd7224d471f667d151f1/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2c99f7e8ccb3dd6e3e4bfeac657a7b208c9bac8075f4b078c02d7404c34107fa", size = 543807, upload-time = "2026-06-30T07:15:27.356Z" }, - { url = "https://files.pythonhosted.org/packages/8f/d6/156c0d3eea27ba09b92562ba2364ba124c0a061b199e17eac637cd25a5e2/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:62698275682bf121181861295c9181e789030a2d516071f5b8f3c23c170cd0fc", size = 611187, upload-time = "2026-06-30T07:15:28.931Z" }, - { url = "https://files.pythonhosted.org/packages/f1/31/774212ed989c62f7f310220089f9b0a3fb8f40f5443d1727abd5d9f52bc9/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a214c993455f99a89aaeadc9b21241900037adc9d97203e374d75513c5911822", size = 573030, upload-time = "2026-06-30T07:15:30.553Z" }, - { url = "https://files.pythonhosted.org/packages/c9/50/22f73127a41f1ce4f87fe39aadfb9a126345801c274aa93ae88456249327/rpds_py-2026.6.3-cp312-cp312-win32.whl", hash = "sha256:501f9f04a588d6a09179368c57071301445191767c64e4b52a6aa9871f1ef5ed", size = 202185, upload-time = "2026-06-30T07:15:32.027Z" }, - { url = "https://files.pythonhosted.org/packages/04/3a/f0ee4d4dde9d3b69dedf1b5f74e7a40017046d55052d173e418c6a94f960/rpds_py-2026.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:2c958bf94822e9290a40aaf2a822d4bc5c88099093e3948ad6c571eca9272e5f", size = 220394, upload-time = "2026-06-30T07:15:33.359Z" }, - { url = "https://files.pythonhosted.org/packages/f3/83/3382fe37f809b59f02aac04dbc4e765b480b46ee0227ed516e3bdc4d3dfc/rpds_py-2026.6.3-cp312-cp312-win_arm64.whl", hash = "sha256:22bffe6042b9bcb0822bcd1955ec00e245daf17b4344e4ed8e9551b976b63e96", size = 215753, upload-time = "2026-06-30T07:15:34.778Z" }, - { url = "https://files.pythonhosted.org/packages/a4/9e/b818ee580026ec578138e961027a68820c40afeb1ec8f6819b54fb99e196/rpds_py-2026.6.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3cfe765c1da0072636ca06628261e0ea05688e160d5c8a03e0217c3854037223", size = 343012, upload-time = "2026-06-30T07:15:36.005Z" }, - { url = "https://files.pythonhosted.org/packages/f3/6b/686d9dc4359a8f163cfbbf89ee0b4e586431de22fe8248edb63a8cf50d49/rpds_py-2026.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f4d78253f6996be4901669ad25319f842f740eccf4d58e3c7f3dd39e6dde1d8f", size = 338203, upload-time = "2026-06-30T07:15:37.462Z" }, - { url = "https://files.pythonhosted.org/packages/9e/9b/069aa329940f8207615e091f5eedbbd40e1e15eac68a0790fd05ccdf796c/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54f45a148e28767bf343d33a684693c70e451c6f4c0e9904709a723fafbdfc1f", size = 367984, upload-time = "2026-06-30T07:15:39.008Z" }, - { url = "https://files.pythonhosted.org/packages/14/db/34c203e4becff3703e4d3bc121842c00b8689197f398161203a880052f4e/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:842e7b070435622248c7a2c44ae53fa1440e073cc3023bc919fed570884097a7", size = 374815, upload-time = "2026-06-30T07:15:40.253Z" }, - { url = "https://files.pythonhosted.org/packages/ee/7d/8071067d2cc453d916ad836e828c943f575e8a44612537759002a1e07381/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8020133a74bd81b4572dd8e4be028a6b1ebcd70e6726edc3918008c08bee6ee6", size = 490545, upload-time = "2026-06-30T07:15:41.729Z" }, - { url = "https://files.pythonhosted.org/packages/a3/42/da06c5aa8f0484ff07f270787434204d9f4535e2f8c3b51ed402267e63c3/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cdc7e35386f3847df728fbcb5e887e2d79c19e2fa1eba9e51b6621d23e3243af", size = 382828, upload-time = "2026-06-30T07:15:43.327Z" }, - { url = "https://files.pythonhosted.org/packages/57/d7/fe978efc2ae50abe48eb7464668ea99f53c010c60aeebb7b35ad27f23661/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acac386b453c2516111b50985d60ce46e7fadb5ea71ae7b25f4c946935bf27cf", size = 365678, upload-time = "2026-06-30T07:15:44.992Z" }, - { url = "https://files.pythonhosted.org/packages/69/9d/1d8922e1990b2a6eb532b6ff53d3e73d2b3bbffc84116c75826bee73dfc6/rpds_py-2026.6.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:425560c6fa0415f27261727bb20bd097568485e5eb0c121f1949417d1c516885", size = 377811, upload-time = "2026-06-30T07:15:46.523Z" }, - { url = "https://files.pythonhosted.org/packages/b1/3d/198dceafb4fb034a6a47347e1b0735d34e0bd4a50be4e898d408ee66cb14/rpds_py-2026.6.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a550fb4950a06dde3beb4721f5ad4b25bf4513784665b0a8522c792e2bd822a4", size = 395382, upload-time = "2026-06-30T07:15:47.955Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f1/13968e49655d40b6b19d8b9140296bbc6f1d86b3f0f6c346cf9f1adddf4b/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f4bca01b63096f606e095734dd56e74e175f94cfbf24ff3d63281cec61f7bb7", size = 543832, upload-time = "2026-06-30T07:15:49.33Z" }, - { url = "https://files.pythonhosted.org/packages/ac/ab/289bcb1b90bd3e40a2900c561fa0e2087345ecbb094f0b870f2345142b7c/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ccffae9a092a00deb7efd545fe5e2c33c33b88e7c054337e9a74c179347d0b7d", size = 611011, upload-time = "2026-06-30T07:15:50.847Z" }, - { url = "https://files.pythonhosted.org/packages/1e/16/5043105e679436ccfbc8e5e0dd2d663ed18a8b8113515fd06a5e5d77c83e/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1cf01971c4f2c5553b772a542e4aaf191789cd331bc2cd4ff0e6e65ba49e1e97", size = 572431, upload-time = "2026-06-30T07:15:52.394Z" }, - { url = "https://files.pythonhosted.org/packages/85/ed/adab103321c0a6565d5ae1c2998349bc3ee175b82ccc5ae8fc04cc413075/rpds_py-2026.6.3-cp313-cp313-win32.whl", hash = "sha256:8c3d1e9c15b9d51ca0391e13da1a25a0a4df3c58a37c9dc368e0736cf7f69df0", size = 201710, upload-time = "2026-06-30T07:15:53.894Z" }, - { url = "https://files.pythonhosted.org/packages/7b/ed/a03b09668e74e5dabbf2e211f6468e1820c0552f7b0500082da31841bf7b/rpds_py-2026.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:9250a9a0a6fd4648b3f868da8d91a4c52b5811a62df58e753d50ae4454a36f80", size = 219454, upload-time = "2026-06-30T07:15:55.25Z" }, - { url = "https://files.pythonhosted.org/packages/27/17/b8642c12930b71bc2b25831f6708ccf0f75abcd11883932ec9ce54ba3a78/rpds_py-2026.6.3-cp313-cp313-win_arm64.whl", hash = "sha256:900a67df3fd1660b035a4761c4ce73c382ea6b35f90f9863c36c6fd8bf8b09bb", size = 215063, upload-time = "2026-06-30T07:15:56.573Z" }, -] - -[[package]] -name = "ruamel-yaml" -version = "0.19.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/3b/ebda527b56beb90cb7652cb1c7e4f91f48649fbcd8d2eb2fb6e77cd3329b/ruamel_yaml-0.19.1.tar.gz", hash = "sha256:53eb66cd27849eff968ebf8f0bf61f46cdac2da1d1f3576dd4ccee9b25c31993", size = 142709, upload-time = "2026-01-02T16:50:31.84Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/0c/51f6841f1d84f404f92463fc2b1ba0da357ca1e3db6b7fbda26956c3b82a/ruamel_yaml-0.19.1-py3-none-any.whl", hash = "sha256:27592957fedf6e0b62f281e96effd28043345e0e66001f97683aa9a40c667c93", size = 118102, upload-time = "2026-01-02T16:50:29.201Z" }, -] - -[[package]] -name = "shellingham" -version = "1.5.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, -] - -[[package]] -name = "six" -version = "1.17.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, -] - -[[package]] -name = "smmap" -version = "5.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1f/ea/49c993d6dfdd7338c9b1000a0f36817ed7ec84577ae2e52f890d1a4ff909/smmap-5.0.3.tar.gz", hash = "sha256:4d9debb8b99007ae47165abc08670bd74cb74b5227dda7f643eccc4e9eb5642c", size = 22506, upload-time = "2026-03-09T03:43:26.1Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl", hash = "sha256:c106e05d5a61449cf6ba9a1e650227ecfb141590d2a98412103ff35d89fc7b2f", size = 24390, upload-time = "2026-03-09T03:43:24.361Z" }, -] - -[[package]] -name = "snowballstemmer" -version = "3.1.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/f8/0a71edf031f03c40db17503cb8ca78a69a171254e568e7db241b0ab57ea1/snowballstemmer-3.1.1.tar.gz", hash = "sha256:e07bbc54a0d798fe6010a12398422e62a8bfbba95c394fd0956ef58cb4d3e260", size = 123314, upload-time = "2026-06-03T00:56:40.194Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4c/07/2ebca9b11fb9be7340a818d8d6f63feaebb146be2c4afbd6061701d6df6e/snowballstemmer-3.1.1-py3-none-any.whl", hash = "sha256:7e207fa178741da09cdee59d3ecec3827ad5f92b1fc5c9ff3755b639f71f5752", size = 104164, upload-time = "2026-06-03T00:56:38.614Z" }, -] - -[[package]] -name = "soupsieve" -version = "2.9.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/69/99/a6ca3beb3ccacb41fb3321d8a60e5566f9e6467601ef8eba6a17e1b89778/soupsieve-2.9.2.tar.gz", hash = "sha256:4a55d8cf158a9c2e587fa4922f1bbb91d68ac829e2d6f25403a85747c71daf74", size = 122445, upload-time = "2026-08-07T00:57:24.801Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/dc/ad025c1ee131eba60c69f4dd5779b18fcf1e6b21a343e2162a84d5d133c7/soupsieve-2.9.2-py3-none-any.whl", hash = "sha256:8089a26fd974ca7a1f30276d3d8492ab266ab15af581642dfe8aa162e0c1c823", size = 37370, upload-time = "2026-08-07T00:57:23.524Z" }, -] - -[[package]] -name = "sphinx" -version = "8.2.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "alabaster" }, - { name = "babel" }, - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "docutils" }, - { name = "imagesize" }, - { name = "jinja2" }, - { name = "packaging" }, - { name = "pygments" }, - { name = "requests" }, - { name = "roman-numerals-py" }, - { name = "snowballstemmer" }, - { name = "sphinxcontrib-applehelp" }, - { name = "sphinxcontrib-devhelp" }, - { name = "sphinxcontrib-htmlhelp" }, - { name = "sphinxcontrib-jsmath" }, - { name = "sphinxcontrib-qthelp" }, - { name = "sphinxcontrib-serializinghtml" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/38/ad/4360e50ed56cb483667b8e6dadf2d3fda62359593faabbe749a27c4eaca6/sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348", size = 8321876, upload-time = "2025-03-02T22:31:59.658Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/31/53/136e9eca6e0b9dc0e1962e2c908fbea2e5ac000c2a2fbd9a35797958c48b/sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3", size = 3589741, upload-time = "2025-03-02T22:31:56.836Z" }, -] - -[[package]] -name = "sphinx-book-theme" -version = "1.1.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pydata-sphinx-theme" }, - { name = "sphinx" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/89/69/61dfa3b3851610b5f45960737bd99f8c5b2d70ba73f9ac84a527e0c564ae/sphinx_book_theme-1.1.3.tar.gz", hash = "sha256:1f25483b1846cb3d353a6bc61b3b45b031f4acf845665d7da90e01ae0aef5b4d", size = 434230, upload-time = "2024-06-12T14:11:22.128Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/80/90574e2e82c955b9c6f6b77f7badb2cf2ef4ef77599e4343cced2d098681/sphinx_book_theme-1.1.3-py3-none-any.whl", hash = "sha256:a554a9a7ac3881979a87a2b10f633aa2a5706e72218a10f71be38b3c9e831ae9", size = 430129, upload-time = "2024-06-12T14:11:20.002Z" }, -] - -[[package]] -name = "sphinx-codelinks" -version = "1.4.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "comment-parser", version = "1.2.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "comment-parser", version = "1.2.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, - { name = "docutils" }, - { name = "giturlparse" }, - { name = "ignore-python" }, - { name = "jinja2" }, - { name = "jsonschema" }, - { name = "pygments" }, - { name = "sphinx" }, - { name = "sphinx-needs" }, - { name = "tree-sitter" }, - { name = "tree-sitter-bash" }, - { name = "tree-sitter-c-sharp" }, - { name = "tree-sitter-cpp" }, - { name = "tree-sitter-go" }, - { name = "tree-sitter-json" }, - { name = "tree-sitter-python" }, - { name = "tree-sitter-rust" }, - { name = "tree-sitter-yaml" }, - { name = "typer" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/30/ae/bf4b642c72b7d383723ea9ef1830d5fef5d5505165fa83e3ee1c547aea5e/sphinx_codelinks-1.4.0.tar.gz", hash = "sha256:bf8a5f88ad4747f4964d4e4da23999a4608db6a86e4935151c17b56338d6f50d", size = 47372, upload-time = "2026-07-30T16:09:57.83Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/28/52/95e8e7020619e8750fbb6ebfa4470d6c5ff2d0adebd37fcbc777065f510b/sphinx_codelinks-1.4.0-py3-none-any.whl", hash = "sha256:5cdc712a327b6ba09ea56836392cc750beaf157a342f0853ee0a773b887a7534", size = 54875, upload-time = "2026-07-30T16:09:56.623Z" }, -] - -[[package]] -name = "sphinx-copybutton" -version = "0.5.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "sphinx" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/fc/2b/a964715e7f5295f77509e59309959f4125122d648f86b4fe7d70ca1d882c/sphinx-copybutton-0.5.2.tar.gz", hash = "sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd", size = 23039, upload-time = "2023-04-14T08:10:22.998Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/48/1ea60e74949eecb12cdd6ac43987f9fd331156388dcc2319b45e2ebb81bf/sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e", size = 13343, upload-time = "2023-04-14T08:10:20.844Z" }, -] - -[[package]] -name = "sphinx-data-viewer" -version = "0.1.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "sphinx" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/71/3a/7aeeb805327af5b0e6f072312f9bc660615045e791a5a8f7258276fdddd1/sphinx_data_viewer-0.1.5.tar.gz", hash = "sha256:a7d5e58613562bb745380bfe61ca8b69997998167fd6fa9aea55606c9a4b17e4", size = 103260, upload-time = "2024-08-28T10:34:21.427Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/57/60/5e902d53a5eb3ec7166a8eda602bd78d4b8671a73917567edc8f13a3b366/sphinx_data_viewer-0.1.5-py3-none-any.whl", hash = "sha256:b74b1d304c505c464d07c7b225ed0d84ea02dcc88bc1c49cdad7c2275fbbdad4", size = 8215, upload-time = "2024-08-28T10:34:20.136Z" }, -] - -[[package]] -name = "sphinx-design" -version = "0.6.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "sphinx" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2b/69/b34e0cb5336f09c6866d53b4a19d76c227cdec1bbc7ac4de63ca7d58c9c7/sphinx_design-0.6.1.tar.gz", hash = "sha256:b44eea3719386d04d765c1a8257caca2b3e6f8421d7b3a5e742c0fd45f84e632", size = 2193689, upload-time = "2024-08-02T13:48:44.277Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/43/65c0acbd8cc6f50195a3a1fc195c404988b15c67090e73c7a41a9f57d6bd/sphinx_design-0.6.1-py3-none-any.whl", hash = "sha256:b11f37db1a802a183d61b159d9a202314d4d2fe29c163437001324fe2f19549c", size = 2215338, upload-time = "2024-08-02T13:48:42.106Z" }, -] - -[[package]] -name = "sphinx-mounts" -version = "0.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "ignore-python" }, - { name = "sphinx" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ed/06/85e09e90824dbf9b81c4f60fe156855d13249c1f1f7b208452b75d8fdc34/sphinx_mounts-0.2.0.tar.gz", hash = "sha256:7c19e867b463bccbff35281e2c6c20dfaa75d4fa88eef6abe97c9c371df6914b", size = 98441, upload-time = "2026-08-27T11:42:44.383Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c4/4c/c92fe023dec5204dae85690fc110b47d0c6a0fe9e57c2f8b8d82228ef168/sphinx_mounts-0.2.0-py3-none-any.whl", hash = "sha256:9e43cba60834bb648a7d3f0f43bb3fbbf9743e69933a89081a90a30d6d42d230", size = 100201, upload-time = "2026-08-27T11:42:43.045Z" }, -] - -[[package]] -name = "sphinx-needs" -version = "8.5.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jsonschema-rs" }, - { name = "minijinja" }, - { name = "requests" }, - { name = "requests-file" }, - { name = "sphinx" }, - { name = "sphinx-data-viewer" }, - { name = "sphinxcontrib-jquery" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a8/c2/9db8259372467a85da7d07aa9f642d292f56feadebb37caab73683bbcfed/sphinx_needs-8.5.0.tar.gz", hash = "sha256:42d327baaf39f5041f692cb85609ef7023c0a73b688cde5803db444e577bb63d", size = 28245371, upload-time = "2026-09-03T11:42:25.929Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/02/96/e0a0b6fdf37460b7aaf8fb887922138aa23bbef7635ddaf09fb64823927a/sphinx_needs-8.5.0-py3-none-any.whl", hash = "sha256:aa7548d1ec33cd66cd0223cf8d5c0a934dc4c67d67ad471b5351b817d5f02750", size = 2767023, upload-time = "2026-09-03T11:42:23.717Z" }, -] - -[[package]] -name = "sphinx-new-tab-link" -version = "0.8.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "sphinx" }, - { name = "sphinxcontrib-extdevhelper-kasane" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d2/b9/ee505f904abefa6b574736c93813275a22821933cf2293c83893107eecb6/sphinx_new_tab_link-0.8.2.tar.gz", hash = "sha256:8a339fbfcb1712db8eb3d503c61b86da11a52f1216bd308d6f6dcec6139dd8ea", size = 7820, upload-time = "2026-08-26T13:08:21.037Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/65/67/ff27727d2a27dcd8e7c5a3fc27102aacd2063dbb33364d4ad245bd915266/sphinx_new_tab_link-0.8.2-py3-none-any.whl", hash = "sha256:c347d2a228d24f522d8b2c7e9aa453fcea87cfc0a98f50617a4dac702e9e3f9f", size = 6154, upload-time = "2026-08-26T13:08:19.713Z" }, -] - -[[package]] -name = "sphinx-test-reports" -version = "1.4.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "lxml" }, - { name = "packaging" }, - { name = "sphinx" }, - { name = "sphinx-needs" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/67/e1/1e1fe898f7ec7ce651ca8738548459a721fdb9d170c4a96f3faccdce9cb0/sphinx_test_reports-1.4.0.tar.gz", hash = "sha256:8a7918946453802380a352b4685f3bdb59efd98cb026921145381c5f558c0a4b", size = 20125, upload-time = "2026-06-16T19:58:05.682Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/b3/283af1e4ad11c88aee669cc170849b028170ec56b6bb6e24e62db3fbdae6/sphinx_test_reports-1.4.0-py3-none-any.whl", hash = "sha256:1ac7af2bc437a4ff3da580d8f7b49eef8f534d4973b8880880833751fa96a794", size = 27336, upload-time = "2026-06-16T19:58:04.399Z" }, -] - -[[package]] -name = "sphinxcontrib-applehelp" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, -] - -[[package]] -name = "sphinxcontrib-datatemplates" -version = "0.11.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "defusedxml" }, - { name = "pyyaml" }, - { name = "sphinx" }, - { name = "sphinxcontrib-runcmd" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b9/9e/8ac54a6a3e7a00339f417568899b64a0c0d622429db73cc1a28c8122c8e2/sphinxcontrib.datatemplates-0.11.0.tar.gz", hash = "sha256:793222e803430076341509cc167f8d715830b05e418c885313101d60fd442557", size = 30996, upload-time = "2023-12-21T14:59:10.199Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/8d/7a7dd95ad1eedec8dc770570c8b1f3dc1d13357383635607b6629ccf329c/sphinxcontrib.datatemplates-0.11.0-py3-none-any.whl", hash = "sha256:88d02f5edab32b88211ebb72a90553e3676a5737877bad1de412f84058ac282e", size = 12493, upload-time = "2023-12-21T14:59:07.96Z" }, -] - -[[package]] -name = "sphinxcontrib-devhelp" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, -] - -[[package]] -name = "sphinxcontrib-extdevhelper-kasane" -version = "0.2.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "sphinx" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ec/18/ac3c641c56ea92a66c311353833e004884560fb67545055ec3c2829855db/sphinxcontrib_extdevhelper_kasane-0.2.1.tar.gz", hash = "sha256:97576f7f09216bc57e54d44a1e92091bafb56cfa90dc26dad7508df158edbc6f", size = 4514, upload-time = "2025-10-08T12:30:23.574Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/52/d513a1fa3d6d66564d1f5f442d085def8c3eb19f695d2e7fc5e2f0ac1bdb/sphinxcontrib_extdevhelper_kasane-0.2.1-py3-none-any.whl", hash = "sha256:dfa63809d1c4b3cab62aae74854dcb1a2c7d797cf52bb496e9806d5af032cdf9", size = 3936, upload-time = "2025-10-08T12:30:22.337Z" }, -] - -[[package]] -name = "sphinxcontrib-htmlhelp" -version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, -] - -[[package]] -name = "sphinxcontrib-jquery" -version = "4.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "sphinx" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/de/f3/aa67467e051df70a6330fe7770894b3e4f09436dea6881ae0b4f3d87cad8/sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a", size = 122331, upload-time = "2023-03-14T15:01:01.944Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/85/749bd22d1a68db7291c89e2ebca53f4306c3f205853cf31e9de279034c3c/sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae", size = 121104, upload-time = "2023-03-14T15:01:00.356Z" }, -] - -[[package]] -name = "sphinxcontrib-jsmath" -version = "1.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, -] - -[[package]] -name = "sphinxcontrib-mermaid" -version = "2.0.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jinja2" }, - { name = "pyyaml" }, - { name = "sphinx" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/51/29/54cf1f7e03630ca4859caba25bc192698954d7c630377c62d1e264715c37/sphinxcontrib_mermaid-2.0.3.tar.gz", hash = "sha256:a6865ef6b65b225c5403a3170de63a04a07227cada11a4a71a6b87b4f9ed185a", size = 20764, upload-time = "2026-07-08T00:30:44.216Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/57/b39c7a69b70a3ce999732bdb57fcaac78fb3cc8e2843a3daf1c1f821bc9d/sphinxcontrib_mermaid-2.0.3-py3-none-any.whl", hash = "sha256:f001ed36a55c108f6221a2d656a441c487ee30651b54db72b7c752a20c7a66e8", size = 15401, upload-time = "2026-07-08T00:30:42.877Z" }, -] - -[[package]] -name = "sphinxcontrib-plantuml" -version = "0.31" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "sphinx" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/08/8f/f26dd61b92176c4cba6b7898ca2875e7dfc29263d8e1b3b63d6acf81434f/sphinxcontrib_plantuml-0.31.tar.gz", hash = "sha256:fd74752f8ea070e641c3f8a402fccfa1d4a4056e0967b56033d2a76282d9f956", size = 15736, upload-time = "2025-09-03T11:11:35.028Z" } - -[[package]] -name = "sphinxcontrib-qthelp" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, -] - -[[package]] -name = "sphinxcontrib-runcmd" -version = "0.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "sphinx" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a2/03/6eb30814c9839f36131284a46ec9fc39d7bd356078648bc7125d5d1c05e8/sphinxcontrib-runcmd-0.2.0.tar.gz", hash = "sha256:3551c389d9c5fe82d693c7222feb9658b1a1a5a1abcb0063e8385e5528c64c76", size = 5376, upload-time = "2018-11-08T20:19:34.32Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/83/d9/67a79080b5d9fcb367470af9e525a9c53122e95744665de09462dcd676d8/sphinxcontrib_runcmd-0.2.0-py2.py3-none-any.whl", hash = "sha256:7b739b68e27210b4c7c12ba16e5b3da7b313c49991401f896d29bea0f0771934", size = 6043, upload-time = "2018-11-08T20:19:33.17Z" }, -] - -[[package]] -name = "sphinxcontrib-serializinghtml" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, -] - -[[package]] -name = "spl-core" -version = "8.6.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "clanguru" }, - { name = "cookiecutter" }, - { name = "gcovr" }, - { name = "hammocking" }, - { name = "junitparser" }, - { name = "kconfiglib" }, - { name = "mlx-traceability" }, - { name = "myst-parser" }, - { name = "py-app-dev" }, - { name = "pydata-sphinx-theme" }, - { name = "pypeline-runner" }, - { name = "pypeline-semantic-release" }, - { name = "sphinx" }, - { name = "sphinx-book-theme" }, - { name = "sphinx-copybutton" }, - { name = "sphinx-design" }, - { name = "sphinx-needs" }, - { name = "sphinx-new-tab-link" }, - { name = "sphinx-test-reports" }, - { name = "sphinxcontrib-datatemplates" }, - { name = "sphinxcontrib-mermaid" }, - { name = "sphinxcontrib-plantuml" }, - { name = "typer" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/04/8f/f074fb9dcfeb08a9dd6c1347b0712b963c53bc1e736d40997e7dd164c9dd/spl_core-8.6.0.tar.gz", hash = "sha256:f9dfd196590ee748a022818ba7eeffa3adb786f7a34e21a37da1d27e4b9b1c1a", size = 121821, upload-time = "2026-07-16T16:15:05.084Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/25/a7/596181b506ee13325c561b4477095798a6a2a3bee84dad7da2b555e34784/spl_core-8.6.0-py3-none-any.whl", hash = "sha256:8cf849d77fd7478c4e3d55b28d18a98fe958a335df4ffbdba2767aaa112b49a1", size = 138118, upload-time = "2026-07-16T16:15:03.589Z" }, -] - -[[package]] -name = "spled" -version = "0.0.1" -source = { virtual = "." } -dependencies = [ - { name = "myst-parser" }, - { name = "object-deps-report" }, - { name = "poks" }, - { name = "pypeline-runner" }, - { name = "pypeline-semantic-release" }, - { name = "pytest" }, - { name = "sphinx-codelinks" }, - { name = "sphinx-mounts" }, - { name = "spl-core" }, -] - -[package.metadata] -requires-dist = [ - { name = "myst-parser", specifier = ">=4.0,<5" }, - { name = "object-deps-report", specifier = ">=0,<1" }, - { name = "poks", specifier = ">=0.9.4,<1" }, - { name = "pypeline-runner", specifier = ">=1.33,<2" }, - { name = "pypeline-semantic-release", specifier = ">=0,<1" }, - { name = "pytest", specifier = ">=8,<9" }, - { name = "sphinx-codelinks", specifier = ">=1.2,<2" }, - { name = "sphinx-mounts", specifier = ">=0.2,<1" }, - { name = "spl-core", specifier = "==8.6.0" }, -] - -[[package]] -name = "text-unidecode" -version = "1.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ab/e2/e9a00f0ccb71718418230718b3d900e71a5d16e701a3dae079a21e9cd8f8/text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93", size = 76885, upload-time = "2019-08-30T21:36:45.405Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/a5/c0b6468d3824fe3fde30dbb5e1f687b291608f9473681bbf7dabbf5a87d7/text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", size = 78154, upload-time = "2019-08-30T21:37:03.543Z" }, -] - -[[package]] -name = "texttable" -version = "1.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1c/dc/0aff23d6036a4d3bf4f1d8c8204c5c79c4437e25e0ae94ffe4bbb55ee3c2/texttable-1.7.0.tar.gz", hash = "sha256:2d2068fb55115807d3ac77a4ca68fa48803e84ebb0ee2340f858107a36522638", size = 12831, upload-time = "2023-10-03T09:48:12.272Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/24/99/4772b8e00a136f3e01236de33b0efda31ee7077203ba5967fcc76da94d65/texttable-1.7.0-py2.py3-none-any.whl", hash = "sha256:72227d592c82b3d7f672731ae73e4d1f88cd8e2ef5b075a7a7f01a23a3743917", size = 10768, upload-time = "2023-10-03T09:48:10.434Z" }, -] - -[[package]] -name = "tomlkit" -version = "0.13.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cc/18/0bbf3884e9eaa38819ebe46a7bd25dcd56b67434402b66a58c4b8e552575/tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", size = 185207, upload-time = "2025-06-05T07:13:44.947Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0", size = 38901, upload-time = "2025-06-05T07:13:43.546Z" }, -] - -[[package]] -name = "tree-sitter" -version = "0.25.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/66/7c/0350cfc47faadc0d3cf7d8237a4e34032b3014ddf4a12ded9933e1648b55/tree-sitter-0.25.2.tar.gz", hash = "sha256:fe43c158555da46723b28b52e058ad444195afd1db3ca7720c59a254544e9c20", size = 177961, upload-time = "2025-09-25T17:37:59.751Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/9e/20c2a00a862f1c2897a436b17edb774e831b22218083b459d0d081c9db33/tree_sitter-0.25.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ddabfff809ffc983fc9963455ba1cecc90295803e06e140a4c83e94c1fa3d960", size = 146941, upload-time = "2025-09-25T17:37:34.813Z" }, - { url = "https://files.pythonhosted.org/packages/ef/04/8512e2062e652a1016e840ce36ba1cc33258b0dcc4e500d8089b4054afec/tree_sitter-0.25.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c0c0ab5f94938a23fe81928a21cc0fac44143133ccc4eb7eeb1b92f84748331c", size = 137699, upload-time = "2025-09-25T17:37:36.349Z" }, - { url = "https://files.pythonhosted.org/packages/47/8a/d48c0414db19307b0fb3bb10d76a3a0cbe275bb293f145ee7fba2abd668e/tree_sitter-0.25.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dd12d80d91d4114ca097626eb82714618dcdfacd6a5e0955216c6485c350ef99", size = 607125, upload-time = "2025-09-25T17:37:37.725Z" }, - { url = "https://files.pythonhosted.org/packages/39/d1/b95f545e9fc5001b8a78636ef942a4e4e536580caa6a99e73dd0a02e87aa/tree_sitter-0.25.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b43a9e4c89d4d0839de27cd4d6902d33396de700e9ff4c5ab7631f277a85ead9", size = 635418, upload-time = "2025-09-25T17:37:38.922Z" }, - { url = "https://files.pythonhosted.org/packages/de/4d/b734bde3fb6f3513a010fa91f1f2875442cdc0382d6a949005cd84563d8f/tree_sitter-0.25.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbb1706407c0e451c4f8cc016fec27d72d4b211fdd3173320b1ada7a6c74c3ac", size = 631250, upload-time = "2025-09-25T17:37:40.039Z" }, - { url = "https://files.pythonhosted.org/packages/46/f2/5f654994f36d10c64d50a192239599fcae46677491c8dd53e7579c35a3e3/tree_sitter-0.25.2-cp312-cp312-win_amd64.whl", hash = "sha256:6d0302550bbe4620a5dc7649517c4409d74ef18558276ce758419cf09e578897", size = 127156, upload-time = "2025-09-25T17:37:41.132Z" }, - { url = "https://files.pythonhosted.org/packages/67/23/148c468d410efcf0a9535272d81c258d840c27b34781d625f1f627e2e27d/tree_sitter-0.25.2-cp312-cp312-win_arm64.whl", hash = "sha256:0c8b6682cac77e37cfe5cf7ec388844957f48b7bd8d6321d0ca2d852994e10d5", size = 113984, upload-time = "2025-09-25T17:37:42.074Z" }, - { url = "https://files.pythonhosted.org/packages/8c/67/67492014ce32729b63d7ef318a19f9cfedd855d677de5773476caf771e96/tree_sitter-0.25.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0628671f0de69bb279558ef6b640bcfc97864fe0026d840f872728a86cd6b6cd", size = 146926, upload-time = "2025-09-25T17:37:43.041Z" }, - { url = "https://files.pythonhosted.org/packages/4e/9c/a278b15e6b263e86c5e301c82a60923fa7c59d44f78d7a110a89a413e640/tree_sitter-0.25.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f5ddcd3e291a749b62521f71fc953f66f5fd9743973fd6dd962b092773569601", size = 137712, upload-time = "2025-09-25T17:37:44.039Z" }, - { url = "https://files.pythonhosted.org/packages/54/9a/423bba15d2bf6473ba67846ba5244b988cd97a4b1ea2b146822162256794/tree_sitter-0.25.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd88fbb0f6c3a0f28f0a68d72df88e9755cf5215bae146f5a1bdc8362b772053", size = 607873, upload-time = "2025-09-25T17:37:45.477Z" }, - { url = "https://files.pythonhosted.org/packages/ed/4c/b430d2cb43f8badfb3a3fa9d6cd7c8247698187b5674008c9d67b2a90c8e/tree_sitter-0.25.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b878e296e63661c8e124177cc3084b041ba3f5936b43076d57c487822426f614", size = 636313, upload-time = "2025-09-25T17:37:46.68Z" }, - { url = "https://files.pythonhosted.org/packages/9d/27/5f97098dbba807331d666a0997662e82d066e84b17d92efab575d283822f/tree_sitter-0.25.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d77605e0d353ba3fe5627e5490f0fbfe44141bafa4478d88ef7954a61a848dae", size = 631370, upload-time = "2025-09-25T17:37:47.993Z" }, - { url = "https://files.pythonhosted.org/packages/d4/3c/87caaed663fabc35e18dc704cd0e9800a0ee2f22bd18b9cbe7c10799895d/tree_sitter-0.25.2-cp313-cp313-win_amd64.whl", hash = "sha256:463c032bd02052d934daa5f45d183e0521ceb783c2548501cf034b0beba92c9b", size = 127157, upload-time = "2025-09-25T17:37:48.967Z" }, - { url = "https://files.pythonhosted.org/packages/d5/23/f8467b408b7988aff4ea40946a4bd1a2c1a73d17156a9d039bbaff1e2ceb/tree_sitter-0.25.2-cp313-cp313-win_arm64.whl", hash = "sha256:b3f63a1796886249bd22c559a5944d64d05d43f2be72961624278eff0dcc5cb8", size = 113975, upload-time = "2025-09-25T17:37:49.922Z" }, -] - -[[package]] -name = "tree-sitter-bash" -version = "0.25.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8e/0e/f0108be910f1eef6499eabce517e79fe3b12057280ed398da67ce2426cba/tree_sitter_bash-0.25.1.tar.gz", hash = "sha256:bfc0bdaa77bc1e86e3c6652e5a6e140c40c0a16b84185c2b63ad7cd809b88f14", size = 419703, upload-time = "2025-12-02T17:01:08.849Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/30/8e/37e7364d9c9c58da89e05c510671d8c45818afd7b31c6939ab72f8dc6c04/tree_sitter_bash-0.25.1-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:0e6235f59e366d220dde7d830196bed597d01e853e44d8ccd1a82c5dd2500acf", size = 194160, upload-time = "2025-12-02T17:00:59.047Z" }, - { url = "https://files.pythonhosted.org/packages/23/bb/2d2cfbb1f89aaeb1ec892624f069d92d058d06bb66f16b9ec9fb5873ab60/tree_sitter_bash-0.25.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:f4a34a6504c7c5b2a9b8c5c4065531dea19ca2c35026e706cf2eeeebe2c92512", size = 202659, upload-time = "2025-12-02T17:01:00.275Z" }, - { url = "https://files.pythonhosted.org/packages/25/f0/1bb25519be27460255d3899db677313cfa1e6306988fbf456a3d7e211bbb/tree_sitter_bash-0.25.1-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e76c4cfb20b076552406782b7f8c2a3946835993df0a44df006de54b7030c7dc", size = 230596, upload-time = "2025-12-02T17:01:01.759Z" }, - { url = "https://files.pythonhosted.org/packages/d7/22/9f70bc3d3b942ab9fc0f89c1dc9e087519a3a94f64ae6b7377aae3a7a0f0/tree_sitter_bash-0.25.1-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3f484c4bb8796cde7a87ca351e6116f09653edac0eb3c6d238566359dd28b117", size = 231981, upload-time = "2025-12-02T17:01:02.859Z" }, - { url = "https://files.pythonhosted.org/packages/7a/c3/f1540e42cd41b323c6821e45e52e1aed6ed386209aad52db996f05703963/tree_sitter_bash-0.25.1-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:5e76af6df46d958c7f5b6d5884c9743218e3902a00ccb493ec92728b1084430b", size = 228364, upload-time = "2025-12-02T17:01:03.997Z" }, - { url = "https://files.pythonhosted.org/packages/f7/a0/c3050a6277dfcac8c480f514dc4fe49f3f65f0eac68b4702cbaca2584e85/tree_sitter_bash-0.25.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a3332d71c7b7d5f78259b19d02d0ea111fcb82b72712ee4a93aaa5b226d3f0a8", size = 230074, upload-time = "2025-12-02T17:01:05.05Z" }, - { url = "https://files.pythonhosted.org/packages/71/0f/203fe6b27211387f4b9ba8c4a321567ca4ded2624dae6ccdbd2b6e940e17/tree_sitter_bash-0.25.1-cp310-abi3-win_amd64.whl", hash = "sha256:52a6802d9218f86278aa3e8b459c3abdad67eed0fde1f9f13aca5b6c634217a6", size = 195574, upload-time = "2025-12-02T17:01:06.412Z" }, - { url = "https://files.pythonhosted.org/packages/47/75/4ca1a9fabd8fb5aea78cea70f7837ce4dbf2afae115f62051e5fa99cba1c/tree_sitter_bash-0.25.1-cp310-abi3-win_arm64.whl", hash = "sha256:59115057ec2bae319e8082ff29559861045002964c3431ccb0fc92aa4bc9bccb", size = 191196, upload-time = "2025-12-02T17:01:07.486Z" }, -] - -[[package]] -name = "tree-sitter-c-sharp" -version = "0.23.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9f/fb/7e2962bc1901daf264e7ce263b168e0139304a5f8f66c9b2baf20e550f87/tree_sitter_c_sharp-0.23.5.tar.gz", hash = "sha256:2635c7d5ec93e59f2e831b571bed99c4cc68a5d183a0994020aa769e1b990a71", size = 1147914, upload-time = "2026-04-14T16:11:22.441Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/c4/86d8d469400a856757a464a6ac01af97d8cdacbb595e62bdb98bf1e9db90/tree_sitter_c_sharp-0.23.5-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:61e1981cf21b09ee547b9c4c68e64fb4394325f8fc8d5f6d50d41471eba923ea", size = 333658, upload-time = "2026-04-14T16:11:11.288Z" }, - { url = "https://files.pythonhosted.org/packages/c8/13/593c8603f834eaf15082b81e079289fc9f062b4c0ab5b9489134084eec06/tree_sitter_c_sharp-0.23.5-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:a75994a11f6fed3f5b8c36ad6a00e5dc43205bd912c43af3a2a54fdf649664eb", size = 376296, upload-time = "2026-04-14T16:11:12.972Z" }, - { url = "https://files.pythonhosted.org/packages/41/5a/a8855cbb5bbab28adb29c2c7f0e7be5a9f1d21450c13b3c3e613190d9b8c/tree_sitter_c_sharp-0.23.5-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:aa88a780204cd153c4c1ae2d59c654cee1402212fa0d069823d6d34301587438", size = 358333, upload-time = "2026-04-14T16:11:14.214Z" }, - { url = "https://files.pythonhosted.org/packages/0a/c8/e0f391e343f5424d0627e3b6886c77baeb1249a3f10986be00b0b64ecdab/tree_sitter_c_sharp-0.23.5-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ea38fb095d85d360dc5a0bec2fa605e496228876f798c9e089d5f0e72bcef46", size = 359448, upload-time = "2026-04-14T16:11:15.419Z" }, - { url = "https://files.pythonhosted.org/packages/6f/fc/10f807ac79f928241c5e0d827fdaf91e97dfba662fc7e07d7bd664140ec1/tree_sitter_c_sharp-0.23.5-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:05a9256415e7f24d4f133133794a9c224c60d19f677a04e2f6a94c25090b6d65", size = 358144, upload-time = "2026-04-14T16:11:17.087Z" }, - { url = "https://files.pythonhosted.org/packages/de/2a/6c3e12ef0cf09138717fcc02e1de8b76a3928d1bed65c7e3c2bd3172bcef/tree_sitter_c_sharp-0.23.5-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8636dc70b5a373c35c1036ed5de98e801f2e4d105ae41e2e20b6804c36e3bf33", size = 357525, upload-time = "2026-04-14T16:11:18.214Z" }, - { url = "https://files.pythonhosted.org/packages/2b/e0/bd287b092d611df95a9149117fd27b5947ce75527113d6898a4b4e2c8858/tree_sitter_c_sharp-0.23.5-cp310-abi3-win_amd64.whl", hash = "sha256:41a28cfa3d9ea50f5629e44550a03188c8fbd5079803dfc03554b6fd594b33fa", size = 338756, upload-time = "2026-04-14T16:11:19.661Z" }, - { url = "https://files.pythonhosted.org/packages/7f/fb/114ff43fdd256d0befed32f77c1dadee9517867181c70794571f718ed05c/tree_sitter_c_sharp-0.23.5-cp310-abi3-win_arm64.whl", hash = "sha256:2de4ebf95ddc2e92cd3105c8a8e0e7ec646bc82f52bfaf2f3acec0fa2401ec09", size = 337260, upload-time = "2026-04-14T16:11:20.849Z" }, -] - -[[package]] -name = "tree-sitter-cpp" -version = "0.23.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/2c/4dd63d705a8933543cad9b92ff31be849b164fec91a6eb63475ebc9ce668/tree_sitter_cpp-0.23.4.tar.gz", hash = "sha256:6a59c4cebb1ad1dc2e8d586cf8a72b39d21b8108b7b139d089719e81a339e41d", size = 940358, upload-time = "2024-11-11T06:59:24.934Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b6/ac/11d56670f7b048362db872ca866fd00ba2002a322ab179f047b7c0fb2910/tree_sitter_cpp-0.23.4-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:aacb1759f0efd9dbc25bd8ee88184a340483018869f75412d9c3bc32c039a520", size = 287861, upload-time = "2024-11-11T06:59:15.005Z" }, - { url = "https://files.pythonhosted.org/packages/12/1c/0337c016bdc00a77a3326d12f10ee836401dd28f27db6fd5b7734bfb21ed/tree_sitter_cpp-0.23.4-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:bc3c404d9f0cbd87951213a85440afbf4c31e718f8d907fa9ee12bea4b8d276f", size = 315513, upload-time = "2024-11-11T06:59:16.679Z" }, - { url = "https://files.pythonhosted.org/packages/b3/7b/dd38c049b10ed7fda118b903a1d28a8b55a36b98c30606ef90e8f374c6de/tree_sitter_cpp-0.23.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccc43ddf1279d5d5a4ef190373f4cb16522801bec4492bcd4754edf2aeba2b7b", size = 334813, upload-time = "2024-11-11T06:59:18.253Z" }, - { url = "https://files.pythonhosted.org/packages/6a/4d/23e390234d2acd351f5563b1079c515d7c1fe13ddb7392cee543be74dda3/tree_sitter_cpp-0.23.4-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:773d2cafc08bbc0f998687fa33f42f378c1a371cdb582870c4d13abb06092706", size = 316110, upload-time = "2024-11-11T06:59:19.823Z" }, - { url = "https://files.pythonhosted.org/packages/32/c7/b94a7e0e803af9d3bd4608fb4f0cfb2e9e233abaf0a38c928bfb0b1a025d/tree_sitter_cpp-0.23.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:247d127f0eb6574b0f6b30c0151e0bd0774e2e7acf9c558bdf9fbb8adc2e80c0", size = 308242, upload-time = "2024-11-11T06:59:21.466Z" }, - { url = "https://files.pythonhosted.org/packages/37/7e/909e52b3dec09c475140b0e175511e275d0d00ba2dbd7c68102d377ae0f6/tree_sitter_cpp-0.23.4-cp39-abi3-win_amd64.whl", hash = "sha256:68606a45bea92669d155399e1239f771a7767d8683cd8f8e30e7d813107030ca", size = 290997, upload-time = "2024-11-11T06:59:22.432Z" }, - { url = "https://files.pythonhosted.org/packages/d4/6a/65435d4d1f4c735be7ffe52d7c2e7b8a7f7c2790343a2719c60c548611c8/tree_sitter_cpp-0.23.4-cp39-abi3-win_arm64.whl", hash = "sha256:712f84f18be94cbe2a148fa4fdf40fcf4a8c25a8f7670efb9f8a47ddec2fc281", size = 288203, upload-time = "2024-11-11T06:59:23.404Z" }, -] - -[[package]] -name = "tree-sitter-go" -version = "0.25.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/05/727308adbbc79bcb1c92fc0ea10556a735f9d0f0a5435a18f59d40f7fd77/tree_sitter_go-0.25.0.tar.gz", hash = "sha256:a7466e9b8d94dda94cae8d91629f26edb2d26166fd454d4831c3bf6dfa2e8d68", size = 93890, upload-time = "2025-08-29T06:20:25.044Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ca/aa/0984707acc2b9bb461fe4a41e7e0fc5b2b1e245c32820f0c83b3c602957c/tree_sitter_go-0.25.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b852993063a3429a443e7bd0aa376dd7dd329d595819fabf56ac4cf9d7257b54", size = 47117, upload-time = "2025-08-29T06:20:14.286Z" }, - { url = "https://files.pythonhosted.org/packages/32/16/dd4cb124b35e99239ab3624225da07d4cb8da4d8564ed81d03fcb3a6ba9f/tree_sitter_go-0.25.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:503b81a2b4c31e302869a1de3a352ad0912ccab3df9ac9950197b0a9ceeabd8f", size = 48674, upload-time = "2025-08-29T06:20:17.557Z" }, - { url = "https://files.pythonhosted.org/packages/86/fb/b30d63a08044115d8b8bd196c6c2ab4325fb8db5757249a4ef0563966e2e/tree_sitter_go-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:04b3b3cb4aff18e74e28d49b716c6f24cb71ddfdd66768987e26e4d0fa812f74", size = 66418, upload-time = "2025-08-29T06:20:18.345Z" }, - { url = "https://files.pythonhosted.org/packages/26/21/d3d88a30ad007419b2c97b3baeeef7431407faf9f686195b6f1cad0aedf9/tree_sitter_go-0.25.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:148255aca2f54b90d48c48a9dbb4c7faad6cad310a980b2c5a5a9822057ed145", size = 72006, upload-time = "2025-08-29T06:20:19.14Z" }, - { url = "https://files.pythonhosted.org/packages/cd/d0/0dd6442353ced8a88bbda9e546f4ea29e381b59b5a40b122e5abb586bb6c/tree_sitter_go-0.25.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4d338116cdf8a6c6ff990d2441929b41323ef17c710407abe0993c13417d6aad", size = 70603, upload-time = "2025-08-29T06:20:21.544Z" }, - { url = "https://files.pythonhosted.org/packages/01/e2/ee5e09f63504fc286539535d374d2eaa0e7d489b80f8f744bb3962aff22a/tree_sitter_go-0.25.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5608e089d2a29fa8d2b327abeb2ad1cdb8e223c440a6b0ceab0d3fa80bdeebae", size = 66088, upload-time = "2025-08-29T06:20:22.336Z" }, - { url = "https://files.pythonhosted.org/packages/6e/b6/d9142583374720e79aca9ccb394b3795149a54c012e1dfd80738df2d984e/tree_sitter_go-0.25.0-cp310-abi3-win_amd64.whl", hash = "sha256:30d4ada57a223dfc2c32d942f44d284d40f3d1215ddcf108f96807fd36d53022", size = 48152, upload-time = "2025-08-29T06:20:23.089Z" }, - { url = "https://files.pythonhosted.org/packages/9e/00/9a2638e7339236f5b01622952a4d71c1474dd3783d1982a89555fc1f03b1/tree_sitter_go-0.25.0-cp310-abi3-win_arm64.whl", hash = "sha256:d5d62362059bf79997340773d47cc7e7e002883b527a05cca829c46e40b70ded", size = 46752, upload-time = "2025-08-29T06:20:24.235Z" }, -] - -[[package]] -name = "tree-sitter-json" -version = "0.24.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/29/e92df6dca3a6b2ab1c179978be398059817e1173fbacd47e832aaff3446b/tree_sitter_json-0.24.8.tar.gz", hash = "sha256:ca8486e52e2d261819311d35cf98656123d59008c3b7dcf91e61d2c0c6f3120e", size = 8155, upload-time = "2024-11-11T06:05:00.667Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/42/41/84866232980fb3cf0cff46f5af2dbb9bfa3324b32614c6a9af3d08926b72/tree_sitter_json-0.24.8-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:59ac06c6db1877d0e2076bce54a5fddcdd2fc38ca778905662e80fa9ffcea2ab", size = 8718, upload-time = "2024-11-11T06:04:49.779Z" }, - { url = "https://files.pythonhosted.org/packages/5c/31/102c15948d97b135611d6a995c97a3933c0e9745f25737723977f58e142c/tree_sitter_json-0.24.8-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:62b4c45b561db31436a81a3f037f71ec29049f4fc9bf5269b6ec3ebaaa35a1cd", size = 9163, upload-time = "2024-11-11T06:04:51.275Z" }, - { url = "https://files.pythonhosted.org/packages/28/64/aa44ea2f3d2e76ec086ce83902eb26b2ed0a92d3fd5e2714c9cb007e90d1/tree_sitter_json-0.24.8-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8627f7d375fda9fc193ebee368c453f374f65c2f25c58b6fea4e6b49a7fccbc", size = 17726, upload-time = "2024-11-11T06:04:52.732Z" }, - { url = "https://files.pythonhosted.org/packages/77/08/10001992526670e0d6f24c571b179f0ece90e5e014a4b98a3ce076884f32/tree_sitter_json-0.24.8-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85cca779872f7278f3a74eb38533d34b9c4de4fd548615e3361fa64fe350ad0a", size = 17236, upload-time = "2024-11-11T06:04:54.189Z" }, - { url = "https://files.pythonhosted.org/packages/92/64/908e9e0bd84fe3c81c564115d3bbe0e49b0e152784bbaf153d749d00bbe6/tree_sitter_json-0.24.8-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:deeb45850dcc52990fbb52c80196492a099e3fa3512d928a390a91cf061068cc", size = 16071, upload-time = "2024-11-11T06:04:55.628Z" }, - { url = "https://files.pythonhosted.org/packages/53/df/31daab1eedb445bef208a04fc35428de3afe2b37075fec84d7737e1c69de/tree_sitter_json-0.24.8-cp39-abi3-win_amd64.whl", hash = "sha256:e4849a03cd7197267b2688a4506a90a13568a8e0e8588080bd0212fcb38974e3", size = 11457, upload-time = "2024-11-11T06:04:57.698Z" }, - { url = "https://files.pythonhosted.org/packages/6c/3d/902d2f3125b6b90cebf404b63ca775bc6d82071ccc76c0d10fabfeb2febe/tree_sitter_json-0.24.8-cp39-abi3-win_arm64.whl", hash = "sha256:591e0096c882d12668b88f30d3ca6f85b9db3406910eaaab6afb6b17d65367dd", size = 10174, upload-time = "2024-11-11T06:04:59.309Z" }, -] - -[[package]] -name = "tree-sitter-python" -version = "0.25.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b8/8b/c992ff0e768cb6768d5c96234579bf8842b3a633db641455d86dd30d5dac/tree_sitter_python-0.25.0.tar.gz", hash = "sha256:b13e090f725f5b9c86aa455a268553c65cadf325471ad5b65cd29cac8a1a68ac", size = 159845, upload-time = "2025-09-11T06:47:58.159Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cf/64/a4e503c78a4eb3ac46d8e72a29c1b1237fa85238d8e972b063e0751f5a94/tree_sitter_python-0.25.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:14a79a47ddef72f987d5a2c122d148a812169d7484ff5c75a3db9609d419f361", size = 73790, upload-time = "2025-09-11T06:47:47.652Z" }, - { url = "https://files.pythonhosted.org/packages/e6/1d/60d8c2a0cc63d6ec4ba4e99ce61b802d2e39ef9db799bdf2a8f932a6cd4b/tree_sitter_python-0.25.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:480c21dbd995b7fe44813e741d71fed10ba695e7caab627fb034e3828469d762", size = 76691, upload-time = "2025-09-11T06:47:49.038Z" }, - { url = "https://files.pythonhosted.org/packages/aa/cb/d9b0b67d037922d60cbe0359e0c86457c2da721bc714381a63e2c8e35eba/tree_sitter_python-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:86f118e5eecad616ecdb81d171a36dde9bef5a0b21ed71ea9c3e390813c3baf5", size = 108133, upload-time = "2025-09-11T06:47:50.499Z" }, - { url = "https://files.pythonhosted.org/packages/40/bd/bf4787f57e6b2860f3f1c8c62f045b39fb32d6bac4b53d7a9e66de968440/tree_sitter_python-0.25.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be71650ca2b93b6e9649e5d65c6811aad87a7614c8c1003246b303f6b150f61b", size = 110603, upload-time = "2025-09-11T06:47:51.985Z" }, - { url = "https://files.pythonhosted.org/packages/5d/25/feff09f5c2f32484fbce15db8b49455c7572346ce61a699a41972dea7318/tree_sitter_python-0.25.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e6d5b5799628cc0f24691ab2a172a8e676f668fe90dc60468bee14084a35c16d", size = 108998, upload-time = "2025-09-11T06:47:53.046Z" }, - { url = "https://files.pythonhosted.org/packages/75/69/4946da3d6c0df316ccb938316ce007fb565d08f89d02d854f2d308f0309f/tree_sitter_python-0.25.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:71959832fc5d9642e52c11f2f7d79ae520b461e63334927e93ca46cd61cd9683", size = 107268, upload-time = "2025-09-11T06:47:54.388Z" }, - { url = "https://files.pythonhosted.org/packages/ed/a2/996fc2dfa1076dc460d3e2f3c75974ea4b8f02f6bc925383aaae519920e8/tree_sitter_python-0.25.0-cp310-abi3-win_amd64.whl", hash = "sha256:9bcde33f18792de54ee579b00e1b4fe186b7926825444766f849bf7181793a76", size = 76073, upload-time = "2025-09-11T06:47:55.773Z" }, - { url = "https://files.pythonhosted.org/packages/07/19/4b5569d9b1ebebb5907d11554a96ef3fa09364a30fcfabeff587495b512f/tree_sitter_python-0.25.0-cp310-abi3-win_arm64.whl", hash = "sha256:0fbf6a3774ad7e89ee891851204c2e2c47e12b63a5edbe2e9156997731c128bb", size = 74169, upload-time = "2025-09-11T06:47:56.747Z" }, -] - -[[package]] -name = "tree-sitter-rust" -version = "0.24.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b7/87/75cbd22b927267d310f76cca1ab3c1d9d41035dfa3eb9cc95f96ee199440/tree_sitter_rust-0.24.2.tar.gz", hash = "sha256:54fb02a5911e345308b405174465112479f56dc39e3f1e7744d7568595f00db9", size = 339341, upload-time = "2026-03-27T21:08:55.629Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/24/2b2d33af5e27c84a4fde4e8cd2594bb4ab1e1cf48756a9f40dadc84956cc/tree_sitter_rust-0.24.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3620cfd12340efa43082d45df76349ff511893a9c361da2f8d6d51e307020a59", size = 129507, upload-time = "2026-03-27T21:08:47.585Z" }, - { url = "https://files.pythonhosted.org/packages/78/2a/cf39f881a545360b5a86bb1accba1f4acc713daab01fb9edd35b6e84f473/tree_sitter_rust-0.24.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:01a46622735498493f29f3e628a90de95c96a07bfbeb88996243eb986b1cee36", size = 136812, upload-time = "2026-03-27T21:08:48.761Z" }, - { url = "https://files.pythonhosted.org/packages/ca/45/a051bbd3045a61182dde25b93ae9a33d2677c935b16952283e12eaf46051/tree_sitter_rust-0.24.2-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e033c5a93b57c88e0a835880de39fc802909ff69f57aaff6000211c196ea5190", size = 164706, upload-time = "2026-03-27T21:08:49.605Z" }, - { url = "https://files.pythonhosted.org/packages/b5/f6/a5a146df5c0a5daea3ffcd5d7245775fe7f084357770d5a313dd6245ae78/tree_sitter_rust-0.24.2-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9d76d1208c3638b871236090759dfc13d478921320653a6c9da5336e7c58f65a", size = 170310, upload-time = "2026-03-27T21:08:50.424Z" }, - { url = "https://files.pythonhosted.org/packages/95/a8/f85b1ca75e01361ca5f92d226593ca4857cea49551b9f6c8fa6fc08ea917/tree_sitter_rust-0.24.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:87930163a462408c49ab62c667e74029bc26b4cc7123dd1bdc7352215786c64a", size = 168668, upload-time = "2026-03-27T21:08:51.404Z" }, - { url = "https://files.pythonhosted.org/packages/a2/e1/3519f866a4679ca36acd9f5a06a779ecb8a92b18887c5546458d521df557/tree_sitter_rust-0.24.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:da2b86099028fd42c6cd32878b7b16b01f8aac0f7b0e98742b7fa6bc3cf09b89", size = 162403, upload-time = "2026-03-27T21:08:52.588Z" }, - { url = "https://files.pythonhosted.org/packages/34/71/7ef609894dbfe5699eb16f7471f9b8af1d958d8ba3e29c238d7607e8cb47/tree_sitter_rust-0.24.2-cp39-abi3-win_amd64.whl", hash = "sha256:4529c125d928882ddfb879fdc6bc0704913261ecc078b6fa7902559e0daf200d", size = 129422, upload-time = "2026-03-27T21:08:54.031Z" }, - { url = "https://files.pythonhosted.org/packages/b9/d8/050a781172745bc345f98abb7c56e72022ea0790f8e793de981c83c2ef15/tree_sitter_rust-0.24.2-cp39-abi3-win_arm64.whl", hash = "sha256:66ba90f61bd54f4c4f5d30434957daf64507c16b0313df76becb37d63f70a227", size = 128245, upload-time = "2026-03-27T21:08:54.803Z" }, -] - -[[package]] -name = "tree-sitter-yaml" -version = "0.7.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/57/b6/941d356ac70c90b9d2927375259e3a4204f38f7499ec6e7e8a95b9664689/tree_sitter_yaml-0.7.2.tar.gz", hash = "sha256:756db4c09c9d9e97c81699e8f941cb8ce4e51104927f6090eefe638ee567d32c", size = 84882, upload-time = "2025-10-07T14:40:36.071Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/38/29/c0b8dbff302c49ff4284666ffb6f2f21145006843bb4c3a9a85d0ec0b7ae/tree_sitter_yaml-0.7.2-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:7e269ddcfcab8edb14fbb1f1d34eed1e1e26888f78f94eedfe7cc98c60f8bc9f", size = 43898, upload-time = "2025-10-07T14:40:29.486Z" }, - { url = "https://files.pythonhosted.org/packages/18/0d/15a5add06b3932b5e4ce5f5e8e179197097decfe82a0ef000952c8b98216/tree_sitter_yaml-0.7.2-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:0807b7966e23ddf7dddc4545216e28b5a58cdadedcecca86b8d8c74271a07870", size = 44691, upload-time = "2025-10-07T14:40:30.369Z" }, - { url = "https://files.pythonhosted.org/packages/72/92/c4b896c90d08deb8308fadbad2210fdcc4c66c44ab4292eac4e80acb4b61/tree_sitter_yaml-0.7.2-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f1a5c60c98b6c4c037aae023569f020d0c489fad8dc26fdfd5510363c9c29a41", size = 91430, upload-time = "2025-10-07T14:40:31.16Z" }, - { url = "https://files.pythonhosted.org/packages/89/59/61f1fed31eb6d46ff080b8c0d53658cf29e10263f41ef5fe34768908037a/tree_sitter_yaml-0.7.2-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88636d19d0654fd24f4f242eaaafa90f6f5ebdba8a62e4b32d251ed156c51a2a", size = 92428, upload-time = "2025-10-07T14:40:31.954Z" }, - { url = "https://files.pythonhosted.org/packages/e3/62/a33a04d19b7f9a0ded780b9c9fcc6279e37c5d00b89b00425bb807a22cc2/tree_sitter_yaml-0.7.2-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1d2e8f0bb14aa4537320952d0f9607eef3021d5aada8383c34ebeece17db1e06", size = 90580, upload-time = "2025-10-07T14:40:33.037Z" }, - { url = "https://files.pythonhosted.org/packages/6c/e7/9525defa7b30792623f56b1fba9bbba361752348875b165b8975b87398fd/tree_sitter_yaml-0.7.2-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:74ca712c50fc9d7dbc68cb36b4a7811d6e67a5466b5a789f19bf8dd6084ef752", size = 90455, upload-time = "2025-10-07T14:40:33.778Z" }, - { url = "https://files.pythonhosted.org/packages/4a/d6/8d1e1ace03db3b02e64e91daf21d1347941d1bbecc606a5473a1a605250d/tree_sitter_yaml-0.7.2-cp310-abi3-win_amd64.whl", hash = "sha256:7587b5ca00fc4f9a548eff649697a3b395370b2304b399ceefa2087d8a6c9186", size = 45514, upload-time = "2025-10-07T14:40:34.562Z" }, - { url = "https://files.pythonhosted.org/packages/d8/c7/dcf3ea1c4f5da9b10353b9af4455d756c92d728a8f58f03c480d3ef0ead5/tree_sitter_yaml-0.7.2-cp310-abi3-win_arm64.whl", hash = "sha256:f63c227b18e7ce7587bce124578f0bbf1f890ac63d3e3cd027417574273642c4", size = 44065, upload-time = "2025-10-07T14:40:35.337Z" }, -] - -[[package]] -name = "typer" -version = "0.26.7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "annotated-doc" }, - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "rich" }, - { name = "shellingham" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5e/ed/ef06584ccdd5c410df0837951ecd7e15d9a6144ea1bd4c73cecab1a89891/typer-0.26.7.tar.gz", hash = "sha256:e314a34c617e419c091b2830dda3ea1f257134ff593061a8f5b9717ab8dddb3a", size = 201709, upload-time = "2026-06-03T07:18:06.843Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/24/25/2201973529af2c954de0bb725323c3aaed6d7f0ceee8f550dec9185df013/typer-0.26.7-py3-none-any.whl", hash = "sha256:5c87cfbc5d34491c5346ebf49c23e18d56ccb863268d3a8d592b26087c2f5e58", size = 122456, upload-time = "2026-06-03T07:18:05.732Z" }, -] - -[[package]] -name = "typing-extensions" -version = "4.16.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, -] - -[[package]] -name = "typing-inspection" -version = "0.4.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a3/26/b09b8010994eccc3c09092e6b34058f36a460eea2d4c3e8b910c695975a0/typing_inspection-0.4.4.tar.gz", hash = "sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47", size = 76928, upload-time = "2026-08-12T12:37:25.997Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/67/81/4add07e5172b7ac40d8ed5ff580409a7801a4fe26d529bdd915401dabfbe/typing_inspection-0.4.4-py3-none-any.whl", hash = "sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147", size = 14750, upload-time = "2026-08-12T12:37:24.648Z" }, -] - -[[package]] -name = "tzdata" -version = "2026.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/92/ff/5a28bdfd8c3ebec42564ac7d0e54ca3db65044a9314a97f9564fa7a1e926/tzdata-2026.3.tar.gz", hash = "sha256:4a1518b8993086a7982523e071643f3c0e5f213e75b21318e78bcabfff9d1415", size = 198674, upload-time = "2026-07-10T08:50:37.887Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl", hash = "sha256:dc096730c87af6cab1b171c9d532be840741ff5d459015e7f6947bd7d7e54931", size = 348168, upload-time = "2026-07-10T08:50:36.46Z" }, -] - -[[package]] -name = "urllib3" -version = "2.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, -] - -[[package]] -name = "west" -version = "1.5.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama" }, - { name = "packaging" }, - { name = "pykwalify" }, - { name = "pyyaml" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/54/8e/ddb81e2635e58d156092556934a2cffe245978c91e4a08e11d72591f46e4/west-1.5.0.tar.gz", hash = "sha256:7088fe0e9afe0719ebee95c51c529149f7bcfc919d83a8206d35fa9c683ed0a5", size = 149326, upload-time = "2025-09-23T14:45:52.443Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/1d/e50fb71de492abb549917fdbf13c4a8a8459af9d78a2fa47905804905c36/west-1.5.0-py3-none-any.whl", hash = "sha256:f71411d11ed9ed00847405c6a83600374c2e0cf8676ed5af5d8b572f92c4765a", size = 106991, upload-time = "2025-09-23T14:45:50.661Z" }, -] - -[[package]] -name = "win32-setctime" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b663af0e9bb3d4de6578d08f46b1b101c2442fd9aecaa2/win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0", size = 4867, upload-time = "2024-12-07T15:28:28.314Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083, upload-time = "2024-12-07T15:28:26.465Z" }, -] - -[[package]] -name = "wrapt" -version = "2.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/65/ba/8dc25478ed234dacc7d83c671634f347d0bdfb65bf0502f41879cf2f15a9/wrapt-2.4.0.tar.gz", hash = "sha256:7082fc1f94b020ac275870c4af71b09cff22876fe6e9c4c0ad01ea21d217b288", size = 161179, upload-time = "2026-08-30T04:41:51.424Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/22/581a0b44349d5babe526c958f365b8126e0fbd8fc2810e80446c47358050/wrapt-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ef4e2d6e399ce6eecc80179a6b9ef6544f121288f95fc132bc36c9d9503903af", size = 96374, upload-time = "2026-08-30T04:39:42.335Z" }, - { url = "https://files.pythonhosted.org/packages/5d/90/095984648cec62a786bb27c0b50f6cfa5856d1e073ba1006fe148d190084/wrapt-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b9b32d5e4f0a179cef5075cc79b79d6d3482c44c434c12969e48c6719e06d95", size = 96178, upload-time = "2026-08-30T04:39:43.789Z" }, - { url = "https://files.pythonhosted.org/packages/f8/fd/b20e3cb3cab35131b515edf18e8cd777dff680fc76fc00919481f4e536af/wrapt-2.4.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d7dbbdbfdacb85c2d962fa52db791c77943fd777d600d74c95af2d53b32f5a94", size = 227806, upload-time = "2026-08-30T04:39:45.264Z" }, - { url = "https://files.pythonhosted.org/packages/08/75/c8dfba5e0caf17cd0718a0cbbe76cb85e637a2d65183fb728232419f6fca/wrapt-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39cd68df4dff79f5336f9c745c06259d204bcb42d504040c9c91eac9e2abb39c", size = 229004, upload-time = "2026-08-30T04:39:47.068Z" }, - { url = "https://files.pythonhosted.org/packages/42/05/d4853fbd33e5860b10d5aec690f563547a92a82e61fb8bb2d4ece1ce3570/wrapt-2.4.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2a9f1a2f75bb95257cc5744e255e10a5a86e923f328b40ad3dbf9d8d03430013", size = 208934, upload-time = "2026-08-30T04:39:48.73Z" }, - { url = "https://files.pythonhosted.org/packages/a3/66/23d0e8de9b411fd198af5121627587563657370c8d509fbe5ea8adb3df79/wrapt-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8763ad01e3725b7751a4575f38bbcc19c0aa0822fec91c5c5bd21ce3ce7e1d2b", size = 225709, upload-time = "2026-08-30T04:39:50.287Z" }, - { url = "https://files.pythonhosted.org/packages/01/37/3b357bc90530d510ae59ae7ac48265c482ae899e47637ca4436645688b40/wrapt-2.4.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9125c6dbe8b88c00dd8ef4fc1e55757e8eb4720b6b2b2cc610a45bd32bd28c57", size = 207090, upload-time = "2026-08-30T04:39:51.78Z" }, - { url = "https://files.pythonhosted.org/packages/6d/0c/d8a5c6dbcc2d221308223bcea4130c6332454a855cb4dbd5dcb2360b13b2/wrapt-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:28f5de1526831b8f173889a436e289fe181ede8c66c9feb669d1aca8fd602eaf", size = 216269, upload-time = "2026-08-30T04:39:53.641Z" }, - { url = "https://files.pythonhosted.org/packages/92/93/cc9fc8fef1d3d25edaa1c2dc2337b556dc1d0613ddc1c4a6fe9ee08ad705/wrapt-2.4.0-cp312-cp312-win32.whl", hash = "sha256:a9ca1cdb3f7facb4990c7739ea5afbaceeb6728d066feedde03a4cfe83b29b03", size = 91187, upload-time = "2026-08-30T04:39:55.38Z" }, - { url = "https://files.pythonhosted.org/packages/ad/ec/a7b10705172bdb669b9687a8ff68bbe5f566437d2a49ad6d976af48b6d10/wrapt-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:8b464316489fb2fca0669ea0f8f07290054a0f26fc72982d3e4cf95469628ba9", size = 96423, upload-time = "2026-08-30T04:39:56.81Z" }, - { url = "https://files.pythonhosted.org/packages/83/7a/e838ac6463a1a1a1817b2f184ee2aa20c54692b80368c5063403c8d2461c/wrapt-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:db1285071ea09a7767fac608e7b5c7b03c09833b06186875a359905fbc659d29", size = 93003, upload-time = "2026-08-30T04:39:58.237Z" }, - { url = "https://files.pythonhosted.org/packages/19/86/f9de4e11582ff96ad2199eeeceaa17faa27bbdc599243f520070c4f3de07/wrapt-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5c5c4c728cd22a36e4b8bb5df4a7d3bccaa865d27725b36eeb3b6f18fb2e1bc2", size = 96041, upload-time = "2026-08-30T04:39:59.575Z" }, - { url = "https://files.pythonhosted.org/packages/c3/ab/1dbf50802bea3b46192fd0dc39bb0eb2e77a064c813b2bbd88d2888ad49f/wrapt-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7de5b8d94417e55c02be50cc226e0ae1209bbc73813bf691dff3979c94438115", size = 96269, upload-time = "2026-08-30T04:40:01.182Z" }, - { url = "https://files.pythonhosted.org/packages/cb/a3/a3b5cde1cd06e04b6e95134eb3187a0a7da607a530e7795b221d4e4fa819/wrapt-2.4.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6436e2bda993a3eb69a1b317fc831c8ebcafb5704c390859ebd49f81218c4bbb", size = 225787, upload-time = "2026-08-30T04:40:02.715Z" }, - { url = "https://files.pythonhosted.org/packages/cb/f7/d100f6c348b7669f19119cf890dcd4764623e2233af065586d110e0cd99e/wrapt-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e084558fbd112d2e1e34b0f5c71e45a3405bdad51a17150368a959bcf6697964", size = 226649, upload-time = "2026-08-30T04:40:04.647Z" }, - { url = "https://files.pythonhosted.org/packages/52/c6/3af8df515d5d7e92306957536f3468c6bdfecbe3659f99dbf09a468c2c4c/wrapt-2.4.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e78c947e18fadfd690c9420c30a96d221feeb93fc8f1cc00509b370ac16c3114", size = 206760, upload-time = "2026-08-30T04:40:06.332Z" }, - { url = "https://files.pythonhosted.org/packages/b9/c5/40d355552bd3eb6c5186e26051c19b573d24d7896de42caa7937d6b5ca9f/wrapt-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:08d8378c4514ac8dcc0ace76044cf87a873e6a52b5e6109834c8fb9037f4441b", size = 223467, upload-time = "2026-08-30T04:40:07.829Z" }, - { url = "https://files.pythonhosted.org/packages/40/ab/d198eebdb39f0d7e182e771e590a36673489cd58cebdad8aa273dcf28e04/wrapt-2.4.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:93180c2199784dd6a1075b33f9ed636bd0966821edbece6b3d5379b1c4f0bb7d", size = 205358, upload-time = "2026-08-30T04:40:09.344Z" }, - { url = "https://files.pythonhosted.org/packages/1c/0e/974a60672ad507d39a3d8a1c6351ef37fe65b07240d000ceba5d2b83e9e9/wrapt-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3d5e5eb76fb87e62752af751d2dcd9d1cd986b12037d2e1363d109ba716029e8", size = 214654, upload-time = "2026-08-30T04:40:10.923Z" }, - { url = "https://files.pythonhosted.org/packages/cc/5a/8b2db70206db0a4246758e0472ce344cb9636217113ef70640fc8d2ce874/wrapt-2.4.0-cp313-cp313-win32.whl", hash = "sha256:49bb5a572469e0e18163a8ec2aa972135a0929899ecbe627665f274506e1b5b4", size = 91171, upload-time = "2026-08-30T04:40:12.895Z" }, - { url = "https://files.pythonhosted.org/packages/fe/1e/e782b511c680dbe7369c92e7d981484aacca0cda584da1f28a84cd9a8e1a/wrapt-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:b1737f46b1e4a81eb93500a7f2854319e1c7a86e8863fb050b7b4daadd5a4178", size = 96178, upload-time = "2026-08-30T04:40:14.336Z" }, - { url = "https://files.pythonhosted.org/packages/9f/62/095ba31123fa5dd482d6183c05200b061314aabbd5442c010aba4b03ff1c/wrapt-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:f1e9e088094f4895f84ab043e7d59401df137d663efbf1e80c82144882960830", size = 92949, upload-time = "2026-08-30T04:40:15.935Z" }, - { url = "https://files.pythonhosted.org/packages/79/c8/fafe0002f572ced999c792cfe8b05d39269c63d8193d15d25bd828bcad7a/wrapt-2.4.0-py3-none-any.whl", hash = "sha256:18aabd9301d06026f5900538051773d6f87f65ae02cdc60de482df978513dc0a", size = 73713, upload-time = "2026-08-30T04:41:49.805Z" }, -] - -[[package]] -name = "zstandard" -version = "0.25.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fd/aa/3e0508d5a5dd96529cdc5a97011299056e14c6505b678fd58938792794b1/zstandard-0.25.0.tar.gz", hash = "sha256:7713e1179d162cf5c7906da876ec2ccb9c3a9dcbdffef0cc7f70c3667a205f0b", size = 711513, upload-time = "2025-09-14T22:15:54.002Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/82/fc/f26eb6ef91ae723a03e16eddb198abcfce2bc5a42e224d44cc8b6765e57e/zstandard-0.25.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7b3c3a3ab9daa3eed242d6ecceead93aebbb8f5f84318d82cee643e019c4b73b", size = 795738, upload-time = "2025-09-14T22:16:56.237Z" }, - { url = "https://files.pythonhosted.org/packages/aa/1c/d920d64b22f8dd028a8b90e2d756e431a5d86194caa78e3819c7bf53b4b3/zstandard-0.25.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:913cbd31a400febff93b564a23e17c3ed2d56c064006f54efec210d586171c00", size = 640436, upload-time = "2025-09-14T22:16:57.774Z" }, - { url = "https://files.pythonhosted.org/packages/53/6c/288c3f0bd9fcfe9ca41e2c2fbfd17b2097f6af57b62a81161941f09afa76/zstandard-0.25.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:011d388c76b11a0c165374ce660ce2c8efa8e5d87f34996aa80f9c0816698b64", size = 5343019, upload-time = "2025-09-14T22:16:59.302Z" }, - { url = "https://files.pythonhosted.org/packages/1e/15/efef5a2f204a64bdb5571e6161d49f7ef0fffdbca953a615efbec045f60f/zstandard-0.25.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dffecc361d079bb48d7caef5d673c88c8988d3d33fb74ab95b7ee6da42652ea", size = 5063012, upload-time = "2025-09-14T22:17:01.156Z" }, - { url = "https://files.pythonhosted.org/packages/b7/37/a6ce629ffdb43959e92e87ebdaeebb5ac81c944b6a75c9c47e300f85abdf/zstandard-0.25.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7149623bba7fdf7e7f24312953bcf73cae103db8cae49f8154dd1eadc8a29ecb", size = 5394148, upload-time = "2025-09-14T22:17:03.091Z" }, - { url = "https://files.pythonhosted.org/packages/e3/79/2bf870b3abeb5c070fe2d670a5a8d1057a8270f125ef7676d29ea900f496/zstandard-0.25.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6a573a35693e03cf1d67799fd01b50ff578515a8aeadd4595d2a7fa9f3ec002a", size = 5451652, upload-time = "2025-09-14T22:17:04.979Z" }, - { url = "https://files.pythonhosted.org/packages/53/60/7be26e610767316c028a2cbedb9a3beabdbe33e2182c373f71a1c0b88f36/zstandard-0.25.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5a56ba0db2d244117ed744dfa8f6f5b366e14148e00de44723413b2f3938a902", size = 5546993, upload-time = "2025-09-14T22:17:06.781Z" }, - { url = "https://files.pythonhosted.org/packages/85/c7/3483ad9ff0662623f3648479b0380d2de5510abf00990468c286c6b04017/zstandard-0.25.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:10ef2a79ab8e2974e2075fb984e5b9806c64134810fac21576f0668e7ea19f8f", size = 5046806, upload-time = "2025-09-14T22:17:08.415Z" }, - { url = "https://files.pythonhosted.org/packages/08/b3/206883dd25b8d1591a1caa44b54c2aad84badccf2f1de9e2d60a446f9a25/zstandard-0.25.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aaf21ba8fb76d102b696781bddaa0954b782536446083ae3fdaa6f16b25a1c4b", size = 5576659, upload-time = "2025-09-14T22:17:10.164Z" }, - { url = "https://files.pythonhosted.org/packages/9d/31/76c0779101453e6c117b0ff22565865c54f48f8bd807df2b00c2c404b8e0/zstandard-0.25.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1869da9571d5e94a85a5e8d57e4e8807b175c9e4a6294e3b66fa4efb074d90f6", size = 4953933, upload-time = "2025-09-14T22:17:11.857Z" }, - { url = "https://files.pythonhosted.org/packages/18/e1/97680c664a1bf9a247a280a053d98e251424af51f1b196c6d52f117c9720/zstandard-0.25.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:809c5bcb2c67cd0ed81e9229d227d4ca28f82d0f778fc5fea624a9def3963f91", size = 5268008, upload-time = "2025-09-14T22:17:13.627Z" }, - { url = "https://files.pythonhosted.org/packages/1e/73/316e4010de585ac798e154e88fd81bb16afc5c5cb1a72eeb16dd37e8024a/zstandard-0.25.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f27662e4f7dbf9f9c12391cb37b4c4c3cb90ffbd3b1fb9284dadbbb8935fa708", size = 5433517, upload-time = "2025-09-14T22:17:16.103Z" }, - { url = "https://files.pythonhosted.org/packages/5b/60/dd0f8cfa8129c5a0ce3ea6b7f70be5b33d2618013a161e1ff26c2b39787c/zstandard-0.25.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99c0c846e6e61718715a3c9437ccc625de26593fea60189567f0118dc9db7512", size = 5814292, upload-time = "2025-09-14T22:17:17.827Z" }, - { url = "https://files.pythonhosted.org/packages/fc/5f/75aafd4b9d11b5407b641b8e41a57864097663699f23e9ad4dbb91dc6bfe/zstandard-0.25.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:474d2596a2dbc241a556e965fb76002c1ce655445e4e3bf38e5477d413165ffa", size = 5360237, upload-time = "2025-09-14T22:17:19.954Z" }, - { url = "https://files.pythonhosted.org/packages/ff/8d/0309daffea4fcac7981021dbf21cdb2e3427a9e76bafbcdbdf5392ff99a4/zstandard-0.25.0-cp312-cp312-win32.whl", hash = "sha256:23ebc8f17a03133b4426bcc04aabd68f8236eb78c3760f12783385171b0fd8bd", size = 436922, upload-time = "2025-09-14T22:17:24.398Z" }, - { url = "https://files.pythonhosted.org/packages/79/3b/fa54d9015f945330510cb5d0b0501e8253c127cca7ebe8ba46a965df18c5/zstandard-0.25.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffef5a74088f1e09947aecf91011136665152e0b4b359c42be3373897fb39b01", size = 506276, upload-time = "2025-09-14T22:17:21.429Z" }, - { url = "https://files.pythonhosted.org/packages/ea/6b/8b51697e5319b1f9ac71087b0af9a40d8a6288ff8025c36486e0c12abcc4/zstandard-0.25.0-cp312-cp312-win_arm64.whl", hash = "sha256:181eb40e0b6a29b3cd2849f825e0fa34397f649170673d385f3598ae17cca2e9", size = 462679, upload-time = "2025-09-14T22:17:23.147Z" }, - { url = "https://files.pythonhosted.org/packages/35/0b/8df9c4ad06af91d39e94fa96cc010a24ac4ef1378d3efab9223cc8593d40/zstandard-0.25.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec996f12524f88e151c339688c3897194821d7f03081ab35d31d1e12ec975e94", size = 795735, upload-time = "2025-09-14T22:17:26.042Z" }, - { url = "https://files.pythonhosted.org/packages/3f/06/9ae96a3e5dcfd119377ba33d4c42a7d89da1efabd5cb3e366b156c45ff4d/zstandard-0.25.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a1a4ae2dec3993a32247995bdfe367fc3266da832d82f8438c8570f989753de1", size = 640440, upload-time = "2025-09-14T22:17:27.366Z" }, - { url = "https://files.pythonhosted.org/packages/d9/14/933d27204c2bd404229c69f445862454dcc101cd69ef8c6068f15aaec12c/zstandard-0.25.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:e96594a5537722fdfb79951672a2a63aec5ebfb823e7560586f7484819f2a08f", size = 5343070, upload-time = "2025-09-14T22:17:28.896Z" }, - { url = "https://files.pythonhosted.org/packages/6d/db/ddb11011826ed7db9d0e485d13df79b58586bfdec56e5c84a928a9a78c1c/zstandard-0.25.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bfc4e20784722098822e3eee42b8e576b379ed72cca4a7cb856ae733e62192ea", size = 5063001, upload-time = "2025-09-14T22:17:31.044Z" }, - { url = "https://files.pythonhosted.org/packages/db/00/87466ea3f99599d02a5238498b87bf84a6348290c19571051839ca943777/zstandard-0.25.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:457ed498fc58cdc12fc48f7950e02740d4f7ae9493dd4ab2168a47c93c31298e", size = 5394120, upload-time = "2025-09-14T22:17:32.711Z" }, - { url = "https://files.pythonhosted.org/packages/2b/95/fc5531d9c618a679a20ff6c29e2b3ef1d1f4ad66c5e161ae6ff847d102a9/zstandard-0.25.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:fd7a5004eb1980d3cefe26b2685bcb0b17989901a70a1040d1ac86f1d898c551", size = 5451230, upload-time = "2025-09-14T22:17:34.41Z" }, - { url = "https://files.pythonhosted.org/packages/63/4b/e3678b4e776db00f9f7b2fe58e547e8928ef32727d7a1ff01dea010f3f13/zstandard-0.25.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8e735494da3db08694d26480f1493ad2cf86e99bdd53e8e9771b2752a5c0246a", size = 5547173, upload-time = "2025-09-14T22:17:36.084Z" }, - { url = "https://files.pythonhosted.org/packages/4e/d5/ba05ed95c6b8ec30bd468dfeab20589f2cf709b5c940483e31d991f2ca58/zstandard-0.25.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3a39c94ad7866160a4a46d772e43311a743c316942037671beb264e395bdd611", size = 5046736, upload-time = "2025-09-14T22:17:37.891Z" }, - { url = "https://files.pythonhosted.org/packages/50/d5/870aa06b3a76c73eced65c044b92286a3c4e00554005ff51962deef28e28/zstandard-0.25.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:172de1f06947577d3a3005416977cce6168f2261284c02080e7ad0185faeced3", size = 5576368, upload-time = "2025-09-14T22:17:40.206Z" }, - { url = "https://files.pythonhosted.org/packages/5d/35/398dc2ffc89d304d59bc12f0fdd931b4ce455bddf7038a0a67733a25f550/zstandard-0.25.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3c83b0188c852a47cd13ef3bf9209fb0a77fa5374958b8c53aaa699398c6bd7b", size = 4954022, upload-time = "2025-09-14T22:17:41.879Z" }, - { url = "https://files.pythonhosted.org/packages/9a/5c/36ba1e5507d56d2213202ec2b05e8541734af5f2ce378c5d1ceaf4d88dc4/zstandard-0.25.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1673b7199bbe763365b81a4f3252b8e80f44c9e323fc42940dc8843bfeaf9851", size = 5267889, upload-time = "2025-09-14T22:17:43.577Z" }, - { url = "https://files.pythonhosted.org/packages/70/e8/2ec6b6fb7358b2ec0113ae202647ca7c0e9d15b61c005ae5225ad0995df5/zstandard-0.25.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0be7622c37c183406f3dbf0cba104118eb16a4ea7359eeb5752f0794882fc250", size = 5433952, upload-time = "2025-09-14T22:17:45.271Z" }, - { url = "https://files.pythonhosted.org/packages/7b/01/b5f4d4dbc59ef193e870495c6f1275f5b2928e01ff5a81fecb22a06e22fb/zstandard-0.25.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:5f5e4c2a23ca271c218ac025bd7d635597048b366d6f31f420aaeb715239fc98", size = 5814054, upload-time = "2025-09-14T22:17:47.08Z" }, - { url = "https://files.pythonhosted.org/packages/b2/e5/fbd822d5c6f427cf158316d012c5a12f233473c2f9c5fe5ab1ae5d21f3d8/zstandard-0.25.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f187a0bb61b35119d1926aee039524d1f93aaf38a9916b8c4b78ac8514a0aaf", size = 5360113, upload-time = "2025-09-14T22:17:48.893Z" }, - { url = "https://files.pythonhosted.org/packages/8e/e0/69a553d2047f9a2c7347caa225bb3a63b6d7704ad74610cb7823baa08ed7/zstandard-0.25.0-cp313-cp313-win32.whl", hash = "sha256:7030defa83eef3e51ff26f0b7bfb229f0204b66fe18e04359ce3474ac33cbc09", size = 436936, upload-time = "2025-09-14T22:17:52.658Z" }, - { url = "https://files.pythonhosted.org/packages/d9/82/b9c06c870f3bd8767c201f1edbdf9e8dc34be5b0fbc5682c4f80fe948475/zstandard-0.25.0-cp313-cp313-win_amd64.whl", hash = "sha256:1f830a0dac88719af0ae43b8b2d6aef487d437036468ef3c2ea59c51f9d55fd5", size = 506232, upload-time = "2025-09-14T22:17:50.402Z" }, - { url = "https://files.pythonhosted.org/packages/d4/57/60c3c01243bb81d381c9916e2a6d9e149ab8627c0c7d7abb2d73384b3c0c/zstandard-0.25.0-cp313-cp313-win_arm64.whl", hash = "sha256:85304a43f4d513f5464ceb938aa02c1e78c2943b29f44a750b48b25ac999a049", size = 462671, upload-time = "2025-09-14T22:17:51.533Z" }, -] From fff37d3f7fa57311f0986fb5e3c29a8c525239f1 Mon Sep 17 00:00:00 2001 From: Stefan Schulz <5165104+Rubyfi@users.noreply.github.com> Date: Tue, 8 Sep 2026 09:16:24 +0000 Subject: [PATCH 02/19] chore: adjust mount conditionals, add correct variant to autoiconf.json --- AGENTS.md | 9 +++++++ CMakeLists.txt | 11 +++++++- components/auto_off/doc/index.md | 20 ++++++++++---- components/brightness_controller/doc/index.md | 20 ++++++++++---- components/light_controller/doc/index.md | 20 ++++++++++---- components/main_control_knob/doc/index.md | 20 ++++++++++---- components/power_button/doc/index.md | 20 ++++++++++---- .../power_signal_processing/doc/index.md | 15 +++++++++++ conf.py | 19 ++++++++++++-- doc/component_report.md | 19 +++----------- doc/components/index.md | 26 ++++++------------- test/spled_integration/doc/index.md | 15 +++++++++++ ubproject.toml | 24 +++++++++++------ 13 files changed, 168 insertions(+), 70 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 3edd887b..5d2604ed 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -159,6 +159,15 @@ enabled. `attach_to` appends it to the empty toctree in `doc/components/index.md`, which is why that page needs no loop. Adding a component to the report means adding one mount block, not editing a document. +A mount condition may only name data that the file in `[needs] variant_data_file` +actually declares. That file is the mirror `CMakeLists.txt` writes for the +configured variant, holding the KConfig features plus `build_config.variant`. +Naming anything else — a key that only `conf.py` injects at build time — makes +the condition unevaluable for ubCode and every other reader of this file, and +their view of the project then silently disagrees with the build. Build-shape +distinctions belong in `conf.py` instead: it switches TOML reading off entirely +for a per-component report rather than encoding the scope in a condition. + **Blocks inside a document: the `{if}` directive of Sphinx-Needs.** Wrap the content in a four-backtick fence and give the condition as the argument, for example ```` ````{if} var.features.BLINKING ````. Content behind a false diff --git a/CMakeLists.txt b/CMakeLists.txt index 426e58ac..b1e0516e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,8 +50,17 @@ include("${SPL_CORE_DIR}/spl.cmake") # way cmake.copyCompileCommands mirrors compile_commands.json, so tools outside CMake # (e.g. ubproject.toml's variant_data_file) can track whichever variant/build kit/build # type is currently selected in the CMake Tools extension without a hardcoded path. +# +# The variant NAME is added alongside the features. KConfig only knows features, but +# the `if` conditions in ubproject.toml are read by tools that never run CMake or +# Sphinx (ubCode, the language server). Everything such a condition names has to be in +# this file, or the tool cannot decide it and its view of the project disagrees with +# the build. if(EXISTS ${AUTOCONF_JSON}) - configure_file(${AUTOCONF_JSON} ${CMAKE_SOURCE_DIR}/build/autoconf.json COPYONLY) + file(READ ${AUTOCONF_JSON} _spled_variant_data) + string(JSON _spled_variant_data SET "${_spled_variant_data}" + build_config "{\"variant\": \"${VARIANT}\"}") + file(WRITE ${CMAKE_SOURCE_DIR}/build/autoconf.json "${_spled_variant_data}") endif() # The object_deps_report extension is currently Windows-only: its index.cmake diff --git a/components/auto_off/doc/index.md b/components/auto_off/doc/index.md index d7451528..c37df2df 100644 --- a/components/auto_off/doc/index.md +++ b/components/auto_off/doc/index.md @@ -2,11 +2,6 @@ **Software Detailed Design** -```{toctree} -:maxdepth: 2 -:caption: Table of Contents -``` - ## Introduction The Auto Off Controller is responsible for monitoring user activity and automatically triggering system shutdown after a configurable period of inactivity. This component helps conserve power by detecting when no user input has occurred for a specified duration. @@ -126,3 +121,18 @@ The internal timer calculation follows the formula: Key press detection and timer reset occur within one execution cycle (CONFIG_OS_TASK_PERIOD), ensuring immediate response to user activity. ``` + +````{if} var.build_config.target == "reports" + +## Verification + +```{toctree} +:maxdepth: 1 +:glob: + +/build/**/components/auto_off/reports/unit_test_spec +/build/**/components/auto_off/reports/unit_test_results +/build/**/components/auto_off/reports/coverage +``` + +```` diff --git a/components/brightness_controller/doc/index.md b/components/brightness_controller/doc/index.md index 1bb44de8..f6c099a6 100644 --- a/components/brightness_controller/doc/index.md +++ b/components/brightness_controller/doc/index.md @@ -2,11 +2,6 @@ **Software Detailed Design** -```{toctree} -:maxdepth: 2 -:caption: Table of Contents -``` - ## Introduction The Brightness Controller is responsible for adjusting the brightness of the LED. @@ -73,3 +68,18 @@ The Brightness Controller is use the RTE interface `RteSetBrightnessAdjustmentCo ```{src-trace} :project: brightness_controller ``` + +````{if} var.build_config.target == "reports" + +## Verification + +```{toctree} +:maxdepth: 1 +:glob: + +/build/**/components/brightness_controller/reports/unit_test_spec +/build/**/components/brightness_controller/reports/unit_test_results +/build/**/components/brightness_controller/reports/coverage +``` + +```` diff --git a/components/light_controller/doc/index.md b/components/light_controller/doc/index.md index 19d157d7..6fa4ed27 100644 --- a/components/light_controller/doc/index.md +++ b/components/light_controller/doc/index.md @@ -2,11 +2,6 @@ **Software Detailed Design** -```{toctree} -:maxdepth: 2 -:caption: Table of Contents -``` - ## Introduction The Light Controller is responsible for managing the behavior of the LED based on the system's power state. @@ -116,3 +111,18 @@ stateDiagram-v2 LIGHT_ON --> LIGHT_OFF : Power State == OFF ``` ```` + +````{if} var.build_config.target == "reports" + +## Verification + +```{toctree} +:maxdepth: 1 +:glob: + +/build/**/components/light_controller/reports/unit_test_spec +/build/**/components/light_controller/reports/unit_test_results +/build/**/components/light_controller/reports/coverage +``` + +```` diff --git a/components/main_control_knob/doc/index.md b/components/main_control_knob/doc/index.md index d35e2cd2..8791758f 100644 --- a/components/main_control_knob/doc/index.md +++ b/components/main_control_knob/doc/index.md @@ -2,11 +2,6 @@ **Software Detailed Design** -```{toctree} -:maxdepth: 2 -:caption: Table of Contents -``` - ## Introduction The main control knob component is responsible for calculating a value between 0 and 100 percent depending on the user's input. @@ -56,3 +51,18 @@ The main control knob component uses the RTE interface `RteGetMainKnobValue()` t The main control knob component uses the RTE interface `RteSetMainKnobValue()` to set the new value of the main control knob. ``` + +````{if} var.build_config.target == "reports" + +## Verification + +```{toctree} +:maxdepth: 1 +:glob: + +/build/**/components/main_control_knob/reports/unit_test_spec +/build/**/components/main_control_knob/reports/unit_test_results +/build/**/components/main_control_knob/reports/coverage +``` + +```` diff --git a/components/power_button/doc/index.md b/components/power_button/doc/index.md index d0d52f4c..f28bde6c 100644 --- a/components/power_button/doc/index.md +++ b/components/power_button/doc/index.md @@ -2,11 +2,6 @@ **Software Detailed Design** -```{toctree} -:maxdepth: 2 -:caption: Table of Contents -``` - ## Introduction This module interfaces with the keyboard and debounces the `POWER_BUTTON_KEY` to provide a stable key press event. @@ -74,3 +69,18 @@ stateDiagram-v2 PRESSED --> RELEASED: Button released long enough RELEASED --> PRESSED: Button pressed long enough ``` + +````{if} var.build_config.target == "reports" + +## Verification + +```{toctree} +:maxdepth: 1 +:glob: + +/build/**/components/power_button/reports/unit_test_spec +/build/**/components/power_button/reports/unit_test_results +/build/**/components/power_button/reports/coverage +``` + +```` diff --git a/components/power_signal_processing/doc/index.md b/components/power_signal_processing/doc/index.md index 1063860f..875f2e0b 100644 --- a/components/power_signal_processing/doc/index.md +++ b/components/power_signal_processing/doc/index.md @@ -108,3 +108,18 @@ graph TD IsOff -->|No| TurnOff --> End ``` ```` + +````{if} var.build_config.target == "reports" + +## Verification + +```{toctree} +:maxdepth: 1 +:glob: + +/build/**/components/power_signal_processing/reports/unit_test_spec +/build/**/components/power_signal_processing/reports/unit_test_results +/build/**/components/power_signal_processing/reports/coverage +``` + +```` diff --git a/conf.py b/conf.py index 8d557b1c..30a3047f 100644 --- a/conf.py +++ b/conf.py @@ -141,19 +141,34 @@ def _feature_defaults() -> dict: _extra_include_patterns = html_context["build_config"].get("include_patterns", []) if build_config["scope"] == "variant": - # Component design documents are mounted per feature by sphinx-mounts, see + # Component design documents are mounted by sphinx-mounts, see # [[source.mounts]] in ubproject.toml. Reading them as ordinary sources as # well would parse every need in them a second time under a second docname. + # Only the hand-written trees are dropped; the generated ones under build/ + # stay, because they are not mounted. _extra_include_patterns = [ pattern for pattern in _extra_include_patterns - if not (pattern.startswith("components/") and pattern.endswith("/doc/**")) + if not (pattern.endswith("/doc/**") and not pattern.startswith("build/")) + # The generated report pages are only ever shown by the reports target. + # The docs target used to read them anyway and then leave them out of + # every toctree, which is 15 orphan warnings for pages nobody sees. + and not ( + build_config["target"] == "docs" + and pattern.startswith("build/") + and pattern.endswith("/reports/**") + ) ] root_doc = "index" exclude_patterns.append("doc/component_report.md") else: root_doc = "doc/component_report" exclude_patterns.append("index.md") + # A per-component report builds one component and stitches its own table of + # contents, so none of the mounts apply. Switching TOML reading off here is + # what lets every `if` in ubproject.toml speak only about the variant, which + # is the only way a reader that never runs Sphinx can decide them too. + sources_from_toml = None include_patterns.extend(_extra_include_patterns) needs_variant_data = { diff --git a/doc/component_report.md b/doc/component_report.md index 6fe1f6fa..ee467bc5 100644 --- a/doc/component_report.md +++ b/doc/component_report.md @@ -5,25 +5,12 @@ This is the root document of the per-component report that spl-core builds for a single component. The build configuration restricts the source set to that -one component, so the glob below resolves to exactly its design document. +one component, so the glob below resolves to exactly its design document, which +in turn groups that component's verification pages. ```{toctree} :maxdepth: 2 :glob: -/components/*/doc/index +/**/doc/index ``` - -````{if} var.build_config.target == "reports" - -```{toctree} -:caption: Unit test results -:maxdepth: 1 -:glob: - -/build/**/reports/unit_test_spec -/build/**/reports/unit_test_results -/build/**/reports/coverage -``` - -```` diff --git a/doc/components/index.md b/doc/components/index.md index e0ca0d4e..1de8af4d 100644 --- a/doc/components/index.md +++ b/doc/components/index.md @@ -2,26 +2,16 @@ Only the components that the built variant actually contains appear below. Each component's design document is mounted by sphinx-mounts under the KConfig -feature that owns it, so the table of contents follows the variant without any -templating in this file. The declarations live in `[[source.mounts]]` in -`ubproject.toml`, and sphinx-mounts appends each mounted entry to the empty -toctree below. +feature that owns it, so this page needs no templating: the declarations live +in `[[source.mounts]]` in `ubproject.toml`, and sphinx-mounts appends each +mounted entry to the empty toctree below. -```{toctree} -:maxdepth: 2 - -``` - -````{if} var.build_config.target == "reports" +Each entry is a group. A component's own document is the entry point of its +bundle and carries the toctree for its verification pages, so unit test results +and coverage appear underneath the component they belong to rather than in one +flat list. ```{toctree} -:caption: Unit test results -:maxdepth: 1 -:glob: +:maxdepth: 2 -/build/**/components/*/reports/unit_test_spec -/build/**/components/*/reports/unit_test_results -/build/**/components/*/reports/coverage ``` - -```` diff --git a/test/spled_integration/doc/index.md b/test/spled_integration/doc/index.md index 27cbca19..72fe22b6 100644 --- a/test/spled_integration/doc/index.md +++ b/test/spled_integration/doc/index.md @@ -1 +1,16 @@ # SPLED Integration Tests + +````{if} var.build_config.target == "reports" + +## Verification + +```{toctree} +:maxdepth: 1 +:glob: + +/build/**/test/spled_integration/reports/unit_test_spec +/build/**/test/spled_integration/reports/unit_test_results +/build/**/test/spled_integration/reports/coverage +``` + +```` diff --git a/ubproject.toml b/ubproject.toml index b71655b3..11ddedc7 100644 --- a/ubproject.toml +++ b/ubproject.toml @@ -80,8 +80,10 @@ default = [] # KConfig feature that owns it is enabled for the variant being built. The # feature values come from the variant's autoconf.json, see conf.py. # -# `scope == "variant"` keeps these out of the per-component reports, which build -# a single component and stitch their own table of contents. +# Every condition names only what the variant data file declares, so ubCode and any +# other reader of this file decide exactly what the Sphinx build decides. Keeping the +# per-component reports out of this is conf.py's job: it switches TOML reading off for +# those builds rather than encoding the build shape in a condition nothing else can see. # # This is the file-level half of variant handling. The block-level half is the # {if} directive of Sphinx-Needs, used inside the component documents. @@ -92,43 +94,49 @@ default = [] # --------------------------------------------------------------------------- [[source.mounts]] -if = 'var.build_config.scope == "variant"' dir = "components/light_controller/doc" mount_at = "components/light_controller/doc" attach_to = "doc/components/index" path_check = "off" [[source.mounts]] -if = 'var.build_config.scope == "variant"' dir = "components/main_control_knob/doc" mount_at = "components/main_control_knob/doc" attach_to = "doc/components/index" path_check = "off" [[source.mounts]] -if = 'var.build_config.scope == "variant"' dir = "components/power_button/doc" mount_at = "components/power_button/doc" attach_to = "doc/components/index" path_check = "off" [[source.mounts]] -if = 'var.build_config.scope == "variant"' dir = "components/power_signal_processing/doc" mount_at = "components/power_signal_processing/doc" attach_to = "doc/components/index" path_check = "off" [[source.mounts]] -if = 'var.build_config.scope == "variant" and var.features.BRIGHTNESS_ADJUSTMENT_ENABLED == True' +if = "var.features.BRIGHTNESS_ADJUSTMENT_ENABLED == True" dir = "components/brightness_controller/doc" mount_at = "components/brightness_controller/doc" attach_to = "doc/components/index" path_check = "off" [[source.mounts]] -if = 'var.build_config.scope == "variant" and var.features.AUTO_OFF == True' +if = "var.features.AUTO_OFF == True" dir = "components/auto_off/doc" mount_at = "components/auto_off/doc" attach_to = "doc/components/index" path_check = "off" + +# The integration test suite is not owned by a feature. It is added by Disco's +# parts.cmake for the test build kit only, so its document follows the variant +# rather than a KConfig symbol. +[[source.mounts]] +if = 'var.build_config.variant == "Disco"' +dir = "test/spled_integration/doc" +mount_at = "test/spled_integration/doc" +attach_to = "doc/components/index" +path_check = "off" From 05234c85397d2940dd02675bcfbcaaa2c23853eb Mon Sep 17 00:00:00 2001 From: Lucas Silber Date: Tue, 15 Sep 2026 09:50:43 +0200 Subject: [PATCH 03/19] fix: display correct html_title --- conf.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/conf.py b/conf.py index 30a3047f..1a285fe5 100644 --- a/conf.py +++ b/conf.py @@ -43,8 +43,11 @@ # html config ############################################################### # @see https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output -# Omit "documentation" in title -html_title = f"{project} {release}" +# Omit "documentation" in title. Include the variant, so that a browser tab or +# the sidebar logo tells two variants' builds apart -- both otherwise render the +# same generic title and are indistinguishable when opened side by side. +_html_title_variant = os.environ.get("VARIANT", "") +html_title = f"{project} {_html_title_variant} {release}".strip() if _html_title_variant else f"{project} {release}" html_logo = "doc/_figures/SPLED_logo.png" From 27d87b373e81fa3c55db1e574cb668526513bb87 Mon Sep 17 00:00:00 2001 From: Lucas Silber Date: Wed, 16 Sep 2026 15:33:08 +0200 Subject: [PATCH 04/19] chore: align the Python pin and unblock the documentation toolchain Phase 0 of doc/variant-handling-without-jinja-plan.md. None of the variant-data work below is testable while the toolchain contradicts itself: - bootstrap_python.sh installed CPython 3.11 and claimed the project required it, while pyproject requires >=3.12,<3.14. Following the documented bootstrap produced a venv Poetry rejects. 3.12 everywhere now: the bootstrap, the pypeline venv step, bootstrap.json, the devcontainer and the two READMEs. - ubproject.toml extended a hard-coded .venv/lib/python3.13 path that does not exist (the venv is 3.12), so the whole base needs configuration silently failed to resolve for ubCode. Corrected to 3.12; the later phase that makes the file self-contained removes the venv path entirely. - [needs] schema_definitions_from_json named schema.json, which exists nowhere in the tree. Dropped rather than left dangling. - /generated (the current-variant pointer) is now ignored instead of showing up as untracked output. Also documents how to point the venv at a local spl-core checkout, since the next phases need spl-core changes: CMakeLists.txt discovers spl-core by importing it, so one editable install redirects both the Python and the CMake side. The path stays out of pyproject on purpose -- CI must keep resolving the released version. Co-Authored-By: Claude Opus 5 (1M context) --- .devcontainer/Dockerfile | 2 +- .gitignore | 5 +++++ AGENTS.md | 20 +++++++++++++++++++- README.md | 2 +- bootstrap.json | 2 +- bootstrap_python.sh | 18 ++++++++++-------- pypeline.yaml | 8 ++++---- ubproject.toml | 4 +--- 8 files changed, 42 insertions(+), 19 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index e6ab5ef4..d376de9a 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -23,7 +23,7 @@ ENV NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt ENV REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt ENV PIP_CERT=/etc/ssl/certs/ca-certificates.crt -# Python toolchain (uv/CPython 3.11/Poetry) baked as the vscode user, so it lands +# Python toolchain (uv/CPython 3.12/Poetry) baked as the vscode user, so it lands # in /home/vscode/.local (owned by the runtime user) and is on PATH the instant the # container starts. Same script bare WSL and CI run at runtime — one source of truth. COPY bootstrap_python.sh /tmp/bootstrap_python.sh diff --git a/.gitignore b/.gitignore index 5ece19af..dda3759d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,11 @@ # Binary output dir, not recommended to push binary results to Git. /build +# Current-variant pointer written by tools/variant_data.py: a symlink (or, on +# Windows without Developer Mode, a copy) of the configured variant's build +# output directory. Generated output, never committed. +/generated + # Output directory of test results /test/output diff --git a/AGENTS.md b/AGENTS.md index 2a8d0696..079b89c0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -37,7 +37,7 @@ On a **bare Linux host (e.g. WSL Ubuntu) that is not a devcontainer**, `build.sh ```bash sudo ./bootstrap_ubuntu.sh # root: apt packages (libc6-dev, build-essential, 7zip, pipx) -./bootstrap_python.sh # user: uv, CPython 3.11, Poetry (into ~/.local) +./bootstrap_python.sh # user: uv, CPython 3.12, Poetry (into ~/.local) # then open a fresh shell (see note below) so ~/.local/bin is on PATH: ./build.sh --install # user: poetry install + poks toolchain ``` @@ -46,6 +46,24 @@ If `poetry` is not found after `bootstrap_python.sh`, open a new shell (or `sour **Always** start VS Code with: `.\build.ps1 -startVSCode` to ensure proper environment variables and Python virtual environment activation (`.venv` with Poetry dependencies). +### Working against a local spl-core checkout + +Some changes to this project need a change in `spl-core` first. `CMakeLists.txt` +locates spl-core by importing it from the venv and then includes its +`spl.cmake`, so a single editable install redirects **both** the Python and the +CMake side at a local checkout: + +```bash +.venv/bin/python -m pip install -e ../spl-core --no-deps # point at the checkout +./build.sh --install # ...and back to the pinned release +``` + +The path is deliberately **not** committed to `pyproject.toml`: CI must keep +resolving the released spl-core, so a local override never silently becomes the +build everyone gets. Land the spl-core change upstream, release it, and bump the +pin in `pyproject.toml` — the override is for the span of one change, not a +working mode. + ### VS Code CMake Extension Configuration VS Code users can build directly using the CMake extension via `.vscode` configuration files: diff --git a/README.md b/README.md index d8cfd2cc..dca9cdc5 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ Provision the OS-level prerequisites once per machine, then install as usual: ```bash sudo ./bootstrap_ubuntu.sh # apt packages -./bootstrap_python.sh # uv, CPython 3.11, Poetry (into ~/.local) +./bootstrap_python.sh # uv, CPython 3.12, Poetry (into ~/.local) # open a fresh shell (or `source ~/.bashrc`) so ~/.local/bin is on PATH ./build.sh --install ``` diff --git a/bootstrap.json b/bootstrap.json index daa30d4f..86479e54 100644 --- a/bootstrap.json +++ b/bootstrap.json @@ -1,5 +1,5 @@ { - "python_version": "3.11", + "python_version": "3.12", "python_package_manager": "poetry>=2.1.0", "bootstrap_packages": [ "pip-system-certs>=4.0,<5.0" diff --git a/bootstrap_python.sh b/bootstrap_python.sh index 020d851f..f6af5a0e 100755 --- a/bootstrap_python.sh +++ b/bootstrap_python.sh @@ -1,7 +1,7 @@ #!/bin/bash # User-level Python toolchain for SPLed (no root / no sudo). -# Installs uv, a standalone CPython 3.11, and Poetry into the current user's -# ~/.local, then points Poetry at the uv 3.11 for the project virtualenv. +# Installs uv, a standalone CPython 3.12, and Poetry into the current user's +# ~/.local, then points Poetry at the uv 3.12 for the project virtualenv. # uv and Poetry are version-pinned below; bump them there. # Run AFTER bootstrap_ubuntu.sh (which provides pipx): # - the devcontainer bakes it into the image at build time (.devcontainer/Dockerfile) @@ -15,9 +15,11 @@ set -euo pipefail # line is what introduced the bare-`python` PATH probe worked around below. UV_VERSION="0.11.32" POETRY_VERSION="2.4.1" -# Minor only: the 3.11 patch level deliberately floats so CPython security fixes are -# picked up. pyproject requires >=3.11,<3.12, so any 3.11.x satisfies the project. -PYTHON_VERSION="3.11" +# Minor only: the 3.12 patch level deliberately floats so CPython security fixes are +# picked up. pyproject requires >=3.12,<3.14, and 3.12 is the minor this project is +# built and gated on -- one interpreter everywhere, so a path computed from the minor +# (a venv site-packages path, a tool config) cannot disagree with the venv that exists. +PYTHON_VERSION="3.12" # pipx (from bootstrap_ubuntu.sh) installs isolated CLI tools into ~/.local/bin. # --force on both installs below: pipx keys on the package NAME, not the version spec, @@ -28,7 +30,7 @@ pipx ensurepath # ~/.local/bin on PATH for future shells export PATH="$HOME/.local/bin:$PATH" # ...and for the rest of this script # Standalone CPython (prebuilt: no from-source build, no GPG keyserver; -# matches pyproject requires-python <3.12,>=3.11). +# matches pyproject requires-python >=3.12,<3.14). uv python install "$PYTHON_VERSION" uv_py="$(uv python find "$PYTHON_VERSION")" @@ -36,7 +38,7 @@ uv_py="$(uv python find "$PYTHON_VERSION")" # interpreter — verified that `virtualenvs.use-poetry-python = true` does NOT suppress # this. The base image ships no bare `python`, so `poetry install` dies with # "[Errno 2] No such file or directory: 'python'". Give it one, pointing at the -# uv-managed 3.11 (which matches pyproject's requires-python). This is the same thing +# uv-managed 3.12 (which matches pyproject's requires-python). This is the same thing # the pre-refactor image achieved; we just keep it in ~/.local instead of /usr/local. # # ONLY `python` — deliberately NOT `python3`: every host this runs on already has a @@ -50,7 +52,7 @@ pipx install --force --python "$uv_py" "poetry==$POETRY_VERSION" # in-project set GLOBALLY (no --local): this script also runs at image-build time, where # the CWD is not a writable project dir. build.sh --install re-applies `--local` in the -# workspace anyway. use-poetry-python additionally pins Poetry's own uv-3.11 as the venv +# workspace anyway. use-poetry-python additionally pins Poetry's own uv-3.12 as the venv # interpreter (belt-and-suspenders alongside the `python` symlink above). poetry config virtualenvs.in-project true poetry config virtualenvs.use-poetry-python true diff --git a/pypeline.yaml b/pypeline.yaml index 5af50f6d..6ea98ca6 100644 --- a/pypeline.yaml +++ b/pypeline.yaml @@ -3,12 +3,12 @@ pipeline: module: pypeline.steps.create_venv config: bootstrap_script: .bootstrap/bootstrap.py - # The uv-managed python3.11 (installed by bootstrap_python.sh into ~/.local/bin) - # is what "python_version: 3.11" would auto-discover via PATH, but its bundled + # The uv-managed python3.12 (installed by bootstrap_python.sh into ~/.local/bin) + # is what "python_version: 3.12" would auto-discover via PATH, but its bundled # ensurepip cannot seed pip into a fresh venv. This step's own throwaway venv - # (just used to pip-install poetry) doesn't need 3.11, so point it at the + # (just used to pip-install poetry) doesn't need 3.12, so point it at the # apt-provisioned system python3 instead -- poetry itself still targets the - # uv-managed 3.11 via virtualenvs.use-poetry-python (see bootstrap_python.sh). + # uv-managed 3.12 via virtualenvs.use-poetry-python (see bootstrap_python.sh). python_executable: python3 python_package_manager: poetry>=2.2 - step: PoksInstall diff --git a/ubproject.toml b/ubproject.toml index 5625b187..54a843dd 100644 --- a/ubproject.toml +++ b/ubproject.toml @@ -1,6 +1,6 @@ "$schema" = "https://ubcode.useblocks.com/ubproject.schema.json" -extend = ".venv/lib/python3.13/site-packages/spl_core/report_generation/ubproject.toml" +extend = ".venv/lib/python3.12/site-packages/spl_core/report_generation/ubproject.toml" [server] index_on_save = true @@ -13,8 +13,6 @@ flavour = "myst" include = ["*.md"] [needs] -schema_definitions_from_json = "schema.json" - variant_data_file = "build/autoconf.json" src_trace_config_from_toml = "ubproject.toml" From 1e39d462485a5880cd9762ea3c3ece362f2c960b Mon Sep 17 00:00:00 2001 From: Lucas Silber Date: Wed, 16 Sep 2026 15:42:38 +0200 Subject: [PATCH 05/19] feat: generate variant data once, and delete the Jinja hook Phases 2 and 4 of doc/variant-handling-without-jinja-plan.md. Phase 4 is pulled ahead of Phase 3 because its only blocker -- spl-core emitting `{% raw %}` into the generated source listings -- is now gone, and until the hook was gone no intermediate state could be verified: a bare `sphinx-build` crashed outright on any brace in any document. One generation step, one map ---------------------------- tools/variant_data.py writes build/variants///.json for the whole matrix, plus build/autoconf.json as the "current" pointer and a `generated` link to the current build directory. Each cell carries everything a condition may name: the complete feature vector (including the promptless booleans KConfig omits, via the new spl-core accessor), the variant, the kit, the target, and the component list parsed out of parts.cmake. That last one matters twice. It is the single source of truth with parts.cmake, so the integration-test document can be gated by membership instead of by a hard-coded variant name; and `target` being IN the file is what turns the report fences from undecidable into a clean false for a reader that is not building reports. conf.py no longer synthesizes any of it. It reads one file and adds nothing -- which is the whole point, since ubCode, ubc and a reviewer's editor cannot execute it. CMake picks the cell per build shape through spl-core's new SPL_VARIANT_DATA_FILE_DOCS / _REPORTS. The generator is a standalone script, not CMake code, on purpose: KConfig is pure Python while the top-level project() call demands a C toolchain, so the documentation and its gate must not have to go through CMake at all. Hardened before committing: the parts.cmake parser now raises on any construct outside the grammar this project uses, instead of treating a guarded body as unconditional and returning a component list that is quietly wrong for one kit. `--check` regenerates and diffs for CI. 37 tests cover the grammar, the completeness of the feature vector and the shape of every cell. No Jinja -------- The `source-read` hook and `{{ timestamp }}` are gone. The timestamp is the theme's last-updated footer instead, which also makes the build reproducible. `sphinx-build -b html .` now works standalone for any variant -- previously it rendered the components page empty, without a warning, and crashed on braces. Verified: Disco (AUTO_OFF off, BLINKING on) builds neither the auto_off nor the brightness_controller page and does build the integration document; Sleep builds both component pages and not the integration document. Co-Authored-By: Claude Opus 5 (1M context) --- CMakeLists.txt | 65 ++++++-- conf.py | 145 +++++----------- index.md | 1 - test/test_variant_data.py | 199 ++++++++++++++++++++++ tools/variant_data.py | 341 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 634 insertions(+), 117 deletions(-) create mode 100644 test/test_variant_data.py create mode 100644 tools/variant_data.py diff --git a/CMakeLists.txt b/CMakeLists.txt index b1e0516e..717b4a94 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,23 +46,60 @@ else() endif() include("${SPL_CORE_DIR}/spl.cmake") -# Mirror the currently configured variant's KConfig JSON to a fixed path, the same -# way cmake.copyCompileCommands mirrors compile_commands.json, so tools outside CMake -# (e.g. ubproject.toml's variant_data_file) can track whichever variant/build kit/build -# type is currently selected in the CMake Tools extension without a hardcoded path. +# Variant data: one generation step, everything downstream declarative. # -# The variant NAME is added alongside the features. KConfig only knows features, but -# the `if` conditions in ubproject.toml are read by tools that never run CMake or -# Sphinx (ubCode, the language server). Everything such a condition names has to be in -# this file, or the tool cannot decide it and its view of the project disagrees with -# the build. -if(EXISTS ${AUTOCONF_JSON}) - file(READ ${AUTOCONF_JSON} _spled_variant_data) - string(JSON _spled_variant_data SET "${_spled_variant_data}" - build_config "{\"variant\": \"${VARIANT}\"}") - file(WRITE ${CMAKE_SOURCE_DIR}/build/autoconf.json "${_spled_variant_data}") +# tools/variant_data.py writes build/variants///.json for +# every variant, plus build/autoconf.json as the "current" pointer and a +# `generated` symlink to this build directory. It is invoked here so that +# configuring in the IDE keeps the pointer in step with whatever CMake Tools +# selected -- the same role cmake.copyCompileCommands plays for the compilation +# database. +# +# It is deliberately a standalone script and not CMake code. KConfig is pure +# Python, while the top-level project() call above demands a C toolchain before +# CMake will configure at all, so anything expressed here could only ever run on +# a machine that can build the software. The documentation, its quality gate and +# the IDE must not need a compiler, so the same script has to be runnable on its +# own: +# +# python tools/variant_data.py --all +# +# Everything a condition in ubproject.toml or an {if} directive may name lives in +# these files -- the complete feature vector including the booleans KConfig omits, +# the variant, the build kit, the build target and the component list. A key that +# only conf.py knew would be invisible to ubCode and every other reader, and their +# view of the project would then silently disagree with the build. +set(_SPLED_VARIANT_DATA_SCRIPT ${CMAKE_SOURCE_DIR}/tools/variant_data.py) +set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS + ${_SPLED_VARIANT_DATA_SCRIPT} + ${CMAKE_SOURCE_DIR}/variants/${VARIANT}/parts.cmake +) +execute_process( + COMMAND "${_VENV_PYTHON}" ${_SPLED_VARIANT_DATA_SCRIPT} + --variant ${VARIANT} + --kit ${BUILD_KIT} + --target docs + --current + --build-dir ${CMAKE_BINARY_DIR} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + RESULT_VARIABLE _spled_variant_data_result + OUTPUT_VARIABLE _spled_variant_data_output + ERROR_VARIABLE _spled_variant_data_output +) +if(NOT _spled_variant_data_result EQUAL 0) + message(FATAL_ERROR "tools/variant_data.py failed:\n${_spled_variant_data_output}") endif() +# Point each Sphinx build shape at its own cell of the matrix. The `docs` cell is +# also the one build/autoconf.json holds, so ubCode and the docs build evaluate +# byte-identical data. The reports fences then resolve to a clean false in the +# IDE rather than being undecidable -- `target` is present, it simply is not +# "reports" there. +set(SPL_VARIANT_DATA_FILE_DOCS + ${CMAKE_SOURCE_DIR}/build/variants/${VARIANT}/${BUILD_KIT}/docs.json CACHE FILEPATH "" FORCE) +set(SPL_VARIANT_DATA_FILE_REPORTS + ${CMAKE_SOURCE_DIR}/build/variants/${VARIANT}/${BUILD_KIT}/reports.json CACHE FILEPATH "" FORCE) + # The object_deps_report extension is currently Windows-only: its index.cmake # hardcodes the runner as "object_deps_report.exe", which does not exist on # Linux/macOS (the console script is "object_deps_report"). Guard the include to diff --git a/conf.py b/conf.py index 1a285fe5..9b845339 100644 --- a/conf.py +++ b/conf.py @@ -1,11 +1,8 @@ # -*- coding: utf-8 -*- """Configuration""" import datetime -import json import os -from pathlib import Path - from importlib.resources import files from spl_core.report_generation.spl_sphinx import SplSphinx from spl_core.report_generation.spl_html_settings import html_theme, html_show_sourcelink, html_theme_options, html_sidebars, html_last_updated_fmt # noqa: F401 @@ -83,67 +80,47 @@ # Always write the merged needs.json (all needs, after import/resolution) to the build output dir. needs_build_json = True -# Expose KConfig feature values (e.g. CUSTOMER) as `var.features.*`. -# Used by the {if} directive in the documents and by the `if` conditions of -# [[source.mounts]] in ubproject.toml. CMake exports AUTOCONF_JSON_FILE for the -# variant it is building; the mirrored copy written by CMakeLists.txt is the -# fallback so a bare `sphinx-build` (or the IDE) resolves the same variant -# instead of silently dropping every variant-gated document. -_autoconf_json_file = os.environ.get("AUTOCONF_JSON_FILE", "build/autoconf.json") -if os.path.exists(_autoconf_json_file): - needs_variant_data_file = _autoconf_json_file - - -def _feature_defaults() -> dict: - """Every boolean the feature model declares, defaulted to False. - - KConfig writes a boolean into autoconf.json only when it has a prompt or - evaluates to y. A helper symbol such as BRIGHTNESS_ADJUSTMENT_ENABLED is - therefore simply absent from the variants where it is off, and a document - or a mount condition naming it would fail to evaluate for exactly those - variants. Reading the model once and defaulting its booleans keeps the - variant data complete without anyone having to maintain a list by hand. - """ - try: - import kconfiglib - import spl_core - - os.environ.setdefault("SPL_CORE_DIR", str(Path(spl_core.__file__).parent)) - os.environ.setdefault("srctree", str(Path(__file__).parent)) - model = kconfiglib.Kconfig("KConfig", warn=False) - except Exception: # noqa: BLE001 - the feature model is optional context here - return {} - return { - name: False - for name, symbol in model.syms.items() - if name and symbol.orig_type == kconfiglib.BOOL - } - - -_features = _feature_defaults() -if os.path.exists(_autoconf_json_file): - with open(_autoconf_json_file) as _handle: - _features.update(json.load(_handle)["features"]) - -html_context = SplSphinx.get_default_html_context() - -build_config = html_context["build_config"].copy() -build_config.pop("components_info", None) - -# Two shapes of build use this configuration: the variant-wide report, and the -# per-component report that spl-core builds for a single component. Normalize -# the difference into plain keys so a document or a mount condition can test it -# without having to know which keys CMake happens to write. CMake writes -# `target` only for the variant-wide builds; for a per-component build it is the -# name of the directory holding the configuration file CMake pointed us at. -_build_config_file = os.environ.get("SPHINX_BUILD_CONFIGURATION_FILE", "") -build_config.setdefault( - "target", "reports" if Path(_build_config_file).parent.name == "reports" else "docs" -) -build_config["scope"] = "component" if build_config.get("component_info") else "variant" - -_extra_include_patterns = html_context["build_config"].get("include_patterns", []) -if build_config["scope"] == "variant": +# variant data ############################################################## +# +# ONE file, and nothing computed here. +# +# Everything a document's {if} directive or a condition in ubproject.toml may +# name -- the complete feature vector, the variant, the build kit, the build +# target, the component list -- is written by tools/variant_data.py and read +# from here verbatim. That is the whole point: ubCode, ubc and a reviewer's +# editor cannot execute this file, so anything synthesized here would be +# invisible to them and their view of the project would silently disagree with +# the build. The rule is "everything a condition may name has to be IN the +# file", and it only holds if this file adds nothing. +# +# CMake exports VARIANT_DATA_FILE for the build shape it is building. The +# fallback is the "current" pointer that tools/variant_data.py maintains, which +# is also what ubproject.toml names -- so a bare `sphinx-build`, the IDE and the +# docs target all resolve byte-identical data. +needs_variant_data_file = os.environ.get("VARIANT_DATA_FILE", "build/autoconf.json") + +# build shape ############################################################### +# +# The rest of this file is Sphinx plumbing, not variant data: which documents +# are in the source set and which one is the root. No other tool needs to +# decide these, and none of it may leak into `var.*`. + +_build_config = SplSphinx.get_default_html_context()["build_config"] +_extra_include_patterns = _build_config.get("include_patterns", []) + +# Two shapes of build use this configuration: the variant-wide one, and the +# per-component report that spl-core builds for a single component. +if _build_config.get("component_info"): + root_doc = "doc/component_report" + exclude_patterns.append("index.md") + # A per-component report builds one component and stitches its own table of + # contents, so none of the mounts apply. Switching TOML reading off here is + # what lets every `if` in ubproject.toml speak only about the variant, which + # is the only way a reader that never runs Sphinx can decide them too. + sources_from_toml = None +else: + root_doc = "index" + exclude_patterns.append("doc/component_report.md") # Component design documents are mounted by sphinx-mounts, see # [[source.mounts]] in ubproject.toml. Reading them as ordinary sources as # well would parse every need in them a second time under a second docname. @@ -157,46 +134,10 @@ def _feature_defaults() -> dict: # The docs target used to read them anyway and then leave them out of # every toctree, which is 15 orphan warnings for pages nobody sees. and not ( - build_config["target"] == "docs" + _build_config.get("target") != "reports" and pattern.startswith("build/") and pattern.endswith("/reports/**") ) ] - root_doc = "index" - exclude_patterns.append("doc/component_report.md") -else: - root_doc = "doc/component_report" - exclude_patterns.append("index.md") - # A per-component report builds one component and stitches its own table of - # contents, so none of the mounts apply. Switching TOML reading off here is - # what lets every `if` in ubproject.toml speak only about the variant, which - # is the only way a reader that never runs Sphinx can decide them too. - sources_from_toml = None -include_patterns.extend(_extra_include_patterns) -needs_variant_data = { - "features": _features, - "build_config": build_config, -} - - -def rstjinja(app, docname, source): - """Render every source file as a Jinja template before Sphinx parses it. - - No hand-written document in this project uses Jinja any more: variant - dependent content is selected by the {if} directive of Sphinx-Needs inside - a document, and by [[source.mounts]] conditions in ubproject.toml for whole - documents. The hook still has to run, because the source listings that - spl-core generates under `__source_docs` wrap their code blocks in - `{% raw %}` and depend on this pass to strip those markers. - """ - # Make sure we're outputting HTML - if app.builder.format != "html": - return - src = source[0] - rendered = app.builder.templates.render_string(src, app.config.html_context) - source[0] = rendered - - -def setup(app): - app.connect("source-read", rstjinja) +include_patterns.extend(_extra_include_patterns) diff --git a/index.md b/index.md index dda4fb36..2003e92a 100644 --- a/index.md +++ b/index.md @@ -1,7 +1,6 @@ # Variant Report **Variant:** {variant}`build_config.variant` -**Timestamp:** {{ timestamp }} ```{toctree} :maxdepth: 1 diff --git a/test/test_variant_data.py b/test/test_variant_data.py new file mode 100644 index 00000000..c8d961ad --- /dev/null +++ b/test/test_variant_data.py @@ -0,0 +1,199 @@ +"""Tests for the variant data generator, tools/variant_data.py. + +These run without a compiler, a CMake configure or a Sphinx build, which is the +whole point of the generator: the documentation and its gate must be derivable +from the sources alone. + +The parts.cmake parser gets the most attention here. It is the one place where +this project reads a CMake file with something other than CMake, and the failure +mode it guards against is silent: a parser that shrugged at a condition it did +not understand would return a component list that is wrong for some kit, and +that list is what gates the documents. The symptom would be documents missing +from a variant's build -- no error, no warning, just less. +""" + +import json +import sys +from pathlib import Path + +import pytest + +PROJECT_ROOT = Path(__file__).resolve().parent.parent +sys.path.insert(0, str(PROJECT_ROOT / "tools")) + +import variant_data # noqa: E402 + +# The variant data is the input to every documentation gate, and generating it +# is pure Python that costs milliseconds -- so it runs in all of them. A +# regression here does not fail a build; it silently drops documents from a +# variant, which is exactly what a gate has to catch early. +pytestmark = [ + pytest.mark.unittests, + pytest.mark.gate_develop_pr, + pytest.mark.gate_develop_push, + pytest.mark.gate_develop_nightly, + pytest.mark.gate_release_pr, + pytest.mark.gate_release, +] + + +# --- the variant set ------------------------------------------------------- + + +def test_finds_every_variant_including_nested_ones() -> None: + """A variant is a directory holding config.cmake, one or two levels deep.""" + assert variant_data.variant_names(PROJECT_ROOT) == [ + "Base/Dev", + "Disco", + "IDEA/Sloemada", + "Sleep", + "Spa", + ] + + +# --- parts.cmake: the grammar this project actually uses -------------------- + + +@pytest.mark.parametrize("kit", variant_data.KITS) +def test_flat_parts_file_is_kit_independent(kit: str) -> None: + """Spa has no guard, so both kits see the same components.""" + assert variant_data.components(PROJECT_ROOT, "Spa", kit) == variant_data.components(PROJECT_ROOT, "Spa", "prod") + + +def test_test_kit_guard_is_honoured() -> None: + """Disco adds its integration suite only for the test kit.""" + prod = variant_data.components(PROJECT_ROOT, "Disco", "prod") + test = variant_data.components(PROJECT_ROOT, "Disco", "test") + + assert "test/spled_integration" not in prod + assert "test/spled_integration" in test + assert test[: len(prod)] == prod, "the guard must only ADD, not reorder" + + +def test_component_order_follows_the_file() -> None: + """The list is the product structure, so it keeps parts.cmake's order.""" + assert variant_data.components(PROJECT_ROOT, "Disco", "prod")[:3] == [ + "components/platform_types", + "components/rte", + "components/main", + ] + + +# --- parts.cmake: everything outside the grammar has to raise --------------- + + +def _write_parts(tmp_path: Path, body: str) -> Path: + parts_dir = tmp_path / "variants" / "Fake" + parts_dir.mkdir(parents=True) + (parts_dir / "parts.cmake").write_text(body) + return tmp_path + + +@pytest.mark.parametrize( + ("body", "because"), + [ + ("if(SOME_OTHER_CONDITION)\n spl_add_component(components/a)\nendif()\n", "unknown condition"), + ("if(BUILD_KIT STREQUAL test)\n if(X)\n endif()\nendif()\n", "nested if"), + ("if(BUILD_KIT STREQUAL test)\nelseif(X)\nendif()\n", "elseif"), + ("spl_add_component(components/a)\nendif()\n", "endif without if"), + ("else()\n", "else outside if"), + ("if(BUILD_KIT STREQUAL test)\n spl_add_component(components/a)\n", "unterminated if"), + ("set(SOMETHING on)\n", "statement that is not spl_add_component"), + ], +) +def test_unsupported_grammar_raises(tmp_path: Path, body: str, because: str) -> None: + root = _write_parts(tmp_path, body) + with pytest.raises(ValueError): + variant_data.components(root, "Fake", "test") + + +def test_comments_and_blank_lines_are_ignored(tmp_path: Path) -> None: + root = _write_parts( + tmp_path, + "# leading comment\n\nspl_add_component(components/a) # trailing\n\n", + ) + assert variant_data.components(root, "Fake", "prod") == ["components/a"] + + +def test_else_branch_belongs_to_the_other_kit(tmp_path: Path) -> None: + """Not used in this project today, but it must not be silently wrong.""" + root = _write_parts( + tmp_path, + "if(BUILD_KIT STREQUAL test)\n" + " spl_add_component(test/suite)\n" + "else()\n" + " spl_add_component(components/stub)\n" + "endif()\n", + ) + assert variant_data.components(root, "Fake", "test") == ["test/suite"] + assert variant_data.components(root, "Fake", "prod") == ["components/stub"] + + +# --- the feature vector ---------------------------------------------------- + + +def test_feature_vector_is_complete() -> None: + """Every boolean the model declares is present, so no condition is unknown. + + BRIGHTNESS_ADJUSTMENT_ENABLED is the case that motivates this: it is + promptless, so KConfig omits it from its JSON for exactly the variants + where it is off. A document or a mount condition naming it would then be + unevaluable there -- and both tools gate unevaluable content OFF rather + than answering False, so the content would silently disappear. + """ + disco = variant_data.features(PROJECT_ROOT, "Disco") + sleep = variant_data.features(PROJECT_ROOT, "Sleep") + + for name in ("BRIGHTNESS_ADJUSTMENT_ENABLED", "AUTO_OFF", "BLINKING", "COLOR_1_IS_ENABLED"): + assert name in disco, f"{name} missing from Disco's feature vector" + assert name in sleep, f"{name} missing from Sleep's feature vector" + + # Disco selects BLINKING, which the brightness choice depends on being off. + assert disco["BLINKING"] is True + assert disco["BRIGHTNESS_ADJUSTMENT_ENABLED"] is False + assert disco["AUTO_OFF"] is False + + # Sleep selects manual brightness and auto-off. + assert sleep["BLINKING"] is False + assert sleep["BRIGHTNESS_ADJUSTMENT_ENABLED"] is True + assert sleep["AUTO_OFF"] is True + + +def test_non_boolean_values_keep_their_type() -> None: + disco = variant_data.features(PROJECT_ROOT, "Disco") + assert disco["CUSTOMER"] == "A" + assert disco["OS_TASK_PERIOD"] == 10 + + +def test_variant_without_config_txt_uses_model_defaults() -> None: + """Base/Dev ships no config.txt; the model's own defaults apply.""" + assert not (PROJECT_ROOT / "variants" / "Base" / "Dev" / "config.txt").exists() + base = variant_data.features(PROJECT_ROOT, "Base/Dev") + assert base["CUSTOMER"] == "None" + assert base["BLINKING"] is False + + +# --- the cell as a whole --------------------------------------------------- + + +@pytest.mark.parametrize("variant", ["Base/Dev", "Disco", "IDEA/Sloemada", "Sleep", "Spa"]) +@pytest.mark.parametrize("kit", variant_data.KITS) +@pytest.mark.parametrize("target", variant_data.TARGETS) +def test_every_cell_carries_the_whole_contract(variant: str, kit: str, target: str) -> None: + """Everything a condition may name has to be IN the file. + + This is the rule the whole design rests on: a key that only conf.py knows + is invisible to ubCode and every other reader, and their view of the + project then silently disagrees with the build. + """ + data = variant_data.variant_data(PROJECT_ROOT, variant, kit, target) + + assert set(data) == {"features", "build_config"} + assert data["build_config"]["variant"] == variant + assert data["build_config"]["kit"] == kit + assert data["build_config"]["target"] == target + assert data["build_config"]["components"] + assert data["features"] + + # It has to survive the round trip to the file both tools read. + assert json.loads(json.dumps(data)) == data diff --git a/tools/variant_data.py b/tools/variant_data.py new file mode 100644 index 00000000..5ac9382a --- /dev/null +++ b/tools/variant_data.py @@ -0,0 +1,341 @@ +#!/usr/bin/env python3 +"""Write the variant data files that every documentation tool reads. + +One generation step, one artefact per build shape, and nothing downstream needs +Python to decide anything: `ubproject.toml` conditions and the `{if}` directives +in the documents read these files, and so do `sphinx-build`, `ubc` and the +ubCode language server. The rule that follows from that is the only one worth +remembering here: + + everything a condition may name has to be IN the file. + +A key that only `conf.py` knows is invisible to every other reader, and their +view of the project then silently disagrees with the build. That is why this +writes the complete feature vector -- including the booleans KConfig omits -- +plus the build shape, rather than leaving any of it to be synthesized later. + +Layout:: + + build/variants///.json the matrix + build/variants/GENERATED marker; nobody edits generated output + build/autoconf.json the "current" pointer, one cell of the matrix + generated/ symlink to the current cell's build directory + +Nothing here needs a compiler. KConfig is pure Python, while CMake's top-level +`project()` call demands a C toolchain before it will even configure -- so with +this script the documentation and its quality gate can be built on a machine +that cannot build the software. + +Usage:: + + python tools/variant_data.py --all + python tools/variant_data.py --variant Disco --kit test --target reports --current + python tools/variant_data.py --all --check # CI: regenerate and diff +""" + +from __future__ import annotations + +import argparse +import json +import os +import shutil +import sys +from pathlib import Path +from typing import Any + +PROJECT_ROOT = Path(__file__).resolve().parent.parent + +#: Components a variant's `parts.cmake` adds only for the test build kit. +TEST_KIT_GUARD = "BUILD_KIT STREQUAL test" + +#: The build shapes spl-core builds. `docs` is the design documentation of a +#: variant; `reports` additionally carries the generated test and coverage +#: output, which is why documents gate report sections on it. +TARGETS = ("docs", "reports") + +#: The build kits. `components` differs between them, because a variant's +#: parts.cmake adds its test suites only for `test`. +KITS = ("prod", "test") + + +def _spl_core_dir() -> Path: + import spl_core + + return Path(spl_core.__file__).parent + + +def variant_names(project_root: Path) -> list[str]: + """Every variant in `variants/`, as the slash-separated name CMake uses. + + A variant is a directory holding a `config.cmake`; it may be one level deep + (`Disco`) or two (`Base/Dev`), which is why this walks rather than lists. + """ + variants_dir = project_root / "variants" + return sorted( + str(path.parent.relative_to(variants_dir)).replace(os.sep, "/") + for path in variants_dir.rglob("config.cmake") + ) + + +def features(project_root: Path, variant: str) -> dict[str, Any]: + """The variant's complete feature vector. + + Every boolean the feature model declares is present, defaulted to False, + before the variant's own values are overlaid. KConfig writes a boolean into + its JSON only when the symbol has a prompt or evaluates to y, so a helper + symbol such as BRIGHTNESS_ADJUSTMENT_ENABLED is simply absent from the + variants where it is off -- and a condition naming it would fail to evaluate + for exactly those variants, which both tools then report as unevaluable and + gate off rather than cleanly answering False. + """ + from spl_core.kconfig.kconfig import JsonWriter, KConfig + + os.environ.setdefault("SPL_CORE_DIR", str(_spl_core_dir())) + os.environ.setdefault("srctree", str(project_root)) + + model_file = project_root / "KConfig" + config_file = project_root / "variants" / variant / "config.txt" + + kconfig = KConfig( + model_file, + config_file if config_file.exists() else None, + project_root, + ) + + # `KConfig.config` only carries the symbols KConfig would write out. The + # model itself knows every symbol, which is what makes the vector complete. + defaults = dict.fromkeys(kconfig.declared_boolean_symbols(), False) + + # spl-core's own JSON writer, so the value conversion (tristates to bool, + # hex, the ${VAR} substitution) is the build's and not a second opinion. + # `generate_content` is the pure half of it; nothing is written here. + values = json.loads(JsonWriter(Path(os.devnull)).generate_content(kconfig.config))["features"] + return {**defaults, **values} + + +def components(project_root: Path, variant: str, kit: str) -> list[str]: + """The components the variant's `parts.cmake` adds for this build kit. + + Parsed rather than imported, because the point is to state the product + structure once, in the file that already states it. The grammar every + parts.cmake in this project uses is a flat list of `spl_add_component()` + calls, optionally with one `if(BUILD_KIT STREQUAL test)` / `else()` / + `endif()` block. + + Anything outside that grammar raises. A parser that shrugged at an `if()` + it did not understand would treat the guarded body as unconditional and + hand back a component list that is quietly wrong for some kit -- and since + this list is what gates the documents, the failure would surface as + documents silently missing from a variant, which is the hardest kind of + wrong to notice. Better to stop and make someone either extend the grammar + or derive the list from CMake. + """ + parts = project_root / "variants" / variant / "parts.cmake" + result: list[str] = [] + #: None outside any if-block, else the kit whose branch we are currently in. + branch_kit: str | None = None + depth = 0 + + for number, raw_line in enumerate(parts.read_text().splitlines(), start=1): + line = raw_line.split("#", 1)[0].strip() + if not line: + continue + lowered = line.lower() + where = f"{parts.relative_to(project_root)}:{number}" + + if lowered.startswith(("if(", "if ")): + if depth or TEST_KIT_GUARD.lower() not in lowered: + raise ValueError( + f"{where}: unsupported condition {line!r}. " + f"{parts.name} may only use a single, non-nested " + f"`if({TEST_KIT_GUARD})` block; extend the grammar in " + "tools/variant_data.py (and its test) deliberately." + ) + depth += 1 + branch_kit = "test" + continue + if lowered.startswith(("else(", "else ", "else")) and not lowered.startswith("elseif"): + if not depth: + raise ValueError(f"{where}: `else()` outside any `if()`.") + # The other side of the test-kit guard is every kit that is not test. + branch_kit = "prod" + continue + if lowered.startswith("elseif"): + raise ValueError(f"{where}: `elseif()` is not supported, see above.") + if lowered.startswith(("endif(", "endif ", "endif")): + if not depth: + raise ValueError(f"{where}: `endif()` without `if()`.") + depth -= 1 + branch_kit = None + continue + if lowered.startswith("spl_add_component("): + if branch_kit is not None and branch_kit != kit: + continue + result.append(line[len("spl_add_component(") : line.rindex(")")].strip()) + continue + + raise ValueError( + f"{where}: unsupported statement {line!r}. {parts.name} may only " + "contain `spl_add_component()` calls and the test-kit guard." + ) + + if depth: + raise ValueError(f"{parts.relative_to(project_root)}: unterminated `if()`.") + + return result + + +def variant_data(project_root: Path, variant: str, kit: str, target: str) -> dict[str, Any]: + """One cell of the matrix: everything a condition anywhere may name.""" + return { + "features": features(project_root, variant), + "build_config": { + "variant": variant, + "kit": kit, + "target": target, + "components": components(project_root, variant, kit), + }, + } + + +def cell_path(project_root: Path, variant: str, kit: str, target: str) -> Path: + return project_root / "build" / "variants" / variant / kit / f"{target}.json" + + +def write_cell(project_root: Path, variant: str, kit: str, target: str, data: dict[str, Any]) -> Path: + path = cell_path(project_root, variant, kit, target) + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n") + return path + + +def write_pointer(project_root: Path, data: dict[str, Any], build_dir: Path | None) -> None: + """Point `build/autoconf.json` and `generated` at one cell. + + `ubproject.toml` names both: the data file as `variant_data_file`, and the + build directory as the `dir` of the report mount. A mount `dir` is resolved + relative to the configuration file and cannot name variant data, so the + indirection has to be on the filesystem. + + The link is called `generated` and sits at the project root rather than + inside `build/`, and that name is load-bearing twice over. It is the docname + prefix of every generated page in BOTH tools: sphinx-mounts renames a mount's + documents to its `mount_at`, while ubCode keeps the path a file is found at, + so the only way one toctree entry can mean one page in both is for the path + and the prefix to be the same string. And ubCode's default `exclude` contains + "build", which would otherwise drop the whole mounted tree. + """ + pointer = project_root / "build" / "autoconf.json" + pointer.parent.mkdir(parents=True, exist_ok=True) + pointer.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n") + + if build_dir is None: + return + + current = project_root / "generated" + if current.is_symlink() or current.is_file(): + current.unlink() + elif current.is_dir(): + shutil.rmtree(current) + try: + current.symlink_to(build_dir.resolve(), target_is_directory=True) + except OSError: + # Windows without Developer Mode refuses a symlink. A copy keeps the + # path stable at the cost of duplicating the tree; the reports target + # rewrites its output wholesale anyway. + shutil.copytree(build_dir, current, dirs_exist_ok=True) + + +def mark_generated(project_root: Path) -> None: + marker = project_root / "build" / "variants" / "GENERATED" + marker.parent.mkdir(parents=True, exist_ok=True) + marker.write_text( + "Written by tools/variant_data.py. Everything under build/ is generated\n" + "output: nobody edits it, neither a person nor an assistant.\n" + ) + + +def _check(project_root: Path, selected: list[str]) -> int: + """Regenerate in memory and report cells that are missing or stale. + + The gate this serves is "the variant data on disk is what the sources say + it is". It has to be a comparison rather than a regeneration, because a + regeneration always passes: it would simply overwrite the drift it was + meant to catch and report success. + """ + stale: list[str] = [] + for variant in selected: + for kit in KITS: + for target in TARGETS: + expected = json.dumps(variant_data(project_root, variant, kit, target), indent=2, sort_keys=True) + "\n" + path = cell_path(project_root, variant, kit, target) + rel = path.relative_to(project_root) + if not path.exists(): + stale.append(f"{rel}: missing") + elif path.read_text() != expected: + stale.append(f"{rel}: stale") + + if stale: + print("variant data is not up to date:", file=sys.stderr) + for line in stale: + print(f" {line}", file=sys.stderr) + print("\nrun: python tools/variant_data.py --all", file=sys.stderr) + return 1 + + print(f"variant data up to date ({len(selected)} variants x {len(KITS)} kits x {len(TARGETS)} targets)") + return 0 + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) + parser.add_argument("--project-root", type=Path, default=PROJECT_ROOT) + parser.add_argument("--all", action="store_true", help="generate the whole matrix") + parser.add_argument("--variant", help="variant name, e.g. Disco or Base/Dev") + parser.add_argument("--kit", choices=KITS, default="prod") + parser.add_argument("--target", choices=TARGETS, default="docs") + parser.add_argument( + "--current", + action="store_true", + help="also write build/autoconf.json and the `generated` link for the selected cell", + ) + parser.add_argument( + "--build-dir", + type=Path, + help="CMake binary dir of the selected cell; `generated` points at it", + ) + parser.add_argument( + "--check", + action="store_true", + help="do not write: compare what would be generated against what is on disk", + ) + args = parser.parse_args(argv) + + project_root: Path = args.project_root.resolve() + + if not args.all and not args.variant: + parser.error("either --all or --variant is required") + + selected = variant_names(project_root) if args.all else [args.variant] + + if args.check: + return _check(project_root, selected) + + for variant in selected: + for kit in KITS: + for target in TARGETS: + data = variant_data(project_root, variant, kit, target) + path = write_cell(project_root, variant, kit, target, data) + print(f"wrote {path.relative_to(project_root)}") + + mark_generated(project_root) + + if args.current or not args.all: + data = variant_data(project_root, args.variant or selected[0], args.kit, args.target) + write_pointer(project_root, data, args.build_dir) + print(f"current -> {args.variant or selected[0]} / {args.kit} / {args.target}") + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) From 8dd1c809174a9b49756907d5a74fe98968af4bee Mon Sep 17 00:00:00 2001 From: Lucas Silber Date: Wed, 16 Sep 2026 15:46:33 +0200 Subject: [PATCH 06/19] refactor: make ubproject.toml the one configuration both readers read Phase 3 of doc/variant-handling-without-jinja-plan.md. The configuration was split in a way that could not be noticed from either end. ubproject.toml extended spl-core's base configuration through a venv path with the Python minor baked into it -- a path that did not exist, so for ubCode the base configuration silently resolved to nothing. conf.py resolved the same file a second time through importlib, so the Sphinx build was fine. Two mechanisms for one file, one of them broken, and the symptom is the IDE quietly disagreeing with the build about which link types and need types exist. sphinx-needs settles which way this has to be fixed: it reads exactly one TOML file's [needs] table and implements no include mechanism at all. So the one file has to be complete. spl-core's base configuration is vendored in, and `extend` is gone. Vendored while modernizing `extra_links` and `extra_options` into the `links` and `fields` tables -- both deprecated in sphinx-needs and warned about on every build, and this file already used the new form for its own `image` field, so it was mixing the two. `integrity` carries an explicit empty-string default because that is what the deprecated form implies; without it the field would have flipped to null on all 87 needs. Verified: same 87 needs, same IDs, zero differing fields, three fewer warnings. Vendoring is a copy and copies rot, so test_ubproject_config.py compares the copy against what the installed spl-core ships and fails on drift. The two deliberate deviations are asserted as such rather than merely tolerated. The larger one is `extend_exclude = ["build/**"]`. spl-core turns respect_gitignore off and then excludes nothing under the build directory, so ubCode indexed every variant and kit ever built on the machine: every need ID many times over, and no possibility of agreeing with any one build. Sphinx never saw it because conf.py narrows its source set per build shape. The generated source listings are still indexed, through `generated/**` -- the current variant's build directory -- so it is one variant's listings instead of all of them. conf.py also has to apply the per-build-shape data file after sphinx-needs reads the TOML, which happens on config-inited: the pointer in the TOML would otherwise win and hand the reports build the docs data, silently evaluating every report fence false. files.readonlyInclude makes the editor refuse edits under build/ and generated/. It is the one guard rail that applies to an assistant as well as a person. Co-Authored-By: Claude Opus 5 (1M context) --- .vscode/settings.json | 8 ++ conf.py | 51 ++++++------ test/test_ubproject_config.py | 143 ++++++++++++++++++++++++++++++++++ ubproject.toml | 115 ++++++++++++++++++++++++++- 4 files changed, 294 insertions(+), 23 deletions(-) create mode 100644 test/test_ubproject_config.py diff --git a/.vscode/settings.json b/.vscode/settings.json index 9d1d758d..fb6ee1a3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,6 +14,14 @@ "editor.defaultFormatter": "josetr.cmake-language-support-vscode" }, "cmake.configureOnOpen": false, + // Generated output. tools/variant_data.py writes the variant data here and + // CMake writes everything else; an edit under build/ is lost on the next + // configure, and worse, it looks like it worked. Making the editor refuse + // the edit is the only guard rail that also applies to an assistant. + "files.readonlyInclude": { + "build/**": true, + "generated/**": true + }, "cmake.buildDirectory": "${workspaceFolder}/build/${variant:variant}/${buildKit}/${buildType}", "cmake.copyCompileCommands": "${workspaceFolder}/build/compile_commands.json", "cmake.configureSettings": { diff --git a/conf.py b/conf.py index 9b845339..71c25f49 100644 --- a/conf.py +++ b/conf.py @@ -3,7 +3,6 @@ import datetime import os -from importlib.resources import files from spl_core.report_generation.spl_sphinx import SplSphinx from spl_core.report_generation.spl_html_settings import html_theme, html_show_sourcelink, html_theme_options, html_sidebars, html_last_updated_fmt # noqa: F401 @@ -60,26 +59,19 @@ myst_enable_extensions = extension_configs["myst_enable_extensions"] source_suffix = extension_configs["source_suffix"] -# Import default SPL sphinx-needs configuration -needs_from_toml = str(files("spl_core.report_generation").joinpath("ubproject.toml")) +# The needs model -- types, link types, fields, and the build_json switch -- +# lives in ubproject.toml, which ubCode and ubc read directly. sphinx-needs +# reads exactly one TOML file and does not implement ubCode's `extend`, so the +# only way both readers can agree is for that one file to be complete. It is; +# spl-core's base configuration is vendored into it. +needs_from_toml = "ubproject.toml" -needs_fields = { - "image": { - "description": "Image associated with the need", - "schema": { - "type": "string" - }, - "nullable": True, - }, -} - -# Additional import required because the configuration references custom functions defined in this module +# Registers project Python that the configuration references by name. This is +# the last thing in the needs model that ubCode cannot see, because it cannot +# run project functions; it goes away with sple_tr_link. needs_functions = SplSphinx.default_needs_functions needs_global_options = SplSphinx.default_needs_global_options -# Always write the merged needs.json (all needs, after import/resolution) to the build output dir. -needs_build_json = True - # variant data ############################################################## # # ONE file, and nothing computed here. @@ -93,11 +85,26 @@ # the build. The rule is "everything a condition may name has to be IN the # file", and it only holds if this file adds nothing. # -# CMake exports VARIANT_DATA_FILE for the build shape it is building. The -# fallback is the "current" pointer that tools/variant_data.py maintains, which -# is also what ubproject.toml names -- so a bare `sphinx-build`, the IDE and the -# docs target all resolve byte-identical data. -needs_variant_data_file = os.environ.get("VARIANT_DATA_FILE", "build/autoconf.json") +# CMake exports VARIANT_DATA_FILE for the build shape it is building. Without +# it -- a bare `sphinx-build`, or the IDE -- the "current" pointer applies, +# which is also what ubproject.toml names, so the two readers see byte-identical +# data by default. +# +# It has to be applied after sphinx-needs has read ubproject.toml, which happens +# on `config-inited` and would otherwise put the pointer back. For the docs +# target that is the same file; for the reports target the pointer would quietly +# supply the docs data and every report fence would evaluate false. +_variant_data_file = os.environ.get("VARIANT_DATA_FILE") + + +def setup(app): + if not _variant_data_file: + return + + def _select_variant_data(app, config): + config.needs_variant_data_file = _variant_data_file + + app.connect("config-inited", _select_variant_data, priority=20) # build shape ############################################################### # diff --git a/test/test_ubproject_config.py b/test/test_ubproject_config.py new file mode 100644 index 00000000..2f6b13cc --- /dev/null +++ b/test/test_ubproject_config.py @@ -0,0 +1,143 @@ +"""Tests for ubproject.toml, the one configuration file both readers read. + +ubCode and ubc read this file directly; the Sphinx build reads it through +`needs_from_toml` in conf.py. sphinx-needs implements neither ubCode's `extend` +nor any other include mechanism -- it reads exactly one file's `[needs]` table -- +so the only way the two readers can agree is for this file to be complete on its +own. spl-core's base needs configuration is therefore vendored into it. + +Vendoring is a copy, and a copy rots. These tests are what stops it rotting +quietly: they fail when spl-core changes something this project copied, so the +choice to follow or to deviate is made deliberately, in a reviewable commit, +rather than discovered later as a link type that exists in one reader and not +the other. +""" + +import tomllib +from importlib.resources import files +from pathlib import Path + +import pytest + +PROJECT_ROOT = Path(__file__).resolve().parent.parent + +pytestmark = [ + pytest.mark.unittests, + pytest.mark.gate_develop_pr, + pytest.mark.gate_develop_push, + pytest.mark.gate_develop_nightly, + pytest.mark.gate_release_pr, + pytest.mark.gate_release, +] + + +@pytest.fixture(scope="module") +def project_config() -> dict: + with (PROJECT_ROOT / "ubproject.toml").open("rb") as handle: + return tomllib.load(handle) + + +@pytest.fixture(scope="module") +def spl_core_config() -> dict: + """The base configuration spl-core ships, as installed.""" + with files("spl_core.report_generation").joinpath("ubproject.toml").open("rb") as handle: + return tomllib.load(handle) + + +# --- the file has to stand on its own -------------------------------------- + + +def test_does_not_extend_anything(project_config: dict) -> None: + """No `extend`, because only one of the two readers implements it. + + The path it used to name also had the Python minor version baked into it + and pointed at an interpreter that does not exist, so the base + configuration silently failed to resolve for ubCode while conf.py resolved + it a second time through importlib. + """ + assert "extend" not in project_config + + +def test_sphinx_reads_this_exact_file() -> None: + conf = (PROJECT_ROOT / "conf.py").read_text() + assert 'needs_from_toml = "ubproject.toml"' in conf + + +# --- the vendored copy has to match what spl-core ships -------------------- + + +def test_every_spl_core_link_type_is_vendored(project_config: dict, spl_core_config: dict) -> None: + """Modernized from `extra_links` to `[needs.links]` while copying.""" + expected = { + link["option"]: {"incoming": link["incoming"], "outgoing": link["outgoing"]} + for link in spl_core_config["needs"]["extra_links"] + } + assert project_config["needs"]["links"] == expected + + +def test_every_spl_core_field_is_vendored(project_config: dict, spl_core_config: dict) -> None: + """Modernized from `extra_options` to `[needs.fields]` while copying. + + The project declares fields of its own on top, so this is a subset check. + `integrity` carries an explicit empty-string default because that is what + the deprecated `extra_options` form implies; defaulting to null instead + would change the value on every existing need. + """ + for option in spl_core_config["needs"]["extra_options"]: + assert option in project_config["needs"]["fields"], f"spl-core declares the field {option!r}, this file does not" + + assert project_config["needs"]["fields"]["integrity"]["default"] == "" + + +def test_every_spl_core_need_type_is_vendored(project_config: dict, spl_core_config: dict) -> None: + ours = {t["directive"]: t for t in project_config["needs"]["types"]} + for need_type in spl_core_config["needs"]["types"]: + directive = need_type["directive"] + assert directive in ours, f"spl-core declares the need type {directive!r}, this file does not" + assert ours[directive] == need_type, f"the {directive!r} need type differs from spl-core's" + + +def test_source_exclusions_are_vendored(project_config: dict, spl_core_config: dict) -> None: + assert project_config["source"]["exclude"] == spl_core_config["source"]["exclude"] + assert project_config["source"]["respect_gitignore"] == spl_core_config["source"]["respect_gitignore"] + + +# --- the deliberate deviations --------------------------------------------- + + +def test_build_output_is_not_indexed(project_config: dict) -> None: + """ubCode must not index every variant and kit ever built. + + spl-core's base config turns `respect_gitignore` off and then includes the + generated source listings, but excludes nothing else under the build + directory. The result is every need ID appearing once per variant built on + that machine, so the IDE cannot agree with any single build about what the + project contains. Sphinx never saw this because conf.py narrows its source + set per build shape -- which is exactly the kind of divergence between + readers this configuration exists to remove. + """ + assert project_config["source"]["extend_exclude"] == ["build/**"] + + +def test_generated_listings_are_included_through_the_stable_path(project_config: dict, spl_core_config: dict) -> None: + """A deliberate deviation from spl-core's `build/**/__source_docs/**`. + + `generated` is the current variant's build directory, maintained by + tools/variant_data.py. Going through it means one variant's listings are + indexed -- the configured one -- instead of all of them, and it survives the + `build/**` exclusion above. + """ + assert spl_core_config["source"]["extend_include"] == ["build/**/__source_docs/**"] + assert project_config["source"]["extend_include"] == ["generated/**/__source_docs/**"] + + +# --- what the variant machinery needs -------------------------------------- + + +def test_variant_data_file_is_the_current_pointer(project_config: dict) -> None: + """What ubCode reads, and what a bare sphinx-build falls back to.""" + assert project_config["needs"]["variant_data_file"] == "build/autoconf.json" + + +def test_needs_json_is_written(project_config: dict) -> None: + assert project_config["needs"]["build_json"] is True diff --git a/ubproject.toml b/ubproject.toml index bdb77cd2..004aa18e 100644 --- a/ubproject.toml +++ b/ubproject.toml @@ -1,6 +1,22 @@ "$schema" = "https://ubcode.useblocks.com/ubproject.schema.json" -extend = ".venv/lib/python3.12/site-packages/spl_core/report_generation/ubproject.toml" +# --------------------------------------------------------------------------- +# The one configuration file. Both readers of this project's documentation read +# it: ubCode and ubc directly, and the Sphinx build through `needs_from_toml` +# in conf.py. +# +# It used to `extend` spl-core's base configuration out of the virtualenv, by a +# path with the Python minor version baked into it. That was wrong twice over. +# The path named a Python that did not exist, so the base configuration silently +# failed to resolve for ubCode; and sphinx-needs does not implement `extend` at +# all -- it reads exactly one file's [needs] table -- so conf.py resolved the +# same base file a second time, through importlib. Two mechanisms for one file, +# one of them broken, and no way to tell from either end. +# +# spl-core's base needs configuration is therefore vendored below rather than +# referenced. test_ubproject_config.py fails if it drifts from what the +# installed spl-core ships, so vendoring cannot rot quietly. +# --------------------------------------------------------------------------- [server] index_on_save = true @@ -12,11 +28,108 @@ hover_need_refs = true flavour = "myst" include = ["*.md"] +[source] +exclude = [".bzr", ".direnv", ".eggs", ".git", ".git-rewrite", ".hg", ".svn", ".venv", ".vscode", "_build", "dist", "node_modules", "site-packages"] +respect_gitignore = false +# The generated source listings are documents; the rest of build/ is build +# output. Without the exclude, ubCode indexes every variant and every kit ever +# built, so need IDs appear many times over and the IDE disagrees with any one +# build about what the project contains. Sphinx never saw this because conf.py +# narrows its source set per build shape. +extend_include = ["generated/**/__source_docs/**"] +extend_exclude = ["build/**"] + [needs] variant_data_file = "build/autoconf.json" src_trace_config_from_toml = "ubproject.toml" +# Write the merged needs.json (all needs, after import and resolution). +build_json = true + +# --- vendored from spl-core ------------------------------------------------- +# Modernized to the `links`/`fields` tables while copying: the `extra_links` +# and `extra_options` lists spl-core still ships are deprecated in sphinx-needs +# and warned about on every build, and the `image` field below already used the +# new form, so the file was mixing both. + +[needs.links.realizes] +incoming = "is realized by" +outgoing = "realizes" + +[needs.links.fulfills] +incoming = "is fulfilled by" +outgoing = "fulfills" + +[needs.links.refines] +incoming = "is refined by" +outgoing = "refines" + +[needs.links.implements] +incoming = "is implemented by" +outgoing = "implements" + +[needs.links.results] +incoming = "results from" +outgoing = "results" + +[needs.links.verifies] +incoming = "is verified by" +outgoing = "verifies" + +[needs.links.tests] +incoming = "is tested by" +outgoing = "tests" + +[needs.fields.integrity] +# spl-core declares this through the deprecated `extra_options` list. That form +# implies an empty-string default, which is what every existing need carries, so +# it is stated explicitly here -- the modern form warns when a field declares +# neither schema, nullable nor default, and defaulting to null instead would +# change the value of `integrity` on all 87 needs. +description = "Integrity level of the need" +default = "" + +[needs.fields.integrity.schema] +type = "string" + +[[needs.types]] +directive = "arch" +title = "Architecture" +prefix = "A_" +color = "#DF744A" +style = "node" + +[[needs.types]] +directive = "req" +title = "Requirement" +prefix = "R_" +color = "#BFD8D2" +style = "node" + +[[needs.types]] +directive = "spec" +title = "Specification" +prefix = "S_" +color = "#FEDCD2" +style = "node" + +[[needs.types]] +directive = "impl" +title = "Implementation" +prefix = "I_" +color = "#DF744A" +style = "node" + +[[needs.types]] +directive = "test" +title = "Test Case" +prefix = "T_" +color = "#DCB239" +style = "node" + +# --- end vendored ----------------------------------------------------------- + [needs.fields.image] description = "Image associated with the need" nullable = true From abc87298b18bac825812b24defda5972763748d7 Mon Sep 17 00:00:00 2001 From: Lucas Silber Date: Wed, 16 Sep 2026 15:53:22 +0200 Subject: [PATCH 07/19] refactor: gate documents on component membership, not on feature or variant Phase 5 of doc/variant-handling-without-jinja-plan.md. The seven per-component [[source.mounts]] become [[source.variant_sources]] rules. Mounts exist for trees that are not in the project; these documents are. What changes beyond the tidying is the condition. They were gated on the KConfig feature that owns the component, except the integration suite, which was gated on `var.build_config.variant == "Disco"` because parts.cmake adds it for the test kit only and a feature could not express that. Both re-encode what parts.cmake already states. The variant-name gate was that drift made concrete: it claimed the suite for Disco's prod kit too, where parts.cmake does not add it. Every rule now asks the one question that matches the build -- is this component in the variant's component list -- and a test refuses any rule that names a variant directly. Rules are subtractive: a FALSE rule removes the files it names, a TRUE one does nothing. So two rules over the same file compose as AND, which is what gates the generated report pages on the build shape AND the component. A `docs` build with a populated build directory no longer reads report pages it will not show. doc/components/index.md becomes a static 150% toctree instead of an empty one that sphinx-mounts filled in. An entry naming an excluded document is reported as INFO by both tools by design, so the page can simply list everything. The /build/** glob toctrees are gone. They matched every variant and build type on disk and only ever resolved to one page because conf.py narrowed the source set behind the scenes -- an invisible gate holding up a visible one. Documents now name `generated/`, the configured variant's build directory, and conf.py's filtering is deleted with them. Verified: the page set is byte-identical to the mount-based build for Disco and Sleep. Against one `generated` tree holding every component's reports, the reports target builds exactly the variant's own and the docs target builds none. A CMake configure produces the pointer, the symlink, the marker and both SPL_VARIANT_DATA_FILE_* cache entries. AGENTS.md now states the rules as rules rather than describing the mechanism. Co-Authored-By: Claude Opus 5 (1M context) --- AGENTS.md | 146 ++++++++++----- components/auto_off/doc/index.md | 7 +- components/brightness_controller/doc/index.md | 7 +- components/light_controller/doc/index.md | 7 +- components/main_control_knob/doc/index.md | 7 +- components/power_button/doc/index.md | 7 +- .../power_signal_processing/doc/index.md | 7 +- conf.py | 58 +++--- doc/components/index.md | 35 +++- index.md | 12 +- test/spled_integration/doc/index.md | 7 +- test/test_ubproject_config.py | 117 ++++++++++++ ubproject.toml | 174 +++++++++++------- 13 files changed, 418 insertions(+), 173 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 53874a13..12e9a247 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -164,47 +164,111 @@ Check feature values in source code via generated `autoconf.h` header. ## Variant-Dependent Documentation -Documents must never use Jinja to select variant-dependent content. Two -mechanisms cover every case, and both read the same data: the feature values of -the variant being built, exposed as `var.features.*`, plus `var.build_config.*` -for the shape of the build (`variant` name, `target` of `docs` or `reports`, -and `scope` of `variant` or `component`). - -**Whole documents: `[[source.mounts]]` in `ubproject.toml`**, read by -sphinx-mounts. Each component's design document is mounted at its own real path -under an `if` condition, so it enters the build only when its feature is -enabled. `attach_to` appends it to the empty toctree in -`doc/components/index.md`, which is why that page needs no loop. Adding a -component to the report means adding one mount block, not editing a document. - -A mount condition may only name data that the file in `[needs] variant_data_file` -actually declares. That file is the mirror `CMakeLists.txt` writes for the -configured variant, holding the KConfig features plus `build_config.variant`. -Naming anything else — a key that only `conf.py` injects at build time — makes -the condition unevaluable for ubCode and every other reader of this file, and -their view of the project then silently disagrees with the build. Build-shape -distinctions belong in `conf.py` instead: it switches TOML reading off entirely -for a per-component report rather than encoding the scope in a condition. - -**Blocks inside a document: the `{if}` directive of Sphinx-Needs.** Wrap the -content in a four-backtick fence and give the condition as the argument, for -example ```` ````{if} var.features.BLINKING ````. Content behind a false -condition is never parsed, so its needs never enter the traceability data. - -Two differences between the engines are worth knowing. The `{if}` directive is -a real Python expression, so a bare `var.features.BLINKING` is enough, while a -mount condition uses a restricted grammar that needs `== True`. And a mount -condition that cannot be evaluated excludes what it gates, so a typo silently -shrinks the document set rather than failing loudly. - -`conf.py` defaults every boolean in the feature model to `False` before -overlaying the variant's own values, because KConfig omits a promptless boolean -from `autoconf.json` when it evaluates to n. Without that, a condition naming -such a feature would fail to evaluate for exactly the variants where it is off. - -The Jinja `source-read` hook in `conf.py` still runs, but only for the source -listings that spl-core generates under `__source_docs`, which wrap their code -blocks in `{% raw %}`. Do not reintroduce Jinja into a hand-written document. +Documents never use Jinja. There is no `source-read` hook any more, and +reintroducing one is a regression, not a shortcut. + +Everything variant-dependent is decided from **one file**: the variant data +that `tools/variant_data.py` writes, exposed as `var.*`. The governing rule: + +> **Everything a condition may name has to be IN the variant data file.** + +A key that only `conf.py` knows is invisible to ubCode, `ubc` and a reviewer's +editor, so their view of the project silently disagrees with the build — +silently, because a condition a tool cannot evaluate gates content **off** +rather than failing. That is why `conf.py` reads the file and adds nothing to +it. + +### The three mechanisms + +**Whole documents: `[[source.variant_sources]]` in `ubproject.toml`.** One rule +per component, gated on membership of the variant's component list: + +```toml +[[source.variant_sources]] +if = "'components/auto_off' in var.build_config.components" +files = [ + "components/auto_off/doc/**", + "generated/components/auto_off/reports/**", + "generated/components/auto_off/__source_docs/**", +] +``` + +Membership, never identity. The component list comes from that variant's +`parts.cmake`, so the product structure is stated once, in the file that already +states it. **Never gate on the variant name** — that is a second encoding of the +same fact, free to drift. + +Rules are *subtractive*: a FALSE rule removes the files it names, a TRUE rule +does nothing. Two rules naming the same file therefore compose as AND, which is +how generated output is gated on both the build shape and the component. + +**Blocks inside a document: the `{if}` directive of Sphinx-Needs.** Four-backtick +fence, condition as the argument: + +````text +````{if} var.features.BLINKING +... +```` +```` + +Content behind a false condition is never parsed, so its needs never enter the +traceability data. + +**External trees: `[[source.mounts]]`.** Currently unused. Everything this +project shows lives in the tree or under `generated/`. + +### What the data holds + +| Key | | +| --- | --- | +| `var.features.*` | every KConfig symbol, with **every** declared boolean present — including the promptless ones KConfig omits when they are off | +| `var.build_config.variant` | e.g. `Disco`, `Base/Dev` | +| `var.build_config.kit` | `prod` or `test` | +| `var.build_config.target` | `docs` or `reports` | +| `var.build_config.components` | the variant's component list, from `parts.cmake` | + +### Two differences between the engines + +The `{if}` directive takes a real Python expression, so a bare +`var.features.BLINKING` is enough. A `variant_sources` condition uses a +restricted grammar that needs `== True`. And a condition that cannot be +evaluated **excludes** what it gates, so a typo silently shrinks the document +set rather than failing loudly — which is what `test_ubproject_config.py` is +for. + +### Adding a component + +1. Add it to the variant's `parts.cmake`. +2. Add one `[[source.variant_sources]]` rule in `ubproject.toml`. +3. Add one line to the 150% toctree in `doc/components/index.md`. + +Nothing is generated, no loop is edited, and nothing under `build/` is touched. + +### Generated output + +`build/` and `generated/` are output. **Nobody edits them — not a person, not an +assistant.** The editor is configured to refuse it (`files.readonlyInclude`) and +`build/variants/GENERATED` says so on disk. + +`generated/` is the configured variant's build directory. Documents name it +directly instead of globbing `/build/**`; a glob matched every variant and build +type on disk and only ever resolved to one page because `conf.py` narrowed the +source set behind the scenes. + +Regenerate without a compiler — KConfig is pure Python, and CMake's top-level +`project()` call demands a C toolchain before it will configure at all: + +```bash +python tools/variant_data.py --all # the whole matrix +python tools/variant_data.py --variant Sleep --kit test # ...and point at one cell +python tools/variant_data.py --all --check # CI: regenerate and diff +``` + +Preview a variant by pointing the build at its cell: + +```bash +VARIANT_DATA_FILE=build/variants/Sleep/test/docs.json sphinx-build -b html . out +``` ## Project-Specific Conventions diff --git a/components/auto_off/doc/index.md b/components/auto_off/doc/index.md index c37df2df..f8cb62b0 100644 --- a/components/auto_off/doc/index.md +++ b/components/auto_off/doc/index.md @@ -128,11 +128,10 @@ Key press detection and timer reset occur within one execution cycle (CONFIG_OS_ ```{toctree} :maxdepth: 1 -:glob: -/build/**/components/auto_off/reports/unit_test_spec -/build/**/components/auto_off/reports/unit_test_results -/build/**/components/auto_off/reports/coverage +/generated/components/auto_off/reports/unit_test_spec +/generated/components/auto_off/reports/unit_test_results +/generated/components/auto_off/reports/coverage ``` ```` diff --git a/components/brightness_controller/doc/index.md b/components/brightness_controller/doc/index.md index f6c099a6..f2b94083 100644 --- a/components/brightness_controller/doc/index.md +++ b/components/brightness_controller/doc/index.md @@ -75,11 +75,10 @@ The Brightness Controller is use the RTE interface `RteSetBrightnessAdjustmentCo ```{toctree} :maxdepth: 1 -:glob: -/build/**/components/brightness_controller/reports/unit_test_spec -/build/**/components/brightness_controller/reports/unit_test_results -/build/**/components/brightness_controller/reports/coverage +/generated/components/brightness_controller/reports/unit_test_spec +/generated/components/brightness_controller/reports/unit_test_results +/generated/components/brightness_controller/reports/coverage ``` ```` diff --git a/components/light_controller/doc/index.md b/components/light_controller/doc/index.md index 6fa4ed27..6d47189d 100644 --- a/components/light_controller/doc/index.md +++ b/components/light_controller/doc/index.md @@ -118,11 +118,10 @@ stateDiagram-v2 ```{toctree} :maxdepth: 1 -:glob: -/build/**/components/light_controller/reports/unit_test_spec -/build/**/components/light_controller/reports/unit_test_results -/build/**/components/light_controller/reports/coverage +/generated/components/light_controller/reports/unit_test_spec +/generated/components/light_controller/reports/unit_test_results +/generated/components/light_controller/reports/coverage ``` ```` diff --git a/components/main_control_knob/doc/index.md b/components/main_control_knob/doc/index.md index 8791758f..58b399f7 100644 --- a/components/main_control_knob/doc/index.md +++ b/components/main_control_knob/doc/index.md @@ -58,11 +58,10 @@ The main control knob component uses the RTE interface `RteSetMainKnobValue()` t ```{toctree} :maxdepth: 1 -:glob: -/build/**/components/main_control_knob/reports/unit_test_spec -/build/**/components/main_control_knob/reports/unit_test_results -/build/**/components/main_control_knob/reports/coverage +/generated/components/main_control_knob/reports/unit_test_spec +/generated/components/main_control_knob/reports/unit_test_results +/generated/components/main_control_knob/reports/coverage ``` ```` diff --git a/components/power_button/doc/index.md b/components/power_button/doc/index.md index f28bde6c..bb4fb559 100644 --- a/components/power_button/doc/index.md +++ b/components/power_button/doc/index.md @@ -76,11 +76,10 @@ stateDiagram-v2 ```{toctree} :maxdepth: 1 -:glob: -/build/**/components/power_button/reports/unit_test_spec -/build/**/components/power_button/reports/unit_test_results -/build/**/components/power_button/reports/coverage +/generated/components/power_button/reports/unit_test_spec +/generated/components/power_button/reports/unit_test_results +/generated/components/power_button/reports/coverage ``` ```` diff --git a/components/power_signal_processing/doc/index.md b/components/power_signal_processing/doc/index.md index 875f2e0b..4908ca88 100644 --- a/components/power_signal_processing/doc/index.md +++ b/components/power_signal_processing/doc/index.md @@ -115,11 +115,10 @@ graph TD ```{toctree} :maxdepth: 1 -:glob: -/build/**/components/power_signal_processing/reports/unit_test_spec -/build/**/components/power_signal_processing/reports/unit_test_results -/build/**/components/power_signal_processing/reports/coverage +/generated/components/power_signal_processing/reports/unit_test_spec +/generated/components/power_signal_processing/reports/unit_test_results +/generated/components/power_signal_processing/reports/coverage ``` ```` diff --git a/conf.py b/conf.py index 71c25f49..44bc5cdb 100644 --- a/conf.py +++ b/conf.py @@ -29,7 +29,19 @@ "**/test_results.rst", # We renamed this file, but nobody deletes it. ] -include_patterns = ["index.md", "doc/**"] +# The 150% source set: every hand-written document the product line has. +# +# Which of them the build actually contains is decided by the +# [[source.variant_sources]] rules in ubproject.toml, against the variant data. +# Narrowing the set here as well would put a second, invisible gate in front of +# the declared one -- and an invisible gate is the thing this whole design +# exists to remove. +include_patterns = [ + "index.md", + "doc/**", + "components/**/doc/**", + "test/**/doc/**", +] # configuration of built-in stuff ########################################### # @see https://www.sphinx-doc.org/en/master/usage/configuration.html @@ -113,38 +125,32 @@ def _select_variant_data(app, config): # decide these, and none of it may leak into `var.*`. _build_config = SplSphinx.get_default_html_context()["build_config"] -_extra_include_patterns = _build_config.get("include_patterns", []) # Two shapes of build use this configuration: the variant-wide one, and the -# per-component report that spl-core builds for a single component. +# per-component report that spl-core builds for a single component. This is the +# only build-shape decision left here, and it is a Sphinx one -- which document +# is the root -- not variant data. No other reader has to decide it. if _build_config.get("component_info"): root_doc = "doc/component_report" exclude_patterns.append("index.md") # A per-component report builds one component and stitches its own table of - # contents, so none of the mounts apply. Switching TOML reading off here is - # what lets every `if` in ubproject.toml speak only about the variant, which - # is the only way a reader that never runs Sphinx can decide them too. + # contents, so the project-wide variant rules do not apply to it. sources_from_toml = None + include_patterns.extend(_build_config.get("include_patterns", [])) else: root_doc = "index" exclude_patterns.append("doc/component_report.md") - # Component design documents are mounted by sphinx-mounts, see - # [[source.mounts]] in ubproject.toml. Reading them as ordinary sources as - # well would parse every need in them a second time under a second docname. - # Only the hand-written trees are dropped; the generated ones under build/ - # stay, because they are not mounted. - _extra_include_patterns = [ - pattern - for pattern in _extra_include_patterns - if not (pattern.endswith("/doc/**") and not pattern.startswith("build/")) - # The generated report pages are only ever shown by the reports target. - # The docs target used to read them anyway and then leave them out of - # every toctree, which is 15 orphan warnings for pages nobody sees. - and not ( - _build_config.get("target") != "reports" - and pattern.startswith("build/") - and pattern.endswith("/reports/**") - ) - ] - -include_patterns.extend(_extra_include_patterns) + # The generated report pages of the CONFIGURED variant, reached through the + # stable `generated` path rather than by globbing every build directory on + # disk. spl-core's own include patterns are deliberately not used here: they + # name the real build/// paths, so the same file would + # enter the build under a second docname and every need in it would be + # parsed twice. + include_patterns.extend( + [ + "generated/reports/**", + "generated/components/**/reports/**", + "generated/test/**/reports/**", + "generated/components/**/__source_docs/**", + ] + ) diff --git a/doc/components/index.md b/doc/components/index.md index 1de8af4d..9ca703b1 100644 --- a/doc/components/index.md +++ b/doc/components/index.md @@ -1,17 +1,34 @@ # Components -Only the components that the built variant actually contains appear below. -Each component's design document is mounted by sphinx-mounts under the KConfig -feature that owns it, so this page needs no templating: the declarations live -in `[[source.mounts]]` in `ubproject.toml`, and sphinx-mounts appends each -mounted entry to the empty toctree below. +This page lists every component the product line has. It is a 150% view: the +entries are always all of them, and the ones the built variant does not contain +are removed from the build by the `[[source.variant_sources]]` rules in +`ubproject.toml`, each gated on membership of that variant's component list. -Each entry is a group. A component's own document is the entry point of its -bundle and carries the toctree for its verification pages, so unit test results -and coverage appear underneath the component they belong to rather than in one -flat list. +So this page needs no templating and no generated content. Adding a component +to the report means adding it to a variant's `parts.cmake` and adding one rule +plus one line here — never editing a loop, and never editing anything under +`build/`. + +An entry naming a document the current variant excludes is reported as INFO by +both Sphinx and ubCode, by design: the 150% tree is the source of truth, and a +variant showing less of it is the normal case rather than an error. + +Each entry is a group. A component's own document is the entry point and +carries the toctree for its verification pages, so unit test results and +coverage appear underneath the component they belong to rather than in one flat +list. ```{toctree} :maxdepth: 2 +/components/light_controller/doc/index +/components/main_control_knob/doc/index +/components/power_button/doc/index +/components/power_signal_processing/doc/index +/components/brightness_controller/doc/index +/components/auto_off/doc/index +/components/examples/hello_gmock/doc/index +/components/examples/flight_controller/doc/index +/test/spled_integration/doc/index ``` diff --git a/index.md b/index.md index 2003e92a..e94af671 100644 --- a/index.md +++ b/index.md @@ -14,17 +14,17 @@ doc/components/index ````{if} var.build_config.target == "reports" -The glob below matches the variant-wide coverage page for any variant name and -any build type, and does not match a component's own coverage page, because the -directory in front of `reports` is the build type there and the component name -here. +`generated` is the build directory of the configured variant, maintained by +`tools/variant_data.py`. Naming it directly is what lets this be one entry +rather than a glob over every variant and build type that happens to be on +disk -- a glob that only ever resolved to one page because `conf.py` narrowed +the source set behind the scenes. ```{toctree} :caption: Code Coverage :maxdepth: 1 -:glob: -/build/**/[A-Z]*/reports/coverage +/generated/reports/coverage ``` ```` diff --git a/test/spled_integration/doc/index.md b/test/spled_integration/doc/index.md index 72fe22b6..3d7394c0 100644 --- a/test/spled_integration/doc/index.md +++ b/test/spled_integration/doc/index.md @@ -6,11 +6,10 @@ ```{toctree} :maxdepth: 1 -:glob: -/build/**/test/spled_integration/reports/unit_test_spec -/build/**/test/spled_integration/reports/unit_test_results -/build/**/test/spled_integration/reports/coverage +/generated/test/spled_integration/reports/unit_test_spec +/generated/test/spled_integration/reports/unit_test_results +/generated/test/spled_integration/reports/coverage ``` ```` diff --git a/test/test_ubproject_config.py b/test/test_ubproject_config.py index 2f6b13cc..cfdb79f6 100644 --- a/test/test_ubproject_config.py +++ b/test/test_ubproject_config.py @@ -141,3 +141,120 @@ def test_variant_data_file_is_the_current_pointer(project_config: dict) -> None: def test_needs_json_is_written(project_config: dict) -> None: assert project_config["needs"]["build_json"] is True + + +# --- the variant rules ----------------------------------------------------- +# +# These evaluate the real conditions with sphinx-mounts' own interpreter, +# against the real generated variant data. That is the closest a test can get +# to "ubCode and the build decide the same thing", because it is literally the +# same grammar and the same file. + + +@pytest.fixture(scope="module") +def rules(project_config: dict) -> list[dict]: + return project_config["source"]["variant_sources"] + + +def _variant_data(variant: str, kit: str, target: str) -> dict: + import json + + with (PROJECT_ROOT / "build" / "variants" / variant / kit / f"{target}.json").open() as handle: + return json.load(handle) + + +def test_every_component_document_is_gated_by_a_rule(rules: list[dict]) -> None: + """A hand-written component document with no rule is 150% in every variant. + + It would then appear in variants that do not contain the component, and + every need in it would enter their traceability data. The failure is + additive and silent, which is why it needs a test rather than a review. + """ + gated = {pattern for rule in rules for pattern in rule["files"]} + for doc_dir in sorted(PROJECT_ROOT.glob("components/**/doc")) + sorted(PROJECT_ROOT.glob("test/*/doc")): + rel = doc_dir.relative_to(PROJECT_ROOT).as_posix() + assert f"{rel}/**" in gated, f"{rel} is not gated by any [[source.variant_sources]] rule" + + +def test_no_rule_names_a_variant_by_name(rules: list[dict]) -> None: + """Membership, never identity. + + Gating on the variant name re-encodes what parts.cmake already says, and + the two are then free to drift. The integration suite is the case in point: + it used to be gated on `variant == "Disco"`, which also claimed it for + Disco's prod kit, where parts.cmake does not add it. + """ + known_variants = {"Disco", "Sleep", "Spa", "Base/Dev", "IDEA/Sloemada"} + for rule in rules: + for variant in known_variants: + assert f'"{variant}"' not in rule["if"], f"rule {rule['if']!r} names a variant directly" + assert f"'{variant}'" not in rule["if"], f"rule {rule['if']!r} names a variant directly" + + +def test_every_rule_condition_is_inside_the_grammar(rules: list[dict]) -> None: + """A condition outside the grammar is refused rather than evaluated.""" + from sphinx_mounts import variants + + for rule in rules: + variants.validate(rule["if"]) + + +@pytest.mark.parametrize( + ("variant", "kit", "expect_present", "expect_absent"), + [ + # Disco: BLINKING, so no brightness; no auto-off; integration suite in + # the test kit only. + ("Disco", "test", ["components/light_controller/doc/**", "test/spled_integration/doc/**"], ["components/auto_off/doc/**", "components/brightness_controller/doc/**"]), + ("Disco", "prod", ["components/light_controller/doc/**"], ["test/spled_integration/doc/**", "components/auto_off/doc/**"]), + # Sleep: manual brightness and auto-off, no integration suite. + ("Sleep", "test", ["components/auto_off/doc/**", "components/brightness_controller/doc/**"], ["test/spled_integration/doc/**"]), + # Spa: brightness but no auto-off. + ("Spa", "test", ["components/brightness_controller/doc/**"], ["components/auto_off/doc/**"]), + # Base/Dev: the example components, none of the product ones. + ("Base/Dev", "test", ["components/examples/hello_gmock/doc/**"], ["components/light_controller/doc/**", "components/auto_off/doc/**"]), + ], +) +def test_rules_select_the_expected_documents(rules: list[dict], variant: str, kit: str, expect_present: list[str], expect_absent: list[str]) -> None: + from sphinx_mounts import variants + + data = _variant_data(variant, kit, "docs") + included: set[str] = set() + excluded: set[str] = set() + for rule in rules: + ok = variants.interpret(variants.validate(rule["if"]), data) + (included if ok else excluded).update(rule["files"]) + + for pattern in expect_present: + assert pattern in included, f"{variant}/{kit}: expected {pattern} to be included" + assert pattern not in excluded, f"{variant}/{kit}: {pattern} is both included and excluded" + for pattern in expect_absent: + assert pattern in excluded, f"{variant}/{kit}: expected {pattern} to be excluded" + + +def test_generated_output_is_gated_on_the_reports_target(rules: list[dict]) -> None: + """A docs build must not read report pages it will not show. + + Rules are subtractive -- a FALSE rule removes the files it names, a TRUE + one does nothing -- so this rule and the per-component ones compose as AND. + """ + from sphinx_mounts import variants + + shape_rule = next(r for r in rules if "target" in r["if"]) + assert shape_rule["files"] == ["generated/**"] + + tree = variants.validate(shape_rule["if"]) + assert variants.interpret(tree, _variant_data("Disco", "test", "reports")) is True + assert variants.interpret(tree, _variant_data("Disco", "test", "docs")) is False + + +def test_no_document_globs_the_build_directory() -> None: + """Toctrees name `generated/`, the configured variant's build directory. + + A `/build/**` glob matched every variant and build type on disk and only + ever resolved to one page because conf.py narrowed the source set behind + the scenes -- an invisible gate holding up a visible one. + """ + for pattern in ("components/*/doc/index.md", "components/*/*/doc/index.md", "test/*/doc/index.md"): + for path in PROJECT_ROOT.glob(pattern): + assert "/build/**" not in path.read_text(), f"{path.relative_to(PROJECT_ROOT)} still globs the build directory" + assert "/build/**" not in (PROJECT_ROOT / "index.md").read_text() diff --git a/ubproject.toml b/ubproject.toml index 004aa18e..85796918 100644 --- a/ubproject.toml +++ b/ubproject.toml @@ -183,71 +183,119 @@ type = "list[str]" default = [] # --------------------------------------------------------------------------- -# Variant-dependent documentation, read by sphinx-mounts. +# Variant-dependent documentation. # -# Each component's design documentation is mounted at its own real path, so the -# docname is exactly what it would be if the file were an ordinary source. What -# the mount adds is the `if` condition: the bundle is in the build only when the -# KConfig feature that owns it is enabled for the variant being built. The -# feature values come from the variant's autoconf.json, see conf.py. +# Two mechanisms, one data source. Both read the file named by +# `[needs] variant_data_file` above, which tools/variant_data.py writes, so +# ubCode, ubc and the Sphinx build decide every condition identically. # -# Every condition names only what the variant data file declares, so ubCode and any -# other reader of this file decide exactly what the Sphinx build decides. Keeping the -# per-component reports out of this is conf.py's job: it switches TOML reading off for -# those builds rather than encoding the build shape in a condition nothing else can see. +# `[[source.variant_sources]]` gates whole documents that live IN this tree. +# `[[source.mounts]]` brings in trees that do not -- here, the generated report +# output. Blocks inside a document are gated by the {if} directive of +# Sphinx-Needs, which reads the same `var.*`. # -# This is the file-level half of variant handling. The block-level half is the -# {if} directive of Sphinx-Needs, used inside the component documents. -# -# `path_check = "off"` because these bundles are not self-contained by design: -# a component's design document legitimately reaches its own `src/` headers and -# the compilation database through sphinx-codelinks. +# A condition may only name what the variant data file declares. Naming +# anything else makes it unevaluable, and an unevaluable condition EXCLUDES +# what it gates -- so a typo silently shrinks the document set rather than +# failing loudly. # --------------------------------------------------------------------------- -[[source.mounts]] -dir = "components/light_controller/doc" -mount_at = "components/light_controller/doc" -attach_to = "doc/components/index" -path_check = "off" - -[[source.mounts]] -dir = "components/main_control_knob/doc" -mount_at = "components/main_control_knob/doc" -attach_to = "doc/components/index" -path_check = "off" - -[[source.mounts]] -dir = "components/power_button/doc" -mount_at = "components/power_button/doc" -attach_to = "doc/components/index" -path_check = "off" - -[[source.mounts]] -dir = "components/power_signal_processing/doc" -mount_at = "components/power_signal_processing/doc" -attach_to = "doc/components/index" -path_check = "off" - -[[source.mounts]] -if = "var.features.BRIGHTNESS_ADJUSTMENT_ENABLED == True" -dir = "components/brightness_controller/doc" -mount_at = "components/brightness_controller/doc" -attach_to = "doc/components/index" -path_check = "off" - -[[source.mounts]] -if = "var.features.AUTO_OFF == True" -dir = "components/auto_off/doc" -mount_at = "components/auto_off/doc" -attach_to = "doc/components/index" -path_check = "off" - -# The integration test suite is not owned by a feature. It is added by Disco's -# parts.cmake for the test build kit only, so its document follows the variant -# rather than a KConfig symbol. -[[source.mounts]] -if = 'var.build_config.variant == "Disco"' -dir = "test/spled_integration/doc" -mount_at = "test/spled_integration/doc" -attach_to = "doc/components/index" -path_check = "off" +# Rules are subtractive: a rule whose condition is FALSE removes the files it +# names, and a TRUE rule does nothing. Two rules naming the same file therefore +# compose as AND, which is what gates the generated report pages on both the +# build shape and the component being part of the variant. + +# Generated output belongs to the `reports` build shape only. +# +# `generated` is the configured variant's build directory, maintained by +# tools/variant_data.py. Without this rule a `docs` build with a populated +# build directory would read every report page and then leave it out of every +# toctree -- pages nobody asked for, one orphan warning each. +[[source.variant_sources]] +if = 'var.build_config.target == "reports"' +files = ["generated/**"] + +# Component documentation, hand-written and generated alike. +# +# Gated on membership of the variant's component list, which the generator +# reads out of that variant's parts.cmake. That is the point: the product +# structure is stated once, in the file that already states it. Gating on the +# owning KConfig feature instead would be a second encoding of the same fact, +# free to drift -- and gating the integration suite on `variant == "Disco"`, as +# this did before, was exactly that drift made concrete: it claimed the suite +# for Disco's prod kit too, where parts.cmake does not add it. +# +# doc/components/index.md carries the full 150% toctree. An entry naming a +# document a rule excluded is reported as INFO by both tools, by design. + +[[source.variant_sources]] +if = "'components/light_controller' in var.build_config.components" +files = [ + "components/light_controller/doc/**", + "generated/components/light_controller/reports/**", + "generated/components/light_controller/__source_docs/**", +] + +[[source.variant_sources]] +if = "'components/main_control_knob' in var.build_config.components" +files = [ + "components/main_control_knob/doc/**", + "generated/components/main_control_knob/reports/**", + "generated/components/main_control_knob/__source_docs/**", +] + +[[source.variant_sources]] +if = "'components/power_button' in var.build_config.components" +files = [ + "components/power_button/doc/**", + "generated/components/power_button/reports/**", + "generated/components/power_button/__source_docs/**", +] + +[[source.variant_sources]] +if = "'components/power_signal_processing' in var.build_config.components" +files = [ + "components/power_signal_processing/doc/**", + "generated/components/power_signal_processing/reports/**", + "generated/components/power_signal_processing/__source_docs/**", +] + +[[source.variant_sources]] +if = "'components/brightness_controller' in var.build_config.components" +files = [ + "components/brightness_controller/doc/**", + "generated/components/brightness_controller/reports/**", + "generated/components/brightness_controller/__source_docs/**", +] + +[[source.variant_sources]] +if = "'components/auto_off' in var.build_config.components" +files = [ + "components/auto_off/doc/**", + "generated/components/auto_off/reports/**", + "generated/components/auto_off/__source_docs/**", +] + +[[source.variant_sources]] +if = "'test/spled_integration' in var.build_config.components" +files = [ + "test/spled_integration/doc/**", + "generated/test/spled_integration/reports/**", + "generated/test/spled_integration/__source_docs/**", +] + +[[source.variant_sources]] +if = "'components/examples/hello_gmock' in var.build_config.components" +files = [ + "components/examples/hello_gmock/doc/**", + "generated/components/examples/hello_gmock/reports/**", + "generated/components/examples/hello_gmock/__source_docs/**", +] + +[[source.variant_sources]] +if = "'components/examples/flight_controller' in var.build_config.components" +files = [ + "components/examples/flight_controller/doc/**", + "generated/components/examples/flight_controller/reports/**", + "generated/components/examples/flight_controller/__source_docs/**", +] From 8225e20a720dd3a47c49f1f9976d82ed9689e5f2 Mon Sep 17 00:00:00 2001 From: Lucas Silber Date: Wed, 16 Sep 2026 15:58:24 +0200 Subject: [PATCH 08/19] ci: gate the documentation without a compiler Phase 7 of doc/variant-handling-without-jinja-plan.md. The documentation is derivable from the sources alone -- KConfig is pure Python and the documents are text -- and only CMake's top-level project() call needs a C toolchain. So the gate for it should not need one either, and now does not. test_documentation.py generates the whole variant matrix and builds each variant's documents, then asserts the property the entire design exists to provide: a component's document is in the build exactly when that variant's parts.cmake adds the component. Not when a feature suggests it, and never because the variant is called something. It also asserts what must not come back -- no Jinja construct in any document, no source-read handler in conf.py -- because with the hook gone an accidental {{ ... }} is shipped as literal text rather than failing the build. It is a pytest module rather than bespoke CI script so that CI stays the thin wrapper it is: the new `documentation` job installs a Python and the locked dependencies and runs `pytest -m "docs and "`, the same command a developer runs. 14 tests, 14 seconds, all five variants -- where the three build jobs only cover the variants they build, and take tens of minutes to say so. VS Code gets tasks for the working mode this enables: select a variant for the docs and ubCode (KConfig only, no CMake configure, no compiler), generate the whole matrix, build one variant's documents, and run the gate. Note on ubc: the plan also proposed `ubc check` per variant in this job. Left out deliberately -- the CLI is not installed in this environment, so an unverified step would be a guess about how it installs in CI. The Sphinx build covers the same document sets meanwhile, and the conditions are asserted with sphinx-mounts' own interpreter in test_ubproject_config.py, which is the same grammar ubCode evaluates. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/ci.yml | 33 ++++++++ .vscode/tasks.json | 57 +++++++++++++ AGENTS.md | 3 +- pytest.ini | 1 + test/test_documentation.py | 163 +++++++++++++++++++++++++++++++++++++ 5 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 test/test_documentation.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index da48aa23..bf0b6466 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,6 +41,39 @@ jobs: echo "Selected gate marker: $marker" echo "marker=$marker" >> "$GITHUB_OUTPUT" + documentation: + name: Documentation (no compiler) + runs-on: ubuntu-24.04 + timeout-minutes: 15 + needs: determine-gate + + # The documentation is derivable from the sources alone: KConfig is pure + # Python and the documents are text. Only CMake's top-level project() + # call needs a C toolchain, and nothing here goes through it. So this + # gate installs a Python and the locked dependencies, and nothing else -- + # no poks, no scoop, no cross-compiler. It is the fastest signal in the + # workflow and it covers every variant, where the build jobs cover the + # variants they build. + steps: + - name: Checkout Code + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Install documentation dependencies + run: | + pipx install poetry==2.4.1 + poetry config virtualenvs.in-project true + poetry install --no-root + + - name: Documentation gate + run: poetry run pytest -m "docs and ${{ needs.determine-gate.outputs.marker }}" + test-on-windows: name: Build and Test on Windows runs-on: windows-2025 diff --git a/.vscode/tasks.json b/.vscode/tasks.json index ccf8cedc..1f2756c0 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -27,6 +27,45 @@ }, "problemMatcher": [] }, + { + "label": "Select documentation variant", + "detail": "Point the docs and ubCode at one variant -- no CMake, no compiler", + "type": "shell", + "command": "${command:python.interpreterPath} tools/variant_data.py --variant ${input:variant} --kit ${input:buildKit} --target ${input:docsTarget} --current", + "problemMatcher": [], + "presentation": { + "reveal": "silent", + "panel": "shared" + } + }, + { + "label": "Generate all variant data", + "detail": "Write build/variants///.json for every variant", + "type": "shell", + "command": "${command:python.interpreterPath} tools/variant_data.py --all", + "problemMatcher": [] + }, + { + "label": "Build documentation for a variant", + "detail": "Sphinx docs for one variant, without configuring or building the software", + "type": "shell", + "command": "${command:python.interpreterPath} -m sphinx -b html . build/docs/${input:variant}", + "options": { + "env": { + "VARIANT": "${input:variant}", + "VARIANT_DATA_FILE": "build/variants/${input:variant}/${input:buildKit}/${input:docsTarget}.json" + } + }, + "problemMatcher": [] + }, + { + "label": "Documentation gate (all variants)", + "detail": "What CI's compiler-free job runs: generate every variant and build its documents", + "type": "shell", + "command": "${command:python.interpreterPath} -m pytest -m docs", + "group": "test", + "problemMatcher": [] + }, { "label": "Open variant test report", "detail": "Open the variant's overall test report in your web browser", @@ -111,6 +150,24 @@ "components/spled" ] }, + { + "type": "pickString", + "id": "buildKit", + "description": "Which build kit? (the test kit adds a variant's test suites to its component list)", + "options": [ + "test", + "prod" + ] + }, + { + "type": "pickString", + "id": "docsTarget", + "description": "Which build shape? (reports additionally shows the generated test and coverage pages)", + "options": [ + "docs", + "reports" + ] + }, { "type": "pickString", "id": "buildType", diff --git a/AGENTS.md b/AGENTS.md index 12e9a247..30515872 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -116,7 +116,8 @@ CI runs on **GitHub Actions** (`.github/workflows/ci.yml`) for every push/PR to Jobs: -- `determine-gate` — computes the `gate_*` quality-gate marker once (by event/branch) and shares it with all three build jobs via `needs`. +- `determine-gate` — computes the `gate_*` quality-gate marker once (by event/branch) and shares it with all four jobs via `needs`. +- `documentation` (`ubuntu-24.04`) — the compiler-free gate: a Python and the locked dependencies, then `pytest -m "docs and "`. It generates the variant data for **every** variant and builds each one's documents, where the build jobs only cover the variants they build. No poks, no scoop, no cross-compiler, so it is also the fastest signal in the workflow. - `test-on-windows` (`windows-2025`) — `build.ps1 -install` then `-selftests -marker `. - `test-on-linux` (`ubuntu-24.04`) — bare-runner path: `bootstrap_ubuntu.sh` + `bootstrap_python.sh`, then `build.sh --install` and `--selftests --marker `. - `test-devcontainer` (`ubuntu-24.04`) — builds `.devcontainer/` via `devcontainers/ci` (which runs `onCreateCommand`, i.e. `build.sh --install`) and runs `build.sh --selftests --marker ` inside the container. diff --git a/pytest.ini b/pytest.ini index 357b7eb8..726babf6 100644 --- a/pytest.ini +++ b/pytest.ini @@ -9,6 +9,7 @@ addopts = -vv markers = unittests: tests of individual units of code + docs: tests of the documentation build, which needs no compiler reports: tests of the report generation build_debug: tests of the target builds with build type debug build_release: tests of the target builds with build type release diff --git a/test/test_documentation.py b/test/test_documentation.py new file mode 100644 index 00000000..5a20a14a --- /dev/null +++ b/test/test_documentation.py @@ -0,0 +1,163 @@ +"""The documentation gate: every variant's documents build, and build right. + +**This suite needs no compiler.** KConfig is pure Python and the documents are +text, while CMake's top-level `project()` call demands a C toolchain before it +will configure at all. Keeping the gate independent of that is the point: the +documentation is the part of this product line that most people read and edit, +and it should not take a cross-compiler to check it. + +What it proves, per variant: + +- the variant data generates at all -- which exercises the KConfig model and the + parts.cmake grammar for every variant, not just the one someone last built; +- Sphinx builds the variant's documents without error; +- the documents present are exactly the ones the variant's component list says, + which is the property the whole variant-gating design exists to provide. +""" + +import os +import subprocess +import sys +from pathlib import Path + +import pytest + +PROJECT_ROOT = Path(__file__).resolve().parent.parent +sys.path.insert(0, str(PROJECT_ROOT / "tools")) + +import variant_data # noqa: E402 + +pytestmark = [ + pytest.mark.docs, + pytest.mark.gate_develop_pr, + pytest.mark.gate_develop_push, + pytest.mark.gate_develop_nightly, + pytest.mark.gate_release_pr, + pytest.mark.gate_release, +] + +VARIANTS = ["Base/Dev", "Disco", "IDEA/Sloemada", "Sleep", "Spa"] + +#: Component documents that exist in the tree, with the component each belongs +#: to. The build must contain a page exactly when the variant contains the +#: component -- stated here rather than derived, so that a bug in the derivation +#: cannot make the test agree with it. +COMPONENT_DOCS = { + "components/light_controller": "components/light_controller/doc/index.html", + "components/main_control_knob": "components/main_control_knob/doc/index.html", + "components/power_button": "components/power_button/doc/index.html", + "components/power_signal_processing": "components/power_signal_processing/doc/index.html", + "components/brightness_controller": "components/brightness_controller/doc/index.html", + "components/auto_off": "components/auto_off/doc/index.html", + "test/spled_integration": "test/spled_integration/doc/index.html", + "components/examples/hello_gmock": "components/examples/hello_gmock/doc/index.html", + "components/examples/flight_controller": "components/examples/flight_controller/doc/index.html", +} + + +@pytest.fixture(scope="module") +def all_variant_data() -> None: + """Generate the whole matrix once, the way a developer or CI would.""" + subprocess.run( + [sys.executable, str(PROJECT_ROOT / "tools" / "variant_data.py"), "--all"], + cwd=PROJECT_ROOT, + check=True, + capture_output=True, + ) + + +def _build_docs(variant: str, kit: str, target: str, out_dir: Path) -> subprocess.CompletedProcess: + env = { + **os.environ, + "VARIANT_DATA_FILE": str(PROJECT_ROOT / "build" / "variants" / variant / kit / f"{target}.json"), + "VARIANT": variant, + } + # Deliberately NOT inheriting SPHINX_BUILD_CONFIGURATION_FILE: this gate is + # the bare build, the one a reader with no CMake in sight performs. + env.pop("SPHINX_BUILD_CONFIGURATION_FILE", None) + return subprocess.run( + [sys.executable, "-m", "sphinx", "-b", "html", str(PROJECT_ROOT), str(out_dir)], + cwd=PROJECT_ROOT, + env=env, + capture_output=True, + text=True, + ) + + +def test_variant_data_generates_for_every_variant(all_variant_data: None) -> None: + """`--check` passes right after `--all`, so the two agree by construction.""" + result = subprocess.run( + [sys.executable, str(PROJECT_ROOT / "tools" / "variant_data.py"), "--all", "--check"], + cwd=PROJECT_ROOT, + capture_output=True, + text=True, + ) + assert result.returncode == 0, result.stderr + + +@pytest.mark.parametrize("variant", VARIANTS) +def test_variant_documents_build(all_variant_data: None, variant: str, tmp_path: Path) -> None: + result = _build_docs(variant, "test", "docs", tmp_path / "html") + assert result.returncode == 0, f"sphinx-build failed for {variant}:\n{result.stdout[-4000:]}\n{result.stderr[-4000:]}" + assert (tmp_path / "html" / "index.html").is_file() + + +@pytest.mark.parametrize("variant", VARIANTS) +def test_the_built_documents_are_exactly_the_variants_components(all_variant_data: None, variant: str, tmp_path: Path) -> None: + """The property the whole design exists to provide. + + A component's document is in the build exactly when the variant's + parts.cmake adds it. Not when a feature suggests it, and never because the + variant happens to be called something. + """ + out = tmp_path / "html" + result = _build_docs(variant, "test", "docs", out) + assert result.returncode == 0, result.stdout[-2000:] + + components = set(variant_data.components(PROJECT_ROOT, variant, "test")) + + for component, page in COMPONENT_DOCS.items(): + built = (out / page).is_file() + if component in components: + assert built, f"{variant} contains {component} but {page} was not built" + else: + assert not built, f"{variant} does not contain {component} but {page} was built" + + +def test_the_integration_suite_follows_the_build_kit(all_variant_data: None, tmp_path: Path) -> None: + """Disco's parts.cmake adds it for the test kit only. + + This is the case the old `variant == "Disco"` gate got wrong: it claimed the + suite for Disco's prod kit too. Gating on the component list is what makes + the document follow the same rule the build does. + """ + page = COMPONENT_DOCS["test/spled_integration"] + + prod = tmp_path / "prod" + assert _build_docs("Disco", "prod", "docs", prod).returncode == 0 + assert not (prod / page).is_file(), "the integration suite is not in Disco's prod kit" + + test = tmp_path / "test" + assert _build_docs("Disco", "test", "docs", test).returncode == 0 + assert (test / page).is_file(), "the integration suite is in Disco's test kit" + + +def test_no_document_is_rendered_through_jinja(all_variant_data: None, tmp_path: Path) -> None: + """A Jinja construct in a document would now reach the reader verbatim. + + There is no `source-read` hook any more, so this is not a style rule: an + accidental `{{ ... }}` is shipped as literal text rather than rendered. The + check is on the sources, because the built HTML escapes braces anyway. + """ + offenders = [] + for pattern in ("index.md", "doc/**/*.md", "components/**/doc/*.md", "test/*/doc/*.md"): + for path in PROJECT_ROOT.glob(pattern): + text = path.read_text() + if "{{" in text or "{%" in text: + offenders.append(str(path.relative_to(PROJECT_ROOT))) + assert not offenders, f"Jinja constructs in documents: {offenders}" + + +def test_conf_py_registers_no_source_read_handler() -> None: + conf = (PROJECT_ROOT / "conf.py").read_text() + assert "source-read" not in conf, "the global Jinja pass must not come back" From 9306f40d8dfd1ae9082739ded36a2360e65e30b7 Mon Sep 17 00:00:00 2001 From: Lucas Silber Date: Wed, 16 Sep 2026 15:59:55 +0200 Subject: [PATCH 09/19] docs: record what was implemented, and why Phase 6 was not Phases 0-5 and 7 are done. Phase 6 is not, and the reason is worth writing down rather than leaving as a gap: investigating it with sphinx-test-reports 2.0.0 actually installed showed that two of its premises do not hold here. link_properties cannot replace sple_tr_link, because it reads a GoogleTest RecordProperty and this repository has none -- the traceability lives in `:tests:` RST comment blocks above each TEST(). Adopting it means adding a RecordProperty call duplicating the list three lines above it, which is the same one-fact-two-places failure this plan exists to remove, reintroduced a layer down. Keeping the comment blocks and joining by name is the alternative, and choosing between them is a decision, not a detail. Relaxing the version pin alone is not safe either: the CLI writes result = "failed" while doc/test_report_template.txt filters on "failure" in four places, so imported data would silently report zero failures. Both confirmed by running the CLI on a GoogleTest XML, not inferred. The good news is also recorded: 2.0.0 still ships the test-report directive, so the upgrade is backward compatible whenever it happens. conf.py's setup() moves to the end of the file, where a reader expects it. Co-Authored-By: Claude Opus 5 (1M context) --- conf.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/conf.py b/conf.py index 44bc5cdb..8eb42252 100644 --- a/conf.py +++ b/conf.py @@ -108,16 +108,6 @@ # supply the docs data and every report fence would evaluate false. _variant_data_file = os.environ.get("VARIANT_DATA_FILE") - -def setup(app): - if not _variant_data_file: - return - - def _select_variant_data(app, config): - config.needs_variant_data_file = _variant_data_file - - app.connect("config-inited", _select_variant_data, priority=20) - # build shape ############################################################### # # The rest of this file is Sphinx plumbing, not variant data: which documents @@ -154,3 +144,14 @@ def _select_variant_data(app, config): "generated/components/**/__source_docs/**", ] ) + + +def setup(app): + """Apply the per-build-shape variant data file, after the TOML is read.""" + if not _variant_data_file: + return + + def _select_variant_data(app, config): + config.needs_variant_data_file = _variant_data_file + + app.connect("config-inited", _select_variant_data, priority=20) From c65b328a4f07f28fd4cd0109de768639505f449f Mon Sep 17 00:00:00 2001 From: Lucas Silber Date: Wed, 16 Sep 2026 16:13:37 +0200 Subject: [PATCH 10/19] fix: check with ubc, and fix the two defects it found I skipped the `ubc check` half of Phase 7 on the belief that the CLI was not available. It is -- it ships inside the ubCode VS Code extension, just not on PATH. Running it immediately found two real defects in the configuration work, both invisible to every Sphinx build, which is precisely the failure mode this whole plan is about: a configuration only one reader executes cannot be validated by that reader. 1. `[source] extend_include` was inert. Configuring parsers puts ubCode in parser mode, where the document set comes from each `[parse.parsers.*]` include and extend_include is ignored. The generated source listings were therefore not indexed at all, while the setting looked correct. They are named in [parse.parsers.rst].include now. (extend_exclude IS honoured -- `ubc config` shows build/** excluded, and shows the variant_sources rules compiled into the same list, which is how the gating is implemented.) 2. The two readers had different document sets. `[parse.parsers.md] include = ["*.md"]` matched every Markdown file in the tree, so ubCode parsed AGENTS.md, README.md, CLAUDE.md and three dozen agent skill definitions as project documents -- 38 files Sphinx never sees. Narrowed to match conf.py's include_patterns; diagnostics went from 58 to 15. It also found the last "key only one reader knows": doc/component_report.md used the {variant} role on build_config.component_info.long_name, which spl-core writes per BUILD for one component. The variant data cannot carry it -- the generator does not know which component a per-component report is for -- so it resolved inside that one build and nowhere else. The gate is now a test rather than a thing I ran once. Per variant it asserts that ubCode removes EXACTLY the component documents the variant's component list omits: the design's central claim, checked against the reader that never runs conf.py instead of argued. Verified to fail on divergence by regressing a rule to gate on a variant name, which breaks IDEA/Sloemada. `ubc` is on neither PyPI nor npm, so there is no install step this repository can own; the tests find it on PATH, via $UBC, or in the extension directory, and skip when absent rather than pretending to cover it. Not added to the CI job for the same reason. Two pre-existing content issues surfaced, both agreed on by both readers: an empty toctree in components/examples/flight_controller/doc (removed), and SWIMPL_BC-003c linking to SWDD_BC-203, a spec inside an {if} fence that does not exist in every variant where the implementation does. The second is left alone -- it is a traceability decision, not part of this plan. Co-Authored-By: Claude Opus 5 (1M context) --- .vscode/tasks.json | 8 ++ AGENTS.md | 33 ++++++ .../examples/flight_controller/doc/index.md | 6 - doc/component_report.md | 13 ++- test/test_documentation.py | 104 ++++++++++++++++++ test/test_ubproject_config.py | 42 +++++-- ubproject.toml | 28 ++++- 7 files changed, 214 insertions(+), 20 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 1f2756c0..144eda9c 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -66,6 +66,14 @@ "group": "test", "problemMatcher": [] }, + { + "label": "Check documentation with ubc (all variants)", + "detail": "Lint every variant with the reader that never runs conf.py", + "type": "shell", + "command": "${command:python.interpreterPath} -m pytest -m docs -k ubc", + "group": "test", + "problemMatcher": [] + }, { "label": "Open variant test report", "detail": "Open the variant's overall test report in your web browser", diff --git a/AGENTS.md b/AGENTS.md index 30515872..b1ee5351 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -237,6 +237,39 @@ evaluated **excludes** what it gates, so a typo silently shrinks the document set rather than failing loudly — which is what `test_ubproject_config.py` is for. +### Checking with the other reader + +The Sphinx build is only half the story: the point of keeping everything in the +variant data file is that a reader which never runs `conf.py` decides the same +things. `ubc` is that reader, and `pytest -m docs -k ubc` proves it — per +variant, it asserts that ubCode removes **exactly** the component documents the +variant's component list omits. + +`ubc` ships inside the ubCode VS Code extension and is on neither PyPI nor npm, +so there is no install step this repository can own. The tests find it on +`PATH`, via the `UBC` environment variable, or in the extension directory, and +**skip** when it is absent rather than pretending to cover it: + +```bash +export UBC="$HOME/.vscode/extensions/useblocks.ubcode-0.35.0-darwin-arm64/server/cli/ubc" +pytest -m docs -k ubc +``` + +To look at one variant by hand, override the data file rather than switching +the project: + +```bash +ubc check -c "needs.variant_data_file = 'build/variants/Sleep/test/docs.json'" +``` + +Two things to know about `ubproject.toml` when editing it. Configuring parsers +puts ubCode in **parser mode**, where the document set comes from each +`[parse.parsers.*].include` and `[source] extend_include` is *ignored* — so the +parser includes have to stay in step with `include_patterns` in `conf.py`, or +the two readers are looking at different files. And `[[source.variant_sources]]` +rules are implemented as exclusions, which is why a rule that cannot be +evaluated removes what it gates. + ### Adding a component 1. Add it to the variant's `parts.cmake`. diff --git a/components/examples/flight_controller/doc/index.md b/components/examples/flight_controller/doc/index.md index bf5c8302..c4682094 100644 --- a/components/examples/flight_controller/doc/index.md +++ b/components/examples/flight_controller/doc/index.md @@ -1,11 +1,5 @@ # Software Detailed Design -```{toctree} -:maxdepth: 2 -:caption: Contents -:class: toc -``` - ## Introduction The Flight Controller module is responsible for evaluating mission abort conditions and triggering the SelfDestruct system if necessary. It demonstrates a clear MC/DC coverage example. diff --git a/doc/component_report.md b/doc/component_report.md index ee467bc5..ba070bd3 100644 --- a/doc/component_report.md +++ b/doc/component_report.md @@ -1,12 +1,19 @@ # Software Component Report -**Variant:** {variant}`build_config.variant`
-**Component:** {variant}`build_config.component_info.long_name` +**Variant:** {variant}`build_config.variant` This is the root document of the per-component report that spl-core builds for a single component. The build configuration restricts the source set to that one component, so the glob below resolves to exactly its design document, which -in turn groups that component's verification pages. +in turn groups that component's verification pages -- and names the component, +which is why this page does not. + +The component name is deliberately not read from `build_config.component_info`. +That key is written per BUILD, by spl-core, for one component; the variant data +every other reader has cannot contain it, because the generator does not know +which component a per-component report is for. A role naming it resolves inside +that one build and nowhere else, which is the divergence between readers this +project's configuration exists to prevent. ```{toctree} :maxdepth: 2 diff --git a/test/test_documentation.py b/test/test_documentation.py index 5a20a14a..9bc9dae7 100644 --- a/test/test_documentation.py +++ b/test/test_documentation.py @@ -15,7 +15,10 @@ which is the property the whole variant-gating design exists to provide. """ +import json import os +import re +import shutil import subprocess import sys from pathlib import Path @@ -161,3 +164,104 @@ def test_no_document_is_rendered_through_jinja(all_variant_data: None, tmp_path: def test_conf_py_registers_no_source_read_handler() -> None: conf = (PROJECT_ROOT / "conf.py").read_text() assert "source-read" not in conf, "the global Jinja pass must not come back" + + +# --- the cross-reader gate ------------------------------------------------- +# +# Everything above proves the Sphinx build behaves. The point of the whole +# design, though, is that a SECOND reader -- one that never runs conf.py -- +# decides the same things. That can only be proven by running it. +# +# `ubc` ships inside the ubCode VS Code extension and is on neither PyPI nor +# npm, so there is no install step this repository can own. These tests skip +# when it is absent rather than pretending to cover it; set UBC, or put it on +# PATH, to turn them on. See AGENTS.md. + + +def _find_ubc() -> str | None: + if (explicit := os.environ.get("UBC")) and Path(explicit).is_file(): + return explicit + if found := shutil.which("ubc"): + return found + extensions = Path.home() / ".vscode" / "extensions" + candidates = sorted(extensions.glob("useblocks.ubcode-*/server/cli/ubc")) + return str(candidates[-1]) if candidates else None + + +UBC = _find_ubc() +needs_ubc = pytest.mark.skipif(UBC is None, reason="ubc not found; set UBC or put it on PATH") + + +def _ubc_check(variant: str, kit: str, target: str) -> list[dict]: + result = subprocess.run( + [ + UBC, + "check", + "-c", + f"needs.variant_data_file = 'build/variants/{variant}/{kit}/{target}.json'", + "--output-format", + "json", + ], + cwd=PROJECT_ROOT, + capture_output=True, + text=True, + ) + assert result.stdout, f"ubc check produced no JSON for {variant}:\n{result.stderr[-2000:]}" + return json.loads(result.stdout).get("diagnostics", []) + + +@needs_ubc +@pytest.mark.parametrize("variant", VARIANTS) +def test_ubc_reports_no_errors(all_variant_data: None, variant: str) -> None: + diagnostics = _ubc_check(variant, "test", "docs") + errors = [d for d in diagnostics if d["severity"] == "error"] + assert not errors, f"{variant}: " + "; ".join(d["message"] for d in errors) + + +@needs_ubc +def test_ubc_finds_no_configuration_problem(all_variant_data: None) -> None: + """A `config` diagnostic means the two readers are configured differently. + + The one informational exception is ubCode noting that a Sphinx build honours + the variant-gating keys only when sphinx-mounts is installed -- which it is, + and which the rest of this suite proves. + """ + diagnostics = _ubc_check("Disco", "test", "docs") + problems = [ + d + for d in diagnostics + if d["code"].startswith("config") and d["code"] != "config.variant_sources_sphinx_unsupported" + ] + assert not problems, "; ".join(f"{d['code']}: {d['message']}" for d in problems) + + +@needs_ubc +@pytest.mark.parametrize("variant", VARIANTS) +def test_ubc_excludes_exactly_what_sphinx_excludes(all_variant_data: None, variant: str) -> None: + """The property the whole design exists to provide, proven across readers. + + ubCode never runs conf.py. If it removes exactly the component documents + that the variant's component list omits -- the same set the Sphinx build + omits, asserted above -- then both readers are deciding from the same data + and agreeing. That is the claim; this is the test of it. + """ + diagnostics = _ubc_check(variant, "test", "docs") + + excluded_by_ubc = set() + for diagnostic in diagnostics: + if diagnostic["code"] != "toctree.variant_excluded": + continue + match = re.search(r"toctree entry '([^']+)'", diagnostic["message"]) + assert match, diagnostic["message"] + excluded_by_ubc.add(match.group(1)) + + components = set(variant_data.components(PROJECT_ROOT, variant, "test")) + expected = { + f"{component}/doc/index" for component in COMPONENT_DOCS if component not in components + } + + assert excluded_by_ubc == expected, ( + f"{variant}: ubCode and the component list disagree.\n" + f" ubCode excluded : {sorted(excluded_by_ubc)}\n" + f" expected : {sorted(expected)}" + ) diff --git a/test/test_ubproject_config.py b/test/test_ubproject_config.py index cfdb79f6..60c948e4 100644 --- a/test/test_ubproject_config.py +++ b/test/test_ubproject_config.py @@ -119,16 +119,42 @@ def test_build_output_is_not_indexed(project_config: dict) -> None: assert project_config["source"]["extend_exclude"] == ["build/**"] -def test_generated_listings_are_included_through_the_stable_path(project_config: dict, spl_core_config: dict) -> None: - """A deliberate deviation from spl-core's `build/**/__source_docs/**`. - - `generated` is the current variant's build directory, maintained by - tools/variant_data.py. Going through it means one variant's listings are - indexed -- the configured one -- instead of all of them, and it survives the - `build/**` exclusion above. +def test_extend_include_is_not_used_because_it_would_be_inert(project_config: dict, spl_core_config: dict) -> None: + """Configuring parsers puts ubCode in parser mode, where this key is ignored. + + spl-core's base config includes the generated listings with + `[source] extend_include`, which works only while no parser is configured. + This project configures both parsers, so file discovery comes from their + `include` lists and `extend_include` does nothing -- silently, apart from + one config warning. Carrying the key anyway would look like configuration + and behave like a comment. `ubc check` is what caught this. """ assert spl_core_config["source"]["extend_include"] == ["build/**/__source_docs/**"] - assert project_config["source"]["extend_include"] == ["generated/**/__source_docs/**"] + assert "extend_include" not in project_config["source"] + assert project_config["parse"]["parsers"]["rst"]["include"] == ["generated/**/*.rst"] + + +def test_ubcode_and_sphinx_see_the_same_documents(project_config: dict) -> None: + """The parser includes have to match conf.py's include_patterns. + + They did not: `include = ["*.md"]` matched every Markdown file in the tree, + so ubCode parsed AGENTS.md, README.md, CLAUDE.md and three dozen agent skill + definitions as project documents -- 38 files Sphinx never sees. Two readers + with different document sets cannot agree about the project, which is the + whole thing this configuration exists to prevent. + """ + conf = (PROJECT_ROOT / "conf.py").read_text() + markdown_includes = project_config["parse"]["parsers"]["md"]["include"] + + assert markdown_includes == [ + "index.md", + "doc/**/*.md", + "components/**/doc/**/*.md", + "test/**/doc/**/*.md", + ] + # Every tree the parser reads is a tree conf.py also names. + for tree in ("index.md", "doc/**", "components/**/doc/**", "test/**/doc/**"): + assert f'"{tree}"' in conf, f"conf.py does not include {tree}, which the md parser reads" # --- what the variant machinery needs -------------------------------------- diff --git a/ubproject.toml b/ubproject.toml index 85796918..a3b61c03 100644 --- a/ubproject.toml +++ b/ubproject.toml @@ -22,11 +22,32 @@ index_on_save = true hover_need_refs = true -[parse.parsers.rst] +# File discovery. Configuring parsers puts ubCode in "parser mode", where the +# document set comes from each parser's `include` and `[source] extend_include` +# is IGNORED -- silently, apart from one config warning. So the document set +# lives here, and it has to be the same set conf.py gives Sphinx through +# `include_patterns`. It was not: `include = ["*.md"]` matched every Markdown +# file in the tree, so ubCode was also parsing AGENTS.md, README.md, CLAUDE.md +# and three dozen agent skill definitions as project documents -- 38 documents +# Sphinx never sees, each reported as missing from a toctree. [parse.parsers.md] flavour = "myst" -include = ["*.md"] +include = [ + "index.md", + "doc/**/*.md", + "components/**/doc/**/*.md", + "test/**/doc/**/*.md", +] + +# Every .rst in this project is generated: the clanguru source listings and the +# report pages, reached through `generated`, the configured variant's build +# directory. They are indexed when the current variant data names the `reports` +# target, and excluded by the variant rule below when it does not -- so the IDE +# shows what a reports build would show, rather than whatever happens to be on +# disk. +[parse.parsers.rst] +include = ["generated/**/*.rst"] [source] exclude = [".bzr", ".direnv", ".eggs", ".git", ".git-rewrite", ".hg", ".svn", ".venv", ".vscode", "_build", "dist", "node_modules", "site-packages"] @@ -36,7 +57,8 @@ respect_gitignore = false # built, so need IDs appear many times over and the IDE disagrees with any one # build about what the project contains. Sphinx never saw this because conf.py # narrows its source set per build shape. -extend_include = ["generated/**/__source_docs/**"] +# `extend_include` is deliberately absent: it is ignored in parser mode, so the +# generated listings are named by [parse.parsers.rst] instead. extend_exclude = ["build/**"] [needs] From 92ad9bd87c75a19ee3a642ae3c5a8bbec34035de Mon Sep 17 00:00:00 2001 From: Lucas Silber Date: Wed, 16 Sep 2026 16:30:47 +0200 Subject: [PATCH 11/19] docs: recheck against the concept note, and record the symlink finding All four "what is still wrong" items and all seven prioritised gaps in the note are closed, each verified against the tree rather than from memory -- including planting a decoy need under build/ to confirm ubCode no longer indexes it. Three deliberate deviations from the target concept are now stated as such: the variant matrix is four-dimensional because the note's own requirements (kit and target in the data) do not fit its proposed filename; the generated reports are gated by a variant_sources rule rather than a mount, because rules compose as AND with the per-component ones and a mount cannot; and conf.py keeps its Sphinx-only plumbing, with both pointers now named explicitly rather than one of them resting on a library default. The note asks to verify rather than assume that ubCode might report the codelinks preprocessor.compile_commands key as unknown. It does not, on 0.35.0 -- `ubc config` resolves the table with defaults filled in. New finding, from testing rather than reading: `generated` is a symlink, and ubCode does not descend symlinked directories -- its own diagnostic text says so. Sphinx follows it, so the reports build is fine, but the IDE sees no generated pages at all. Verified both ways: with `generated` a real directory a planted need appears under the reports pointer and is absent under the docs pointer, so the gating itself is correct; with the symlink the same file is invisible and both pointers yield an identical needs count. This costs nothing today, because no needs live under generated/ yet. It starts costing when Phase 6 lands, since that is exactly where imported test-result needs would go. The fix is to materialise the generated .rst files into a real directory after the reports target produces them -- a post-build step, not configure time -- and it is not written here because the reports target cannot be run on this machine. Recorded at the symlink, in ubproject.toml next to the now-knowingly-inert include, and in the plan. Also drops a GENERATED marker in build/ itself, not only build/variants/, since build/ is the directory someone is most likely to open. Co-Authored-By: Claude Opus 5 (1M context) --- conf.py | 5 +++++ tools/variant_data.py | 25 +++++++++++++++++++------ ubproject.toml | 17 ++++++++++++----- 3 files changed, 36 insertions(+), 11 deletions(-) diff --git a/conf.py b/conf.py index 8eb42252..0c5e57c9 100644 --- a/conf.py +++ b/conf.py @@ -78,6 +78,11 @@ # spl-core's base configuration is vendored into it. needs_from_toml = "ubproject.toml" +# The same file, named again for the other extension that reads it. sphinx-mounts +# already defaults to this path; naming it makes the coupling visible in conf.py +# rather than resting on a library default. +sources_from_toml = "ubproject.toml" + # Registers project Python that the configuration references by name. This is # the last thing in the needs model that ubCode cannot see, because it cannot # run project functions; it goes away with sple_tr_link. diff --git a/tools/variant_data.py b/tools/variant_data.py index 5ac9382a..c8dc74a5 100644 --- a/tools/variant_data.py +++ b/tools/variant_data.py @@ -224,6 +224,14 @@ def write_pointer(project_root: Path, data: dict[str, Any], build_dir: Path | No so the only way one toctree entry can mean one page in both is for the path and the prefix to be the same string. And ubCode's default `exclude` contains "build", which would otherwise drop the whole mounted tree. + + KNOWN LIMITATION: a symlink gets this right for Sphinx and wrong for ubCode, + which does not descend symlinked directories. The generated report pages are + therefore invisible to the IDE. Materialising them -- copying the .rst files + into a real `generated/` tree after the reports target has produced them -- + is the fix, and it has to happen after that target runs, not here at + configure time when they do not exist yet. Pointing `generated` at a real + directory makes ubCode index them, gated exactly as intended. """ pointer = project_root / "build" / "autoconf.json" pointer.parent.mkdir(parents=True, exist_ok=True) @@ -246,13 +254,18 @@ def write_pointer(project_root: Path, data: dict[str, Any], build_dir: Path | No shutil.copytree(build_dir, current, dirs_exist_ok=True) +#: Dropped into every directory this script owns. `build/` itself gets one too, +#: because that is the directory somebody is most likely to open and edit in. +GENERATED_MARKER = ( + "Written by tools/variant_data.py. Everything under build/ is generated\n" + "output: nobody edits it, neither a person nor an assistant.\n" +) + + def mark_generated(project_root: Path) -> None: - marker = project_root / "build" / "variants" / "GENERATED" - marker.parent.mkdir(parents=True, exist_ok=True) - marker.write_text( - "Written by tools/variant_data.py. Everything under build/ is generated\n" - "output: nobody edits it, neither a person nor an assistant.\n" - ) + for directory in (project_root / "build", project_root / "build" / "variants"): + directory.mkdir(parents=True, exist_ok=True) + (directory / "GENERATED").write_text(GENERATED_MARKER) def _check(project_root: Path, selected: list[str]) -> int: diff --git a/ubproject.toml b/ubproject.toml index a3b61c03..68cfccc6 100644 --- a/ubproject.toml +++ b/ubproject.toml @@ -41,11 +41,18 @@ include = [ ] # Every .rst in this project is generated: the clanguru source listings and the -# report pages, reached through `generated`, the configured variant's build -# directory. They are indexed when the current variant data names the `reports` -# target, and excluded by the variant rule below when it does not -- so the IDE -# shows what a reports build would show, rather than whatever happens to be on -# disk. +# report pages, under `generated`, the configured variant's build directory. +# They are indexed when the current variant data names the `reports` target and +# excluded by the variant rule below when it does not. +# +# KNOWN LIMITATION, verified: ubCode does not descend symlinked directories +# ("directories are skipped and symlinked directories are not descended"), and +# tools/variant_data.py makes `generated` a symlink. So this include is correct +# and currently inert FOR UBCODE -- Sphinx follows the link and does read these +# pages. Point `generated` at a real directory and ubCode indexes them, gated +# exactly as intended; that is verified, not assumed. Until the generated +# documents are materialised rather than linked, the IDE simply shows no report +# pages, which costs nothing today because no needs live in them yet. [parse.parsers.rst] include = ["generated/**/*.rst"] From 375771a69127a3bf9220f5fa90226e87c01722ea Mon Sep 17 00:00:00 2001 From: Lucas Silber Date: Wed, 16 Sep 2026 17:42:24 +0200 Subject: [PATCH 12/19] fix: work against the pinned spl-core, not a local checkout CI failed all three build jobs and the documentation gate: 51 failures, two causes. The branch depended on an unreleased spl-core. tools/variant_data.py called KConfig.declared_boolean_symbols(), which exists only in the fork I had installed as an editable override here. CI installs the version pinned in pyproject.toml, so it raised AttributeError -- and because CMakeLists.txt runs the generator at configure time and fails hard, that took every variant build down with it, not just the docs gate. It worked on exactly one machine: the one with the local checkout. The accessor is now asked for and fallen back from, so the generator works against both. Tests read generated state they did not generate. test_ubproject_config.py loaded build/variants/** with no fixture; build/ is gitignored, so on a fresh checkout the files are simply absent. It passed locally only because earlier runs had left them there. A module fixture generates the matrix first. Chasing this found a third problem CI would NOT have caught. The SPL_VARIANT_DATA_FILE_* cache variables exist only in the fork, so against the pinned spl-core the reports build got no VARIANT_DATA_FILE, fell back to the pointer holding the DOCS cell, and every report fence would have evaluated false: a reports target with no reports in it, no error anywhere, and a green test_reports, because that test asserts the build succeeded and junit.xml exists rather than that any report page was produced. So the dependency is removed instead of trusted. CMake publishes the two cells under fixed names and conf.py selects the one matching its build shape, which spl-core already tells it through the directory holding the per-target config.json. VARIANT_DATA_FILE still takes precedence where something passes it, so the fork and any later release keep working. Choosing which generated file to read is not synthesizing data -- the file is complete and conf.py still adds nothing to it. A test pins it, since silent regression is the failure mode. Verified from a clean tree against spl-core 8.6.0: 84 tests pass. One degradation remains and needs the spl-core release: the pinned version always passes --jinja-raw-tags to clanguru, so generated source listings carry literal {% raw %} lines now that the Jinja pass is gone. Confirmed on a built page. Cosmetic, confined to the reports target, breaks nothing. Co-Authored-By: Claude Opus 5 (1M context) --- CMakeLists.txt | 28 ++++++++++++---- conf.py | 27 +++++++++++----- test/test_documentation.py | 61 +++++++++++++++++++++++++++++++++++ test/test_ubproject_config.py | 21 ++++++++++-- tools/variant_data.py | 28 +++++++++++++++- 5 files changed, 147 insertions(+), 18 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 717b4a94..222fc752 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,15 +90,29 @@ if(NOT _spled_variant_data_result EQUAL 0) message(FATAL_ERROR "tools/variant_data.py failed:\n${_spled_variant_data_output}") endif() -# Point each Sphinx build shape at its own cell of the matrix. The `docs` cell is -# also the one build/autoconf.json holds, so ubCode and the docs build evaluate -# byte-identical data. The reports fences then resolve to a clean false in the -# IDE rather than being undecidable -- `target` is present, it simply is not -# "reports" there. +# Publish the configured variant's two cells under fixed names, so a Sphinx +# build can find the one matching its build shape without being told the variant +# or the kit. conf.py reads these; `build/autoconf.json` (the docs cell) stays +# the pointer ubCode reads, so the IDE and the docs build see identical data and +# the report fences resolve to a clean false in the IDE rather than being +# undecidable. +# +# Fixed names rather than an environment variable because spl-core only passes +# one in from 8.9 onwards, and this project builds against the version pinned in +# pyproject.toml. Where that variable IS passed, conf.py prefers it. +configure_file( + ${CMAKE_SOURCE_DIR}/build/variants/${VARIANT}/${BUILD_KIT}/docs.json + ${CMAKE_SOURCE_DIR}/build/variant-data-docs.json COPYONLY) +configure_file( + ${CMAKE_SOURCE_DIR}/build/variants/${VARIANT}/${BUILD_KIT}/reports.json + ${CMAKE_SOURCE_DIR}/build/variant-data-reports.json COPYONLY) + +# Honoured by spl-core 8.9+; harmlessly unused before that, which is why the +# fixed-name files above exist. set(SPL_VARIANT_DATA_FILE_DOCS - ${CMAKE_SOURCE_DIR}/build/variants/${VARIANT}/${BUILD_KIT}/docs.json CACHE FILEPATH "" FORCE) + ${CMAKE_SOURCE_DIR}/build/variant-data-docs.json CACHE FILEPATH "" FORCE) set(SPL_VARIANT_DATA_FILE_REPORTS - ${CMAKE_SOURCE_DIR}/build/variants/${VARIANT}/${BUILD_KIT}/reports.json CACHE FILEPATH "" FORCE) + ${CMAKE_SOURCE_DIR}/build/variant-data-reports.json CACHE FILEPATH "" FORCE) # The object_deps_report extension is currently Windows-only: its index.cmake # hardcodes the runner as "object_deps_report.exe", which does not exist on diff --git a/conf.py b/conf.py index 0c5e57c9..d8059fd6 100644 --- a/conf.py +++ b/conf.py @@ -3,6 +3,8 @@ import datetime import os +from pathlib import Path + from spl_core.report_generation.spl_sphinx import SplSphinx from spl_core.report_generation.spl_html_settings import html_theme, html_show_sourcelink, html_theme_options, html_sidebars, html_last_updated_fmt # noqa: F401 @@ -102,16 +104,25 @@ # the build. The rule is "everything a condition may name has to be IN the # file", and it only holds if this file adds nothing. # -# CMake exports VARIANT_DATA_FILE for the build shape it is building. Without -# it -- a bare `sphinx-build`, or the IDE -- the "current" pointer applies, -# which is also what ubproject.toml names, so the two readers see byte-identical -# data by default. +# Which cell of the matrix this build reads. Selecting a file is not the same as +# synthesizing data: the file is complete and generated, and nothing here adds a +# key to it. +# +# Three sources, in order. VARIANT_DATA_FILE if something passed one -- spl-core +# does from 8.9, and the tests do. Otherwise the fixed-name cell that CMake +# publishes for this build shape, which spl-core tells us via the directory its +# per-target configuration file sits in. Otherwise nothing, and the pointer named +# in ubproject.toml applies, which is what a bare `sphinx-build` and the IDE get. # -# It has to be applied after sphinx-needs has read ubproject.toml, which happens -# on `config-inited` and would otherwise put the pointer back. For the docs -# target that is the same file; for the reports target the pointer would quietly -# supply the docs data and every report fence would evaluate false. +# Without the middle step the reports build would quietly read the docs cell and +# every report fence would evaluate false -- a reports target with no reports in +# it, and no error anywhere. _variant_data_file = os.environ.get("VARIANT_DATA_FILE") +if not _variant_data_file: + _shape = "reports" if Path(os.environ.get("SPHINX_BUILD_CONFIGURATION_FILE", "")).parent.name == "reports" else "docs" + _published = Path(__file__).parent / "build" / f"variant-data-{_shape}.json" + if _published.is_file(): + _variant_data_file = str(_published) # build shape ############################################################### # diff --git a/test/test_documentation.py b/test/test_documentation.py index 9bc9dae7..21b0d2c6 100644 --- a/test/test_documentation.py +++ b/test/test_documentation.py @@ -265,3 +265,64 @@ def test_ubc_excludes_exactly_what_sphinx_excludes(all_variant_data: None, varia f" ubCode excluded : {sorted(excluded_by_ubc)}\n" f" expected : {sorted(expected)}" ) + + +# --- the build shape must select its own cell ------------------------------- + + +def _build_with_spl_core_env(shape: str, out_dir: Path, tmp_path: Path) -> subprocess.CompletedProcess: + """Build the way spl-core starts Sphinx: a per-target config.json, no more. + + spl-core only passes VARIANT_DATA_FILE from 8.9; against the version this + project pins it passes SPHINX_BUILD_CONFIGURATION_FILE, and the directory + that file sits in is what names the build shape. + """ + config_dir = tmp_path / "cfg" / shape + config_dir.mkdir(parents=True, exist_ok=True) + (config_dir / "config.json").write_text("{}") + + env = {**os.environ, "SPHINX_BUILD_CONFIGURATION_FILE": str(config_dir / "config.json"), "VARIANT": "Disco"} + env.pop("VARIANT_DATA_FILE", None) + return subprocess.run( + [sys.executable, "-m", "sphinx", "-b", "html", str(PROJECT_ROOT), str(out_dir)], + cwd=PROJECT_ROOT, + env=env, + capture_output=True, + text=True, + ) + + +def test_the_reports_shape_reads_the_reports_cell(all_variant_data: None, tmp_path: Path) -> None: + """Without this, a reports build quietly reads the docs cell. + + Every `{if} var.build_config.target == "reports"` fence would then evaluate + false and the reports target would contain no reports -- with no error + anywhere, and with the reports test still passing, because it asserts that + the build succeeded and not that it produced anything. + """ + published = PROJECT_ROOT / "build" / "variant-data-reports.json" + published.write_text((PROJECT_ROOT / "build" / "variants" / "Disco" / "test" / "reports.json").read_text()) + docs_published = PROJECT_ROOT / "build" / "variant-data-docs.json" + docs_published.write_text((PROJECT_ROOT / "build" / "variants" / "Disco" / "test" / "docs.json").read_text()) + + # A minimal generated tree, so the report toctrees have something to resolve. + generated = PROJECT_ROOT / "generated" + reports = generated / "components" / "light_controller" / "reports" + try: + reports.mkdir(parents=True, exist_ok=True) + for page in ("unit_test_spec", "unit_test_results", "coverage"): + (reports / f"{page}.rst").write_text("Sim\n===\n\nbody\n") + + reports_out = tmp_path / "reports_html" + assert _build_with_spl_core_env("reports", reports_out, tmp_path).returncode == 0 + assert (reports_out / "generated" / "components" / "light_controller" / "reports" / "coverage.html").is_file(), ( + "the reports shape did not read the reports cell" + ) + + docs_out = tmp_path / "docs_html" + assert _build_with_spl_core_env("docs", docs_out, tmp_path).returncode == 0 + assert not (docs_out / "generated").exists(), "the docs shape must not build report pages" + finally: + shutil.rmtree(generated, ignore_errors=True) + published.unlink(missing_ok=True) + docs_published.unlink(missing_ok=True) diff --git a/test/test_ubproject_config.py b/test/test_ubproject_config.py index 60c948e4..67c8573f 100644 --- a/test/test_ubproject_config.py +++ b/test/test_ubproject_config.py @@ -13,6 +13,9 @@ the other. """ +import json +import subprocess +import sys import tomllib from importlib.resources import files from pathlib import Path @@ -182,9 +185,23 @@ def rules(project_config: dict) -> list[dict]: return project_config["source"]["variant_sources"] -def _variant_data(variant: str, kit: str, target: str) -> dict: - import json +@pytest.fixture(scope="module", autouse=True) +def generated_variant_data() -> None: + """Generate the matrix before reading it. + + build/ is gitignored, so on a fresh checkout -- which is every CI run -- + these files do not exist yet. Reading them without generating them made + these tests pass only on a machine that had happened to build already. + """ + subprocess.run( + [sys.executable, str(PROJECT_ROOT / "tools" / "variant_data.py"), "--all"], + cwd=PROJECT_ROOT, + check=True, + capture_output=True, + ) + +def _variant_data(variant: str, kit: str, target: str) -> dict: with (PROJECT_ROOT / "build" / "variants" / variant / kit / f"{target}.json").open() as handle: return json.load(handle) diff --git a/tools/variant_data.py b/tools/variant_data.py index c8dc74a5..078aec8c 100644 --- a/tools/variant_data.py +++ b/tools/variant_data.py @@ -77,6 +77,32 @@ def variant_names(project_root: Path) -> list[str]: ) +def _declared_boolean_symbols(kconfig: Any) -> list[str]: + """Every boolean the feature model declares, however spl-core lets us ask. + + spl-core grew a public `declared_boolean_symbols()` for exactly this, but + this project must keep working against the version pinned in + pyproject.toml -- which is what CI installs and therefore what "works" + means. Depending on an unreleased accessor would make the build pass only + on a machine with a local checkout, which is the same class of mistake as + a configuration only one reader can evaluate. + + So: use the accessor when it is there, and otherwise read the kconfiglib + instance spl-core holds. Drop the fallback once pyproject.toml pins a + release that has the method. + """ + if hasattr(kconfig, "declared_boolean_symbols"): + return kconfig.declared_boolean_symbols() + + import kconfiglib + + return sorted( + name + for name, symbol in kconfig._config.syms.items() # noqa: SLF001 - see docstring + if name and symbol.orig_type == kconfiglib.BOOL + ) + + def features(project_root: Path, variant: str) -> dict[str, Any]: """The variant's complete feature vector. @@ -104,7 +130,7 @@ def features(project_root: Path, variant: str) -> dict[str, Any]: # `KConfig.config` only carries the symbols KConfig would write out. The # model itself knows every symbol, which is what makes the vector complete. - defaults = dict.fromkeys(kconfig.declared_boolean_symbols(), False) + defaults = dict.fromkeys(_declared_boolean_symbols(kconfig), False) # spl-core's own JSON writer, so the value conversion (tristates to bool, # hex, the ${VAR} substitution) is the build's and not a second opinion. From 92aad17eed617df99a240b1795e165df81045622 Mon Sep 17 00:00:00 2001 From: Lucas Silber Date: Wed, 16 Sep 2026 17:57:37 +0200 Subject: [PATCH 13/19] fix: keep the report toctrees on the build path spl-core writes to Second CI round: five failures, all the reports test not finding reports/html/build////components//reports/coverage.html. Not a test-path quibble. spl-core writes the gcovr tree at `reports/html/${component_reports_output_dir}/coverage/index.html` -- the component's build-relative path -- SplBuild looks the pages up in the same place, and the coverage.rst it generates links to `coverage/index.html` relative to itself. Page docname and gcovr tree have to stay siblings. Phase 5b moved the docname to generated/components//reports/coverage and left the tree where spl-core put it, so every variant report would have shipped a broken coverage link. The artifact assertion caught it only because it happens to sit on the same path the link depends on. A static toctree entry cannot name a build-directory-dependent docname, so without a stable path it has to be a glob, narrowed by conf.py to the configured variant's build directory. That is gap 4 of the concept note, and it is now deferred rather than closed -- behind the same spl-core release as the {% raw %} opt-out and Phase 6. Fixing it properly means spl-core emitting the gcovr tree next to wherever the page lands, and TARGET_COMPONENT_BUILD_ARTIFACTS following; both small, neither doable from here against a pinned release. Kept: all of Phase 5a. Membership gating, the 150% toctree, no variant name in any condition -- the part that was about correctness rather than paths. The variant_sources rule over generated/** stays as the right shape for the day the pages move, inert until then. The reports-shape test now asserts on the rendered page ("Verification" present for reports, absent for docs) rather than on a file list, which is what the fence actually controls and does not depend on where the pages land. Verified from a clean tree against the pinned spl-core: 84 tests pass, ubc reports the same 8 warnings and 5 info as before. Co-Authored-By: Claude Opus 5 (1M context) --- components/auto_off/doc/index.md | 7 +-- components/brightness_controller/doc/index.md | 7 +-- components/light_controller/doc/index.md | 7 +-- components/main_control_knob/doc/index.md | 7 +-- components/power_button/doc/index.md | 7 +-- .../power_signal_processing/doc/index.md | 7 +-- conf.py | 26 ++++++----- index.md | 17 ++++--- test/spled_integration/doc/index.md | 7 +-- test/test_documentation.py | 45 +++++++++---------- test/test_ubproject_config.py | 34 +++++++++----- ubproject.toml | 21 +++++---- 12 files changed, 108 insertions(+), 84 deletions(-) diff --git a/components/auto_off/doc/index.md b/components/auto_off/doc/index.md index f8cb62b0..c37df2df 100644 --- a/components/auto_off/doc/index.md +++ b/components/auto_off/doc/index.md @@ -128,10 +128,11 @@ Key press detection and timer reset occur within one execution cycle (CONFIG_OS_ ```{toctree} :maxdepth: 1 +:glob: -/generated/components/auto_off/reports/unit_test_spec -/generated/components/auto_off/reports/unit_test_results -/generated/components/auto_off/reports/coverage +/build/**/components/auto_off/reports/unit_test_spec +/build/**/components/auto_off/reports/unit_test_results +/build/**/components/auto_off/reports/coverage ``` ```` diff --git a/components/brightness_controller/doc/index.md b/components/brightness_controller/doc/index.md index f2b94083..f6c099a6 100644 --- a/components/brightness_controller/doc/index.md +++ b/components/brightness_controller/doc/index.md @@ -75,10 +75,11 @@ The Brightness Controller is use the RTE interface `RteSetBrightnessAdjustmentCo ```{toctree} :maxdepth: 1 +:glob: -/generated/components/brightness_controller/reports/unit_test_spec -/generated/components/brightness_controller/reports/unit_test_results -/generated/components/brightness_controller/reports/coverage +/build/**/components/brightness_controller/reports/unit_test_spec +/build/**/components/brightness_controller/reports/unit_test_results +/build/**/components/brightness_controller/reports/coverage ``` ```` diff --git a/components/light_controller/doc/index.md b/components/light_controller/doc/index.md index 6d47189d..6fa4ed27 100644 --- a/components/light_controller/doc/index.md +++ b/components/light_controller/doc/index.md @@ -118,10 +118,11 @@ stateDiagram-v2 ```{toctree} :maxdepth: 1 +:glob: -/generated/components/light_controller/reports/unit_test_spec -/generated/components/light_controller/reports/unit_test_results -/generated/components/light_controller/reports/coverage +/build/**/components/light_controller/reports/unit_test_spec +/build/**/components/light_controller/reports/unit_test_results +/build/**/components/light_controller/reports/coverage ``` ```` diff --git a/components/main_control_knob/doc/index.md b/components/main_control_knob/doc/index.md index 58b399f7..8791758f 100644 --- a/components/main_control_knob/doc/index.md +++ b/components/main_control_knob/doc/index.md @@ -58,10 +58,11 @@ The main control knob component uses the RTE interface `RteSetMainKnobValue()` t ```{toctree} :maxdepth: 1 +:glob: -/generated/components/main_control_knob/reports/unit_test_spec -/generated/components/main_control_knob/reports/unit_test_results -/generated/components/main_control_knob/reports/coverage +/build/**/components/main_control_knob/reports/unit_test_spec +/build/**/components/main_control_knob/reports/unit_test_results +/build/**/components/main_control_knob/reports/coverage ``` ```` diff --git a/components/power_button/doc/index.md b/components/power_button/doc/index.md index bb4fb559..f28bde6c 100644 --- a/components/power_button/doc/index.md +++ b/components/power_button/doc/index.md @@ -76,10 +76,11 @@ stateDiagram-v2 ```{toctree} :maxdepth: 1 +:glob: -/generated/components/power_button/reports/unit_test_spec -/generated/components/power_button/reports/unit_test_results -/generated/components/power_button/reports/coverage +/build/**/components/power_button/reports/unit_test_spec +/build/**/components/power_button/reports/unit_test_results +/build/**/components/power_button/reports/coverage ``` ```` diff --git a/components/power_signal_processing/doc/index.md b/components/power_signal_processing/doc/index.md index 4908ca88..875f2e0b 100644 --- a/components/power_signal_processing/doc/index.md +++ b/components/power_signal_processing/doc/index.md @@ -115,10 +115,11 @@ graph TD ```{toctree} :maxdepth: 1 +:glob: -/generated/components/power_signal_processing/reports/unit_test_spec -/generated/components/power_signal_processing/reports/unit_test_results -/generated/components/power_signal_processing/reports/coverage +/build/**/components/power_signal_processing/reports/unit_test_spec +/build/**/components/power_signal_processing/reports/unit_test_results +/build/**/components/power_signal_processing/reports/coverage ``` ```` diff --git a/conf.py b/conf.py index d8059fd6..eb846037 100644 --- a/conf.py +++ b/conf.py @@ -146,19 +146,21 @@ else: root_doc = "index" exclude_patterns.append("doc/component_report.md") - # The generated report pages of the CONFIGURED variant, reached through the - # stable `generated` path rather than by globbing every build directory on - # disk. spl-core's own include patterns are deliberately not used here: they - # name the real build/// paths, so the same file would - # enter the build under a second docname and every need in it would be - # parsed twice. + # The generated pages of the CONFIGURED variant, named by spl-core for the + # build it is running. This is what makes the `/build/**` globs in the report + # sections resolve to one page each rather than one per variant on disk. + # + # A stable path would be better and `generated` exists for it, but spl-core + # writes the gcovr tree at `reports/html//coverage` + # and looks its report artifacts up there too. Moving the page without moving + # those breaks the coverage link, so the stable path waits for the spl-core + # change. Only the hand-written component trees are dropped, because the + # variant rules already own those; keeping them here would parse every need + # in them a second time under a second docname. include_patterns.extend( - [ - "generated/reports/**", - "generated/components/**/reports/**", - "generated/test/**/reports/**", - "generated/components/**/__source_docs/**", - ] + pattern + for pattern in _build_config.get("include_patterns", []) + if pattern.startswith("build/") ) diff --git a/index.md b/index.md index e94af671..161f8d79 100644 --- a/index.md +++ b/index.md @@ -14,17 +14,22 @@ doc/components/index ````{if} var.build_config.target == "reports" -`generated` is the build directory of the configured variant, maintained by -`tools/variant_data.py`. Naming it directly is what lets this be one entry -rather than a glob over every variant and build type that happens to be on -disk -- a glob that only ever resolved to one page because `conf.py` narrowed -the source set behind the scenes. +The glob matches the variant-wide coverage page for any variant name and build +type, and not a component's own, because the directory in front of `reports` is +the build type there and the component name here. It resolves to exactly one +page because the source set is the configured variant's build directory. + +A fixed path would be better, and `generated` exists for it, but spl-core writes +the gcovr tree next to the build-relative page path and its report artifacts are +looked up there too -- so the stable path needs the spl-core change, and is +deferred with it. ```{toctree} :caption: Code Coverage :maxdepth: 1 +:glob: -/generated/reports/coverage +/build/**/[A-Z]*/reports/coverage ``` ```` diff --git a/test/spled_integration/doc/index.md b/test/spled_integration/doc/index.md index 3d7394c0..72fe22b6 100644 --- a/test/spled_integration/doc/index.md +++ b/test/spled_integration/doc/index.md @@ -6,10 +6,11 @@ ```{toctree} :maxdepth: 1 +:glob: -/generated/test/spled_integration/reports/unit_test_spec -/generated/test/spled_integration/reports/unit_test_results -/generated/test/spled_integration/reports/coverage +/build/**/test/spled_integration/reports/unit_test_spec +/build/**/test/spled_integration/reports/unit_test_results +/build/**/test/spled_integration/reports/coverage ``` ```` diff --git a/test/test_documentation.py b/test/test_documentation.py index 21b0d2c6..829efc6d 100644 --- a/test/test_documentation.py +++ b/test/test_documentation.py @@ -299,30 +299,27 @@ def test_the_reports_shape_reads_the_reports_cell(all_variant_data: None, tmp_pa false and the reports target would contain no reports -- with no error anywhere, and with the reports test still passing, because it asserts that the build succeeded and not that it produced anything. + + Checked on the rendered page rather than on a file list, because the fence + is what is under test: the report sections only appear when the variant data + says `reports`. """ - published = PROJECT_ROOT / "build" / "variant-data-reports.json" - published.write_text((PROJECT_ROOT / "build" / "variants" / "Disco" / "test" / "reports.json").read_text()) - docs_published = PROJECT_ROOT / "build" / "variant-data-docs.json" - docs_published.write_text((PROJECT_ROOT / "build" / "variants" / "Disco" / "test" / "docs.json").read_text()) - - # A minimal generated tree, so the report toctrees have something to resolve. - generated = PROJECT_ROOT / "generated" - reports = generated / "components" / "light_controller" / "reports" + published = { + "reports": PROJECT_ROOT / "build" / "variant-data-reports.json", + "docs": PROJECT_ROOT / "build" / "variant-data-docs.json", + } + for shape, path in published.items(): + path.write_text((PROJECT_ROOT / "build" / "variants" / "Disco" / "test" / f"{shape}.json").read_text()) + try: - reports.mkdir(parents=True, exist_ok=True) - for page in ("unit_test_spec", "unit_test_results", "coverage"): - (reports / f"{page}.rst").write_text("Sim\n===\n\nbody\n") - - reports_out = tmp_path / "reports_html" - assert _build_with_spl_core_env("reports", reports_out, tmp_path).returncode == 0 - assert (reports_out / "generated" / "components" / "light_controller" / "reports" / "coverage.html").is_file(), ( - "the reports shape did not read the reports cell" - ) - - docs_out = tmp_path / "docs_html" - assert _build_with_spl_core_env("docs", docs_out, tmp_path).returncode == 0 - assert not (docs_out / "generated").exists(), "the docs shape must not build report pages" + for shape, expect_verification in (("reports", True), ("docs", False)): + out = tmp_path / f"{shape}_html" + result = _build_with_spl_core_env(shape, out, tmp_path) + assert result.returncode == 0, result.stdout[-2000:] + page = (out / "components" / "light_controller" / "doc" / "index.html").read_text() + assert ("Verification" in page) is expect_verification, ( + f"the {shape} shape {'should' if expect_verification else 'should not'} render the verification section" + ) finally: - shutil.rmtree(generated, ignore_errors=True) - published.unlink(missing_ok=True) - docs_published.unlink(missing_ok=True) + for path in published.values(): + path.unlink(missing_ok=True) diff --git a/test/test_ubproject_config.py b/test/test_ubproject_config.py index 67c8573f..1d6d9a86 100644 --- a/test/test_ubproject_config.py +++ b/test/test_ubproject_config.py @@ -290,14 +290,28 @@ def test_generated_output_is_gated_on_the_reports_target(rules: list[dict]) -> N assert variants.interpret(tree, _variant_data("Disco", "test", "docs")) is False -def test_no_document_globs_the_build_directory() -> None: - """Toctrees name `generated/`, the configured variant's build directory. - - A `/build/**` glob matched every variant and build type on disk and only - ever resolved to one page because conf.py narrowed the source set behind - the scenes -- an invisible gate holding up a visible one. +def test_report_globs_resolve_through_the_configured_build_only() -> None: + """The report toctrees glob `/build/**`, and that is currently correct. + + A fixed path under `generated/` would be better, and the concept note asks + for one. It cannot be adopted yet: spl-core writes the gcovr tree at + `reports/html//coverage/index.html` and looks its + report artifacts up at the same place, so moving the page that links to it + without moving the tree breaks the coverage link. Deferred with the spl-core + change. + + What keeps a glob honest meanwhile is that conf.py admits exactly the + configured variant's build directory, so each pattern resolves to one page. """ - for pattern in ("components/*/doc/index.md", "components/*/*/doc/index.md", "test/*/doc/index.md"): - for path in PROJECT_ROOT.glob(pattern): - assert "/build/**" not in path.read_text(), f"{path.relative_to(PROJECT_ROOT)} still globs the build directory" - assert "/build/**" not in (PROJECT_ROOT / "index.md").read_text() + conf = (PROJECT_ROOT / "conf.py").read_text() + assert 'pattern.startswith("build/")' in conf, "conf.py must narrow the source set to the configured build" + + globbed = [ + path + for pattern in ("components/*/doc/index.md", "test/*/doc/index.md") + for path in PROJECT_ROOT.glob(pattern) + if "/build/**" in path.read_text() + ] + for path in globbed: + body = path.read_text() + assert ":glob:" in body, f"{path.relative_to(PROJECT_ROOT)} uses /build/** without :glob:" diff --git a/ubproject.toml b/ubproject.toml index 68cfccc6..8706ccc8 100644 --- a/ubproject.toml +++ b/ubproject.toml @@ -41,18 +41,17 @@ include = [ ] # Every .rst in this project is generated: the clanguru source listings and the -# report pages, under `generated`, the configured variant's build directory. -# They are indexed when the current variant data names the `reports` target and -# excluded by the variant rule below when it does not. +# report pages, which live under the configured variant's build directory and +# are therefore covered by `extend_exclude = ["build/**"]` above. ubCode +# deliberately shows none of them -- they are build output, and indexing them +# for every variant ever built is the problem that exclusion solves. # -# KNOWN LIMITATION, verified: ubCode does not descend symlinked directories -# ("directories are skipped and symlinked directories are not descended"), and -# tools/variant_data.py makes `generated` a symlink. So this include is correct -# and currently inert FOR UBCODE -- Sphinx follows the link and does read these -# pages. Point `generated` at a real directory and ubCode indexes them, gated -# exactly as intended; that is verified, not assumed. Until the generated -# documents are materialised rather than linked, the IDE simply shows no report -# pages, which costs nothing today because no needs live in them yet. +# The include below names `generated`, the stable path a future spl-core will +# let the report pages live at. It is inert twice over today: nothing is written +# there yet, and ubCode does not descend symlinked directories anyway +# ("directories are skipped and symlinked directories are not descended"), +# which is what tools/variant_data.py currently makes it. Both were verified, +# not assumed. [parse.parsers.rst] include = ["generated/**/*.rst"] From 8430cc86fd9d51c8f208a4294ba1d001801d56e6 Mon Sep 17 00:00:00 2001 From: Lucas Silber Date: Wed, 16 Sep 2026 18:10:46 +0200 Subject: [PATCH 14/19] fix: pin utf-8 on every file read, for Windows Linux and the devcontainer went green; Windows failed on one test with UnicodeDecodeError: 'charmap' codec can't decode byte 0x9d Python on Windows defaults text I/O to the ANSI code page, cp1252 on the runner, not UTF-8. test_no_document_is_rendered_through_jinja reads every document to check none of them contains a Jinja construct, and doc/cross-platform-alignment-proposal.md -- which predates this branch -- carries bytes cp1252 cannot decode. The test was right; the read was platform-dependent. Every read_text/write_text/open in the files this branch added now names encoding="utf-8" explicitly. That is the correct fix rather than skipping the file: the documents are UTF-8, and a check that walks all of them must not depend on which platform runs it. Verified with PYTHONWARNDEFAULTENCODING=1 and -W error::EncodingWarning that neither tools/variant_data.py nor the two unit-test modules perform any implicitly-encoded text I/O. (Running the whole suite under that flag also trips sphinx and spl-core internals through the subprocesses it spawns; those are not this branch's to fix.) Co-Authored-By: Claude Opus 5 (1M context) --- test/test_documentation.py | 10 +++++----- test/test_ubproject_config.py | 12 ++++++------ test/test_variant_data.py | 2 +- tools/variant_data.py | 10 +++++----- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/test/test_documentation.py b/test/test_documentation.py index 829efc6d..fbe98ade 100644 --- a/test/test_documentation.py +++ b/test/test_documentation.py @@ -155,14 +155,14 @@ def test_no_document_is_rendered_through_jinja(all_variant_data: None, tmp_path: offenders = [] for pattern in ("index.md", "doc/**/*.md", "components/**/doc/*.md", "test/*/doc/*.md"): for path in PROJECT_ROOT.glob(pattern): - text = path.read_text() + text = path.read_text(encoding="utf-8") if "{{" in text or "{%" in text: offenders.append(str(path.relative_to(PROJECT_ROOT))) assert not offenders, f"Jinja constructs in documents: {offenders}" def test_conf_py_registers_no_source_read_handler() -> None: - conf = (PROJECT_ROOT / "conf.py").read_text() + conf = (PROJECT_ROOT / "conf.py").read_text(encoding="utf-8") assert "source-read" not in conf, "the global Jinja pass must not come back" @@ -279,7 +279,7 @@ def _build_with_spl_core_env(shape: str, out_dir: Path, tmp_path: Path) -> subpr """ config_dir = tmp_path / "cfg" / shape config_dir.mkdir(parents=True, exist_ok=True) - (config_dir / "config.json").write_text("{}") + (config_dir / "config.json").write_text("{}", encoding="utf-8") env = {**os.environ, "SPHINX_BUILD_CONFIGURATION_FILE": str(config_dir / "config.json"), "VARIANT": "Disco"} env.pop("VARIANT_DATA_FILE", None) @@ -309,14 +309,14 @@ def test_the_reports_shape_reads_the_reports_cell(all_variant_data: None, tmp_pa "docs": PROJECT_ROOT / "build" / "variant-data-docs.json", } for shape, path in published.items(): - path.write_text((PROJECT_ROOT / "build" / "variants" / "Disco" / "test" / f"{shape}.json").read_text()) + path.write_text((PROJECT_ROOT / "build" / "variants" / "Disco" / "test" / f"{shape}.json").read_text(encoding="utf-8")) try: for shape, expect_verification in (("reports", True), ("docs", False)): out = tmp_path / f"{shape}_html" result = _build_with_spl_core_env(shape, out, tmp_path) assert result.returncode == 0, result.stdout[-2000:] - page = (out / "components" / "light_controller" / "doc" / "index.html").read_text() + page = (out / "components" / "light_controller" / "doc" / "index.html").read_text(encoding="utf-8") assert ("Verification" in page) is expect_verification, ( f"the {shape} shape {'should' if expect_verification else 'should not'} render the verification section" ) diff --git a/test/test_ubproject_config.py b/test/test_ubproject_config.py index 1d6d9a86..df8281d1 100644 --- a/test/test_ubproject_config.py +++ b/test/test_ubproject_config.py @@ -62,7 +62,7 @@ def test_does_not_extend_anything(project_config: dict) -> None: def test_sphinx_reads_this_exact_file() -> None: - conf = (PROJECT_ROOT / "conf.py").read_text() + conf = (PROJECT_ROOT / "conf.py").read_text(encoding="utf-8") assert 'needs_from_toml = "ubproject.toml"' in conf @@ -146,7 +146,7 @@ def test_ubcode_and_sphinx_see_the_same_documents(project_config: dict) -> None: with different document sets cannot agree about the project, which is the whole thing this configuration exists to prevent. """ - conf = (PROJECT_ROOT / "conf.py").read_text() + conf = (PROJECT_ROOT / "conf.py").read_text(encoding="utf-8") markdown_includes = project_config["parse"]["parsers"]["md"]["include"] assert markdown_includes == [ @@ -202,7 +202,7 @@ def generated_variant_data() -> None: def _variant_data(variant: str, kit: str, target: str) -> dict: - with (PROJECT_ROOT / "build" / "variants" / variant / kit / f"{target}.json").open() as handle: + with (PROJECT_ROOT / "build" / "variants" / variant / kit / f"{target}.json").open(encoding="utf-8") as handle: return json.load(handle) @@ -303,15 +303,15 @@ def test_report_globs_resolve_through_the_configured_build_only() -> None: What keeps a glob honest meanwhile is that conf.py admits exactly the configured variant's build directory, so each pattern resolves to one page. """ - conf = (PROJECT_ROOT / "conf.py").read_text() + conf = (PROJECT_ROOT / "conf.py").read_text(encoding="utf-8") assert 'pattern.startswith("build/")' in conf, "conf.py must narrow the source set to the configured build" globbed = [ path for pattern in ("components/*/doc/index.md", "test/*/doc/index.md") for path in PROJECT_ROOT.glob(pattern) - if "/build/**" in path.read_text() + if "/build/**" in path.read_text(encoding="utf-8") ] for path in globbed: - body = path.read_text() + body = path.read_text(encoding="utf-8") assert ":glob:" in body, f"{path.relative_to(PROJECT_ROOT)} uses /build/** without :glob:" diff --git a/test/test_variant_data.py b/test/test_variant_data.py index c8d961ad..50afd828 100644 --- a/test/test_variant_data.py +++ b/test/test_variant_data.py @@ -85,7 +85,7 @@ def test_component_order_follows_the_file() -> None: def _write_parts(tmp_path: Path, body: str) -> Path: parts_dir = tmp_path / "variants" / "Fake" parts_dir.mkdir(parents=True) - (parts_dir / "parts.cmake").write_text(body) + (parts_dir / "parts.cmake").write_text(body, encoding="utf-8") return tmp_path diff --git a/tools/variant_data.py b/tools/variant_data.py index 078aec8c..e67df962 100644 --- a/tools/variant_data.py +++ b/tools/variant_data.py @@ -162,7 +162,7 @@ def components(project_root: Path, variant: str, kit: str) -> list[str]: branch_kit: str | None = None depth = 0 - for number, raw_line in enumerate(parts.read_text().splitlines(), start=1): + for number, raw_line in enumerate(parts.read_text(encoding="utf-8").splitlines(), start=1): line = raw_line.split("#", 1)[0].strip() if not line: continue @@ -231,7 +231,7 @@ def cell_path(project_root: Path, variant: str, kit: str, target: str) -> Path: def write_cell(project_root: Path, variant: str, kit: str, target: str, data: dict[str, Any]) -> Path: path = cell_path(project_root, variant, kit, target) path.parent.mkdir(parents=True, exist_ok=True) - path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n") + path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n", encoding="utf-8") return path @@ -261,7 +261,7 @@ def write_pointer(project_root: Path, data: dict[str, Any], build_dir: Path | No """ pointer = project_root / "build" / "autoconf.json" pointer.parent.mkdir(parents=True, exist_ok=True) - pointer.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n") + pointer.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n", encoding="utf-8") if build_dir is None: return @@ -291,7 +291,7 @@ def write_pointer(project_root: Path, data: dict[str, Any], build_dir: Path | No def mark_generated(project_root: Path) -> None: for directory in (project_root / "build", project_root / "build" / "variants"): directory.mkdir(parents=True, exist_ok=True) - (directory / "GENERATED").write_text(GENERATED_MARKER) + (directory / "GENERATED").write_text(GENERATED_MARKER, encoding="utf-8") def _check(project_root: Path, selected: list[str]) -> int: @@ -311,7 +311,7 @@ def _check(project_root: Path, selected: list[str]) -> int: rel = path.relative_to(project_root) if not path.exists(): stale.append(f"{rel}: missing") - elif path.read_text() != expected: + elif path.read_text(encoding="utf-8") != expected: stale.append(f"{rel}: stale") if stale: From 6c4e9b424d7a7e0aa81c58edf76e032e47cea87c Mon Sep 17 00:00:00 2001 From: Lucas Silber Date: Thu, 17 Sep 2026 14:50:48 +0200 Subject: [PATCH 15/19] fix: hide the per-component report root from ubCode `ubc build html` reported five toctree.not_included warnings where Sphinx reports four. The extra one was doc/component_report.md, the root document of spl-core's PER-COMPONENT report -- a build shape ubCode never performs. conf.py drops it from every variant-wide build, so Sphinx never sees it as an orphan; ubCode had no such exclusion and indexed it. Four orphans in one reader and five in the other is the two of them disagreeing about the document set, which is the one thing this configuration exists to prevent. Both now report the same four, verified by diffing the two orphan lists. `[parse.parsers.*]` has no `exclude`, but `extend_exclude` IS honoured in parser mode -- unlike `extend_include`, which is not -- so that is the right knob. The existing test pinned extend_exclude to exactly ["build/**"], which is why it did not catch this: it asserted a list rather than a property. It now asserts that build output is excluded, and a new test ties the ubCode exclusion to the conf.py exclusion it mirrors, so the two cannot drift apart silently. The remaining four warnings are genuine orphans that predate this branch and that both readers agree on. Co-Authored-By: Claude Opus 5 (1M context) --- test/test_ubproject_config.py | 20 +++++++++++++++++++- ubproject.toml | 12 +++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/test/test_ubproject_config.py b/test/test_ubproject_config.py index df8281d1..aee5ff0f 100644 --- a/test/test_ubproject_config.py +++ b/test/test_ubproject_config.py @@ -119,7 +119,25 @@ def test_build_output_is_not_indexed(project_config: dict) -> None: set per build shape -- which is exactly the kind of divergence between readers this configuration exists to remove. """ - assert project_config["source"]["extend_exclude"] == ["build/**"] + assert "build/**" in project_config["source"]["extend_exclude"] + + +def test_the_per_component_report_root_is_hidden_from_ubcode(project_config: dict) -> None: + """conf.py excludes it from every variant build, so ubCode must too. + + doc/component_report.md is the root document of spl-core's PER-COMPONENT + report -- a build shape ubCode never performs. conf.py drops it from the + variant-wide source set, so Sphinx never sees it; ubCode indexed it anyway + and reported it as an orphan. Four orphans in one reader and five in the + other is the two of them disagreeing about the document set, which is the + one thing this configuration exists to prevent. + + `[parse.parsers.*]` has no `exclude`, but `extend_exclude` is honoured in + parser mode -- unlike `extend_include` -- so that is where it belongs. + """ + conf = (PROJECT_ROOT / "conf.py").read_text(encoding="utf-8") + assert 'exclude_patterns.append("doc/component_report.md")' in conf + assert "doc/component_report.md" in project_config["source"]["extend_exclude"] def test_extend_include_is_not_used_because_it_would_be_inert(project_config: dict, spl_core_config: dict) -> None: diff --git a/ubproject.toml b/ubproject.toml index 8706ccc8..ac11240b 100644 --- a/ubproject.toml +++ b/ubproject.toml @@ -65,7 +65,17 @@ respect_gitignore = false # narrows its source set per build shape. # `extend_include` is deliberately absent: it is ignored in parser mode, so the # generated listings are named by [parse.parsers.rst] instead. -extend_exclude = ["build/**"] +extend_exclude = [ + "build/**", + # The root document of spl-core's PER-COMPONENT report, a build shape + # ubCode never performs. conf.py excludes it from every variant-wide + # build, so Sphinx never sees it either; without this line ubCode indexed + # it and reported it as an orphan -- the two readers disagreeing about the + # document set, which is the one thing this configuration exists to + # prevent. `[parse.parsers.*]` has no `exclude`, and extend_exclude IS + # honoured in parser mode (unlike extend_include), so this is the knob. + "doc/component_report.md", +] [needs] variant_data_file = "build/autoconf.json" From 27918fefacaa225a551f134a9514125b9ae44eb3 Mon Sep 17 00:00:00 2001 From: Lucas Silber Date: Mon, 21 Sep 2026 15:16:41 +0200 Subject: [PATCH 16/19] fix: read the generated report pages only in the reports shape Review finding 2. spl-core writes unit_test_spec.rst, unit_test_results.rst and coverage.rst per component at configure time and lists them among its include patterns for BOTH build shapes. conf.py forwarded every build/ pattern unfiltered, so a docs build read all three per component -- and referenced none of them, because the fences that would have linked them are false in a docs build. Fifteen orphan warnings on Spa, for pages nobody asked to see. Measured on a real CMake docs build: 19 toc.not_included before, 4 after, the four being the genuine orphans both readers already agree on. The build shape is now derived once. It was computed for the variant data file and would have been computed again here; two derivations of one fact drift. The review notes the compiler-free gate cannot see this, because a bare build has no spl-core config.json. It can: the only thing CMake contributes is that file, and it is three lines of JSON. Two tests fabricate it -- one asserts the docs shape orphans nothing, the other that the reports shape still renders the pages, because narrowing one shape must not starve the other. The first version of that test passed with the fix reverted: it scanned stdout, and Sphinx writes warnings to stderr. Caught by reverting the fix and watching it stay green, which is the only way to find a vacuous test. It now fails with three named warnings. The same blind spot made several failure messages quote stdout alone; they include stderr now. Co-Authored-By: Claude Opus 5 (1M context) --- conf.py | 14 +++++- test/test_documentation.py | 90 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 99 insertions(+), 5 deletions(-) diff --git a/conf.py b/conf.py index eb846037..5597159b 100644 --- a/conf.py +++ b/conf.py @@ -117,9 +117,13 @@ # Without the middle step the reports build would quietly read the docs cell and # every report fence would evaluate false -- a reports target with no reports in # it, and no error anywhere. +# spl-core names the build shape by the directory holding the per-target +# configuration file it points us at. Derived once: the variant data file and +# the source set both depend on it, and two derivations of one fact drift. +_shape = "reports" if Path(os.environ.get("SPHINX_BUILD_CONFIGURATION_FILE", "")).parent.name == "reports" else "docs" + _variant_data_file = os.environ.get("VARIANT_DATA_FILE") if not _variant_data_file: - _shape = "reports" if Path(os.environ.get("SPHINX_BUILD_CONFIGURATION_FILE", "")).parent.name == "reports" else "docs" _published = Path(__file__).parent / "build" / f"variant-data-{_shape}.json" if _published.is_file(): _variant_data_file = str(_published) @@ -157,10 +161,18 @@ # change. Only the hand-written component trees are dropped, because the # variant rules already own those; keeping them here would parse every need # in them a second time under a second docname. + # + # The report pages are admitted ONLY by the reports shape. spl-core writes + # unit_test_spec.rst, unit_test_results.rst and coverage.rst at configure + # time and lists them for both shapes, so a docs build would read all three + # per component and reference none of them -- the fences that would have + # linked them are false in a docs build. That is three orphan warnings per + # component, fifteen on Spa, and no reader is better off for it. include_patterns.extend( pattern for pattern in _build_config.get("include_patterns", []) if pattern.startswith("build/") + and (_shape == "reports" or not pattern.endswith("/reports/**")) ) diff --git a/test/test_documentation.py b/test/test_documentation.py index fbe98ade..6b10e5c2 100644 --- a/test/test_documentation.py +++ b/test/test_documentation.py @@ -115,7 +115,7 @@ def test_the_built_documents_are_exactly_the_variants_components(all_variant_dat """ out = tmp_path / "html" result = _build_docs(variant, "test", "docs", out) - assert result.returncode == 0, result.stdout[-2000:] + assert result.returncode == 0, (result.stdout + result.stderr)[-2000:] components = set(variant_data.components(PROJECT_ROOT, variant, "test")) @@ -270,7 +270,7 @@ def test_ubc_excludes_exactly_what_sphinx_excludes(all_variant_data: None, varia # --- the build shape must select its own cell ------------------------------- -def _build_with_spl_core_env(shape: str, out_dir: Path, tmp_path: Path) -> subprocess.CompletedProcess: +def _build_with_spl_core_env(shape: str, out_dir: Path, tmp_path: Path, config: dict | None = None) -> subprocess.CompletedProcess: """Build the way spl-core starts Sphinx: a per-target config.json, no more. spl-core only passes VARIANT_DATA_FILE from 8.9; against the version this @@ -279,7 +279,7 @@ def _build_with_spl_core_env(shape: str, out_dir: Path, tmp_path: Path) -> subpr """ config_dir = tmp_path / "cfg" / shape config_dir.mkdir(parents=True, exist_ok=True) - (config_dir / "config.json").write_text("{}", encoding="utf-8") + (config_dir / "config.json").write_text(json.dumps(config or {}), encoding="utf-8") env = {**os.environ, "SPHINX_BUILD_CONFIGURATION_FILE": str(config_dir / "config.json"), "VARIANT": "Disco"} env.pop("VARIANT_DATA_FILE", None) @@ -315,7 +315,7 @@ def test_the_reports_shape_reads_the_reports_cell(all_variant_data: None, tmp_pa for shape, expect_verification in (("reports", True), ("docs", False)): out = tmp_path / f"{shape}_html" result = _build_with_spl_core_env(shape, out, tmp_path) - assert result.returncode == 0, result.stdout[-2000:] + assert result.returncode == 0, (result.stdout + result.stderr)[-2000:] page = (out / "components" / "light_controller" / "doc" / "index.html").read_text(encoding="utf-8") assert ("Verification" in page) is expect_verification, ( f"the {shape} shape {'should' if expect_verification else 'should not'} render the verification section" @@ -323,3 +323,85 @@ def test_the_reports_shape_reads_the_reports_cell(all_variant_data: None, tmp_pa finally: for path in published.values(): path.unlink(missing_ok=True) + + +# --- the generated report pages belong to the reports shape only ------------ + + +#: The three pages spl-core writes per component at CONFIGURE time, and lists +#: among its include patterns for BOTH build shapes. +SPL_CORE_REPORT_PAGES = ("unit_test_spec", "unit_test_results", "coverage") + + +def _fake_spl_core_report_tree(component: str) -> tuple[Path, str]: + """Write what spl-core's configure step writes, and the pattern naming it. + + Under build/, so it is gitignored and invisible to every other test. + """ + rel = Path("build") / "_fake_report_tree" / component / "reports" + root = PROJECT_ROOT / rel + root.mkdir(parents=True, exist_ok=True) + for page in SPL_CORE_REPORT_PAGES: + (root / f"{page}.rst").write_text( + f"{page}\n{'=' * len(page)}\n\nGenerated by spl-core at configure time.\n", + encoding="utf-8", + ) + return root, f"{rel.as_posix()}/**" + + +def test_the_docs_shape_does_not_read_the_generated_report_pages(all_variant_data: None, tmp_path: Path) -> None: + """spl-core lists the report pages for both shapes; only one should read them. + + They exist from configure time, and in a docs build the fences that would + link them are false -- so reading them yields three documents per component + that no toctree references. Fifteen orphan warnings on Spa, for pages nobody + asked to see. + + This needs no compiler: the only thing a CMake build contributes here is the + config.json, and that is three lines of JSON. + """ + published = PROJECT_ROOT / "build" / "variant-data-docs.json" + published.write_text( + (PROJECT_ROOT / "build" / "variants" / "Disco" / "test" / "docs.json").read_text(encoding="utf-8"), + encoding="utf-8", + ) + tree, pattern = _fake_spl_core_report_tree("components/light_controller") + + try: + out = tmp_path / "docs_html" + result = _build_with_spl_core_env("docs", out, tmp_path, {"target": "docs", "include_patterns": [pattern]}) + assert result.returncode == 0, (result.stdout + result.stderr)[-2000:] + + # Sphinx writes warnings to stderr, so scanning stdout alone made an + # earlier version of this test pass with the fix reverted. + orphaned = [ + line + for line in (result.stdout + result.stderr).splitlines() + if "toc.not_included" in line and "_fake_report_tree" in line + ] + assert not orphaned, "the docs shape read the generated report pages:\n" + "\n".join(orphaned) + finally: + shutil.rmtree(tree.parent.parent, ignore_errors=True) + published.unlink(missing_ok=True) + + +def test_the_reports_shape_still_reads_them(all_variant_data: None, tmp_path: Path) -> None: + """The other half: narrowing the docs shape must not starve the reports one.""" + published = PROJECT_ROOT / "build" / "variant-data-reports.json" + published.write_text( + (PROJECT_ROOT / "build" / "variants" / "Disco" / "test" / "reports.json").read_text(encoding="utf-8"), + encoding="utf-8", + ) + tree, pattern = _fake_spl_core_report_tree("components/light_controller") + + try: + out = tmp_path / "reports_html" + result = _build_with_spl_core_env("reports", out, tmp_path, {"target": "reports", "include_patterns": [pattern]}) + assert result.returncode == 0, (result.stdout + result.stderr)[-2000:] + + built = out / "build" / "_fake_report_tree" / "components" / "light_controller" / "reports" + for page in SPL_CORE_REPORT_PAGES: + assert (built / f"{page}.html").is_file(), f"the reports shape did not read {page}" + finally: + shutil.rmtree(tree.parent.parent, ignore_errors=True) + published.unlink(missing_ok=True) From 5afd49e1b76c77b2aae3de24db8612861642124a Mon Sep 17 00:00:00 2001 From: Lucas Silber Date: Mon, 21 Sep 2026 15:19:20 +0200 Subject: [PATCH 17/19] fix: strip the Jinja armour from generated source listings Review finding 1, taken as the in-repo option because the other one does not exist yet: no released spl-core lets the flag be turned off. 8.8.0 is the newest stable and 9.0.1rc4 the newest prerelease, and both hardcode --jinja-raw-tags. So this is not a stopgap ahead of a pin bump -- it is load-bearing for an unknown period, and it is written as a compatibility shim with its removal condition stated, not as a TODO. Every generated listing under __source_docs wraps its code-block in `{% raw %}` / `{% endraw %}`. The only thing that ever consumed those markers was the global Jinja pass this branch deletes, so in a reports build they reached the reader as two literal paragraphs per listing. The handler is a line filter, not a template render: nothing is evaluated, no brace elsewhere is touched, and hand-written documents are never seen. The markers become EMPTY LINES rather than disappearing, so the file keeps its line count -- broken source mapping was one of the reasons the Jinja pass had to go, and re-creating it in the replacement would have missed the point. Two things that forbade any source-read handler are corrected rather than worked around, because a rule the code visibly breaks is worse than no rule. The test now asserts the shape -- exactly one handler, named, scoped to __source_docs, no render_string anywhere -- and AGENTS.md states the exception and its expiry. The fixture is generated by clanguru itself, so the test tracks what spl-core actually emits rather than what it emitted once. Verified non-vacuous by unregistering the handler and watching it fail on the marker. A second markup trap caught in passing: asserting "int add" against raw HTML fails because syntax highlighting splits it across spans. The test strips tags before asserting, the same mistake that hid this bug from me a week ago. Co-Authored-By: Claude Opus 5 (1M context) --- AGENTS.md | 11 +++- conf.py | 40 +++++++++++++- test/test_documentation.py | 108 ++++++++++++++++++++++++++++++++++++- 3 files changed, 154 insertions(+), 5 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index b1ee5351..5f33be1f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -165,8 +165,15 @@ Check feature values in source code via generated `autoconf.h` header. ## Variant-Dependent Documentation -Documents never use Jinja. There is no `source-read` hook any more, and -reintroducing one is a regression, not a shortcut. +Documents never use Jinja. The global `source-read` pass that rendered every +document is gone, and bringing it back is a regression, not a shortcut. + +One narrowly scoped `source-read` handler does exist, and it is not that. +spl-core passes `--jinja-raw-tags` to clanguru, so generated source listings +under `__source_docs` wrap their code in `{% raw %}` markers that nothing else +removes. `conf.py` blanks those two lines, for those docnames only: a line +filter, not a template render. It goes away when `pyproject.toml` can pin an +spl-core that lets the flag be turned off -- no released version does yet. Everything variant-dependent is decided from **one file**: the variant data that `tools/variant_data.py` writes, exposed as `var.*`. The governing rule: diff --git a/conf.py b/conf.py index 5597159b..e65f4d93 100644 --- a/conf.py +++ b/conf.py @@ -176,8 +176,46 @@ ) +# generated source listings ################################################## +# +# COMPATIBILITY SHIM, not a return of the Jinja pass. +# +# spl-core passes --jinja-raw-tags to clanguru, so every generated listing under +# __source_docs wraps its code-block in `{% raw %}` / `{% endraw %}` lines. The +# only thing that ever consumed those markers was the global Jinja `source-read` +# hook this project deleted, so without this they render as two literal +# paragraphs on every listing page in a reports build. +# +# This is a line filter, not a template render. Nothing is evaluated; no brace +# anywhere else in the project is touched; hand-written documents are not seen +# at all. The markers are replaced by EMPTY LINES rather than removed, so the +# file keeps its line count and a warning about a generated page still points at +# the right line -- the source-mapping breakage was one of the reasons the Jinja +# pass had to go, and re-creating it here would be missing the point. +# +# REMOVE THIS once pyproject.toml pins an spl-core that lets the flag be turned +# off (`SPL_SOURCE_DOCS_JINJA_RAW_TAGS`). No released version does today: 8.8.0 +# is the newest stable and 9.0.1rc4 the newest prerelease, and both hardcode it. +# Until then this is load-bearing, not a TODO. +_JINJA_RAW_MARKERS = frozenset({"{% raw %}", "{% endraw %}"}) + + +def _strip_jinja_raw_markers(app, docname, source): + if "__source_docs/" not in f"{docname}/": + return + lines = source[0].splitlines(keepends=True) + if not any(line.strip() in _JINJA_RAW_MARKERS for line in lines): + return + source[0] = "".join( + ("\n" if line.endswith("\n") else "") if line.strip() in _JINJA_RAW_MARKERS else line + for line in lines + ) + + def setup(app): - """Apply the per-build-shape variant data file, after the TOML is read.""" + """Register the two handlers this configuration needs.""" + app.connect("source-read", _strip_jinja_raw_markers) + if not _variant_data_file: return diff --git a/test/test_documentation.py b/test/test_documentation.py index 6b10e5c2..edab7131 100644 --- a/test/test_documentation.py +++ b/test/test_documentation.py @@ -161,9 +161,24 @@ def test_no_document_is_rendered_through_jinja(all_variant_data: None, tmp_path: assert not offenders, f"Jinja constructs in documents: {offenders}" -def test_conf_py_registers_no_source_read_handler() -> None: +def test_the_only_source_read_handler_is_the_scoped_marker_strip() -> None: + """The rule is "no templating of documents", not "no handler at all". + + conf.py does register one `source-read` handler: a line filter that blanks + the `{% raw %}` markers spl-core puts in generated listings, scoped to + docnames under __source_docs. That is categorically different from the + global Jinja pass this branch removed -- nothing is evaluated and no + hand-written document is seen -- so this asserts the shape rather than the + absence of a string. + """ conf = (PROJECT_ROOT / "conf.py").read_text(encoding="utf-8") - assert "source-read" not in conf, "the global Jinja pass must not come back" + + handlers = re.findall(r'app\.connect\(\s*"source-read"\s*,\s*([A-Za-z_][A-Za-z0-9_]*)', conf) + assert handlers == ["_strip_jinja_raw_markers"], f"unexpected source-read handlers: {handlers}" + + # The pass that rendered every document is gone and stays gone. + assert "render_string" not in conf, "the global Jinja pass must not come back" + assert "__source_docs" in conf, "the handler must stay scoped to generated listings" # --- the cross-reader gate ------------------------------------------------- @@ -405,3 +420,92 @@ def test_the_reports_shape_still_reads_them(all_variant_data: None, tmp_path: Pa finally: shutil.rmtree(tree.parent.parent, ignore_errors=True) published.unlink(missing_ok=True) + + +# --- the generated listings must not show their Jinja armour ---------------- + + +def test_generated_source_listings_carry_no_jinja_markers(all_variant_data: None, tmp_path: Path) -> None: + """spl-core wraps generated listings in `{% raw %}`; nothing else unwraps them. + + The global Jinja pass used to consume those markers. It is gone, and no + released spl-core lets the flag be turned off, so without the scoped strip + in conf.py they reach the reader as two literal paragraphs on every listing + page of a reports build. + + The fixture is produced by clanguru itself rather than hand-written, so this + tracks what spl-core actually emits instead of what it emitted once. + """ + clanguru = shutil.which("clanguru") or str(Path(sys.executable).parent / "clanguru") + if not Path(clanguru).exists(): + pytest.skip("clanguru not installed") + + source = tmp_path / "sample.c" + source.write_text("int add(int a, int b) { return a + b; }\n", encoding="utf-8") + + listing_dir = PROJECT_ROOT / "build" / "_fake_source_docs" / "components" / "x" / "__source_docs" + listing_dir.mkdir(parents=True, exist_ok=True) + listing = listing_dir / "sample_c.rst" + subprocess.run( + [clanguru, "docs", "--source-file", str(source), "--output-file", str(listing), + "--format", "rst", "--jinja-raw-tags"], + check=True, capture_output=True, + ) + assert "{% raw %}" in listing.read_text(encoding="utf-8"), "fixture is not representative" + + published = PROJECT_ROOT / "build" / "variant-data-reports.json" + published.write_text( + (PROJECT_ROOT / "build" / "variants" / "Disco" / "test" / "reports.json").read_text(encoding="utf-8"), + encoding="utf-8", + ) + pattern = "build/_fake_source_docs/components/x/__source_docs/**" + + try: + out = tmp_path / "html" + result = _build_with_spl_core_env( + "reports", out, tmp_path, {"target": "reports", "include_patterns": [pattern]} + ) + assert result.returncode == 0, (result.stdout + result.stderr)[-2000:] + + page = out / "build" / "_fake_source_docs" / "components" / "x" / "__source_docs" / "sample_c.html" + assert page.is_file(), "the listing was not built" + rendered = page.read_text(encoding="utf-8") + for marker in ("{% raw %}", "{% endraw %}"): + assert marker not in rendered, f"{marker} reached the reader" + + # Syntax highlighting splits the code across spans, so assert on the + # text rather than the markup -- checking the raw HTML for a contiguous + # "int add" is how an earlier version of this test fooled itself. + text = re.sub(r"<[^>]+>", "", rendered) + assert "int" in text and "add" in text, "the strip removed more than the markers" + finally: + shutil.rmtree(listing_dir.parent.parent.parent, ignore_errors=True) + published.unlink(missing_ok=True) + + +def test_the_strip_preserves_line_numbers(all_variant_data: None) -> None: + """Markers become blank lines, not nothing. + + Removing them would shift every line after them, so a warning about a + generated page would point at the wrong line -- which is one of the reasons + the global Jinja pass had to go. Re-creating it in the replacement would be + missing the point. + """ + spec = __import__("importlib.util", fromlist=["util"]).spec_from_file_location( + "spled_conf", PROJECT_ROOT / "conf.py" + ) + module = __import__("importlib.util", fromlist=["util"]).module_from_spec(spec) + spec.loader.exec_module(module) + + before = "a\n{% raw %}\n.. code-block:: c\n\n int x;\n{% endraw %}\n" + source = [before] + module._strip_jinja_raw_markers(None, "components/x/__source_docs/y", source) + + assert source[0].count("\n") == before.count("\n"), "line count changed" + assert "{% raw %}" not in source[0] and "{% endraw %}" not in source[0] + assert ".. code-block:: c" in source[0] and "int x;" in source[0] + + # A hand-written document is never touched, whatever it contains. + untouched = ["{% raw %}\nkeep me\n"] + module._strip_jinja_raw_markers(None, "doc/components/index", untouched) + assert untouched[0] == "{% raw %}\nkeep me\n" From a5c45c1e7918fc22dc40e4e0ba94855e9448014f Mon Sep 17 00:00:00 2001 From: Lucas Silber Date: Mon, 21 Sep 2026 15:27:59 +0200 Subject: [PATCH 18/19] fix: stop copying the binary directory, and run ubc in CI Review findings 3 and 4. The Windows symlink fallback copied the whole CMake binary directory. Wrong three times over: it ran at CONFIGURE time, when the reports the path exists for have not been generated, so it only ever copied objects, binaries and CMakeFiles; dirs_exist_ok meant a later configure never cleared an earlier one's leftovers, so the copy went stale rather than obviously wrong; and nothing reads generated/ yet, so the cost bought nothing. It writes a marker explaining itself instead, and the fallback now has a test rather than leaving it to the one platform nobody develops on. ubc now runs in the documentation job, via useblocks/ubc-action, which puts the binary on PATH where the tests already look for it. CI_REQUIRE_UBC turns a missing binary into one clear failure instead of eleven skips: the parity tests are the only check that the two readers agree, and a check that silently does not run is worse than none, because the green tick claims it did. The other tests still skip rather than erroring on a missing binary, so the reason is stated once. Verified in all three modes -- optional and present, required and present, required and missing. Correcting the plan on one point: I recorded this as blocked on UBCODE_LICENSE_KEY / UBCODE_LICENSE_USER secrets. It is not. `ubc license show` reports no licence on this machine and there is no licence file, yet `ubc check` and `ubc build html` both work -- every local run in this branch was unlicensed. The action's licence inputs are wired anyway, so adding the secrets later changes nothing, but nothing waits on them. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/ci.yml | 14 ++++++++++++ test/test_documentation.py | 23 ++++++++++++++++++++ test/test_variant_data.py | 44 ++++++++++++++++++++++++++++++++++++++ tools/variant_data.py | 28 ++++++++++++++++++++---- 4 files changed, 105 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bf0b6466..0690f69e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -71,7 +71,21 @@ jobs: poetry config virtualenvs.in-project true poetry install --no-root + # `ubc` is the second reader. Without it the parity tests skip, and + # a guarantee that only holds on a developer machine is not a + # guarantee -- so the gate installs it and CI_REQUIRE_UBC turns a + # skip into a failure. No licence is needed for `check`; the inputs + # are passed anyway so that adding the secrets later just works. + - name: Install ubc + uses: useblocks/ubc-action@0.1 + with: + version: "0.35.0" + license-key: ${{ secrets.UBCODE_LICENSE_KEY }} + license-user: ${{ secrets.UBCODE_LICENSE_USER }} + - name: Documentation gate + env: + CI_REQUIRE_UBC: "1" run: poetry run pytest -m "docs and ${{ needs.determine-gate.outputs.marker }}" test-on-windows: diff --git a/test/test_documentation.py b/test/test_documentation.py index edab7131..cb3b3c7b 100644 --- a/test/test_documentation.py +++ b/test/test_documentation.py @@ -207,6 +207,29 @@ def _find_ubc() -> str | None: needs_ubc = pytest.mark.skipif(UBC is None, reason="ubc not found; set UBC or put it on PATH") +def test_ubc_is_available_where_it_is_required() -> None: + """A skipped parity test must not be able to pass the gate by omission. + + The tests below are the only check that the two readers agree, and they + skip when ubc is absent -- which it is on any machine that has not + installed it. A check that silently does not run is worse than no check, + because the green tick claims it did. + + So the CI job that installs ubc sets CI_REQUIRE_UBC, and this turns the + skip into one clear failure. The other tests still skip rather than + erroring on a missing binary, so the reason is stated once. + """ + if not os.environ.get("CI_REQUIRE_UBC"): + pytest.skip("CI_REQUIRE_UBC is not set; ubc is optional here") + + assert UBC is not None, ( + "CI_REQUIRE_UBC is set but ubc was not found on PATH, in $UBC, or in the " + "VS Code extension directory. The parity tests would have skipped and the " + "documentation gate would have passed without checking that ubCode and " + "Sphinx agree, which is the property it exists for." + ) + + def _ubc_check(variant: str, kit: str, target: str) -> list[dict]: result = subprocess.run( [ diff --git a/test/test_variant_data.py b/test/test_variant_data.py index 50afd828..aa4451c0 100644 --- a/test/test_variant_data.py +++ b/test/test_variant_data.py @@ -197,3 +197,47 @@ def test_every_cell_carries_the_whole_contract(variant: str, kit: str, target: s # It has to survive the round trip to the file both tools read. assert json.loads(json.dumps(data)) == data + + +# --- the current-variant pointer ------------------------------------------- + + +def test_the_pointer_is_a_symlink_where_the_platform_allows_one(tmp_path: Path) -> None: + build_dir = tmp_path / "build" / "V" / "test" / "Debug" + build_dir.mkdir(parents=True) + (build_dir / "payload.txt").write_text("x", encoding="utf-8") + + variant_data.write_pointer(tmp_path, {"features": {}, "build_config": {}}, build_dir) + + generated = tmp_path / "generated" + assert generated.is_symlink() + assert (generated / "payload.txt").is_file() + + +def test_a_refused_symlink_copies_nothing(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """The fallback must not duplicate the CMake binary directory. + + It used to `copytree` the whole thing at CONFIGURE time -- objects, + binaries, CMakeFiles -- before the reports it exists for had been generated, + and `dirs_exist_ok` meant a later configure never cleared an earlier one's + leftovers. Nothing reads `generated/` yet, so it bought nothing at all. + + Only Windows without Developer Mode takes this path, which is why it needs a + test rather than the platform nobody develops on finding out. + """ + build_dir = tmp_path / "build" / "V" / "test" / "Debug" + (build_dir / "CMakeFiles").mkdir(parents=True) + (build_dir / "CMakeFiles" / "huge.o").write_text("x" * 1000, encoding="utf-8") + + def refuse(*args, **kwargs): + raise OSError("symlinks not permitted") + + monkeypatch.setattr(Path, "symlink_to", refuse) + + variant_data.write_pointer(tmp_path, {"features": {}, "build_config": {}}, build_dir) + + generated = tmp_path / "generated" + assert generated.is_dir() and not generated.is_symlink() + assert (generated / "NO_SYMLINK").is_file(), "the fallback must explain itself" + assert not (generated / "CMakeFiles").exists(), "the fallback copied the binary directory" + assert [p.name for p in generated.iterdir()] == ["NO_SYMLINK"] diff --git a/tools/variant_data.py b/tools/variant_data.py index e67df962..2a6177f4 100644 --- a/tools/variant_data.py +++ b/tools/variant_data.py @@ -274,10 +274,30 @@ def write_pointer(project_root: Path, data: dict[str, Any], build_dir: Path | No try: current.symlink_to(build_dir.resolve(), target_is_directory=True) except OSError: - # Windows without Developer Mode refuses a symlink. A copy keeps the - # path stable at the cost of duplicating the tree; the reports target - # rewrites its output wholesale anyway. - shutil.copytree(build_dir, current, dirs_exist_ok=True) + # Windows without Developer Mode refuses a symlink. + # + # This used to copy the tree. That was wrong three times over: it ran at + # CONFIGURE time, when the reports the path exists for have not been + # generated yet, so it only ever copied object files, binaries and + # CMakeFiles; `dirs_exist_ok` meant a later configure never removed what + # an earlier one left, so the copy grew stale rather than wrong-and- + # obvious; and nothing reads `generated/` yet anyway, so the whole cost + # bought nothing. + # + # A marker instead. It keeps the path present and self-explanatory, and + # when the path does acquire a consumer this is where the decision -- + # copy the documents, or require Developer Mode -- gets made with the + # facts of that day. + current.mkdir(parents=True, exist_ok=True) + (current / "NO_SYMLINK").write_text( + "This platform refused a symlink, so `generated` is a plain directory.\n" + f"It would have pointed at: {build_dir.resolve()}\n" + "\n" + "Nothing reads `generated` yet, so nothing is copied here. Enable\n" + "Developer Mode on Windows to get the symlink, or see\n" + "tools/variant_data.py if this path has since gained a consumer.\n", + encoding="utf-8", + ) #: Dropped into every directory this script owns. `build/` itself gets one too, From bd851d1ba73daac4ea923aabd61512456238cab4 Mon Sep 17 00:00:00 2001 From: Lucas Silber Date: Mon, 21 Sep 2026 16:31:58 +0200 Subject: [PATCH 19/19] fix: stop walking the symlink, and guard the path that replaces it Review findings 5, 6, 7 and 8. Sphinx walks with followlinks=True, so `generated` -- a symlink to the configured variant's build directory -- was descended in full, under a second set of paths that the `build/modules` and `build/deps` exclusions never match. Excluding it prunes the walk: get_matching_files applies exclude_patterns to directories and writes the result back to `dirs`, so the descent stops rather than the files merely being filtered. Measured 0.09s -> 0.04s on a 301-file build directory, and it scales with the tree. That makes the forward-looking `generated/` configuration definitively inert for Sphinx as well as for ubCode, which is the right state but a fragile one: when spl-core writes the gcovr tree relative to the page and `generated/` becomes real, the `build/` forwarding has to be dropped in the SAME change or every report page is discovered under two docnames. That coupling was a comment, which is read once. It is now a test that fails when only half the change is done, verified by making the wrong half and watching it fire. The variant-selection task no longer passes a target: the pointer is always the docs cell. Choosing `reports` there opened every report fence in the IDE, each globbing into build/**, which ubCode excludes -- an unmatched glob per component. CMake writes the docs cell for the same reason. The target choice stays on the build task, where it changes what is produced rather than what is linted. AGENTS.md claimed documents name `generated/` instead of globbing `/build/**`. All seven still glob. It now describes the deferral, why it cannot be lifted from this repository, and what has to move together when it is. Co-Authored-By: Claude Opus 5 (1M context) --- .vscode/tasks.json | 8 +++++++- AGENTS.md | 21 +++++++++++++++++---- conf.py | 13 +++++++++++++ test/test_ubproject_config.py | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 70 insertions(+), 5 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 144eda9c..c02c03ce 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -31,7 +31,13 @@ "label": "Select documentation variant", "detail": "Point the docs and ubCode at one variant -- no CMake, no compiler", "type": "shell", - "command": "${command:python.interpreterPath} tools/variant_data.py --variant ${input:variant} --kit ${input:buildKit} --target ${input:docsTarget} --current", + // Always the docs cell. Choosing `reports` here would open every + // report fence in the IDE, and each one globs into build/**, which + // ubCode excludes -- so every component would report an unmatched + // glob. CMake writes the docs cell as the pointer for the same + // reason. The target choice belongs to the build task below, which + // is where it changes what is produced rather than what is linted. + "command": "${command:python.interpreterPath} tools/variant_data.py --variant ${input:variant} --kit ${input:buildKit} --target docs --current", "problemMatcher": [], "presentation": { "reveal": "silent", diff --git a/AGENTS.md b/AGENTS.md index 5f33be1f..28fbc68a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -291,10 +291,23 @@ Nothing is generated, no loop is edited, and nothing under `build/` is touched. assistant.** The editor is configured to refuse it (`files.readonlyInclude`) and `build/variants/GENERATED` says so on disk. -`generated/` is the configured variant's build directory. Documents name it -directly instead of globbing `/build/**`; a glob matched every variant and build -type on disk and only ever resolved to one page because `conf.py` narrowed the -source set behind the scenes. +`generated/` is the configured variant's build directory, and today **nothing +reads it**. The report toctrees still glob `/build/**`, and `conf.py` narrows +the source set to the configured build so each glob resolves to one page. + +That is a deferral, not the end state. A fixed path under `generated/` would be +better, and the configuration for it is already in place -- the rst parser +include and the `generated/...` entries in every variant rule. It cannot be +switched on from this repository: spl-core writes the gcovr tree at +`reports/html//coverage/index.html` and looks its +report artifacts up in the same place, so moving the page that links to it +without moving the tree breaks every coverage link. + +When spl-core does write the tree relative to the page, the `build/` forwarding +in `conf.py` and the `generated` entry in its `exclude_patterns` have to go in +the *same* change, or Sphinx discovers every report page under both names. +`test_generated_and_build_discovery_are_never_both_live` fails if only half of +that is done. Regenerate without a compiler — KConfig is pure Python, and CMake's top-level `project()` call demands a C toolchain before it will configure at all: diff --git a/conf.py b/conf.py index e65f4d93..9a8abf05 100644 --- a/conf.py +++ b/conf.py @@ -29,6 +29,19 @@ ".venv", ".git", "**/test_results.rst", # We renamed this file, but nobody deletes it. + # `generated` is a symlink to the configured variant's build directory, and + # Sphinx walks with followlinks=True -- so without this it descends the + # whole build tree a second time, under a second set of paths that the + # `build/...` exclusions above do not match. Excluding it prunes the walk + # (get_matching_files applies exclude_patterns to directories, not just + # files), which halves the scan on a small build directory and more on a + # real one. + # + # It also means Sphinx CANNOT discover anything through `generated`, which + # is the invariant test_ubproject_config.py guards: the report pages are + # discovered through spl-core's `build/` patterns instead, and exactly one + # of those two routes may ever be live or every page exists twice. + "generated", ] # The 150% source set: every hand-written document the product line has. diff --git a/test/test_ubproject_config.py b/test/test_ubproject_config.py index aee5ff0f..94171e78 100644 --- a/test/test_ubproject_config.py +++ b/test/test_ubproject_config.py @@ -333,3 +333,36 @@ def test_report_globs_resolve_through_the_configured_build_only() -> None: for path in globbed: body = path.read_text(encoding="utf-8") assert ":glob:" in body, f"{path.relative_to(PROJECT_ROOT)} uses /build/** without :glob:" + + +def test_generated_and_build_discovery_are_never_both_live(project_config: dict) -> None: + """Exactly one route to the generated report pages, or they exist twice. + + Two routes are configured, on purpose, and only one is switched on: + + * Sphinx reaches them through spl-core's `build/...` include patterns, + which conf.py forwards for the reports shape. Live today. + * `generated/`, the stable path, is named by the rst parser and by every + component's variant rule. Inert today -- conf.py excludes `generated` + from the Sphinx walk, and ubCode does not descend symlinks. + + The forward-looking half stays because it is the shape we want once + spl-core writes the gcovr tree relative to the page. But when that lands, + dropping the `build/` forwarding has to happen in the SAME change, or + Sphinx discovers every report page under both names. A comment saying so + would be read once; this fails the build instead. + """ + conf = (PROJECT_ROOT / "conf.py").read_text(encoding="utf-8") + + sphinx_walks_generated = '"generated",' not in conf + forwards_build_patterns = 'pattern.startswith("build/")' in conf + + assert not (sphinx_walks_generated and forwards_build_patterns), ( + "conf.py both lets Sphinx walk `generated/` and forwards spl-core's " + "`build/` patterns. Every generated report page is then discovered " + "twice, under two docnames. Drop the `build/` forwarding in the same " + "change that makes `generated/` real." + ) + + # And the forward-looking configuration is still there to be switched on. + assert project_config["parse"]["parsers"]["rst"]["include"] == ["generated/**/*.rst"]