diff --git a/.github/workflows/build-guidelines.yml b/.github/workflows/build-guidelines.yml
index c5957eaa..484a0615 100644
--- a/.github/workflows/build-guidelines.yml
+++ b/.github/workflows/build-guidelines.yml
@@ -12,18 +12,18 @@ on:
- "main"
# workflow_call trigger to make this workflow reusable
workflow_call:
- # You can add inputs here if needed in the future
- # inputs:
- # example_input:
- # required: false
- # type: string
- # default: 'default value'
+
jobs:
+ # Check Rust examples compile before building docs
+ check_rust_examples:
+ uses: ./.github/workflows/check-rust-examples.yml
+
build:
+ needs: check_rust_examples
runs-on: ubuntu-latest
steps:
- name: Checkout repository
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v6
- name: Build documentation
@@ -78,9 +78,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Install typos
run: cargo install typos-cli
- name: Check for typos
run: typos
-
diff --git a/.github/workflows/check-rust-examples.yml b/.github/workflows/check-rust-examples.yml
new file mode 100644
index 00000000..7e045123
--- /dev/null
+++ b/.github/workflows/check-rust-examples.yml
@@ -0,0 +1,444 @@
+# SPDX-License-Identifier: MIT OR Apache-2.0
+# SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors
+
+name: Check Rust Examples
+
+# This workflow is designed to be called from build-guidelines.yml
+# It does not have its own triggers to avoid duplicate runs
+on:
+ workflow_call:
+
+jobs:
+ # Test Rust examples across multiple toolchains (tests/ directory only)
+ test-examples:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ # Test with stable (latest) - all examples except nightly-only should pass
+ - rust: stable
+ name: Stable (latest)
+ # Test with an older version to verify version skipping works correctly
+ - rust: '1.75.0'
+ name: '1.75.0'
+ # Test with nightly for channel-specific examples
+ - rust: nightly
+ name: Nightly
+
+ name: Test Examples (${{ matrix.name }})
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Install Rust toolchain (${{ matrix.name }})
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ toolchain: ${{ matrix.rust }}
+
+ - name: Display Rust version
+ run: |
+ rustc --version
+ cargo --version
+
+ - name: Install uv
+ uses: astral-sh/setup-uv@v6
+
+ - name: Extract and test Rust examples
+ id: test-examples
+ run: |
+ mkdir -p build/examples
+
+ # Run combined extract and test - only test the test examples, not src/
+ set -o pipefail # Ensure pipe failures are captured
+
+ uv run python scripts/extract_rust_examples.py \
+ --test \
+ --src-dir tests/rust-examples \
+ --prelude src/examples_prelude.rs \
+ --json build/examples/results.json \
+ --fail-on-error \
+ --verbose \
+ 2>&1 | tee build/examples/test_output.log
+
+ EXIT_CODE=${PIPESTATUS[0]}
+
+ # Generate summary
+ echo "## ๐งช Rust Examples Test Results (${{ matrix.name }})" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "**Rust version:** \`$(rustc --version)\`" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+
+ if [ -f build/examples/results.json ]; then
+ TOTAL=$(jq '.total' build/examples/results.json)
+ PASSED=$(jq '.passed' build/examples/results.json)
+ FAILED=$(jq '.failed' build/examples/results.json)
+ SKIPPED=$(jq '.skipped // 0' build/examples/results.json)
+
+ if [ "$FAILED" -eq 0 ]; then
+ if [ "$SKIPPED" -gt 0 ]; then
+ echo "โ
**$PASSED of $TOTAL examples passed, $SKIPPED skipped**" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "โ
**All $TOTAL examples passed!**" >> $GITHUB_STEP_SUMMARY
+ fi
+ else
+ echo "โ **$FAILED of $TOTAL examples failed** ($SKIPPED skipped)" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "### Failed Examples" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+
+ jq -r '.results[] | select(.passed == false) | "- **\(.example.source_file):\(.example.line_number)**: \(.error_message)"' \
+ build/examples/results.json >> $GITHUB_STEP_SUMMARY
+ fi
+
+ # Show skipped examples if any
+ if [ "$SKIPPED" -gt 0 ]; then
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "### Skipped Examples" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+
+ jq -r '.results[] | select(.skipped == true) | "- **\(.example.source_file):\(.example.line_number)**: \(.skip_reason)"' \
+ build/examples/results.json >> $GITHUB_STEP_SUMMARY 2>/dev/null || true
+ fi
+ fi
+
+ exit $EXIT_CODE
+
+ - name: Compare against expected results
+ if: always()
+ run: |
+ echo "## ๐ Expected Results Comparison (${{ matrix.name }})" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+
+ if [ ! -f build/examples/results.json ] || [ ! -f tests/rust-examples/expected-results.json ]; then
+ echo "โ ๏ธ Missing results files for comparison" >> $GITHUB_STEP_SUMMARY
+ exit 0
+ fi
+
+ # Determine which expected key to use based on matrix
+ case "${{ matrix.rust }}" in
+ stable) EXPECTED_KEY="stable" ;;
+ nightly) EXPECTED_KEY="nightly" ;;
+ *) EXPECTED_KEY="${{ matrix.rust }}" ;;
+ esac
+
+ MISMATCHES=0
+
+ # Compare each example against expected results
+ while read -r expected_line; do
+ LINE=$(echo "$expected_line" | jq -r '.line')
+ NAME=$(echo "$expected_line" | jq -r '.name')
+ EXPECTED=$(echo "$expected_line" | jq -r ".expected[\"$EXPECTED_KEY\"] // \"pass\"")
+
+ # Find actual result for this line
+ ACTUAL_RESULT=$(jq -r --argjson line "$LINE" '.results[] | select(.example.line_number == $line)' build/examples/results.json)
+
+ if [ -z "$ACTUAL_RESULT" ]; then
+ echo "โ ๏ธ Line $LINE ($NAME): Not found in results" >> $GITHUB_STEP_SUMMARY
+ MISMATCHES=$((MISMATCHES + 1))
+ continue
+ fi
+
+ PASSED=$(echo "$ACTUAL_RESULT" | jq -r '.passed')
+ SKIPPED=$(echo "$ACTUAL_RESULT" | jq -r '.skipped // false')
+
+ if [ "$SKIPPED" = "true" ]; then
+ ACTUAL="skip"
+ elif [ "$PASSED" = "true" ]; then
+ ACTUAL="pass"
+ else
+ ACTUAL="fail"
+ fi
+
+ if [ "$ACTUAL" != "$EXPECTED" ]; then
+ echo "โ Line $LINE ($NAME): Expected **$EXPECTED**, got **$ACTUAL**" >> $GITHUB_STEP_SUMMARY
+ MISMATCHES=$((MISMATCHES + 1))
+ fi
+ done < <(jq -c '.examples[]' tests/rust-examples/expected-results.json)
+
+ if [ "$MISMATCHES" -eq 0 ]; then
+ echo "โ
All results match expected values" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "**$MISMATCHES mismatches found**" >> $GITHUB_STEP_SUMMARY
+ exit 1
+ fi
+
+ - name: Upload example test artifacts
+ uses: actions/upload-artifact@v4
+ if: always()
+ with:
+ name: rust-examples-results-${{ matrix.rust }}
+ path: |
+ build/examples/results.json
+ build/examples/test_output.log
+ build/examples/examples.json
+ retention-days: 7
+
+ - name: Annotate failures in PR
+ if: failure() && github.event_name == 'pull_request'
+ run: |
+ if [ -f build/examples/results.json ]; then
+ jq -r '.results[] | select(.passed == false) |
+ "::error file=\(.example.source_file),line=\(.example.line_number)::\(.error_message)"' \
+ build/examples/results.json
+ fi
+
+ # Analyze what toolchains are needed for guideline examples
+ analyze-requirements:
+ runs-on: ubuntu-latest
+ outputs:
+ needs_nightly: ${{ steps.analyze.outputs.needs_nightly }}
+ nightly_count: ${{ steps.analyze.outputs.nightly_count }}
+ version_requirements: ${{ steps.analyze.outputs.version_requirements }}
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Install uv
+ uses: astral-sh/setup-uv@v6
+
+ - name: Analyze toolchain requirements
+ id: analyze
+ run: |
+ # Get requirements as JSON (script outputs clean JSON to stdout)
+ REQUIREMENTS=$(uv run python scripts/extract_rust_examples.py \
+ --list-requirements \
+ --src-dir src/coding-guidelines)
+
+ # Extract summary info
+ NEEDS_NIGHTLY=$(echo "$REQUIREMENTS" | jq '.summary.needs_nightly > 0')
+ NIGHTLY_COUNT=$(echo "$REQUIREMENTS" | jq '.summary.needs_nightly')
+ VERSION_REQS=$(echo "$REQUIREMENTS" | jq -c '.versions | keys')
+
+ echo "needs_nightly=$NEEDS_NIGHTLY" >> $GITHUB_OUTPUT
+ echo "nightly_count=$NIGHTLY_COUNT" >> $GITHUB_OUTPUT
+ echo "version_requirements=$VERSION_REQS" >> $GITHUB_OUTPUT
+
+ # Add to summary
+ echo "## ๐ Toolchain Requirements Analysis" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "$REQUIREMENTS" | jq -r '"- **Total examples:** \(.summary.total)"' >> $GITHUB_STEP_SUMMARY
+ echo "$REQUIREMENTS" | jq -r '"- **Default (stable, no version req):** \(.summary.default_only)"' >> $GITHUB_STEP_SUMMARY
+ echo "$REQUIREMENTS" | jq -r '"- **Needs nightly:** \(.summary.needs_nightly)"' >> $GITHUB_STEP_SUMMARY
+ echo "$REQUIREMENTS" | jq -r '"- **Needs specific version:** \(.summary.needs_specific_version)"' >> $GITHUB_STEP_SUMMARY
+
+ if [ "$NIGHTLY_COUNT" -gt 0 ]; then
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "### Nightly Examples" >> $GITHUB_STEP_SUMMARY
+ echo "$REQUIREMENTS" | jq -r '.channels.nightly[] | "- \(.file):\(.line)"' >> $GITHUB_STEP_SUMMARY
+ fi
+
+ # Test guideline examples that work on stable with no version requirements
+ test-guidelines-stable:
+ runs-on: ubuntu-latest
+ needs: [test-examples, analyze-requirements]
+ name: Test Guidelines (Stable)
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Install Rust toolchain (stable)
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ toolchain: stable
+
+ - name: Display Rust version
+ run: |
+ rustc --version
+ cargo --version
+
+ - name: Install uv
+ uses: astral-sh/setup-uv@v6
+
+ - name: Test guideline examples (stable)
+ id: test-guidelines
+ run: |
+ mkdir -p build/examples
+
+ set -o pipefail
+
+ # Test all examples - those requiring newer versions or nightly will be skipped
+ uv run python scripts/extract_rust_examples.py \
+ --test \
+ --src-dir src/coding-guidelines \
+ --prelude src/examples_prelude.rs \
+ --json build/examples/guideline-results.json \
+ --fail-on-error \
+ --verbose \
+ 2>&1 | tee build/examples/guideline_test_output.log
+
+ EXIT_CODE=${PIPESTATUS[0]}
+
+ # Generate summary
+ echo "## ๐ Guideline Examples Test Results (Stable)" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "**Rust version:** \`$(rustc --version)\`" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+
+ if [ -f build/examples/guideline-results.json ]; then
+ TOTAL=$(jq '.total' build/examples/guideline-results.json)
+ PASSED=$(jq '.passed' build/examples/guideline-results.json)
+ FAILED=$(jq '.failed' build/examples/guideline-results.json)
+ SKIPPED=$(jq '.skipped // 0' build/examples/guideline-results.json)
+
+ if [ "$FAILED" -eq 0 ]; then
+ if [ "$SKIPPED" -gt 0 ]; then
+ echo "โ
**$PASSED of $TOTAL guideline examples passed, $SKIPPED skipped**" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "โ
**All $TOTAL guideline examples passed!**" >> $GITHUB_STEP_SUMMARY
+ fi
+ else
+ echo "โ **$FAILED of $TOTAL guideline examples failed** ($SKIPPED skipped)" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "### Failed Examples" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+
+ jq -r '.results[] | select(.passed == false) | "- **\(.example.source_file):\(.example.line_number)**: \(.error_message)"' \
+ build/examples/guideline-results.json >> $GITHUB_STEP_SUMMARY
+ fi
+
+ if [ "$SKIPPED" -gt 0 ]; then
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "### Skipped Examples" >> $GITHUB_STEP_SUMMARY
+ echo "_These require nightly or a specific Rust version and are tested separately._" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+
+ jq -r '.results[] | select(.skipped == true) | "- **\(.example.source_file):\(.example.line_number)**: \(.skip_reason)"' \
+ build/examples/guideline-results.json >> $GITHUB_STEP_SUMMARY 2>/dev/null || true
+ fi
+ fi
+
+ exit $EXIT_CODE
+
+ - name: Upload guideline test artifacts
+ uses: actions/upload-artifact@v4
+ if: always()
+ with:
+ name: guideline-examples-results-stable
+ path: |
+ build/examples/guideline-results.json
+ build/examples/guideline_test_output.log
+ retention-days: 7
+
+ - name: Annotate failures in PR
+ if: failure() && github.event_name == 'pull_request'
+ run: |
+ if [ -f build/examples/guideline-results.json ]; then
+ jq -r '.results[] | select(.passed == false) |
+ "::error file=\(.example.source_file),line=\(.example.line_number)::\(.error_message)"' \
+ build/examples/guideline-results.json
+ fi
+
+ # Test guideline examples that require nightly (only if there are any)
+ test-guidelines-nightly:
+ runs-on: ubuntu-latest
+ needs: [test-examples, analyze-requirements]
+ if: needs.analyze-requirements.outputs.needs_nightly == 'true'
+ name: Test Guidelines (Nightly)
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Install Rust toolchain (nightly)
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ toolchain: nightly
+
+ - name: Display Rust version
+ run: |
+ rustc --version
+ cargo --version
+
+ - name: Install uv
+ uses: astral-sh/setup-uv@v6
+
+ - name: Test nightly-only guideline examples
+ id: test-guidelines-nightly
+ run: |
+ mkdir -p build/examples
+
+ set -o pipefail
+
+ # Only test examples that require nightly
+ uv run python scripts/extract_rust_examples.py \
+ --test \
+ --src-dir src/coding-guidelines \
+ --prelude src/examples_prelude.rs \
+ --filter-channel nightly \
+ --json build/examples/guideline-nightly-results.json \
+ --fail-on-error \
+ --verbose \
+ 2>&1 | tee build/examples/guideline_nightly_test_output.log
+
+ EXIT_CODE=${PIPESTATUS[0]}
+
+ # Generate summary
+ echo "## ๐ Guideline Examples Test Results (Nightly)" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "**Rust version:** \`$(rustc --version)\`" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+
+ if [ -f build/examples/guideline-nightly-results.json ]; then
+ TOTAL=$(jq '.total' build/examples/guideline-nightly-results.json)
+ PASSED=$(jq '.passed' build/examples/guideline-nightly-results.json)
+ FAILED=$(jq '.failed' build/examples/guideline-nightly-results.json)
+
+ if [ "$FAILED" -eq 0 ]; then
+ echo "โ
**All $TOTAL nightly examples passed!**" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "โ **$FAILED of $TOTAL nightly examples failed**" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "### Failed Examples" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+
+ jq -r '.results[] | select(.passed == false) | "- **\(.example.source_file):\(.example.line_number)**: \(.error_message)"' \
+ build/examples/guideline-nightly-results.json >> $GITHUB_STEP_SUMMARY
+ fi
+ fi
+
+ exit $EXIT_CODE
+
+ - name: Upload nightly test artifacts
+ uses: actions/upload-artifact@v4
+ if: always()
+ with:
+ name: guideline-examples-results-nightly
+ path: |
+ build/examples/guideline-nightly-results.json
+ build/examples/guideline_nightly_test_output.log
+ retention-days: 7
+
+ # Validate rustdoc attribute consistency
+ check-rustdoc-format:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Check for legacy code blocks
+ run: |
+ echo "## ๐ Rustdoc Format Check" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+
+ LEGACY_COUNT=0
+ LEGACY_FILES=""
+
+ for file in $(find src/coding-guidelines -name "*.rst" 2>/dev/null); do
+ COUNT=$(grep -c "^\s*\.\. code-block:: rust" "$file" 2>/dev/null || echo 0)
+ if [ "$COUNT" -gt 0 ]; then
+ LEGACY_FILES="$LEGACY_FILES\n- $file: $COUNT occurrences"
+ LEGACY_COUNT=$((LEGACY_COUNT + COUNT))
+ fi
+ done
+
+ if [ "$LEGACY_COUNT" -eq 0 ]; then
+ echo "โ
All Rust code examples use the rust-example:: directive" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "โ ๏ธ **Found $LEGACY_COUNT code blocks using legacy format**" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "Files with legacy code-block:: rust:" >> $GITHUB_STEP_SUMMARY
+ echo -e "$LEGACY_FILES" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "Consider migrating with: \`uv run python scripts/migrate_rust_examples.py\`" >> $GITHUB_STEP_SUMMARY
+ fi
diff --git a/exts/coding_guidelines/__init__.py b/exts/coding_guidelines/__init__.py
index 27f8a87d..2aab5686 100644
--- a/exts/coding_guidelines/__init__.py
+++ b/exts/coding_guidelines/__init__.py
@@ -8,6 +8,7 @@
fls_checks,
fls_linking,
guidelines_checks,
+ rust_examples,
std_role,
write_guidelines_ids,
)
@@ -74,6 +75,9 @@ def setup(app):
logger.setLevel(logging.INFO)
common.disable_tqdm = True
+ # Set up rust_examples extension
+ rust_examples.setup(app)
+
app.connect("env-check-consistency", guidelines_checks.validate_required_fields)
app.connect("env-check-consistency", fls_checks.check_fls)
app.connect("build-finished", write_guidelines_ids.build_finished)
diff --git a/exts/coding_guidelines/rust_examples.py b/exts/coding_guidelines/rust_examples.py
new file mode 100644
index 00000000..7e30d7c9
--- /dev/null
+++ b/exts/coding_guidelines/rust_examples.py
@@ -0,0 +1,343 @@
+# SPDX-License-Identifier: MIT OR Apache-2.0
+# SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors
+
+"""
+Sphinx extension providing the `rust-example` directive for code examples
+with rustdoc-style attributes for compilation testing.
+
+Supported attributes:
+- ignore: Don't compile this example
+- compile_fail: Example should fail to compile (optionally with error code)
+- should_panic: Example should compile but panic at runtime
+- no_run: Compile but don't run the example
+
+Hidden lines (prefixed with `# `) can be shown or hidden based on configuration.
+"""
+
+import re
+from typing import List, Optional, Tuple
+
+from docutils import nodes
+from docutils.parsers.rst import Directive, directives
+from sphinx.application import Sphinx
+from sphinx.util import logging
+
+logger = logging.getLogger(__name__)
+
+
+# Valid rustdoc attributes
+RUSTDOC_ATTRIBUTES = {
+ "ignore": "This example is not compiled or tested",
+ "compile_fail": "This example should fail to compile",
+ "should_panic": "This example should panic at runtime",
+ "no_run": "This example is compiled but not executed",
+}
+
+
+def parse_compile_fail_error(value: str) -> Tuple[bool, Optional[str]]:
+ """
+ Parse compile_fail option value.
+
+ Returns:
+ Tuple of (is_compile_fail, optional_error_code)
+
+ Examples:
+ "" -> (True, None)
+ "E0277" -> (True, "E0277")
+ """
+ if not value or value.lower() in ("true", "yes", "1"):
+ return (True, None)
+ # Check if it looks like an error code (E followed by digits)
+ if re.match(r"^E\d{4}$", value.strip()):
+ return (True, value.strip())
+ return (True, None)
+
+
+def process_hidden_lines(code: str, show_hidden: bool = False) -> Tuple[str, str]:
+ """
+ Process code to handle hidden lines (prefixed with `# `).
+
+ Args:
+ code: The raw code with potential hidden line markers
+ show_hidden: Whether to include hidden lines in rendered output
+
+ Returns:
+ Tuple of (display_code, full_code_for_testing)
+ """
+ lines = code.split('\n')
+ display_lines = []
+ full_lines = []
+
+ for line in lines:
+ # Check for hidden line marker (rustdoc style: line starts with # followed by space or nothing)
+ if line.startswith('# ') or line == '#':
+ # Hidden line - always include in full code
+ full_lines.append(line[2:] if line.startswith('# ') else '')
+ if show_hidden:
+ # Show with a visual indicator
+ display_lines.append(line)
+ else:
+ display_lines.append(line)
+ full_lines.append(line)
+
+ return '\n'.join(display_lines), '\n'.join(full_lines)
+
+
+class RustExampleDirective(Directive):
+ """
+ A directive for Rust code examples with rustdoc-style attributes.
+
+ Usage:
+ .. rust-example::
+ :compile_fail: E0277
+
+ fn example() {
+ let x: i32 = "string"; // This fails
+ }
+
+ .. rust-example::
+ :ignore:
+ :show_hidden:
+
+ # use std::collections::HashMap;
+ # fn main() {
+ let map = HashMap::new();
+ # }
+ """
+
+ has_content = True
+ required_arguments = 0
+ optional_arguments = 0
+ final_argument_whitespace = True
+
+ option_spec = {
+ # Rustdoc attributes
+ "ignore": directives.flag,
+ "compile_fail": directives.unchanged, # Can be flag or error code
+ "should_panic": directives.unchanged, # Can be flag or expected message
+ "no_run": directives.flag,
+ # Display options
+ "show_hidden": directives.flag, # Show hidden lines in rendered output
+ # Metadata
+ "name": directives.unchanged, # Optional name for the example
+ }
+
+ def run(self) -> List[nodes.Node]:
+ env = self.state.document.settings.env
+
+ # Get configuration for showing hidden lines globally
+ show_hidden_global = getattr(env.config, 'rust_examples_show_hidden', False)
+ show_hidden = 'show_hidden' in self.options or show_hidden_global
+
+ # Parse the code content
+ raw_code = '\n'.join(self.content)
+ display_code, full_code = process_hidden_lines(raw_code, show_hidden)
+
+ # Determine which rustdoc attribute is set (only one should be set)
+ rustdoc_attr = None
+ attr_value = None
+
+ if 'ignore' in self.options:
+ rustdoc_attr = 'ignore'
+ elif 'compile_fail' in self.options:
+ rustdoc_attr = 'compile_fail'
+ _, attr_value = parse_compile_fail_error(self.options.get('compile_fail', ''))
+ elif 'should_panic' in self.options:
+ rustdoc_attr = 'should_panic'
+ attr_value = self.options.get('should_panic', None)
+ if attr_value in ('', None):
+ attr_value = None
+ elif 'no_run' in self.options:
+ rustdoc_attr = 'no_run'
+
+ # Store metadata for extraction by the test runner
+ example_name = self.options.get('name', '')
+
+ # Create the container node
+ container = nodes.container()
+ container['classes'].append('rust-example-container')
+
+ # Add the badge if there's an attribute
+ if rustdoc_attr:
+ badge = nodes.container()
+ badge['classes'].append('rust-example-badge')
+ badge['classes'].append(f'rust-example-badge-{rustdoc_attr.replace("_", "-")}')
+
+ badge_text = rustdoc_attr
+ if attr_value:
+ badge_text += f"({attr_value})"
+
+ badge_para = nodes.paragraph()
+ badge_para += nodes.Text(badge_text)
+ badge += badge_para
+ container += badge
+
+ # Create the code block
+ code_node = nodes.literal_block(display_code, display_code)
+ code_node['language'] = 'rust'
+ code_node['classes'].append('rust-example-code')
+
+ # Store rustdoc metadata as custom attributes for later extraction
+ code_node['rustdoc_attr'] = rustdoc_attr
+ code_node['rustdoc_attr_value'] = attr_value
+ code_node['rustdoc_full_code'] = full_code
+ code_node['rustdoc_example_name'] = example_name
+
+ # Store source location for error reporting
+ source, line = self.state_machine.get_source_and_line(self.lineno)
+ code_node['source'] = source
+ code_node['line'] = line
+
+ container += code_node
+
+ return [container]
+
+
+class RustExampleCollector:
+ """
+ Collector that gathers all rust-example code blocks for testing.
+ """
+
+ def __init__(self):
+ self.examples = []
+
+ def collect(self, app, doctree, docname):
+ """Collect rust examples from the doctree."""
+ for node in doctree.traverse(nodes.literal_block):
+ if node.get('language') == 'rust' and 'rustdoc_full_code' in node.attributes:
+ example = {
+ 'docname': docname,
+ 'source': node.get('source', ''),
+ 'line': node.get('line', 0),
+ 'code': node.get('rustdoc_full_code', ''),
+ 'display_code': node.astext(),
+ 'attr': node.get('rustdoc_attr'),
+ 'attr_value': node.get('rustdoc_attr_value'),
+ 'name': node.get('rustdoc_example_name', ''),
+ }
+ self.examples.append(example)
+
+
+def add_css(app: Sphinx):
+ """Add CSS for rust-example styling."""
+ css = """
+/* Rust Example Container */
+.rust-example-container {
+ margin: 1em 0;
+ border-radius: 4px;
+ overflow: hidden;
+}
+
+/* Badge styling */
+.rust-example-badge {
+ padding: 0.3em 0.8em;
+ font-family: monospace;
+ font-size: 0.85em;
+ font-weight: bold;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+}
+
+.rust-example-badge p {
+ margin: 0;
+}
+
+.rust-example-badge-ignore {
+ background-color: #6c757d;
+ color: white;
+}
+
+.rust-example-badge-ignore::before {
+ content: "โญ ";
+}
+
+.rust-example-badge-compile-fail {
+ background-color: #dc3545;
+ color: white;
+}
+
+.rust-example-badge-compile-fail::before {
+ content: "โ ";
+}
+
+.rust-example-badge-should-panic {
+ background-color: #fd7e14;
+ color: white;
+}
+
+.rust-example-badge-should-panic::before {
+ content: "๐ฅ ";
+}
+
+.rust-example-badge-no-run {
+ background-color: #17a2b8;
+ color: white;
+}
+
+.rust-example-badge-no-run::before {
+ content: "โ ";
+}
+
+/* Hidden lines styling (when shown) */
+.rust-example-code .hidden-line {
+ opacity: 0.6;
+ font-style: italic;
+}
+
+/* Code block within container */
+.rust-example-container .rust-example-code {
+ margin-top: 0;
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+}
+"""
+
+ import os
+ css_path = os.path.join(app.outdir, "_static", "rust_examples.css")
+ os.makedirs(os.path.dirname(css_path), exist_ok=True)
+ with open(css_path, "w") as f:
+ f.write(css)
+
+
+def inject_css_link(app, pagename, templatename, context, doctree):
+ """Inject CSS link into HTML pages that have rust examples."""
+ if doctree is None:
+ return
+
+ # Check if this page has any rust examples
+ has_examples = False
+ for node in doctree.traverse(nodes.container):
+ if 'rust-example-container' in node.get('classes', []):
+ has_examples = True
+ break
+
+ if has_examples:
+ # Add CSS to metatags or similar
+ if 'metatags' not in context:
+ context['metatags'] = ''
+ context['metatags'] += '\n'
+
+
+def build_finished(app, exception):
+ """Write CSS file when build finishes."""
+ if exception is not None:
+ return
+ add_css(app)
+
+
+def setup(app: Sphinx):
+ """Setup the rust-example extension."""
+
+ # Register the directive
+ app.add_directive('rust-example', RustExampleDirective)
+
+ # Configuration options
+ app.add_config_value('rust_examples_show_hidden', False, 'env')
+ app.add_config_value('rust_examples_prelude_file', None, 'env')
+
+ # Connect to build events
+ app.connect('build-finished', build_finished)
+
+ return {
+ 'version': '0.1',
+ 'parallel_read_safe': True,
+ }
diff --git a/scripts/README.md b/scripts/README.md
index 3e72ef22..6b702757 100644
--- a/scripts/README.md
+++ b/scripts/README.md
@@ -2,8 +2,6 @@
This directory contains utility scripts for managing coding guidelines.
-**Location: scripts/README.md (replaces existing file)**
-
## Scripts Overview
| Script | Purpose |
@@ -11,6 +9,172 @@ This directory contains utility scripts for managing coding guidelines.
| `auto-pr-helper.py` | Transforms issue JSON to RST format (used by auto-PR workflow) |
| `generate-rst-comment.py` | Generates GitHub comment with RST preview |
| `guideline_utils.py` | Shared utility functions for guideline processing |
+| `rustdoc_utils.py` | Shared utilities for Rust example handling |
+| `migrate_rust_examples.py` | Migrate code-block to rust-example directives |
+| `extract_rust_examples.py` | Extract and test Rust examples |
+
+---
+
+## Rust Example Scripts
+
+These scripts support the rustdoc-style code example system.
+
+### `rustdoc_utils.py`
+
+A shared module containing common functions for Rust example handling:
+
+- `RustExample` - Data class representing a Rust code example
+- `TestResult` - Data class for test results
+- `process_hidden_lines()` - Handle rustdoc hidden line syntax (`# `)
+- `generate_doctest()` - Generate rustdoc-style doctest from example
+- `generate_test_crate()` - Generate a Cargo crate for testing
+- `compile_single_example()` - Compile and test a single example
+- `format_test_results()` - Format results for display
+
+### `migrate_rust_examples.py`
+
+Converts existing `.. code-block:: rust` directives to the new `.. rust-example::` directive format.
+
+```bash
+# Preview changes (dry run)
+uv run python scripts/migrate_rust_examples.py --dry-run
+
+# Apply changes
+uv run python scripts/migrate_rust_examples.py
+
+# Apply changes and try to auto-detect which examples need 'ignore'
+uv run python scripts/migrate_rust_examples.py --detect-failures
+```
+
+**Options:**
+- `--dry-run`: Preview changes without writing
+- `--detect-failures`: Try compiling examples and add `:ignore:` for failures
+- `--prelude PATH`: Path to prelude file for compilation checks
+- `--src-dir DIR`: Source directory to scan (default: `src/coding-guidelines`)
+- `-v, --verbose`: Verbose output
+
+### `extract_rust_examples.py`
+
+Extracts Rust examples from RST documentation and tests them.
+
+```bash
+# Extract examples and generate test crate
+uv run python scripts/extract_rust_examples.py --extract
+
+# Extract and test examples
+uv run python scripts/extract_rust_examples.py --test
+
+# Just test (assuming already extracted)
+uv run python scripts/extract_rust_examples.py --test-only
+
+# Output results as JSON
+uv run python scripts/extract_rust_examples.py --test --json results.json
+
+# List all examples
+uv run python scripts/extract_rust_examples.py --list
+```
+
+**Options:**
+- `--extract`: Extract examples and generate test crate
+- `--test`: Extract and test examples
+- `--test-only`: Test already extracted examples
+- `--list`: Just list all examples found
+- `--src-dir DIR`: Source directory to scan (default: `src/coding-guidelines`)
+- `--output-dir DIR`: Output directory for generated crate (default: `build/examples`)
+- `--prelude PATH`: Path to shared prelude file
+- `--json PATH`: Output results to JSON file
+- `--fail-on-error`: Exit with error code if any tests fail
+- `-v, --verbose`: Verbose output
+
+---
+
+## The `rust-example` Directive
+
+The `.. rust-example::` directive is a custom Sphinx directive for Rust code examples with rustdoc-style attributes.
+
+### Basic Usage
+
+```rst
+.. rust-example::
+
+ fn example() {
+ println!("Hello, world!");
+ }
+```
+
+### Rustdoc Attributes
+
+**`:ignore:`** - Don't compile this example:
+```rst
+.. rust-example::
+ :ignore:
+
+ fn incomplete_example() {
+ // This code is intentionally incomplete
+ }
+```
+
+**`:compile_fail:`** - Example should fail to compile:
+```rst
+.. rust-example::
+ :compile_fail: E0277
+
+ fn type_error() {
+ let x: i32 = "string"; // Type mismatch
+ }
+```
+
+**`:should_panic:`** - Example should panic at runtime:
+```rst
+.. rust-example::
+ :should_panic:
+
+ fn panicking() {
+ panic!("This should panic");
+ }
+```
+
+**`:no_run:`** - Compile but don't execute:
+```rst
+.. rust-example::
+ :no_run:
+
+ fn infinite_loop() {
+ loop {}
+ }
+```
+
+### Hidden Lines
+
+Use `# ` prefix for lines that should compile but not display:
+
+```rst
+.. rust-example::
+
+ # use std::collections::HashMap;
+ # fn main() {
+ let map = HashMap::new();
+ # }
+```
+
+To show hidden lines in rendered output, add `:show_hidden:`:
+
+```rst
+.. rust-example::
+ :show_hidden:
+
+ # use std::collections::HashMap;
+ let map = HashMap::new();
+```
+
+### Rendering
+
+In the rendered documentation, examples with attributes show a badge:
+
+- **ignore** (gray): โญ ignore
+- **compile_fail** (red): โ compile_fail(E0277)
+- **should_panic** (orange): ๐ฅ should_panic
+- **no_run** (blue): โ no_run
---
@@ -53,7 +217,7 @@ cat path/to/issue.json | uv run python scripts/auto-pr-helper.py --save
## `generate-rst-comment.py`
-This script generates a formatted GitHub comment containing an RST preview of a coding guideline. It's used by the RST Preview Comment workflow to post helpful comments on coding guideline issues.
+This script generates a formatted GitHub comment containing an RST preview of a coding guideline.
### Usage
@@ -65,39 +229,6 @@ cat path/to/issue.json | uv run python scripts/generate-rst-comment.py
curl https://api.github.com/repos/rustfoundation/safety-critical-rust-coding-guidelines/issues/123 | uv run python scripts/generate-rst-comment.py
```
-### Output
-
-The script outputs a Markdown-formatted comment that includes:
-
-1. **Instructions** on how to use the RST content
-2. **Target file path** indicating which chapter file to add the guideline to
-3. **Collapsible RST content** that can be copied and pasted
-
-### Example Output
-
-```markdown
-## ๐ RST Preview for Coding Guideline
-
-This is an automatically generated preview...
-
-### ๐ Target File
-Add this guideline to: `src/coding-guidelines/concurrency.rst`
-
-### ๐ How to Use This
-1. Fork the repository...
-...
-
-
-๐ Click to expand RST content
-
-\`\`\`rst
-.. guideline:: My Guideline Title
- :id: gui_ABC123...
-\`\`\`
-
-
-```
-
---
## How to Get Issue JSON from GitHub API
diff --git a/scripts/extract_rust_examples.py b/scripts/extract_rust_examples.py
new file mode 100644
index 00000000..a1bcb157
--- /dev/null
+++ b/scripts/extract_rust_examples.py
@@ -0,0 +1,753 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT OR Apache-2.0
+# SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors
+
+"""
+Extract and test Rust examples from RST documentation.
+
+This script:
+1. Parses RST files to find rust-example:: directives
+2. Extracts the code and rustdoc attributes
+3. Generates a test crate with all examples as doc tests
+4. Runs the tests and reports results
+
+Usage:
+ # Extract examples and generate test crate
+ uv run python scripts/extract_rust_examples.py --extract
+
+ # Extract and test examples
+ uv run python scripts/extract_rust_examples.py --test
+
+ # Just test (assuming already extracted)
+ uv run python scripts/extract_rust_examples.py --test-only
+
+ # Output results as JSON
+ uv run python scripts/extract_rust_examples.py --test --json results.json
+"""
+
+import argparse
+import json
+import os
+import re
+import sys
+from pathlib import Path
+from typing import Dict, List, Optional, Tuple
+
+# Add scripts directory to path
+script_dir = os.path.dirname(os.path.abspath(__file__))
+sys.path.insert(0, script_dir)
+
+from rustdoc_utils import (
+ RustExample,
+ TestResult,
+ compile_single_example,
+ format_test_results,
+ generate_test_crate,
+ get_rust_version,
+ load_prelude,
+ process_hidden_lines,
+ save_results_json,
+)
+
+# Patterns for parsing RST
+RUST_EXAMPLE_PATTERN = re.compile(
+ r'^([ \t]*)\.\.\ rust-example::', # Use [ \t]* not \s* to avoid capturing newlines
+ re.MULTILINE
+)
+
+CODE_BLOCK_PATTERN = re.compile(
+ r'^([ \t]*)\.\.\ code-block::\ rust[ \t]*$', # Use [ \t]* not \s* to avoid capturing newlines
+ re.MULTILINE
+)
+
+EXAMPLE_DIRECTIVE_PATTERN = re.compile(
+ r'^([ \t]*)\.\.\ (compliant_example|non_compliant_example)::', # Use [ \t]* not \s*
+ re.MULTILINE
+)
+
+GUIDELINE_PATTERN = re.compile(
+ r'^([ \t]*)\.\.\ guideline::', # Use [ \t]* not \s*
+ re.MULTILINE
+)
+
+
+def parse_directive_options(content: str, start_pos: int, base_indent: str) -> Tuple[Dict[str, str], int]:
+ """
+ Parse options from a directive.
+
+ Args:
+ content: Full file content
+ start_pos: Position after the directive line
+ base_indent: The indentation of the directive
+
+ Returns:
+ Tuple of (options dict, position after options)
+ """
+ options = {}
+ pos = start_pos
+ lines = content[start_pos:].split('\n')
+ option_indent = base_indent + " "
+
+ for line in lines:
+ pos += len(line) + 1
+
+ if not line.strip():
+ continue
+
+ # Check if this is an option line
+ if line.startswith(option_indent) and line.strip().startswith(':'):
+ # Parse option
+ match = re.match(r'\s*:(\w+):\s*(.*)', line)
+ if match:
+ opt_name = match.group(1)
+ opt_value = match.group(2).strip()
+ options[opt_name] = opt_value
+ continue
+
+ # Check if we've moved past options (content starts)
+ stripped = line.lstrip()
+ current_indent = len(line) - len(stripped)
+
+ if current_indent >= len(option_indent) and not stripped.startswith(':'):
+ # This is content, not an option
+ pos -= len(line) + 1 # Back up
+ break
+ elif current_indent < len(option_indent):
+ # Dedented past directive
+ pos -= len(line) + 1
+ break
+
+ return options, pos
+
+
+def extract_directive_content(content: str, start_pos: int, base_indent: str) -> Tuple[str, int]:
+ """
+ Extract the content of a directive starting at the given position.
+
+ Args:
+ content: Full file content
+ start_pos: Position after the directive options
+ base_indent: The indentation of the directive
+
+ Returns:
+ Tuple of (extracted_content, end_position)
+ """
+ lines = content[start_pos:].split('\n')
+ content_lines = []
+ end_pos = start_pos
+ content_indent = None
+ in_content = False
+
+ for line in lines:
+ if not line.strip():
+ if in_content:
+ content_lines.append('')
+ end_pos += len(line) + 1
+ continue
+
+ stripped = line.lstrip()
+ current_indent = len(line) - len(stripped)
+
+ if content_indent is None:
+ # First non-empty line after options
+ content_indent = current_indent
+ if current_indent <= len(base_indent):
+ # No content
+ break
+ in_content = True
+ content_lines.append(stripped)
+ end_pos += len(line) + 1
+ elif current_indent >= content_indent:
+ # Still in content
+ relative_indent = ' ' * (current_indent - content_indent)
+ content_lines.append(relative_indent + stripped)
+ end_pos += len(line) + 1
+ else:
+ # Dedented - end of content
+ break
+
+ return '\n'.join(content_lines).rstrip(), end_pos
+
+
+def find_parent_context(content: str, pos: int) -> Tuple[Optional[str], Optional[str], Optional[str]]:
+ """
+ Find the parent directive context for a position.
+
+ Args:
+ content: Full file content
+ pos: Position to check
+
+ Returns:
+ Tuple of (parent_type, parent_id, guideline_id)
+ """
+ before = content[:pos]
+
+ # Find parent example directive
+ parent_type = None
+ parent_id = None
+
+ example_matches = list(EXAMPLE_DIRECTIVE_PATTERN.finditer(before))
+ if example_matches:
+ match = example_matches[-1]
+ parent_type = match.group(2)
+
+ # Find ID
+ after_directive = content[match.end():pos]
+ id_match = re.search(r':id:\s*(\S+)', after_directive)
+ parent_id = id_match.group(1) if id_match else None
+
+ # Find parent guideline
+ guideline_id = None
+ guideline_matches = list(GUIDELINE_PATTERN.finditer(before))
+ if guideline_matches:
+ match = guideline_matches[-1]
+ after_directive = content[match.end():pos]
+ next_directive = re.search(r'\n\s*\.\. \w+::', after_directive)
+ if next_directive:
+ after_directive = after_directive[:next_directive.start()]
+ id_match = re.search(r':id:\s*(\S+)', after_directive)
+ guideline_id = id_match.group(1) if id_match else None
+
+ return parent_type, parent_id, guideline_id
+
+
+def extract_rust_examples_from_file(file_path: Path) -> List[RustExample]:
+ """
+ Extract all Rust examples from an RST file.
+
+ This handles both:
+ - New rust-example:: directives
+ - Legacy code-block:: rust directives (for backwards compatibility during migration)
+
+ Args:
+ file_path: Path to the RST file
+
+ Returns:
+ List of RustExample objects
+ """
+ content = file_path.read_text()
+ examples = []
+
+ # First, find rust-example:: directives
+ for match in RUST_EXAMPLE_PATTERN.finditer(content):
+ indent = match.group(1)
+ start = match.start()
+ line_number = content[:start].count('\n') + 1
+
+ # Parse options
+ options, opt_end = parse_directive_options(content, match.end(), indent)
+
+ # Extract content
+ code, _ = extract_directive_content(content, opt_end, indent)
+
+ # Process hidden lines
+ display_code, full_code = process_hidden_lines(code)
+
+ # Determine rustdoc attribute
+ attr = None
+ attr_value = None
+
+ if 'ignore' in options:
+ attr = 'ignore'
+ elif 'compile_fail' in options:
+ attr = 'compile_fail'
+ attr_value = options.get('compile_fail') or None
+ elif 'should_panic' in options:
+ attr = 'should_panic'
+ attr_value = options.get('should_panic') or None
+ elif 'no_run' in options:
+ attr = 'no_run'
+
+ # Parse version/edition requirements
+ min_version = options.get('version') or options.get('min-version')
+ channel = options.get('channel', 'stable')
+ edition = options.get('edition', '2021')
+
+ # Find parent context
+ parent_type, parent_id, guideline_id = find_parent_context(content, start)
+
+ example = RustExample(
+ source_file=str(file_path),
+ line_number=line_number,
+ code=full_code,
+ display_code=display_code,
+ attr=attr,
+ attr_value=attr_value,
+ min_version=min_version,
+ channel=channel,
+ edition=edition,
+ example_name=options.get('name', ''),
+ parent_directive=parent_type or '',
+ parent_id=parent_id or '',
+ guideline_id=guideline_id or '',
+ )
+
+ examples.append(example)
+
+ # Also find legacy code-block:: rust directives within example contexts
+ for match in CODE_BLOCK_PATTERN.finditer(content):
+ indent = match.group(1)
+ start = match.start()
+ line_number = content[:start].count('\n') + 1
+
+ # Check if this is inside an example directive
+ parent_type, parent_id, guideline_id = find_parent_context(content, start)
+
+ if not parent_type:
+ # Not in an example context, skip
+ continue
+
+ # Check if there's already a rust-example at this location
+ # (avoid double-counting during partial migration)
+ already_processed = False
+ for existing in examples:
+ if abs(existing.line_number - line_number) < 5:
+ already_processed = True
+ break
+
+ if already_processed:
+ continue
+
+ # Extract code content
+ code, _ = extract_directive_content(content, match.end(), indent)
+
+ example = RustExample(
+ source_file=str(file_path),
+ line_number=line_number,
+ code=code,
+ display_code=code,
+ parent_directive=parent_type or '',
+ parent_id=parent_id or '',
+ guideline_id=guideline_id or '',
+ )
+
+ examples.append(example)
+
+ return examples
+
+
+def extract_all_examples(src_dirs: List[Path], quiet: bool = False) -> List[RustExample]:
+ """
+ Extract all Rust examples from all RST files in the given directories.
+
+ Args:
+ src_dirs: List of directories to scan
+ quiet: If True, suppress progress output
+
+ Returns:
+ List of all RustExample objects found
+ """
+ examples = []
+
+ for src_dir in src_dirs:
+ rst_files = list(src_dir.glob("**/*.rst"))
+
+ if not quiet:
+ print(f"๐ Scanning {len(rst_files)} RST files in {src_dir}", file=sys.stderr)
+
+ for file_path in rst_files:
+ file_examples = extract_rust_examples_from_file(file_path)
+ if file_examples:
+ if not quiet:
+ print(f" ๐ {file_path.name}: {len(file_examples)} examples", file=sys.stderr)
+ examples.extend(file_examples)
+
+ if not quiet:
+ print(f"\n๐ Total: {len(examples)} examples found", file=sys.stderr)
+
+ return examples
+
+
+def test_examples_individually(
+ examples: List[RustExample],
+ prelude: str = ""
+) -> List[TestResult]:
+ """
+ Test each example individually.
+
+ Args:
+ examples: List of examples to test
+ prelude: Optional prelude code
+
+ Returns:
+ List of TestResult objects
+ """
+ results = []
+
+ # Detect current Rust version and channel
+ current_version, current_channel = get_rust_version()
+ if current_version:
+ print(f"\n๐ฆ Detected Rust {current_version} ({current_channel})")
+ else:
+ print("\nโ ๏ธ Could not detect Rust version")
+
+ print(f"\n๐งช Testing {len(examples)} examples...")
+
+ for i, example in enumerate(examples):
+ result = compile_single_example(
+ example,
+ prelude,
+ current_version=current_version,
+ current_channel=current_channel
+ )
+ results.append(result)
+
+ # Progress indicator
+ if result.skipped:
+ status = "โญ๏ธ"
+ elif result.passed:
+ status = "โ
"
+ else:
+ status = "โ"
+ print(f" [{i+1}/{len(examples)}] {status} {example.source_file}:{example.line_number}")
+
+ return results
+
+
+def analyze_requirements(examples: List[RustExample]) -> Dict:
+ """
+ Analyze toolchain requirements for all examples.
+
+ Returns a dict with:
+ - channels: dict mapping channel -> list of examples
+ - versions: dict mapping min_version -> list of examples
+ - editions: dict mapping edition -> list of examples
+ - default: list of examples with no special requirements
+ """
+ requirements = {
+ "channels": {"stable": [], "beta": [], "nightly": []},
+ "versions": {},
+ "editions": {},
+ "default": [],
+ "summary": {
+ "total": len(examples),
+ "needs_nightly": 0,
+ "needs_specific_version": 0,
+ "needs_old_edition": 0,
+ "default_only": 0,
+ }
+ }
+
+ for example in examples:
+ example_info = {
+ "file": example.source_file,
+ "line": example.line_number,
+ "guideline_id": example.guideline_id,
+ }
+
+ has_special_requirement = False
+
+ # Check channel requirement
+ if example.channel and example.channel != "stable":
+ requirements["channels"][example.channel].append(example_info)
+ has_special_requirement = True
+ if example.channel == "nightly":
+ requirements["summary"]["needs_nightly"] += 1
+
+ # Check version requirement
+ if example.min_version:
+ version = example.min_version
+ if version not in requirements["versions"]:
+ requirements["versions"][version] = []
+ requirements["versions"][version].append(example_info)
+ has_special_requirement = True
+ requirements["summary"]["needs_specific_version"] += 1
+
+ # Check edition (track non-2021 editions)
+ if example.edition and example.edition != "2021":
+ edition = example.edition
+ if edition not in requirements["editions"]:
+ requirements["editions"][edition] = []
+ requirements["editions"][edition].append(example_info)
+ has_special_requirement = True
+ requirements["summary"]["needs_old_edition"] += 1
+
+ # Track examples with no special requirements
+ if not has_special_requirement:
+ requirements["default"].append(example_info)
+ requirements["summary"]["default_only"] += 1
+
+ return requirements
+
+
+def filter_examples(
+ examples: List[RustExample],
+ filter_channel: Optional[str] = None,
+ filter_min_version: Optional[str] = None,
+ filter_default: bool = False,
+) -> List[RustExample]:
+ """
+ Filter examples based on toolchain requirements.
+
+ Args:
+ examples: List of examples to filter
+ filter_channel: Only include examples requiring this channel (e.g., "nightly")
+ filter_min_version: Only include examples requiring at least this version
+ filter_default: Only include examples with no special requirements
+
+ Returns:
+ Filtered list of examples
+ """
+ filtered = []
+
+ for example in examples:
+ # If filtering for default only
+ if filter_default:
+ has_special = (
+ (example.channel and example.channel != "stable") or
+ example.min_version is not None
+ )
+ if not has_special:
+ filtered.append(example)
+ continue
+
+ # If filtering by channel
+ if filter_channel:
+ # "nightly" filter: only examples that require nightly
+ if filter_channel == "nightly":
+ if example.channel == "nightly":
+ filtered.append(example)
+ # "stable" filter: examples that work on stable (no nightly requirement)
+ elif filter_channel == "stable":
+ if example.channel != "nightly":
+ filtered.append(example)
+ continue
+
+ # If filtering by minimum version
+ if filter_min_version:
+ if example.min_version:
+ # Parse versions to compare
+ try:
+ filter_parts = [int(x) for x in filter_min_version.split('.')[:2]]
+ example_parts = [int(x) for x in example.min_version.split('.')[:2]]
+
+ # Include if example requires >= filter version
+ if example_parts >= filter_parts:
+ filtered.append(example)
+ except ValueError:
+ # If parsing fails, include the example
+ filtered.append(example)
+ continue
+
+ # No filter - include all
+ filtered.append(example)
+
+ return filtered
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description="Extract and test Rust examples from documentation"
+ )
+
+ # Actions
+ parser.add_argument(
+ "--extract",
+ action="store_true",
+ help="Extract examples and generate test crate"
+ )
+ parser.add_argument(
+ "--test",
+ action="store_true",
+ help="Extract and test examples"
+ )
+ parser.add_argument(
+ "--test-only",
+ action="store_true",
+ help="Test already extracted examples (requires --crate-dir)"
+ )
+ parser.add_argument(
+ "--list",
+ action="store_true",
+ help="Just list all examples found"
+ )
+
+ # Options
+ parser.add_argument(
+ "--src-dir",
+ type=str,
+ action="append",
+ dest="src_dirs",
+ help="Source directory to scan for RST files (can be specified multiple times)"
+ )
+ parser.add_argument(
+ "--output-dir",
+ type=str,
+ default="build/examples",
+ help="Output directory for generated test crate"
+ )
+ parser.add_argument(
+ "--prelude",
+ type=str,
+ default=None,
+ help="Path to shared prelude file"
+ )
+ parser.add_argument(
+ "--json",
+ type=str,
+ default=None,
+ help="Output results to JSON file"
+ )
+ parser.add_argument(
+ "--fail-on-error",
+ action="store_true",
+ help="Exit with error code if any tests fail"
+ )
+ parser.add_argument(
+ "-v", "--verbose",
+ action="store_true",
+ help="Verbose output"
+ )
+
+ # Filtering options
+ parser.add_argument(
+ "--list-requirements",
+ action="store_true",
+ help="List toolchain requirements for all examples as JSON"
+ )
+ parser.add_argument(
+ "--filter-channel",
+ type=str,
+ choices=["stable", "beta", "nightly"],
+ default=None,
+ help="Only test examples that require this specific channel"
+ )
+ parser.add_argument(
+ "--filter-min-version",
+ type=str,
+ default=None,
+ help="Only test examples that require at least this Rust version (e.g., 1.79)"
+ )
+ parser.add_argument(
+ "--filter-default",
+ action="store_true",
+ help="Only test examples with no special requirements (default channel/version)"
+ )
+
+ args = parser.parse_args()
+
+ # Validate arguments
+ if not any([args.extract, args.test, args.test_only, args.list, args.list_requirements]):
+ parser.print_help()
+ sys.exit(1)
+
+ # Handle source directories - default to src/coding-guidelines if none specified
+ src_dirs = args.src_dirs if args.src_dirs else ["src/coding-guidelines"]
+
+ # Validate all source directories exist
+ validated_src_dirs = []
+ for src_dir in src_dirs:
+ src_path = Path(src_dir)
+ if not src_path.exists():
+ print(f"โ Source directory not found: {src_path}")
+ sys.exit(1)
+ validated_src_dirs.append(src_path)
+
+ output_dir = Path(args.output_dir)
+
+ # Load prelude
+ prelude = ""
+ if args.prelude:
+ prelude = load_prelude(args.prelude)
+ if prelude and not args.list_requirements:
+ print(f"๐ Loaded prelude from {args.prelude}", file=sys.stderr)
+
+ # Extract examples from all source directories
+ if args.list or args.extract or args.test or args.list_requirements:
+ # Use quiet mode for list-requirements to get clean JSON output
+ quiet = args.list_requirements
+ examples = extract_all_examples(validated_src_dirs, quiet=quiet)
+
+ # Handle --list-requirements
+ if args.list_requirements:
+ requirements = analyze_requirements(examples)
+ print(json.dumps(requirements, indent=2))
+ sys.exit(0)
+
+ if args.list:
+ print("\n๐ Examples found:")
+ for example in examples:
+ attr_str = f" [{example.attr}]" if example.attr else ""
+ print(f" {example.source_file}:{example.line_number}{attr_str}")
+ if args.verbose:
+ print(f" Parent: {example.parent_directive} ({example.parent_id})")
+ print(f" Guideline: {example.guideline_id}")
+ sys.exit(0)
+
+ # Apply filters if specified
+ if args.filter_channel or args.filter_min_version or args.filter_default:
+ original_count = len(examples)
+ examples = filter_examples(
+ examples,
+ filter_channel=args.filter_channel,
+ filter_min_version=args.filter_min_version,
+ filter_default=args.filter_default,
+ )
+ if args.verbose:
+ print(f"๐ Filtered {original_count} -> {len(examples)} examples")
+ if args.filter_channel:
+ print(f" Filter: channel={args.filter_channel}")
+ if args.filter_min_version:
+ print(f" Filter: min_version={args.filter_min_version}")
+ if args.filter_default:
+ print(" Filter: default (no special requirements)")
+
+ if args.extract or args.test:
+ # Generate test crate
+ output_dir.mkdir(parents=True, exist_ok=True)
+ crate_dir = generate_test_crate(examples, output_dir, prelude)
+ print(f"\n๐ฆ Generated test crate at {crate_dir}")
+
+ # Save examples JSON for reference
+ examples_json = output_dir / "examples.json"
+ with open(examples_json, 'w') as f:
+ json.dump([e.to_dict() for e in examples], f, indent=2)
+ print(f"๐ Saved examples metadata to {examples_json}")
+
+ if args.test:
+ # Run tests
+ results = test_examples_individually(examples, prelude)
+
+ # Print results
+ print(format_test_results(results))
+
+ # Save JSON if requested
+ if args.json:
+ save_results_json(results, Path(args.json))
+ print(f"\n๐ Results saved to {args.json}")
+
+ # Check for failures
+ failures = [r for r in results if not r.passed]
+ if failures and args.fail_on_error:
+ sys.exit(1)
+
+ elif args.test_only:
+ # Load existing examples
+ examples_json = output_dir / "examples.json"
+ if not examples_json.exists():
+ print(f"โ Examples file not found: {examples_json}")
+ print(" Run with --extract first")
+ sys.exit(1)
+
+ with open(examples_json) as f:
+ examples = [RustExample.from_dict(e) for e in json.load(f)]
+
+ # Run tests
+ results = test_examples_individually(examples, prelude)
+
+ # Print results
+ print(format_test_results(results))
+
+ # Save JSON if requested
+ if args.json:
+ save_results_json(results, Path(args.json))
+ print(f"\n๐ Results saved to {args.json}")
+
+ # Check for failures
+ failures = [r for r in results if not r.passed]
+ if failures and args.fail_on_error:
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/migrate_rust_examples.py b/scripts/migrate_rust_examples.py
new file mode 100644
index 00000000..d6bd7bba
--- /dev/null
+++ b/scripts/migrate_rust_examples.py
@@ -0,0 +1,417 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT OR Apache-2.0
+# SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors
+
+"""
+Migration script to convert existing `.. code-block:: rust` directives
+to the new `.. rust-example::` directive format.
+
+This script:
+1. Scans RST files for code-block:: rust directives within example contexts
+2. Converts them to rust-example:: directives
+3. Optionally runs a compilation check to suggest appropriate attributes
+
+Usage:
+ # Preview changes (dry run)
+ uv run python scripts/migrate_rust_examples.py --dry-run
+
+ # Apply changes
+ uv run python scripts/migrate_rust_examples.py
+
+ # Apply changes and try to auto-detect which examples need 'ignore'
+ uv run python scripts/migrate_rust_examples.py --detect-failures
+"""
+
+import argparse
+import os
+import re
+import sys
+from pathlib import Path
+from typing import Dict, List, Optional, Tuple
+
+# Add scripts directory to path
+script_dir = os.path.dirname(os.path.abspath(__file__))
+sys.path.insert(0, script_dir)
+
+from rustdoc_utils import RustExample, compile_single_example
+
+# Pattern to find code-block:: rust within the content
+# Use [ \t]* instead of \s* to avoid matching newlines
+CODE_BLOCK_PATTERN = re.compile(
+ r'^(\s*)\.\.\ code-block::\ rust[ \t]*$',
+ re.MULTILINE
+)
+
+# Pattern to detect we're inside an example directive
+EXAMPLE_DIRECTIVE_PATTERN = re.compile(
+ r'^(\s*)\.\.\ (compliant_example|non_compliant_example)::',
+ re.MULTILINE
+)
+
+# Pattern to match guideline directives
+GUIDELINE_PATTERN = re.compile(
+ r'^(\s*)\.\.\ guideline::',
+ re.MULTILINE
+)
+
+
+def find_rst_files(src_dir: Path) -> List[Path]:
+ """Find all RST files in the source directory."""
+ return list(src_dir.glob("**/*.rst"))
+
+
+def extract_code_block_content(content: str, start_pos: int, base_indent: str) -> Tuple[str, int]:
+ """
+ Extract the content of a code block starting at the given position.
+
+ Args:
+ content: Full file content
+ start_pos: Position after the code-block directive line
+ base_indent: The indentation of the code-block directive
+
+ Returns:
+ Tuple of (extracted_code, end_position)
+ """
+ lines = content[start_pos:].split('\n')
+ code_lines = []
+ end_pos = start_pos
+ in_code = False
+ code_indent = None
+ # Minimum indent for code content (must be more indented than the directive)
+ min_code_indent = len(base_indent) + 4 # At least 4 spaces more than directive
+
+ for i, line in enumerate(lines):
+ # Track position
+ line_len = len(line) + 1 # +1 for newline
+
+ # Check if line is empty or whitespace only
+ if not line.strip():
+ if in_code:
+ code_lines.append('')
+ end_pos += line_len
+ continue
+
+ # Check indentation
+ stripped = line.lstrip()
+ current_indent = len(line) - len(stripped)
+
+ # Check if this looks like a new directive (starts with ..)
+ if stripped.startswith('.. ') and current_indent <= len(base_indent) + 4:
+ # This is a new directive at same or lower level - stop
+ break
+
+ # Check if this is a directive option (starts with :)
+ if stripped.startswith(':') and not in_code:
+ # Skip directive options
+ end_pos += line_len
+ continue
+
+ if code_indent is None:
+ # First non-empty, non-option line determines code indent
+ if current_indent < min_code_indent:
+ # Not indented enough - end of code block or empty block
+ break
+ code_indent = current_indent
+ in_code = True
+ code_lines.append(stripped)
+ end_pos += line_len
+ elif current_indent >= code_indent:
+ # Still in code block - preserve relative indentation
+ relative_indent = ' ' * (current_indent - code_indent)
+ code_lines.append(relative_indent + stripped)
+ end_pos += line_len
+ elif current_indent > len(base_indent):
+ # Less indented than code but still more than directive
+ # Could be continuation - include it
+ relative_indent = ' ' * (current_indent - code_indent) if current_indent >= code_indent else ''
+ code_lines.append(relative_indent + stripped)
+ end_pos += line_len
+ else:
+ # Dedented to directive level or less - end of code block
+ break
+
+ # Clean up trailing empty lines
+ while code_lines and not code_lines[-1].strip():
+ code_lines.pop()
+
+ return '\n'.join(code_lines), end_pos
+
+
+def find_parent_directive(content: str, pos: int) -> Tuple[Optional[str], Optional[str]]:
+ """
+ Find the parent directive (compliant_example or non_compliant_example) for a position.
+
+ Args:
+ content: Full file content
+ pos: Position of the code block
+
+ Returns:
+ Tuple of (directive_type, directive_id) or (None, None)
+ """
+ # Look backwards from the position for an example directive
+ before = content[:pos]
+
+ # Find all example directives before this position
+ example_matches = list(EXAMPLE_DIRECTIVE_PATTERN.finditer(before))
+
+ if not example_matches:
+ return None, None
+
+ # Get the last (closest) match
+ match = example_matches[-1]
+ directive_type = match.group(2)
+
+ # Try to find the :id: option after this directive
+ after_directive = content[match.end():pos]
+ id_match = re.search(r':id:\s*(\S+)', after_directive)
+ directive_id = id_match.group(1) if id_match else None
+
+ return directive_type, directive_id
+
+
+def find_parent_guideline(content: str, pos: int) -> Optional[str]:
+ """
+ Find the parent guideline ID for a position.
+
+ Args:
+ content: Full file content
+ pos: Position of the code block
+
+ Returns:
+ Guideline ID or None
+ """
+ before = content[:pos]
+
+ # Find all guideline directives before this position
+ guideline_matches = list(GUIDELINE_PATTERN.finditer(before))
+
+ if not guideline_matches:
+ return None
+
+ # Get the last match
+ match = guideline_matches[-1]
+
+ # Try to find the :id: option after this directive
+ after_directive = content[match.end():pos]
+
+ # Only look until the next directive (any directive)
+ next_directive = re.search(r'\n\s*\.\. \w+::', after_directive)
+ if next_directive:
+ after_directive = after_directive[:next_directive.start()]
+
+ id_match = re.search(r':id:\s*(\S+)', after_directive)
+ return id_match.group(1) if id_match else None
+
+
+def convert_code_block_to_rust_example(
+ content: str,
+ detect_failures: bool = False,
+ prelude: str = ""
+) -> Tuple[str, List[Dict]]:
+ """
+ Convert all code-block:: rust directives to rust-example:: directives.
+
+ This does a simple in-place replacement of the directive line only,
+ leaving the code content exactly as-is.
+
+ Args:
+ content: The RST file content
+ detect_failures: Whether to try compiling and add :ignore: for failures
+ prelude: Optional prelude code
+
+ Returns:
+ Tuple of (converted_content, list of changes made)
+ """
+ changes = []
+
+ # Find all matches first
+ matches = list(CODE_BLOCK_PATTERN.finditer(content))
+
+ # Filter to only those inside example directives
+ valid_matches = []
+ for match in matches:
+ parent_type, parent_id = find_parent_directive(content, match.start())
+ if parent_type:
+ guideline_id = find_parent_guideline(content, match.start())
+ valid_matches.append((match, parent_type, parent_id, guideline_id))
+
+ # Process in reverse order so positions don't shift
+ result = content
+ for match, parent_type, parent_id, guideline_id in reversed(valid_matches):
+ indent = match.group(1)
+ start = match.start()
+ end = match.end()
+
+ # For detect_failures mode, we need to extract the code
+ attr = None
+ attr_value = None
+ code_preview = ""
+
+ if detect_failures:
+ code, _ = extract_code_block_content(content, end, indent)
+ code_preview = code[:100] + '...' if len(code) > 100 else code
+
+ if code.strip():
+ example = RustExample(
+ source_file="migration",
+ line_number=0,
+ code=code,
+ display_code=code,
+ parent_directive=parent_type,
+ parent_id=parent_id or "",
+ guideline_id=guideline_id or "",
+ )
+
+ result_check = compile_single_example(example, prelude)
+ if not result_check.passed:
+ attr = 'ignore'
+
+ # Build the replacement
+ new_directive = f"{indent}.. rust-example::"
+
+ if attr:
+ new_directive += f"\n{indent} :{attr}:"
+ if attr_value:
+ new_directive += f" {attr_value}"
+
+ # Replace just the matched portion
+ result = result[:start] + new_directive + result[end:]
+
+ # Record the change
+ changes.append({
+ 'parent_type': parent_type,
+ 'parent_id': parent_id,
+ 'guideline_id': guideline_id,
+ 'attr': attr,
+ 'code_preview': code_preview,
+ })
+
+ # Reverse changes list so it's in document order
+ changes.reverse()
+
+ return result, changes
+
+
+def process_file(
+ file_path: Path,
+ dry_run: bool = True,
+ detect_failures: bool = False,
+ prelude: str = "",
+ verbose: bool = False
+) -> List[Dict]:
+ """
+ Process a single RST file.
+
+ Args:
+ file_path: Path to the RST file
+ dry_run: If True, don't write changes
+ detect_failures: Whether to detect compilation failures
+ prelude: Optional prelude code
+ verbose: Print detailed information
+
+ Returns:
+ List of changes made
+ """
+ content = file_path.read_text()
+
+ new_content, changes = convert_code_block_to_rust_example(
+ content,
+ detect_failures=detect_failures,
+ prelude=prelude
+ )
+
+ if changes:
+ if verbose:
+ print(f"\n๐ {file_path}")
+ for change in changes:
+ attr_info = f" [{change['attr']}]" if change['attr'] else ""
+ print(f" โ๏ธ {change['parent_type']} ({change['parent_id']}){attr_info}")
+
+ if not dry_run:
+ file_path.write_text(new_content)
+ if verbose:
+ print(" โ
Written")
+
+ return changes
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description="Migrate code-block:: rust to rust-example:: directives"
+ )
+ parser.add_argument(
+ "--dry-run",
+ action="store_true",
+ help="Preview changes without writing"
+ )
+ parser.add_argument(
+ "--detect-failures",
+ action="store_true",
+ help="Try compiling examples and add :ignore: for failures"
+ )
+ parser.add_argument(
+ "--prelude",
+ type=str,
+ default=None,
+ help="Path to prelude file for compilation checks"
+ )
+ parser.add_argument(
+ "--src-dir",
+ type=str,
+ default="src/coding-guidelines",
+ help="Source directory to scan"
+ )
+ parser.add_argument(
+ "-v", "--verbose",
+ action="store_true",
+ help="Verbose output"
+ )
+
+ args = parser.parse_args()
+
+ src_dir = Path(args.src_dir)
+ if not src_dir.exists():
+ print(f"โ Source directory not found: {src_dir}")
+ sys.exit(1)
+
+ # Load prelude if specified
+ prelude = ""
+ if args.prelude:
+ prelude_path = Path(args.prelude)
+ if prelude_path.exists():
+ prelude = prelude_path.read_text()
+ else:
+ print(f"โ ๏ธ Prelude file not found: {prelude_path}")
+
+ # Find and process RST files
+ rst_files = find_rst_files(src_dir)
+ print(f"๐ Found {len(rst_files)} RST files in {src_dir}")
+
+ if args.dry_run:
+ print("๐ DRY RUN - no files will be modified")
+
+ total_changes = 0
+ files_changed = 0
+
+ for file_path in rst_files:
+ changes = process_file(
+ file_path,
+ dry_run=args.dry_run,
+ detect_failures=args.detect_failures,
+ prelude=prelude,
+ verbose=args.verbose
+ )
+
+ if changes:
+ total_changes += len(changes)
+ files_changed += 1
+
+ print(f"\n{'='*60}")
+ print(f"Summary: {total_changes} code blocks in {files_changed} files")
+
+ if args.dry_run and total_changes > 0:
+ print("\nRun without --dry-run to apply changes")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/rustdoc_utils.py b/scripts/rustdoc_utils.py
new file mode 100644
index 00000000..ebeb5874
--- /dev/null
+++ b/scripts/rustdoc_utils.py
@@ -0,0 +1,772 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: MIT OR Apache-2.0
+# SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors
+
+"""
+Shared utilities for handling Rust code examples with rustdoc-style attributes.
+
+This module provides common functionality used by:
+- migrate_rust_examples.py: Convert existing code-block directives
+- extract_rust_examples.py: Extract examples for testing
+- check_rust_examples.py: Validate and test examples
+"""
+
+import json
+import re
+import subprocess
+import tempfile
+from dataclasses import dataclass
+from pathlib import Path
+from typing import Dict, List, Optional, Tuple
+
+# Regex patterns for parsing RST
+RST_CODE_BLOCK_PATTERN = re.compile(
+ r'^(\s*)\.\.\ code-block::\ rust\s*$',
+ re.MULTILINE
+)
+
+RST_RUST_EXAMPLE_PATTERN = re.compile(
+ r'^(\s*)\.\.\ rust-example::\s*$',
+ re.MULTILINE
+)
+
+# Pattern to match directive options (lines starting with :option:)
+RST_OPTION_PATTERN = re.compile(r'^(\s+):(\w+):(.*)$')
+
+# Pattern to extract content indentation
+RST_CONTENT_INDENT_PATTERN = re.compile(r'^(\s+)(\S)')
+
+
+@dataclass
+class RustExample:
+ """Represents a Rust code example extracted from documentation."""
+
+ # Source location
+ source_file: str
+ line_number: int
+
+ # The code itself
+ code: str
+ display_code: str # Code as displayed (hidden lines may be stripped)
+
+ # Rustdoc attributes
+ attr: Optional[str] = None # ignore, compile_fail, should_panic, no_run
+ attr_value: Optional[str] = None # e.g., error code for compile_fail
+
+ # Version/edition requirements
+ min_version: Optional[str] = None # Minimum Rust version, e.g., "1.79"
+ channel: str = "stable" # stable, beta, or nightly
+ edition: str = "2021" # Rust edition: 2015, 2018, 2021, 2024
+
+ # Metadata
+ example_name: str = ""
+ parent_directive: str = "" # compliant_example, non_compliant_example, etc.
+ parent_id: str = ""
+ guideline_id: str = ""
+
+ def to_dict(self) -> Dict:
+ """Convert to dictionary for JSON serialization."""
+ return {
+ 'source_file': self.source_file,
+ 'line_number': self.line_number,
+ 'code': self.code,
+ 'display_code': self.display_code,
+ 'attr': self.attr,
+ 'attr_value': self.attr_value,
+ 'min_version': self.min_version,
+ 'channel': self.channel,
+ 'edition': self.edition,
+ 'example_name': self.example_name,
+ 'parent_directive': self.parent_directive,
+ 'parent_id': self.parent_id,
+ 'guideline_id': self.guideline_id,
+ }
+
+ @classmethod
+ def from_dict(cls, data: Dict) -> 'RustExample':
+ """Create from dictionary."""
+ return cls(
+ source_file=data['source_file'],
+ line_number=data['line_number'],
+ code=data['code'],
+ display_code=data.get('display_code', data['code']),
+ attr=data.get('attr'),
+ attr_value=data.get('attr_value'),
+ min_version=data.get('min_version'),
+ channel=data.get('channel', 'stable'),
+ edition=data.get('edition', '2021'),
+ example_name=data.get('example_name', ''),
+ parent_directive=data.get('parent_directive', ''),
+ parent_id=data.get('parent_id', ''),
+ guideline_id=data.get('guideline_id', ''),
+ )
+
+
+@dataclass
+class TestResult:
+ """Result of testing a single Rust example."""
+
+ example: RustExample
+ passed: bool
+ expected_to_fail: bool = False
+ skipped: bool = False
+ skip_reason: str = ""
+ error_message: str = ""
+ compiler_output: str = ""
+
+ def to_dict(self) -> Dict:
+ """Convert to dictionary for JSON serialization."""
+ return {
+ 'example': self.example.to_dict(),
+ 'passed': self.passed,
+ 'expected_to_fail': self.expected_to_fail,
+ 'skipped': self.skipped,
+ 'skip_reason': self.skip_reason,
+ 'error_message': self.error_message,
+ 'compiler_output': self.compiler_output,
+ }
+
+
+def process_hidden_lines(code: str) -> Tuple[str, str]:
+ """
+ Process code to separate hidden lines (prefixed with `# `).
+
+ Rustdoc convention:
+ - Lines starting with `# ` (hash + space) are hidden in docs but compiled
+ - Lines starting with `##` become `#` in the output
+
+ Args:
+ code: The raw code with potential hidden line markers
+
+ Returns:
+ Tuple of (display_code, full_code_for_testing)
+ """
+ lines = code.split('\n')
+ display_lines = []
+ full_lines = []
+
+ for line in lines:
+ if line.startswith('# '):
+ # Hidden line - include in full code without the marker
+ full_lines.append(line[2:])
+ elif line == '#':
+ # Empty hidden line
+ full_lines.append('')
+ elif line.startswith('## '):
+ # Escaped hash - show as single hash
+ display_lines.append('#' + line[2:])
+ full_lines.append('#' + line[2:])
+ else:
+ display_lines.append(line)
+ full_lines.append(line)
+
+ return '\n'.join(display_lines), '\n'.join(full_lines)
+
+
+def get_rust_version() -> Tuple[Optional[str], str]:
+ """
+ Get the current Rust compiler version and channel.
+
+ Returns:
+ Tuple of (version_string, channel) where version_string is like "1.75.0"
+ and channel is "stable", "beta", or "nightly".
+ Returns (None, "unknown") if rustc is not available.
+ """
+ try:
+ result = subprocess.run(
+ ['rustc', '--version'],
+ capture_output=True,
+ text=True,
+ timeout=10
+ )
+ if result.returncode != 0:
+ return None, "unknown"
+
+ # Parse version string like "rustc 1.75.0 (82e1608df 2023-12-21)"
+ # or "rustc 1.79.0-nightly (abc123 2024-01-01)"
+ output = result.stdout.strip()
+ match = re.search(r'rustc (\d+\.\d+\.\d+)(?:-(\w+))?', output)
+ if match:
+ version = match.group(1)
+ channel = match.group(2) if match.group(2) else "stable"
+ # Normalize channel names
+ if channel not in ("stable", "beta", "nightly"):
+ channel = "stable" # Release versions without suffix are stable
+ return version, channel
+ return None, "unknown"
+ except (subprocess.TimeoutExpired, FileNotFoundError, Exception):
+ return None, "unknown"
+
+
+def parse_version(version_str: str) -> Tuple[int, int, int]:
+ """
+ Parse a version string into a tuple of (major, minor, patch).
+
+ Args:
+ version_str: Version string like "1.75" or "1.75.0"
+
+ Returns:
+ Tuple of (major, minor, patch)
+ """
+ parts = version_str.split('.')
+ major = int(parts[0]) if len(parts) > 0 else 0
+ minor = int(parts[1]) if len(parts) > 1 else 0
+ patch = int(parts[2]) if len(parts) > 2 else 0
+ return (major, minor, patch)
+
+
+def version_satisfied(current: str, required: str) -> bool:
+ """
+ Check if the current version satisfies the minimum required version.
+
+ Args:
+ current: Current Rust version (e.g., "1.75.0")
+ required: Minimum required version (e.g., "1.79")
+
+ Returns:
+ True if current >= required
+ """
+ current_tuple = parse_version(current)
+ required_tuple = parse_version(required)
+ return current_tuple >= required_tuple
+
+
+def channel_satisfied(current: str, required: str) -> bool:
+ """
+ Check if the current channel satisfies the required channel.
+
+ Channel hierarchy: nightly > beta > stable
+ - stable code can run on any channel
+ - beta code can run on beta or nightly
+ - nightly code can only run on nightly
+
+ Args:
+ current: Current Rust channel
+ required: Required channel
+
+ Returns:
+ True if current channel can run the required code
+ """
+ channel_order = {"stable": 0, "beta": 1, "nightly": 2}
+ current_level = channel_order.get(current, 0)
+ required_level = channel_order.get(required, 0)
+ return current_level >= required_level
+
+
+def add_hidden_lines(code: str, hidden_prefix: str = "") -> str:
+ """
+ Add hidden line markers to code.
+
+ Args:
+ code: The code to add markers to
+ hidden_prefix: Lines starting with this will be marked as hidden
+
+ Returns:
+ Code with hidden line markers added
+ """
+ if not hidden_prefix:
+ return code
+
+ lines = code.split('\n')
+ result_lines = []
+
+ for line in lines:
+ if line.strip().startswith(hidden_prefix):
+ result_lines.append('# ' + line)
+ else:
+ result_lines.append(line)
+
+ return '\n'.join(result_lines)
+
+
+def wrap_in_main(code: str) -> str:
+ """
+ Wrap code in a main function if it doesn't have one.
+
+ This is similar to what rustdoc does for doc tests.
+ """
+ # Check if code already has a main function
+ if re.search(r'\bfn\s+main\s*\(', code):
+ return code
+
+ # Check if it has any function definitions at all
+ has_functions = re.search(r'\bfn\s+\w+\s*[<(]', code)
+ has_impl = re.search(r'\bimpl\b', code)
+ has_struct = re.search(r'\bstruct\b', code)
+ has_enum = re.search(r'\benum\b', code)
+ has_trait = re.search(r'\btrait\b', code)
+ has_use = re.search(r'\buse\b', code)
+ has_mod = re.search(r'\bmod\b', code)
+ has_const = re.search(r'\bconst\b', code)
+ has_static = re.search(r'\bstatic\b', code)
+ has_type = re.search(r'\btype\b', code)
+
+ # If it looks like top-level items, don't wrap
+ if has_functions or has_impl or has_struct or has_enum or has_trait or has_mod or has_type:
+ # But we might still need a main if there's code outside functions
+ return code
+
+ # If it's just statements, wrap in main
+ if has_use or has_const or has_static:
+ # Keep use/const/static at top level, wrap the rest
+ lines = code.split('\n')
+ top_level = []
+ body = []
+ in_top_level = True
+
+ for line in lines:
+ stripped = line.strip()
+ if in_top_level and (stripped.startswith('use ') or
+ stripped.startswith('const ') or
+ stripped.startswith('static ')):
+ top_level.append(line)
+ else:
+ in_top_level = False
+ body.append(line)
+
+ if body:
+ indented_body = '\n'.join(' ' + line if line.strip() else '' for line in body)
+ return '\n'.join(top_level) + '\n\nfn main() {\n' + indented_body + '\n}'
+ else:
+ return '\n'.join(top_level) + '\n\nfn main() {}'
+
+ # Simple case: wrap everything
+ indented = '\n'.join(' ' + line if line.strip() else '' for line in code.split('\n'))
+ return 'fn main() {\n' + indented + '\n}'
+
+
+def load_prelude(prelude_path: Optional[str], required: bool = True) -> str:
+ """
+ Load the shared prelude file.
+
+ Args:
+ prelude_path: Path to the prelude file
+ required: If True, exit with error if file not found
+
+ Returns:
+ Contents of the prelude file, or empty string if not provided
+ """
+ if not prelude_path:
+ return ""
+
+ path = Path(prelude_path)
+ if path.exists():
+ return path.read_text()
+
+ # File specified but not found
+ if required:
+ print(f"โ Error: Prelude file not found: {prelude_path}")
+ print(" Examples that use ArithmeticError, DivError, etc. will fail without the prelude.")
+ import sys
+ sys.exit(1)
+ else:
+ print(f"โ ๏ธ Warning: Prelude file not found: {prelude_path}")
+ return ""
+
+
+def generate_doctest(
+ example: RustExample,
+ prelude: str = "",
+ wrap_main: bool = True
+) -> str:
+ """
+ Generate a rustdoc-style doctest from an example.
+
+ Args:
+ example: The example to convert
+ prelude: Optional prelude code to prepend
+ wrap_main: Whether to wrap in main() if needed
+
+ Returns:
+ The complete test code
+ """
+ code = example.code
+
+ # Extract inner attributes (like #![feature(...)]) - they must be at file start
+ inner_attrs = []
+ remaining_lines = []
+ for line in code.split('\n'):
+ stripped = line.strip()
+ if stripped.startswith('#![') and not remaining_lines:
+ # This is an inner attribute at the start of code
+ inner_attrs.append(line)
+ elif not stripped and not remaining_lines:
+ # Skip leading blank lines before checking for more inner attrs
+ continue
+ else:
+ remaining_lines.append(line)
+
+ # Reconstruct code without leading inner attributes
+ code = '\n'.join(remaining_lines)
+
+ # Build final code: inner attrs first, then prelude, then code
+ parts = []
+ if inner_attrs:
+ parts.append('\n'.join(inner_attrs))
+ if prelude:
+ parts.append(prelude)
+ parts.append(code)
+
+ code = '\n\n'.join(parts)
+
+ # Wrap in main if needed
+ if wrap_main:
+ code = wrap_in_main(code)
+
+ return code
+
+
+def generate_test_crate(
+ examples: List[RustExample],
+ output_dir: Path,
+ prelude: str = "",
+ crate_name: str = "guidelines_examples"
+) -> Path:
+ """
+ Generate a Cargo crate containing all examples as doc tests.
+
+ Args:
+ examples: List of examples to include
+ output_dir: Directory to create the crate in
+ prelude: Optional shared prelude code
+ crate_name: Name for the generated crate
+
+ Returns:
+ Path to the generated crate
+ """
+ crate_dir = output_dir / crate_name
+ crate_dir.mkdir(parents=True, exist_ok=True)
+
+ # Create Cargo.toml
+ cargo_toml = f"""[package]
+name = "{crate_name}"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+doctest = true
+"""
+ (crate_dir / "Cargo.toml").write_text(cargo_toml)
+
+ # Create src directory
+ src_dir = crate_dir / "src"
+ src_dir.mkdir(exist_ok=True)
+
+ # Generate lib.rs with all examples as doc tests
+ lib_content = generate_lib_rs(examples, prelude)
+ (src_dir / "lib.rs").write_text(lib_content)
+
+ return crate_dir
+
+
+def generate_lib_rs(examples: List[RustExample], prelude: str = "") -> str:
+ """
+ Generate lib.rs content with all examples as doc tests.
+
+ Args:
+ examples: List of examples to include
+ prelude: Optional shared prelude code
+
+ Returns:
+ The lib.rs content
+ """
+ sections = []
+
+ # Add module-level documentation with examples
+ sections.append("//! # Coding Guidelines Examples")
+ sections.append("//!")
+ sections.append("//! This crate contains all code examples from the Safety-Critical Rust Coding Guidelines.")
+ sections.append("//! Each example is tested as a rustdoc test.")
+ sections.append("")
+
+ for i, example in enumerate(examples):
+ # Generate a unique function/module for each example
+ example_id = example.example_name or f"example_{i}"
+ safe_id = re.sub(r'[^a-zA-Z0-9_]', '_', example_id)
+
+ # Build the rustdoc attribute
+ attr_line = ""
+ if example.attr == 'ignore':
+ attr_line = "/// ```ignore"
+ elif example.attr == 'compile_fail':
+ if example.attr_value:
+ attr_line = f"/// ```compile_fail,{example.attr_value}"
+ else:
+ attr_line = "/// ```compile_fail"
+ elif example.attr == 'should_panic':
+ if example.attr_value:
+ attr_line = f'/// ```should_panic = "{example.attr_value}"'
+ else:
+ attr_line = "/// ```should_panic"
+ elif example.attr == 'no_run':
+ attr_line = "/// ```no_run"
+ else:
+ attr_line = "/// ```"
+
+ # Build the doc comment
+ sections.append(f"/// Example from {example.source_file}:{example.line_number}")
+ if example.parent_id:
+ sections.append(f"/// Parent: {example.parent_id}")
+ sections.append("///")
+ sections.append(attr_line)
+
+ # Add prelude as hidden lines if present
+ if prelude:
+ for line in prelude.split('\n'):
+ if line.strip():
+ sections.append(f"/// # {line}")
+
+ # Add the example code
+ code = example.code
+ for line in code.split('\n'):
+ sections.append(f"/// {line}")
+
+ sections.append("/// ```")
+ sections.append(f"pub fn {safe_id}() {{}}")
+ sections.append("")
+
+ return '\n'.join(sections)
+
+
+def run_doctests(crate_dir: Path) -> Tuple[bool, str, List[TestResult]]:
+ """
+ Run cargo test --doc on a generated crate.
+
+ Args:
+ crate_dir: Path to the crate directory
+
+ Returns:
+ Tuple of (all_passed, output, list of TestResult)
+ """
+ try:
+ result = subprocess.run(
+ ["cargo", "test", "--doc"],
+ cwd=crate_dir,
+ capture_output=True,
+ text=True,
+ timeout=300 # 5 minute timeout
+ )
+
+ output = result.stdout + "\n" + result.stderr
+ passed = result.returncode == 0
+
+ return passed, output, []
+
+ except subprocess.TimeoutExpired:
+ return False, "Test execution timed out", []
+ except Exception as e:
+ return False, f"Error running tests: {e}", []
+
+
+def compile_single_example(
+ example: RustExample,
+ prelude: str = "",
+ current_version: Optional[str] = None,
+ current_channel: str = "stable"
+) -> TestResult:
+ """
+ Compile a single example and check if it meets expectations.
+
+ Args:
+ example: The example to compile
+ prelude: Optional prelude code
+ current_version: Current Rust version (detected if not provided)
+ current_channel: Current Rust channel (detected if not provided)
+
+ Returns:
+ TestResult with compilation outcome
+ """
+ # Check version compatibility
+ if example.min_version and current_version:
+ if not version_satisfied(current_version, example.min_version):
+ return TestResult(
+ example=example,
+ passed=True, # Not a failure, just skipped
+ skipped=True,
+ skip_reason=f"Requires Rust {example.min_version}+ (have {current_version})",
+ )
+
+ # Check channel compatibility
+ if example.channel and example.channel != "stable":
+ if not channel_satisfied(current_channel, example.channel):
+ return TestResult(
+ example=example,
+ passed=True, # Not a failure, just skipped
+ skipped=True,
+ skip_reason=f"Requires {example.channel} channel (have {current_channel})",
+ )
+
+ # Skip ignored examples
+ if example.attr == 'ignore':
+ return TestResult(
+ example=example,
+ passed=True,
+ expected_to_fail=False,
+ skipped=True,
+ skip_reason="ignore attribute",
+ )
+
+ # Determine expected outcome
+ should_fail = example.attr == 'compile_fail'
+
+ # Generate the test code
+ code = generate_doctest(example, prelude, wrap_main=True)
+
+ # Write to temp file and compile
+ with tempfile.TemporaryDirectory() as tmpdir:
+ src_file = Path(tmpdir) / "test.rs"
+ src_file.write_text(code)
+
+ try:
+ out_file = Path(tmpdir) / "test_binary"
+ edition = example.edition or "2021"
+ result = subprocess.run(
+ ["rustc", f"--edition={edition}", "--crate-type=bin", "-o", str(out_file), str(src_file)],
+ capture_output=True,
+ text=True,
+ timeout=30
+ )
+
+ compiled_successfully = result.returncode == 0
+ compiler_output = result.stderr
+
+ if should_fail:
+ # Expected to fail
+ if compiled_successfully:
+ return TestResult(
+ example=example,
+ passed=False,
+ expected_to_fail=True,
+ error_message="Expected compilation to fail, but it succeeded",
+ compiler_output=compiler_output,
+ )
+ else:
+ # Check error code if specified
+ if example.attr_value:
+ if example.attr_value in compiler_output:
+ return TestResult(
+ example=example,
+ passed=True,
+ expected_to_fail=True,
+ compiler_output=compiler_output,
+ )
+ else:
+ return TestResult(
+ example=example,
+ passed=False,
+ expected_to_fail=True,
+ error_message=f"Expected error {example.attr_value} not found in output",
+ compiler_output=compiler_output,
+ )
+ else:
+ return TestResult(
+ example=example,
+ passed=True,
+ expected_to_fail=True,
+ compiler_output=compiler_output,
+ )
+ else:
+ # Expected to compile
+ if compiled_successfully:
+ return TestResult(
+ example=example,
+ passed=True,
+ expected_to_fail=False,
+ )
+ else:
+ return TestResult(
+ example=example,
+ passed=False,
+ expected_to_fail=False,
+ error_message="Compilation failed unexpectedly",
+ compiler_output=compiler_output,
+ )
+
+ except subprocess.TimeoutExpired:
+ return TestResult(
+ example=example,
+ passed=False,
+ expected_to_fail=should_fail,
+ error_message="Compilation timed out",
+ )
+ except Exception as e:
+ return TestResult(
+ example=example,
+ passed=False,
+ expected_to_fail=should_fail,
+ error_message=f"Error: {e}",
+ )
+
+
+def format_test_results(results: List[TestResult]) -> str:
+ """
+ Format test results for display.
+
+ Args:
+ results: List of test results
+
+ Returns:
+ Formatted string
+ """
+ lines = []
+ passed = sum(1 for r in results if r.passed and not r.skipped)
+ skipped = sum(1 for r in results if r.skipped)
+ failed = sum(1 for r in results if not r.passed)
+
+ lines.append(f"{'='*60}")
+ if skipped:
+ lines.append(f"Test Results: {passed} passed, {failed} failed, {skipped} skipped")
+ else:
+ lines.append(f"Test Results: {passed} passed, {failed} failed")
+ lines.append(f"{'='*60}")
+
+ # Show failures first
+ failures = [r for r in results if not r.passed]
+ if failures:
+ lines.append("\nFAILURES:")
+ lines.append("-" * 40)
+
+ for result in failures:
+ example = result.example
+ lines.append(f"\n๐ {example.source_file}:{example.line_number}")
+ if example.parent_id:
+ lines.append(f" Parent: {example.parent_id}")
+ lines.append(f" โ {result.error_message}")
+
+ if result.compiler_output:
+ lines.append(" Compiler output:")
+ for line in result.compiler_output.split('\n')[:20]: # Limit output
+ lines.append(f" {line}")
+ if len(result.compiler_output.split('\n')) > 20:
+ lines.append(" ... (truncated)")
+
+ # Show skipped tests
+ skipped_results = [r for r in results if r.skipped]
+ if skipped_results:
+ lines.append("\nSKIPPED:")
+ lines.append("-" * 40)
+
+ for result in skipped_results:
+ example = result.example
+ lines.append(f"\nโญ๏ธ {example.source_file}:{example.line_number}")
+ lines.append(f" {result.skip_reason}")
+
+ return '\n'.join(lines)
+
+
+def save_results_json(results: List[TestResult], output_path: Path):
+ """Save test results to JSON file."""
+ passed = sum(1 for r in results if r.passed and not r.skipped)
+ skipped = sum(1 for r in results if r.skipped)
+ failed = sum(1 for r in results if not r.passed)
+
+ data = {
+ 'total': len(results),
+ 'passed': passed,
+ 'failed': failed,
+ 'skipped': skipped,
+ 'results': [r.to_dict() for r in results],
+ }
+
+ with open(output_path, 'w') as f:
+ json.dump(data, f, indent=2)
diff --git a/src/coding-guidelines/associated-items.rst b/src/coding-guidelines/associated-items.rst
index a45edecc..dcf3e805 100644
--- a/src/coding-guidelines/associated-items.rst
+++ b/src/coding-guidelines/associated-items.rst
@@ -30,25 +30,27 @@ Associated Items
The below function ``concat_strings`` is not complaint because it call itself and depending on depth of data provided as input it could generate an stack overflow exception or undefine behavior.
- .. code-block:: rust
+ .. rust-example::
// Recursive enum to represent a string or a list of `MyEnum`
- enum MyEnum {
- Str(String),
- List(Vec),
- }
-
- // Concatenates strings from a nested structure of `MyEnum` using recursion.
- fn concat_strings(input: &[MyEnum]) -> String {
- let mut result = String::new();
- for item in input {
- match item {
- MyEnum::Str(s) => result.push_str(s),
- MyEnum::List(list) => result.push_str(&concat_strings(list)),
- }
- }
- result
- }
+ enum MyEnum {
+ Str(String),
+ List(Vec),
+ }
+
+ // Concatenates strings from a nested structure of `MyEnum` using recursion.
+ fn concat_strings(input: &[MyEnum]) -> String {
+ let mut result = String::new();
+ for item in input {
+ match item {
+ MyEnum::Str(s) => result.push_str(s),
+ MyEnum::List(list) => result.push_str(&concat_strings(list)),
+ }
+ }
+ result
+ }
+ #
+ # fn main() {}
.. compliant_example::
:id: compl_ex_9pK3h65rfceO
@@ -56,37 +58,39 @@ Associated Items
The following code implements the same functionality using iteration instead of recursion. The ``stack`` variable is used to maintain the processing context at each step of the loop. This approach provides explicit control over memory usage. If the stack grows beyond a predefined limit due to the structure or size of the input, the function returns an error rather than risking a stack overflow or out-of-memory exception. This ensures more predictable and robust behavior in resource-constrained environments.
- .. code-block:: rust
+ .. rust-example::
// Recursive enum to represent a string or a list of `MyEnum`
- enum MyEnum {
- Str(String),
- List(Vec),
- }
-
- /// Concatenates strings from a nested structure of `MyEnum` without using recursion.
- /// Returns an error if the stack size exceeds `MAX_STACK_SIZE`.
- fn concat_strings_non_recursive(input: &[MyEnum]) -> Result {
- const MAX_STACK_SIZE: usize = 1000;
- let mut result = String::new();
- let mut stack = Vec::new();
-
- // Add all items to the stack
- stack.extend(input.iter());
-
- while let Some(item) = stack.pop() {
- match item {
- MyEnum::Str(s) => result.insert_str(0, s),
- MyEnum::List(list) => {
- // Add list items to the stack
- for sub_item in list.iter() {
- stack.push(sub_item);
- if stack.len() > MAX_STACK_SIZE {
- return Err("Too big structure");
- }
- }
- }
- }
- }
- Ok(result)
- }
+ enum MyEnum {
+ Str(String),
+ List(Vec),
+ }
+
+ /// Concatenates strings from a nested structure of `MyEnum` without using recursion.
+ /// Returns an error if the stack size exceeds `MAX_STACK_SIZE`.
+ fn concat_strings_non_recursive(input: &[MyEnum]) -> Result {
+ const MAX_STACK_SIZE: usize = 1000;
+ let mut result = String::new();
+ let mut stack = Vec::new();
+
+ // Add all items to the stack
+ stack.extend(input.iter());
+
+ while let Some(item) = stack.pop() {
+ match item {
+ MyEnum::Str(s) => result.insert_str(0, s),
+ MyEnum::List(list) => {
+ // Add list items to the stack
+ for sub_item in list.iter() {
+ stack.push(sub_item);
+ if stack.len() > MAX_STACK_SIZE {
+ return Err("Too big structure");
+ }
+ }
+ }
+ }
+ }
+ Ok(result)
+ }
+ #
+ # fn main() {}
diff --git a/src/coding-guidelines/expressions.rst b/src/coding-guidelines/expressions.rst
index 531da9a0..4e85019e 100644
--- a/src/coding-guidelines/expressions.rst
+++ b/src/coding-guidelines/expressions.rst
@@ -51,12 +51,14 @@ Expressions
This noncompliant code example can result in arithmetic overflow during the addition of the signed operands ``si_a`` and ``si_b``:
- .. code-block:: rust
+ .. rust-example::
fn add(si_a: i32, si_b: i32) {
- let sum: i32 = si_a + si_b;
+ let _sum: i32 = si_a + si_b;
// ...
}
+ #
+ # fn main() {}
.. compliant_example::
:id: compl_ex_BgUHiRB4kc4b_1
@@ -72,13 +74,10 @@ Expressions
Code that invoked these functions would typically further restrict the range of possible values,
based on the anticipated range of the inputs.
- .. code-block:: rust
-
- enum ArithmeticError {
- Overflow,
- DivisionByZero,
- }
+ .. rust-example::
+ # #[derive(Debug)]
+ # enum ArithmeticError { Overflow, DivisionByZero }
use std::i32::{MAX as INT_MAX, MIN as INT_MIN};
fn add(si_a: i32, si_b: i32) -> Result {
@@ -117,6 +116,8 @@ Expressions
Ok(si_a * si_b)
}
}
+ #
+ # fn main() {}
.. compliant_example::
:id: compl_ex_BgUHiRB4kc4c
@@ -125,8 +126,10 @@ Expressions
This compliant example uses safe checked addition instead of manual bounds checks.
Checked functions can reduce readability when complex arithmetic expressions are needed.
- .. code-block:: rust
+ .. rust-example::
+ # #[derive(Debug)]
+ # enum ArithmeticError { Overflow, DivisionByZero }
fn add(si_a: i32, si_b: i32) -> Result {
si_a.checked_add(si_b).ok_or(ArithmeticError::Overflow)
}
@@ -138,6 +141,8 @@ Expressions
fn mul(a: i32, b: i32) -> Result {
a.checked_mul(b).ok_or(ArithmeticError::Overflow)
}
+ #
+ # fn main() {}
.. compliant_example::
:id: compl_ex_BgUHiRB4kc4b
@@ -145,7 +150,7 @@ Expressions
Wrapping behavior must be explicitly requested. This compliant example uses wrapping functions.
- .. code-block:: rust
+ .. rust-example::
fn add(a: i32, b: i32) -> i32 {
a.wrapping_add(b)
@@ -158,6 +163,8 @@ Expressions
fn mul(a: i32, b: i32) -> i32 {
a.wrapping_mul(b)
}
+ #
+ # fn main() {}
.. compliant_example::
:id: compl_ex_BhUHiRB4kc4b
@@ -169,7 +176,7 @@ Expressions
The ``Wrapping`` type provides a consistent way to force wrapping behavior in all build modes,
which is useful in specific scenarios like implementing cryptography or hash functions where wrapping arithmetic is the intended behavior.
- .. code-block:: rust
+ .. rust-example::
use std::num::Wrapping;
@@ -204,7 +211,7 @@ Expressions
Saturation semantics always conform to this rule because they ensure that integer operations do not result in arithmetic overflow.
This compliant solution shows how to use saturating functions to provide saturation semantics for some basic arithmetic operations.
- .. code-block:: rust
+ .. rust-example::
fn add(a: i32, b: i32) -> i32 {
a.saturating_add(b)
@@ -217,16 +224,18 @@ Expressions
fn mul(a: i32, b: i32) -> i32 {
a.saturating_mul(b)
}
+ #
+ # fn main() {}
.. compliant_example::
:id: compl_ex_BgUHiSB4kd4b
:status: draft
- ``Saturating`` is a wrapper type in Rustโs ``core`` library (``core::num::Saturating``) that makes arithmetic operations on the wrapped value perform saturating arithmetic instead of wrapping, panicking, or overflowing.
+ ``Saturating`` is a wrapper type in Rust's ``core`` library (``core::num::Saturating``) that makes arithmetic operations on the wrapped value perform saturating arithmetic instead of wrapping, panicking, or overflowing.
``Saturating`` is useful when you have a section of code or a data type where all arithmetic must be saturating.
This compliant solution uses the ``Saturating`` type to define several functions that perform basic integer operations using saturation semantics.
- .. code-block:: rust
+ .. rust-example::
use std::num::Saturating;
@@ -254,8 +263,10 @@ Expressions
This noncompliant code example example prevents divide-by-zero errors, but does not prevent arithmetic overflow.
- .. code-block:: rust
+ .. rust-example::
+ # #[derive(Debug)]
+ # enum DivError { DivisionByZero, Overflow }
fn div(s_a: i64, s_b: i64) -> Result {
if s_b == 0 {
Err(DivError::DivisionByZero)
@@ -263,6 +274,8 @@ Expressions
Ok(s_a / s_b)
}
}
+ #
+ # fn main() {}
.. compliant_example::
:id: compl_ex_BgUHiRB4kc4d
@@ -270,18 +283,21 @@ Expressions
This compliant solution eliminates the possibility of both divide-by-zero errors and arithmetic overflow:
- .. code-block:: rust
-
+ .. rust-example::
+ # #[derive(Debug)]
+ # enum DivError { DivisionByZero, Overflow }
fn div(s_a: i64, s_b: i64) -> Result {
if s_b == 0 {
- Err("division by zero")
+ Err(DivError::DivisionByZero)
} else if s_a == i64::MIN && s_b == -1 {
- Err("arithmetic overflow")
+ Err(DivError::Overflow)
} else {
Ok(s_a / s_b)
}
}
+ #
+ # fn main() {}
.. guideline:: Avoid as underscore pointer casts
:id: gui_HDnAZ7EZ4z6G
@@ -312,7 +328,7 @@ Expressions
The following code leaves it up to type inference to figure out the concrete types of the raw pointer casts, allowing changes to ``with_base``'s function signature to affect the types the function body of ``non_compliant_example`` without incurring a compiler error.
- .. code-block:: rust
+ .. rust-example::
#[repr(C)]
struct Base {
@@ -330,7 +346,9 @@ Expressions
with_base(unsafe { &*(extended as *const _) })
}
- fn with_base(_: &Base) { ... }
+ fn with_base(_: &Base) {}
+ #
+ # fn main() {}
.. compliant_example::
:id: compl_ex_W08ckDrkOhkt
@@ -338,7 +356,7 @@ Expressions
We specify the concrete target types for our pointer casts resulting in a compilation error if the function signature of ``with_base`` is changed.
- .. code-block:: rust
+ .. rust-example::
#[repr(C)]
struct Base {
@@ -351,12 +369,14 @@ Expressions
scale: f32
}
- fn non_compliant_example(extended: &Extended) {
+ fn compliant_example(extended: &Extended) {
let extended = extended as *const Extended;
with_base(unsafe { &*(extended as *const Base) })
}
- fn with_base(_: &Base) { ... }
+ fn with_base(_: &Base) {}
+ #
+ # fn main() {}
.. guideline:: Do not use an integer type as a divisor during integer division
:id: gui_7y0GAMmtMhch
@@ -402,11 +422,14 @@ Expressions
Both the division and remainder operations in this non-compliant example will panic if evaluated because the right operand is zero.
- .. code-block:: rust
+ .. rust-example::
+ :compile_fail:
- let x = 0;
- let y = 5 / x; // This line will panic.
- let z = 5 % x; // This line would also panic.
+ fn main() {
+ let x = 0;
+ let _y = 5 / x; // This line will panic.
+ let _z = 5 % x; // This line would also panic.
+ }
.. compliant_example::
:id: compl_ex_k1CD6xoZxhXb
@@ -417,19 +440,21 @@ Expressions
Using checked division and remainder is particularly important in the signed integer case,
where arithmetic overflow can also occur when dividing the minimum representable value by -1.
- .. code-block:: rust
+ .. rust-example::
- // Using the checked division API
- let y = match 5i32.checked_div(0) {
- None => 0
- Some(r) => r
- };
+ fn main() {
+ // Using the checked division API
+ let _y = match 5i32.checked_div(0) {
+ None => 0,
+ Some(r) => r,
+ };
- // Using the checked remainder API
- let z = match 5i32.checked_rem(0) {
- None => 0
- Some(r) => r
- };
+ // Using the checked remainder API
+ let _z = match 5i32.checked_rem(0) {
+ None => 0,
+ Some(r) => r,
+ };
+ }
.. compliant_example::
:id: compl_ex_k1CD6xoZxhXc
@@ -443,11 +468,16 @@ Expressions
Note that the test for arithmetic overflow that occurs when dividing the minimum representable value by -1 is unnecessary
in this compliant example because the result of the division expression is an unsigned integer type.
- .. code-block:: rust
+ .. rust-example::
+ :version: 1.79
+
+ use std::num::NonZero;
- let x = 0u32;
- if let Some(divisor) = match NonZero::::new(x) {
- let result = 5u32 / divisor;
+ fn main() {
+ let x = 0u32;
+ if let Some(divisor) = NonZero::::new(x) {
+ let _result = 5u32 / divisor;
+ }
}
.. guideline:: Do not divide by 0
@@ -501,11 +531,14 @@ Expressions
This non-compliant example panics when the right operand is zero for either the division or remainder operations.
- .. code-block:: rust
+ .. rust-example::
+ :compile_fail:
- let x = 0;
- let y = 5 / x; // Results in a panic.
- let z = 5 % x; // Also results in a panic.
+ fn main() {
+ let x = 0;
+ let _y = 5 / x; // Results in a panic.
+ let _z = 5 % x; // Also results in a panic.
+ }
.. compliant_example::
:id: compl_ex_Ri9pP5Ch3kcc
@@ -518,21 +551,23 @@ Expressions
Note that the test for arithmetic overflow is not necessary for unsigned integers.
- .. code-block:: rust
-
- // Checking for zero by hand
- let x = 0u32;
- let y = if x != 0u32 {
- 5u32 / x
- } else {
- 0u32
- };
-
- let z = if x != 0u32 {
- 5u32 % x
- } else {
- 0u32
- };
+ .. rust-example::
+
+ fn main() {
+ // Checking for zero by hand
+ let x = 0u32;
+ let _y = if x != 0u32 {
+ 5u32 / x
+ } else {
+ 0u32
+ };
+
+ let _z = if x != 0u32 {
+ 5u32 % x
+ } else {
+ 0u32
+ };
+ }
.. guideline:: The 'as' operator should not be used with numeric operands
:id: gui_ADHABsmK9FXz
@@ -585,27 +620,32 @@ Expressions
Even when it doesn't, nothing enforces the correct behaviour or communicates whether
we intend to allow lossy conversions, or only expect valid conversions.
- .. code-block:: rust
+ .. rust-example::
fn f1(x: u16, y: i32, z: u64, w: u8) {
- let a = w as char; // non-compliant
- let b = y as u32; // non-compliant - changes value range, converting negative values
- let c = x as i64; // non-compliant - could use .into()
+ let _a = w as char; // non-compliant
+ let _b = y as u32; // non-compliant - changes value range, converting negative values
+ let _c = x as i64; // non-compliant - could use .into()
let d = y as f32; // non-compliant - lossy
let e = d as f64; // non-compliant - could use .into()
- let f = e as f32; // non-compliant - lossy
+ let _f = e as f32; // non-compliant - lossy
- let g = e as i64; // non-compliant - lossy despite object size
+ let _g = e as i64; // non-compliant - lossy despite object size
+ let b: u32 = 0;
let p1: * const u32 = &b;
- let a1 = p1 as usize; // compliant by exception
- let a2 = p1 as u16; // non-compliant - may lose address range
- let a3 = p1 as u64; // non-compliant - use usize to indicate intent
-
- let p2 = a1 as * const u32; // non-compliant - prefer transmute
- let p3 = a2 as * const u32; // non-compliant (and most likely not in a valid address range)
+ let _a1 = p1 as usize; // compliant by exception
+ let _a2 = p1 as u16; // non-compliant - may lose address range
+ let _a3 = p1 as u64; // non-compliant - use usize to indicate intent
+
+ let a1 = p1 as usize;
+ let _p2 = a1 as * const u32; // non-compliant - prefer transmute
+ let a2 = p1 as u16;
+ let _p3 = a2 as * const u32; // non-compliant (and most likely not in a valid address range)
}
+ #
+ # fn main() {}
.. compliant_example::
:id: compl_ex_uilHTIOgxD37
@@ -617,15 +657,17 @@ Expressions
communicate this and include an error check, with ``try_into`` or ``try_from``.
Other forms of conversion may find ``transmute`` better communicates their intent.
- .. code-block:: rust
+ .. rust-example::
+
+ use std::convert::TryInto;
- fn f2(x: u16, y: i32, z: u64, w: u8) {
- let a: char = w.into();
- let b: Result = y.try_into(); // produce an error on range clip
- let c: i64 = x.into();
+ fn f2(x: u16, y: i32, _z: u64, w: u8) {
+ let _a: char = w.into();
+ let _b: Result = y.try_into(); // produce an error on range clip
+ let _c: i64 = x.into();
let d = f32::from(x); // u16 is within range, u32 is not
- let e = f64::from(d);
+ let _e = f64::from(d);
// let f = f32::from(e); // no From exists
// let g = ... // no From exists
@@ -635,21 +677,23 @@ Expressions
let a1 = p1 as usize; // (compliant)
unsafe {
- let a2: usize = std::mem::transmute(p1); // OK
- let a3: u64 = std::mem::transmute(p1); // OK, size is checked
+ let _a2: usize = std::mem::transmute(p1); // OK
+ let _a3: u64 = std::mem::transmute(p1); // OK, size is checked
// let a3: u16 = std::mem::transmute(p1); // invalid, different sizes
- let p2: * const u32 = std::mem::transmute(a1); // OK
- let p3: * const u32 = std::mem::transmute(a1); // OK
+ let _p2: * const u32 = std::mem::transmute(a1); // OK
+ let _p3: * const u32 = std::mem::transmute(a1); // OK
}
unsafe {
// does something entirely different,
// reinterpreting the bits of z as the IEEE bit pattern of a double
// precision object, rather than converting the integer value
- let f1: f64 = std::mem::transmute(z);
+ let _f1: f64 = std::mem::transmute(_z);
}
}
+ #
+ # fn main() {}
.. guideline:: An integer shall not be converted to a pointer
@@ -691,27 +735,29 @@ Expressions
Any use of ``as`` or ``transmute`` to create a pointer from an arithmetic address value
is non-compliant:
- .. code-block:: rust
+ .. rust-example::
fn f1(x: u16, y: i32, z: u64, w: usize) {
- let p1 = x as * const u32; // not compliant
- let p2 = y as * const u32; // not compliant
- let p3 = z as * const u32; // not compliant
- let p4 = w as * const u32; // not compliant despite being the right size
+ let _p1 = x as * const u32; // not compliant
+ let _p2 = y as * const u32; // not compliant
+ let _p3 = z as * const u32; // not compliant
+ let _p4 = w as * const u32; // not compliant despite being the right size
- let f: f64 = 10.0;
+ let _f: f64 = 10.0;
// let p5 = f as * const u32; // not valid
unsafe {
// let p5: * const u32 = std::mem::transmute(x); // not valid
// let p6: * const u32 = std::mem::transmute(y); // not valid
- let p7: * const u32 = std::mem::transmute(z); // not compliant
- let p8: * const u32 = std::mem::transmute(w); // not compliant
+ let _p7: * const u32 = std::mem::transmute(z); // not compliant
+ let _p8: * const u32 = std::mem::transmute(w); // not compliant
- let p9: * const u32 = std::mem::transmute(f); // not compliant, and very strange
+ let _p9: * const u32 = std::mem::transmute(_f); // not compliant, and very strange
}
}
+ #
+ # fn main() {}
.. compliant_example::
:id: compl_ex_oneKuF52yzrx
@@ -748,14 +794,16 @@ Expressions
silently interfere with the address value. On platforms where pointers are 64-bits this may have
particularly unexpected results.
- .. code-block:: rust
+ .. rust-example::
fn f1(flag: u32, ptr: * const u32) {
/* ... */
let mut rep = ptr as usize;
rep = (rep & 0x7fffff) | ((flag as usize) << 23);
- let p2 = rep as * const u32;
+ let _p2 = rep as * const u32;
}
+ #
+ # fn main() {}
.. compliant_example::
:id: compl_ex_oBoluiKSvREu
@@ -765,7 +813,7 @@ Expressions
This solution is portable to machines of different word sizes, both smaller and larger than 32 bits,
working even when pointers cannot be represented in any integer type.
- .. code-block:: rust
+ .. rust-example::
struct PtrFlag {
pointer: * const u32,
@@ -773,12 +821,14 @@ Expressions
}
fn f2(flag: u32, ptr: * const u32) {
- let ptrflag = PtrFlag {
+ let _ptrflag = PtrFlag {
pointer: ptr,
flag: flag
};
/* ... */
}
+ #
+ # fn main() {}
.. guideline:: Do not shift an expression by a negative number of bits or by greater than or equal to the bitwidth of the operand
:id: gui_RHvQj8BHlz9b
@@ -833,14 +883,17 @@ Expressions
Any type can support ``<<`` or ``>>`` if you implement the trait:
- .. code-block:: rust
+ .. rust-example::
use core::ops::Shl;
+ # struct MyType;
impl Shl for MyType {
type Output = MyType;
- fn shl(self, rhs: u32) -> Self::Output { /* ... */ }
+ fn shl(self, _rhs: u32) -> Self::Output { MyType }
}
+ #
+ # fn main() {}
You may choose any type for the right operand (not just integers), because you control the implementation.
@@ -861,7 +914,7 @@ Expressions
This noncompliant example shifts by a negative value (-1) and also by greater than or equal to the number of bits that exist in the left operand (40):.
- .. code-block:: rust
+ .. rust-example::
fn main() {
let bits : u32 = 61;
@@ -879,7 +932,7 @@ Expressions
This noncompliant example test the value of ``sh`` to ensure the value of the right operand is negative or greater
than or equal to the width of the left operand.
- .. code-block:: rust
+ .. rust-example::
fn main() {
let bits: u32 = 61;
@@ -901,7 +954,7 @@ Expressions
Note that this is not the same as a rotate-left.
The ``wrapping_shl`` has the same behavior as the ``<<`` operator in release mode.
- .. code-block:: rust
+ .. rust-example::
fn main() {
let bits : u32 = 61;
@@ -923,7 +976,8 @@ Expressions
and -1 for a negative number.
The use of this function is noncompliant because it does not detect out-of-range shifts.
- .. code-block:: rust
+ .. rust-example::
+ :version: 1.87
fn main() {
let bits : u32 = 61;
@@ -942,7 +996,7 @@ Expressions
Returns a tuple of the shifted version of self along with a boolean indicating whether the shift value was larger than or equal to the number of bits.
If the shift value is too large, then value is masked (N-1) where N is the number of bits, and this value is used to perform the shift.
- .. code-block:: rust
+ .. rust-example::
fn main() {
let bits: u32 = 61;
@@ -978,7 +1032,7 @@ Expressions
* negative values is impossible because ``checked_shl`` only accepts unsigned integers as shift lengths, and
* greater than or equal to the number of bits that exist in the left operand returns a ``None`` value.
- .. code-block:: rust
+ .. rust-example::
fn main() {
let bits : u32 = 61;
@@ -1044,14 +1098,17 @@ Expressions
Any type can support ``<<`` or ``>>`` if you implement the trait:
- .. code-block:: rust
+ .. rust-example::
use core::ops::Shl;
+ # struct MyType;
impl Shl for MyType {
type Output = MyType;
- fn shl(self, rhs: u32) -> Self::Output { /* ... */ }
+ fn shl(self, _rhs: u32) -> Self::Output { MyType }
}
+ #
+ # fn main() {}
You may choose any type for the right operand (not just integers), because you control the implementation.
@@ -1076,7 +1133,7 @@ Expressions
This noncompliant example shifts by a negative value (-1) and also by greater than or equal to the number of bits that exist in the left operand (40):.
- .. code-block:: rust
+ .. rust-example::
fn main() {
let bits : u32 = 61;
@@ -1094,7 +1151,7 @@ Expressions
This compliant example test the value of ``sh`` to ensure the value of the right operand is negative or greater
than or equal to the width of the left operand.
- .. code-block:: rust
+ .. rust-example::
fn main() {
let bits: u32 = 61;
@@ -1115,7 +1172,7 @@ Expressions
Returns a tuple of the shifted version of self along with a boolean indicating whether the shift value was larger than or equal to the number of bits.
If the shift value is too large, then value is masked (N-1) where N is the number of bits, and this value is used to perform the shift.
- .. code-block:: rust
+ .. rust-example::
fn safe_shl(bits: u32, shift: u32) -> u32 {
let (result, overflowed) = bits.overflowing_shl(shift);
diff --git a/src/coding-guidelines/macros.rst b/src/coding-guidelines/macros.rst
index 6316073c..7b5e2b66 100644
--- a/src/coding-guidelines/macros.rst
+++ b/src/coding-guidelines/macros.rst
@@ -30,11 +30,13 @@ Macros
Explanation of code example.
- .. code-block:: rust
+ .. rust-example::
fn example_function() {
// Non-compliant implementation
}
+ #
+ # fn main() {}
.. compliant_example::
:id: compl_ex_Pw7YCh4Iv47Z
@@ -42,11 +44,13 @@ Macros
Explanation of code example
- .. code-block:: rust
+ .. rust-example::
fn example_function() {
// Compliant implementation
}
+ #
+ # fn main() {}
.. guideline:: Procedural macros should not be used
:id: gui_66FSqzD55VRZ
@@ -76,9 +80,10 @@ Macros
(example of a simple expansion using a proc-macro)
- .. code-block:: rust
+ .. rust-example::
// TODO
+ fn main() {}
.. compliant_example::
:id: compl_ex_4VFyucETB7C3
@@ -86,9 +91,10 @@ Macros
(example of the same simple expansion using a declarative macro)
- .. code-block:: rust
+ .. rust-example::
// TODO
+ fn main() {}
.. guideline:: A macro should not be used in place of a function
:id: gui_2jjWUoF1teOY
@@ -135,7 +141,7 @@ Macros
Using a macro where a simple function would suffice, leads to hidden mutation:
- .. code-block:: rust
+ .. rust-example::
macro_rules! increment_and_double {
($x:expr) => {
@@ -145,12 +151,15 @@ Macros
}
};
}
- let mut num = 5;
- let result = increment_and_double!(num);
- println!("Result: {}, Num: {}", result, num);
- // Result: 12, Num: 6
- In this example, calling the macro both increments and returns the value in one goโwithout any clear indication in its โsignatureโ that it mutates its argument. As a result, num is changed behind the scenes, which can surprise readers and make debugging more difficult.
+ fn main() {
+ let mut num = 5;
+ let result = increment_and_double!(num);
+ println!("Result: {}, Num: {}", result, num);
+ // Result: 12, Num: 6
+ }
+
+ In this example, calling the macro both increments and returns the value in one goโwithout any clear indication in its "signature" that it mutates its argument. As a result, num is changed behind the scenes, which can surprise readers and make debugging more difficult.
.. compliant_example::
@@ -159,16 +168,19 @@ Macros
The same functionality, implemented as a function with explicit borrowing:
- .. code-block:: rust
+ .. rust-example::
fn increment_and_double(x: &mut i32) -> i32 {
*x += 1; // mutation is explicit
*x * 2
}
- let mut num = 5;
- let result = increment_and_double(&mut num);
- println!("Result: {}, Num: {}", result, num);
- // Result: 12, Num: 6
+
+ fn main() {
+ let mut num = 5;
+ let result = increment_and_double(&mut num);
+ println!("Result: {}, Num: {}", result, num);
+ // Result: 12, Num: 6
+ }
The function version makes the mutation and borrowing explicit in its signature, improving readability, safety, and debuggability.
@@ -198,11 +210,13 @@ Macros
Explanation of code example.
- .. code-block:: rust
+ .. rust-example::
fn example_function() {
// Non-compliant implementation
}
+ #
+ # fn main() {}
.. compliant_example::
:id: compl_ex_AEKEOYhBWPMl
@@ -210,11 +224,13 @@ Macros
Explanation of code example.
- .. code-block:: rust
+ .. rust-example::
fn example_function() {
// Compliant implementation
}
+ #
+ # fn main() {}
.. guideline:: Shall not invoke macros
:id: gui_a1mHfjgKk4Xr
@@ -240,11 +256,13 @@ Macros
Explanation of code example.
- .. code-block:: rust
+ .. rust-example::
fn example_function() {
// Non-compliant implementation
}
+ #
+ # fn main() {}
.. compliant_example::
:id: compl_ex_ti7GWHCOhUvT
@@ -252,11 +270,13 @@ Macros
Explanation of code example.
- .. code-block:: rust
+ .. rust-example::
fn example_function() {
// Compliant implementation
}
+ #
+ # fn main() {}
.. guideline:: Shall not write code that expands macros
:id: gui_uuDOArzyO3Qw
@@ -282,11 +302,13 @@ Macros
Explanation of code example.
- .. code-block:: rust
+ .. rust-example::
fn example_function() {
// Non-compliant implementation
}
+ #
+ # fn main() {}
.. compliant_example::
:id: compl_ex_cFPg6y7upNdl
@@ -294,11 +316,13 @@ Macros
Explanation of code example.
- .. code-block:: rust
+ .. rust-example::
fn example_function() {
// Compliant implementation
}
+ #
+ # fn main() {}
.. guideline:: Shall ensure complete hygiene of macros
:id: gui_8hs33nyp0ipX
@@ -324,11 +348,13 @@ Macros
Explanation of code example.
- .. code-block:: rust
+ .. rust-example::
fn example_function() {
// Non-compliant implementation
}
+ #
+ # fn main() {}
.. compliant_example::
:id: compl_ex_GLP05s9c1g8N
@@ -336,11 +362,13 @@ Macros
Explanation of code example.
- .. code-block:: rust
+ .. rust-example::
fn example_function() {
// Compliant implementation
}
+ #
+ # fn main() {}
.. guideline:: Attribute macros shall not be used
:id: gui_13XWp3mb0g2P
@@ -365,14 +393,17 @@ Macros
:id: non_compl_ex_eW374waRPbeL
:status: draft
- Explanation of code example.
-
- .. code-block:: rust
+ The ``#[test]`` attribute macro transforms the function into a test harness entry point.
- #[tokio::main] // non-compliant
- async fn main() {
+ .. rust-example::
+ :no_run:
- }
+ #[test] // non-compliant: attribute macro rewrites the item
+ fn example_test() {
+ assert!(true);
+ }
+ #
+ # fn main() {}
.. compliant_example::
:id: compl_ex_Mg8ePOgbGJeW
@@ -380,11 +411,13 @@ Macros
Explanation of code example.
- .. code-block:: rust
+ .. rust-example::
fn example_function() {
// Compliant implementation
}
+ #
+ # fn main() {}
.. guideline:: Do not hide unsafe blocks within macro expansions
:id: gui_FRLaMIMb4t3S
@@ -410,11 +443,13 @@ Macros
Explanation of code example.
- .. code-block:: rust
+ .. rust-example::
fn example_function() {
// Non-compliant implementation
}
+ #
+ # fn main() {}
.. compliant_example::
:id: compl_ex_pO5gP1aj2v4F
@@ -422,11 +457,13 @@ Macros
Explanation of code example.
- .. code-block:: rust
+ .. rust-example::
fn example_function() {
// Compliant implementation
}
+ #
+ # fn main() {}
.. guideline:: Names in a macro definition shall use a fully qualified path
:id: gui_SJMrWDYZ0dN4
@@ -459,19 +496,21 @@ Macros
defines a struct `Vec` which is not preset at the macro definition, the macro user might be intending to
use that in the macro.
- .. code-block:: rust
-
- #[macro_export]
- macro_rules! vec {
- ( $( $x:expr ),* ) => {
- {
- let mut temp_vec = Vec::new(); // non-global path
- $(
- temp_vec.push($x);
- )*
- temp_vec
- }
- };
+ .. rust-example::
+
+ fn main() {
+ #[macro_export]
+ macro_rules! vec {
+ ( $( $x:expr ),* ) => {
+ {
+ let mut temp_vec = Vec::new(); // non-global path
+ $(
+ temp_vec.push($x);
+ )*
+ temp_vec
+ }
+ };
+ }
}
.. compliant_example::
@@ -482,17 +521,19 @@ Macros
`Vec` defined in the scope of the macro usage, this macro will unambiguously use the `Vec` from the
Standard Library.
- .. code-block:: rust
-
- #[macro_export]
- macro_rules! vec {
- ( $( $x:expr ),* ) => {
- {
- let mut temp_vec = ::std::vec::Vec::new(); // global path
- $(
- temp_vec.push($x);
- )*
- temp_vec
- }
- };
+ .. rust-example::
+
+ fn main() {
+ #[macro_export]
+ macro_rules! vec {
+ ( $( $x:expr ),* ) => {
+ {
+ let mut temp_vec = ::std::vec::Vec::new(); // global path
+ $(
+ temp_vec.push($x);
+ )*
+ temp_vec
+ }
+ };
+ }
}
diff --git a/src/coding-guidelines/types-and-traits.rst b/src/coding-guidelines/types-and-traits.rst
index 3ede5564..1c756f44 100644
--- a/src/coding-guidelines/types-and-traits.rst
+++ b/src/coding-guidelines/types-and-traits.rst
@@ -58,7 +58,7 @@ Types and Traits
The units of each type are not clear from the function signature alone.
Mistakes compile cleanly and silently produce wrong results.
- .. code-block:: rust
+ .. rust-example::
fn travel(distance: u32, time: u32) -> u32 {
distance / time
@@ -66,7 +66,7 @@ Types and Traits
fn main() {
let d = 100;
- let t = = 10;
+ let t = 10;
let _result = travel(t, d); // Compiles, but semantically incorrect
}
@@ -83,7 +83,7 @@ Types and Traits
The units of each type are not clear from the function signature alone.
Mistakes compile cleanly and silently produce wrong results.
- .. code-block:: rust
+ .. rust-example::
type Meters = u32;
type Seconds = u32;
@@ -107,7 +107,7 @@ Types and Traits
The compiler enforces correct usage, preventing accidental swapping of parameters.
The function signature clearly conveys the intended semantics of each parameter and return value.
- .. code-block:: rust
+ .. rust-example::
use std::ops::Div;
diff --git a/src/conf.py b/src/conf.py
index 0c59ec54..fcffe45e 100644
--- a/src/conf.py
+++ b/src/conf.py
@@ -26,6 +26,12 @@
"coding_guidelines",
]
+# Show hidden lines in all examples (default: False)
+rust_examples_show_hidden = False
+
+# Path to shared prelude (default: None)
+rust_examples_prelude_file = "src/examples_prelude.rs"
+
# Basic needs configuration
needs_id_regex = "^[A-Za-z0-9_]+"
needs_title_optional = True
diff --git a/src/examples_prelude.rs b/src/examples_prelude.rs
new file mode 100644
index 00000000..a5de162d
--- /dev/null
+++ b/src/examples_prelude.rs
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: MIT OR Apache-2.0
+// SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors
+
+//! Shared prelude for coding guideline examples.
+//!
+//! This file contains common utilities that are implicitly
+//! available to all code examples in the documentation. These are
+//! prepended as hidden lines when testing examples.
+//!
+//! Note: Type definitions like ArithmeticError and DivError are defined
+//! as hidden lines within individual examples to keep them self-contained.
+
+/// A placeholder function that does nothing.
+/// Use this in examples where you need to call a function but
+/// the implementation doesn't matter.
+pub fn placeholder() {}
+
+/// A placeholder function that takes any argument.
+pub fn use_value(_value: T) {}
+
+/// Macro to suppress unused variable warnings in examples.
+#[macro_export]
+macro_rules! ignore {
+ ($($x:tt)*) => { let _ = { $($x)* }; };
+}
diff --git a/tests/rust-examples/expected-results.json b/tests/rust-examples/expected-results.json
new file mode 100644
index 00000000..69da0346
--- /dev/null
+++ b/tests/rust-examples/expected-results.json
@@ -0,0 +1,81 @@
+{
+ "description": "Expected test results for tests/rust-examples/test-examples.rst",
+ "examples": [
+ {
+ "line": 25,
+ "name": "Edition 2021 (default)",
+ "expected": {
+ "stable": "pass",
+ "1.75.0": "pass",
+ "nightly": "pass"
+ }
+ },
+ {
+ "line": 42,
+ "name": "Edition 2018 explicit",
+ "expected": {
+ "stable": "pass",
+ "1.75.0": "pass",
+ "nightly": "pass"
+ }
+ },
+ {
+ "line": 61,
+ "name": "Edition 2015",
+ "expected": {
+ "stable": "pass",
+ "1.75.0": "pass",
+ "nightly": "pass"
+ }
+ },
+ {
+ "line": 82,
+ "name": "Version 1.79 (NonZero)",
+ "expected": {
+ "stable": "pass",
+ "1.75.0": "skip",
+ "nightly": "pass"
+ },
+ "version": "1.79"
+ },
+ {
+ "line": 101,
+ "name": "Version 1.87 (unbounded_shl)",
+ "expected": {
+ "stable": "pass",
+ "1.75.0": "skip",
+ "nightly": "pass"
+ },
+ "version": "1.87"
+ },
+ {
+ "line": 123,
+ "name": "Nightly channel (#![feature(test)])",
+ "expected": {
+ "stable": "skip",
+ "1.75.0": "skip",
+ "nightly": "pass"
+ },
+ "channel": "nightly"
+ },
+ {
+ "line": 144,
+ "name": "Combined edition + version",
+ "expected": {
+ "stable": "pass",
+ "1.75.0": "skip",
+ "nightly": "pass"
+ },
+ "version": "1.79"
+ },
+ {
+ "line": 170,
+ "name": "Standard example (no options)",
+ "expected": {
+ "stable": "pass",
+ "1.75.0": "pass",
+ "nightly": "pass"
+ }
+ }
+ ]
+}
diff --git a/tests/rust-examples/test-examples.rst b/tests/rust-examples/test-examples.rst
new file mode 100644
index 00000000..a5685f21
--- /dev/null
+++ b/tests/rust-examples/test-examples.rst
@@ -0,0 +1,175 @@
+.. SPDX-License-Identifier: MIT OR Apache-2.0
+ SPDX-FileCopyrightText: The Coding Guidelines Subcommittee Contributors
+
+.. This file contains test examples to verify the rust-example directive options work correctly.
+ These examples are designed to test the :edition:, :version:, and :channel: options.
+
+=======================================
+Test Examples for Directive Validation
+=======================================
+
+This file contains examples specifically designed to test that the ``rust-example`` directive
+options (`:edition:`, `:version:`, `:channel:`) work correctly.
+
+Edition Examples
+================
+
+.. guideline:: Test edition 2021 (default)
+ :id: gui_test_edition_2021
+ :category: advisory
+ :status: draft
+ :release: test
+
+ Example using Rust 2021 edition (the default).
+
+ .. rust-example::
+
+ fn main() {
+ // 2021 edition - closures capture only what they use
+ let s = String::from("hello");
+ let closure = || println!("{}", s);
+ closure();
+ }
+
+.. guideline:: Test edition 2018 explicit
+ :id: gui_test_edition_2018
+ :category: advisory
+ :status: draft
+ :release: test
+
+ Example explicitly using Rust 2018 edition.
+
+ .. rust-example::
+ :edition: 2018
+
+ fn main() {
+ // 2018 edition syntax works
+ let v: Vec = vec![1, 2, 3];
+ for i in &v {
+ println!("{}", i);
+ }
+ }
+
+.. guideline:: Test edition 2015
+ :id: gui_test_edition_2015
+ :category: advisory
+ :status: draft
+ :release: test
+
+ Example using Rust 2015 edition.
+
+ .. rust-example::
+ :edition: 2015
+
+ fn main() {
+ // 2015 edition - basic syntax
+ let x = 5;
+ println!("{}", x);
+ }
+
+Version Examples
+================
+
+.. guideline:: Test version requirement
+ :id: gui_test_version_requirement
+ :category: advisory
+ :status: draft
+ :release: test
+
+ Example requiring a specific minimum Rust version. This example uses ``std::num::NonZero``
+ which was stabilized in Rust 1.79.
+
+ .. rust-example::
+ :version: 1.79
+
+ use std::num::NonZero;
+
+ fn main() {
+ if let Some(n) = NonZero::::new(42) {
+ println!("Got non-zero: {}", n);
+ }
+ }
+
+.. guideline:: Test newer version requirement
+ :id: gui_test_version_newer
+ :category: advisory
+ :status: draft
+ :release: test
+
+ Example requiring Rust 1.87+ for ``unbounded_shl``/``unbounded_shr`` methods.
+
+ .. rust-example::
+ :version: 1.87
+
+ fn main() {
+ let x: u32 = 0b1010;
+ // unbounded_shl was stabilized in 1.87
+ let shifted = x.unbounded_shl(2);
+ println!("Shifted: {}", shifted);
+ }
+
+Channel Examples
+================
+
+.. guideline:: Test nightly channel requirement
+ :id: gui_test_nightly_channel
+ :category: advisory
+ :status: draft
+ :release: test
+
+ Example requiring nightly channel. This uses a feature that's only available on nightly.
+ Note: This example uses ``#![feature(...)]`` which requires nightly.
+
+ .. rust-example::
+ :channel: nightly
+
+ #![feature(test)]
+
+ fn main() {
+ // The test feature is permanently unstable
+ println!("This requires nightly");
+ }
+
+Combined Options
+================
+
+.. guideline:: Test combined edition and version
+ :id: gui_test_combined_options
+ :category: advisory
+ :status: draft
+ :release: test
+
+ Example combining edition and version requirements.
+
+ .. rust-example::
+ :edition: 2021
+ :version: 1.79
+
+ use std::num::NonZero;
+
+ fn main() {
+ // Combines 2021 edition with 1.79+ version requirement
+ let maybe_zero = 0u32;
+ match NonZero::new(maybe_zero) {
+ Some(n) => println!("Non-zero: {}", n),
+ None => println!("Was zero"),
+ }
+ }
+
+Standard Example (No Special Options)
+=====================================
+
+.. guideline:: Test standard example
+ :id: gui_test_standard
+ :category: advisory
+ :status: draft
+ :release: test
+
+ A standard example with no special options - uses defaults (edition 2021, stable channel).
+
+ .. rust-example::
+
+ fn main() {
+ let message = "Hello from a standard example";
+ println!("{}", message);
+ }
diff --git a/uv.lock b/uv.lock
index 7c2d39d7..5e3954da 100644
--- a/uv.lock
+++ b/uv.lock
@@ -1,5 +1,5 @@
version = 1
-revision = 3
+revision = 1
requires-python = "==3.12.*"
[manifest]
@@ -12,9 +12,9 @@ members = [
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" }
+sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210 }
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" },
+ { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929 },
]
[[package]]
@@ -26,63 +26,63 @@ dependencies = [
{ name = "sniffio" },
{ name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916 },
]
[[package]]
name = "attrs"
version = "25.1.0"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/49/7c/fdf464bcc51d23881d110abd74b512a42b3d5d376a55a831b44c603ae17f/attrs-25.1.0.tar.gz", hash = "sha256:1c97078a80c814273a76b2a298a932eb681c87415c11dee0a6921de7f1b02c3e", size = 810562, upload-time = "2025-01-25T11:30:12.508Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/49/7c/fdf464bcc51d23881d110abd74b512a42b3d5d376a55a831b44c603ae17f/attrs-25.1.0.tar.gz", hash = "sha256:1c97078a80c814273a76b2a298a932eb681c87415c11dee0a6921de7f1b02c3e", size = 810562 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/fc/30/d4986a882011f9df997a55e6becd864812ccfcd821d64aac8570ee39f719/attrs-25.1.0-py3-none-any.whl", hash = "sha256:c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a", size = 63152, upload-time = "2025-01-25T11:30:10.164Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/30/d4986a882011f9df997a55e6becd864812ccfcd821d64aac8570ee39f719/attrs-25.1.0-py3-none-any.whl", hash = "sha256:c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a", size = 63152 },
]
[[package]]
name = "babel"
version = "2.17.0"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537 },
]
[[package]]
name = "builder"
version = "0.1.0"
-source = { editable = "builder" }
+source = { virtual = "builder" }
[[package]]
name = "certifi"
version = "2025.1.31"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577, upload-time = "2025-01-31T02:16:47.166Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393, upload-time = "2025-01-31T02:16:45.015Z" },
+ { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 },
]
[[package]]
name = "charset-normalizer"
version = "3.4.1"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188, upload-time = "2024-12-24T18:12:35.43Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105, upload-time = "2024-12-24T18:10:38.83Z" },
- { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404, upload-time = "2024-12-24T18:10:44.272Z" },
- { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423, upload-time = "2024-12-24T18:10:45.492Z" },
- { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184, upload-time = "2024-12-24T18:10:47.898Z" },
- { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268, upload-time = "2024-12-24T18:10:50.589Z" },
- { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601, upload-time = "2024-12-24T18:10:52.541Z" },
- { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098, upload-time = "2024-12-24T18:10:53.789Z" },
- { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520, upload-time = "2024-12-24T18:10:55.048Z" },
- { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852, upload-time = "2024-12-24T18:10:57.647Z" },
- { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488, upload-time = "2024-12-24T18:10:59.43Z" },
- { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192, upload-time = "2024-12-24T18:11:00.676Z" },
- { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550, upload-time = "2024-12-24T18:11:01.952Z" },
- { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785, upload-time = "2024-12-24T18:11:03.142Z" },
- { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767, upload-time = "2024-12-24T18:12:32.852Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 },
+ { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 },
+ { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 },
+ { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 },
+ { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 },
+ { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 },
+ { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 },
+ { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 },
+ { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 },
+ { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 },
+ { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 },
+ { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 },
+ { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 },
+ { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 },
]
[[package]]
@@ -92,54 +92,54 @@ 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" }
+sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 }
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" },
+ { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 },
]
[[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" }
+sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
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" },
+ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
]
[[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" }
+sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444 }
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" },
+ { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408 },
]
[[package]]
name = "h11"
version = "0.14.0"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418, upload-time = "2022-09-25T15:40:01.519Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259, upload-time = "2022-09-25T15:39:59.68Z" },
+ { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 },
]
[[package]]
name = "idna"
version = "3.10"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
+ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
]
[[package]]
name = "imagesize"
version = "1.4.1"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026, upload-time = "2022-07-01T12:21:05.687Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769, upload-time = "2022-07-01T12:21:02.467Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769 },
]
[[package]]
@@ -149,9 +149,9 @@ 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" }
+sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 }
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" },
+ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 },
]
[[package]]
@@ -164,9 +164,9 @@ dependencies = [
{ name = "referencing" },
{ name = "rpds-py" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/38/2e/03362ee4034a4c917f697890ccd4aec0800ccf9ded7f511971c75451deec/jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4", size = 325778, upload-time = "2024-07-08T18:40:05.546Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/38/2e/03362ee4034a4c917f697890ccd4aec0800ccf9ded7f511971c75451deec/jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4", size = 325778 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/69/4a/4f9dbeb84e8850557c02365a0eee0649abe5eb1d84af92a25731c6c0f922/jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566", size = 88462, upload-time = "2024-07-08T18:40:00.165Z" },
+ { url = "https://files.pythonhosted.org/packages/69/4a/4f9dbeb84e8850557c02365a0eee0649abe5eb1d84af92a25731c6c0f922/jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566", size = 88462 },
]
[[package]]
@@ -176,64 +176,54 @@ source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "referencing" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/10/db/58f950c996c793472e336ff3655b13fbcf1e3b359dcf52dcf3ed3b52c352/jsonschema_specifications-2024.10.1.tar.gz", hash = "sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272", size = 15561, upload-time = "2024-10-08T12:29:32.068Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/10/db/58f950c996c793472e336ff3655b13fbcf1e3b359dcf52dcf3ed3b52c352/jsonschema_specifications-2024.10.1.tar.gz", hash = "sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272", size = 15561 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/d1/0f/8910b19ac0670a0f80ce1008e5e751c4a57e14d2c4c13a482aa6079fa9d6/jsonschema_specifications-2024.10.1-py3-none-any.whl", hash = "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf", size = 18459, upload-time = "2024-10-08T12:29:30.439Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/0f/8910b19ac0670a0f80ce1008e5e751c4a57e14d2c4c13a482aa6079fa9d6/jsonschema_specifications-2024.10.1-py3-none-any.whl", hash = "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf", size = 18459 },
]
-[[package]]
-name = "m2r"
-version = "0.3.1"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "docutils" },
- { name = "mistune" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/94/65/fd40fbdc608298e760affb95869c3baed237dfe5649d62da1eaa1deca8f3/m2r-0.3.1.tar.gz", hash = "sha256:aafb67fc49cfb1d89e46a3443ac747e15f4bb42df20ed04f067ad9efbee256ab", size = 16622, upload-time = "2022-11-17T08:12:08.781Z" }
-
[[package]]
name = "markupsafe"
version = "3.0.2"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" },
- { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" },
- { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" },
- { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" },
- { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" },
- { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" },
- { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" },
- { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" },
- { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" },
- { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" },
+ { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 },
+ { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 },
+ { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 },
+ { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 },
+ { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 },
+ { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 },
+ { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 },
+ { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 },
+ { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 },
+ { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 },
]
[[package]]
-name = "mistune"
-version = "0.8.4"
+name = "packaging"
+version = "24.2"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/2d/a4/509f6e7783ddd35482feda27bc7f72e65b5e7dc910eca4ab2164daf9c577/mistune-0.8.4.tar.gz", hash = "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e", size = 58322, upload-time = "2018-10-11T06:59:27.908Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/09/ec/4b43dae793655b7d8a25f76119624350b4d65eb663459eb9603d7f1f0345/mistune-0.8.4-py2.py3-none-any.whl", hash = "sha256:88a1051873018da288eee8538d476dffe1262495144b33ecb586c4ab266bb8d4", size = 16220, upload-time = "2018-10-11T06:59:26.044Z" },
+ { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 },
]
[[package]]
-name = "packaging"
-version = "24.2"
+name = "pygments"
+version = "2.19.1"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950, upload-time = "2024-11-08T09:47:47.202Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451, upload-time = "2024-11-08T09:47:44.722Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 },
]
[[package]]
-name = "pygments"
-version = "2.19.1"
+name = "pypandoc"
+version = "1.16.2"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/0b/18/9f5f70567b97758625335209b98d5cb857e19aa1a9306e9749567a240634/pypandoc-1.16.2.tar.gz", hash = "sha256:7a72a9fbf4a5dc700465e384c3bb333d22220efc4e972cb98cf6fc723cdca86b", size = 31477 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/e9/b145683854189bba84437ea569bfa786f408c8dc5bc16d8eb0753f5583bf/pypandoc-1.16.2-py3-none-any.whl", hash = "sha256:c200c1139c8e3247baf38d1e9279e85d9f162499d1999c6aa8418596558fe79b", size = 19451 },
]
[[package]]
@@ -245,9 +235,9 @@ dependencies = [
{ name = "rpds-py" },
{ name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744, upload-time = "2025-01-25T08:48:16.138Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775, upload-time = "2025-01-25T08:48:14.241Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775 },
]
[[package]]
@@ -260,9 +250,9 @@ dependencies = [
{ name = "idna" },
{ name = "urllib3" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload-time = "2024-05-29T15:37:49.536Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 },
]
[[package]]
@@ -272,64 +262,64 @@ source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "requests" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/72/97/bf44e6c6bd8ddbb99943baf7ba8b1a8485bcd2fe0e55e5708d7fee4ff1ae/requests_file-2.1.0.tar.gz", hash = "sha256:0f549a3f3b0699415ac04d167e9cb39bccfb730cb832b4d20be3d9867356e658", size = 6891, upload-time = "2024-05-21T16:28:00.24Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/72/97/bf44e6c6bd8ddbb99943baf7ba8b1a8485bcd2fe0e55e5708d7fee4ff1ae/requests_file-2.1.0.tar.gz", hash = "sha256:0f549a3f3b0699415ac04d167e9cb39bccfb730cb832b4d20be3d9867356e658", size = 6891 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/d7/25/dd878a121fcfdf38f52850f11c512e13ec87c2ea72385933818e5b6c15ce/requests_file-2.1.0-py2.py3-none-any.whl", hash = "sha256:cf270de5a4c5874e84599fc5778303d496c10ae5e870bfa378818f35d21bda5c", size = 4244, upload-time = "2024-05-21T16:27:57.733Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/25/dd878a121fcfdf38f52850f11c512e13ec87c2ea72385933818e5b6c15ce/requests_file-2.1.0-py2.py3-none-any.whl", hash = "sha256:cf270de5a4c5874e84599fc5778303d496c10ae5e870bfa378818f35d21bda5c", size = 4244 },
]
[[package]]
name = "roman-numerals-py"
version = "3.1.0"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/30/76/48fd56d17c5bdbdf65609abbc67288728a98ed4c02919428d4f52d23b24b/roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d", size = 9017, upload-time = "2025-02-22T07:34:54.333Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/30/76/48fd56d17c5bdbdf65609abbc67288728a98ed4c02919428d4f52d23b24b/roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d", size = 9017 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/53/97/d2cbbaa10c9b826af0e10fdf836e1bf344d9f0abb873ebc34d1f49642d3f/roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c", size = 7742, upload-time = "2025-02-22T07:34:52.422Z" },
+ { url = "https://files.pythonhosted.org/packages/53/97/d2cbbaa10c9b826af0e10fdf836e1bf344d9f0abb873ebc34d1f49642d3f/roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c", size = 7742 },
]
[[package]]
name = "rpds-py"
version = "0.23.1"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/0a/79/2ce611b18c4fd83d9e3aecb5cba93e1917c050f556db39842889fa69b79f/rpds_py-0.23.1.tar.gz", hash = "sha256:7f3240dcfa14d198dba24b8b9cb3b108c06b68d45b7babd9eefc1038fdf7e707", size = 26806, upload-time = "2025-02-21T15:04:23.169Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/0a/79/2ce611b18c4fd83d9e3aecb5cba93e1917c050f556db39842889fa69b79f/rpds_py-0.23.1.tar.gz", hash = "sha256:7f3240dcfa14d198dba24b8b9cb3b108c06b68d45b7babd9eefc1038fdf7e707", size = 26806 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/f3/8c/d17efccb9f5b9137ddea706664aebae694384ae1d5997c0202093e37185a/rpds_py-0.23.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3902df19540e9af4cc0c3ae75974c65d2c156b9257e91f5101a51f99136d834c", size = 364369, upload-time = "2025-02-21T15:02:02.396Z" },
- { url = "https://files.pythonhosted.org/packages/6e/c0/ab030f696b5c573107115a88d8d73d80f03309e60952b64c584c70c659af/rpds_py-0.23.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66f8d2a17e5838dd6fb9be6baaba8e75ae2f5fa6b6b755d597184bfcd3cb0eba", size = 349965, upload-time = "2025-02-21T15:02:04.527Z" },
- { url = "https://files.pythonhosted.org/packages/b3/55/b40170f5a079c4fb0b6a82b299689e66e744edca3c3375a8b160fb797660/rpds_py-0.23.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:112b8774b0b4ee22368fec42749b94366bd9b536f8f74c3d4175d4395f5cbd31", size = 389064, upload-time = "2025-02-21T15:02:06.633Z" },
- { url = "https://files.pythonhosted.org/packages/ab/1c/b03a912c59ec7c1e16b26e587b9dfa8ddff3b07851e781e8c46e908a365a/rpds_py-0.23.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e0df046f2266e8586cf09d00588302a32923eb6386ced0ca5c9deade6af9a149", size = 397741, upload-time = "2025-02-21T15:02:08.195Z" },
- { url = "https://files.pythonhosted.org/packages/52/6f/151b90792b62fb6f87099bcc9044c626881fdd54e31bf98541f830b15cea/rpds_py-0.23.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f3288930b947cbebe767f84cf618d2cbe0b13be476e749da0e6a009f986248c", size = 448784, upload-time = "2025-02-21T15:02:09.473Z" },
- { url = "https://files.pythonhosted.org/packages/71/2a/6de67c0c97ec7857e0e9e5cd7c52405af931b303eb1e5b9eff6c50fd9a2e/rpds_py-0.23.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce473a2351c018b06dd8d30d5da8ab5a0831056cc53b2006e2a8028172c37ce5", size = 440203, upload-time = "2025-02-21T15:02:11.745Z" },
- { url = "https://files.pythonhosted.org/packages/db/5e/e759cd1c276d98a4b1f464b17a9bf66c65d29f8f85754e27e1467feaa7c3/rpds_py-0.23.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d550d7e9e7d8676b183b37d65b5cd8de13676a738973d330b59dc8312df9c5dc", size = 391611, upload-time = "2025-02-21T15:02:13.76Z" },
- { url = "https://files.pythonhosted.org/packages/1c/1e/2900358efcc0d9408c7289769cba4c0974d9db314aa884028ed7f7364f61/rpds_py-0.23.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e14f86b871ea74c3fddc9a40e947d6a5d09def5adc2076ee61fb910a9014fb35", size = 423306, upload-time = "2025-02-21T15:02:15.096Z" },
- { url = "https://files.pythonhosted.org/packages/23/07/6c177e6d059f5d39689352d6c69a926ee4805ffdb6f06203570234d3d8f7/rpds_py-0.23.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1bf5be5ba34e19be579ae873da515a2836a2166d8d7ee43be6ff909eda42b72b", size = 562323, upload-time = "2025-02-21T15:02:17.379Z" },
- { url = "https://files.pythonhosted.org/packages/70/e4/f9097fd1c02b516fff9850792161eb9fc20a2fd54762f3c69eae0bdb67cb/rpds_py-0.23.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d7031d493c4465dbc8d40bd6cafefef4bd472b17db0ab94c53e7909ee781b9ef", size = 588351, upload-time = "2025-02-21T15:02:19.688Z" },
- { url = "https://files.pythonhosted.org/packages/87/39/5db3c6f326bfbe4576ae2af6435bd7555867d20ae690c786ff33659f293b/rpds_py-0.23.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:55ff4151cfd4bc635e51cfb1c59ac9f7196b256b12e3a57deb9e5742e65941ad", size = 557252, upload-time = "2025-02-21T15:02:21.875Z" },
- { url = "https://files.pythonhosted.org/packages/fd/14/2d5ad292f144fa79bafb78d2eb5b8a3a91c358b6065443cb9c49b5d1fedf/rpds_py-0.23.1-cp312-cp312-win32.whl", hash = "sha256:a9d3b728f5a5873d84cba997b9d617c6090ca5721caaa691f3b1a78c60adc057", size = 222181, upload-time = "2025-02-21T15:02:23.353Z" },
- { url = "https://files.pythonhosted.org/packages/a3/4f/0fce63e0f5cdd658e71e21abd17ac1bc9312741ebb8b3f74eeed2ebdf771/rpds_py-0.23.1-cp312-cp312-win_amd64.whl", hash = "sha256:b03a8d50b137ee758e4c73638b10747b7c39988eb8e6cd11abb7084266455165", size = 237426, upload-time = "2025-02-21T15:02:25.326Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/8c/d17efccb9f5b9137ddea706664aebae694384ae1d5997c0202093e37185a/rpds_py-0.23.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3902df19540e9af4cc0c3ae75974c65d2c156b9257e91f5101a51f99136d834c", size = 364369 },
+ { url = "https://files.pythonhosted.org/packages/6e/c0/ab030f696b5c573107115a88d8d73d80f03309e60952b64c584c70c659af/rpds_py-0.23.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66f8d2a17e5838dd6fb9be6baaba8e75ae2f5fa6b6b755d597184bfcd3cb0eba", size = 349965 },
+ { url = "https://files.pythonhosted.org/packages/b3/55/b40170f5a079c4fb0b6a82b299689e66e744edca3c3375a8b160fb797660/rpds_py-0.23.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:112b8774b0b4ee22368fec42749b94366bd9b536f8f74c3d4175d4395f5cbd31", size = 389064 },
+ { url = "https://files.pythonhosted.org/packages/ab/1c/b03a912c59ec7c1e16b26e587b9dfa8ddff3b07851e781e8c46e908a365a/rpds_py-0.23.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e0df046f2266e8586cf09d00588302a32923eb6386ced0ca5c9deade6af9a149", size = 397741 },
+ { url = "https://files.pythonhosted.org/packages/52/6f/151b90792b62fb6f87099bcc9044c626881fdd54e31bf98541f830b15cea/rpds_py-0.23.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f3288930b947cbebe767f84cf618d2cbe0b13be476e749da0e6a009f986248c", size = 448784 },
+ { url = "https://files.pythonhosted.org/packages/71/2a/6de67c0c97ec7857e0e9e5cd7c52405af931b303eb1e5b9eff6c50fd9a2e/rpds_py-0.23.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce473a2351c018b06dd8d30d5da8ab5a0831056cc53b2006e2a8028172c37ce5", size = 440203 },
+ { url = "https://files.pythonhosted.org/packages/db/5e/e759cd1c276d98a4b1f464b17a9bf66c65d29f8f85754e27e1467feaa7c3/rpds_py-0.23.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d550d7e9e7d8676b183b37d65b5cd8de13676a738973d330b59dc8312df9c5dc", size = 391611 },
+ { url = "https://files.pythonhosted.org/packages/1c/1e/2900358efcc0d9408c7289769cba4c0974d9db314aa884028ed7f7364f61/rpds_py-0.23.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e14f86b871ea74c3fddc9a40e947d6a5d09def5adc2076ee61fb910a9014fb35", size = 423306 },
+ { url = "https://files.pythonhosted.org/packages/23/07/6c177e6d059f5d39689352d6c69a926ee4805ffdb6f06203570234d3d8f7/rpds_py-0.23.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1bf5be5ba34e19be579ae873da515a2836a2166d8d7ee43be6ff909eda42b72b", size = 562323 },
+ { url = "https://files.pythonhosted.org/packages/70/e4/f9097fd1c02b516fff9850792161eb9fc20a2fd54762f3c69eae0bdb67cb/rpds_py-0.23.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d7031d493c4465dbc8d40bd6cafefef4bd472b17db0ab94c53e7909ee781b9ef", size = 588351 },
+ { url = "https://files.pythonhosted.org/packages/87/39/5db3c6f326bfbe4576ae2af6435bd7555867d20ae690c786ff33659f293b/rpds_py-0.23.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:55ff4151cfd4bc635e51cfb1c59ac9f7196b256b12e3a57deb9e5742e65941ad", size = 557252 },
+ { url = "https://files.pythonhosted.org/packages/fd/14/2d5ad292f144fa79bafb78d2eb5b8a3a91c358b6065443cb9c49b5d1fedf/rpds_py-0.23.1-cp312-cp312-win32.whl", hash = "sha256:a9d3b728f5a5873d84cba997b9d617c6090ca5721caaa691f3b1a78c60adc057", size = 222181 },
+ { url = "https://files.pythonhosted.org/packages/a3/4f/0fce63e0f5cdd658e71e21abd17ac1bc9312741ebb8b3f74eeed2ebdf771/rpds_py-0.23.1-cp312-cp312-win_amd64.whl", hash = "sha256:b03a8d50b137ee758e4c73638b10747b7c39988eb8e6cd11abb7084266455165", size = 237426 },
]
[[package]]
name = "ruff"
version = "0.12.3"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/c3/2a/43955b530c49684d3c38fcda18c43caf91e99204c2a065552528e0552d4f/ruff-0.12.3.tar.gz", hash = "sha256:f1b5a4b6668fd7b7ea3697d8d98857390b40c1320a63a178eee6be0899ea2d77", size = 4459341, upload-time = "2025-07-11T13:21:16.086Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/c3/2a/43955b530c49684d3c38fcda18c43caf91e99204c2a065552528e0552d4f/ruff-0.12.3.tar.gz", hash = "sha256:f1b5a4b6668fd7b7ea3697d8d98857390b40c1320a63a178eee6be0899ea2d77", size = 4459341 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/e2/fd/b44c5115539de0d598d75232a1cc7201430b6891808df111b8b0506aae43/ruff-0.12.3-py3-none-linux_armv6l.whl", hash = "sha256:47552138f7206454eaf0c4fe827e546e9ddac62c2a3d2585ca54d29a890137a2", size = 10430499, upload-time = "2025-07-11T13:20:26.321Z" },
- { url = "https://files.pythonhosted.org/packages/43/c5/9eba4f337970d7f639a37077be067e4ec80a2ad359e4cc6c5b56805cbc66/ruff-0.12.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:0a9153b000c6fe169bb307f5bd1b691221c4286c133407b8827c406a55282041", size = 11213413, upload-time = "2025-07-11T13:20:30.017Z" },
- { url = "https://files.pythonhosted.org/packages/e2/2c/fac3016236cf1fe0bdc8e5de4f24c76ce53c6dd9b5f350d902549b7719b2/ruff-0.12.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fa6b24600cf3b750e48ddb6057e901dd5b9aa426e316addb2a1af185a7509882", size = 10586941, upload-time = "2025-07-11T13:20:33.046Z" },
- { url = "https://files.pythonhosted.org/packages/c5/0f/41fec224e9dfa49a139f0b402ad6f5d53696ba1800e0f77b279d55210ca9/ruff-0.12.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2506961bf6ead54887ba3562604d69cb430f59b42133d36976421bc8bd45901", size = 10783001, upload-time = "2025-07-11T13:20:35.534Z" },
- { url = "https://files.pythonhosted.org/packages/0d/ca/dd64a9ce56d9ed6cad109606ac014860b1c217c883e93bf61536400ba107/ruff-0.12.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c4faaff1f90cea9d3033cbbcdf1acf5d7fb11d8180758feb31337391691f3df0", size = 10269641, upload-time = "2025-07-11T13:20:38.459Z" },
- { url = "https://files.pythonhosted.org/packages/63/5c/2be545034c6bd5ce5bb740ced3e7014d7916f4c445974be11d2a406d5088/ruff-0.12.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40dced4a79d7c264389de1c59467d5d5cefd79e7e06d1dfa2c75497b5269a5a6", size = 11875059, upload-time = "2025-07-11T13:20:41.517Z" },
- { url = "https://files.pythonhosted.org/packages/8e/d4/a74ef1e801ceb5855e9527dae105eaff136afcb9cc4d2056d44feb0e4792/ruff-0.12.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:0262d50ba2767ed0fe212aa7e62112a1dcbfd46b858c5bf7bbd11f326998bafc", size = 12658890, upload-time = "2025-07-11T13:20:44.442Z" },
- { url = "https://files.pythonhosted.org/packages/13/c8/1057916416de02e6d7c9bcd550868a49b72df94e3cca0aeb77457dcd9644/ruff-0.12.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12371aec33e1a3758597c5c631bae9a5286f3c963bdfb4d17acdd2d395406687", size = 12232008, upload-time = "2025-07-11T13:20:47.374Z" },
- { url = "https://files.pythonhosted.org/packages/f5/59/4f7c130cc25220392051fadfe15f63ed70001487eca21d1796db46cbcc04/ruff-0.12.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:560f13b6baa49785665276c963edc363f8ad4b4fc910a883e2625bdb14a83a9e", size = 11499096, upload-time = "2025-07-11T13:20:50.348Z" },
- { url = "https://files.pythonhosted.org/packages/d4/01/a0ad24a5d2ed6be03a312e30d32d4e3904bfdbc1cdbe63c47be9d0e82c79/ruff-0.12.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:023040a3499f6f974ae9091bcdd0385dd9e9eb4942f231c23c57708147b06311", size = 11688307, upload-time = "2025-07-11T13:20:52.945Z" },
- { url = "https://files.pythonhosted.org/packages/93/72/08f9e826085b1f57c9a0226e48acb27643ff19b61516a34c6cab9d6ff3fa/ruff-0.12.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:883d844967bffff5ab28bba1a4d246c1a1b2933f48cb9840f3fdc5111c603b07", size = 10661020, upload-time = "2025-07-11T13:20:55.799Z" },
- { url = "https://files.pythonhosted.org/packages/80/a0/68da1250d12893466c78e54b4a0ff381370a33d848804bb51279367fc688/ruff-0.12.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2120d3aa855ff385e0e562fdee14d564c9675edbe41625c87eeab744a7830d12", size = 10246300, upload-time = "2025-07-11T13:20:58.222Z" },
- { url = "https://files.pythonhosted.org/packages/6a/22/5f0093d556403e04b6fd0984fc0fb32fbb6f6ce116828fd54306a946f444/ruff-0.12.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6b16647cbb470eaf4750d27dddc6ebf7758b918887b56d39e9c22cce2049082b", size = 11263119, upload-time = "2025-07-11T13:21:01.503Z" },
- { url = "https://files.pythonhosted.org/packages/92/c9/f4c0b69bdaffb9968ba40dd5fa7df354ae0c73d01f988601d8fac0c639b1/ruff-0.12.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e1417051edb436230023575b149e8ff843a324557fe0a265863b7602df86722f", size = 11746990, upload-time = "2025-07-11T13:21:04.524Z" },
- { url = "https://files.pythonhosted.org/packages/fe/84/7cc7bd73924ee6be4724be0db5414a4a2ed82d06b30827342315a1be9e9c/ruff-0.12.3-py3-none-win32.whl", hash = "sha256:dfd45e6e926deb6409d0616078a666ebce93e55e07f0fb0228d4b2608b2c248d", size = 10589263, upload-time = "2025-07-11T13:21:07.148Z" },
- { url = "https://files.pythonhosted.org/packages/07/87/c070f5f027bd81f3efee7d14cb4d84067ecf67a3a8efb43aadfc72aa79a6/ruff-0.12.3-py3-none-win_amd64.whl", hash = "sha256:a946cf1e7ba3209bdef039eb97647f1c77f6f540e5845ec9c114d3af8df873e7", size = 11695072, upload-time = "2025-07-11T13:21:11.004Z" },
- { url = "https://files.pythonhosted.org/packages/e0/30/f3eaf6563c637b6e66238ed6535f6775480db973c836336e4122161986fc/ruff-0.12.3-py3-none-win_arm64.whl", hash = "sha256:5f9c7c9c8f84c2d7f27e93674d27136fbf489720251544c4da7fb3d742e011b1", size = 10805855, upload-time = "2025-07-11T13:21:13.547Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/fd/b44c5115539de0d598d75232a1cc7201430b6891808df111b8b0506aae43/ruff-0.12.3-py3-none-linux_armv6l.whl", hash = "sha256:47552138f7206454eaf0c4fe827e546e9ddac62c2a3d2585ca54d29a890137a2", size = 10430499 },
+ { url = "https://files.pythonhosted.org/packages/43/c5/9eba4f337970d7f639a37077be067e4ec80a2ad359e4cc6c5b56805cbc66/ruff-0.12.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:0a9153b000c6fe169bb307f5bd1b691221c4286c133407b8827c406a55282041", size = 11213413 },
+ { url = "https://files.pythonhosted.org/packages/e2/2c/fac3016236cf1fe0bdc8e5de4f24c76ce53c6dd9b5f350d902549b7719b2/ruff-0.12.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fa6b24600cf3b750e48ddb6057e901dd5b9aa426e316addb2a1af185a7509882", size = 10586941 },
+ { url = "https://files.pythonhosted.org/packages/c5/0f/41fec224e9dfa49a139f0b402ad6f5d53696ba1800e0f77b279d55210ca9/ruff-0.12.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2506961bf6ead54887ba3562604d69cb430f59b42133d36976421bc8bd45901", size = 10783001 },
+ { url = "https://files.pythonhosted.org/packages/0d/ca/dd64a9ce56d9ed6cad109606ac014860b1c217c883e93bf61536400ba107/ruff-0.12.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c4faaff1f90cea9d3033cbbcdf1acf5d7fb11d8180758feb31337391691f3df0", size = 10269641 },
+ { url = "https://files.pythonhosted.org/packages/63/5c/2be545034c6bd5ce5bb740ced3e7014d7916f4c445974be11d2a406d5088/ruff-0.12.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40dced4a79d7c264389de1c59467d5d5cefd79e7e06d1dfa2c75497b5269a5a6", size = 11875059 },
+ { url = "https://files.pythonhosted.org/packages/8e/d4/a74ef1e801ceb5855e9527dae105eaff136afcb9cc4d2056d44feb0e4792/ruff-0.12.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:0262d50ba2767ed0fe212aa7e62112a1dcbfd46b858c5bf7bbd11f326998bafc", size = 12658890 },
+ { url = "https://files.pythonhosted.org/packages/13/c8/1057916416de02e6d7c9bcd550868a49b72df94e3cca0aeb77457dcd9644/ruff-0.12.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12371aec33e1a3758597c5c631bae9a5286f3c963bdfb4d17acdd2d395406687", size = 12232008 },
+ { url = "https://files.pythonhosted.org/packages/f5/59/4f7c130cc25220392051fadfe15f63ed70001487eca21d1796db46cbcc04/ruff-0.12.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:560f13b6baa49785665276c963edc363f8ad4b4fc910a883e2625bdb14a83a9e", size = 11499096 },
+ { url = "https://files.pythonhosted.org/packages/d4/01/a0ad24a5d2ed6be03a312e30d32d4e3904bfdbc1cdbe63c47be9d0e82c79/ruff-0.12.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:023040a3499f6f974ae9091bcdd0385dd9e9eb4942f231c23c57708147b06311", size = 11688307 },
+ { url = "https://files.pythonhosted.org/packages/93/72/08f9e826085b1f57c9a0226e48acb27643ff19b61516a34c6cab9d6ff3fa/ruff-0.12.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:883d844967bffff5ab28bba1a4d246c1a1b2933f48cb9840f3fdc5111c603b07", size = 10661020 },
+ { url = "https://files.pythonhosted.org/packages/80/a0/68da1250d12893466c78e54b4a0ff381370a33d848804bb51279367fc688/ruff-0.12.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2120d3aa855ff385e0e562fdee14d564c9675edbe41625c87eeab744a7830d12", size = 10246300 },
+ { url = "https://files.pythonhosted.org/packages/6a/22/5f0093d556403e04b6fd0984fc0fb32fbb6f6ce116828fd54306a946f444/ruff-0.12.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6b16647cbb470eaf4750d27dddc6ebf7758b918887b56d39e9c22cce2049082b", size = 11263119 },
+ { url = "https://files.pythonhosted.org/packages/92/c9/f4c0b69bdaffb9968ba40dd5fa7df354ae0c73d01f988601d8fac0c639b1/ruff-0.12.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e1417051edb436230023575b149e8ff843a324557fe0a265863b7602df86722f", size = 11746990 },
+ { url = "https://files.pythonhosted.org/packages/fe/84/7cc7bd73924ee6be4724be0db5414a4a2ed82d06b30827342315a1be9e9c/ruff-0.12.3-py3-none-win32.whl", hash = "sha256:dfd45e6e926deb6409d0616078a666ebce93e55e07f0fb0228d4b2608b2c248d", size = 10589263 },
+ { url = "https://files.pythonhosted.org/packages/07/87/c070f5f027bd81f3efee7d14cb4d84067ecf67a3a8efb43aadfc72aa79a6/ruff-0.12.3-py3-none-win_amd64.whl", hash = "sha256:a946cf1e7ba3209bdef039eb97647f1c77f6f540e5845ec9c114d3af8df873e7", size = 11695072 },
+ { url = "https://files.pythonhosted.org/packages/e0/30/f3eaf6563c637b6e66238ed6535f6775480db973c836336e4122161986fc/ruff-0.12.3-py3-none-win_arm64.whl", hash = "sha256:5f9c7c9c8f84c2d7f27e93674d27136fbf489720251544c4da7fb3d742e011b1", size = 10805855 },
]
[[package]]
@@ -338,7 +328,7 @@ version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "builder" },
- { name = "m2r" },
+ { name = "pypandoc" },
{ name = "sphinx" },
{ name = "sphinx-autobuild" },
{ name = "sphinx-needs" },
@@ -353,8 +343,8 @@ dev = [
[package.metadata]
requires-dist = [
- { name = "builder", editable = "builder" },
- { name = "m2r" },
+ { name = "builder", virtual = "builder" },
+ { name = "pypandoc" },
{ name = "sphinx", specifier = ">=8.2.3" },
{ name = "sphinx-autobuild", specifier = ">=2024.10.3" },
{ name = "sphinx-needs", specifier = ">=5.1.0" },
@@ -369,18 +359,18 @@ dev = [{ name = "ruff", specifier = ">=0.12.3" }]
name = "sniffio"
version = "1.3.1"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 },
]
[[package]]
name = "snowballstemmer"
version = "2.2.0"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/44/7b/af302bebf22c749c56c9c3e8ae13190b5b5db37a33d9068652e8f73b7089/snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1", size = 86699, upload-time = "2021-11-16T18:38:38.009Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/44/7b/af302bebf22c749c56c9c3e8ae13190b5b5db37a33d9068652e8f73b7089/snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1", size = 86699 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/ed/dc/c02e01294f7265e63a7315fe086dd1df7dacb9f840a804da846b96d01b96/snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a", size = 93002, upload-time = "2021-11-16T18:38:34.792Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/dc/c02e01294f7265e63a7315fe086dd1df7dacb9f840a804da846b96d01b96/snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a", size = 93002 },
]
[[package]]
@@ -406,9 +396,9 @@ dependencies = [
{ 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" }
+sdist = { url = "https://files.pythonhosted.org/packages/38/ad/4360e50ed56cb483667b8e6dadf2d3fda62359593faabbe749a27c4eaca6/sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348", size = 8321876 }
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" },
+ { url = "https://files.pythonhosted.org/packages/31/53/136e9eca6e0b9dc0e1962e2c908fbea2e5ac000c2a2fbd9a35797958c48b/sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3", size = 3589741 },
]
[[package]]
@@ -423,9 +413,9 @@ dependencies = [
{ name = "watchfiles" },
{ name = "websockets" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/a5/2c/155e1de2c1ba96a72e5dba152c509a8b41e047ee5c2def9e9f0d812f8be7/sphinx_autobuild-2024.10.3.tar.gz", hash = "sha256:248150f8f333e825107b6d4b86113ab28fa51750e5f9ae63b59dc339be951fb1", size = 14023, upload-time = "2024-10-02T23:15:30.172Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/a5/2c/155e1de2c1ba96a72e5dba152c509a8b41e047ee5c2def9e9f0d812f8be7/sphinx_autobuild-2024.10.3.tar.gz", hash = "sha256:248150f8f333e825107b6d4b86113ab28fa51750e5f9ae63b59dc339be951fb1", size = 14023 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/18/c0/eba125db38c84d3c74717008fd3cb5000b68cd7e2cbafd1349c6a38c3d3b/sphinx_autobuild-2024.10.3-py3-none-any.whl", hash = "sha256:158e16c36f9d633e613c9aaf81c19b0fc458ca78b112533b20dafcda430d60fa", size = 11908, upload-time = "2024-10-02T23:15:28.739Z" },
+ { url = "https://files.pythonhosted.org/packages/18/c0/eba125db38c84d3c74717008fd3cb5000b68cd7e2cbafd1349c6a38c3d3b/sphinx_autobuild-2024.10.3-py3-none-any.whl", hash = "sha256:158e16c36f9d633e613c9aaf81c19b0fc458ca78b112533b20dafcda430d60fa", size = 11908 },
]
[[package]]
@@ -435,9 +425,9 @@ 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" }
+sdist = { url = "https://files.pythonhosted.org/packages/71/3a/7aeeb805327af5b0e6f072312f9bc660615045e791a5a8f7258276fdddd1/sphinx_data_viewer-0.1.5.tar.gz", hash = "sha256:a7d5e58613562bb745380bfe61ca8b69997998167fd6fa9aea55606c9a4b17e4", size = 103260 }
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" },
+ { url = "https://files.pythonhosted.org/packages/57/60/5e902d53a5eb3ec7166a8eda602bd78d4b8671a73917567edc8f13a3b366/sphinx_data_viewer-0.1.5-py3-none-any.whl", hash = "sha256:b74b1d304c505c464d07c7b225ed0d84ea02dcc88bc1c49cdad7c2275fbbdad4", size = 8215 },
]
[[package]]
@@ -452,9 +442,9 @@ dependencies = [
{ name = "sphinx-data-viewer" },
{ name = "sphinxcontrib-jquery" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/14/cc/6ec2b659b9b7f453cb5c5bd70efef21d083bee7f68525ecf5a1462d540ff/sphinx_needs-5.1.0.tar.gz", hash = "sha256:23a0ca1dfe733a0a58e884b59ce53a8b63a530f0ac87ae5ab0d40f05f853fbe7", size = 27276848, upload-time = "2025-03-06T15:25:34.315Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/14/cc/6ec2b659b9b7f453cb5c5bd70efef21d083bee7f68525ecf5a1462d540ff/sphinx_needs-5.1.0.tar.gz", hash = "sha256:23a0ca1dfe733a0a58e884b59ce53a8b63a530f0ac87ae5ab0d40f05f853fbe7", size = 27276848 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/33/51/76e0975800ced3179936ae9544a9c2dd5ddddc70dfef6668f9f22f8b87bf/sphinx_needs-5.1.0-py3-none-any.whl", hash = "sha256:7adf3763478e91171146918d8af4a22aa0fc062a73856f1ebeb6822a62cbe215", size = 2635665, upload-time = "2025-03-06T15:25:31.166Z" },
+ { url = "https://files.pythonhosted.org/packages/33/51/76e0975800ced3179936ae9544a9c2dd5ddddc70dfef6668f9f22f8b87bf/sphinx_needs-5.1.0-py3-none-any.whl", hash = "sha256:7adf3763478e91171146918d8af4a22aa0fc062a73856f1ebeb6822a62cbe215", size = 2635665 },
]
[[package]]
@@ -466,36 +456,36 @@ dependencies = [
{ name = "sphinx" },
{ name = "sphinxcontrib-jquery" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/91/44/c97faec644d29a5ceddd3020ae2edffa69e7d00054a8c7a6021e82f20335/sphinx_rtd_theme-3.0.2.tar.gz", hash = "sha256:b7457bc25dda723b20b086a670b9953c859eab60a2a03ee8eb2bb23e176e5f85", size = 7620463, upload-time = "2024-11-13T11:06:04.545Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/91/44/c97faec644d29a5ceddd3020ae2edffa69e7d00054a8c7a6021e82f20335/sphinx_rtd_theme-3.0.2.tar.gz", hash = "sha256:b7457bc25dda723b20b086a670b9953c859eab60a2a03ee8eb2bb23e176e5f85", size = 7620463 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/85/77/46e3bac77b82b4df5bb5b61f2de98637724f246b4966cfc34bc5895d852a/sphinx_rtd_theme-3.0.2-py2.py3-none-any.whl", hash = "sha256:422ccc750c3a3a311de4ae327e82affdaf59eb695ba4936538552f3b00f4ee13", size = 7655561, upload-time = "2024-11-13T11:06:02.094Z" },
+ { url = "https://files.pythonhosted.org/packages/85/77/46e3bac77b82b4df5bb5b61f2de98637724f246b4966cfc34bc5895d852a/sphinx_rtd_theme-3.0.2-py2.py3-none-any.whl", hash = "sha256:422ccc750c3a3a311de4ae327e82affdaf59eb695ba4936538552f3b00f4ee13", size = 7655561 },
]
[[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" }
+sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053 }
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" },
+ { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300 },
]
[[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" }
+sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967 }
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" },
+ { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530 },
]
[[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" }
+sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617 }
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" },
+ { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705 },
]
[[package]]
@@ -505,36 +495,36 @@ 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" }
+sdist = { url = "https://files.pythonhosted.org/packages/de/f3/aa67467e051df70a6330fe7770894b3e4f09436dea6881ae0b4f3d87cad8/sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a", size = 122331 }
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" },
+ { url = "https://files.pythonhosted.org/packages/76/85/749bd22d1a68db7291c89e2ebca53f4306c3f205853cf31e9de279034c3c/sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae", size = 121104 },
]
[[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" }
+sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787 }
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" },
+ { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071 },
]
[[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" }
+sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165 }
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" },
+ { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743 },
]
[[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" }
+sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080 }
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" },
+ { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072 },
]
[[package]]
@@ -544,9 +534,9 @@ source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/ce/20/08dfcd9c983f6a6f4a1000d934b9e6d626cff8d2eeb77a89a68eef20a2b7/starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5", size = 2580846, upload-time = "2025-04-13T13:56:17.942Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/ce/20/08dfcd9c983f6a6f4a1000d934b9e6d626cff8d2eeb77a89a68eef20a2b7/starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5", size = 2580846 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/8b/0c/9d30a4ebeb6db2b25a841afbb80f6ef9a854fc3b41be131d249a977b4959/starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35", size = 72037, upload-time = "2025-04-13T13:56:16.21Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/0c/9d30a4ebeb6db2b25a841afbb80f6ef9a854fc3b41be131d249a977b4959/starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35", size = 72037 },
]
[[package]]
@@ -556,27 +546,27 @@ source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540 },
]
[[package]]
name = "typing-extensions"
version = "4.12.2"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321, upload-time = "2024-06-07T18:52:15.995Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438, upload-time = "2024-06-07T18:52:13.582Z" },
+ { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 },
]
[[package]]
name = "urllib3"
version = "2.3.0"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268, upload-time = "2024-12-22T07:47:30.032Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369, upload-time = "2024-12-22T07:47:28.074Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 },
]
[[package]]
@@ -587,9 +577,9 @@ dependencies = [
{ name = "click" },
{ name = "h11" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/a6/ae/9bbb19b9e1c450cf9ecaef06463e40234d98d95bf572fab11b4f19ae5ded/uvicorn-0.34.2.tar.gz", hash = "sha256:0e929828f6186353a80b58ea719861d2629d766293b6d19baf086ba31d4f3328", size = 76815, upload-time = "2025-04-19T06:02:50.101Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/a6/ae/9bbb19b9e1c450cf9ecaef06463e40234d98d95bf572fab11b4f19ae5ded/uvicorn-0.34.2.tar.gz", hash = "sha256:0e929828f6186353a80b58ea719861d2629d766293b6d19baf086ba31d4f3328", size = 76815 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/b1/4b/4cef6ce21a2aaca9d852a6e84ef4f135d99fcd74fa75105e2fc0c8308acd/uvicorn-0.34.2-py3-none-any.whl", hash = "sha256:deb49af569084536d269fe0a6d67e3754f104cf03aba7c11c40f01aadf33c403", size = 62483, upload-time = "2025-04-19T06:02:48.42Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/4b/4cef6ce21a2aaca9d852a6e84ef4f135d99fcd74fa75105e2fc0c8308acd/uvicorn-0.34.2-py3-none-any.whl", hash = "sha256:deb49af569084536d269fe0a6d67e3754f104cf03aba7c11c40f01aadf33c403", size = 62483 },
]
[[package]]
@@ -599,39 +589,39 @@ source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/03/e2/8ed598c42057de7aa5d97c472254af4906ff0a59a66699d426fc9ef795d7/watchfiles-1.0.5.tar.gz", hash = "sha256:b7529b5dcc114679d43827d8c35a07c493ad6f083633d573d81c660abc5979e9", size = 94537, upload-time = "2025-04-08T10:36:26.722Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/03/e2/8ed598c42057de7aa5d97c472254af4906ff0a59a66699d426fc9ef795d7/watchfiles-1.0.5.tar.gz", hash = "sha256:b7529b5dcc114679d43827d8c35a07c493ad6f083633d573d81c660abc5979e9", size = 94537 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/2a/8c/4f0b9bdb75a1bfbd9c78fad7d8854369283f74fe7cf03eb16be77054536d/watchfiles-1.0.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b5eb568c2aa6018e26da9e6c86f3ec3fd958cee7f0311b35c2630fa4217d17f2", size = 401511, upload-time = "2025-04-08T10:35:17.956Z" },
- { url = "https://files.pythonhosted.org/packages/dc/4e/7e15825def77f8bd359b6d3f379f0c9dac4eb09dd4ddd58fd7d14127179c/watchfiles-1.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0a04059f4923ce4e856b4b4e5e783a70f49d9663d22a4c3b3298165996d1377f", size = 392715, upload-time = "2025-04-08T10:35:19.202Z" },
- { url = "https://files.pythonhosted.org/packages/58/65/b72fb817518728e08de5840d5d38571466c1b4a3f724d190cec909ee6f3f/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e380c89983ce6e6fe2dd1e1921b9952fb4e6da882931abd1824c092ed495dec", size = 454138, upload-time = "2025-04-08T10:35:20.586Z" },
- { url = "https://files.pythonhosted.org/packages/3e/a4/86833fd2ea2e50ae28989f5950b5c3f91022d67092bfec08f8300d8b347b/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fe43139b2c0fdc4a14d4f8d5b5d967f7a2777fd3d38ecf5b1ec669b0d7e43c21", size = 458592, upload-time = "2025-04-08T10:35:21.87Z" },
- { url = "https://files.pythonhosted.org/packages/38/7e/42cb8df8be9a37e50dd3a818816501cf7a20d635d76d6bd65aae3dbbff68/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee0822ce1b8a14fe5a066f93edd20aada932acfe348bede8aa2149f1a4489512", size = 487532, upload-time = "2025-04-08T10:35:23.143Z" },
- { url = "https://files.pythonhosted.org/packages/fc/fd/13d26721c85d7f3df6169d8b495fcac8ab0dc8f0945ebea8845de4681dab/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a0dbcb1c2d8f2ab6e0a81c6699b236932bd264d4cef1ac475858d16c403de74d", size = 522865, upload-time = "2025-04-08T10:35:24.702Z" },
- { url = "https://files.pythonhosted.org/packages/a1/0d/7f9ae243c04e96c5455d111e21b09087d0eeaf9a1369e13a01c7d3d82478/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a2014a2b18ad3ca53b1f6c23f8cd94a18ce930c1837bd891262c182640eb40a6", size = 499887, upload-time = "2025-04-08T10:35:25.969Z" },
- { url = "https://files.pythonhosted.org/packages/8e/0f/a257766998e26aca4b3acf2ae97dff04b57071e991a510857d3799247c67/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10f6ae86d5cb647bf58f9f655fcf577f713915a5d69057a0371bc257e2553234", size = 454498, upload-time = "2025-04-08T10:35:27.353Z" },
- { url = "https://files.pythonhosted.org/packages/81/79/8bf142575a03e0af9c3d5f8bcae911ee6683ae93a625d349d4ecf4c8f7df/watchfiles-1.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1a7bac2bde1d661fb31f4d4e8e539e178774b76db3c2c17c4bb3e960a5de07a2", size = 630663, upload-time = "2025-04-08T10:35:28.685Z" },
- { url = "https://files.pythonhosted.org/packages/f1/80/abe2e79f610e45c63a70d271caea90c49bbf93eb00fa947fa9b803a1d51f/watchfiles-1.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ab626da2fc1ac277bbf752446470b367f84b50295264d2d313e28dc4405d663", size = 625410, upload-time = "2025-04-08T10:35:30.42Z" },
- { url = "https://files.pythonhosted.org/packages/91/6f/bc7fbecb84a41a9069c2c6eb6319f7f7df113adf113e358c57fc1aff7ff5/watchfiles-1.0.5-cp312-cp312-win32.whl", hash = "sha256:9f4571a783914feda92018ef3901dab8caf5b029325b5fe4558c074582815249", size = 277965, upload-time = "2025-04-08T10:35:32.023Z" },
- { url = "https://files.pythonhosted.org/packages/99/a5/bf1c297ea6649ec59e935ab311f63d8af5faa8f0b86993e3282b984263e3/watchfiles-1.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:360a398c3a19672cf93527f7e8d8b60d8275119c5d900f2e184d32483117a705", size = 291693, upload-time = "2025-04-08T10:35:33.225Z" },
- { url = "https://files.pythonhosted.org/packages/7f/7b/fd01087cc21db5c47e5beae507b87965db341cce8a86f9eb12bf5219d4e0/watchfiles-1.0.5-cp312-cp312-win_arm64.whl", hash = "sha256:1a2902ede862969077b97523987c38db28abbe09fb19866e711485d9fbf0d417", size = 283287, upload-time = "2025-04-08T10:35:34.568Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/8c/4f0b9bdb75a1bfbd9c78fad7d8854369283f74fe7cf03eb16be77054536d/watchfiles-1.0.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b5eb568c2aa6018e26da9e6c86f3ec3fd958cee7f0311b35c2630fa4217d17f2", size = 401511 },
+ { url = "https://files.pythonhosted.org/packages/dc/4e/7e15825def77f8bd359b6d3f379f0c9dac4eb09dd4ddd58fd7d14127179c/watchfiles-1.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0a04059f4923ce4e856b4b4e5e783a70f49d9663d22a4c3b3298165996d1377f", size = 392715 },
+ { url = "https://files.pythonhosted.org/packages/58/65/b72fb817518728e08de5840d5d38571466c1b4a3f724d190cec909ee6f3f/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e380c89983ce6e6fe2dd1e1921b9952fb4e6da882931abd1824c092ed495dec", size = 454138 },
+ { url = "https://files.pythonhosted.org/packages/3e/a4/86833fd2ea2e50ae28989f5950b5c3f91022d67092bfec08f8300d8b347b/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fe43139b2c0fdc4a14d4f8d5b5d967f7a2777fd3d38ecf5b1ec669b0d7e43c21", size = 458592 },
+ { url = "https://files.pythonhosted.org/packages/38/7e/42cb8df8be9a37e50dd3a818816501cf7a20d635d76d6bd65aae3dbbff68/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee0822ce1b8a14fe5a066f93edd20aada932acfe348bede8aa2149f1a4489512", size = 487532 },
+ { url = "https://files.pythonhosted.org/packages/fc/fd/13d26721c85d7f3df6169d8b495fcac8ab0dc8f0945ebea8845de4681dab/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a0dbcb1c2d8f2ab6e0a81c6699b236932bd264d4cef1ac475858d16c403de74d", size = 522865 },
+ { url = "https://files.pythonhosted.org/packages/a1/0d/7f9ae243c04e96c5455d111e21b09087d0eeaf9a1369e13a01c7d3d82478/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a2014a2b18ad3ca53b1f6c23f8cd94a18ce930c1837bd891262c182640eb40a6", size = 499887 },
+ { url = "https://files.pythonhosted.org/packages/8e/0f/a257766998e26aca4b3acf2ae97dff04b57071e991a510857d3799247c67/watchfiles-1.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10f6ae86d5cb647bf58f9f655fcf577f713915a5d69057a0371bc257e2553234", size = 454498 },
+ { url = "https://files.pythonhosted.org/packages/81/79/8bf142575a03e0af9c3d5f8bcae911ee6683ae93a625d349d4ecf4c8f7df/watchfiles-1.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1a7bac2bde1d661fb31f4d4e8e539e178774b76db3c2c17c4bb3e960a5de07a2", size = 630663 },
+ { url = "https://files.pythonhosted.org/packages/f1/80/abe2e79f610e45c63a70d271caea90c49bbf93eb00fa947fa9b803a1d51f/watchfiles-1.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ab626da2fc1ac277bbf752446470b367f84b50295264d2d313e28dc4405d663", size = 625410 },
+ { url = "https://files.pythonhosted.org/packages/91/6f/bc7fbecb84a41a9069c2c6eb6319f7f7df113adf113e358c57fc1aff7ff5/watchfiles-1.0.5-cp312-cp312-win32.whl", hash = "sha256:9f4571a783914feda92018ef3901dab8caf5b029325b5fe4558c074582815249", size = 277965 },
+ { url = "https://files.pythonhosted.org/packages/99/a5/bf1c297ea6649ec59e935ab311f63d8af5faa8f0b86993e3282b984263e3/watchfiles-1.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:360a398c3a19672cf93527f7e8d8b60d8275119c5d900f2e184d32483117a705", size = 291693 },
+ { url = "https://files.pythonhosted.org/packages/7f/7b/fd01087cc21db5c47e5beae507b87965db341cce8a86f9eb12bf5219d4e0/watchfiles-1.0.5-cp312-cp312-win_arm64.whl", hash = "sha256:1a2902ede862969077b97523987c38db28abbe09fb19866e711485d9fbf0d417", size = 283287 },
]
[[package]]
name = "websockets"
version = "15.0.1"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" },
- { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" },
- { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" },
- { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload-time = "2025-03-05T20:02:22.286Z" },
- { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload-time = "2025-03-05T20:02:24.368Z" },
- { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload-time = "2025-03-05T20:02:25.669Z" },
- { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload-time = "2025-03-05T20:02:26.99Z" },
- { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload-time = "2025-03-05T20:02:30.291Z" },
- { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload-time = "2025-03-05T20:02:31.634Z" },
- { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload-time = "2025-03-05T20:02:33.017Z" },
- { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload-time = "2025-03-05T20:02:34.498Z" },
- { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" },
+sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437 },
+ { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096 },
+ { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332 },
+ { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152 },
+ { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096 },
+ { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523 },
+ { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790 },
+ { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165 },
+ { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160 },
+ { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395 },
+ { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841 },
+ { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743 },
]