diff --git a/.claude/skills/update-libtmux/SKILL.md b/.claude/skills/update-libtmux/SKILL.md new file mode 100644 index 0000000000..530ae6d9e1 --- /dev/null +++ b/.claude/skills/update-libtmux/SKILL.md @@ -0,0 +1,359 @@ +--- +name: update-libtmux +description: >- + Use when the user asks to "update libtmux", "bump libtmux", + "upgrade libtmux dependency", "check for new libtmux version", + or when investigating whether tmuxp needs a libtmux update. + Guides the full workflow: studying upstream changes, updating + the dependency, migrating code and tests, and producing + separate atomic commits with rich messages. +user-invocable: true +argument-hint: "[target-version] (optional, defaults to latest on PyPI)" +--- + +# Update libtmux Dependency + +Workflow for updating the libtmux dependency in tmuxp with separate, atomic commits. + +## Overview + +This skill produces up to four atomic commits on a dedicated branch, then opens a PR: + +1. **Package commit** — bump `pyproject.toml` + `uv.lock` +2. **Code commit(s)** — API migrations, new feature adoption (if needed) +3. **Test commit(s)** — test updates for changed/new APIs (if needed) +4. **CHANGES commit** — changelog entry documenting the bump + +Each commit stands alone, passes tests independently, and has a rich commit body. + +## Step 0: Preflight + +Gather current state before making any changes. + +### 0a. Current dependency + +Read `pyproject.toml` and find the `libtmux~=X.Y.Z` specifier in `[project] dependencies`. + +### 0b. Latest version on PyPI + +```bash +pip index versions libtmux +``` + +If the user provided a target version, use that. Otherwise use the latest from PyPI. + +### 0c. Short-circuit check + +If the current specifier already covers the target version, inform the user and stop. + +### 0d. Ensure local libtmux clone is current + +The local libtmux clone lives at `~/work/python/libtmux`. Fetch and check: + +```bash +cd ~/work/python/libtmux && git fetch --tags && git log --oneline -5 +``` + +Verify the target version tag exists. If not, the version may not be released yet — warn the user. + +## Step 1: Study upstream changes + +This is the most important step. Read the libtmux CHANGES file to understand what changed between the current pinned version and the target. + +### 1a. Read libtmux CHANGES + +Read `~/work/python/libtmux/CHANGES` from the section for the target version back through all versions since the current pin. + +Categorize changes into: + +| Category | Action needed in tmuxp | +|----------|----------------------| +| **Breaking changes** | Must fix code/tests | +| **Deprecations** | Should migrate away | +| **New APIs** | Optionally adopt | +| **Bug fixes** | Note for commit message | +| **Internal/docs** | Note for commit message only | + +### 1b. Check for API impact in tmuxp + +For each breaking change or deprecation, grep tmuxp source and tests: + +```bash +# Example: if Window.rename_window() changed signature +rg "rename_window" src/ tests/ +``` + +Search patterns to check (adapt based on actual changes): +- Method/function names that changed +- Constructor parameters that changed +- Import paths that moved +- Exception types that changed +- Return type changes + +### 1c. Check libtmux git log for details + +For breaking changes where the CHANGES entry is unclear, read the actual commits: + +```bash +cd ~/work/python/libtmux && git log --oneline v{CURRENT}..v{TARGET} -- src/ +``` + +### 1d. Summarize findings + +Present findings to the user before proceeding: +- Versions being skipped (e.g., "0.53.1, 0.54.0, 0.55.0") +- Breaking changes requiring code updates +- New APIs available for adoption +- Test impact assessment +- Estimated commit count + +Get user confirmation to proceed. + +## Step 2: Create branch + +```bash +git checkout -b deps/libtmux-{TARGET_VERSION} +``` + +Branch naming convention: `deps/libtmux-X.Y.Z` + +## Step 3: Package commit + +Update the dependency specifier and lock file. + +### 3a. Edit pyproject.toml + +Change the `libtmux~=X.Y.Z` line in `[project] dependencies`. + +### 3b. Update lock file + +```bash +uv lock +``` + +### 3c. Verify installation + +```bash +uv sync +``` + +### 3d. Run tests (smoke check) + +```bash +uv run py.test tests/ -x -q 2>&1 | tail -20 +``` + +Note any failures — these indicate code changes needed in Step 4. + +### 3e. Commit + +Commit message format (use heredoc for multiline): + +``` +deps(libtmux[~=X.Y.Z]): Bump from ~=A.B.C + +why: Pick up N libtmux release(s) (list versions) bringing +[brief summary of key changes]. + +what: +- Bump libtmux dependency specifier ~=A.B.C -> ~=X.Y.Z in pyproject.toml +- Update uv.lock + +libtmux X.Y.Z (date): +- [key change 1] +- [key change 2] + +[repeat for each intermediate version] + +Release: https://github.com/tmux-python/libtmux/releases/tag/vX.Y.Z +Changelog: https://libtmux.git-pull.com/history.html#libtmux-X-Y-Z-YYYY-MM-DD +``` + +Stage only `pyproject.toml` and `uv.lock`. + +## Step 4: Code commit(s) — if needed + +Skip this step if no breaking changes or API migrations are needed. + +### 4a. Fix breaking changes + +Address each breaking change identified in Step 1b. Make minimal, targeted fixes. + +### 4b. Adopt new APIs (optional) + +Only if the user requested it or it simplifies existing code significantly. + +### 4c. Run linting and type checking + +```bash +uv run ruff check . --fix --show-fixes +uv run ruff format . +uv run mypy +``` + +### 4d. Run tests + +```bash +uv run py.test tests/ -x -q +``` + +### 4e. Commit + +One commit per logical change. Format: + +``` +Scope(type[detail]): description of the migration + +why: libtmux X.Y.Z changed [what changed]. +what: +- [specific code change 1] +- [specific code change 2] +``` + +Use the project's standard scope conventions: +- `workspace/builder(fix[method])` for builder changes +- `cli/load(fix[feature])` for CLI changes +- `plugin(fix[hook])` for plugin changes + +## Step 5: Test commit(s) — if needed + +Skip if no test changes are required beyond what was fixed in Step 4. + +### 5a. Update tests for API changes + +Fix any tests that broke due to upstream changes. + +### 5b. Add tests for newly adopted APIs + +If Step 4 adopted new libtmux features, add tests. + +### 5c. Run full test suite + +```bash +uv run py.test +``` + +All tests must pass (doctests included — pytest is configured with `--doctest-modules`). + +### 5d. Commit + +``` +tests(scope[detail]): description + +why: Adapt tests for libtmux X.Y.Z [change]. +what: +- [specific test change 1] +- [specific test change 2] +``` + +## Step 6: CHANGES commit + +### 6a. Determine placement + +The CHANGES file has a placeholder section for the next unreleased version at the top. Add the entry below the placeholder comments. + +### 6b. Write the entry + +Add under `### Breaking Changes` if the bump changes minimum version, or `### Development` / `### Dependencies` for non-breaking bumps: + +For a breaking bump: + +```markdown +#### **libtmux** minimum bumped from `~=A.B.C` to `~=X.Y.Z` + + Picks up N releases: [version list with brief descriptions]. +``` + +For a non-breaking bump, use `### Dependencies`: + +```markdown +### Dependencies + +- Bump libtmux `~=A.B.C` -> `~=X.Y.Z` ([key changes summary]) +``` + +### 6c. Commit + +``` +docs(CHANGES): libtmux ~=A.B.C -> ~=X.Y.Z + +why: Document the dependency bump for the upcoming release. +what: +- Add entry for libtmux bump under [section name] +- Summarize key upstream changes +``` + +## Step 7: Push and open PR + +### 7a. Push the branch + +```bash +git push -u origin deps/libtmux-{TARGET_VERSION} +``` + +### 7b. Open PR + +```bash +gh pr create \ + --title "deps(libtmux[~=X.Y.Z]): Bump from ~=A.B.C" \ + --body "$(cat <<'EOF' +## Summary + +- Bump libtmux from `~=A.B.C` to `~=X.Y.Z` +- [N] upstream releases included +- [Breaking changes summary, or "No breaking changes"] + +## Upstream changes + +### libtmux X.Y.Z (date) +- [changes] + +[repeat for intermediate versions] + +## Changes in this PR + +- **Package**: pyproject.toml + uv.lock +- **Code**: [summary or "No code changes needed"] +- **Tests**: [summary or "No test changes needed"] +- **CHANGES**: Documented bump + +## Test plan + +- [ ] `uv run py.test` passes +- [ ] `uv run mypy` passes +- [ ] `uv run ruff check .` passes +EOF +)" +``` + +### 7c. Report to user + +Provide the PR URL and a summary of all commits created. + +## Reference: Past libtmux bumps + +These exemplar commits show the established patterns: + +| Version bump | Deps commit | CHANGES commit | PR | +|---|---|---|---| +| 0.53.0 → 0.55.0 | `ff52d0d2` | `094800f4` | #1019 | +| 0.52.1 → 0.53.0 | `5ff6400f` | `240d85fe` | #1003 | +| 0.51.0 → 0.52.1 | `fabd678f` | (in same commit) | #1001 | +| 0.50.1 → 0.51.0 | (in merge) | (in merge) | #999 | + +The 0.53→0.55 bump (`ff52d0d2`) is the gold standard for commit message richness — per-version changelogs, upstream links, and clear why/what structure. + +## Checklist + +Use this as a progress tracker: + +- [ ] Preflight: identify current and target versions +- [ ] Study upstream CHANGES and identify impact +- [ ] Summarize findings and get user confirmation +- [ ] Create branch `deps/libtmux-X.Y.Z` +- [ ] Package commit: pyproject.toml + uv.lock +- [ ] Code commit(s): API migrations (if needed) +- [ ] Test commit(s): test updates (if needed) +- [ ] CHANGES commit: changelog entry +- [ ] Push and open PR +- [ ] Report PR URL to user diff --git a/.github/contributing.md b/.github/contributing.md index 5712dcf03a..c0eddac2c2 100644 --- a/.github/contributing.md +++ b/.github/contributing.md @@ -3,25 +3,17 @@ When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the maintainers of this repository before making a change. -Please note we have a code of conduct, please follow it in all your interactions with the project. +See [developing](../docs/developing.md) for environment setup and [AGENTS.md](../AGENTS.md) for +detailed coding standards. ## Pull Request Process -1. Ensure any install or build dependencies are removed before the end of the layer when doing a - build. -2. This project uses flake8 to conform with common Python standards. Make sure - to run your code through linter using latest version of flake8, before pull request. -3. Bad documnentation is a Bug. If your change demands documentation update, please do so. If you - find an issue with documentation, take the time to improve or fix it. -4. pytest is used for automated testing. Please make sure to update tests that are needed, and to run - `make test` before submitting your pull request. This should prevent issues with TravisCI and - make the review and merging process easier and faster. -5. Update the README.md with details of changes to the interface, this includes new environment - variables, exposed ports, useful file locations and container parameters. -6. Increase the version numbers in any examples files and the README.md to the new version that this - Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). -7. You may merge the Pull Request in once you have the sign-off of one other developer. If you - do not have permission to do that, you may request reviewer to merge it for you. +1. **Format and lint**: `uv run ruff format .` then `uv run ruff check . --fix --show-fixes` +2. **Type check**: `uv run mypy` +3. **Test**: `uv run pytest` — all tests must pass before submitting +4. **Document**: Update docs if your change affects the public interface +5. You may merge the Pull Request once you have the sign-off of one other developer. If you + do not have permission to do that, you may request a reviewer to merge it for you. ## Decorum diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index dd5f19f491..4ccd9a8621 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -19,7 +19,7 @@ jobs: steps: - uses: actions/checkout@v6 - name: Filter changed file paths to outputs - uses: dorny/paths-filter@v3.0.2 + uses: dorny/paths-filter@v4.0.1 id: changes with: filters: | @@ -54,7 +54,7 @@ jobs: - name: Install just if: env.PUBLISH == 'true' - uses: extractions/setup-just@v3 + uses: extractions/setup-just@v4 - name: Print python versions if: env.PUBLISH == 'true' @@ -62,6 +62,15 @@ jobs: python -V uv run python -V + - name: Cache sphinx fonts + if: env.PUBLISH == 'true' + uses: actions/cache@v5 + with: + path: ~/.cache/sphinx-fonts + key: sphinx-fonts-${{ hashFiles('docs/conf.py') }} + restore-keys: | + sphinx-fonts- + - name: Build documentation if: env.PUBLISH == 'true' run: | @@ -69,7 +78,7 @@ jobs: - name: Configure AWS Credentials if: env.PUBLISH == 'true' - uses: aws-actions/configure-aws-credentials@v5 + uses: aws-actions/configure-aws-credentials@v6 with: role-to-assume: ${{ secrets.TMUXP_DOCS_ROLE_ARN }} aws-region: us-east-1 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 683b359067..ac83d5b923 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -83,7 +83,7 @@ jobs: tmux -V uv run py.test --cov=./ --cov-report=xml --verbose - - uses: codecov/codecov-action@v5 + - uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore index f82c6fba00..9dadb843bd 100644 --- a/.gitignore +++ b/.gitignore @@ -80,6 +80,10 @@ doc/_build/ # MonkeyType monkeytype.sqlite3 +# Generated by sphinx_fonts extension (downloaded at build time) +docs/_static/fonts/ +docs/_static/css/fonts.css + # Claude code **/CLAUDE.local.md **/CLAUDE.*.md diff --git a/.tool-versions b/.tool-versions index 0c2c7c1d5d..298af84d17 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,3 +1,3 @@ -just 1.46.0 -uv 0.10.9 +just 1.50 +uv 0.11.12 python 3.14 3.13 3.12 3.11 3.10 diff --git a/AGENTS.md b/AGENTS.md index bdbe24f926..6252a23946 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -116,6 +116,55 @@ windows: - **Type imports**: Use `import typing as t` and access via namespace (e.g., `t.Optional`) - **Development workflow**: Format → Test → Commit → Lint/Type Check → Test → Final Commit +## Git Commit Standards + +Format commit messages as: +``` +Scope(type[detail]): concise description + +why: Explanation of necessity or impact. +what: +- Specific technical changes made +- Focused on a single topic +``` + +Common commit types: +- **feat**: New features or enhancements +- **fix**: Bug fixes +- **refactor**: Code restructuring without functional change +- **docs**: Documentation updates +- **chore**: Maintenance (dependencies, tooling, config) +- **test**: Test-related updates +- **style**: Code style and formatting +- **py(deps)**: Dependencies +- **py(deps[dev])**: Dev Dependencies +- **ai(rules[AGENTS])**: AI rule updates +- **ai(claude[rules])**: Claude Code rules (CLAUDE.md) +- **ai(claude[command])**: Claude Code command changes + +Example: +``` +Pane(feat[send_keys]): Add support for literal flag + +why: Enable sending literal characters without tmux interpretation +what: +- Add literal parameter to send_keys method +- Update send_keys to pass -l flag when literal=True +- Add tests for literal key sending +``` +For multi-line commits, use heredoc to preserve formatting: +```bash +git commit -m "$(cat <<'EOF' +feat(Component[method]) add feature description + +why: Explanation of the change. +what: +- First change +- Second change +EOF +)" +``` + ## Logging Standards These rules guide future logging changes; existing code may not yet conform. @@ -282,7 +331,7 @@ Raw `print()` is forbidden in command/business logic. The `print()` call lives o When writing documentation (README, CHANGES, docs/), follow these rules for code blocks: -**One command per code block.** This makes commands individually copyable. +**One command per code block.** This makes commands individually copyable. For sequential commands, either use separate code blocks or chain them with `&&` or `;` and `\` continuations (keeping it one logical command). **Put explanations outside the code block**, not as comments inside. @@ -310,6 +359,73 @@ $ uv run pytest $ uv run pytest --cov ``` +### Shell Command Formatting + +These rules apply to shell commands in documentation (README, CHANGES, docs/), **not** to Python doctests. + +**Use `console` language tag with `$ ` prefix.** This distinguishes interactive commands from scripts and enables prompt-aware copy in many terminals. + +Good: + +```console +$ uv run pytest +``` + +Bad: + +```bash +uv run pytest +``` + +**Split long commands with `\` for readability.** Each flag or flag+value pair gets its own continuation line, indented. Positional parameters go on the final line. + +Good: + +```console +$ pipx install \ + --suffix=@next \ + --pip-args '\--pre' \ + --force \ + 'tmuxp' +``` + +Bad: + +```console +$ pipx install --suffix=@next --pip-args '\--pre' --force 'tmuxp' +``` + +### Changelog Conventions + +These rules apply when authoring entries in `CHANGES`, which is rendered as the Sphinx changelog page. Modeled on Django's release-notes shape — deliverables get titles and prose, not bullets. + +**Release entry boilerplate.** Every release header is `## tmuxp X.Y.Z (YYYY-MM-DD)`. The file opens with a `## tmuxp X.Y.Z (Yet to be released)` placeholder block fenced by `` and `` HTML comments — new release entries land immediately below the END marker, never above it. + +**Open with a multi-sentence lead paragraph.** Plain prose, no italic. Open with the version as sentence subject (*"tmuxp X.Y.Z ships …"*) so the lead is self-contained when excerpted. Two to four sentences telling the reader what shipped and who cares — user-visible takeaways, not internal mechanism. Cross-reference detail docs with `{ref}` to keep the lead compact. + +**Each deliverable is a section, not a bullet.** Inside `### What's new`, every distinct deliverable gets a `#### Deliverable title (#NN)` heading naming it in user vocabulary, followed by 1-3 prose paragraphs explaining what shipped. Don't wrap a paragraph in `- ` — bullets are for enumerable lists, not paragraph containers. Cross-link detail docs (`See {ref}\`foo\` for details.`) so prose stays focused. + +**The deliverable test.** Before writing an entry, ask: "What's the deliverable, in user vocabulary?" If you can't answer in one sentence, the entry isn't ready. Mechanism (helper internals, byte counters, schema-validation locations) belongs in PR descriptions and code comments, not the changelog. + +**Fixed subheadings**, in this order when present: `### Breaking changes`, `### Dependencies`, `### What's new`, `### Fixes`, `### Documentation`, `### Development`. Dev tooling (helper scripts, internal automation) lives under `### Development`. For breaking changes, show the migration path with concrete inline code (e.g. a `# Before` / `# After` fenced code block). Dependency floor bumps use the form ``Minimum `pkg>=X.Y.Z` (was `>=X.Y.W`)``. + +**PR refs `(#NN)`** sit in each deliverable's `####` heading. + +**When bullets are appropriate.** Catch-all sections (`### Fixes`, occasionally `### Documentation`) with 3+ genuinely small items use bullets — one line each, never paragraphs. If a bullet swells past two lines, promote it to a `#### Title (#NN)` heading with prose body. + +**Anti-patterns.** + +- Fragile metrics: token ceilings, third-party version pins, percent benchmarks, exact byte counts. Describe the *capability*, not the math. +- Internal jargon: private symbols (leading-underscore identifiers), algorithm names exposed for the first time, backend scaffolding. +- Walls of text dressed up as bullets. +- Buried breaking changes — they get their own subheading at the top of the entry. + +**Always link autodoc'd APIs.** Any class, method, function, exception, or attribute that has its own rendered page must be cited via the appropriate role (`{class}`, `{meth}`, `{func}`, `{exc}`, `{attr}`) — never with plain backticks. Doc pages without explicit ref labels use `{doc}`. Plain backticks are correct for code syntax, env vars, parameter names, and file paths that aren't doc pages — anything without an autodoc destination. + +**MyST roles.** Class references use `{class}` (e.g. `{class}\`~tmuxp.workspace.builder.WorkspaceBuilder\``), methods use `{meth}`, functions use `{func}`, exceptions use `{exc}`, attributes use `{attr}`, internal anchors use `{ref}`, doc-path links use `{doc}`. + +**Summarization style.** When a user asks "what changed in the latest version?" or similar, lead with the entry's lead paragraph (paraphrased if needed), followed by each `####` deliverable heading under `### What's new` with a one-sentence summary. Cite `(#NN)` only if the user asks for source links. Don't invent versions, dates, or numbers not present in `CHANGES`. Don't quote line numbers or file offsets — those shift as the file evolves. + ## Important Notes - **QA every edit**: Run formatting and tests before committing diff --git a/CHANGES b/CHANGES index 707359f3ac..7a20ddd0b8 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,6 @@ # Changelog -To install the unreleased tmuxp version, see [developmental releases](https://tmuxp.git-pull.com/quickstart.html#developmental-releases). +To install the unreleased tmuxp version, see {ref}`developmental-releases`. [pip](https://pip.pypa.io/en/stable/): @@ -23,11 +23,20 @@ $ uvx --from 'tmuxp' --prerelease allow tmuxp [pipx](https://pypa.github.io/pipx/docs/): ```console -$ pipx install --suffix=@next 'tmuxp' --pip-args '\--pre' --force -// Usage: tmuxp@next load yoursession +$ pipx install \ + --suffix=@next \ + --pip-args '\--pre' \ + --force \ + 'tmuxp' ``` -## tmuxp 1.68.0 (Yet to be released) +Run the developmental install with: + +```console +$ tmuxp@next load yoursession +``` + +## tmuxp 1.69.0 (Yet to be released) @@ -35,2765 +44,2834 @@ $ pipx install --suffix=@next 'tmuxp' --pip-args '\--pre' --force _Notes on the upcoming release will go here._ +## tmuxp 1.68.0 (2026-05-10) + +tmuxp 1.68.0 refreshes the documentation stack and developer workflow for the next release train. The docs now sit on the shared [gp-sphinx](https://gp-sphinx.git-pull.com/) platform, the API pages pick up the newer gp-furo visual language, and the test suite avoids paying for each contributor's interactive shell startup. The release also raises the libtmux floor so {ref}`tmuxp-shell` users can script against the expanded tmux command wrapper surface. + +### Dependencies + +#### Minimum `libtmux~=0.56.0` (was `~=0.55.0`) (#1038) + +The libtmux bump pulls in the 0.55.1 test-fixture socket cleanup and the 0.56.0 command-coverage release. tmuxp's runtime imports stay on stable APIs, while scripts launched through {ref}`tmuxp-shell` can now reach more upstream wrappers for interactive commands, buffer I/O, key bindings, and window/pane manipulation without falling back to raw `cmd()` calls. + +### Documentation + +#### Shared gp-sphinx documentation platform (#1033, #1035, #1036, #1037) + +The docs move from repo-local Sphinx extension copies to the shared gp-sphinx package family. That makes the tmuxp site inherit the common Furo-based theme, IBM Plex typography, packaged argparse documentation helpers, API badge styling, and MyST-aware object references used across the surrounding Python projects. + +The later gp-sphinx bumps bring in the `gp-furo-theme` Tailwind v4 respin and `sphinx-vite-builder` asset pipeline. In practice, this keeps tmuxp's CLI and API docs visually aligned with the newer [libtmux-mcp](https://libtmux-mcp.git-pull.com/) docs without keeping private copies of the same theme and extension code. + +#### Documentation command blocks are easier to copy (#1024) + +Shell examples now use one command per `console` code block with a `$` prompt. Long commands are split with continuations so rendered docs stay readable while still copying cleanly. + +### Development + +#### Test panes spawn `/bin/sh` instead of the developer's `$SHELL` (#1041) + +The pytest suite now pins `$SHELL=/bin/sh` while tests run, so tmux panes created by fixtures skip the developer's interactive shell startup files. That removes a large local performance cost for zsh or heavily customized shell setups and does not change tmuxp runtime behavior. + ## tmuxp 1.67.0 (2026-03-08) +tmuxp 1.67.0 makes {ref}`tmuxp-load` visibly track the workspace build it is performing. Users get progress feedback while windows, panes, and `before_script` hooks are being prepared, and automation can still disable the display entirely. + ### What's new -#### Animated progress spinner for `tmuxp load` (#1020) +#### Animated progress for `tmuxp load` (#1020) -The `load` command now shows an animated spinner with real-time build progress -as windows and panes are created. Five built-in presets control the display -format (`default`, `minimal`, `window`, `pane`, `verbose`), and custom format -strings are supported via `--progress-format` or `TMUXP_PROGRESS_FORMAT`. +The {ref}`tmuxp-load` command now shows an animated progress display while it builds a session. Built-in formats cover terse, window-focused, pane-focused, and verbose views, while `--progress-format` and `TMUXP_PROGRESS_FORMAT` allow a custom display. -- `--progress-lines N` / `TMUXP_PROGRESS_LINES`: Control how many lines of - `before_script` output appear in the spinner panel (default: 3). Use `0` to - hide the panel, `-1` for unlimited (capped to terminal height). -- `--no-progress` / `TMUXP_PROGRESS=0`: Disable the spinner entirely. -- During `before_script` execution, the progress bar shows a marching animation - and ⏸ icon. +`--progress-lines` and `TMUXP_PROGRESS_LINES` control how much `before_script` output appears in the panel, and `--no-progress` or `TMUXP_PROGRESS=0` restores quiet output. ## tmuxp 1.66.0 (2026-03-08) -### Bug fixes +tmuxp 1.66.0 cleans up diagnostics and user-facing output. Normal CLI use is quieter, build failures stop dumping raw tracebacks by default, and structured logging becomes the common diagnostic path. + +### Fixes + +#### Quieter CLI failures and machine-readable output fixes (#1017) -- Fix default CLI log level from INFO to WARNING so normal usage is not noisy (#1017) -- Suppress raw Python tracebacks on workspace build failure; error details available via `--log-level debug` while the user sees only `[Error] ` (#1017) -- Fix `get_pane()` to match sibling methods: widen catch to `Exception`, preserve exception chain via `from e`, replace bare `print()` with structured debug log (#1017) -- Route `ls --json` and `debug-info --json` through `OutputFormatter` for consistent machine-readable output (#1017) +The default CLI log level moved from INFO to WARNING so routine commands no longer emit internal chatter. Workspace build failures now show a concise user-facing error unless `--log-level debug` is requested, `get_pane()` preserves exception context without printing directly, and JSON output for `ls` and `debug-info` now flows through the shared output formatter. ### Development -#### Structured logging with `extra` context across all modules (#1017) +#### Structured logging across tmuxp (#1017) -All modules now use `logging.getLogger(__name__)` with structured `extra` keys -(`tmux_session`, `tmux_window`, `tmux_pane`, `tmux_config_path`, etc.) for -filtering and aggregation. Library `__init__.py` adds `NullHandler` per Python -best practices. A new `TmuxpLoggerAdapter` provides persistent context for -objects with stable identity. +Modules now use named loggers with structured `extra` fields such as `tmux_session`, `tmux_window`, `tmux_pane`, and `tmux_config_path`. Library imports install a `NullHandler`, and stable objects can carry persistent context through a logger adapter. -- Remove `colorama` runtime and type-stub dependencies; replace with stdlib ANSI constants (#1017) -- Route all raw `print()` calls through `tmuxp_echo()` for consistent output channels (#1017) +The same cleanup removes the `colorama` dependency in favor of stdlib ANSI helpers and routes direct user-visible printing through tmuxp's output layer. ## tmuxp 1.65.0 (2026-03-08) -### Breaking Changes +tmuxp 1.65.0 is a libtmux compatibility release. It raises the floor to pick up upstream lifecycle logging, error propagation fixes, pane title support, configurable tmux binary support, and safer pre-execution command logging. -#### **libtmux** minimum bumped from `~=0.53.0` to `~=0.55.0` (#1019) +### Dependencies - Picks up three releases: 0.53.1 (race condition fix in `new_session()`), - 0.54.0 (structured lifecycle logging, error propagation fixes), and - 0.55.0 (`Pane.set_title()`, `Server(tmux_bin=)`, pre-execution DEBUG logging). +#### Minimum `libtmux~=0.55.0` (was `~=0.53.0`) (#1019) -## tmuxp 1.64.2 (2026-03-08) +This pulls in libtmux 0.53.1, 0.54.0, and 0.55.0. The most visible downstream effects are the upstream `new_session()` race fix, structured lifecycle logging, `Pane.set_title()`, `Server(tmux_bin=)`, and DEBUG logging before tmux subprocess execution. -### Packaging +## tmuxp 1.64.2 (2026-03-08) -- Fix `__about__.__version__` not updated in 1.64.1 release +tmuxp 1.64.2 is a packaging-only correction for the previous patch release. -## tmuxp 1.64.1 (2026-03-08) +### Fixes -### Bug fixes +#### Published version metadata corrected -#### Fix `%` character appearing in panes on workspace load (#1018) +The package version in `__about__.__version__` is updated to match the 1.64.1 release. -Fixed long-standing issue ([#365]) where zsh panes displayed an inverse `%` marker after -loading a workspace. WorkspaceBuilder now waits for each pane's shell to be ready before -applying layout or sending commands. +## tmuxp 1.64.1 (2026-03-08) -[#365]: https://github.com/tmux-python/tmuxp/issues/365 +tmuxp 1.64.1 fixes a long-standing zsh display artifact during workspace load and adds linkable, richer CLI option documentation. -### Documentation +### Fixes -#### Linkable CLI arguments and options (#1010) +#### Pane readiness before layout and commands (#1018) -CLI documentation now supports direct linking to specific arguments: +Workspace loading now waits for each pane's shell to become ready before applying layout or sending commands. This fixes the inverse `%` marker that zsh could leave in panes after loading a workspace ([#365](https://github.com/tmux-python/tmuxp/issues/365)). -- **Linkable options**: Each `--option` and positional argument has a permanent URL anchor (e.g., `cli/load.html#load-d`) -- **Headerlinks**: Hover over any argument to reveal a ¶ link for easy sharing -- **Visual styling**: Argument names displayed with syntax-highlighted backgrounds for better readability -- **Default value badges**: Default values shown as styled inline code (e.g., `auto`) +### Documentation -#### Improved argument metadata display (#1011) +#### Linkable and richer CLI arguments (#1010, #1011) -- **Cleaner layout**: Default, Type, Choices shown as key-value pairs instead of pipe-separated text -- **Required badge**: Subtle amber tag visible in both light and dark modes -- **Consistent sizing**: All CLI elements match code block font size +CLI docs now expose permanent anchors for command options and positional arguments. Argument metadata is rendered as readable key/value detail with required badges, default-value styling, and header links for sharing a specific option. ## tmuxp 1.64.0 (2026-01-24) -### Documentation - -#### CLI documentation overhaul with sphinx_argparse_neo (#1009) - -Replaced the `pretty_argparse` extension with a new `sphinx_argparse_neo` package: +tmuxp 1.64.0 replaces the first-generation argparse docs extension with a maintained package and fixes the warnings that surfaced during that migration. -- **New argparse documentation engine**: Complete rewrite for cleaner, more maintainable CLI docs -- **Syntax highlighting**: Custom Pygments lexers for CLI usage blocks and argparse output -- **Examples transformation**: Automatically converts epilog "examples:" definition lists into proper documentation sections with TOC entries -- **ANSI stripping**: Removes escape codes when `FORCE_COLOR` is set -- **RST emphasis escaping**: Prevents warnings from glob patterns like `django-*` -- **Restructured CLI pages**: Consistent layout matching vcspull documentation pattern +### Documentation -### Bug fixes +#### CLI docs powered by `sphinx_argparse_neo` (#1009) -#### Docutils node.children protocol (#1009) +The CLI reference moves to a packaged argparse documentation engine with syntax-highlighted usage blocks, transformed examples sections, ANSI stripping, safer RST escaping, and a page structure consistent with sibling projects. -- Fixed direct `node.children` assignment that bypassed docutils' parent-child tracking mechanism +### Fixes -#### Mypy type annotation (#1009) +#### Docutils and typing cleanup (#1009) -- Added missing type annotation to fix `no-any-return` error +The docs extension no longer assigns directly to `node.children`, preserving docutils parent/child tracking, and a missing type annotation was added to satisfy mypy. ## tmuxp 1.63.1 (2026-01-11) -### Bug fixes +tmuxp 1.63.1 is a small CLI help fix after the colorized command release. -#### CLI example colorization (#1008) - -- Fix example sections not being colorized in `tmuxp --help` output -- Change `build_description` to use `"{heading} examples:"` format (e.g., "load examples:") for proper formatter detection +### Fixes -## tmuxp 1.63.0 (2026-01-11) +#### Example sections colorize correctly (#1008) -### Features +Example headings in `tmuxp --help` now use the command-specific heading format needed by the formatter, so example sections receive the same styling as other help output. -#### CLI Colors (#1006) +## tmuxp 1.63.0 (2026-01-11) -New semantic color output for all CLI commands: +tmuxp 1.63.0 is the CLI usability release. It adds semantic color, a searchable workspace index, richer `ls` output, and JSON output for debugging and automation. -- New `--color` flag (auto/always/never) on root CLI for controlling color output -- Respects `NO_COLOR` and `FORCE_COLOR` environment variables per [no-color.org](https://no-color.org) standard -- Semantic color methods: `success()` (green), `warning()` (yellow), `error()` (red), `info()` (cyan), `highlight()` (magenta), `muted()` (blue) -- All commands updated with colored output: `load`, `ls`, `freeze`, `convert`, `import`, `edit`, `shell`, `debug-info` -- Interactive prompts enhanced with color support -- `PrivatePath` utility masks home directory as `~` for privacy protection in output -- Beautiful `--help` output with usage examples +### What's new -#### Search Command (#1006) +#### Semantic color across CLI output (#1006) -New `tmuxp search` command for finding workspace files: +The root CLI gains `--color=auto|always|never` and respects `NO_COLOR` and `FORCE_COLOR`. Commands use semantic color roles for success, warnings, errors, paths, highlights, muted labels, interactive prompts, and help examples. -- Field-scoped search with prefixes: `name:`, `session:`, `path:`, `window:`, `pane:` -- Matching options: `-i` (ignore-case), `-S` (smart-case), `-F` (fixed-strings), `-w` (word) -- Logic operators: `--any` for OR, `-v` for invert match -- Output formats: human (with match highlighting), `--json`, `--ndjson` for automation and piping to `jq` -- Searches local (cwd and parents) and global (~/.tmuxp/) workspaces +#### Workspace search command (#1006) -#### Enhanced ls Command (#1006) +The new {ref}`search-config` command searches local and global workspace files with field prefixes such as `name:`, `session:`, `path:`, `window:`, and `pane:`. It supports smart-case, fixed-string, word, OR, and inverted matching, plus human, JSON, and NDJSON output. -New output options for `tmuxp ls`: +#### Richer workspace listing (#1006) -- `--tree`: Display workspaces grouped by directory -- `--full`: Include complete parsed config content -- `--json` / `--ndjson`: Machine-readable output for automation and piping to `jq` -- Local workspace discovery from current directory and parents -- Source field distinguishes "local" vs "global" workspaces -- "Global workspace directories" section shows XDG vs legacy paths with status +The {ref}`ls-config` command can display grouped trees, complete parsed config content, local-versus-global source information, and machine-readable JSON/NDJSON output. Global workspace directories also show legacy and XDG locations with status. -#### JSON Output for debug-info (#1006) +#### JSON debug information (#1006) -- `tmuxp debug-info --json`: Structured JSON output for automation, issue reporting, and piping to `jq` +{ref}`tmuxp-debug-info` can now emit structured JSON for automation and issue-reporting workflows. -### Development +### Documentation -#### Makefile -> Justfile (#1005) +#### Better CLI documentation primitives (#1007) -- Migrate from `Makefile` to `justfile` for running development tasks -- Update documentation to reference `just` commands +The docs gained a pretty-argparse extension for stripping ANSI escape codes, turning help examples into documentation sections, highlighting usage blocks, and ordering generated sections more naturally. -### Documentation +### Development -#### pretty_argparse Sphinx extension (#1007) +#### Development tasks move to Just (#1005) -New Sphinx extension that enhances sphinx-argparse CLI documentation: +The project development entrypoint moved from `Makefile` targets to `just` recipes, and the docs were updated around the new workflow. -- Strip ANSI escape codes from help text (FORCE_COLOR support) -- Transform examples into proper documentation sections -- Custom Pygments lexer (`cli-usage`) for usage block syntax highlighting -- Reorder sections so usage appears before examples +#### Docs deployment uses OIDC (#1000) -- Migrate docs deployment to AWS OIDC authentication and AWS CLI +The documentation deployment path moved to AWS OIDC credentials and the AWS CLI, removing long-lived credentials from the normal deploy flow. ## tmuxp 1.62.0 (2025-12-14) -### Breaking changes +tmuxp 1.62.0 pairs a libtmux floor bump with a load-time error fix. -#### libtmux 0.52.1 (#1001) +### Dependencies -libtmux minimum version bumped from 0.52.1 -> 0.53.0. +#### Minimum `libtmux~=0.53.0` (was `~=0.52.1`) (#1001, #1003) -### Bug fixes +The libtmux bump keeps tmuxp aligned with the upstream session-management fixes required by the current test and runtime surface. -#### Fixed traceback on session load (#1002, #1003) +### Fixes + +#### Session-killed tracebacks are surfaced correctly (#1002, #1003) -Fixed issue where session killed tracebacks should show after `tmuxp load`'s attach. +When a session is killed during the attach path after `tmuxp load`, the failure now reports through the expected error path instead of hiding the traceback. ## tmuxp 1.61.0 (2025-12-07) -### Breaking changes +tmuxp 1.61.0 keeps the dependency stack on the newly published trusted-release line. -#### libtmux 0.52.1 (#1001) +### Dependencies -libtmux minimum version bumped from 0.51.0 -> 0.52.1. +#### Minimum `libtmux~=0.52.1` (was `~=0.51.0`) (#1001) -- libtmux 0.52.0: Improved `Pane.capture_pane()` support -- libtmux 0.52.1: PyPI Trusted Published package +The libtmux update picks up improved pane capture support and the upstream package's Trusted Publisher release. -#### gp-libs 0.0.7 (#1001) +#### `gp-libs` trusted-publishing refresh (#1001) -gp-libs now published via PyPI Trusted Published package. +The gp-libs dependency moves to a package published through PyPI Trusted Publisher. ## tmuxp 1.60.1 (2025-12-07) -### CI +tmuxp 1.60.1 is a release-infrastructure update. + +### Development + +#### PyPI Trusted Publisher (#1000) -- Migrate to PyPI Trusted Publisher (#1000) +tmuxp publishing moved to PyPI Trusted Publisher, reducing the need for long-lived upload credentials. ## tmuxp 1.60.0 (2025-12-06) -#### libtmux 0.51.0 (#999) +tmuxp 1.60.0 raises the libtmux floor to the release where old deprecation warnings became hard API errors. -libtmux minimum version bumped from 0.50.1 -> 0.51.0. +### Dependencies -Hard API deprecations in libtmux 0.51.0 will cause deprecated APIs that faced only warnings to now error. +#### Minimum `libtmux~=0.51.0` (was `~=0.50.1`) (#999) -## tmuxp 1.59.1 (2025-12-06) +Users and plugins relying on deprecated libtmux names should migrate before this line, because libtmux 0.51.0 turns some formerly warned APIs into errors. -### Breaking changes +## tmuxp 1.59.1 (2025-12-06) -#### libtmux 0.50.1 (#998) +tmuxp 1.59.1 is a docs-health and dependency patch. -libtmux minimum version bumped from 0.50.0 -> 0.50.1. +### Dependencies -### Documentation (#997) +#### Minimum `libtmux~=0.50.1` (was `~=0.50.0`) (#998) -Sphinx build warnings fixed: +The libtmux patch keeps tmuxp on the latest 0.50.x maintenance line. -- `docs/conf.py`: Add sphinx-autodoc-typehints configuration - (`always_document_param_types`, `typehints_use_rtype`) and suppress - forward reference warnings from TYPE_CHECKING blocks -- `src/tmuxp/workspace/builder.py`: Fix "via via" typo in docstring -- `src/tmuxp/workspace/freezer.py`: Fix "workspacee" typo in docstring -- `src/tmuxp/log.py`: Fix RST parameter formatting in docstrings -- `src/tmuxp/workspace/finders.py`: Add missing Returns/Raises sections - to `find_workspace_file` docstring -- `docs/about_tmux.md`: Fix orphan footnote reference +### Documentation -## tmuxp 1.59.0 (2025-11-30) +#### Sphinx warnings fixed (#997) -### Breaking changes +The docs build received typehint configuration, docstring typo fixes, missing `Returns` and `Raises` sections, RST parameter formatting fixes, and an orphan footnote cleanup. -#### libtmux 0.50.0 (#996) +## tmuxp 1.59.0 (2025-11-30) -libtmux minimum version bumped from 0.49.0 -> 0.50.0. +tmuxp 1.59.0 adapts to libtmux's unified options API and its Python-native option values. -Internal updates to use libtmux's new unified options API: +### Dependencies -- `Session.attach_session()` → `Session.attach()` -- `Window.show_window_option()` → `Window.show_option()` -- `Window.show_window_options()` → `Window.show_options()` -- `Window.set_window_option()` → `Window.set_option()` +#### Minimum `libtmux~=0.50.0` (was `~=0.49.0`) (#996) -Note: Options now return Python-native types (`True`/`False` instead of -`"on"`/`"off"`, `int` instead of numeric strings). +Internal tmuxp calls moved from the old libtmux option helpers to the newer unified option methods. Historical names such as `Session.attach_session()`, `Window.show_window_option()`, and `Window.set_window_option()` are intentionally left as inline code here because they describe the old migration surface. ## tmuxp 1.58.0 (2025-11-30) +tmuxp 1.58.0 completes the move away from old tmux versions. + ### Breaking changes -#### Minimum tmux version bumped to 3.2+ (#992, #993) +#### tmux 3.2+ is now required (#992, #993) -tmux versions below 3.2a are now disabled (via libtmux v0.49.0). +tmux versions below 3.2a are disabled through the libtmux 0.49.0 update. -#### libtmux 0.49.0 (#992) +### Dependencies -libtmux minimum version bumped from 0.48.0 -> 0.49.0. +#### Minimum `libtmux~=0.49.0` (was `~=0.48.0`) (#992) + +The libtmux update carries the tmux-version enforcement used by tmuxp. ## tmuxp 1.57.0 (2025-11-28) +tmuxp 1.57.0 starts the tmux 3.2+ migration and expands test coverage to tmux 3.6. + ### Breaking changes -#### Minimum tmux version bumped to 3.2+ (#991) +#### tmux versions below 3.2a are deprecated (#991) -tmux versions below 3.2a are now deprecated (via libtmux v0.48.0). +Older tmux versions now emit a `FutureWarning` on first use through libtmux. Set `LIBTMUX_SUPPRESS_VERSION_WARNING=1` only when you intentionally need to silence the warning during a transition. -A `FutureWarning` will be emitted on first use. Support for these versions will be removed in a future release. Set `LIBTMUX_SUPPRESS_VERSION_WARNING=1` to suppress. +### Dependencies -#### libtmux 0.48.0 (#990) +#### Minimum `libtmux~=0.48.0` (was `~=0.47.0`) (#990) -libtmux minimum version bumped from 0.47.0 -> 0.48.0. +The libtmux floor carries the warning path for older tmux versions. -### What's new +### Development -#### tmux 3.6 support (#989) +#### tmux 3.6 in the test grid (#989) -Added tmux 3.6 to test grid. +CI now covers tmux 3.6. ## tmuxp 1.56.0 (2025-11-01) +tmuxp 1.56.0 drops Python 3.9 and starts the Python 3.14 readiness line. + ### Breaking changes -- Bump minimum libtmux from 0.46.0 to 0.47.0 -- Drop Python 3.9, EOL October 5th, 2025 (#987) +#### Python 3.10+ is now required (#987) + +tmuxp 1.55.0 was the last release supporting Python 3.9. Python 3.9 reached end of life on October 5, 2025. - tmuxp 1.55.0 was the last release for Python 3.9. +#### Minimum `libtmux~=0.47.0` (was `~=0.46.0`) - The minimum Python for tmuxp as of 1.56.0 is Python 3.10 +The libtmux floor moves with the supported Python baseline. ### Development -- Add Python 3.14 to test matrix (#986) +#### Python 3.14 added to CI (#986) + +The test matrix now includes Python 3.14. ## tmuxp 1.55.0 (2025-02-26) -_Maintenance only, no bug fixes or new features_ +tmuxp 1.55.0 is a maintenance release for libtmux test-helper compatibility. -### Breaking changes +### Dependencies -- libtmux: Bump minimum version from 0.45.0 -> 0.46.0 (#969) +#### Minimum `libtmux~=0.46.0` (was `~=0.45.0`) (#969) - 0.46.0+ needs this release of tmuxp to stay up-to-date with test helpers. +The dependency bump keeps tmuxp current with upstream libtmux test helper changes. ## tmuxp 1.54.0 (2025-02-23) -### Breaking changes +tmuxp 1.54.0 is a maintenance release around libtmux and runtime-dependency checks. + +### Dependencies -- libtmux: Bump minimum version from 0.44.2 -> 0.45.0 (#968) +#### Minimum `libtmux~=0.45.0` (was `~=0.44.2`) (#968) - 0.45.0+ needs this release of tmuxp to stay up-to-date with test helpers. +The libtmux update keeps the project aligned with upstream test helper changes. ### Development -- CI: Check CLI modules runtime dependencies (#967) +#### CLI runtime dependency checks (#967) - An extra set of checks on top of #965. +CI now verifies runtime dependencies for CLI modules, extending the earlier dependency-import checks. ## tmuxp 1.53.0 (2025-02-19) -### Bug fixes +tmuxp 1.53.0 fixes a runtime typing import issue and makes the test suite easier to read. -- Fix import type unavailable at runtime (#965) +### Fixes -### Development +#### Runtime import fix (#965) -- CI: Check for runtime dependencies (#965) -- libtmux: Bump minimum version from 0.42.0 -> 0.44.2 (#962) -- Tests: Improve parametrized test suite (#964) +Imports that were only available for typing no longer leak into runtime paths. + +### Development - Convert remaining `pytest.mark.parametrize()` tests to `NamedTuple` fixtures: +#### Dependency and test fixture cleanup (#962, #964, #965) - - Improved test maintainability and readability - - Added descriptive test IDs for better failure reporting - - Added docstrings to test fixtures - - Files updated: - - `test_cli.py`: Added `HelpTestFixture` - - `test_convert.py`: Added `ConvertTestFixture` and `ConvertJsonTestFixture` - - `test_freeze.py`: Added `FreezeTestFixture` and `FreezeOverwriteTestFixture` - - `test_import.py`: Added `ImportTestFixture`, `ImportTeamocilTestFixture`, and `ImportTmuxinatorTestFixture` - - `test_load.py`: Added `ZshAutotitleTestFixture`, `LogFileTestFixture`, `PluginVersionTestFixture`, and `PluginMissingTestFixture` +The libtmux floor moves to 0.44.2, CI verifies runtime dependencies, and parametrized tests were converted to named fixture records so failures show clearer IDs. ## tmuxp 1.52.2 (2025-02-02) -### Bug fixes +tmuxp 1.52.2 continues the `run_before_script()` output-capture fix line. -- `run_before_script()`: Additional output capturing improvements (#960) +### Fixes + +#### `run_before_script()` captures output more reliably (#960) + +Additional output-capture edge cases were fixed for pre-load scripts. ## tmuxp 1.52.1 (2025-02-02) -### Bug fixes +tmuxp 1.52.1 fixes the first output-capture issue in `run_before_script()`. + +### Fixes + +#### `run_before_script()` output fix (#959) -- `run_before_script()`: Fix output issue (#959) +The pre-load script runner now captures output through the corrected libtmux path. ## tmuxp 1.52.0 (2025-02-02) -_Maintenance only, no bug fixes or new features_ +tmuxp 1.52.0 is a maintenance release for the libtmux 0.42.0 line. -### Development +### Dependencies -- libtmux: Bump minimum version from 0.40.1 -> 0.42.0 (#958) +#### Minimum `libtmux~=0.42.0` (was `~=0.40.1`) (#958) - - `run_before_script()`: Remove reliance on `console_to_str()` +tmuxp drops its reliance on the older `console_to_str()` helper while staying on the current libtmux command-output path. ## tmuxp 1.51.0 (2025-02-02) -_Maintenance only, no bug fixes or new features_ +tmuxp 1.51.0 modernizes annotations and lint rules. ### Development -#### chore: Implement PEP 563 deferred annotation resolution (#957) - -- Add `from __future__ import annotations` to defer annotation resolution and reduce unnecessary runtime computations during type checking. -- Enable Ruff checks for PEP-compliant annotations: - - [non-pep585-annotation (UP006)](https://docs.astral.sh/ruff/rules/non-pep585-annotation/) - - [non-pep604-annotation (UP007)](https://docs.astral.sh/ruff/rules/non-pep604-annotation/) +#### Deferred annotations and modern typing checks (#957) -For more details on PEP 563, see: https://peps.python.org/pep-0563/ +Source files now use `from __future__ import annotations`, and Ruff checks for PEP 585 and PEP 604 style annotations are enabled. ## tmuxp 1.50.1 (2024-12-24) -### Development +tmuxp 1.50.1 is a libtmux maintenance update. + +### Dependencies -- libtmux: Bump minimum version 0.40.0 -> 0.40.1 (#956) +#### Minimum `libtmux~=0.40.1` (was `~=0.40.0`) (#956) - Bug fix for server environmental variables from https://github.com/tmux-python/libtmux/pull/553. - Thank you @ppentchev! +The libtmux bump picks up the server environment variable fix from [libtmux#553](https://github.com/tmux-python/libtmux/pull/553). ## tmuxp 1.50.0 (2024-12-20) -_Maintenance only, no bug fixes or new features_ +tmuxp 1.50.0 modernizes syntax for the Python 3.9 baseline. -### Development +### Dependencies -- libtmux: Bump minimum version 0.39.0 -> 0.40.0 (#954) +#### Minimum `libtmux~=0.40.0` (was `~=0.39.0`) (#954) - Adopts Python 3.9 syntax features +The libtmux update adopts Python 3.9 syntax features. -- Aggressive automated lint fixes via `ruff` (#953) +### Development - via ruff v0.8.4, all automated lint fixes, including unsafe and previews were applied for Python 3.9: +#### Ruff modernization pass (#953) - ```sh - ruff check --select ALL . --fix --unsafe-fixes --preview --show-fixes; ruff format . - ``` +The codebase was run through Ruff's automated fixes and formatter for the Python 3.9 baseline. ## tmuxp 1.49.0 (2024-11-26) -_Maintenance only, no bug fixes or new features_ +tmuxp 1.49.0 drops Python 3.8 and moves the dependency stack to the Python 3.9 line. ### Breaking changes -- Drop Python 3.8. end of life was October 7th, 2024 (#951) +#### Python 3.9+ is now required (#951) - tmuxp 1.48.0 was the last release for Python 3.8. +tmuxp 1.48.0 was the last release supporting Python 3.8. Python 3.8 reached end of life on October 7, 2024. - The minimum python for tmuxp as of 1.49.0 is Python 3.9 -- libtmux 0.38.1 -> 0.39.0 (Minimum Python version of 3.9) +#### Minimum `libtmux~=0.39.0` (was `~=0.38.1`) + +The libtmux floor moves with the Python baseline. ## tmuxp 1.48.0 (2024-11-26) -_Maintenance only, no bug fixes or new features_ +tmuxp 1.48.0 moves project management from Poetry to uv and the build backend from Poetry to Hatchling. ### Breaking changes -#### Project and package management: poetry to uv (#949) - -[uv] is the new package and project manager for the project, replacing Poetry. +#### Project management moved to uv (#949) -[uv]: https://github.com/astral-sh/uv +[uv](https://github.com/astral-sh/uv) replaces Poetry for development workflows and dependency locking. -#### Build system: poetry to hatchling (#949) +#### Build backend moved to Hatchling (#949) -[Build system] moved from [poetry] to [hatchling]. +[Hatchling](https://hatch.pypa.io/latest/) replaces Poetry's build backend for packaging. -[Build system]: https://packaging.python.org/en/latest/tutorials/packaging-projects/#choosing-a-build-backend -[poetry]: https://github.com/python-poetry/poetry -[hatchling]: https://hatch.pypa.io/latest/ +#### Minimum `libtmux~=0.38.1` (was `~=0.37.0`) (#950) -#### Minimum libtmux version 0.37.0 -> 0.38.1 (#950) - -Built with uv. +The libtmux dependency is rebuilt and locked through the new uv-based project workflow. ### Development -- Code quality: Use f-strings in more places (#931) - - via [ruff 0.4.2](https://github.com/astral-sh/ruff/blob/v0.4.2/CHANGELOG.md). +#### Ruff 0.4.2 cleanup (#931) -[uv]: https://github.com/astral-sh/uv +Code quality updates include f-string cleanups from the Ruff 0.4.2 rule set. ## tmuxp 1.47.0 (2024-04-21) -_Maintenance only, no bug fixes or new features_ +tmuxp 1.47.0 is a libtmux maintenance release. -### Developmental +### Dependencies -- libtmux: 0.36.0 -> 0.37.0 (#929) +#### Minimum `libtmux~=0.37.0` (was `~=0.36.0`) (#929) - Internal improvements to test suite (pytest-xdist and relaxing `retry_until()` tests) +The libtmux update brings upstream test-suite improvements, including pytest-xdist work and more relaxed `retry_until()` tests. ## tmuxp 1.46.0 (2024-04-12) -### Breaking change +tmuxp 1.46.0 changes workspace building to respect the actual terminal size. -#### Workspace builder now detects terminal size (#926) - -Dimensions used by workspace builder now use {py:func}`shutil.get_terminal_size()`. - -In conjunction with `main-pane-height: 67%`, for instance, this will render a -proportional layout: +### Breaking changes -```yaml -session_name: my session -windows: -- window_name: example with percentage - focus: True - layout: main-horizontal - options: - main-pane-height: 67% - panes: - - focus: true - - pane -``` +#### Workspace builder detects terminal size (#926) -To use old behavior, set `TMUXP_DETECT_TERMINAL_SIZE=0` in your terminal -environment and file an issue on the tracker. +The workspace builder now uses `shutil.get_terminal_size()` for dimensions, which makes percentage-based layouts such as `main-pane-height: 67%` render proportionally. Set `TMUXP_DETECT_TERMINAL_SIZE=0` to use the old fixed-size behavior during migration. ### Documentation -- Automatically linkify links that were previously only text. +#### Plain links are linkified + +Documentation links that were previously plain text are now automatically linkified. ### Development -- Another `ruff` linting pass, this time with ruff 0.3.7 (#928) -- poetry: 1.8.1 -> 1.8.2 +#### Ruff and Poetry maintenance (#928) - See also: https://github.com/python-poetry/poetry/blob/1.8.2/CHANGELOG.md +The release includes another Ruff cleanup pass and a Poetry 1.8.2 update. ## tmuxp 1.45.0 (2024-03-24) -_Maintenance only, no bug fixes or new features_ +tmuxp 1.45.0 is a code-quality maintenance release. ### Development -- Aggressive automated lint fixes via `ruff` (#922) - - via ruff v0.3.4, all automated lint fixes, including unsafe and previews were applied: - - ```sh - ruff check --select ALL . --fix --unsafe-fixes --preview --show-fixes; ruff format . - ``` - - Branches were treated with: +#### Ruff 0.3.4 cleanup (#922) - ```sh - git rebase \ - --strategy-option=theirs \ - --exec 'poetry run ruff check --select ALL . --fix --unsafe-fixes --preview --show-fixes; poetry run ruff format .; git add src tests; git commit --amend --no-edit' \ - origin/master - ``` +The codebase was run through Ruff's automated fixes and formatter, including preview and unsafe fixes where they were accepted by maintainers. ## tmuxp 1.44.0 (2024-03-24) -### Breaking changes +tmuxp 1.44.0 tracks libtmux 0.36.0. + +### Dependencies -- libtmux: 0.35.1 -> 0.36.0 (#923) +#### Minimum `libtmux~=0.36.0` (was `~=0.35.1`) (#923) - Internal refactorings and maintenance. +The libtmux bump carries internal refactoring and maintenance. ## tmuxp 1.43.1 (2024-03-24) -### Breaking changes +tmuxp 1.43.1 picks up libtmux multi-client fixes. + +### Dependencies -- libtmux: 0.35.0 -> 0.35.1 +#### Minimum `libtmux~=0.35.1` (was `~=0.35.0`) - Improved support in libtmux when multiple clients attached to a session in a server. +The update improves behavior when multiple clients are attached to a session in one server. ## tmuxp 1.43.0 (2024-03-17) -### Breaking changes +tmuxp 1.43.0 follows libtmux's target-handling cleanup. + +### Dependencies -- libtmux: 0.34.0 -> 0.35.0 (#920) +#### Minimum `libtmux~=0.35.0` (was `~=0.34.0`) (#920) - Simplify redundant `target` passing and `window-index` usages (#920) +tmuxp adapts to libtmux changes that simplify redundant target passing and window-index usage. ## tmuxp 1.42.0 (2024-03-17) -### Breaking changes +tmuxp 1.42.0 follows libtmux's explicit-target command behavior. + +### Dependencies -- libtmux: 0.33.0 -> 0.34.0 (#919) +#### Minimum `libtmux~=0.34.0` (was `~=0.33.0`) (#919) - Explicit targets in `cmd()` +The libtmux update makes explicit command targets the expected path. ## tmuxp 1.41.1 (2024-03-17) -_Maintenance only, no bug fixes or new features_ +tmuxp 1.41.1 is a small compatibility patch after the split-window API change. -- WorkspaceBuilder: Use `Pane.split` instead of `Window.split_window` +### Development + +#### Workspace builder uses `Pane.split` + +The builder now uses the newer `Pane.split` path instead of the historical `Window.split_window` helper. ## tmuxp 1.41.0 (2024-03-17) -_Maintenance only, no bug fixes or new features_ +tmuxp 1.41.0 tracks libtmux's split API rename and refreshes development tooling. -### Breaking changes +### Dependencies -- libtmux: 0.32.0 -> 0.33.0 (#918) +#### Minimum `libtmux~=0.33.0` (was `~=0.32.0`) (#918) - Move `split_window()` to `split()`. +libtmux moved the split-window behavior from `split_window()` to `split()`. The old method name is kept inline here as migration history rather than linked as current API. ### Development -- poetry: 1.7.1 -> 1.8.1 +#### Poetry 1.8.1 - See also: https://github.com/python-poetry/poetry/blob/1.8.1/CHANGELOG.md +Development tooling moved to Poetry 1.8.1. ## tmuxp 1.40.0 (2024-03-32) -_Maintenance only, no bug fixes or new features_ +tmuxp 1.40.0 is a maintenance release for libtmux export fixes and Ruff 0.3. -### Breaking changes +### Dependencies -- libtmux: 0.31.0.post0 -> 0.32.0 (#914) +#### Minimum `libtmux~=0.32.0` (was `~=0.31.0.post0`) (#914) - Export fix, ruff 0.3.0 updates. +The libtmux update includes an export fix and matching lint updates. ### Development -- ruff 0.2.2 -> 0.3.0 (#913) +#### Ruff 0.3 command shape (#913) - Related formattings. Update CI to use `ruff check .` instead of `ruff .`. +CI now uses `ruff check .`, matching the Ruff 0.3 command interface. ## tmuxp 1.39.0 (2024-02-17) -_Maintenance only, no bug fixes or new features_ +tmuxp 1.39.0 tracks libtmux command-streamlining changes. -#### Breaking changes +### Dependencies -- libtmux: 0.30.2 -> 0.31.0 (#912) +#### Minimum `libtmux~=0.31.0` (was `~=0.30.2`) (#912) -- Renamings of libtmux 0.31.0's streamlining of `cmd()`, renaming of `attached_{window,pane}s` to - `active_{window,pane}s`. +Internal tmuxp code follows libtmux renames around command helpers and active window/pane accessors. Historical names such as `attached_windows` and `attached_panes` are left as migration notes. ## tmuxp 1.38.0 (2024-02-16) -_Maintenance only, no bug fixes or new features_ - -#### Breaking changes +tmuxp 1.38.0 follows the libtmux 0.30 API-name cleanup. -- libtmux: 0.28.1 -> 0.30.1 (#911) +### Dependencies - Updated method names +#### Minimum `libtmux~=0.30.1` (was `~=0.28.1`) (#911) -- Rename methods to libtmux v0.30.0+-style (#911). +tmuxp internal method usage was updated to match the libtmux 0.30 naming style. ## tmuxp 1.37.1 (2024-02-15) -#### Development +tmuxp 1.37.1 is a maintenance release for libtmux docs/CI fixes and CI action updates. -- libtmux: 0.28.0 -> 0.28.1 +### Dependencies - Maintenance release (docs and CI bumps) +#### Minimum `libtmux~=0.28.1` (was `~=0.28.0`) -#### Testing +The libtmux update is a maintenance release focused on docs and CI. -- CI: Bump actions to node 20+ versions - -## tmuxp 1.37.0 (2024-02-14) +### Development -### Breaking changes +#### GitHub Actions updated to Node 20 -- libtmux: 0.27.0 -> 0.28.0 (#910) +CI actions were bumped to Node 20-compatible versions. - Refresh and resize improvements +## tmuxp 1.37.0 (2024-02-14) -### Tests +tmuxp 1.37.0 tracks libtmux refresh and resize improvements. -- CI: Add tmux 3.4 to test matrix (#909) +### Dependencies -## tmuxp 1.36.0 (2024-02-07) +#### Minimum `libtmux~=0.28.0` (was `~=0.27.0`) (#910) -_Maintenance only, no bug fixes or new features_ +The libtmux update brings refresh and resize improvements used by tmuxp. -### Breaking changes +### Development -- libtmux: 0.26.0 -> 0.27.0 (#908) +#### tmux 3.4 in CI (#909) - QueryList generic typing improvements. +The CI matrix now includes tmux 3.4. -## tmuxp 1.35.0 (2024-02-07) +## tmuxp 1.36.0 (2024-02-07) -_Maintenance only, no bug fixes or new features_ +tmuxp 1.36.0 is a libtmux typing maintenance release. -### Breaking changes +### Dependencies -- libtmux: 0.25.0 -> 0.26.0, maintenance release (#906) +#### Minimum `libtmux~=0.27.0` (was `~=0.26.0`) (#908) - Doc string + linting stringency updates. +The libtmux update improves `QueryList` generic typing. -### Development +## tmuxp 1.35.0 (2024-02-07) -- Strengthen linting (#907) +tmuxp 1.35.0 tightens linting and follows libtmux docstring maintenance. - - Add flake8-commas (COM) +### Dependencies - - https://docs.astral.sh/ruff/rules/#flake8-commas-com - - https://pypi.org/project/flake8-commas/ +#### Minimum `libtmux~=0.26.0` (was `~=0.25.0`) (#906) - - Add flake8-builtins (A) +The libtmux update carries docstring and linting maintenance. - - https://docs.astral.sh/ruff/rules/#flake8-builtins-a - - https://pypi.org/project/flake8-builtins/ +### Development - - Add flake8-errmsg (EM) +#### Stricter Ruff rule families (#907) - - https://docs.astral.sh/ruff/rules/#flake8-errmsg-em - - https://pypi.org/project/flake8-errmsg/ +Linting now includes additional rule families for commas, builtins, and exception-message style. ## tmuxp 1.34.0 (2023-12-21) -_Maintenance only, no bug fixes or new features_ +tmuxp 1.34.0 is an API documentation and internal package-boundary cleanup release. ### Breaking changes -- libtmux: 0.24.1 -> 0.25.0, maintenance release (#896) +#### Internal modules moved under `tmuxp._internal` (#897, #900) - Improve styling via pydocstyle. +`config_reader` and `_types` moved into the internal namespace. These were implementation details, and the changelog leaves the old names unlinked because they are not stable public entrypoints. -- `config_reader`: Move to `tmuxp._internal` (#897) -- `_types`: Move to `tmuxp._internal` (#900) +### Dependencies -### Documentation - -- Refactor API docs to split across multiple pages (#898) -- Remove unused reStructuredText section headers from some modules (#898) +#### Minimum `libtmux~=0.25.0` (was `~=0.24.1`) (#896) -## tmuxp 1.33.0 (2023-12-21) +The libtmux update improves docstring style through pydocstyle work. -_Maintenance only, no bug fixes or new features_ +### Documentation -### CI +#### API docs split across pages (#898) -- Move CodeQL from advanced configuration file to GitHub's default -- Add pydocstyle rule to ruff (#891) +API documentation was broken out into multiple pages and stale reStructuredText section headings were removed. -### Documentation +## tmuxp 1.33.0 (2023-12-21) -- Add docstrings to functions, methods, classes, and packages (#891) +tmuxp 1.33.0 makes documentation and CI stricter. -## tmuxp 1.32.1 (2023-11-23) +### Documentation -### Packaging +#### More complete docstrings (#891) -- pypoetry: Add `gp-lib` to `test` dependency group +Functions, methods, classes, and packages received docstrings, and pydocstyle enforcement was added through Ruff. ### Development -- libtmux: 0.24.0 -> 0.24.1 (maintenance release) +#### CodeQL defaults -### Tests +CodeQL moved from an advanced configuration file to GitHub's default setup. -- Shell tests: Use named, typed test fixture (#893) +## tmuxp 1.32.1 (2023-11-23) -## tmuxp 1.32.0 (2023-11-19) +tmuxp 1.32.1 is a test dependency and fixture-maintenance patch. -_Maintenance only, no bug fixes or new features_ +### Dependencies -### Packaging +#### libtmux 0.24.1 and `gp-lib` test dependency -- Move pytest configuration to `pyproject.toml` (#886) -- Poetry: 1.6.1 -> 1.7.0 +The test dependency group gained `gp-lib`, and libtmux moved to the 0.24.1 maintenance release. - See also: https://github.com/python-poetry/poetry/blob/1.7.0/CHANGELOG.md +### Development -- Add Python 3.12 to trove classifiers -- Packaging (poetry): Fix development dependencies +#### Named shell test fixtures (#893) - Per [Poetry's docs on managing dependencies] and `poetry check`, we had it wrong: Instead of using extras, we should create these: +Shell tests now use named, typed fixtures for clearer failures. - ```toml - [tool.poetry.group.group-name.dependencies] - dev-dependency = "1.0.0" - ``` +## tmuxp 1.32.0 (2023-11-19) - Which we now do. +tmuxp 1.32.0 consolidates test configuration and moves formatting from Black to Ruff. - [Poetry's docs on managing dependencies]: https://python-poetry.org/docs/master/managing-dependencies/ +### Packaging -### Development +#### Pytest and Poetry metadata cleanup (#886) -- libtmux: 0.23.0 -> 0.24.0 (maintenance release) -- Move formatting from `black` to [`ruff format`] (#890) +Pytest configuration moved into `pyproject.toml`, Python 3.12 classifiers were added, and development dependency groups were corrected to match Poetry's dependency-group model. - This retains the same formatting style of `black` while eliminating a - dev dependency by using our existing rust-based `ruff` linter. +### Development - [`ruff format`]: https://docs.astral.sh/ruff/formatter/ +#### Ruff formatter replaces Black (#890) -- CI: Update action packages to fix warnings +Formatting now uses [Ruff format](https://docs.astral.sh/ruff/formatter/), eliminating a separate Black dependency while keeping the same style. - - [dorny/paths-filter]: 2.7.0 -> 2.11.1 +#### libtmux 0.24.0 and CI action updates - [dorny/paths-filter]: https://github.com/dorny/paths-filter +The dependency stack moved to libtmux 0.24.0, Poetry 1.7.0, and newer GitHub Actions packages. ## tmuxp 1.31.0 (2023-09-23) -### Breaking changes +tmuxp 1.31.0 drops Python 3.7 and reaches strict mypy compliance. -- Python 3.7 Dropped (#885) +### Breaking changes -### Development +#### Python 3.8+ is now required (#885) -- **Improved typings** +Python 3.7 support ends on this line. - Now [`mypy --strict`] compliant (#859) +### Development - [`mypy --strict`]: https://mypy.readthedocs.io/en/stable/command_line.html#cmdoption-mypy-strict +#### Strict typing (#859) -- Poetry 1.5.1 -> 1.6.1 (#885) +The project is now `mypy --strict` compliant, and Poetry moved to 1.6.1. ## tmuxp 1.30.1 (2023-09-09) -_Maintenance only, no bug fixes or new features_ +tmuxp 1.30.1 is the final Python 3.7 release line. ### Breaking changes -- Cut last python 3.7 release (EOL was June 27th, 2023) +#### Python 3.7 maintenance branch - For security updates, a 1.30.x branch can be maintained for a limited time, - if necessary. +Security updates, if needed, can target the 1.30.x branch after Python 3.7 end of life. ## tmuxp 1.30.0 (2023-09-04) -_Maintenance only, no bug fixes or new features_ +tmuxp 1.30.0 is a code-quality release built around Ruff. ### Development -- Code quality improved via [ruff] rules (#879) - - This includes fixes made by hand alongside ruff's automated fixes. The more - stringent rules include import sorting, and still runs almost instantly - against the whole codebase. +#### Faster linting and import sorting with Ruff (#879) -- CI: `black . --check` now runs on pushes and pull requests +Ruff now handles more code-quality rules and runs quickly over the full codebase. CI also checks Black formatting. -### Packaging +### Dependencies -- libtmux: v0.23.1 -> v0.23.2 +#### libtmux 0.23.2 - Final Python 3.7 release of libtmux +The libtmux update is the final Python 3.7-compatible libtmux line. ### Documentation -- README Example for Nix (#883), thank you @ChristopherHarwell! - -## tmuxp 1.29.1 (2023-09-02) +#### Nix README example (#883) -_Maintenance only, no bug fixes or new features_ +The README gained a Nix example from @ChristopherHarwell. -### Development +## tmuxp 1.29.1 (2023-09-02) -- libtmux: v0.23.0 -> v0.23.1 +tmuxp 1.29.1 is a typo-fix release. - Typo fixes inside libtmux +### Fixes -### Docs +#### Documentation and dependency typo fixes (#884) -- Typo fixes (#884), thanks @kianmeng +tmuxp docs received typo fixes from @kianmeng, and libtmux moved to a typo-fix patch release. ## tmuxp 1.29.0 (2023-08-20) -_Maintenance only, no bug fixes or new features_ +tmuxp 1.29.0 tracks libtmux code-quality work. -### Development +### Dependencies -- libtmux v0.22.2 -> v0.23.0 (#882) +#### libtmux 0.23.0 (#882) - Code quality improvements from https://github.com/tmux-python/libtmux/pull/488 +The libtmux update brings upstream code-quality improvements from [libtmux#488](https://github.com/tmux-python/libtmux/pull/488). -### Post-release: v1.29.0post0 (2023-09-02) +### Fixes -- libtmux post-release bumps +#### Post-release libtmux comments restored - Re-add comments that went missing from `ruff` formatter. +The `v1.29.0post0` follow-up re-added comments that were accidentally dropped during formatter work. ## tmuxp 1.28.2 (2023-08-20) -_Maintenance only, no bug fixes or new features_ +tmuxp 1.28.2 is a packaging-maintenance patch. -### Packaging +### Dependencies -- libtmux v0.22.1 -> v0.22.2 +#### libtmux 0.22.2 - Removes `setuptools` from `build-system` requirements +The libtmux update removes `setuptools` from build-system requirements. ## tmuxp 1.28.1 (2023-05-28) -_Maintenance only, no bug fixes or new features_ +tmuxp 1.28.1 restores Black as a development dependency while Ruff formatting matured. ### Development -- Add back `black` for formatting +#### Black restored temporarily - This is still necessary to accompany `ruff`, until it replaces black. +Black returned alongside Ruff until Ruff could fully replace it. ## tmuxp 1.28.0 (2023-05-27) -_Maintenance only, no bug fixes or new features_ - -### Internal improvements +tmuxp 1.28.0 starts the move from the older Black/isort/flake8 stack to Ruff. -- Move formatting, import sorting, and linting to [ruff]. - - This rust-based checker has dramatically improved performance. Linting and - formatting can be done almost instantly. - - This change replaces black, isort, flake8 and flake8 plugins. - -- libtmux: 0.21.1 -> 0.22.0 (Moved to ruff as well) -- poetry: 1.4.0 -> 1.5.0 +### Development - See also: https://github.com/python-poetry/poetry/releases/tag/1.5.0 +#### Ruff for linting, sorting, and formatting -[ruff]: https://ruff.rs +Ruff replaces Black, isort, flake8, and flake8 plugins for much faster whole-repo checks. The release also updates libtmux to 0.22.0 and Poetry to 1.5.0. ## tmuxp 1.27.1 (2022-04-07) -_Maintenance only, no bug fixes or new features_ +tmuxp 1.27.1 is a typing-maintenance patch. ### Development -- Update mypy to 1.2.0 -- libtmux 0.21.0 -> 0.21.1 (mypy / typing updates only) +#### mypy 1.2 and libtmux 0.21.1 + +The release updates mypy and picks up libtmux typing-only changes. ## tmuxp 1.27.0 (2022-01-29) -_Maintenance only, no bug fixes or new features_ +tmuxp 1.27.0 updates libtmux's tmux-format separator behavior. -### Internal improvements +### Dependencies -- libtmux 0.20.0 -> 0.21.0 (#865) +#### libtmux 0.21.0 (#865) - This updates the separator uses from libtmux to be a rarer character. See - [libtmux#475](https://github.com/tmux-python/libtmux/pull/475). +The libtmux bump uses a rarer separator for tmux format output, reducing the chance of parsing collisions. See [libtmux#475](https://github.com/tmux-python/libtmux/pull/475). ## tmuxp 1.26.0 (2023-01-15) -_Maintenance only, no bug fixes or new features_ +tmuxp 1.26.0 improves new-session parameter support through libtmux. -### Internal improvements +### Dependencies -- libtmux 0.19.1 -> 0.20.0 (#863) +#### libtmux 0.20.0 (#863) - Improves support around `Server.new_session` and its params, e.g. `-x` and - `-y`. +The libtmux bump improves `Server.new_session` support for tmux size flags such as `-x` and `-y`. ## tmuxp 1.25.0 (2023-01-07) -### Internal improvements +tmuxp 1.25.0 is a libtmux patch-line update. + +### Dependencies -- libtmux 0.18.3 -> 0.19.1 (#862) +#### libtmux 0.19.1 (#862) - 0.19.1 has a fix for `Window.set_window_option()` +The update includes a fix for the historical `Window.set_window_option()` path. ## tmuxp 1.24.1 (2023-01-07) -### Internal improvements +tmuxp 1.24.1 improves test reliability and tracks a libtmux patch release. + +### Dependencies + +#### libtmux 0.18.3 (#861) -- libtmux: 0.18.2 -> 0.18.3 (#861) +The libtmux update follows [libtmux#466](https://github.com/tmux-python/libtmux/pull/466). - via [libtmux#466](https://github.com/tmux-python/libtmux/pull/466) +### Development + +#### More reliable pane path test -- tests(test_pane_order): Improve reliability of `pane_current_path` test. +`test_pane_order` became less timing-sensitive around `pane_current_path`. ## tmuxp 1.24.0 (2022-12-30) -_Maintenance only, no bug fixes or new features_ +tmuxp 1.24.0 fixes session creation through libtmux and tightens test organization. -### Internal improvements +### Dependencies -- libtmux: 0.18.1 -> 0.18.2 +#### libtmux 0.18.2 - Fix for starting new sessions at default socket / temporary directory - ([libtmux#464](https://github.com/tmux-python/libtmux/pull/464)) +The libtmux update fixes starting new sessions at the default socket and temporary directory ([libtmux#464](https://github.com/tmux-python/libtmux/pull/464)). -- CLI Tests: Refactor `tests/cli` (#858) +### Development - - Fix resolution of directories +#### CLI test and builder constructor cleanup (#857, #858) -- WorkspaceBuilder: Require `Server` in constructor (#857) +CLI tests were reorganized, directory resolution was fixed, and `WorkspaceBuilder` now requires an explicit `Server` in its constructor. ## tmuxp 1.23.0 (_yanked_, 2022-12-28) -_Yanked release: `tmuxp load` issues, see #856_ +tmuxp 1.23.0 was yanked because of `tmuxp load` issues tracked in #856. -### Internal improvements +### Development -_Maintenance only, no bug fixes or new features_ +#### More type annotations (#796) -- Type annotations: More mypy typings (#796) +The yanked release carried additional mypy typing work. ## tmuxp 1.22.1 (2022-12-27) -_Maintenance only, no bug fixes or new features_ +tmuxp 1.22.1 is a documentation-only libtmux patch. -- Update libtmux 0.18.0 -> 0.18.1 +### Dependencies - Only code documentation fixes +#### libtmux 0.18.1 + +The libtmux update contains code documentation fixes. ## tmuxp 1.22.0 (2022-12-27) -### Improvement +tmuxp 1.22.0 improves {ref}`tmuxp-shell` server detection. + +### What's new + +#### `tmuxp shell` detects the current server from `TMUX` (#854) -- `tmuxp shell`: now detects current `server` via `TMUX` (#854) +The shell command can now infer the active tmux server from the `TMUX` environment variable. ## tmuxp 1.21.0 (2022-12-27) -_Maintenance only, no bug fixes or new features_ +tmuxp 1.21.0 is a libtmux maintenance release. -- libtmux 0.17.2 -> 0.18.0 +### Dependencies - Server: Improved `__repr__` +#### libtmux 0.18.0 -## tmuxp 1.20.3 (2022-12-27) +The libtmux update improves `Server.__repr__`. -- Fix warnings for `_update_panes()` in builder +## tmuxp 1.20.3 (2022-12-27) -## tmuxp 1.20.2 (2022-12-27) +tmuxp 1.20.3 removes builder warning noise. -_Internal update only_ +### Fixes -- libtmux 0.17.1 -> 0.17.2 +#### `_update_panes()` warnings fixed - More deprecation warnings +Builder warnings around `_update_panes()` were corrected. -## tmuxp 1.20.1 (2022-12-27) +## tmuxp 1.20.2 (2022-12-27) -_Internal update only_ +tmuxp 1.20.2 is an internal libtmux deprecation-warning update. -- libtmux 0.17.0 -> ~0.17.1 +### Dependencies - Deprecation warning updates, doc fixes +#### libtmux 0.17.2 -### Development +The update carries more upstream deprecation warning coverage. -- Poetry 1.2.2 -> 1.3.1 +## tmuxp 1.20.1 (2022-12-27) -## tmuxp 1.20.0 (2022-12-26) +tmuxp 1.20.1 is a small libtmux and tooling maintenance release. -### Breaking change +### Dependencies -- libtmux 0.16 -> 0.17 (#850) +#### libtmux 0.17.1 - This includes the API overhaul from - [libtmux#426](https://github.com/tmux-python/libtmux/pull/426). +The update carries deprecation-warning and documentation fixes. ### Development -- Tests: Stabilization fix for automatic rename test (#853) +#### Poetry 1.3.1 -## tmuxp 1.19.1 (2022-12-12) +Development tooling moved to Poetry 1.3.1. -### Fixes +## tmuxp 1.20.0 (2022-12-26) -- Update libtmux 0.16.0 -> 0.16.1 +tmuxp 1.20.0 adopts the libtmux 0.17 API overhaul. - This removes the underlying dependency on `packaging` +### Breaking changes -## tmuxp 1.19.0 (2022-12-10) +#### libtmux 0.17 API update (#850) -### What's new +The dependency bump includes the upstream API overhaul from [libtmux#426](https://github.com/tmux-python/libtmux/pull/426). Historical method names are intentionally not linked here because this entry describes a migration point. -- Environment variables for windows / panes (#845) +### Development - _Requires tmux 3.0_ +#### Automatic-rename test stabilization (#853) - Allow to configure window and pane specific environment variables +The automatic rename test received a reliability fix. - Having a setup like: +## tmuxp 1.19.1 (2022-12-12) - ```yaml - session_name: env-demo - environment: - DATABASE_URL: "sqlite3:///default.db" - windows: - - window_name: dev - environment: - DATABASE_URL: "sqlite3:///dev-1.db" - panes: - - pane - - environment: - DATABASE_URL: "sqlite3:///dev-2.db" - ``` +tmuxp 1.19.1 removes an indirect packaging dependency. - will result in a window with two panes. In the first pane `$DATABASE_URL` is - `sqlite3:///dev-1.db`, while in the second pane it is `sqlite3://dev-2.db`. - Any freshly created window gets `sqlite3:///default.db` as this is what was - defined for the session. +### Fixes - Credit: @zappolowski +#### libtmux 0.16.1 removes `packaging` -### Internal +The libtmux patch removes the underlying dependency on `packaging`. -- Update libtmux 0.15.9 -> 0.16.0 +## tmuxp 1.19.0 (2022-12-10) - - Support for environmental variables - - Remove reliance on `distutils.version.LooseVersion` for - `libtmux._compat.LegacyVersion` +tmuxp 1.19.0 adds scoped environment variables for sessions, windows, and panes. -- Fix distutil warnings by using libtmux 0.16.0's `LegacyVersion` (#727) +### What's new -## tmuxp 1.18.2 (2022-11-06) +#### Environment variables for windows and panes (#845) -**Maintenance release, no features or fixes** +Workspace configuration can now define environment variables at the session, window, and pane levels for tmux 3.0+. See {ref}`environmental-variables` for the documented environment surface and {ref}`configuration` for workspace structure. -### Development +### Dependencies -- Bump libtmux from 0.15.9 -> 0.15.10 (only test tweaks) -- Poetry no longer forces `in-project: true` +#### libtmux 0.16.0 and distutils warning fixes (#727) -## tmuxp 1.18.1 (2022-10-31) +The libtmux update supports the environment-variable work and removes reliance on `distutils.version.LooseVersion`. -### Bug fix +## tmuxp 1.18.2 (2022-11-06) -- cli: `tmuxp load`: Fix pass-through of config files (#843) +tmuxp 1.18.2 is a maintenance release with no user-facing features or fixes. -## tmuxp 1.18.0 (2022-10-30) +### Development -We now refer to configs as workspaces. Other than just, just maintenance. +#### libtmux and Poetry defaults -### Internal refactoring (#840) +libtmux moved to 0.15.10 for test tweaks, and Poetry no longer forces `in-project: true`. -#### Rename config to workspace +## tmuxp 1.18.1 (2022-10-31) -Reference to "config" are now "workspace" +tmuxp 1.18.1 fixes tmux config pass-through. -#### Organize files +### Fixes -- `cli/utils.py` functions moved to `workspace/finders.py` -- `config.py` split between: +#### `tmuxp load` passes through config files (#843) - - `workspace/finders.py` - - `workspace/freezer.py` - - `workspace/importers.py` - - `workspace/validation.py` +The load command correctly forwards tmux config file arguments again. -- `workspacebuilder.py` split into: +## tmuxp 1.18.0 (2022-10-30) - - `workspace/builder.py` - - `workspace/freezer.py` +tmuxp 1.18.0 renames the project vocabulary from "config" toward "workspace" and splits the old large modules into focused workspace modules. - `config.inline` moved to freezer +### Development -#### Tests +#### Workspace package split (#840) -- `tests/fixtures/{workspacebuilder,workspacefreezer}` -> `tests/fixtures/workspace/{builder,freezer}` -- `tests/test_import_{teamocil,tmuxinator}.py` -> `tests/workspace/test_import_{teamocil,tmuxinator}.py` +Finder, freezer, importer, validation, and builder behavior moved into the `tmuxp.workspace` package. Tests moved with those boundaries, making workspace loading, freezing, importing, and validation easier to maintain separately. ## tmuxp 1.17.3 (2022-10-30) -**Maintenance release, no features or fixes** +tmuxp 1.17.3 adds Python 3.11 metadata and CI coverage. ### Development -- Add python 3.11 to test grid, pyenv / asdf files (#842) - -### Packaging +#### Python 3.11 in the test grid (#842) -- Add python 3.11 to classifiers +CI, pyenv/asdf files, and package classifiers now include Python 3.11. ## tmuxp 1.17.2 (2022-10-29) -### Bug fix +tmuxp 1.17.2 fixes multi-workspace loading. -- CLI: Fix loading of multiple workspaces in `tmuxp load` (#838 fixes #837) +### Fixes -## tmuxp 1.17.1 (2022-10-15) +#### Multiple workspace loads (#838) -### Minor completion improvements +`tmuxp load` can load multiple workspace arguments correctly again, fixing #837. -- Improved shtab completions for files with `tmuxp load [tab]` (#834) +## tmuxp 1.17.1 (2022-10-15) -### Internal +tmuxp 1.17.1 improves shell completions after the argparse migration. -- Remove unused completion code leftover from click (#834) +### Fixes -## tmuxp 1.17.0 (2022-10-09) +#### Better file completions for `tmuxp load` (#834) -### Breaking changes +shtab completions now handle file completion for `tmuxp load` more usefully, and leftover Click completion code was removed. -- **Completions have changed** (#830) +## tmuxp 1.17.0 (2022-10-09) - Completions now use a different tool: [shtab]. See the [completions page] for more information. +tmuxp 1.17.0 replaces Click with argparse and switches completions to shtab. - If you were using earlier versions of tmuxp (earlier than 1.17.0), you may need to uninstall the old completions, first. +### Breaking changes - [completions page]: https://tmuxp.git-pull.com/cli/completion.html - [shtab]: https://docs.iterative.ai/shtab/ +#### Completion setup changed (#830) -- Deprecate `click` in favor of {mod}`argparse` (#830) +Completions are now generated with [shtab](https://docs.iterative.ai/shtab/). Users with older tmuxp completions may need to remove those before installing the new completions. See {ref}`cli-completions`. -### Packages +#### Click dependency removed (#830) -- Remove `click` dependency +The CLI moved from Click to argparse, so Click is no longer a runtime dependency. ## tmuxp 1.16.2 (2022-10-08) +tmuxp 1.16.2 fixes package metadata for YAML support. + ### Packaging -- Add `yaml` to required dependencies (#833, credit: @heindsight) +#### YAML runtime dependency declared (#833) + +`yaml` is now included in required dependencies. Thanks @heindsight. ## tmuxp 1.16.1 (2022-10-02) -### Bug fix +tmuxp 1.16.1 improves blank window-name behavior through libtmux. -- Update libtmux 0.15.7 -> 0.15.8 +### Fixes - Includes an improvement for blank window names, e.g. `window_name: ''` +#### Blank `window_name` support - See also: https://github.com/tmux-python/libtmux/pull/444 +The libtmux 0.15.8 update improves handling for configurations such as `window_name: ''`. ## tmuxp 1.16.0 (2022-10-01) -**Maintenance release, no features or fixes** +tmuxp 1.16.0 replaces the kaptan configuration dependency with tmuxp's own typed reader. -### Internal +### Development -- Add `ConfigReader`: Our clean, typed parser for raw strings and files (#828) +#### `ConfigReader` replaces kaptan (#828) - This is our shiny, new, 200-line, doctested and typed parser that replaces `kaptan`. +The new reader handles raw strings and files with a smaller typed, doctested implementation. ### Packaging -- Drop kaptan dependency (#828) +#### kaptan removed (#828) + +The kaptan dependency was dropped. ## tmuxp 1.15.3 (2022-10-01) -### Bug fixes +tmuxp 1.15.3 fixes `start_directory` behavior in the workspace builder. + +### Fixes + +#### `start_directory` builder fix (#829) -- WorkspaceBuilder: Fix bug in `start_directory` (#829, credit: @heindsight) +The workspace builder now handles the reported `start_directory` case correctly. Thanks @heindsight. ## tmuxp 1.15.2 (2022-09-24) -**Maintenance release, no features or fixes** +tmuxp 1.15.2 fixes packaging around test configuration. ### Packaging -- Move conftest.py to root, to avoid packaging in wheel (#826 vs #825) +#### Root `conftest.py` kept out of wheels (#826) -## tmuxp 1.15.1 (2022-09-23) +The test `conftest.py` moved to the repository root so it is not accidentally packaged in wheels. -**Maintenance release, no features or fixes** +## tmuxp 1.15.1 (2022-09-23) -### Infrastructure +tmuxp 1.15.1 is an infrastructure release for CI, packaging, and dependency cleanup. -- CI speedups (#819) +### Development - - Split out release to separate job so the PyPI Upload docker image isn't pulled on normal runs - - Clean up CodeQL +#### Faster CI and cleaner coverage configuration (#819, #824) -- Bump poetry 1.1.x -> 1.2.x -- Bump libtmux 0.15.1 -> 0.15.7 - - 0.15.7 (move `.coveragerc` -> `pyproject.toml`) - - 0.15.6 (#823, pytest fixes, packaging improvements) - - 0.15.3 (#821, pytest plugin improvements, root-level - conftest.py) -- Move `.coveragerc` -> `pyproject.toml` (#824) +CI no longer pulls the PyPI upload image for ordinary runs, CodeQL was cleaned up, Poetry moved to 1.2, coverage configuration moved into `pyproject.toml`, and libtmux advanced through its 0.15.x pytest-plugin improvements. ### Packaging -- Remove `MANIFEST.in` - - This is handled by poetry's `include` in pyproject.toml. +#### Poetry-managed package contents -- Remove `.tmuxp-before-script.sh` from `.tmuxp.yaml` +`MANIFEST.in` was removed in favor of Poetry metadata, and the project tmuxp config no longer references `.tmuxp-before-script.sh`. ## tmuxp 1.15.0 (2022-09-11) -**Maintenance release, no features or fixes** +tmuxp 1.15.0 moves the project into `src/` layout. ### Development -- Project moved to `src/` layout (#814) +#### Source layout migration (#814) -## tmuxp 1.14.0 (2022-09-11) +Package code now lives under `src/`, aligning tmuxp with the surrounding Python packaging convention. -**Maintenance release, no features or fixes** +## tmuxp 1.14.0 (2022-09-11) -There will be several of these releases as infrastructure and APIs are upgraded -to facilitate fixes for layout issues and general contributions. +tmuxp 1.14.0 starts a maintenance series for infrastructure and API upgrades. ### Development -- libtmux bumped to v0.15.1 +#### libtmux 0.15.1 and doctested docs - This includes a major retooling underneath, including `src/` layout and - testing of `doctest` in documentation. - -[v0.15.1]: https://github.com/tmux-python/libtmux/blob/v0.15.1/CHANGES#libtmux-0151-2022-09-11 +The libtmux bump brings a major upstream retooling, including `src/` layout and doctest coverage for documentation. ### Documentation -- Render changelog in [`linkify_issues`] (#812) -- Fix Table of contents rendering with sphinx autodoc with [`sphinx_toctree_autodoc_fix`] (#812) -- Test doctests in our docs via [`pytest_doctest_docutils`] (built on [`doctest_docutils`]) (#812) +#### Changelog, autodoc, and doctest helpers (#812) -[`linkify_issues`]: https://gp-libs.git-pull.com/linkify_issues/ -[`sphinx_toctree_autodoc_fix`]: https://gp-libs.git-pull.com/sphinx_toctree_autodoc_fix/ -[`pytest_doctest_docutils`]: https://gp-libs.git-pull.com/doctest/pytest.html -[`doctest_docutils`]: https://gp-libs.git-pull.com/doctest +The docs gained issue-linking, a Sphinx autodoc TOC rendering fix, and documentation doctests through the gp-libs doctest helpers. ## tmuxp 1.13.3 (2022-09-10) -- Revert v1.13.1's #793 change for now, the behavior behind launching workspace - layouts need to be understood clearly. Future releases will decisively solve - this issue (#811). +tmuxp 1.13.3 rolls back the 1.13.1 layout change while the loading behavior is studied further. + +### Fixes + +#### Layout change reverted (#811) + +The #793 layout fix was reverted so future releases can address pane layout behavior more deliberately. ## tmuxp 1.13.2 (2022-09-10) -### Bug fixes +tmuxp 1.13.2 adjusts layout sizing for users hitting pane spacing issues. -- Layout size has been bumped for those experiencing layout spacing issues - (#809, fixes #800) +### Fixes - If you encounter issues with pane spacing, consider passing an `option` like - so: +#### Larger default layout size (#809) - ```yaml - session_name: main-pane-height - start_directory: "~" - options: - default-size: 999x999 - windows: - - window_name: my window name - layout: main-horizontal - options: - main-pane-height: 30 - panes: - - shell_command: top - - shell_command: top - - shell_command: top - - shell_command: echo "hey" - - shell_command: echo "moo" - ``` +The layout size was bumped as a mitigation for spacing issues tracked in #800. ### Development -- Add [flake8-bugbear](https://github.com/PyCQA/flake8-bugbear) (#807) -- Add [flake8-comprehensions](https://github.com/adamchainz/flake8-comprehensions) (#808) +#### Additional lint plugins (#807, #808) + +flake8-bugbear and flake8-comprehensions were added to the linting stack. ## tmuxp 1.13.1 (2022-08-21) -### Bug fixes +tmuxp 1.13.1 attempted to fix several layout-related issues. + +### Fixes + +#### Layout issue fixes (#793) -- Fix layout related issues from #667, #704, #737, via - #793. Thank you @nvasilas. +Layout issues from #667, #704, and #737 were addressed with help from @nvasilas. ## tmuxp 1.13.0 (2022-08-14) -### Internal +tmuxp 1.13.0 adds early typing and doctest infrastructure. -- libtmux updated from v0.12 to v0.14 #790 -- Add [doctest](https://docs.python.org/3/library/doctest.html) w/ - [pytest + doctest](https://docs.pytest.org/en/7.1.x/how-to/doctest.html) via #791. -- Added basic [mypy](http://mypy-lang.org/) type annotations via #786 +### Development + +#### mypy, doctest, and libtmux updates (#786, #790, #791) + +The release adds basic mypy annotations, doctest support through pytest, and a libtmux update from 0.12 to 0.14. ## tmuxp 1.12.1 (2022-08-04) -### Bug fix +tmuxp 1.12.1 fixes first-pane `start_directory` handling. + +### Fixes + +#### First pane `start_directory` fix (#787) -- #787 Fix #724 Fix `start_directory` issue with first pane, - credit: nvasilas. +The first pane now respects `start_directory` correctly, fixing #724. Thanks @nvasilas. ## tmuxp 1.12.0 (2022-07-31) -_Mostly internal cleanups, no features_ +tmuxp 1.12.0 is mostly test cleanup. + +### Development -### Tests +#### Test reliability work (#774, #777, #781, #783) -- #774 Fix #620 tests not finishing -- #777 Fix #778 to move the old, broken `retry()` to the new - `retry_until()`, credit: @categulario. -- #783 Fix #620 Symlink edge for for pane order tests, credit: @categulario. -- #781 Testing with ZSH will mock `~/.zshrc` startup file for cleaner - pane outputs. +The suite was adjusted so tests finish reliably, old retry helpers moved to `retry_until()`, symlink edge cases for pane order were covered, and zsh startup files were mocked for cleaner pane output. ## tmuxp 1.11.1 (2022-05-02) -### Bug fix +tmuxp 1.11.1 tightens the Click compatibility floor. -- #775: Assure click 8+ +### Fixes + +#### Click 8+ required (#775) - tmuxp 1.10 supports click 7.x. +The 1.11 line now requires Click 8 or newer. tmuxp 1.10 remains the Click 7-compatible line. ## tmuxp 1.11.0 (2022-04-24) -### Compatibility +tmuxp 1.11.0 updates Click compatibility, shell completion behavior, and project maintenance tooling. -- #773: Allow click 8.1.x -- #770: Fix shell completion for `tmuxp load` {kbd}`tab` and `tmuxp freeze` - {kbd}`tab` -- Note: Requires click 8+ (#770) +### Fixes -### Maintenance +#### Click 8.1 and shell completion fixes (#770, #773) -- #762: CLI: Break up into modules in _cli.py_ -> _cli/{command}_ -- #761: Refactor _tests/_ constants and fixtures -- Show libtmux version with `-V` / `--version` +The CLI allows Click 8.1.x and fixes completions for `tmuxp load` and `tmuxp freeze`. ### Development -- Remove tox and tox-poetry-installer - - This created issues with running poetry while inside the virtualenv. +#### CLI module split and publishing cleanup (#761, #762) -- Publish packages to PyPI via github action by setting git tag. +The large CLI module was split into per-command modules, tests/constants were refactored, tox was removed, package publishing moved to GitHub Actions tags, and `-V`/`--version` shows the libtmux version. ## tmuxp 1.10.1 (2022-04-17) -### Compatibility +tmuxp 1.10.1 backports Click 8.1 compatibility. -- #773 (backport): Allow click 8.1.x +### Fixes + +#### Click 8.1 allowed (#773) + +The compatibility fix from the 1.11 line was backported to the final Python 3.7/3.8 branch. ## tmuxp 1.10.0 (2022-03-19) -### Compatibility +tmuxp 1.10.0 is the final Python 3.7 and 3.8 release and a major command-execution ergonomics release. -- Final python 3.7 and 3.8 release +### Breaking changes - Bug fixes and security updates will go to - [`v1.10.x`](https://github.com/tmux-python/tmuxp/tree/v1.10.x) +#### Final Python 3.7 and 3.8 release + +Bug fixes and security updates for those Python versions moved to the [`v1.10.x`](https://github.com/tmux-python/tmuxp/tree/v1.10.x) branch. ### What's new -- #747: Skip execution via `enter: false` - - See {ref}`enter`. - - :::{note} +#### Command execution controls: `enter`, `sleep_before`, and `sleep_after` (#747, #750) - _Experimental setting_: behavior and api is subject to change until stable. +Pane commands can now be sent without pressing Enter via `enter: false`, and execution can pause before or after individual commands. See {ref}`enter` and {ref}`sleep` for examples. - ::: +#### Non-interactive freeze improvements (#701) - ```yaml - session_name: Should not execute - windows: - - panes: - - shell_command: - - echo "this sends" - - cmd: echo "___$((1 + 3))___" - enter: false - # pane-wide skip - - shell_command: - - echo "___$((1 + 3))___" - enter: false - ``` +{ref}`cli-freeze` gained `--quiet`, `--yes`, `--config-format`, and `--save-to`, making one-command save flows and tmux key bindings practical. -- #750: Pause execution via `sleep_before: [int]` and `sleep_after: [int]` +#### Pane `shell` command support (#672) - See {ref}`sleep`. +Pane configuration can specify a `shell` for the initial command, matching tmux's `split-window [shell-command]` behavior. Thanks @jerri. - :::{note} +### Fixes - _Experimental setting_: behavior and api is subject to change until stable. +#### `.yml` conversion support (#725) - ::: +{ref}`cli-convert` now loads `.yml` files correctly. Thanks @kalixi. - ```yaml - session_name: Pause / skip command execution (command-level) - windows: - - panes: - - shell_command: - # Executes immediately - - echo "___$((11 + 1))___" - # Delays before sending 2 seconds - - cmd: echo "___$((1 + 3))___" - sleep_before: 2 - # Executes immediately - - cmd: echo "___$((1 + 3))___" - # Pauses 2 seconds after - - cmd: echo "Stuff rendering here!" - sleep_after: 2 - # Executes after earlier commands (after 2 sec) - - cmd: echo "2 seconds later" - ``` +### Development -- #701: `tmuxp freeze` now accepts `--quiet` and `--yes` along with the - `--config-format` and filename (`--save-to`). This means you can do it all in - one command: +#### Command parsing refactor (#752) - `tmuxp freeze -yqo .tmuxp.yaml` +Internally, command entries moved from bare strings to dictionaries so per-command options can be represented without changing normal user configuration. - Or bind it to `.tmux.conf` itself: `bind -T root C-s run-shell "tmuxp freeze -yqo .tmuxp.yaml"` +#### Tooling and docs cleanup (#738, #745) - Credit: [@davidatbu](https://github.com/davidatbu) +The codebase was formatted with Black and isort, run through pyupgrade, and the docs moved to Furo with expanded command-execution examples. -- #672: Panes now accept `shell` for their initial command. +## tmuxp 1.9.4 (2022-01-10) - Equivalent to `tmux split-windows`'s `[shell-command]` +tmuxp 1.9.4 moves packaging to Poetry and adds the edit command. - ```yaml - session_name: Pane shell example - windows: - - window_name: first - window_shell: /usr/bin/python2 - layout: even-vertical - suppress_history: false - options: - remain-on-exit: true - panes: - - shell: /usr/bin/python3 - shell_command: - - print('This is python 3') - - shell: /usr/bin/vim -u none - shell_command: - - iAll panes have the `remain-on-exit` setting on. - - When you exit out of the shell or application, the panes will remain. - - Use tmux command `:kill-pane` to remove the pane. - - Use tmux command `:respawn-pane` to restart the shell in the pane. - - Use and then `:q!` to get out of this vim window. :-) - - shell_command: - - print('Hello World 2') - - shell: /usr/bin/top - ``` +### What's new - Credit: @jerri +#### `tmuxp edit` (#707) -### Improvements +The new {ref}`cli-edit` command opens workspace files from a project or config directory for editing. -- Improve `tmuxp freeze` UX flow, credit @joseph-flinn (#657, in re: #627) -- `tmuxp freeze` will now detect the attached session if no session name - is specified. Credit: @will-ockmore. (#660) +### Breaking changes -### Bugs +#### Python 3.6 support removed (#726) -- Fix loading of `.yml` files with `tmuxp convert`, thank you @kalixi! (#725) +Python 3.6 support ends on this release line. -### Internal API +### Packaging -- #752: Command structure (internal API) +#### Poetry build and publish (#729) - To pave the way for per-command options such as `enter: false` (#53), commands are now a different format: +Packages are built with `poetry build` and published with `poetry publish`; libtmux is pinned to a matching Poetry-built release. - Before, [`str`](str): +### Development - ```python - "echo hello" - ``` +#### Pre-commit trial and formatter updates (#726) - After, {py:class}`dict`: +The project began trying pre-commit in pull requests, updated Poetry, and moved Black to 21.12b0. - ```python - { - "cmd": "echo hello" - } - ``` +## tmuxp 1.9.3 (2021-10-30) - This is purely internal. Normal usage should be the same since the - configuration emits the equivalent result. +tmuxp 1.9.3 is a small CLI and tooling maintenance release. -- #752: Configuration parsing refactorings +### Fixes -### Development +#### Help flag and typo fixes (#696, #700) -- Run through black + isort with string normalization (#738). This way we can - use black without any configuration. (One-time, big diff) -- Run codebase through pyupgrade (#745) -- Add `codeql-analysis.yml` to try it out +The CLI gained `-h`/`--help`, and documentation typos were fixed. -### Documentation +### Development -- Move to `furo` sphinx theme -- Reorganize documentation into sections -- Added examples for `enter: false` and `sleep: [second]` +#### Poetry 1.1 update (#689) -## tmuxp 1.9.4 (2022-01-10) +CI and lock files moved to Poetry 1.1.7. -### Packaging +## tmuxp 1.9.2 (2021-06-17) -- `poetry build` used to package in place of `python setup.py build` (#729) +tmuxp 1.9.2 allows Click 8.0.x and moves tmux manuals out of the repo. - Package maintainers: If you run into any issues check in at #625 and file an issue. +### Fixes - Additionally, `libtmux` has been pinned to a similar release at - [0.10.3](https://pypi.org/project/libtmux/0.10.3/) which has used the new - build process. +#### Click 8.0 compatibility (#686) -- `poetry publish` instead of `twine upload dist/*` (#729) +The dependency range now allows Click 8.0.x. - Similar to the above, reach out to the #625 issue if you bump into problems. +### Documentation -### What's new +#### tmux manuals split out -- `tmuxp edit` for configuration changes (#707, @GlebPoljakov) +The old `manual/` tree moved to [tmux-python/tmux-manuals](https://github.com/tmux-python/tmux-manuals). - Inside of configuration directory: `tmuxp edit yourconfig` +## tmuxp 1.9.1 (2021-06-16) - Inside a project: `tmuxp edit .` +tmuxp 1.9.1 picks up a libtmux window-selection fix. -### Removed support +### Dependencies -- Python 3.6 support has been removed (#726) +#### libtmux 0.10.1+ -### Development +The libtmux bump includes the `Window.select_window()` fix from [libtmux#271](https://github.com/tmux-python/libtmux/pull/271). -- We are trying `.pre-commit-config.yaml` in pull requests to automate - black, isort and flake8 for those who forget (#726) -- Poetry update 1.1.7 -> 1.1.12 and use new installer URL (#726) -- Black updated 21.9b0 -> 21.12b0 (#726) +## tmuxp 1.9.0 (2021-06-16) -## tmuxp 1.9.3 (2021-10-30) +tmuxp 1.9.0 moves to libtmux 0.10.x. -- #700: Add `-h` / `--help` option, thanks @GHPS -- #689: Update poetry to 1.1 - - CI: Use poetry 1.1.7 and `install-poetry.py` installer - - Relock poetry.lock at 1.1 (w/ 1.1.7's fix) -- #696: Typo fix, thanks @inkch +### Dependencies -## tmuxp 1.9.2 (2021-06-17) +#### libtmux 0.10.x -- #686: Allow click 8.0.x -- Remove `manual/`, move to https://github.com/tmux-python/tmux-manuals +tmuxp tracks the current libtmux 0.10 release line. -## tmuxp 1.9.1 (2021-06-16) +## tmuxp 1.8.2 (2021-06-15) -- libtmux: Update to 0.10.1+ to include `Window.select_window()` fix +tmuxp 1.8.2 republishes the package with missing test files restored. - https://github.com/tmux-python/libtmux/pull/271 +### Packaging -## tmuxp 1.9.0 (2021-06-16) +#### Source distribution test files (#474) -- libtmux: Update to 0.10.x +The release was rebuilt with `python setup.py sdist bdist_wheel` to include the missing test files. -## tmuxp 1.8.2 (2021-06-15) +## tmuxp 1.8.1 (2021-06-14) -- #474 Re-release with `python setup.py sdist bdist_wheel` to - fix missing test files +tmuxp 1.8.1 is a Homebrew packaging helper release. -## tmuxp 1.8.1 (2021-06-14) +### Packaging -- #681 Bump version to make homebrew release easier +#### Version bump for Homebrew (#681) + +The version was bumped to make the Homebrew release flow easier. ## tmuxp 1.8.0.post0 (2021-06-14) -- #681 tmuxp is now available on homebrew! Thank you @jvcarli! +tmuxp 1.8.0.post0 announces the Homebrew package. + +### Packaging + +#### Homebrew availability (#681) + +tmuxp became available on Homebrew. Thanks @jvcarli. ## tmuxp 1.8.0 (2021-06-14) -- #662 CI: Only update docs when changed -- #666 CI: Move test plugin packages from pyproject.toml to - pytest fixtures. Fixes #658 -- #661 +tmuxp 1.8.0 drops Python 2.7, raises the Python floor, and cleans up plugin-test packaging. + +### Breaking changes + +#### Python 3.6+ is now required (#661) - - Bump minimum version 3.5 -> 3.6 - - Drop python 2.7 support - - Modernize syntax (remove `__future__` and modesets) - - Update black to 21.6b0 +Python 2.7 support was removed, syntax was modernized, and Black moved to 21.6b0. + +### Development + +#### Plugin test package handling (#666) + +Test plugin packages moved from `pyproject.toml` metadata into pytest fixtures, fixing #658. + +#### Docs deploy only when changed (#662) + +CI avoids unnecessary docs updates when docs are untouched. ## tmuxp 1.7.2 (2021-02-03) -- #666 CI: Move test plugin packages from pyproject.toml to - pytest fixtures. Fixes #658 +tmuxp 1.7.2 backports plugin test package handling. + +### Development + +#### Plugin test packages through pytest fixtures (#666) + +The 1.8 test package fix was also applied on this line. ## tmuxp 1.7.1 (2021-02-03) -- #665 Support for passing tmux config file (`-f`), thanks - @jfindlay! Fixes #654 +tmuxp 1.7.1 adds support for a tmux config file flag. + +### What's new + +#### tmux config file pass-through (#665) + +tmuxp can pass `-f` to tmux for a specific configuration file. Thanks @jfindlay. ## tmuxp 1.6.5 (2021-02-03) -- #665 Support for passing tmux config file (`-f`), thanks @jfindlay! Fixes - #654 +tmuxp 1.6.5 backports tmux config file support to the 1.6 line. -## tmuxp 1.7.0 (2021-01-09) +### What's new -This will be the last Python 2.7 release of tmuxp. Bug fixes for python -2.7 will live in the [1.7.x branch]. +#### tmux config file pass-through (#665) -- #530 New feature: Plugin system +The `-f` config-file support from 1.7.1 was applied to the 1.6 branch. - - Add plugin system for user customization of tmuxp - - Add tests for the plugin system - - Update existing tests for the plugin system - - Add the plugin interface to the tmuxp package - - Add in depth documentation for the plugin system +## tmuxp 1.7.0 (2021-01-09) + +tmuxp 1.7.0 is the last Python 2.7 release and the first stable release with plugins, append-loading, file logging, and debug information. + +### Breaking changes - Thank you @joseph-flinn! +#### Last Python 2.7 release -- #656 New feature: Ability to append windows to a session +Bug fixes for Python 2.7 moved to the [`1.7.x`](https://github.com/tmux-python/tmuxp/tree/v1.7.x) branch. - `tmuxp load -a configfile` will append a configuration to your current - tmux session. +### What's new - Thank you @will-ockmore! +#### Plugin system (#530) -- #647 Improvement: Logging to file: +The release adds the plugin system, tests, public plugin interface, and documentation. See {ref}`plugins`. - `tmuxp load --log-level outputfile.log` +#### Append windows to the current session (#656) -- #643 New command: Debug information +`tmuxp load -a configfile` can append a workspace to the current tmux session. Thanks @will-ockmore. - Port `tmuxp debug-info` from via v1.6.2 +#### Load logging and debug info (#643, #647) -[1.7.x branch]: https://github.com/tmux-python/tmuxp/tree/v1.7.x +`tmuxp load` can write output to a log file, and {ref}`tmuxp-debug-info` is available for collecting issue-reporting context. Thanks @joseph-flinn. ## tmuxp 1.7.0a4 (2021-01-06) -- Port click package fix from 1.6.4 +tmuxp 1.7.0a4 carries a Click packaging fix onto the alpha line. + +### Fixes + +#### Click package fix + +The 1.6.4 Click packaging fix was ported. ## tmuxp 1.7.0a3 (2020-11-22) -- Port `tmuxp load --log-level outputfile.log` from - 1.6.3 +tmuxp 1.7.0a3 ports load logging to the alpha line. + +### What's new + +#### Load output log file + +The `tmuxp load --log-file` feature from 1.6.3 was ported. ## tmuxp 1.7.0a2 (2020-11-08) -- Port `tmuxp debug-info` from #643 from via v1.6.2 +tmuxp 1.7.0a2 ports debug information support to the alpha line. + +### What's new + +#### `tmuxp debug-info` + +The debug-info command from 1.6.2 was ported. ## tmuxp 1.7.0a1 (2020-11-07) -- #530 Plugin system +tmuxp 1.7.0a1 previews the plugin system. + +### What's new - - Add plugin system for user customization of tmuxp - - Add tests for the plugin system - - Update existing tests for the plugin system - - Add the plugin interface to the tmuxp package - - Add in depth documentation for the plugin system +#### Plugin system (#530) - Thank you @joseph-flinn! +The alpha adds plugin hooks, tests, interface code, and documentation. Thanks @joseph-flinn. ## tmuxp 1.6.4 (2021-01-06) -- #651 Fix packaging issue with click, thanks @dougharris! Fixes - #649 +tmuxp 1.6.4 fixes a Click packaging issue. + +### Fixes + +#### Click packaging fix (#651) + +The release fixes the Click package issue tracked in #649. Thanks @dougharris. ## tmuxp 1.6.3 (2020-11-22) -- #647 Adding option to dump `load` output to log file, thank you - @joseph-flinn! +tmuxp 1.6.3 adds log-file output to `tmuxp load`. - `tmuxp load file.yaml --log-file yourfile.txt` +### What's new - Adjust log levels: +#### Load output to a file (#647) - `tmuxp --log-level DEBUG load file.yaml --log-file yourfile.txt` +`tmuxp load file.yaml --log-file yourfile.txt` writes load output to a file, and the root `--log-level` flag controls verbosity. ## tmuxp 1.6.2 (2020-11-08) -- #643 New command `tmuxp debug-info` for creating github - issues, fixes #352. Thank you @joseph-flinn! +tmuxp 1.6.2 adds a debug-info command for issue reporting. + +### What's new + +#### `tmuxp debug-info` (#643) + +The new command collects system information for GitHub issues, fixing #352. Thanks @joseph-flinn. (v1-6-1)= ## tmuxp 1.6.1 (2020-11-07) -- #641 Improvements to `shell` +tmuxp 1.6.1 improves the interactive Python shell command. - Thanks [django-extensions] (licensed MIT) for the shell detection abstraction. - - - Deprecate `shell_plus` - - `tmuxp shell` now detects the best shell available by default - - Python 3.7+ with `PYTHONBREAKPOINT` set in env will drop into `pdb` by - default - - Drop into `code.interact` by default instead of `pdb` if no third - party shells found - - New options, override: +### What's new - - `--pdb`: Use plain old `breakpoint()` (python 3.7+) or - `pdb.set_trace` - - `--code`: Drop into `code.interact`, accepts `--use-pythonrc` - - `--bpython`: Drop into [bpython] - - `--ipython`: Drop into [ipython] - - `--ptpython`: Drop into [ptpython], accepts `--use-vi-mode` - - `--ptipython`: Drop into [ipython], accepts - `--use-vi-mode` +#### Smarter `tmuxp shell` selection (#641) -[django-extensions]: https://github.com/django-extensions/django-extensions +`tmuxp shell` deprecates `shell_plus`, chooses the best available shell by default, honors `PYTHONBREAKPOINT` on Python 3.7+, and exposes explicit `--pdb`, `--code`, `--bpython`, `--ipython`, `--ptpython`, and `--ptipython` choices. ## tmuxp 1.6.0 (2020-11-06) -- #636 + #638 New command: `tmuxp shell` +tmuxp 1.6.0 introduces {ref}`tmuxp-shell`, a Python console preloaded with the current tmux session, window, and pane. - Automatically preloads session, window, and pane via [libtmux] - {ref}`API objects ` and makes them available in a python - console. +### What's new - ```{image} _static/tmuxp-shell.gif - :width: 100% +#### Python shell with tmux context (#636, #638) - ``` +`tmuxp shell` opens an interactive Python environment with libtmux objects already available. It can also execute expressions directly with `-c`, making quick session/window/pane inspection possible from scripts. - As of {ref}`1.6.1 (above) `, `tmuxp shell` will find the most - feature-rich shell available. If you have [ipython], or - [bpython] available, it will be selected automatically. Pass `--pdb` - to use standard library pdb, or `--code` to use `code.interact`. +## tmuxp 1.5.8 (2020-10-31) - In python 3.7+, supports `PYTHONBREAKPOINT`: +tmuxp 1.5.8 fixes session `start_directory` propagation. - ```{code-block} sh +### Fixes - $ pip install --user ipdb - $ env PYTHONBREAKPOINT=ipdb.set_trace tmuxp shell +#### `start_directory` passed to new sessions (#639) - ``` +New tmux sessions receive the configured `start_directory`, fixing #631. Thanks @joseph-flinn. - Inside a [uv](https://docs.astral.sh/uv/getting-started/features/#python-versions) - project you can add `ipdb` as a development dependency instead: +## tmuxp 1.5.7 (2020-10-31) - ```{code-block} sh +tmuxp 1.5.7 fixes project path loading for directories with periods. - $ uv add --dev ipdb +### Fixes - ``` +#### Directory paths with periods load correctly (#637) - For a pipx-style ad hoc execution, reach for - [uvx](https://docs.astral.sh/uv/guides/tools/): +`tmuxp load ~/work/your.project` now works without requiring the explicit `.tmuxp.yaml` path, fixing #212 and #201. - ```{code-block} sh +## tmuxp 1.5.6 (2020-10-12) - $ uvx --from ipdb ipdb3 --help +tmuxp 1.5.6 is a mixed freeze, prompt, session-name, docs, CI, and packaging update. - ``` +### What's new - You can execute python directly via `-c`: +#### Freeze overwrite and session-name options (#618, #626) - ```{code-block} sh +`tmuxp freeze` can accept `--overwrite`, and the CLI gained a new session-name option with tests and docs. - $ tmuxp shell -c 'print(session.name); print(window.name)' - my_server - my_window +#### Auto-confirm prompt option (#589) - $ tmuxp shell my_server -c 'print(session.name); print(window.name)' - my_server - my_window +The confirm command can auto-confirm prompts. Thanks @aRkedos. - $ tmuxp shell my_server my_window -c 'print(session.name); print(window.name)' - my_server - my_window +### Development - $ tmuxp shell my_server my_window -c 'print(window.name.upper())' - MY_WINDOW +#### Docs, CI, and packaging modernization (#619, #623, #629) - ``` +Docs moved to the self-hosted site, tests moved to GitHub Actions, Makefiles were modernized, Poetry packaging experiments began, isort moved to 5.x, and Black moved to 20.08b1. -[bpython]: https://bpython-interpreter.org/ -[ipython]: https://ipython.org/ -[ptpython]: https://github.com/prompt-toolkit/ptpython +## tmuxp 1.5.5 (2020-07-26) -## tmuxp 1.5.8 (2020-10-31) +tmuxp 1.5.5 adds the `ls` command and includes a broad maintenance pass. -- #639 Passes start_directory through to new tmux session - Fixes #631, thank you @joseph-flinn! +### What's new -## tmuxp 1.5.7 (2020-10-31) +#### `tmuxp ls` (#616) -- #637 Support for loading directories with periods in it +The new `tmuxp ls` command lists workspaces available from the config directory so they can be loaded by name. Thanks @pythops. - `tmuxp load ~/work/your.project` will now work +### Fixes - Earlier workaround was to do `tmuxp load ~/work/your.project/.tmuxp.yaml` +#### Documentation typos and CI cleanup (#480, #506, #519, #578) - Fixes #212 and #201 +The release includes community typo fixes, Travis cleanup, cached tmux builds, tmux 2.9/3.0a testing, and the move from Pipenv to Poetry. -## tmuxp 1.5.6 (2020-10-12) +## tmuxp 1.5.4 (2019-11-06) -- #618: allow passing `--overwrite` to `tmuxp freeze`. Thank you - @betoSolares! -- #589 added option for the the confirm command to auto-confirm the prompt. - Thank you @aRkedos! -- #626 Add new session name option to cli. Thank you @joseph-flinn! -- #626 Add test for new session name option -- #626 Update docs for new session name option -- #623 Move docs from RTD to self-serve site -- #623 Modernize Makefiles -- #623 New development docs -- #623 Move doc -> docs -- #623 Move tests to GitHub Actions -- #623 Update pyproject.toml to experiment with poetry packaging -- #619 isort 5 -- #629 Update black from 19.10b0 to 20.08b1 +tmuxp 1.5.4 fixes window focus and Python 3.7 CI. -## tmuxp 1.5.5 (2020-07-26) +### Fixes -- #616 (via: #599) New command: `tmuxp ls` +#### Window focus and Travis CI (#500) - List commands available via config directory. If the config is printed, - it's loadable via `tmuxp load configfilename` without needing to type the - full filepath. Thank you @pythops! +Window focus handling was corrected, and Travis builds were fixed for Python 3.7. -- #480 Fix typo, thanks @jstoja -- #578 Fix typo, thanks @mauroporras -- #519 Fix typo, thanks @timgates42 -- #506 Fix Makefile typo, thanks @wolfgangpfnuer -- #619 Update isort to 5.x -- Travis: Only run on master and PRs one time -- Travis: Add caching for tmux builds -- Travis: Test 2.9 and 3.0a -- #613: Move from Pipenv to Poetry +## tmuxp 1.5.3 (2019-06-06) -## tmuxp 1.5.4 (2019-11-06) +tmuxp 1.5.3 fixes the source distribution contents. -- #500: Fix window focus -- Fix travis CI builds for python 3.7 +### Packaging -## tmuxp 1.5.3 (2019-06-06) +#### Examples included in sdist (#377) -- #377: Include examples in source distribution package +Example files are included in the source distribution. ## tmuxp 1.5.2 (2019-06-02) -- Loosen libtmux version constraint to >0.8 and <0.9 (libtmux 0.8.2 - released today) -- #484 CHANGES converted to plain reStructuredText -- #491 `tmuxp freeze` will now infer active session with freezing -- #490 Fix XDG's `$XDG_CONFIG_HOME` behavior -- #483, #482, #480 Doc fixes -- #487 Simplifying handling of configs with no panes (Fixes - #470) +tmuxp 1.5.2 fixes packaging constraints, XDG config handling, and freeze defaults. -## tmuxp 1.5.1 (2019-02-18) +### Fixes -- Add tests/\*.sh scripts to MANIFEST.in to include them in Pypi package. -- Include twine to dev packages on requirements and Pipfile files. +#### Freeze and config-path fixes (#483, #487, #490, #491) -## tmuxp 1.5.0 (2018-10-02) +`tmuxp freeze` can infer the active session, XDG `$XDG_CONFIG_HOME` handling was fixed, empty-pane configs were simplified, docs were corrected, and libtmux constraints were loosened for the 0.8.2 release. -- Support Click 7.0 -- Remove unused `__future__` imports -- #471 Update libtmux 0.8.0 -> 0.8.1 -- #404 from @anddam, support XDG base directory -- Sort imports -- Add configuration and make command for isort. -- Add sphinxcontrib-napoleon. -- Assure _requirements/dev.txt_ dependencies are in _Pipfile_ -- Update sphinx, releases to latest version -- Sync _requirements/_.txt* dependencies with *Pipfile\*. -- Update docstring style to use numpy-style documentation. - This enhances readability and plays nicely with sphinx documentation. -- Documentation overhaul. - - - Areas like {meth}`tmuxp.cli.load_workspace` are now documented verbosely. - This is so contributors helping on the project can more quickly gain - situational awareness in this tricky area of code. +### Documentation -## tmuxp 1.4.2 (2018-09-30) +#### CHANGES converted to plain reStructuredText (#484) -- #431 Include tests in source distribution +The changelog format moved to plain reStructuredText at that point in the project history. -## tmuxp 1.4.1 (2018-09-26) +## tmuxp 1.5.1 (2019-02-18) -- Loosen click restraint to <7 +tmuxp 1.5.1 fixes package contents and development dependencies. -## tmuxp 1.4.0 (2018-03-11) +### Packaging -- Bump libtmux to 0.8.0 -- #264 Update license from BSD to MIT -- #348 Continuous integration updates and fixes for Travis CI +#### Test scripts included in source distributions - - Update builds to use trusty - - Remove older python 3 versions (before 3.6) - - Update pypy versions +Shell test scripts are included in package manifests, and Twine was added to development dependency files. -- #349 flake8 via continuous integration -- Improve reliability of time-sensitive tests by - using `while True` with a timeout. -- Update sphinx to 1.7.1 -- Update alagitpull (sphinx theme) to 0.0.19. - External websites open in new window. -- Update pytest to 3.4.1 +## tmuxp 1.5.0 (2018-10-02) -## tmuxp 1.3.5 (2017-11-10) +tmuxp 1.5.0 is a broad maintenance and documentation release around Click 7, XDG support, import sorting, and API docs. -- #312 Support for tmux 2.6 layout setting (via hooks) in the - following scenarios: +### What's new - - loading outside tmux - - loading inside tmux, via switch-client - - loading inside tmux, with session in background (with -d), and - reattaching/switching to after - - loading session outside tmux in background with -d, and - reattaching/switching after +#### XDG base directory support (#404) -- #308 Fix bug where layouts don't correctly set on tmux 2.6 -- Upgrade libtmux to 0.7.7 +tmuxp gained support for the XDG base directory convention. -## tmuxp 1.3.4 (2017-10-12) +#### Click 7 compatibility -- `before_script` now respects `start_directory` - in the session root. This makes it easier to run things like `pipenv install` as a `before_script`. +The CLI supports Click 7.0. -## tmuxp 1.3.3 (2017-10-07) +### Development -- Update libtmux to 0.7.5 for tmux 2.6 hotfix +#### Documentation and style modernization (#471) -## tmuxp 1.3.2 (2017-08-20) +The release updates libtmux, removes stale `__future__` imports, adds isort configuration, adopts NumPy-style docstrings, updates Sphinx tooling, and expands documentation for contributors working near the load workspace path. -- #184 - update libtmux to fix environmental variables in the - session scope -- Update libtmux to 0.7.4 -- Updates to pytest and pytest-rerunfailures +## tmuxp 1.4.2 (2018-09-30) -## tmuxp 1.3.1 (2017-05-29) +tmuxp 1.4.2 fixes source distribution contents. -- #252 Fix bug where loading a session with a name matching a subset - of current session causes undesired behavior. -- Update libtmux to 0.7.3 -- Switch theme to alagitpull (alabaster subtheme) -- Remove unneeded doc dependency packages +### Packaging -## tmuxp 1.3.0 (2017-04-27) +#### Tests included in source distributions (#431) -- #239 Improve support for formatted options when freezing and - using configs with them. -- #236 Support for symlinked directories, thanks @rafi. -- #235 Support for `options_after`, for setting options like - `synchronize-panes`. Thanks @sebastianst. -- #248 Drop python 2.6 support -- #248 Upgrade libtmux to 0.7.1 -- Upgrade colorama from 0.3.7 to 0.3.9 +Test files are included in the source distribution. -## tmuxp 1.2.8 (2017-04-02) +## tmuxp 1.4.1 (2018-09-26) -- #229 More helpful error message on systems missing - tmux. -- Update libtmux from 0.6.4 to 0.6.5. +tmuxp 1.4.1 loosens the Click dependency constraint. -## tmuxp 1.2.7 (2017-03-25) +### Dependencies -- Support for OpenBSD. +#### Click constraint loosened -## tmuxp 1.2.6 (2017-02-24) +The Click version constraint was relaxed to `<7`. -- #218 Fix pane ordering by running `select-layout` before - splits. +## tmuxp 1.4.0 (2018-03-11) -## tmuxp 1.2.5 (2017-02-08) +tmuxp 1.4.0 updates licensing, CI, docs, and dependency versions. -- #207 add custom tmuxp config directory via - `TMUXP_CONFIGDIR` variable. -- #199 support for running tmuxp on tmux `master`. -- update libtmux from 0.6.2 to 0.6.3. +### Breaking changes -## tmuxp 1.2.4 (2017-01-13) +#### License changed to MIT (#264) -- #198 bump click from 6.6 to 6.7 -- #195 pin packages for colorama and doc requirements +The project license moved from BSD to MIT. -## tmuxp 1.2.3 (2016-12-21) +### Development -- bump libtmux 0.6.0 to 0.6.1 -- #193 improve suppress history test, courtesy of @abeyer. -- #191 documentation typo from @modille -- #186 documentation typo from @joelwallis +#### CI and dependency refresh (#348, #349) -## tmuxp 1.2.2 (2016-09-16) +Travis moved to Trusty, older Python 3 versions were removed from CI, PyPy versions were updated, flake8 entered CI, time-sensitive tests became more reliable, and Sphinx, theme, and pytest versions were bumped. -- #181 Support tmux 2.3 +### Dependencies -## tmuxp 1.2.1 (2016-09-16) +#### libtmux 0.8.0 -- #132 Handle cases with invalid session names -- update libtmux from 0.5.0 to 0.6.0 +tmuxp tracks libtmux 0.8.0. -## tmuxp 1.2.0 (2016-06-16) +## tmuxp 1.3.5 (2017-11-10) -- #65 Ability to specify `options` and `global_options` via - configuration. Also you can specify environment variables via that. +tmuxp 1.3.5 improves tmux 2.6 layout handling. - Include tests and add example. +### Fixes -## tmuxp 1.1.1 (2016-06-02) +#### tmux 2.6 layout support (#308, #312) -- #167 fix attaching multiple sessions -- #165 fix typo in error output, thanks @fpietka -- #166 add new docs on zsh/bash completion -- Add back `tmuxp -V` for version info +Layouts now set correctly across outside-tmux loads, switch-client loads, detached sessions, and background loads followed by reattach or switch. libtmux moved to 0.7.7. -## tmuxp 1.1.0 (2016-06-01) +## tmuxp 1.3.4 (2017-10-12) -- #160 load tmuxp configs by name -- #134 Use `click` for command-line completion, Rewrite command - line functionality for importing, config finding, conversion and prompts. -- Remove `-l` from `tmuxp import tmuxinator|teamocil` -- #158 argparse bug overcome by switch to click +tmuxp 1.3.4 fixes `before_script` working directories. -## tmuxp 1.0.2 (2016-05-25) +### Fixes -- #163 fix issue re-attaching sessions that are already loaded -- #159 improved support for tmuxinator imports, from @fpietka. -- #161 readme link fixes from @Omeryl. +#### `before_script` respects session `start_directory` -## tmuxp 1.0.1 (2016-05-25) +Pre-load scripts now run relative to the session root, which makes project setup commands such as `pipenv install` work as expected. -- switch to readthedocs.io for docs -- #157 bump libtmux to 0.4.1 +## tmuxp 1.3.3 (2017-10-07) -## tmuxp 1.0.0-rc1 (2016-05-25) +tmuxp 1.3.3 is a tmux 2.6 hotfix release. -- version jump 0.11.1 to 1.0 -- tests moved to py.test framework -- [libtmux] core split into its own project -- #145 Add new-window command functionality, @ikirudennis -- #146 Optionally disable shell history suppression, @kmactavish -- #147 Patching unittest timing for shell history suppression -- move doc building, tests and watcher to Makefile -- update .tmuxp.yaml and .tmuxp.json for Makefile change -- overhaul README +### Dependencies -## tmuxp 0.11.0 (2016-02-29) +#### libtmux 0.7.5 -- #137 Support for environment settings in configs, thanks - `@tasdomas` -- Spelling correction, thanks [@sehe]. +The libtmux update carries a tmux 2.6 hotfix. -## tmuxp 0.10.0 (2016-01-30) +## tmuxp 1.3.2 (2017-08-20) -- #135 Load multiple tmux sessions at once, thanks [@madprog]. -- #131 #133 README and Documentation fixes +tmuxp 1.3.2 fixes session-scope environment variables through libtmux. -## tmuxp 0.9.3 (2016-01-06) +### Fixes -- switch to `.venv` for virtualenv directory to not conflict - with `.env` (used by [autoenv]). -- #130 move to [entr(1)] for file watching in tests. update docs. -- [compatibility] Support [Anaconda Python] 2 and 3 +#### Session-scope environment variables (#184) -## tmuxp 0.9.2 (2015-10-21) +The libtmux 0.7.4 update fixes environment variables in the session scope and refreshes pytest dependencies. -- #122 Update to support tmux 2.1, thank you [@estin]. -- use travis container infrastructure for faster tests -- change test in workspace builder test to use `top(1)` instead of - `man(1)`. `man(1)` caused errors on some systems where `PAGER` is set. +## tmuxp 1.3.1 (2017-05-29) -## tmuxp 0.9.1 (2015-08-23) +tmuxp 1.3.1 fixes ambiguous session-name matching and refreshes docs dependencies. -- #119 Add fix python 3 for [sysutils/pytmuxp] on FreeBSD ports. - See GH issue 119 and [#201564] @ FreeBSD Bugzilla. Thanks Ruslan - Makhmatkhanov. +### Fixes -## tmuxp 0.9.0 (2015-07-08) +#### Exact session-name matching (#252) -- Renamed `config.expandpath` to `config.expandshell`. -- compat 2.7/3.3 wrapper for `EnvironmentVarGuard` for - testing. -- You can now use environment variables inside of - `start_directory`, `before_script`, `shell_command_before`, - `session_name` and `window_name`. -- [examples]: add example for environmental variables, - `examples/env-variables.json` and `examples/env-variables.yaml`. -- #110 de-vendorize [colorama]. -- #109 fix failure of test_pane_order on fedora machines from - [@marbu] -- #105 append `.txt` extension to manuals (repo only) - from [@yegortimoshenko]. -- #107 Fix Server.attached_sessions return type by - [@thomasballinger]. -- update travis to use new tmux git repository. +Loading a session whose name is a subset of another session no longer causes the wrong attach/switch behavior. -## tmuxp 0.8.1 (2015-05-09) +### Dependencies -- [testing]: fix sniffer test runner in python 3 -- new animated image demo for RTD and README +#### libtmux 0.7.3 and docs theme update -## tmuxp 0.8.0 (2015-05-07) +tmuxp updated libtmux, switched docs to the alagitpull theme, and removed unneeded docs dependencies. -- version bump 0.1.13 -> 0.8.0 -- tmux 2.0 support -- Fix documentation for :meth:`Session.switch_client()`. -- Add `--log-level` argument. -- Refactor `{Server,Session,Window,Pane}.tmux` into: +## tmuxp 1.3.0 (2017-04-27) - - {meth}`Server.cmd()` - - {meth}`Session.cmd()` - - {meth}`Window.cmd()` - - {meth}`Pane.cmd()` +tmuxp 1.3.0 adds better formatted-option support, symlinked directory support, and `options_after`. - (See conversation at ) +### What's new -- Refactor `util.tmux` into {meth}`util.tmux_cmd`. +#### Formatted options, symlinked directories, and `options_after` (#235, #236, #239) -## tmuxp 0.1.13 (2015-03-25) +Freezing and loading handle formatted options more accurately, symlinked directories are supported, and `options_after` can set late tmux options such as `synchronize-panes`. -- Remove `package_metadata.py` in favor of `__about__.py`. -- `scent.py` for building docs -- docutils from 0.11 to 0.12 -- `bootstrap_env.py` will check for linux, darwin (OS X) and - windows and install the correct [sniffer] file watcher plugin. -- testsuite for cli uses {py:func}`tempfile.mkdtemp()` instead - `TMP_DIR` (which resolved to `.tmuxp` in the testsuite directory. -- replace [watchingtestrunner] in examples. - `.tmuxp.conf` and `.tmux.json` updated -- updates to doc links -- `make checkbuild` for verifying internal / intersphinx doc - references. -- Add Warning tmux versions less than 1.4 from [@techtonik]. -- Add documentation on leading space in `send_keys` - from [@thomasballinger]. -- Update about page from teamocil and erb support from [@raine]. +### Breaking changes -## tmuxp 0.1.12 (2014-08-06) +#### Python 2.7+ baseline (#248) -- [config] {meth}`config.expand` now resolves directories in configuration - via {py:func}`os.path.expanduser` and {py:func}`os.path.expandvars`. -- [config] {meth}`config.expandpath` for helping resolve paths. -- improved support for loading tmuxp project files from - outside current working directory. e.g. +Python 2.6 support was dropped, libtmux moved to 0.7.1, and colorama was updated. - ``` - $ tmuxp load /path/to/my/project/.tmuxp.yaml - ``` +## tmuxp 1.2.8 (2017-04-02) - Will behave better with relative directories. +tmuxp 1.2.8 improves missing-tmux errors. -## tmuxp 0.1.11 (2014-04-06) +### Fixes -- `before_script` now loads relative to project directory with - `./`. -- Use `bootstrap_env.py` in tmuxp's `.tmuxp.yaml` and - `.tmuxp.json` project files. -- Improvements to {meth}`util.run_before_script()`, - {class}`exc.BeforeLoadScriptFailed` behavior to print `stdout` and - return `stderr` is a non-zero exit is returned. -- `run_script_before` has moved to `util`. -- `BeforeLoadScriptFailed` and `BeforeLoadScriptNotExists` - has moved to the `exc` module. -- Tests for `run_script_before` refactored. +#### Helpful missing-tmux message (#229) -## tmuxp 0.1.10 (2014-04-02) +Systems without `tmux` on PATH receive a clearer error message. libtmux moved from 0.6.4 to 0.6.5. -- 2 bug fixes and allow panes with no shell commands to accept options, - thanks for these 3 patches, [@ThiefMaster]: -- #73 Fix an error caused by spaces in - `start_directory`. -- #77 Fix bug where having a `-` in a - `shell_command` would cauesd a build error. -- #76 Don't require `shell_command` to - pass options to panes (like `focus: true`). +## tmuxp 1.2.7 (2017-03-25) -## tmuxp 0.1.9 (2014-04-01) +tmuxp 1.2.7 adds OpenBSD support. -- The `--force` was not with us. +### What's new -## tmuxp 0.1.8 (2014-03-30) +#### OpenBSD support -- #72 Create destination directory if it doesn't exist. Thanks - [@ThiefMaster]. -- New context manager for tests, `temp_session`. -- New testsuite, `testsuite.test_utils` for testing testsuite - tools. -- New command, `before_script`, which is a file to - be executed with a return code. It can be a bash, perl, python etc. - script. -- #56 {ref}`python_api_quickstart ` +The release adds platform support for OpenBSD. -## tmuxp 0.1.7 (2014-02-25) +## tmuxp 1.2.6 (2017-02-24) -- #55 where tmuxp would crash with letter numbers in version. - Write tests. +tmuxp 1.2.6 fixes pane ordering. -## tmuxp 0.1.6 (2014-02-08) +### Fixes -- {meth}`Window.split_window()` now allows `-c start_directory`. -- #35 Builder will now use `-c start_directory` to - create new windows and panes. +#### Layout selected before splits (#218) - This removes a hack where `default-path` would be set for new pane and - window creations. This would bleed into tmux user sessions after - creations. +Pane order is preserved by running `select-layout` before creating splits. -## tmuxp 0.1.5-1 (2014-02-05) +## tmuxp 1.2.5 (2017-02-08) -- #49 bug where `package_manifest.py` missing - from `MANIFEST.in` would cause error installing. +tmuxp 1.2.5 adds custom config-directory support and tracks tmux master. -## tmuxp 0.1.5 (2014-02-05) +### What's new -- section heading normalization. -- tao of tmux section now treated as a chatper. tao of tmux may be - split off into its own project. -- use conventions from [tony/cookiecutter-pypackage]. +#### `TMUXP_CONFIGDIR` (#207) -## tmuxp 0.1.4 (2014-02-02) +Users can choose a custom tmuxp config directory with `TMUXP_CONFIGDIR`. See {ref}`TMUXP_CONFIGDIR`. -- Fix `$ tmuxp freeze` CLI output. -- Update `_compat` support module. -- Fix extra space in [PEP 263]. +### Development -## tmuxp 0.1.3 (2014-01-29) +#### tmux master support (#199) -- #48 Fix Python 3 CLI issue. -- #48 `$ tmuxp` without option raises an error. -- Add space before send-keys to not populate bash and zsh - history. +The project added support for running against tmux master and updated libtmux to 0.6.3. -## tmuxp 0.1.2 (2014-01-08) +## tmuxp 1.2.4 (2017-01-13) -- now using werkzeug / flask style testsuites. -- #43 Merge `tmuxp -d` for loading in detached mode. - Thanks [roxit]. +tmuxp 1.2.4 is a dependency pin release. -## tmuxp 0.1.1 (2013-12-25) +### Dependencies -- #32 Fix bug where special characters caused unicode caused - unexpected outcomes loading and freezing sessions. +#### Click 6.7 and pinned docs dependencies (#195, #198) -## tmuxp 0.1.0 (2013-12-18) +Click moved from 6.6 to 6.7, and colorama/docs dependencies were pinned. -- fix duplicate print out of filename with using `tmuxp load .`. -- version to 0.1. No `--pre` needed. Future versions will not use rc. +## tmuxp 1.2.3 (2016-12-21) -## tmuxp 0.1-rc8 (2013-12-17) +tmuxp 1.2.3 is a small libtmux, test, and docs patch. -- `unicode_literals` -- Move py2/py3 compliance code to `_compat`. +### Fixes -## tmuxp 0.1-rc7 (2013-12-07) +#### Suppress-history test and docs fixes (#186, #191, #193) -- #33 Partial rewrite of {meth}`config.expand`. -- tmuxp will exit silently with `Ctrl-c`. +The release improves suppress-history tests, fixes documentation typos, and bumps libtmux from 0.6.0 to 0.6.1. -## tmuxp 0.1-rc6 (2013-12-06) +## tmuxp 1.2.2 (2016-09-16) -- #31 [examples] from stratoukos add `window_index` option, - and example. +tmuxp 1.2.2 adds tmux 2.3 support. -## tmuxp 0.1-rc5 (2013-12-04) +### What's new -- #28 shell_command_before in session scope of config causing - duplication. New test. -- #26 #29 for OS X tests. Thanks stratoukos. -- #27 `$ tmuxp freeze` raises unhelpful message if session doesn't - exist. +#### tmux 2.3 support (#181) -## tmuxp 0.1-rc4 (2013-12-03) +tmuxp now supports tmux 2.3. -- fix bug were `focus: true` would not launch sessions when using - `$ tmuxp load` in a tmux session. +## tmuxp 1.2.1 (2016-09-16) -## tmuxp 0.1-rc3 (2013-12-03) +tmuxp 1.2.1 fixes invalid session-name handling. -- #25 `focus: true` not working in panes. Add - tests for focusing panes in config. -- {meth}`Pane.select_pane()`. -- add new example for `focus: true`. +### Fixes -## tmuxp 0.1-rc2 (2013-11-23) +#### Invalid session names (#132) -- #23 fix bug where workspace would not build with pane-base-index - set to 1. Update tests to fix if `pane-base-index` is not 0. -- removed `$ tmuxp load --list` functionality. Update - {ref}`quickstart` accordingly. +Invalid session names are handled correctly, and libtmux moved from 0.5.0 to 0.6.0. -## tmuxp 0.1-rc1 (2013-11-23) +## tmuxp 1.2.0 (2016-06-16) -- [pep8] in unit tests. -- Changelog will now be updated on a version basis, use [pep440] - versioning. +tmuxp 1.2.0 adds configuration support for tmux options and environment variables. -## tmuxp 0.1-dev (2013-11-21) +### What's new -- {meth}`Session.show_options`, {meth}`Session.show_option` now - accept `g` to pass in `-g`. +#### Session options and global options (#65) -## tmuxp 0.1-dev (2013-11-20) +Workspace configuration can define `options`, `global_options`, and environment variables, with examples and tests added. -- {meth}`Window.show_window_options`, - {meth}`Window.show_window_option` now accept `g` to pass in `-g`. -- #15 Behavioral changes in the WorkspaceBuilder to fix pane - ordering. -- #21 Error with unit testing python 2.6 python configuration tests. - Use {py:mod}`tempfile` instead. -- WorkspaceBuilder tests have been improved to use async better. +## tmuxp 1.1.1 (2016-06-02) -## tmuxp 0.1-dev (2013-11-17) +tmuxp 1.1.1 fixes attach behavior and restores version output. -- fix a bug where missing tmux didn't show correct warning. +### Fixes -## tmuxp 0.1-dev (2013-11-15) +#### Multiple-session attach and CLI polish (#165, #166, #167) -- Travis now tests python 2.6 as requirement and not allowed to - fail. +Attaching multiple sessions works again, typo fixes landed, zsh/bash completion docs were added, and `tmuxp -V` returned for version info. -## tmuxp 0.1-dev (2013-11-13) +## tmuxp 1.1.0 (2016-06-01) -- #19 accept `-y` argument to answer yes to questions. -- {meth}`cli.SessionCompleter` no longer allows a duplicate session - after one is added. -- ongoing work on {ref}`about-tmux`. +tmuxp 1.1.0 rewrites the CLI around Click and adds config-name loading. -## tmuxp 0.1-dev (2013-11-09) +### What's new -- [translation] [documentation in Chinese]. -- More work done on the {ref}`about-tmux` page. -- {meth}`Pane.split_window()` for splitting {class}`Window` at - `target-pane` location. +#### Load configs by name (#160) -## tmuxp 0.1-dev (2013-11-08) +tmuxp can load named configs from the configured search paths. -- [freeze] - `$ tmuxp freeze` will now freeze a window with a - `start_directory` when all panes in a window are inside the same - directory. -- [config] {meth}`config.inline` will now turn panes with no - other attributes and 1 command into a single item value. +#### Click CLI rewrite (#134, #158) - ``` - - panes: - - shell_command: top +Click replaced argparse for completions, importing, config finding, conversion, and prompts. The `-l` option was removed from `tmuxp import tmuxinator|teamocil`. - # will now inline to: +## tmuxp 1.0.2 (2016-05-25) - - panes - - top +tmuxp 1.0.2 fixes reattaching and improves tmuxinator imports. - This will improve ``$ tmuxp freeze`` - ``` +### Fixes -## tmuxp 0.1-dev (2013-11-07) +#### Already-loaded session reattach (#159, #161, #163) -- Remove old logger (based on [tornado's log.py]), replace - with new, simpler one. -- fix [teamocil] import. -- support import teamocil `root` to - `start_directory`. +Reattaching to loaded sessions was fixed, tmuxinator imports improved, and README links were corrected. -## tmuxp 0.1-dev (2013-11-06) +## tmuxp 1.0.1 (2016-05-25) -- tagged v0.0.37. Many fixes. Python 2.6 support. Will - switch to per-version changelog after 0.1 release. -- support for blank panes (null, `pane`, `blank`) and panes - with empty strings. -- tmuxp freeze supports exporting to blank panes. -- tmuxp freeze will now return a blank pane for panes that would - previously return a duplicate shell command, or generic python, node - interpreter. +tmuxp 1.0.1 moves docs hosting and updates libtmux. -## tmuxp 0.1-dev (2013-11-05) +### Documentation -- Support for `[-L socket-name]` and `[-S socket-path]` in - autocompletion and when loading. Note, switching client into another - socket may cause an error. -- Documentation tweaking to {ref}`API`, {ref}`about-tmux`. -- [pep257]. +#### Read the Docs hosting -## tmuxp 0.1-dev (2013-11-04) +Documentation moved to readthedocs.io. -- [pep257]. -- tagged version `v0.0.36`. +### Dependencies -## tmuxp 0.1-dev (2013-11-02) +#### libtmux 0.4.1 (#157) -- Many documentation, [pep257] fixes -- move old {class}`Server` methods `__list_panes()`, - `__list_windows` and `__list_sessions` into the single underscore. -- #12 fix for `$ tmuxp freeze` by @finder. -- Support for spaces in `$ tmuxp attach-session` and - `$ tmuxp kill-session`, and `$ tmuxp freeze`. -- [config] support for relative paths of `start_directory`. Add an - update config in _Start Directory_ on {ref}`examples`. +tmuxp tracks libtmux 0.4.1. -## tmuxp 0.1-dev (2013-11-01) +## tmuxp 1.0.0-rc1 (2016-05-25) -- New servers for {class}`Server` arguments `socket_name`, - `socket_path`, `config_file`. -- {class}`Server` support for `-2` with `colors=256` and - `colors=8`. -- `$ tmuxp -2` for forcing 256 colors and `tmuxp -8` for forcing 88. -- [config] Concatenation with `start_directory` via - {meth}`config.trickle()` if window `start_directory` is alphanumeric / - relative (doesn't start with `/`). See {ref}`Examples` in _start directory_. -- Fix bug with import teamocil and tmuxinator -- Improve quality of tmuxinator imports. Especially `session_name` - and `start_directory`. -- Allow saving with `~` in file destination. +tmuxp 1.0.0-rc1 is the release-candidate jump from the 0.11 line to 1.0. -## tmuxp 0.1-dev (2013-10-31) +### What's new -- {meth}`util.is_version()` -- correctly {meth}`config.trickle()` the `start_directory`. -- get `start_directory` working for configs -- fix :meth:`Window.kill_window()` target to - `session_id:window_index` for compatibility and pass tests. -- [examples]: Example for `start_directory`. -- fix bug where first and second window would load in mixed order -- {class}`Window.move_window()` for moving window. -- doc overhaul. front page, renamed orm_al.rst to internals.rst. +#### libtmux split and pytest migration (#145, #146, #147) -## tmuxp 0.1-dev (2013-10-30) +Core tmux APIs split into [libtmux](https://github.com/tmux-python/libtmux), tests moved to pytest, new-window support landed, shell-history suppression became configurable, Makefile-based docs/test tasks were refreshed, and the README was overhauled. -- fix bug where if inside tmux, loading a workspace via switch_client - wouldn't work. -- fix bug where `tmuxp load .` would return an error instead of a - notice. -- `tmuxp freeze ` experimental -- tmuxp now has experimental support for freezing live - sessions. -- {meth}`Window.kill_window()` -- support for `start_directory` (work in progress) +## tmuxp 0.11.0 (2016-02-29) -## tmuxp 0.1-dev (2013-10-29) +tmuxp 0.11.0 adds environment settings in configs. -- {meth}`Window.select_pane` now accepts `-l`, `-U`, `-D`, - `-L`, `-R`. -- support for `automatic-rename` option. -- 3 new {ref}`examples`, 'main-pane-height', 'automatic-rename', and - 'shorthands'. -- enhancements to prompts -- `tmuxp import` for teamocil and tmuxinator now has a wizard and offers - to save in JSON or YAML format. -- [b6c2e84] Fix bug where tmuxp load w/ session already loaded would - switch/attach even if no was entered -- when workspace loader crashes, give option to kill session, attach it or - detach it. -- tmux 1.8 `set-option` / `set-window-options` command - `target-window` fix. -- {class}`WorkspaceBuilder` now has `.session` attribute accessible - publicly. -- tmux will now use {meth}`Session.switch_client` and - {meth}`Session.attach_session` to open new sessions instead of `os.exec`. -- [config] tmuxp now allows a new shorter form for panes. Panes can just be a - string. See the shorthand form in the {ref}`examples` section. -- [config] support loading `.yml`. +### What's new -## tmuxp 0.1-dev (2013-10-28) +#### Config environment settings (#137) -- fix `tmuxp load .` fixed -- fix `tmuxp convert ` fixed. -- [pep257] fixes. -- {class}`Pane` now has {meth}`Pane.set_width` and - {meth}`Pane.set_height`. -- `./run_tests.py --tests` now automatically prepends - `tmuxp.testsuite` to names. -- {meth}`Window.tmux` and {meth}`Pane.tmux` will automatically add - their `{window/pane}_id` if one isn't specific. +Configuration files can now include environment settings. Thanks @tasdomas. -## tmuxp 0.1-dev (2013-10-27) +### Documentation -- [argcomplete] overhaul for CLI bash completion. -- `tmuxp load`, `tmuxp convert` and `tmuxp import` now support - relative and full filenames in addition to searching the config - directory. +#### Spelling correction -## tmuxp 0.1-dev (2013-10-26) +Spelling fixes landed from @sehe. -- initial version of [tmuxinator] - config importer. it does not support all options and it not guaranteed - to fully convert window/pane size and state. -- {meth}`config.in_dir` supports a list of `extensions` for - filetypes to search, i.e. `['.yaml', '.json']`. -- {meth}`config.is_config_file` now supports `extensions` - argument as a string also. -- fix `$ tmuxp load -l` to work correctly alongside - `$ tmuxp load filename`. +## tmuxp 0.10.0 (2016-01-30) -## tmuxp 0.1-dev (2013-10-25) +tmuxp 0.10.0 adds multi-session loading. -- fix bug where `-v` and `--version` wouldn't print version. -- property handle case where no tmux server exists when - `attach-session` or `kill-session` is used. -- test fixtures and initial work for importing - [tmuxinator] configs +### What's new -## tmuxp 0.1-dev (2013-10-24) +#### Load multiple sessions (#135) -- clean out old code for `automatic-rename` option. it will - be reimplemented fresh. -- check for `oh-my-zsh` when using `$SHELL` `zsh`. Prompt if - `DISABLE_AUTO_TITLE` is unset or set to `true`. -- tmuxp can now `$ tmuxp convert ` from JSON <=> YAML, back - and forth. -- New examples in JSON. Update the {ref}`examples` page in the - docs. -- [dev] `.tmuxp.json` now exists as a config for tmuxp development and - as an example. -- Fix bug where `tmuxp kill-session` would give bad output -- Fix bug in tab completion for listing sessions with no tmux server - is active. +tmuxp can load multiple tmux sessions at once. Thanks @madprog. -## tmuxp 0.1-dev (2013-10-23) +### Documentation -- zsh/bash/tcsh completion improvements for tab-completion options -- tmuxp `kill-session` with tab-completion. -- tmuxp `attach-session` with tab-completion. Attach session will - `switch-client` for you if you are inside of of a tmux client. -- tmuxp `load` for loading configs. -- unit test fixes. +#### README and docs fixes (#131, #133) -## tmuxp 0.1-dev (2013-10-21) +Documentation fixes landed alongside the feature. -- Make 1.8 the official minimym version, give warning notice to - upgrade tmux if out of date -- Fix regression causing unexpected build behavior due to unremoved code - supporting old tmux versions. -- Added 2 new examples to the {ref}`examples` page. -- Examples now have graphics -- `$ tmuxp -v` will print the version info. +## tmuxp 0.9.3 (2016-01-06) -## tmuxp 0.1-dev (2013-10-19) +tmuxp 0.9.3 improves development environment compatibility. -- tmuxp will now give warning and sys.exit() with a message if - `tmux` not found in system PATH -- internal overhaul of {class}`Server`, {class}`Session` - , {class}`Window`, and {class}`Pane` continues. - - - {class}`Server` has @property {meth}`Server.sessions`, which is forward - to {meth}`Server.list_sessions()` (kept to keep tmux commands in - serendipty with api), {meth}`Server._list_sessions()` returns dict - object from {meth}`Server.__list_sessions()` tmux command. - {meth}`Server.__list_sessions()` exists to keep the command layered so - it can be tested against in a matrix with travis and compatibility - methods can be made. - - {class}`Session` now has @property {meth}`Session.windows` returning a - list of {class}`Window` objects via {meth}`Session.list_windows()`. - @property {meth}`Session._windows` to {meth}`Session._list_windows()` - to return a list of dicts without making objects. - - {class}`Window` now has @property {meth}`Window.panes` returning a - list of {class}`Pane` objects via {meth}`Window.list_panes()`. - @property {meth}`Window._panes` to {meth}`Window._list_panes()` - to return a list of dicts without making objects. +### Development -## tmuxp 0.1-dev (2013-10-18) +#### `.venv`, entr, and Anaconda support (#130) -- internal overhaul of {class}`Server`, {class}`Session`, - {class}`Window`, and {class}`Pane`. +Virtualenv directories moved from `.env` to `.venv`, tests moved to [entr](http://entrproject.org/) for file watching, and Anaconda Python 2 and 3 were supported. - - `Session`, `Window` and `Pane` now refer to a data object - in {class}`Server` internally and always pull the freshest data. - - A lot of code and complexity regarding setting new data for objects - has been reduced since objects use their unique key identifier to - filter their objects through the windows and panes in `Server` - object. - - `Server` object is what does the updating now. +## tmuxp 0.9.2 (2015-10-21) -- [project] some research into supporting legacy tmux versions. tmux 1.6 - and 1.7 support seem likely eventually if there is enough demand. -- python 3 support +tmuxp 0.9.2 adds tmux 2.1 support and fixes test portability. -## tmuxp 0.1-dev (2013-10-17) +### What's new -- updated README docs with new project details, screenshots -- new example `.tmuxp.yaml` file updated to include - development workflow. Removed nodemon as the tool for checking files for - now. -- Support for switching sessions from within tmux. In both cases - after the the session is built and if session already exists. +#### tmux 2.1 support (#122) -## tmuxp 0.1-dev (2013-10-16) +tmuxp now supports tmux 2.1. Thanks @estin. -- use {meth}`util.which()` from salt.util to find tmux binary. -- add MANIFEST.in, fix issue where package would not - install because missing file -- bash / zsh completion. -- New page on {ref}`internals`. -- Updates to {ref}`about-tmux` page. -- add vim modeline for rst to bottom of this page -- Server is now a subclass of `util.TmuxObject`. -- subclasses of {class}`util.TmuxRelationalObject`, - {class}`Server`, {class}`Session`, {class}`Window`, {class}`Pane` now - have {meth}`util.TmuxRelationalObject.getById` (similar to [.get()] in - [backbone.js] collection), {meth}`util.TmuxRelationalObject.where` and - {meth}`util.TmuxRelationalObject.findWhere` ([.where()] and - [.findWhere()]), to easily find child objects. -- tmux object mapping has been split into - {class}`util.TmuxMappingObject`. The mapping and the relational has been - decoupled to allow {class}`Server` to have children while not being a - dict-like object. -- {class}`Server`, {class}`Session`, {class}`Window`, - {class}`Pane` now explicitly mixin subclasses. +### Development -## tmuxp 0.1-dev (2013-10-15) +#### Faster CI and safer pane-order tests -- new theme -- initial examples, misc. updates, front page update. -- support for `$ tmux .` to load `.tmuxp.{yaml/json/py}` in current - working directory. -- support for `socket-name` (`-L`) and `socket-path` - (`socket-path`) -- [config] Support for 1-command pane items. - - ``` - session_name: my session - windows: - - window_name: hi - panes: - - bash - - htop - ``` - -- If session name is already exists, prompt to attach. +Travis moved to container infrastructure, and pane-order tests stopped relying on `man(1)`. -## tmuxp 0.1-dev (2013-10-14) +## tmuxp 0.9.1 (2015-08-23) + +tmuxp 0.9.1 fixes FreeBSD Python 3 packaging support. + +### Fixes + +#### FreeBSD ports Python 3 fix (#119) + +The release includes the Python 3 fix for [sysutils/pytmuxp](http://www.freshports.org/sysutils/py-tmuxp/) in FreeBSD ports. + +## tmuxp 0.9.0 (2015-07-08) + +tmuxp 0.9.0 expands shell expansion, environment-variable support, and tmux 2.0-era compatibility. + +### Breaking changes + +#### `config.expandpath` renamed to `config.expandshell` + +The old config helper was renamed as environment and shell expansion behavior grew. + +### What's new + +#### Environment variables in workspace fields + +Environment variables can be used in `start_directory`, `before_script`, `shell_command_before`, `session_name`, and `window_name`, with JSON and YAML examples added. + +### Fixes + +#### Test and dependency cleanup (#105, #107, #109, #110) + +The release devendorizes colorama, fixes Fedora pane-order tests, updates manual file extensions, fixes an attached-sessions return type, and tracks the new tmux git repository. + +## tmuxp 0.8.1 (2015-05-09) + +tmuxp 0.8.1 is a test and docs media patch. + +### Fixes + +#### Python 3 sniffer test runner + +The sniffer test runner works under Python 3, and a new animated demo was added to docs and README. + +## tmuxp 0.8.0 (2015-05-07) + +tmuxp 0.8.0 jumps from 0.1.13 to the 0.8 line for tmux 2.0 support and command API cleanup. + +### What's new + +#### tmux 2.0 support and log-level option + +The release adds tmux 2.0 support, improves `Session.switch_client()` docs, and adds `--log-level`. + +### Breaking changes + +#### `.tmux` helpers renamed to `.cmd` + +Historical `{Server,Session,Window,Pane}.tmux` helpers were refactored into `.cmd()` methods, and `util.tmux` became `util.tmux_cmd`. These old names remain inline here because they describe the 2015 API surface. + +## tmuxp 0.1.13 (2015-03-25) + +tmuxp 0.1.13 cleans up package metadata, docs building, and tests. + +### Development + +#### Metadata and docs runner cleanup + +`package_metadata.py` was replaced with `__about__.py`, docs building moved to `scent.py`, docutils moved to 0.12, `bootstrap_env.py` learned platform-specific watcher setup, CLI tests stopped using the old `TMP_DIR`, and stale watchingtestrunner examples were replaced. + +### Documentation + +#### tmux compatibility and history docs + +The release adds warnings for tmux versions below 1.4, documents leading spaces in `send_keys`, and updates the about page from teamocil and erb support. + +## tmuxp 0.1.12 (2014-08-06) + +tmuxp 0.1.12 improves path expansion when loading project files from outside the current directory. + +### Fixes + +#### Relative project file loading + +Config path expansion now resolves user and environment variables, and project files such as `/path/to/project/.tmuxp.yaml` behave better with relative directories. + +## tmuxp 0.1.11 (2014-04-06) + +tmuxp 0.1.11 improves `before_script` handling. + +### Fixes + +#### Project-relative `before_script` + +`before_script` runs relative to the project directory, reports stdout/stderr more clearly on failure, and the script-runner exceptions moved into the exception module. + +## tmuxp 0.1.10 (2014-04-02) + +tmuxp 0.1.10 fixes early pane-option and command parsing bugs. + +### Fixes + +#### Pane command and option edge cases (#73, #76, #77) + +Patches from @ThiefMaster fix spaces in `start_directory`, dashes in `shell_command`, and panes that need options but no shell command. + +## tmuxp 0.1.9 (2014-04-01) + +tmuxp 0.1.9 fixes force behavior. + +### Fixes + +#### `--force` restored + +The release restores the intended `--force` behavior. + +## tmuxp 0.1.8 (2014-03-30) + +tmuxp 0.1.8 adds pre-load scripts and directory creation. + +### What's new + +#### `before_script` and destination directory creation (#56, #72) + +tmuxp can create missing destination directories and run a `before_script` before starting a tmux session. The release also adds test helpers and links users toward the libtmux Python API quickstart. + +## tmuxp 0.1.7 (2014-02-25) + +tmuxp 0.1.7 fixes version parsing. + +### Fixes + +#### Lettered version support (#55) + +tmuxp no longer crashes on lettered tmux version strings. + +## tmuxp 0.1.6 (2014-02-08) + +tmuxp 0.1.6 stops relying on tmux `default-path` for start directories. + +### Fixes + +#### `-c start_directory` for windows and panes (#35) + +Window and pane creation now use tmux's `-c start_directory`, avoiding the old `default-path` hack that could bleed into user sessions. + +## tmuxp 0.1.5-1 (2014-02-05) + +tmuxp 0.1.5-1 fixes an installation manifest issue. + +### Packaging + +#### Manifest package metadata fix (#49) + +`package_manifest.py` is included correctly so installs no longer fail. + +## tmuxp 0.1.5 (2014-02-05) + +tmuxp 0.1.5 normalizes docs structure and packaging conventions. + +### Documentation + +#### Tao of tmux and section-heading cleanup + +The Tao of tmux docs became their own chapter candidate, headings were normalized, and project structure borrowed conventions from `tony/cookiecutter-pypackage`. + +## tmuxp 0.1.4 (2014-02-02) + +tmuxp 0.1.4 fixes CLI output and compatibility helpers. + +### Fixes + +#### Freeze output and compatibility cleanup + +`tmuxp freeze` output was corrected, `_compat` was updated, and a PEP 263 spacing issue was fixed. + +## tmuxp 0.1.3 (2014-01-29) + +tmuxp 0.1.3 fixes Python 3 CLI behavior and shell-history suppression. + +### Fixes + +#### Python 3 CLI and history suppression (#48) + +Running `tmuxp` without an option raises the expected error, Python 3 CLI behavior was fixed, and sent keys are prefixed with a space to avoid populating bash and zsh history. + +## tmuxp 0.1.2 (2014-01-08) + +tmuxp 0.1.2 adds detached loading and refreshes test style. + +### What's new + +#### Detached load mode (#43) + +`tmuxp -d` can load sessions detached. Thanks roxit. + +### Development + +#### Werkzeug/Flask-style tests + +The testsuite moved toward Werkzeug/Flask-style testing helpers. + +## tmuxp 0.1.1 (2013-12-25) + +tmuxp 0.1.1 fixes special-character handling. + +### Fixes + +#### Unicode and special characters (#32) + +Loading and freezing sessions now handle special characters more predictably. + +## tmuxp 0.1.0 (2013-12-18) + +tmuxp 0.1.0 marks the first stable 0.1 release. + +### Fixes + +#### Current-directory load output + +The duplicate filename output from `tmuxp load .` was fixed, and future releases no longer require `--pre`. + +## tmuxp 0.1-rc8 (2013-12-17) + +tmuxp 0.1-rc8 tightens Python 2/3 compatibility. + +### Development + +#### Compatibility helpers + +`unicode_literals` was adopted, and Python 2/3 compatibility code moved into `_compat`. + +## tmuxp 0.1-rc7 (2013-12-07) + +tmuxp 0.1-rc7 improves config expansion and interrupt behavior. + +### Fixes + +#### Config expansion and Ctrl-C (#33) + +The config expansion path was partially rewritten, and tmuxp exits silently on `Ctrl-C`. + +## tmuxp 0.1-rc6 (2013-12-06) + +tmuxp 0.1-rc6 adds window-index configuration. + +### What's new + +#### `window_index` option (#31) + +`window_index` support and examples were added from stratoukos. + +## tmuxp 0.1-rc5 (2013-12-04) + +tmuxp 0.1-rc5 fixes early pre-command and test behavior. + +### Fixes + +#### Session-scope `shell_command_before` and freeze errors (#26, #27, #28, #29) + +The release fixes duplicated session-scope pre-commands, improves OS X tests, and makes the missing-session error from `tmuxp freeze` less unhelpful. + +## tmuxp 0.1-rc4 (2013-12-03) + +tmuxp 0.1-rc4 fixes focused pane loading inside tmux. + +### Fixes + +#### `focus: true` inside tmux + +Focused panes no longer prevent sessions from launching when `tmuxp load` is run inside tmux. + +## tmuxp 0.1-rc3 (2013-12-03) + +tmuxp 0.1-rc3 adds focused-pane support and tests. + +### Fixes + +#### Pane focus option (#25) + +`focus: true` works for panes, tests cover focused pane config, and an example was added. + +## tmuxp 0.1-rc2 (2013-11-23) + +tmuxp 0.1-rc2 fixes pane-base-index handling. + +### Fixes + +#### `pane-base-index` set to 1 (#23) + +Workspaces build correctly when `pane-base-index` is not zero. + +### Breaking changes + +#### `tmuxp load --list` removed + +The old list behavior was removed and docs were updated. + +## tmuxp 0.1-rc1 (2013-11-23) + +tmuxp 0.1-rc1 starts per-version changelogging. + +### Development + +#### PEP 8 tests and PEP 440 versions + +Unit tests adopted PEP 8 checks, and changelog entries moved to a versioned PEP 440 scheme. + +## tmuxp 0.1-dev (2013-11-21) + +The November 21 development snapshot adds global option reads for sessions. + +### Development + +#### Session option helpers accept `g` + +`Session.show_options` and `Session.show_option` can pass `-g`. + +## tmuxp 0.1-dev (2013-11-20) + +The November 20 development snapshot improves pane ordering and option tests. + +### Fixes + +#### Pane ordering and base-index tests (#15, #21) + +Workspace builder behavior changed to fix pane ordering, Python 2.6 config tests were corrected, and async builder tests improved. + +### Development + +#### Window option helpers accept `g` + +`Window.show_window_options` and `Window.show_window_option` can pass `-g`. + +## tmuxp 0.1-dev (2013-11-17) + +The November 17 development snapshot improves missing-tmux errors. + +### Fixes + +#### Missing tmux warning + +Missing tmux now produces the correct warning. + +## tmuxp 0.1-dev (2013-11-15) + +The November 15 development snapshot makes Python 2.6 a required CI target. + +### Development + +#### Python 2.6 required in Travis + +Travis no longer treats Python 2.6 as an allowed failure. + +## tmuxp 0.1-dev (2013-11-13) + +The November 13 development snapshot adds yes-to-all prompting and duplicate-session completion protection. + +### What's new + +#### `-y` prompt confirmation (#19) + +The CLI can answer yes to questions. + +### Fixes + +#### Session completion deduplication + +The session completer no longer offers a duplicate session after one is added, and work continued on {ref}`about-tmux`. + +## tmuxp 0.1-dev (2013-11-09) + +The November 9 development snapshot adds translated docs and pane splitting at a target location. + +### Documentation + +#### Chinese documentation and about page work + +Chinese documentation was linked, and the tmux background page continued to improve. + +### Development + +#### Targeted pane splitting + +The old `Pane.split_window()` path can split a window at a target pane. + +## tmuxp 0.1-dev (2013-11-08) -- can now -l to list configs in current directory and $HOME/.tmuxp -- tmuxp can now launch configs and build sessions -- new exceptions -- {meth}`config.check_consistency()` to verify and diagnose - issues with config files. -- {meth}`cli.startup()` -- {meth}`config.is_config_file()` -- {meth}`config.in_dir()` -- {meth}`config.in_cwd()` +The November 8 development snapshot improves freeze output and pane shorthand. + +### What's new + +#### Freeze start directories and inline pane output + +`tmuxp freeze` can record a window `start_directory` when panes share a directory, and config exports inline simple one-command panes for more readable output. + +## tmuxp 0.1-dev (2013-11-07) + +The November 7 development snapshot replaces the logger and improves teamocil imports. + +### Development + +#### Simpler logging and teamocil root import + +The old Tornado-derived logger was replaced, teamocil import was fixed, and teamocil `root` maps to `start_directory`. + +## tmuxp 0.1-dev (2013-11-06) + +The November 6 development snapshot improves blank panes and freeze output. + +### What's new + +#### Blank panes and cleaner freeze output + +tmuxp supports blank panes (`null`, `pane`, `blank`, and empty strings), and freeze exports blank panes where it previously emitted duplicate or generic commands. + +### Development + +#### Python 2.6 support + +Python 2.6 support was restored while the project prepared to switch to per-version changelogs. + +## tmuxp 0.1-dev (2013-11-05) + +The November 5 development snapshot adds socket options to completion and loading. + +### What's new + +#### `-L` and `-S` socket support + +Autocompletion and loading support socket name and socket path arguments. Switching clients across sockets could still error in this early implementation. + +### Documentation + +#### API and about-tmux docs + +The API and {ref}`about-tmux` docs received more updates, and PEP 257 work continued. + +## tmuxp 0.1-dev (2013-11-04) + +The November 4 development snapshot is a PEP 257 and version-tag update. + +### Development + +#### PEP 257 cleanup + +Documentation style work continued, and `v0.0.36` was tagged. + +## tmuxp 0.1-dev (2013-11-02) + +The November 2 development snapshot fixes freeze paths, paths with spaces, and internal server method names. + +### Fixes + +#### Freeze, spaces, and relative `start_directory` (#12) + +`tmuxp freeze`, attach-session, kill-session, and relative `start_directory` support improved, including paths with spaces. + +### Development + +#### Internal server method cleanup + +Old double-underscore server list methods moved to single-underscore names, and docs/PEP 257 work continued. + +## tmuxp 0.1-dev (2013-11-01) + +The November 1 development snapshot expands server construction and import quality. + +### What's new + +#### Socket, config-file, and color flags + +Server construction gained `socket_name`, `socket_path`, and `config_file` support, plus `-2`/`-8` color options. + +### Fixes + +#### Relative `start_directory` and import quality + +Relative start directories concatenate correctly, teamocil and tmuxinator imports improved, and saving to `~` destinations became possible. + +## tmuxp 0.1-dev (2013-10-31) + +The October 31 development snapshot gets `start_directory` working and fixes early window ordering. + +### Fixes + +#### Window order, kill targets, and start directories + +The first and second windows no longer load in mixed order, `Window.kill_window()` targets were corrected for compatibility, and examples for `start_directory` were added. + +### Development + +#### Window moving and docs overhaul + +`Window.move_window()` landed, `util.is_version()` appeared, and the front page/internal docs were overhauled. + +## tmuxp 0.1-dev (2013-10-30) + +The October 30 development snapshot adds experimental session freezing. + +### What's new + +#### Experimental `tmuxp freeze` + +tmuxp can experimentally freeze live sessions to a file, while `tmuxp load .` and inside-tmux switching bugs were fixed. + +### Development + +#### Start directory work + +Support for `start_directory` continued, with `Window.kill_window()` work in the same snapshot. + +## tmuxp 0.1-dev (2013-10-29) + +The October 29 development snapshot adds shorthand panes, import wizards, and better crash handling. + +### What's new + +#### Pane shorthand, `.yml`, and import wizard + +Panes can be written as strings, `.yml` files load, and teamocil/tmuxinator imports gained a wizard that can save JSON or YAML. + +#### Automatic rename and pane selection + +The configuration gained `automatic-rename`, examples for pane sizing and shorthands, and pane selection accepted directional flags. + +### Fixes + +#### Loaded-session prompt and builder crash recovery + +tmuxp no longer switches or attaches after a negative prompt response, and workspace loader crashes offer kill, attach, or detach recovery options. + +## tmuxp 0.1-dev (2013-10-28) + +The October 28 development snapshot fixes current-directory load and conversion. + +### Fixes + +#### `tmuxp load .` and `tmuxp convert` + +Both commands were fixed, and test runner name handling improved. + +### Development + +#### Pane sizing and target defaults + +Historical pane sizing helpers and automatic window/pane target behavior landed during this API-building period. + +## tmuxp 0.1-dev (2013-10-27) + +The October 27 development snapshot overhauls filename completion. + +### What's new + +#### Relative and full filenames in CLI commands + +`tmuxp load`, `tmuxp convert`, and `tmuxp import` accept relative and full filenames in addition to config-directory discovery. + +### Development + +#### argcomplete CLI completion + +The CLI completion implementation was overhauled with argcomplete. + +## tmuxp 0.1-dev (2013-10-26) + +The October 26 development snapshot starts tmuxinator import support. + +### What's new + +#### Initial tmuxinator importer + +The first tmuxinator importer can convert some options, but was not expected to fully preserve window/pane size and state. + +### Fixes + +#### Config file extension handling + +Config discovery accepts extension lists and string extension arguments, and `tmuxp load -l` works alongside `tmuxp load filename`. + +## tmuxp 0.1-dev (2013-10-25) + +The October 25 development snapshot fixes version output and serverless session commands. + +### Fixes + +#### Version flag and no-server attach/kill + +`-v` and `--version` print correctly, and attach-session/kill-session handle the case where no tmux server exists. + +### Development + +#### Import fixtures + +Test fixtures and initial teamocil import work were added. + +## tmuxp 0.1-dev (2013-10-24) + +The October 24 development snapshot adds config conversion and JSON examples. + +### What's new + +#### JSON/YAML conversion + +`tmuxp convert` can convert between JSON and YAML in both directions, and JSON examples were added to the docs. + +### Fixes + +#### zsh auto-title prompts and session output + +tmuxp checks oh-my-zsh auto-title settings, fixes bad kill-session output, and improves tab completion with no active tmux server. + +## tmuxp 0.1-dev (2013-10-23) + +The October 23 development snapshot expands tab completion and core CLI commands. + +### What's new + +#### Completion for sessions and commands + +zsh, bash, and tcsh completions improved for `kill-session`, `attach-session`, and command options. `attach-session` switches clients when already inside tmux, and `load` can load configs. + +## tmuxp 0.1-dev (2013-10-21) + +The October 21 development snapshot makes tmux 1.8 the official minimum and restores version output. + +### Breaking changes + +#### tmux 1.8 minimum + +tmuxp warns when tmux is out of date and removed old compatibility code that caused build regressions. + +### Documentation + +#### Examples and graphics + +Two examples and graphics were added to {ref}`examples`, and `$ tmuxp -v` prints version info. + +## tmuxp 0.1-dev (2013-10-19) + +The October 19 development snapshot improves missing-binary handling and continues the early object-model overhaul. + +### Fixes + +#### Missing tmux exits clearly + +tmuxp exits with a clear message when `tmux` is not on PATH. + +### Development + +#### Server, session, window, and pane object refresh + +The early libtmux-style object model continued to separate public object lists from lower-level dict data returned by tmux commands. + +## tmuxp 0.1-dev (2013-10-18) + +The October 18 development snapshot moves more state refresh behavior into the server object. + +### Development + +#### Object data refresh model + +Session, window, and pane objects now refer back to server-owned data and refresh through unique identifiers, reducing manual data-setting complexity. + +#### Python 3 and legacy tmux research + +Python 3 support and possible tmux 1.6/1.7 support were under active research. + +## tmuxp 0.1-dev (2013-10-17) + +The October 17 development snapshot refreshes project docs and session switching. + +### What's new + +#### Switching sessions from inside tmux + +tmuxp can switch sessions from inside tmux after a session is built and when a session already exists. + +### Documentation + +#### README and development example update + +The README, screenshots, and development `.tmuxp.yaml` example were updated. + +## tmuxp 0.1-dev (2013-10-16) + +The October 16 development snapshot adds manifest fixes, completions, and an early object mapping layer. + +### Fixes + +#### Missing manifest file + +`MANIFEST.in` was added so packages install with required files. + +### What's new + +#### Shell completion and tmux binary lookup + +bash/zsh completion landed, and tmuxp uses a `which` helper to find the tmux binary. + +### Development + +#### Early relational object API + +Server/session/window/pane object mapping gained Backbone-inspired lookup helpers and split mapping behavior from relational behavior. + +## tmuxp 0.1-dev (2013-10-15) + +The October 15 development snapshot adds the first practical config loading flow. + +### What's new + +#### Load current-directory configs and one-command panes + +`tmuxp .` can load `.tmuxp.{yaml,json,py}` in the current directory, configs can express one-command panes compactly, and sessions prompt to attach when already present. + +#### Socket options and theme refresh + +Socket name/path support, initial examples, and a new theme landed in the same snapshot. + +## tmuxp 0.1-dev (2013-10-14) + +The October 14 development snapshot can list and build configs. + +### What's new + +#### Config listing and session building + +tmuxp can list configs in the current directory and `$HOME/.tmuxp`, launch configs, and build sessions. + +### Development + +#### Config validation helpers + +Early helpers for config consistency, config-file detection, and config-directory discovery were added. ## tmuxp 0.1-dev (2013-10-13) -- {meth}`config.inline()` to produce far far better looking - config exports and tests. -- {meth}`Pane.resize_pane()` and tests -- documentation fixes and updates -- {meth}`Session.refresh()`, {meth}`Window.refresh()`, - {meth}`Pane.refresh()`. -- {meth}`Server.find()`, {meth}`Session.find()`, - {meth}`Window.find()`. +The October 13 development snapshot improves exported config readability and object refresh helpers. + +### Development + +#### Inline config output and refresh helpers + +`config.inline()` produces cleaner exported configs, pane resizing tests were added, and session/window/pane refresh and find helpers landed. ## tmuxp 0.1-dev (2013-10-12) -- Test documentation updates -- Builder is now {class}`WorkspaceBuilder` + tests. - - WorkspaceBuilder can build panes - - WorkspaceBuilder can build windows and set options -- {meth}`Window.show_window_options()`, - {meth}`Window.show_window_option()`, {meth}`Window.set_window_option()` -- {meth}`Session.show_options()`, - {meth}`Session.show_option()`, {meth}`Session.set_option()` +The October 12 development snapshot introduces the workspace builder. + +### Development + +#### WorkspaceBuilder starts building sessions + +The builder can create panes, build windows, and set tmux options, with early option helpers on session and window objects. ## tmuxp 0.1-dev (2013-10-11) -- More preparation for builder / session maker utility. -- test runner and test suite overhaul. -- Documentation for development environment and test runner updated. -- Travis now tests against tmux 1.8 and latest source. Door open for - future testing against python 3 and earlier tmux versions in the future. -- Quiet logger down in some areas -- **future** imports for future python 3 compatibility -- setup.py import **version** via regex from tmuxp package -- move beginnings of cli to `tmuxp.cli` +The October 11 development snapshot prepares the builder and test runner. + +### Development + +#### Test runner, Travis matrix, and CLI beginnings + +The test suite and runner were overhauled, Travis tested tmux 1.8 and tmux source, logging was quieted, Python 3 compatibility imports were added, setup reads package version via regex, and CLI code began moving into `tmuxp.cli`. ## tmuxp 0.1-dev (2013-10-09) -- New logging module -- Removed dependency logutils -- Removed dependency sh +The October 9 development snapshot simplifies logging dependencies. + +### Development + +#### Logging dependency cleanup + +A new logging module replaced the `logutils` and `sh` dependencies. ## tmuxp 0.1-dev (2013-10-08) -- switch to semver - -[tmuxinator]: https://github.com/aziz/tmuxinator -[teamocil]: https://github.com/remiprev/teamocil -[argcomplete]: https://github.com/kislyuk/argcomplete -[pep257]: http://www.python.org/dev/peps/pep-0257/ -[pep8]: http://www.python.org/dev/peps/pep-0008/ -[pep440]: http://www.python.org/dev/peps/pep-0440/ -[tony/cookiecutter-pypackage]: https://github.com/tony/cookiecutter-pypackage -[@tasdomas]: https://github.com/tasdomas -[@sehe]: https://github.com/sehe -[@madprog]: https://github.com/madprog -[autoenv]: https://github.com/kennethreitz/autoenv -[entr(1)]: http://entrproject.org/ -[anaconda python]: http://docs.continuum.io/anaconda/index -[@estin]: https://github.com/estin -[sysutils/pytmuxp]: http://www.freshports.org/sysutils/py-tmuxp/ -[#201564]: https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=201564 -[colorama]: https://pypi.python.org/pypi/colorama -[@marbu]: https://github.com/marbu -[@yegortimoshenko]: https://github.com/yegortimoshenko -[@thomasballinger]: https://github.com/thomasballinger -[sniffer]: https://github.com/jeffh/sniffer -[watchingtestrunner]: https://pypi.python.org/pypi/watching_testrunner/1.0 -[@raine]: https://github.com/raine -[@techtonik]: https://github.com/techtonik -[@thiefmaster]: https://github.com/ThiefMaster -[pep 263]: http://www.python.org/dev/peps/pep-0263/ -[roxit]: https://github.com/roxit -[documentation in chinese]: http://tmuxp-zh.readthedocs.io -[wrongwaycn]: https://github.com/wrongwaycn -[tornado's log.py]: https://github.com/facebook/tornado/blob/master/tornado/log.py -[underscore.js]: http://underscorejs.org/ -[backbone.js]: http://backbonejs.org/ -[.get()]: http://backbonejs.org/#Collection-get -[.where()]: http://underscorejs.org/#where -[.findwhere()]: http://underscorejs.org/#findWhere -[libtmux]: https://github.com/tmux-python/libtmux - - +The October 8 development snapshot moves the project to semantic versioning. + +### Development + +#### Semantic versioning + +tmuxp switched to semver for early release numbering. diff --git a/README.md b/README.md index 1a1de04be9..359ae17202 100644 --- a/README.md +++ b/README.md @@ -132,9 +132,9 @@ Name a session: tmuxp load -s session_name ./mysession.yaml ``` -[simple](http://tmuxp.git-pull.com/examples.html#short-hand-inline) and +[simple](https://tmuxp.git-pull.com/configuration/examples/#short-hand-inline-style) and [very -elaborate](http://tmuxp.git-pull.com/examples.html#super-advanced-dev-environment) +elaborate](https://tmuxp.git-pull.com/configuration/examples/#super-advanced-dev-environment) config examples # User-level configurations @@ -197,16 +197,14 @@ $ tmuxp shell -c 'print(window.name.upper())' MY_WINDOW ``` -Read more on [tmuxp shell](https://tmuxp.git-pull.com/cli/shell.html) in +Read more on [tmuxp shell](https://tmuxp.git-pull.com/cli/shell/) in the CLI docs. # Pre-load hook -Run custom startup scripts (such as installing project dependencies +Run custom startup scripts (such as installing project dependencies) before loading tmux. See the -[bootstrap_env.py](https://github.com/tmux-python/tmuxp/blob/master/bootstrap_env.py) -and -[before_script](http://tmuxp.git-pull.com/examples.html#bootstrap-project-before-launch) +[before_script](https://tmuxp.git-pull.com/configuration/examples/#bootstrap-project-before-launch) example # Load in detached state @@ -226,7 +224,7 @@ $ tmuxp freeze session-name ``` See more about [freezing -tmux](https://tmuxp.git-pull.com/cli/freeze.html) sessions. +tmux](https://tmuxp.git-pull.com/cli/freeze/) sessions. # Convert a session file @@ -249,7 +247,7 @@ $ tmuxp convert --yes filename # Plugin System tmuxp has a plugin system to allow for custom behavior. See more about -the [Plugin System](http://tmuxp.git-pull.com/plugin_system.html). +the [Plugin System](https://tmuxp.git-pull.com/topics/plugins/). # Debugging Helpers @@ -274,13 +272,13 @@ environment: # Docs / Reading material -See the [Quickstart](http://tmuxp.git-pull.com/quickstart.html). +See the [Quickstart](https://tmuxp.git-pull.com/quickstart/). -[Documentation](http://tmuxp.git-pull.com) homepage (also in +[Documentation](https://tmuxp.git-pull.com) homepage (also in [中文](http://tmuxp-zh.rtfd.org/)) Want to learn more about tmux itself? [Read The Tao of Tmux -online](http://tmuxp.git-pull.com/about_tmux.html). +online](https://tmuxp.git-pull.com/about_tmux/). # Donations @@ -297,8 +295,8 @@ See donation options at . - python support: >= 3.10, pypy, pypy3 - Source: - Docs: -- API: -- Changelog: +- API: +- Changelog: - Issues: - Test Coverage: - pypi: diff --git a/conftest.py b/conftest.py index 5ae04a57a3..1f0583439e 100644 --- a/conftest.py +++ b/conftest.py @@ -30,6 +30,20 @@ USING_ZSH = "zsh" in os.getenv("SHELL", "") +@pytest.fixture(autouse=True) +def _pin_test_shell_env(monkeypatch: pytest.MonkeyPatch) -> None: + """Pin ``$SHELL`` to ``/bin/sh`` for every test. + + tmux falls back to ``$SHELL`` for new panes when ``default-shell`` is + unset, so pinning the env var here forces every test pane to spawn + ``/bin/sh`` instead of the contributor's interactive shell. Eliminates + rcfile init cost (zsh / oh-my-zsh / bash profile chains) and yields + deterministic prompt output that doesn't flake capture-pane assertions. + ``monkeypatch`` restores the prior value at teardown. + """ + monkeypatch.setenv("SHELL", "/bin/sh") + + @pytest.fixture(autouse=USING_ZSH, scope="session") def zshrc(user_path: pathlib.Path) -> pathlib.Path | None: """Quiets ZSH default message. diff --git a/docs/_ext/aafig.py b/docs/_ext/aafig.py index 418dbbc028..6f90d51378 100644 --- a/docs/_ext/aafig.py +++ b/docs/_ext/aafig.py @@ -176,7 +176,8 @@ def render_aafigure( fname = "{}.{}".format(get_basename(text, options), options["format"]) if app.builder.format == "html": # HTML - imgpath = relative_uri(app.builder.env.docname, "_images") + target_uri = app.builder.get_target_uri(app.builder.env.docname) + imgpath = relative_uri(target_uri, "_images") relfn = posixpath.join(imgpath, fname) outfn = path.join(app.builder.outdir, "_images", fname) else: diff --git a/docs/_ext/argparse_exemplar.py b/docs/_ext/argparse_exemplar.py deleted file mode 100644 index a4a7e1fc8b..0000000000 --- a/docs/_ext/argparse_exemplar.py +++ /dev/null @@ -1,1305 +0,0 @@ -"""Transform argparse epilog "examples:" definition lists into documentation sections. - -This Sphinx extension post-processes sphinx_argparse_neo output to convert -specially-formatted "examples:" definition lists in argparse epilogs into -proper documentation sections with syntax-highlighted code blocks. - -The extension is designed to be generic and reusable across different projects. -All behavior can be customized via Sphinx configuration options. - -Purpose -------- -When documenting CLI tools with argparse, it's useful to include examples in -the epilog. This extension recognizes a specific definition list format and -transforms it into structured documentation sections that appear in the TOC. - -Input Format ------------- -Format your argparse epilog with definition lists where terms end with "examples:": - -.. code-block:: python - - parser = argparse.ArgumentParser( - epilog=textwrap.dedent(''' - examples: - myapp sync - myapp sync myrepo - - Machine-readable output examples: - myapp sync --json - myapp sync -F json myrepo - '''), - formatter_class=argparse.RawDescriptionHelpFormatter, - ) - -The epilog text will be parsed as a definition list by docutils, with: -- Terms: "examples:", "Machine-readable output examples:", etc. -- Definitions: The example commands (one per line) - -Output ------- -The extension transforms these into proper sections: - -- A base "examples:" term creates an "Examples" section -- Category-prefixed terms like "Machine-readable output examples:" create - subsections nested under the parent Examples section -- Each command line becomes a syntax-highlighted console code block - -Configuration -------------- -Configure via conf.py. All options have sensible defaults. - -**Term Detection:** - -``argparse_examples_term_suffix`` : str (default: "examples") - Term must end with this string to be treated as an examples header. - -``argparse_examples_base_term`` : str (default: "examples") - Exact match for the base examples section (case-insensitive). - -``argparse_examples_section_title`` : str (default: "Examples") - Title used for the base examples section. - -**Usage Detection:** - -``argparse_usage_pattern`` : str (default: "usage:") - Text must start with this to be treated as a usage block (case-insensitive). - -**Code Block Formatting:** - -``argparse_examples_command_prefix`` : str (default: "$ ") - Prefix added to each command line in examples code blocks. - -``argparse_examples_code_language`` : str (default: "console") - Language identifier for examples code blocks. - -``argparse_examples_code_classes`` : list[str] (default: ["highlight-console"]) - CSS classes added to examples code blocks. - -``argparse_usage_code_language`` : str (default: "cli-usage") - Language identifier for usage blocks. - -**Behavior:** - -``argparse_reorder_usage_before_examples`` : bool (default: True) - Whether to reorder nodes so usage appears before examples. - -Additional Features -------------------- -- Removes ANSI escape codes (useful when FORCE_COLOR is set) -- Applies syntax highlighting to usage blocks -- Reorders sections so usage appears before examples in the output -- Extracts sections from argparse_program containers for TOC visibility - -Project-Specific Setup ----------------------- -Projects using this extension should register their own lexers and CSS in -their conf.py setup() function. For example:: - - def setup(app): - from my_lexer import MyLexer - app.add_lexer("my-output", MyLexer) - app.add_css_file("css/my-highlight.css") -""" - -from __future__ import annotations - -import dataclasses -import typing as t - -from docutils import nodes -from sphinx_argparse_neo.directive import ArgparseDirective -from sphinx_argparse_neo.utils import strip_ansi - -if t.TYPE_CHECKING: - import sphinx.config - from sphinx.application import Sphinx - - -@dataclasses.dataclass -class ExemplarConfig: - """Configuration for argparse_exemplar transformation. - - This dataclass provides all configurable options for the argparse_exemplar - extension. Functions accept an optional config parameter with a factory - default, allowing them to work standalone with defaults or accept custom - config for full control. - - Attributes - ---------- - examples_term_suffix : str - Term must end with this string (case-insensitive) to be treated as an - examples header. Default: "examples". - examples_base_term : str - Exact match (case-insensitive, after stripping ":") for the base - examples section. Default: "examples". - examples_section_title : str - Title used for the base examples section. Default: "Examples". - usage_pattern : str - Text must start with this string (case-insensitive, after stripping - whitespace) to be treated as a usage block. Default: "usage:". - command_prefix : str - Prefix added to each command line in examples code blocks. - Default: "$ ". - code_language : str - Language identifier for examples code blocks. Default: "console". - code_classes : tuple[str, ...] - CSS classes added to examples code blocks. - Default: ("highlight-console",). - usage_code_language : str - Language identifier for usage blocks. Default: "cli-usage". - reorder_usage_before_examples : bool - Whether to reorder nodes so usage appears before examples. - Default: True. - - Examples - -------- - Using default configuration: - - >>> config = ExemplarConfig() - >>> config.examples_term_suffix - 'examples' - >>> config.command_prefix - '$ ' - - Custom configuration: - - >>> config = ExemplarConfig( - ... command_prefix="> ", - ... code_language="bash", - ... ) - >>> config.command_prefix - '> ' - >>> config.code_language - 'bash' - """ - - # Term detection - examples_term_suffix: str = "examples" - examples_base_term: str = "examples" - examples_section_title: str = "Examples" - - # Usage detection - usage_pattern: str = "usage:" - - # Code block formatting - command_prefix: str = "$ " - code_language: str = "console" - code_classes: tuple[str, ...] = ("highlight-console",) - usage_code_language: str = "cli-usage" - - # Behavior - reorder_usage_before_examples: bool = True - - @classmethod - def from_sphinx_config(cls, config: sphinx.config.Config) -> ExemplarConfig: - """Create ExemplarConfig from Sphinx configuration. - - Parameters - ---------- - config : sphinx.config.Config - The Sphinx configuration object. - - Returns - ------- - ExemplarConfig - Configuration populated from Sphinx config values. - - Examples - -------- - This is typically called from a directive's run() method: - - >>> # In CleanArgParseDirective.run(): - >>> # config = ExemplarConfig.from_sphinx_config(self.env.config) - """ - # Get code_classes as tuple (Sphinx stores lists) - code_classes_raw = getattr( - config, "argparse_examples_code_classes", ("highlight-console",) - ) - code_classes = ( - tuple(code_classes_raw) - if isinstance(code_classes_raw, list) - else code_classes_raw - ) - - return cls( - examples_term_suffix=getattr( - config, "argparse_examples_term_suffix", "examples" - ), - examples_base_term=getattr( - config, "argparse_examples_base_term", "examples" - ), - examples_section_title=getattr( - config, "argparse_examples_section_title", "Examples" - ), - usage_pattern=getattr(config, "argparse_usage_pattern", "usage:"), - command_prefix=getattr(config, "argparse_examples_command_prefix", "$ "), - code_language=getattr(config, "argparse_examples_code_language", "console"), - code_classes=code_classes, - usage_code_language=getattr( - config, "argparse_usage_code_language", "cli-usage" - ), - reorder_usage_before_examples=getattr( - config, "argparse_reorder_usage_before_examples", True - ), - ) - - -# Re-export for backwards compatibility and public API -__all__ = [ - "CleanArgParseDirective", - "ExemplarConfig", - "is_base_examples_term", - "is_examples_term", - "make_section_id", - "make_section_title", - "process_node", - "strip_ansi", - "transform_definition_list", -] - - -def is_examples_term(term_text: str, *, config: ExemplarConfig | None = None) -> bool: - """Check if a definition term is an examples header. - - Parameters - ---------- - term_text : str - The text content of a definition term. - config : ExemplarConfig | None - Optional configuration. If None, uses default ExemplarConfig(). - - Returns - ------- - bool - True if this is an examples header. - - Examples - -------- - >>> is_examples_term("examples:") - True - >>> is_examples_term("Machine-readable output examples:") - True - >>> is_examples_term("Usage:") - False - - With custom configuration: - - >>> custom_config = ExemplarConfig(examples_term_suffix="demos") - >>> is_examples_term("demos:", config=custom_config) - True - >>> is_examples_term("examples:", config=custom_config) - False - """ - config = config or ExemplarConfig() - return term_text.lower().rstrip(":").endswith(config.examples_term_suffix) - - -def is_base_examples_term( - term_text: str, *, config: ExemplarConfig | None = None -) -> bool: - """Check if a definition term is a base "examples:" header (no prefix). - - Parameters - ---------- - term_text : str - The text content of a definition term. - config : ExemplarConfig | None - Optional configuration. If None, uses default ExemplarConfig(). - - Returns - ------- - bool - True if this is just "examples:" with no category prefix. - - Examples - -------- - >>> is_base_examples_term("examples:") - True - >>> is_base_examples_term("Examples") - True - >>> is_base_examples_term("Field-scoped examples:") - False - - With custom configuration: - - >>> custom_config = ExemplarConfig(examples_base_term="demos") - >>> is_base_examples_term("demos:", config=custom_config) - True - >>> is_base_examples_term("examples:", config=custom_config) - False - """ - config = config or ExemplarConfig() - return term_text.lower().rstrip(":").strip() == config.examples_base_term - - -def make_section_id( - term_text: str, - counter: int = 0, - *, - is_subsection: bool = False, - page_prefix: str = "", - config: ExemplarConfig | None = None, -) -> str: - """Generate a section ID from an examples term. - - Parameters - ---------- - term_text : str - The examples term text (e.g., "Machine-readable output: examples:") - counter : int - Counter for uniqueness if multiple examples sections exist. - is_subsection : bool - If True, omit "-examples" suffix for cleaner nested IDs. - page_prefix : str - Optional prefix from the page name (e.g., "sync", "add") to ensure - uniqueness across different documentation pages. - config : ExemplarConfig | None - Optional configuration. If None, uses default ExemplarConfig(). - - Returns - ------- - str - A normalized section ID. - - Examples - -------- - >>> make_section_id("examples:") - 'examples' - >>> make_section_id("examples:", page_prefix="sync") - 'sync-examples' - >>> make_section_id("Machine-readable output examples:") - 'machine-readable-output-examples' - >>> make_section_id("Field-scoped examples:", is_subsection=True) - 'field-scoped' - >>> make_section_id("examples:", counter=1) - 'examples-1' - - With custom configuration: - - >>> custom_config = ExemplarConfig(examples_term_suffix="demos") - >>> make_section_id("demos:", config=custom_config) - 'demos' - >>> make_section_id("Machine-readable output demos:", config=custom_config) - 'machine-readable-output-demos' - """ - config = config or ExemplarConfig() - term_suffix = config.examples_term_suffix - - # Extract prefix before the term suffix (e.g., "Machine-readable output") - lower_text = term_text.lower().rstrip(":") - if term_suffix in lower_text: - prefix = lower_text.rsplit(term_suffix, 1)[0].strip() - # Remove trailing colon from prefix (handles ": examples" pattern) - prefix = prefix.rstrip(":").strip() - if prefix: - normalized_prefix = prefix.replace(" ", "-") - # Subsections don't need "-examples" suffix - if is_subsection: - section_id = normalized_prefix - else: - section_id = f"{normalized_prefix}-{term_suffix}" - else: - # Plain "examples" - add page prefix if provided for uniqueness - section_id = f"{page_prefix}-{term_suffix}" if page_prefix else term_suffix - else: - section_id = term_suffix - - # Add counter suffix for uniqueness - if counter > 0: - section_id = f"{section_id}-{counter}" - - return section_id - - -def make_section_title( - term_text: str, - *, - is_subsection: bool = False, - config: ExemplarConfig | None = None, -) -> str: - """Generate a section title from an examples term. - - Parameters - ---------- - term_text : str - The examples term text (e.g., "Machine-readable output: examples:") - is_subsection : bool - If True, omit "Examples" suffix for cleaner nested titles. - config : ExemplarConfig | None - Optional configuration. If None, uses default ExemplarConfig(). - - Returns - ------- - str - A proper title (e.g., "Machine-readable Output Examples" or just - "Machine-Readable Output" if is_subsection=True). - - Examples - -------- - >>> make_section_title("examples:") - 'Examples' - >>> make_section_title("Machine-readable output examples:") - 'Machine-Readable Output Examples' - >>> make_section_title("Field-scoped examples:", is_subsection=True) - 'Field-Scoped' - - With custom configuration: - - >>> custom_config = ExemplarConfig( - ... examples_base_term="demos", - ... examples_term_suffix="demos", - ... examples_section_title="Demos", - ... ) - >>> make_section_title("demos:", config=custom_config) - 'Demos' - >>> make_section_title("Machine-readable output demos:", config=custom_config) - 'Machine-Readable Output Demos' - """ - config = config or ExemplarConfig() - base_term = config.examples_base_term - term_suffix = config.examples_term_suffix - section_title = config.examples_section_title - - # Remove trailing colon and normalize - text = term_text.rstrip(":").strip() - # Handle base term case (e.g., "examples:") - if text.lower() == base_term: - return section_title - - # Extract the prefix (category name) before the term suffix - lower = text.lower() - colon_suffix = f": {term_suffix}" - space_suffix = f" {term_suffix}" - if lower.endswith(colon_suffix): - prefix = text[: -len(colon_suffix)] - elif lower.endswith(space_suffix): - prefix = text[: -len(space_suffix)] - else: - prefix = text - - # Title case the prefix - titled_prefix = prefix.title() - - # For subsections, just use the prefix (cleaner nested titles) - if is_subsection: - return titled_prefix - - # For top-level sections, append the section title - return f"{titled_prefix} {section_title}" - - -def _create_example_section( - term_text: str, - def_node: nodes.definition, - *, - is_subsection: bool = False, - page_prefix: str = "", - config: ExemplarConfig | None = None, -) -> nodes.section: - """Create a section node for an examples item. - - Parameters - ---------- - term_text : str - The examples term text. - def_node : nodes.definition - The definition node containing example commands. - is_subsection : bool - If True, create a subsection with simpler title/id. - page_prefix : str - Optional prefix from the page name for unique section IDs. - config : ExemplarConfig | None - Optional configuration. If None, uses default ExemplarConfig(). - - Returns - ------- - nodes.section - A section node with title and code blocks. - - Examples - -------- - Create a section from a definition node containing example commands: - - >>> from docutils import nodes - >>> def_node = nodes.definition() - >>> def_node += nodes.paragraph(text="myapp sync") - >>> section = _create_example_section("examples:", def_node) - >>> section["ids"] - ['examples'] - >>> section[0].astext() - 'Examples' - - With a page prefix for uniqueness across documentation pages: - - >>> section = _create_example_section("examples:", def_node, page_prefix="sync") - >>> section["ids"] - ['sync-examples'] - - Category-prefixed examples create descriptive section IDs: - - >>> section = _create_example_section("Machine-readable output examples:", def_node) - >>> section["ids"] - ['machine-readable-output-examples'] - >>> section[0].astext() - 'Machine-Readable Output Examples' - """ - config = config or ExemplarConfig() - section_id = make_section_id( - term_text, is_subsection=is_subsection, page_prefix=page_prefix, config=config - ) - section_title = make_section_title( - term_text, is_subsection=is_subsection, config=config - ) - - section = nodes.section() - section["ids"] = [section_id] - section["names"] = [nodes.fully_normalize_name(section_title)] - - title = nodes.title(text=section_title) - section += title - - # Extract commands from definition and create separate code blocks - def_text = strip_ansi(def_node.astext()) - for line in def_text.split("\n"): - line = line.strip() - if line: - code_block = nodes.literal_block( - text=f"{config.command_prefix}{line}", - classes=list(config.code_classes), - ) - code_block["language"] = config.code_language - section += code_block - - return section - - -def transform_definition_list( - dl_node: nodes.definition_list, - *, - page_prefix: str = "", - config: ExemplarConfig | None = None, -) -> list[nodes.Node]: - """Transform a definition list, converting examples items to code blocks. - - If there's a base "examples:" item followed by category-specific examples - (e.g., "Field-scoped: examples:"), the categories are nested under the - parent Examples section for cleaner ToC structure. - - Parameters - ---------- - dl_node : nodes.definition_list - A definition list node. - page_prefix : str - Optional prefix from the page name for unique section IDs. - config : ExemplarConfig | None - Optional configuration. If None, uses default ExemplarConfig(). - - Returns - ------- - list[nodes.Node] - Transformed nodes - code blocks for examples, original for others. - - Note - ---- - **Intentional reordering behavior:** This function always emits non-example - items (preamble text, descriptions, etc.) before example sections, regardless - of their original position in the definition list. This "flush first" approach - groups conceptually related content: introductory material appears before - examples, even if the source document interleaves them. This produces cleaner - documentation structure where descriptions introduce their examples. - - If you need to preserve the original interleaved order, you would need to - modify this function to track item positions during the first pass. - """ - config = config or ExemplarConfig() - - # First pass: collect examples and non-examples items separately - example_items: list[tuple[str, nodes.definition]] = [] # (term_text, def_node) - non_example_items: list[nodes.Node] = [] - base_examples_index: int | None = None - - for item in dl_node.children: - if not isinstance(item, nodes.definition_list_item): - continue - - # Get the term and definition - term_node = None - def_node = None - for child in item.children: - if isinstance(child, nodes.term): - term_node = child - elif isinstance(child, nodes.definition): - def_node = child - - if term_node is None or def_node is None: - non_example_items.append(item) - continue - - term_text = strip_ansi(term_node.astext()) - - if is_examples_term(term_text, config=config): - if is_base_examples_term(term_text, config=config): - base_examples_index = len(example_items) - example_items.append((term_text, def_node)) - else: - non_example_items.append(item) - - # Build result nodes - result_nodes: list[nodes.Node] = [] - - # Emit non-example items first (see docstring Note on reordering behavior) - if non_example_items: - new_dl = nodes.definition_list() - new_dl.extend(non_example_items) - result_nodes.append(new_dl) - - # Determine nesting strategy - # Nest if: there's a base "examples:" AND at least one other example category - should_nest = base_examples_index is not None and len(example_items) > 1 - - if should_nest and base_examples_index is not None: - # Create parent "Examples" section - base_term, base_def = example_items[base_examples_index] - parent_section = _create_example_section( - base_term, - base_def, - is_subsection=False, - page_prefix=page_prefix, - config=config, - ) - - # Add other examples as nested subsections - for i, (term_text, def_node) in enumerate(example_items): - if i == base_examples_index: - continue # Skip the base (already used as parent) - subsection = _create_example_section( - term_text, - def_node, - is_subsection=True, - page_prefix=page_prefix, - config=config, - ) - parent_section += subsection - - result_nodes.append(parent_section) - else: - # No nesting - create flat sections (backwards compatible) - for term_text, def_node in example_items: - section = _create_example_section( - term_text, - def_node, - is_subsection=False, - page_prefix=page_prefix, - config=config, - ) - result_nodes.append(section) - - return result_nodes - - -def process_node( - node: nodes.Node, - *, - page_prefix: str = "", - config: ExemplarConfig | None = None, -) -> nodes.Node | list[nodes.Node]: - """Process a node: strip ANSI codes and transform examples. - - Parameters - ---------- - node : nodes.Node - A docutils node to process. - page_prefix : str - Optional prefix from the page name for unique section IDs. - config : ExemplarConfig | None - Optional configuration. If None, uses default ExemplarConfig(). - - Returns - ------- - nodes.Node | list[nodes.Node] - The processed node(s). - """ - config = config or ExemplarConfig() - - # Handle text nodes - strip ANSI - if isinstance(node, nodes.Text): - cleaned = strip_ansi(node.astext()) - if cleaned != node.astext(): - return nodes.Text(cleaned) - return node - - # Handle definition lists - transform examples - if isinstance(node, nodes.definition_list): - # Check if any items are examples - has_examples = False - for item in node.children: - if isinstance(item, nodes.definition_list_item): - for child in item.children: - if isinstance(child, nodes.term) and is_examples_term( - strip_ansi(child.astext()), config=config - ): - has_examples = True - break - if has_examples: - break - - if has_examples: - return transform_definition_list( - node, page_prefix=page_prefix, config=config - ) - - # Handle literal_block nodes - strip ANSI and apply usage highlighting - if isinstance(node, nodes.literal_block): - text = strip_ansi(node.astext()) - needs_update = text != node.astext() - - # Check if this is a usage block (starts with configured pattern) - is_usage = text.lstrip().lower().startswith(config.usage_pattern.lower()) - - if needs_update or is_usage: - new_block = nodes.literal_block(text=text) - # Preserve attributes - for attr in ("language", "classes"): - if attr in node: - new_block[attr] = node[attr] - # Apply configured language to usage blocks - if is_usage: - new_block["language"] = config.usage_code_language - return new_block - return node - - # Handle paragraph nodes - strip ANSI and lift sections out - if isinstance(node, nodes.paragraph): - # Process children and check if any become sections - processed_children: list[nodes.Node] = [] - changed = False - has_sections = False - - for child in node.children: - if isinstance(child, nodes.Text): - cleaned = strip_ansi(child.astext()) - if cleaned != child.astext(): - processed_children.append(nodes.Text(cleaned)) - changed = True - else: - processed_children.append(child) - else: - result = process_node(child, page_prefix=page_prefix, config=config) - if isinstance(result, list): - processed_children.extend(result) - changed = True - # Check if any results are sections - if any(isinstance(r, nodes.section) for r in result): - has_sections = True - elif result is not child: - processed_children.append(result) - changed = True - if isinstance(result, nodes.section): - has_sections = True - else: - processed_children.append(child) - - if not changed: - return node - - # If no sections, return a normal paragraph - if not has_sections: - new_para = nodes.paragraph() - new_para.extend(processed_children) - return new_para - - # Sections found - lift them out of the paragraph - # Return a list: [para_before, section1, section2, ..., para_after] - result_nodes: list[nodes.Node] = [] - current_para_children: list[nodes.Node] = [] - - for child in processed_children: - if isinstance(child, nodes.section): - # Flush current paragraph content - if current_para_children: - para = nodes.paragraph() - para.extend(current_para_children) - result_nodes.append(para) - current_para_children = [] - # Add section as a sibling - result_nodes.append(child) - else: - current_para_children.append(child) - - # Flush remaining paragraph content - if current_para_children: - para = nodes.paragraph() - para.extend(current_para_children) - result_nodes.append(para) - - return result_nodes - - # Recursively process children for other node types - if hasattr(node, "children"): - new_children: list[nodes.Node] = [] - children_changed = False - for child in node.children: - result = process_node(child, page_prefix=page_prefix, config=config) - if isinstance(result, list): - new_children.extend(result) - children_changed = True - elif result is not child: - new_children.append(result) - children_changed = True - else: - new_children.append(child) - if children_changed: - node[:] = new_children # type: ignore[index] - - return node - - -def _is_usage_block(node: nodes.Node, *, config: ExemplarConfig | None = None) -> bool: - """Check if a node is a usage literal block. - - Parameters - ---------- - node : nodes.Node - A docutils node to check. - config : ExemplarConfig | None - Optional configuration. If None, uses default ExemplarConfig(). - - Returns - ------- - bool - True if this is a usage block (literal_block starting with usage pattern). - - Examples - -------- - >>> from docutils import nodes - >>> _is_usage_block(nodes.literal_block(text="usage: cmd [-h]")) - True - >>> _is_usage_block(nodes.literal_block(text="Usage: myapp sync")) - True - >>> _is_usage_block(nodes.literal_block(text=" usage: cmd")) - True - >>> _is_usage_block(nodes.literal_block(text="some other text")) - False - >>> _is_usage_block(nodes.paragraph(text="usage: cmd")) - False - >>> _is_usage_block(nodes.section()) - False - - With custom configuration: - - >>> custom_config = ExemplarConfig(usage_pattern="synopsis:") - >>> _is_usage_block(nodes.literal_block(text="synopsis: cmd"), config=custom_config) - True - >>> _is_usage_block(nodes.literal_block(text="usage: cmd"), config=custom_config) - False - """ - config = config or ExemplarConfig() - if not isinstance(node, nodes.literal_block): - return False - text = node.astext() - return text.lstrip().lower().startswith(config.usage_pattern.lower()) - - -def _is_usage_section(node: nodes.Node) -> bool: - """Check if a node is a usage section. - - Parameters - ---------- - node : nodes.Node - A docutils node to check. - - Returns - ------- - bool - True if this is a section with "usage" in its ID. - - Examples - -------- - >>> from docutils import nodes - >>> section = nodes.section() - >>> section["ids"] = ["usage"] - >>> _is_usage_section(section) - True - >>> section2 = nodes.section() - >>> section2["ids"] = ["sync-usage"] - >>> _is_usage_section(section2) - True - >>> section3 = nodes.section() - >>> section3["ids"] = ["options"] - >>> _is_usage_section(section3) - False - >>> _is_usage_section(nodes.paragraph()) - False - """ - if not isinstance(node, nodes.section): - return False - ids: list[str] = node.get("ids", []) - return any(id_str == "usage" or id_str.endswith("-usage") for id_str in ids) - - -def _is_examples_section( - node: nodes.Node, *, config: ExemplarConfig | None = None -) -> bool: - """Check if a node is an examples section. - - Parameters - ---------- - node : nodes.Node - A docutils node to check. - config : ExemplarConfig | None - Optional configuration. If None, uses default ExemplarConfig(). - - Returns - ------- - bool - True if this is an examples section (section with term suffix in its ID). - - Examples - -------- - >>> from docutils import nodes - >>> section = nodes.section() - >>> section["ids"] = ["examples"] - >>> _is_examples_section(section) - True - >>> section2 = nodes.section() - >>> section2["ids"] = ["machine-readable-output-examples"] - >>> _is_examples_section(section2) - True - >>> section3 = nodes.section() - >>> section3["ids"] = ["positional-arguments"] - >>> _is_examples_section(section3) - False - >>> _is_examples_section(nodes.paragraph()) - False - >>> _is_examples_section(nodes.literal_block(text="examples")) - False - - With custom configuration: - - >>> custom_config = ExemplarConfig(examples_term_suffix="demos") - >>> section = nodes.section() - >>> section["ids"] = ["demos"] - >>> _is_examples_section(section, config=custom_config) - True - >>> section2 = nodes.section() - >>> section2["ids"] = ["examples"] - >>> _is_examples_section(section2, config=custom_config) - False - """ - config = config or ExemplarConfig() - if not isinstance(node, nodes.section): - return False - ids: list[str] = node.get("ids", []) - return any(config.examples_term_suffix in id_str.lower() for id_str in ids) - - -def _reorder_nodes( - processed: list[nodes.Node], *, config: ExemplarConfig | None = None -) -> list[nodes.Node]: - """Reorder nodes so usage sections/blocks appear before examples sections. - - This ensures the CLI usage synopsis appears above examples in the - documentation, making it easier to understand command syntax before - seeing example invocations. - - The function handles both: - - Usage as literal_block (legacy format from older renderer) - - Usage as section#usage (new format with TOC support) - - Parameters - ---------- - processed : list[nodes.Node] - List of processed docutils nodes. - config : ExemplarConfig | None - Optional configuration. If None, uses default ExemplarConfig(). - - Returns - ------- - list[nodes.Node] - Reordered nodes with usage before examples (if enabled). - - Examples - -------- - >>> from docutils import nodes - - Create test nodes: - - >>> desc = nodes.paragraph(text="Description") - >>> examples = nodes.section() - >>> examples["ids"] = ["examples"] - >>> usage = nodes.literal_block(text="usage: cmd [-h]") - >>> args = nodes.section() - >>> args["ids"] = ["arguments"] - - When usage appears after examples, it gets moved before: - - >>> result = _reorder_nodes([desc, examples, usage, args]) - >>> [type(n).__name__ for n in result] - ['paragraph', 'literal_block', 'section', 'section'] - - When no examples exist, order is unchanged: - - >>> result = _reorder_nodes([desc, usage, args]) - >>> [type(n).__name__ for n in result] - ['paragraph', 'literal_block', 'section'] - - When usage already before examples, order is preserved: - - >>> result = _reorder_nodes([desc, usage, examples, args]) - >>> [type(n).__name__ for n in result] - ['paragraph', 'literal_block', 'section', 'section'] - - Empty list returns empty: - - >>> _reorder_nodes([]) - [] - - Usage sections (with TOC heading) are also handled: - - >>> usage_section = nodes.section() - >>> usage_section["ids"] = ["usage"] - >>> result = _reorder_nodes([desc, examples, usage_section, args]) - >>> [n.get("ids", []) for n in result if isinstance(n, nodes.section)] - [['usage'], ['examples'], ['arguments']] - - Reordering can be disabled via config: - - >>> no_reorder_config = ExemplarConfig(reorder_usage_before_examples=False) - >>> result = _reorder_nodes([desc, examples, usage, args], config=no_reorder_config) - >>> [type(n).__name__ for n in result] - ['paragraph', 'section', 'literal_block', 'section'] - """ - config = config or ExemplarConfig() - - # If reordering is disabled, return as-is - if not config.reorder_usage_before_examples: - return processed - - # First pass: check if there are any examples sections - has_examples = any(_is_examples_section(node, config=config) for node in processed) - if not has_examples: - # No examples, preserve original order - return processed - - usage_nodes: list[nodes.Node] = [] - examples_sections: list[nodes.Node] = [] - other_before_examples: list[nodes.Node] = [] - other_after_examples: list[nodes.Node] = [] - - seen_examples = False - for node in processed: - # Check for both usage block (literal_block) and usage section - if _is_usage_block(node, config=config) or _is_usage_section(node): - usage_nodes.append(node) - elif _is_examples_section(node, config=config): - examples_sections.append(node) - seen_examples = True - elif not seen_examples: - other_before_examples.append(node) - else: - other_after_examples.append(node) - - # Order: before_examples → usage → examples → after_examples - return ( - other_before_examples + usage_nodes + examples_sections + other_after_examples - ) - - -def _extract_sections_from_container( - container: nodes.Node, -) -> tuple[nodes.Node, list[nodes.section]]: - """Extract section nodes from a container, returning modified container. - - This function finds any section nodes that are children of the container - (typically argparse_program), removes them from the container, and returns - them separately so they can be made siblings. - - This is needed because Sphinx's TocTreeCollector only discovers sections - that are direct children of the document or properly nested in the section - hierarchy - sections inside arbitrary div containers are invisible to TOC. - - Parameters - ---------- - container : nodes.Node - A container node (typically argparse_program) that may contain sections. - - Returns - ------- - tuple[nodes.Node, list[nodes.section]] - A tuple of (modified_container, extracted_sections). - - Examples - -------- - >>> from docutils import nodes - >>> from sphinx_argparse_neo.nodes import argparse_program - >>> container = argparse_program() - >>> para = nodes.paragraph(text="Description") - >>> examples = nodes.section() - >>> examples["ids"] = ["examples"] - >>> container += para - >>> container += examples - >>> modified, extracted = _extract_sections_from_container(container) - >>> len(modified.children) - 1 - >>> len(extracted) - 1 - >>> extracted[0]["ids"] - ['examples'] - """ - if not hasattr(container, "children"): - return container, [] - - extracted_sections: list[nodes.section] = [] - remaining_children: list[nodes.Node] = [] - - for child in container.children: - if isinstance(child, nodes.section): - extracted_sections.append(child) - else: - remaining_children.append(child) - - # Update container with remaining children only - container[:] = remaining_children # type: ignore[index] - - return container, extracted_sections - - -class CleanArgParseDirective(ArgparseDirective): # type: ignore[misc] - """ArgParse directive that strips ANSI codes and formats examples.""" - - def run(self) -> list[nodes.Node]: - """Run the directive, clean output, format examples, and reorder. - - The processing pipeline: - 1. Run base directive to get initial nodes - 2. Load configuration from Sphinx config - 3. Process each node (strip ANSI, transform examples definition lists) - 4. Extract sections from inside argparse_program containers - 5. Reorder so usage appears before examples (if enabled) - """ - result = super().run() - - # Load configuration from Sphinx - config = ExemplarConfig.from_sphinx_config(self.env.config) - - # Extract page name for unique section IDs across different CLI pages - page_prefix = "" - if hasattr(self.state, "document"): - settings = self.state.document.settings - if hasattr(settings, "env") and hasattr(settings.env, "docname"): - # docname is like "cli/sync" - extract "sync" - docname = settings.env.docname - page_prefix = docname.split("/")[-1] - - processed: list[nodes.Node] = [] - for node in result: - processed_node = process_node(node, page_prefix=page_prefix, config=config) - if isinstance(processed_node, list): - processed.extend(processed_node) - else: - processed.append(processed_node) - - # Extract sections from inside argparse_program containers - # This is needed because sections inside divs are invisible to Sphinx TOC - flattened: list[nodes.Node] = [] - for node in processed: - # Check if this is an argparse_program (or similar container) - # that might have sections inside - node_class_name = type(node).__name__ - if node_class_name == "argparse_program": - modified, extracted = _extract_sections_from_container(node) - flattened.append(modified) - flattened.extend(extracted) - else: - flattened.append(node) - - # Reorder: usage sections/blocks before examples sections - return _reorder_nodes(flattened, config=config) - - -def setup(app: Sphinx) -> dict[str, t.Any]: - """Register the clean argparse directive, lexers, and CLI roles. - - Configuration Options - --------------------- - The following configuration options can be set in conf.py: - - ``argparse_examples_term_suffix`` : str (default: "examples") - Term must end with this string to be treated as examples header. - - ``argparse_examples_base_term`` : str (default: "examples") - Exact match for the base examples section. - - ``argparse_examples_section_title`` : str (default: "Examples") - Title used for the base examples section. - - ``argparse_usage_pattern`` : str (default: "usage:") - Text must start with this to be treated as a usage block. - - ``argparse_examples_command_prefix`` : str (default: "$ ") - Prefix added to each command line in examples code blocks. - - ``argparse_examples_code_language`` : str (default: "console") - Language identifier for examples code blocks. - - ``argparse_examples_code_classes`` : list[str] (default: ["highlight-console"]) - CSS classes added to examples code blocks. - - ``argparse_usage_code_language`` : str (default: "cli-usage") - Language identifier for usage blocks. - - ``argparse_reorder_usage_before_examples`` : bool (default: True) - Whether to reorder nodes so usage appears before examples. - - Parameters - ---------- - app : Sphinx - The Sphinx application object. - - Returns - ------- - dict - Extension metadata. - """ - # Load the base sphinx_argparse_neo extension first - app.setup_extension("sphinx_argparse_neo") - - # Register configuration options - app.add_config_value("argparse_examples_term_suffix", "examples", "html") - app.add_config_value("argparse_examples_base_term", "examples", "html") - app.add_config_value("argparse_examples_section_title", "Examples", "html") - app.add_config_value("argparse_usage_pattern", "usage:", "html") - app.add_config_value("argparse_examples_command_prefix", "$ ", "html") - app.add_config_value("argparse_examples_code_language", "console", "html") - app.add_config_value( - "argparse_examples_code_classes", ["highlight-console"], "html" - ) - app.add_config_value("argparse_usage_code_language", "cli-usage", "html") - app.add_config_value("argparse_reorder_usage_before_examples", True, "html") - - # Override the argparse directive with our enhanced version - app.add_directive("argparse", CleanArgParseDirective, override=True) - - # Register CLI usage lexer for usage block highlighting - from cli_usage_lexer import CLIUsageLexer - - app.add_lexer("cli-usage", CLIUsageLexer) - - # Register argparse lexers for help output highlighting - from argparse_lexer import ( - ArgparseHelpLexer, - ArgparseLexer, - ArgparseUsageLexer, - ) - - app.add_lexer("argparse", ArgparseLexer) - app.add_lexer("argparse-usage", ArgparseUsageLexer) - app.add_lexer("argparse-help", ArgparseHelpLexer) - - # Register CLI inline roles for documentation - from argparse_roles import register_roles - - register_roles() - - return {"version": "4.0", "parallel_read_safe": True} diff --git a/docs/_ext/argparse_lexer.py b/docs/_ext/argparse_lexer.py deleted file mode 100644 index 14aed55649..0000000000 --- a/docs/_ext/argparse_lexer.py +++ /dev/null @@ -1,429 +0,0 @@ -"""Pygments lexers for argparse help output. - -This module provides custom Pygments lexers for highlighting argparse-generated -command-line help text, including usage lines, section headers, and full help output. - -Three lexer classes are provided: -- ArgparseUsageLexer: For usage lines only -- ArgparseHelpLexer: For full -h output (delegates usage to ArgparseUsageLexer) -- ArgparseLexer: Smart auto-detecting wrapper -""" - -from __future__ import annotations - -from pygments.lexer import RegexLexer, bygroups, include -from pygments.token import Generic, Name, Operator, Punctuation, Text, Whitespace - - -class ArgparseUsageLexer(RegexLexer): - """Lexer for argparse usage lines only. - - Handles patterns like: - - usage: PROG [-h] [--foo FOO] bar {a,b,c} - - Mutually exclusive: [-a | -b], (--foo | --bar) - - Choices: {json,yaml,table} - - Variadic: FILE ..., [FILE ...], [--foo [FOO]] - - Examples - -------- - >>> from pygments.token import Token - >>> lexer = ArgparseUsageLexer() - >>> tokens = list(lexer.get_tokens("usage: cmd [-h]")) - >>> tokens[0] - (Token.Generic.Heading, 'usage:') - >>> tokens[2] - (Token.Name.Label, 'cmd') - """ - - name = "Argparse Usage" - aliases = ["argparse-usage"] # noqa: RUF012 - filenames: list[str] = [] # noqa: RUF012 - mimetypes = ["text/x-argparse-usage"] # noqa: RUF012 - - tokens = { # noqa: RUF012 - "root": [ - # "usage:" at start of line - then look for program name - ( - r"^(usage:)(\s+)", - bygroups(Generic.Heading, Whitespace), # type: ignore[no-untyped-call] - "after_usage", - ), - # Continuation lines (leading whitespace for wrapped usage) - (r"^(\s+)(?=\S)", Whitespace), - include("inline"), - ], - "after_usage": [ - # Whitespace - (r"\s+", Whitespace), - # Program name (first lowercase word after usage:) - (r"\b[a-z][-a-z0-9_]*\b", Name.Label, "usage_body"), - # Fallback to inline if something unexpected - include("inline"), - ], - "usage_body": [ - # Whitespace - (r"\s+", Whitespace), - # Ellipsis for variadic args (before other patterns) - (r"\.\.\.", Punctuation), - # Long options with = value (e.g., --log-level=VALUE) - ( - r"(--[a-zA-Z0-9][-a-zA-Z0-9]*)(=)([A-Z][A-Z0-9_]*|[a-z][-a-z0-9_]*)", - bygroups(Name.Tag, Operator, Name.Variable), # type: ignore[no-untyped-call] - ), - # Long options standalone - (r"--[a-zA-Z0-9][-a-zA-Z0-9]*", Name.Tag), - # Short options with space-separated value (e.g., -S socket-path) - ( - r"(-[a-zA-Z0-9])(\s+)([A-Z][A-Z0-9_]*|[a-z][-a-z0-9_]*)", - bygroups(Name.Attribute, Whitespace, Name.Variable), # type: ignore[no-untyped-call] - ), - # Short options standalone - (r"-[a-zA-Z0-9]", Name.Attribute), - # Opening brace - enter choices state - (r"\{", Punctuation, "choices"), - # Opening bracket - enter optional state - (r"\[", Punctuation, "optional"), - # Closing bracket (fallback for unmatched) - (r"\]", Punctuation), - # Opening paren - enter required mutex state - (r"\(", Punctuation, "required"), - # Closing paren (fallback for unmatched) - (r"\)", Punctuation), - # Choice separator (pipe) for mutex groups - (r"\|", Operator), - # UPPERCASE meta-variables (COMMAND, FILE, PATH) - (r"\b[A-Z][A-Z0-9_]*\b", Name.Variable), - # Subcommand/positional names (Name.Function for distinct styling) - (r"\b[a-z][-a-z0-9_]*\b", Name.Function), - # Catch-all for any other text - (r"[^\s\[\]|(){},]+", Text), - ], - "inline": [ - # Whitespace - (r"\s+", Whitespace), - # Ellipsis for variadic args (before other patterns) - (r"\.\.\.", Punctuation), - # Long options with = value (e.g., --log-level=VALUE) - ( - r"(--[a-zA-Z0-9][-a-zA-Z0-9]*)(=)([A-Z][A-Z0-9_]*|[a-z][-a-z0-9_]*)", - bygroups(Name.Tag, Operator, Name.Variable), # type: ignore[no-untyped-call] - ), - # Long options standalone - (r"--[a-zA-Z0-9][-a-zA-Z0-9]*", Name.Tag), - # Short options with space-separated value (e.g., -S socket-path) - ( - r"(-[a-zA-Z0-9])(\s+)([A-Z][A-Z0-9_]*|[a-z][-a-z0-9_]*)", - bygroups(Name.Attribute, Whitespace, Name.Variable), # type: ignore[no-untyped-call] - ), - # Short options standalone - (r"-[a-zA-Z0-9]", Name.Attribute), - # Opening brace - enter choices state - (r"\{", Punctuation, "choices"), - # Opening bracket - enter optional state - (r"\[", Punctuation, "optional"), - # Closing bracket (fallback for unmatched) - (r"\]", Punctuation), - # Opening paren - enter required mutex state - (r"\(", Punctuation, "required"), - # Closing paren (fallback for unmatched) - (r"\)", Punctuation), - # Choice separator (pipe) for mutex groups - (r"\|", Operator), - # UPPERCASE meta-variables (COMMAND, FILE, PATH) - (r"\b[A-Z][A-Z0-9_]*\b", Name.Variable), - # Positional/command names (lowercase with dashes) - (r"\b[a-z][-a-z0-9_]*\b", Name.Label), - # Catch-all for any other text - (r"[^\s\[\]|(){},]+", Text), - ], - "optional": [ - # Nested optional bracket - (r"\[", Punctuation, "#push"), - # End optional - (r"\]", Punctuation, "#pop"), - # Contents use usage_body rules (subcommands are green) - include("usage_body"), - ], - "required": [ - # Nested required paren - (r"\(", Punctuation, "#push"), - # End required - (r"\)", Punctuation, "#pop"), - # Contents use usage_body rules (subcommands are green) - include("usage_body"), - ], - "choices": [ - # Choice values (comma-separated inside braces) - (r"[a-zA-Z0-9][-a-zA-Z0-9_]*", Name.Constant), - # Comma separator - (r",", Punctuation), - # End choices - (r"\}", Punctuation, "#pop"), - # Whitespace - (r"\s+", Whitespace), - ], - } - - -class ArgparseHelpLexer(RegexLexer): - """Lexer for full argparse -h help output. - - Handles: - - Usage lines (delegates to ArgparseUsageLexer patterns) - - Section headers (positional arguments:, options:, etc.) - - Option entries with help text - - Indented descriptions - - Examples - -------- - >>> from pygments.token import Token - >>> lexer = ArgparseHelpLexer() - >>> tokens = list(lexer.get_tokens("positional arguments:")) - >>> any(t[0] == Token.Generic.Subheading for t in tokens) - True - >>> tokens = list(lexer.get_tokens(" -h, --help show help")) - >>> any(t[0] == Token.Name.Attribute for t in tokens) - True - """ - - name = "Argparse Help" - aliases = ["argparse-help"] # noqa: RUF012 - filenames: list[str] = [] # noqa: RUF012 - mimetypes = ["text/x-argparse-help"] # noqa: RUF012 - - tokens = { # noqa: RUF012 - "root": [ - # "usage:" line - switch to after_usage to find program name - ( - r"^(usage:)(\s+)", - bygroups(Generic.Heading, Whitespace), # type: ignore[no-untyped-call] - "after_usage", - ), - # Section headers (e.g., "positional arguments:", "options:") - (r"^([a-zA-Z][-a-zA-Z0-9_ ]*:)\s*$", Generic.Subheading), - # Option entry lines (indented with spaces/tabs, not just newlines) - (r"^([ \t]+)", Whitespace, "option_line"), - # Continuation of usage (leading spaces/tabs followed by content) - (r"^([ \t]+)(?=\S)", Whitespace), - # Anything else (must match at least one char to avoid infinite loop) - (r".+\n?", Text), - # Standalone newlines - (r"\n", Whitespace), - ], - "after_usage": [ - # Whitespace - (r"\s+", Whitespace), - # Program name (first lowercase word after usage:) - (r"\b[a-z][-a-z0-9_]*\b", Name.Label, "usage"), - # Fallback to usage if something unexpected - include("usage_inline"), - ], - "usage": [ - # End of usage on blank line or section header - (r"\n(?=[a-zA-Z][-a-zA-Z0-9_ ]*:\s*$)", Text, "#pop:2"), - (r"\n(?=\n)", Text, "#pop:2"), - # Usage content - use usage_inline rules (subcommands are green) - include("usage_inline"), - # Line continuation - (r"\n", Text), - ], - "usage_inline": [ - # Whitespace - (r"\s+", Whitespace), - # Ellipsis for variadic args - (r"\.\.\.", Punctuation), - # Long options with = value - ( - r"(--[a-zA-Z0-9][-a-zA-Z0-9]*)(=)([A-Z][A-Z0-9_]*|[a-z][-a-z0-9_]*)", - bygroups(Name.Tag, Operator, Name.Variable), # type: ignore[no-untyped-call] - ), - # Long options standalone - (r"--[a-zA-Z0-9][-a-zA-Z0-9]*", Name.Tag), - # Short options with value - ( - r"(-[a-zA-Z0-9])(\s+)([A-Z][A-Z0-9_]*|[a-z][-a-z0-9_]*)", - bygroups(Name.Attribute, Whitespace, Name.Variable), # type: ignore[no-untyped-call] - ), - # Short options standalone - (r"-[a-zA-Z0-9]", Name.Attribute), - # Choices in braces - (r"\{", Punctuation, "choices"), - # Optional brackets - (r"\[", Punctuation, "optional"), - (r"\]", Punctuation), - # Required parens (mutex) - (r"\(", Punctuation, "required"), - (r"\)", Punctuation), - # Pipe for mutex - (r"\|", Operator), - # UPPERCASE metavars - (r"\b[A-Z][A-Z0-9_]*\b", Name.Variable), - # Subcommand/positional names (Name.Function for distinct styling) - (r"\b[a-z][-a-z0-9_]*\b", Name.Function), - # Other text - (r"[^\s\[\]|(){},\n]+", Text), - ], - "option_line": [ - # Short option with comma (e.g., "-h, --help") - ( - r"(-[a-zA-Z0-9])(,)(\s*)(--[a-zA-Z0-9][-a-zA-Z0-9]*)", - bygroups(Name.Attribute, Punctuation, Whitespace, Name.Tag), # type: ignore[no-untyped-call] - ), - # Long options with = value - ( - r"(--[a-zA-Z0-9][-a-zA-Z0-9]*)(=)([A-Z][A-Z0-9_]*|[a-z][-a-z0-9_]*)", - bygroups(Name.Tag, Operator, Name.Variable), # type: ignore[no-untyped-call] - ), - # Long options with space-separated metavar - ( - r"(--[a-zA-Z0-9][-a-zA-Z0-9]*)(\s+)([A-Z][A-Z0-9_]+)", - bygroups(Name.Tag, Whitespace, Name.Variable), # type: ignore[no-untyped-call] - ), - # Long options standalone - (r"--[a-zA-Z0-9][-a-zA-Z0-9]*", Name.Tag), - # Short options with metavar - ( - r"(-[a-zA-Z0-9])(\s+)([A-Z][A-Z0-9_]+)", - bygroups(Name.Attribute, Whitespace, Name.Variable), # type: ignore[no-untyped-call] - ), - # Short options standalone - (r"-[a-zA-Z0-9]", Name.Attribute), - # Choices in braces - (r"\{", Punctuation, "option_choices"), - # Help text (everything after double space or large gap) - (r"([ \t]{2,})(.+)$", bygroups(Whitespace, Text)), # type: ignore[no-untyped-call] - # End of line - MUST come before \s+ to properly pop on newlines - (r"\n", Text, "#pop"), - # Other whitespace (spaces/tabs only, not newlines) - (r"[ \t]+", Whitespace), - # UPPERCASE metavars - (r"\b[A-Z][A-Z0-9_]*\b", Name.Variable), - # Anything else on the line - (r"[^\s\n]+", Text), - ], - "optional": [ - (r"\[", Punctuation, "#push"), - (r"\]", Punctuation, "#pop"), - include("usage_inline"), - ], - "required": [ - (r"\(", Punctuation, "#push"), - (r"\)", Punctuation, "#pop"), - include("usage_inline"), - ], - "choices": [ - (r"[a-zA-Z0-9][-a-zA-Z0-9_]*", Name.Constant), - (r",", Punctuation), - (r"\}", Punctuation, "#pop"), - (r"\s+", Whitespace), - ], - "option_choices": [ - (r"[a-zA-Z0-9][-a-zA-Z0-9_]*", Name.Constant), - (r",", Punctuation), - (r"\}", Punctuation, "#pop"), - (r"\s+", Whitespace), - ], - } - - -class ArgparseLexer(ArgparseHelpLexer): - """Smart auto-detecting lexer for argparse output. - - Inherits from ArgparseHelpLexer to properly handle Pygments' metaclass - token processing. Using inheritance (not token dict copying) avoids - shared mutable state that causes memory corruption. - - This is the recommended lexer for general argparse highlighting. - - Examples - -------- - >>> from pygments.token import Token - >>> lexer = ArgparseLexer() - - Usage line detection: - - >>> tokens = list(lexer.get_tokens("usage: cmd [-h]")) - >>> tokens[0] - (Token.Generic.Heading, 'usage:') - - Section header detection (Pygments appends newline to input): - - >>> tokens = list(lexer.get_tokens("positional arguments:")) - >>> any(t[0] == Token.Generic.Subheading for t in tokens) - True - - Option highlighting in option line context: - - >>> tokens = list(lexer.get_tokens(" -h, --help show help")) - >>> any(t[0] == Token.Name.Attribute for t in tokens) - True - """ - - name = "Argparse" - aliases = ["argparse"] # noqa: RUF012 - filenames: list[str] = [] # noqa: RUF012 - mimetypes = ["text/x-argparse"] # noqa: RUF012 - - # Tokens inherited from ArgparseHelpLexer - do NOT redefine or copy - - -def tokenize_argparse(text: str) -> list[tuple[str, str]]: - """Tokenize argparse text and return list of (token_type, value) tuples. - - Parameters - ---------- - text : str - Argparse help or usage text to tokenize. - - Returns - ------- - list[tuple[str, str]] - List of (token_type_name, text_value) tuples. - - Examples - -------- - >>> result = tokenize_argparse("usage: cmd [-h]") - >>> result[0] - ('Token.Generic.Heading', 'usage:') - >>> result[2] - ('Token.Name.Label', 'cmd') - - >>> result = tokenize_argparse("positional arguments:") - >>> any('Token.Generic.Subheading' in t[0] for t in result) - True - """ - lexer = ArgparseLexer() - return [ - (str(tok_type), tok_value) for tok_type, tok_value in lexer.get_tokens(text) - ] - - -def tokenize_usage(text: str) -> list[tuple[str, str]]: - """Tokenize usage text and return list of (token_type, value) tuples. - - Parameters - ---------- - text : str - CLI usage text to tokenize. - - Returns - ------- - list[tuple[str, str]] - List of (token_type_name, text_value) tuples. - - Examples - -------- - >>> result = tokenize_usage("usage: cmd [-h]") - >>> result[0] - ('Token.Generic.Heading', 'usage:') - >>> result[2] - ('Token.Name.Label', 'cmd') - >>> result[4] - ('Token.Punctuation', '[') - >>> result[5] - ('Token.Name.Attribute', '-h') - """ - lexer = ArgparseUsageLexer() - return [ - (str(tok_type), tok_value) for tok_type, tok_value in lexer.get_tokens(text) - ] diff --git a/docs/_ext/argparse_roles.py b/docs/_ext/argparse_roles.py deleted file mode 100644 index 86e5459a28..0000000000 --- a/docs/_ext/argparse_roles.py +++ /dev/null @@ -1,370 +0,0 @@ -"""Docutils inline roles for CLI/argparse highlighting. - -This module provides custom docutils roles for inline highlighting of CLI -elements in reStructuredText and MyST documentation. - -Available roles: -- :cli-option: - CLI options (--verbose, -h) -- :cli-metavar: - Metavar placeholders (FILE, PATH) -- :cli-command: - Command names (sync, add) -- :cli-default: - Default values (None, "default") -- :cli-choice: - Choice values (json, yaml) -""" - -from __future__ import annotations - -import typing as t - -from docutils import nodes -from docutils.parsers.rst import roles - -if t.TYPE_CHECKING: - from docutils.parsers.rst.states import Inliner - - -def normalize_options(options: dict[str, t.Any] | None) -> dict[str, t.Any]: - """Normalize role options, converting None to empty dict. - - Parameters - ---------- - options : dict | None - Options passed to the role. - - Returns - ------- - dict - Normalized options dict (never None). - - Examples - -------- - >>> normalize_options(None) - {} - >>> normalize_options({"class": "custom"}) - {'class': 'custom'} - """ - return options if options is not None else {} - - -def cli_option_role( - name: str, - rawtext: str, - text: str, - lineno: int, - inliner: Inliner | None, - options: dict[str, t.Any] | None = None, - content: list[str] | None = None, -) -> tuple[list[nodes.Node], list[nodes.system_message]]: - """Role for CLI options like --foo or -h. - - Generates a literal node with appropriate CSS classes for styling. - Long options (--foo) get 'cli-option-long', short options (-h) get - 'cli-option-short'. - - Parameters - ---------- - name : str - Local name of the role used in document. - rawtext : str - Full interpreted text including role markup. - text : str - Content between backticks. - lineno : int - Line number. - inliner : Inliner | None - Object that called the role (has .reporter, .document). - options : dict | None - Options from role directive. - content : list | None - Content from role directive. - - Returns - ------- - tuple[list[nodes.Node], list[nodes.system_message]] - Nodes to insert and any messages. - - Examples - -------- - >>> node_list, messages = cli_option_role( - ... "cli-option", ":cli-option:`--verbose`", "--verbose", - ... 1, None - ... ) - >>> node_list[0]["classes"] - ['cli-option', 'cli-option-long'] - - >>> node_list, messages = cli_option_role( - ... "cli-option", ":cli-option:`-h`", "-h", - ... 1, None - ... ) - >>> node_list[0]["classes"] - ['cli-option', 'cli-option-short'] - - >>> node_list, messages = cli_option_role( - ... "cli-option", ":cli-option:`--no-color`", "--no-color", - ... 1, None - ... ) - >>> node_list[0].astext() - '--no-color' - """ - options = normalize_options(options) - node = nodes.literal(rawtext, text, classes=["cli-option"]) - - if text.startswith("--"): - node["classes"].append("cli-option-long") - elif text.startswith("-"): - node["classes"].append("cli-option-short") - - return [node], [] - - -def cli_metavar_role( - name: str, - rawtext: str, - text: str, - lineno: int, - inliner: Inliner | None, - options: dict[str, t.Any] | None = None, - content: list[str] | None = None, -) -> tuple[list[nodes.Node], list[nodes.system_message]]: - """Role for CLI metavar placeholders like FILE or PATH. - - Generates a literal node with 'cli-metavar' CSS class for styling. - - Parameters - ---------- - name : str - Local name of the role used in document. - rawtext : str - Full interpreted text including role markup. - text : str - Content between backticks. - lineno : int - Line number. - inliner : Inliner | None - Object that called the role. - options : dict | None - Options from role directive. - content : list | None - Content from role directive. - - Returns - ------- - tuple[list[nodes.Node], list[nodes.system_message]] - Nodes to insert and any messages. - - Examples - -------- - >>> node_list, messages = cli_metavar_role( - ... "cli-metavar", ":cli-metavar:`FILE`", "FILE", - ... 1, None - ... ) - >>> node_list[0]["classes"] - ['cli-metavar'] - >>> node_list[0].astext() - 'FILE' - - >>> node_list, messages = cli_metavar_role( - ... "cli-metavar", ":cli-metavar:`PATH`", "PATH", - ... 1, None - ... ) - >>> "cli-metavar" in node_list[0]["classes"] - True - """ - options = normalize_options(options) - node = nodes.literal(rawtext, text, classes=["cli-metavar"]) - return [node], [] - - -def cli_command_role( - name: str, - rawtext: str, - text: str, - lineno: int, - inliner: Inliner | None, - options: dict[str, t.Any] | None = None, - content: list[str] | None = None, -) -> tuple[list[nodes.Node], list[nodes.system_message]]: - """Role for CLI command names like sync or add. - - Generates a literal node with 'cli-command' CSS class for styling. - - Parameters - ---------- - name : str - Local name of the role used in document. - rawtext : str - Full interpreted text including role markup. - text : str - Content between backticks. - lineno : int - Line number. - inliner : Inliner | None - Object that called the role. - options : dict | None - Options from role directive. - content : list | None - Content from role directive. - - Returns - ------- - tuple[list[nodes.Node], list[nodes.system_message]] - Nodes to insert and any messages. - - Examples - -------- - >>> node_list, messages = cli_command_role( - ... "cli-command", ":cli-command:`sync`", "sync", - ... 1, None - ... ) - >>> node_list[0]["classes"] - ['cli-command'] - >>> node_list[0].astext() - 'sync' - - >>> node_list, messages = cli_command_role( - ... "cli-command", ":cli-command:`myapp`", "myapp", - ... 1, None - ... ) - >>> "cli-command" in node_list[0]["classes"] - True - """ - options = normalize_options(options) - node = nodes.literal(rawtext, text, classes=["cli-command"]) - return [node], [] - - -def cli_default_role( - name: str, - rawtext: str, - text: str, - lineno: int, - inliner: Inliner | None, - options: dict[str, t.Any] | None = None, - content: list[str] | None = None, -) -> tuple[list[nodes.Node], list[nodes.system_message]]: - """Role for CLI default values like None or "default". - - Generates a literal node with 'cli-default' CSS class for styling. - - Parameters - ---------- - name : str - Local name of the role used in document. - rawtext : str - Full interpreted text including role markup. - text : str - Content between backticks. - lineno : int - Line number. - inliner : Inliner | None - Object that called the role. - options : dict | None - Options from role directive. - content : list | None - Content from role directive. - - Returns - ------- - tuple[list[nodes.Node], list[nodes.system_message]] - Nodes to insert and any messages. - - Examples - -------- - >>> node_list, messages = cli_default_role( - ... "cli-default", ":cli-default:`None`", "None", - ... 1, None - ... ) - >>> node_list[0]["classes"] - ['cli-default'] - >>> node_list[0].astext() - 'None' - - >>> node_list, messages = cli_default_role( - ... "cli-default", ':cli-default:`"auto"`', '"auto"', - ... 1, None - ... ) - >>> "cli-default" in node_list[0]["classes"] - True - """ - options = normalize_options(options) - node = nodes.literal(rawtext, text, classes=["cli-default"]) - return [node], [] - - -def cli_choice_role( - name: str, - rawtext: str, - text: str, - lineno: int, - inliner: Inliner | None, - options: dict[str, t.Any] | None = None, - content: list[str] | None = None, -) -> tuple[list[nodes.Node], list[nodes.system_message]]: - """Role for CLI choice values like json or yaml. - - Generates a literal node with 'cli-choice' CSS class for styling. - - Parameters - ---------- - name : str - Local name of the role used in document. - rawtext : str - Full interpreted text including role markup. - text : str - Content between backticks. - lineno : int - Line number. - inliner : Inliner | None - Object that called the role. - options : dict | None - Options from role directive. - content : list | None - Content from role directive. - - Returns - ------- - tuple[list[nodes.Node], list[nodes.system_message]] - Nodes to insert and any messages. - - Examples - -------- - >>> node_list, messages = cli_choice_role( - ... "cli-choice", ":cli-choice:`json`", "json", - ... 1, None - ... ) - >>> node_list[0]["classes"] - ['cli-choice'] - >>> node_list[0].astext() - 'json' - - >>> node_list, messages = cli_choice_role( - ... "cli-choice", ":cli-choice:`yaml`", "yaml", - ... 1, None - ... ) - >>> "cli-choice" in node_list[0]["classes"] - True - """ - options = normalize_options(options) - node = nodes.literal(rawtext, text, classes=["cli-choice"]) - return [node], [] - - -def register_roles() -> None: - """Register all CLI roles with docutils. - - This function registers the following roles: - - cli-option: For CLI options (--verbose, -h) - - cli-metavar: For metavar placeholders (FILE, PATH) - - cli-command: For command names (sync, add) - - cli-default: For default values (None, "default") - - cli-choice: For choice values (json, yaml) - - Examples - -------- - >>> register_roles() - >>> # Roles are now available in docutils RST parsing - """ - roles.register_local_role("cli-option", cli_option_role) # type: ignore[arg-type] - roles.register_local_role("cli-metavar", cli_metavar_role) # type: ignore[arg-type] - roles.register_local_role("cli-command", cli_command_role) # type: ignore[arg-type] - roles.register_local_role("cli-default", cli_default_role) # type: ignore[arg-type] - roles.register_local_role("cli-choice", cli_choice_role) # type: ignore[arg-type] diff --git a/docs/_ext/cli_usage_lexer.py b/docs/_ext/cli_usage_lexer.py deleted file mode 100644 index 40170e3178..0000000000 --- a/docs/_ext/cli_usage_lexer.py +++ /dev/null @@ -1,115 +0,0 @@ -"""Pygments lexer for CLI usage/help output. - -This module provides a custom Pygments lexer for highlighting command-line -usage text typically generated by argparse, getopt, or similar libraries. -""" - -from __future__ import annotations - -from pygments.lexer import RegexLexer, bygroups, include -from pygments.token import Generic, Name, Operator, Punctuation, Text, Whitespace - - -class CLIUsageLexer(RegexLexer): - """Lexer for CLI usage/help text (argparse, etc.). - - Highlights usage patterns including options, arguments, and meta-variables. - - Examples - -------- - >>> from pygments.token import Token - >>> lexer = CLIUsageLexer() - >>> tokens = list(lexer.get_tokens("usage: cmd [-h]")) - >>> tokens[0] - (Token.Generic.Heading, 'usage:') - >>> tokens[2] - (Token.Name.Label, 'cmd') - """ - - name = "CLI Usage" - aliases = ["cli-usage", "usage"] # noqa: RUF012 - filenames: list[str] = [] # noqa: RUF012 - mimetypes = ["text/x-cli-usage"] # noqa: RUF012 - - tokens = { # noqa: RUF012 - "root": [ - # "usage:" at start of line - (r"^(usage:)(\s+)", bygroups(Generic.Heading, Whitespace)), # type: ignore[no-untyped-call] - # Continuation lines (leading whitespace for wrapped usage) - (r"^(\s+)(?=\S)", Whitespace), - include("inline"), - ], - "inline": [ - # Whitespace - (r"\s+", Whitespace), - # Long options with = value (e.g., --log-level=VALUE) - ( - r"(--[a-zA-Z0-9][-a-zA-Z0-9]*)(=)([A-Z][A-Z0-9_]*|[a-z][-a-z0-9]*)", - bygroups(Name.Tag, Operator, Name.Variable), # type: ignore[no-untyped-call] - ), - # Long options standalone - (r"--[a-zA-Z0-9][-a-zA-Z0-9]*", Name.Tag), - # Short options with space-separated value (e.g., -S socket-path) - ( - r"(-[a-zA-Z0-9])(\s+)([A-Z][A-Z0-9_]*|[a-z][-a-z0-9]*)", - bygroups(Name.Attribute, Whitespace, Name.Variable), # type: ignore[no-untyped-call] - ), - # Short options standalone - (r"-[a-zA-Z0-9]", Name.Attribute), - # UPPERCASE meta-variables (COMMAND, FILE, PATH) - (r"\b[A-Z][A-Z0-9_]+\b", Name.Constant), - # Opening bracket - enter optional state - (r"\[", Punctuation, "optional"), - # Closing bracket (fallback for unmatched) - (r"\]", Punctuation), - # Choice separator (pipe) - (r"\|", Operator), - # Parentheses for grouping - (r"[()]", Punctuation), - # Positional/command names (lowercase with dashes) - (r"\b[a-z][-a-z0-9]*\b", Name.Label), - # Catch-all for any other text - (r"[^\s\[\]|()]+", Text), - ], - "optional": [ - # Nested optional bracket - (r"\[", Punctuation, "#push"), - # End optional - (r"\]", Punctuation, "#pop"), - # Contents use inline rules - include("inline"), - ], - } - - -def tokenize_usage(text: str) -> list[tuple[str, str]]: - """Tokenize usage text and return list of (token_type, value) tuples. - - Parameters - ---------- - text : str - CLI usage text to tokenize. - - Returns - ------- - list[tuple[str, str]] - List of (token_type_name, text_value) tuples. - - Examples - -------- - >>> result = tokenize_usage("usage: cmd [-h]") - >>> result[0] - ('Token.Generic.Heading', 'usage:') - >>> result[2] - ('Token.Name.Label', 'cmd') - >>> result[4] - ('Token.Punctuation', '[') - >>> result[5] - ('Token.Name.Attribute', '-h') - >>> result[6] - ('Token.Punctuation', ']') - """ - lexer = CLIUsageLexer() - return [ - (str(tok_type), tok_value) for tok_type, tok_value in lexer.get_tokens(text) - ] diff --git a/docs/_ext/sphinx_argparse_neo/__init__.py b/docs/_ext/sphinx_argparse_neo/__init__.py deleted file mode 100644 index 5fa8dd94fe..0000000000 --- a/docs/_ext/sphinx_argparse_neo/__init__.py +++ /dev/null @@ -1,101 +0,0 @@ -"""sphinx_argparse_neo - Modern sphinx-argparse replacement. - -A Sphinx extension for documenting argparse-based CLI tools that: -- Works with Sphinx 8.x AND 9.x (no autodoc.mock dependency) -- Fixes long-standing sphinx-argparse issues (TOC pollution, heading levels) -- Provides configurable output (rubrics vs sections, flattened subcommands) -- Supports extensibility via renderer classes -- Text processing utilities (ANSI stripping) -""" - -from __future__ import annotations - -import typing as t - -from sphinx_argparse_neo.directive import ArgparseDirective -from sphinx_argparse_neo.nodes import ( - argparse_argument, - argparse_group, - argparse_program, - argparse_subcommand, - argparse_subcommands, - argparse_usage, - depart_argparse_argument_html, - depart_argparse_group_html, - depart_argparse_program_html, - depart_argparse_subcommand_html, - depart_argparse_subcommands_html, - depart_argparse_usage_html, - visit_argparse_argument_html, - visit_argparse_group_html, - visit_argparse_program_html, - visit_argparse_subcommand_html, - visit_argparse_subcommands_html, - visit_argparse_usage_html, -) -from sphinx_argparse_neo.utils import strip_ansi - -__all__ = [ - "ArgparseDirective", - "strip_ansi", -] - -if t.TYPE_CHECKING: - from sphinx.application import Sphinx - -__version__ = "1.0.0" - - -def setup(app: Sphinx) -> dict[str, t.Any]: - """Register the argparse directive and configuration options. - - Parameters - ---------- - app : Sphinx - The Sphinx application object. - - Returns - ------- - dict[str, t.Any] - Extension metadata. - """ - # Configuration options - app.add_config_value("argparse_group_title_prefix", "", "html") - app.add_config_value("argparse_show_defaults", True, "html") - app.add_config_value("argparse_show_choices", True, "html") - app.add_config_value("argparse_show_types", True, "html") - - # Register custom nodes - app.add_node( - argparse_program, - html=(visit_argparse_program_html, depart_argparse_program_html), - ) - app.add_node( - argparse_usage, - html=(visit_argparse_usage_html, depart_argparse_usage_html), - ) - app.add_node( - argparse_group, - html=(visit_argparse_group_html, depart_argparse_group_html), - ) - app.add_node( - argparse_argument, - html=(visit_argparse_argument_html, depart_argparse_argument_html), - ) - app.add_node( - argparse_subcommands, - html=(visit_argparse_subcommands_html, depart_argparse_subcommands_html), - ) - app.add_node( - argparse_subcommand, - html=(visit_argparse_subcommand_html, depart_argparse_subcommand_html), - ) - - # Register directive - app.add_directive("argparse", ArgparseDirective) - - return { - "version": __version__, - "parallel_read_safe": True, - "parallel_write_safe": True, - } diff --git a/docs/_ext/sphinx_argparse_neo/compat.py b/docs/_ext/sphinx_argparse_neo/compat.py deleted file mode 100644 index 15816d574c..0000000000 --- a/docs/_ext/sphinx_argparse_neo/compat.py +++ /dev/null @@ -1,271 +0,0 @@ -"""Compatibility utilities for module loading. - -This module provides utilities for loading Python modules safely, -including mock handling for imports that may fail during documentation -builds. - -Unlike sphinx-argparse, this module does NOT depend on autodoc's mock -functionality, which moved in Sphinx 9.x. -""" - -from __future__ import annotations - -import contextlib -import importlib -import sys -import typing as t - -if t.TYPE_CHECKING: - import argparse - from collections.abc import Iterator - - -class MockModule: - """Simple mock for unavailable imports. - - This class provides a minimal mock that can be used as a placeholder - for modules that aren't available during documentation builds. - - Parameters - ---------- - name : str - The module name being mocked. - - Examples - -------- - >>> mock = MockModule("mypackage.submodule") - >>> mock.__name__ - 'mypackage.submodule' - >>> child = mock.child_attr - >>> child.__name__ - 'mypackage.submodule.child_attr' - >>> callable(mock.some_function) - True - >>> mock.some_function() - - """ - - def __init__(self, name: str) -> None: - """Initialize the mock module.""" - self.__name__ = name - self._name = name - - def __repr__(self) -> str: - """Return string representation.""" - return f"" - - def __getattr__(self, name: str) -> MockModule: - """Return a child mock for any attribute access. - - Parameters - ---------- - name : str - The attribute name. - - Returns - ------- - MockModule - A new mock for the child attribute. - """ - return MockModule(f"{self._name}.{name}") - - def __call__(self, *args: t.Any, **kwargs: t.Any) -> MockModule: - """Return self when called as a function. - - Parameters - ---------- - *args : t.Any - Positional arguments (ignored). - **kwargs : t.Any - Keyword arguments (ignored). - - Returns - ------- - MockModule - Self. - """ - return self - - -@contextlib.contextmanager -def mock_imports(modules: list[str]) -> Iterator[None]: - """Context manager to mock missing imports. - - This provides a simple way to temporarily add mock modules to - sys.modules, allowing imports to succeed during documentation builds - even when the actual modules aren't available. - - Parameters - ---------- - modules : list[str] - List of module names to mock. - - Yields - ------ - None - Context manager yields nothing. - - Examples - -------- - >>> import sys - >>> "fake_module" in sys.modules - False - >>> with mock_imports(["fake_module", "fake_module.sub"]): - ... import fake_module - ... fake_module.__name__ - 'fake_module' - >>> "fake_module" in sys.modules - False - """ - mocked: dict[str, MockModule] = {} - - for name in modules: - if name not in sys.modules: - mocked[name] = MockModule(name) - sys.modules[name] = mocked[name] # type: ignore[assignment] - - try: - yield - finally: - for name in mocked: - del sys.modules[name] - - -def import_module(module_name: str) -> t.Any: - """Import a module by name. - - Parameters - ---------- - module_name : str - The fully qualified module name. - - Returns - ------- - t.Any - The imported module. - - Raises - ------ - ImportError - If the module cannot be imported. - - Examples - -------- - >>> mod = import_module("argparse") - >>> hasattr(mod, "ArgumentParser") - True - """ - return importlib.import_module(module_name) - - -def get_parser_from_module( - module_name: str, - func_name: str, - mock_modules: list[str] | None = None, -) -> argparse.ArgumentParser: - """Import a module and call a function to get an ArgumentParser. - - Parameters - ---------- - module_name : str - The module containing the parser factory function. - func_name : str - The name of the function that returns an ArgumentParser. - Can be a dotted path like "Class.method". - mock_modules : list[str] | None - Optional list of module names to mock during import. - - Returns - ------- - argparse.ArgumentParser - The argument parser returned by the function. - - Raises - ------ - ImportError - If the module cannot be imported. - AttributeError - If the function is not found. - TypeError - If the function doesn't return an ArgumentParser. - - Examples - -------- - Load tmuxp's parser factory: - - >>> parser = get_parser_from_module("tmuxp.cli", "create_parser") - >>> parser.prog - 'tmuxp' - >>> hasattr(parser, 'parse_args') - True - """ - ctx = mock_imports(mock_modules) if mock_modules else contextlib.nullcontext() - - with ctx: - module = import_module(module_name) - - # Handle dotted paths like "Class.method" - obj = module - for part in func_name.split("."): - obj = getattr(obj, part) - - # Call the function if it's callable - parser = obj() if callable(obj) else obj - - # Validate the return type at runtime - import argparse as argparse_module - - if not isinstance(parser, argparse_module.ArgumentParser): - msg = ( - f"{module_name}:{func_name} returned {type(parser).__name__}, " - f"expected ArgumentParser" - ) - raise TypeError(msg) - - return parser - - -def get_parser_from_entry_point( - entry_point: str, - mock_modules: list[str] | None = None, -) -> argparse.ArgumentParser: - """Get an ArgumentParser from a setuptools-style entry point string. - - Parameters - ---------- - entry_point : str - Entry point in the format "module:function" or "module:Class.method". - mock_modules : list[str] | None - Optional list of module names to mock during import. - - Returns - ------- - argparse.ArgumentParser - The argument parser. - - Raises - ------ - ValueError - If the entry point format is invalid. - - Examples - -------- - Load tmuxp's parser using entry point syntax: - - >>> parser = get_parser_from_entry_point("tmuxp.cli:create_parser") - >>> parser.prog - 'tmuxp' - - Invalid format raises ValueError: - - >>> get_parser_from_entry_point("no_colon") - Traceback (most recent call last): - ... - ValueError: Invalid entry point format: 'no_colon'. Expected 'module:function' - """ - if ":" not in entry_point: - msg = f"Invalid entry point format: {entry_point!r}. Expected 'module:function'" - raise ValueError(msg) - - module_name, func_name = entry_point.split(":", 1) - return get_parser_from_module(module_name, func_name, mock_modules) diff --git a/docs/_ext/sphinx_argparse_neo/directive.py b/docs/_ext/sphinx_argparse_neo/directive.py deleted file mode 100644 index 80d6d155ab..0000000000 --- a/docs/_ext/sphinx_argparse_neo/directive.py +++ /dev/null @@ -1,240 +0,0 @@ -"""Sphinx directive for argparse documentation. - -This module provides the ArgparseDirective class that integrates -with Sphinx to generate documentation from ArgumentParser instances. -""" - -from __future__ import annotations - -import typing as t - -from docutils import nodes -from docutils.parsers.rst import directives -from sphinx.util.docutils import SphinxDirective -from sphinx_argparse_neo.compat import get_parser_from_module -from sphinx_argparse_neo.parser import extract_parser -from sphinx_argparse_neo.renderer import ArgparseRenderer, RenderConfig - -if t.TYPE_CHECKING: - import argparse - - -class ArgparseDirective(SphinxDirective): - """Sphinx directive for documenting argparse-based CLI tools. - - Usage - ----- - .. argparse:: - :module: myapp.cli - :func: create_parser - :prog: myapp - - Options - ------- - :module: - The Python module containing the parser factory function. - :func: - The function name that returns an ArgumentParser. - Can be a dotted path like "Class.method". - :prog: - Override the program name (optional). - :path: - Navigate to a specific subparser by path (e.g., "sync pull"). - :no-defaults: - Don't show default values (flag). - :no-description: - Don't show parser description (flag). - :no-epilog: - Don't show parser epilog (flag). - :mock-modules: - Comma-separated list of modules to mock during import. - - Examples - -------- - In RST documentation:: - - .. argparse:: - :module: myapp.cli - :func: create_parser - :prog: myapp - - :path: subcommand - """ - - has_content = True - required_arguments = 0 - optional_arguments = 0 - - option_spec: t.ClassVar[dict[str, t.Any]] = { - "module": directives.unchanged_required, - "func": directives.unchanged_required, - "prog": directives.unchanged, - "path": directives.unchanged, - "no-defaults": directives.flag, - "no-description": directives.flag, - "no-epilog": directives.flag, - "no-choices": directives.flag, - "no-types": directives.flag, - "mock-modules": directives.unchanged, - # sphinx-argparse compatibility options - "nosubcommands": directives.flag, - "nodefault": directives.flag, - "noepilog": directives.flag, - "nodescription": directives.flag, - } - - def run(self) -> list[nodes.Node]: - """Execute the directive and return docutils nodes. - - Returns - ------- - list[nodes.Node] - List of docutils nodes representing the CLI documentation. - """ - # Get required options - module_name = self.options.get("module") - func_name = self.options.get("func") - - if not module_name or not func_name: - error = self.state_machine.reporter.error( - "argparse directive requires :module: and :func: options", - line=self.lineno, - ) - return [error] - - # Parse mock modules - mock_modules: list[str] | None = None - if "mock-modules" in self.options: - mock_modules = [m.strip() for m in self.options["mock-modules"].split(",")] - - # Load the parser - try: - parser = get_parser_from_module(module_name, func_name, mock_modules) - except Exception as e: - error = self.state_machine.reporter.error( - f"Failed to load parser from {module_name}:{func_name}: {e}", - line=self.lineno, - ) - return [error] - - # Override prog if specified - if "prog" in self.options: - parser.prog = self.options["prog"] - - # Navigate to subparser if path specified - if "path" in self.options: - parser = self._navigate_to_subparser(parser, self.options["path"]) - if parser is None: - error = self.state_machine.reporter.error( - f"Subparser path not found: {self.options['path']}", - line=self.lineno, - ) - return [error] - - # Build render config from directive options and Sphinx config - config = self._build_render_config() - - # Extract parser info - parser_info = extract_parser(parser) - - # Apply directive-level overrides - # Handle both new-style and sphinx-argparse compatibility options - if "no-description" in self.options or "nodescription" in self.options: - parser_info = parser_info.__class__( - prog=parser_info.prog, - usage=parser_info.usage, - bare_usage=parser_info.bare_usage, - description=None, - epilog=parser_info.epilog, - argument_groups=parser_info.argument_groups, - subcommands=parser_info.subcommands, - subcommand_dest=parser_info.subcommand_dest, - ) - if "no-epilog" in self.options or "noepilog" in self.options: - parser_info = parser_info.__class__( - prog=parser_info.prog, - usage=parser_info.usage, - bare_usage=parser_info.bare_usage, - description=parser_info.description, - epilog=None, - argument_groups=parser_info.argument_groups, - subcommands=parser_info.subcommands, - subcommand_dest=parser_info.subcommand_dest, - ) - if "nosubcommands" in self.options: - parser_info = parser_info.__class__( - prog=parser_info.prog, - usage=parser_info.usage, - bare_usage=parser_info.bare_usage, - description=parser_info.description, - epilog=parser_info.epilog, - argument_groups=parser_info.argument_groups, - subcommands=None, - subcommand_dest=None, - ) - - # Render to nodes - renderer = ArgparseRenderer(config=config, state=self.state) - return t.cast(list[nodes.Node], renderer.render(parser_info)) - - def _build_render_config(self) -> RenderConfig: - """Build RenderConfig from directive and Sphinx config options. - - Returns - ------- - RenderConfig - Configuration for the renderer. - """ - # Start with Sphinx config defaults - config = RenderConfig.from_sphinx_config(self.config) - - # Override with directive options - # Handle both new-style and sphinx-argparse compatibility options - if "no-defaults" in self.options or "nodefault" in self.options: - config.show_defaults = False - if "no-choices" in self.options: - config.show_choices = False - if "no-types" in self.options: - config.show_types = False - - return config - - def _navigate_to_subparser( - self, parser: argparse.ArgumentParser, path: str - ) -> argparse.ArgumentParser | None: - """Navigate to a nested subparser by path. - - Parameters - ---------- - parser : argparse.ArgumentParser - The root parser. - path : str - Space-separated path to the subparser (e.g., "sync pull"). - - Returns - ------- - argparse.ArgumentParser | None - The subparser, or None if not found. - """ - import argparse as argparse_module - - current = parser - for name in path.split(): - # Find subparsers action - subparser_action = None - for action in current._actions: - if isinstance(action, argparse_module._SubParsersAction): - subparser_action = action - break - - if subparser_action is None: - return None - - # Find the named subparser - choices = subparser_action.choices or {} - if name not in choices: - return None - - current = choices[name] - - return current diff --git a/docs/_ext/sphinx_argparse_neo/nodes.py b/docs/_ext/sphinx_argparse_neo/nodes.py deleted file mode 100644 index 468b5876a5..0000000000 --- a/docs/_ext/sphinx_argparse_neo/nodes.py +++ /dev/null @@ -1,647 +0,0 @@ -"""Custom docutils node types for argparse documentation. - -This module defines custom node types that represent the structure of -CLI documentation, along with HTML visitor functions for rendering. -""" - -from __future__ import annotations - -import typing as t - -from docutils import nodes - -if t.TYPE_CHECKING: - from sphinx.writers.html5 import HTML5Translator - -# Import the lexer - use absolute import from parent package -import pathlib -import sys - -# Add parent directory to path for lexer import -_ext_dir = pathlib.Path(__file__).parent.parent -if str(_ext_dir) not in sys.path: - sys.path.insert(0, str(_ext_dir)) - -from argparse_lexer import ArgparseUsageLexer # noqa: E402 -from sphinx_argparse_neo.utils import strip_ansi # noqa: E402 - - -def _generate_argument_id(names: list[str], id_prefix: str = "") -> str: - """Generate unique ID for an argument based on its names. - - Creates a slug-style ID suitable for HTML anchors by: - 1. Stripping leading dashes from option names - 2. Joining multiple names with hyphens - 3. Prepending optional prefix for namespace isolation - - Parameters - ---------- - names : list[str] - List of argument names (e.g., ["-L", "--socket-name"]). - id_prefix : str - Optional prefix for uniqueness (e.g., "shell" -> "shell-L-socket-name"). - - Returns - ------- - str - A slug-style ID suitable for HTML anchors. - - Examples - -------- - >>> _generate_argument_id(["-L"]) - 'L' - >>> _generate_argument_id(["--help"]) - 'help' - >>> _generate_argument_id(["-v", "--verbose"]) - 'v-verbose' - >>> _generate_argument_id(["-L"], "shell") - 'shell-L' - >>> _generate_argument_id(["filename"]) - 'filename' - >>> _generate_argument_id([]) - '' - """ - clean_names = [name.lstrip("-") for name in names if name.lstrip("-")] - if not clean_names: - return "" - name_part = "-".join(clean_names) - return f"{id_prefix}-{name_part}" if id_prefix else name_part - - -def _token_to_css_class(token_type: t.Any) -> str: - """Map a Pygments token type to its CSS class abbreviation. - - Pygments uses hierarchical token names like Token.Name.Attribute. - These map to CSS classes using abbreviations of the last two parts: - - Token.Name.Attribute → 'na' (Name.Attribute) - - Token.Generic.Heading → 'gh' (Generic.Heading) - - Token.Punctuation → 'p' (just Punctuation) - - Parameters - ---------- - token_type : Any - A Pygments token type (from pygments.token). - - Returns - ------- - str - CSS class abbreviation, or empty string if not mappable. - - Examples - -------- - >>> from pygments.token import Token - >>> _token_to_css_class(Token.Name.Attribute) - 'na' - >>> _token_to_css_class(Token.Generic.Heading) - 'gh' - >>> _token_to_css_class(Token.Punctuation) - 'p' - >>> _token_to_css_class(Token.Text.Whitespace) - 'tw' - """ - type_str = str(token_type) - # Token string looks like "Token.Name.Attribute" or "Token.Punctuation" - parts = type_str.split(".") - - if len(parts) >= 3: - # Token.Name.Attribute -> "na" (first char of each of last two parts) - return parts[-2][0].lower() + parts[-1][0].lower() - elif len(parts) == 2: - # Token.Punctuation -> "p" (first char of last part) - return parts[-1][0].lower() - return "" - - -def _highlight_usage(usage_text: str, encode: t.Callable[[str], str]) -> str: - """Tokenize usage text and wrap tokens in highlighted span elements. - - Uses ArgparseUsageLexer to tokenize the usage string, then wraps each - token in a with the appropriate CSS class for styling. - - Parameters - ---------- - usage_text : str - The usage string to highlight (should include "usage: " prefix). - encode : Callable[[str], str] - HTML encoding function (typically translator.encode). - - Returns - ------- - str - HTML string with tokens wrapped in styled elements. - - Examples - -------- - >>> def mock_encode(s: str) -> str: - ... return s.replace("&", "&").replace("<", "<") - >>> html = _highlight_usage("usage: cmd [-h]", mock_encode) - >>> 'usage:' in html - True - >>> 'cmd' in html - True - >>> '-h' in html - True - """ - lexer = ArgparseUsageLexer() - parts: list[str] = [] - - for tok_type, tok_value in lexer.get_tokens(usage_text): - if not tok_value: - continue - - css_class = _token_to_css_class(tok_type) - escaped = encode(tok_value) - type_str = str(tok_type).lower() - - # Skip wrapping for whitespace and plain text tokens - if css_class and "whitespace" not in type_str and "text" not in type_str: - parts.append(f'{escaped}') - else: - parts.append(escaped) - - return "".join(parts) - - -def _highlight_argument_names( - names: list[str], metavar: str | None, encode: t.Callable[[str], str] -) -> str: - """Highlight argument names and metavar with appropriate CSS classes. - - Short options (-h) get class 'na' (Name.Attribute). - Long options (--help) get class 'nt' (Name.Tag). - Positional arguments get class 'nl' (Name.Label). - Metavars get class 'nv' (Name.Variable). - - Parameters - ---------- - names : list[str] - List of argument names (e.g., ["-v", "--verbose"]). - metavar : str | None - Optional metavar (e.g., "FILE", "PATH"). - encode : Callable[[str], str] - HTML encoding function. - - Returns - ------- - str - HTML string with highlighted argument signature. - - Examples - -------- - >>> def mock_encode(s: str) -> str: - ... return s - >>> html = _highlight_argument_names(["-h", "--help"], None, mock_encode) - >>> '-h' in html - True - >>> '--help' in html - True - >>> html = _highlight_argument_names(["--output"], "FILE", mock_encode) - >>> 'FILE' in html - True - >>> html = _highlight_argument_names(["sync"], None, mock_encode) - >>> 'sync' in html - True - """ - sig_parts: list[str] = [] - - for name in names: - escaped = encode(name) - if name.startswith("--"): - sig_parts.append(f'{escaped}') - elif name.startswith("-"): - sig_parts.append(f'{escaped}') - else: - # Positional argument or subcommand - sig_parts.append(f'{escaped}') - - result = ", ".join(sig_parts) - - if metavar: - escaped_metavar = encode(metavar) - result = f'{result} {escaped_metavar}' - - return result - - -class argparse_program(nodes.General, nodes.Element): - """Root node for an argparse program documentation block. - - Attributes - ---------- - prog : str - The program name. - - Examples - -------- - >>> node = argparse_program() - >>> node["prog"] = "myapp" - >>> node["prog"] - 'myapp' - """ - - pass - - -class argparse_usage(nodes.General, nodes.Element): - """Node for displaying program usage. - - Contains the usage string as a literal block. - - Examples - -------- - >>> node = argparse_usage() - >>> node["usage"] = "myapp [-h] [--verbose] command" - >>> node["usage"] - 'myapp [-h] [--verbose] command' - """ - - pass - - -class argparse_group(nodes.General, nodes.Element): - """Node for an argument group (positional, optional, or custom). - - Attributes - ---------- - title : str - The group title. - description : str | None - Optional group description. - - Examples - -------- - >>> node = argparse_group() - >>> node["title"] = "Output Options" - >>> node["title"] - 'Output Options' - """ - - pass - - -class argparse_argument(nodes.Part, nodes.Element): - """Node for a single CLI argument. - - Attributes - ---------- - names : list[str] - Argument names/flags. - help : str | None - Help text. - default : str | None - Default value string. - choices : list[str] | None - Available choices. - required : bool - Whether the argument is required. - metavar : str | None - Metavar for display. - - Examples - -------- - >>> node = argparse_argument() - >>> node["names"] = ["-v", "--verbose"] - >>> node["names"] - ['-v', '--verbose'] - """ - - pass - - -class argparse_subcommands(nodes.General, nodes.Element): - """Container node for subcommands section. - - Examples - -------- - >>> node = argparse_subcommands() - >>> node["title"] = "Commands" - >>> node["title"] - 'Commands' - """ - - pass - - -class argparse_subcommand(nodes.General, nodes.Element): - """Node for a single subcommand. - - Attributes - ---------- - name : str - Subcommand name. - aliases : list[str] - Subcommand aliases. - help : str | None - Subcommand help text. - - Examples - -------- - >>> node = argparse_subcommand() - >>> node["name"] = "sync" - >>> node["aliases"] = ["s"] - >>> node["name"] - 'sync' - """ - - pass - - -# HTML Visitor Functions - - -def visit_argparse_program_html(self: HTML5Translator, node: argparse_program) -> None: - """Visit argparse_program node - start program container. - - Parameters - ---------- - self : HTML5Translator - The Sphinx HTML translator. - node : argparse_program - The program node being visited. - """ - prog = node.get("prog", "") - self.body.append(f'
\n') - - -def depart_argparse_program_html(self: HTML5Translator, node: argparse_program) -> None: - """Depart argparse_program node - close program container. - - Parameters - ---------- - self : HTML5Translator - The Sphinx HTML translator. - node : argparse_program - The program node being departed. - """ - self.body.append("
\n") - - -def visit_argparse_usage_html(self: HTML5Translator, node: argparse_usage) -> None: - """Visit argparse_usage node - render usage block with syntax highlighting. - - The usage text is tokenized using ArgparseUsageLexer and wrapped in - styled elements for semantic highlighting of options, metavars, - commands, and punctuation. - - Parameters - ---------- - self : HTML5Translator - The Sphinx HTML translator. - node : argparse_usage - The usage node being visited. - """ - usage = strip_ansi(node.get("usage", "")) - # Add both argparse-usage class and highlight class for CSS targeting - self.body.append('
')
-    # Prepend "usage: " and highlight the full usage string
-    highlighted = _highlight_usage(f"usage: {usage}", self.encode)
-    self.body.append(highlighted)
-
-
-def depart_argparse_usage_html(self: HTML5Translator, node: argparse_usage) -> None:
-    """Depart argparse_usage node - close usage block.
-
-    Parameters
-    ----------
-    self : HTML5Translator
-        The Sphinx HTML translator.
-    node : argparse_usage
-        The usage node being departed.
-    """
-    self.body.append("
\n") - - -def visit_argparse_group_html(self: HTML5Translator, node: argparse_group) -> None: - """Visit argparse_group node - start argument group. - - The title is now rendered by the parent section node, so this visitor - only handles the group container and description. - - Parameters - ---------- - self : HTML5Translator - The Sphinx HTML translator. - node : argparse_group - The group node being visited. - """ - title = node.get("title", "") - group_id = title.lower().replace(" ", "-") if title else "arguments" - self.body.append(f'
\n') - # Title rendering removed - parent section now provides the heading - description = node.get("description") - if description: - self.body.append( - f'

{self.encode(description)}

\n' - ) - self.body.append('
\n') - - -def depart_argparse_group_html(self: HTML5Translator, node: argparse_group) -> None: - """Depart argparse_group node - close argument group. - - Parameters - ---------- - self : HTML5Translator - The Sphinx HTML translator. - node : argparse_group - The group node being departed. - """ - self.body.append("
\n") - self.body.append("
\n") - - -def visit_argparse_argument_html( - self: HTML5Translator, node: argparse_argument -) -> None: - """Visit argparse_argument node - render argument entry with highlighting. - - Argument names are highlighted with semantic CSS classes: - - Short options (-h) get class 'na' (Name.Attribute) - - Long options (--help) get class 'nt' (Name.Tag) - - Positional arguments get class 'nl' (Name.Label) - - Metavars get class 'nv' (Name.Variable) - - The argument is wrapped in a container div with a unique ID for linking. - A headerlink anchor (¶) is added for direct navigation. - - Parameters - ---------- - self : HTML5Translator - The Sphinx HTML translator. - node : argparse_argument - The argument node being visited. - """ - names: list[str] = node.get("names", []) - metavar = node.get("metavar") - id_prefix: str = node.get("id_prefix", "") - - # Generate unique ID for this argument - arg_id = _generate_argument_id(names, id_prefix) - - # Open wrapper div with ID for linking - if arg_id: - self.body.append(f'
\n') - else: - self.body.append('
\n') - - # Build the argument signature with syntax highlighting - highlighted_sig = _highlight_argument_names(names, metavar, self.encode) - - # Add headerlink anchor inside dt for navigation - headerlink = "" - if arg_id: - headerlink = f'' - - self.body.append( - f'
{highlighted_sig}{headerlink}
\n' - ) - self.body.append('
') - - # Add help text - help_text = node.get("help") - if help_text: - self.body.append(f"

{self.encode(help_text)}

") - - -def depart_argparse_argument_html( - self: HTML5Translator, node: argparse_argument -) -> None: - """Depart argparse_argument node - close argument entry. - - Adds default, choices, and type information if present. - Default values are wrapped in ```` for styled display. - - Parameters - ---------- - self : HTML5Translator - The Sphinx HTML translator. - node : argparse_argument - The argument node being departed. - """ - # Build metadata as definition list items - default = node.get("default_string") - choices = node.get("choices") - type_name = node.get("type_name") - required = node.get("required", False) - - if default is not None or choices or type_name or required: - self.body.append('
\n') - - if default is not None: - self.body.append('
') - self.body.append('
Default
') - self.body.append( - f'
' - f'{self.encode(default)}
' - ) - self.body.append("
\n") - - if type_name: - self.body.append('
') - self.body.append('
Type
') - self.body.append( - f'
' - f'{self.encode(type_name)}
' - ) - self.body.append("
\n") - - if choices: - choices_str = ", ".join(str(c) for c in choices) - self.body.append('
') - self.body.append('
Choices
') - self.body.append( - f'
{self.encode(choices_str)}
' - ) - self.body.append("
\n") - - if required: - self.body.append('
Required
\n') - - self.body.append("
\n") - - self.body.append("
\n") - # Close wrapper div - self.body.append("
\n") - - -def visit_argparse_subcommands_html( - self: HTML5Translator, node: argparse_subcommands -) -> None: - """Visit argparse_subcommands node - start subcommands section. - - Parameters - ---------- - self : HTML5Translator - The Sphinx HTML translator. - node : argparse_subcommands - The subcommands node being visited. - """ - title = node.get("title", "Sub-commands") - self.body.append('
\n') - self.body.append( - f'

{self.encode(title)}

\n' - ) - - -def depart_argparse_subcommands_html( - self: HTML5Translator, node: argparse_subcommands -) -> None: - """Depart argparse_subcommands node - close subcommands section. - - Parameters - ---------- - self : HTML5Translator - The Sphinx HTML translator. - node : argparse_subcommands - The subcommands node being departed. - """ - self.body.append("
\n") - - -def visit_argparse_subcommand_html( - self: HTML5Translator, node: argparse_subcommand -) -> None: - """Visit argparse_subcommand node - start subcommand entry. - - Parameters - ---------- - self : HTML5Translator - The Sphinx HTML translator. - node : argparse_subcommand - The subcommand node being visited. - """ - name = node.get("name", "") - aliases: list[str] = node.get("aliases", []) - - self.body.append(f'
\n') - - # Subcommand header - header = name - if aliases: - alias_str = ", ".join(aliases) - header = f"{name} ({alias_str})" - self.body.append( - f'

{self.encode(header)}

\n' - ) - - # Help text - help_text = node.get("help") - if help_text: - self.body.append( - f'

{self.encode(help_text)}

\n' - ) - - -def depart_argparse_subcommand_html( - self: HTML5Translator, node: argparse_subcommand -) -> None: - """Depart argparse_subcommand node - close subcommand entry. - - Parameters - ---------- - self : HTML5Translator - The Sphinx HTML translator. - node : argparse_subcommand - The subcommand node being departed. - """ - self.body.append("
\n") diff --git a/docs/_ext/sphinx_argparse_neo/parser.py b/docs/_ext/sphinx_argparse_neo/parser.py deleted file mode 100644 index f3a6db44af..0000000000 --- a/docs/_ext/sphinx_argparse_neo/parser.py +++ /dev/null @@ -1,659 +0,0 @@ -"""Argparse introspection - extract structured data from ArgumentParser. - -This module provides dataclasses and functions to introspect argparse -ArgumentParser instances and convert them into structured data suitable -for documentation rendering. -""" - -from __future__ import annotations - -import argparse -import dataclasses -import typing as t - -from sphinx_argparse_neo.utils import strip_ansi - -# Sentinel for "no default" (distinct from None which is a valid default) -NO_DEFAULT = object() - - -@dataclasses.dataclass -class ArgumentInfo: - """Represents a single CLI argument. - - Examples - -------- - >>> info = ArgumentInfo( - ... names=["-v", "--verbose"], - ... help="Enable verbose output", - ... default=False, - ... default_string="False", - ... choices=None, - ... required=False, - ... metavar=None, - ... nargs=None, - ... action="store_true", - ... type_name=None, - ... const=True, - ... dest="verbose", - ... ) - >>> info.names - ['-v', '--verbose'] - >>> info.is_positional - False - """ - - names: list[str] - help: str | None - default: t.Any - default_string: str | None - choices: list[t.Any] | None - required: bool - metavar: str | None - nargs: str | int | None - action: str - type_name: str | None - const: t.Any - dest: str - - @property - def is_positional(self) -> bool: - """Return True if this is a positional argument. - - Examples - -------- - >>> ArgumentInfo( - ... names=["filename"], - ... help=None, - ... default=None, - ... default_string=None, - ... choices=None, - ... required=True, - ... metavar=None, - ... nargs=None, - ... action="store", - ... type_name=None, - ... const=None, - ... dest="filename", - ... ).is_positional - True - >>> ArgumentInfo( - ... names=["-f", "--file"], - ... help=None, - ... default=None, - ... default_string=None, - ... choices=None, - ... required=False, - ... metavar=None, - ... nargs=None, - ... action="store", - ... type_name=None, - ... const=None, - ... dest="file", - ... ).is_positional - False - """ - return bool(self.names) and not self.names[0].startswith("-") - - -@dataclasses.dataclass -class MutuallyExclusiveGroup: - """Arguments that cannot be used together. - - Examples - -------- - >>> group = MutuallyExclusiveGroup(arguments=[], required=True) - >>> group.required - True - """ - - arguments: list[ArgumentInfo] - required: bool - - -@dataclasses.dataclass -class ArgumentGroup: - """Named group of arguments. - - Examples - -------- - >>> group = ArgumentGroup( - ... title="Output Options", - ... description="Control output format", - ... arguments=[], - ... mutually_exclusive=[], - ... ) - >>> group.title - 'Output Options' - """ - - title: str - description: str | None - arguments: list[ArgumentInfo] - mutually_exclusive: list[MutuallyExclusiveGroup] - - -@dataclasses.dataclass -class SubcommandInfo: - """A subparser/subcommand. - - Examples - -------- - >>> sub = SubcommandInfo( - ... name="sync", - ... aliases=["s"], - ... help="Synchronize repositories", - ... parser=None, # type: ignore[arg-type] - ... ) - >>> sub.aliases - ['s'] - """ - - name: str - aliases: list[str] - help: str | None - parser: ParserInfo # Recursive reference - - -@dataclasses.dataclass -class ParserInfo: - """Complete parsed ArgumentParser. - - Examples - -------- - >>> info = ParserInfo( - ... prog="myapp", - ... usage=None, - ... bare_usage="myapp [-h] command", - ... description="My application", - ... epilog=None, - ... argument_groups=[], - ... subcommands=None, - ... subcommand_dest=None, - ... ) - >>> info.prog - 'myapp' - """ - - prog: str - usage: str | None - bare_usage: str - description: str | None - epilog: str | None - argument_groups: list[ArgumentGroup] - subcommands: list[SubcommandInfo] | None - subcommand_dest: str | None - - -def _format_default(default: t.Any) -> str | None: - """Format a default value for display. - - Parameters - ---------- - default : t.Any - The default value to format. - - Returns - ------- - str | None - Formatted string representation, or None if suppressed/unset. - - Examples - -------- - >>> _format_default(None) - 'None' - >>> _format_default("hello") - 'hello' - >>> _format_default(42) - '42' - >>> _format_default(argparse.SUPPRESS) is None - True - >>> _format_default([1, 2, 3]) - '[1, 2, 3]' - """ - if default is argparse.SUPPRESS: - return None - if default is None: - return "None" - if isinstance(default, str): - return default - return repr(default) - - -def _get_type_name(action: argparse.Action) -> str | None: - """Extract the type name from an action. - - Parameters - ---------- - action : argparse.Action - The argparse action to inspect. - - Returns - ------- - str | None - The type name, or None if no type is specified. - - Examples - -------- - >>> parser = argparse.ArgumentParser() - >>> action = parser.add_argument("--count", type=int) - >>> _get_type_name(action) - 'int' - >>> action2 = parser.add_argument("--name") - >>> _get_type_name(action2) is None - True - """ - if action.type is None: - return None - if hasattr(action.type, "__name__"): - return action.type.__name__ - return str(action.type) - - -def _get_action_name(action: argparse.Action) -> str: - """Get the action type name. - - Parameters - ---------- - action : argparse.Action - The argparse action to inspect. - - Returns - ------- - str - The action type name. - - Examples - -------- - >>> parser = argparse.ArgumentParser() - >>> action = parser.add_argument("--verbose", action="store_true") - >>> _get_action_name(action) - 'store_true' - >>> action2 = parser.add_argument("--file") - >>> _get_action_name(action2) - 'store' - """ - # Map action classes to their string names - action_class = type(action).__name__ - action_map = { - "_StoreAction": "store", - "_StoreTrueAction": "store_true", - "_StoreFalseAction": "store_false", - "_StoreConstAction": "store_const", - "_AppendAction": "append", - "_AppendConstAction": "append_const", - "_CountAction": "count", - "_HelpAction": "help", - "_VersionAction": "version", - "_ExtendAction": "extend", - "BooleanOptionalAction": "boolean_optional", - } - return action_map.get(action_class, action_class.lower()) - - -def _extract_argument(action: argparse.Action) -> ArgumentInfo: - """Extract ArgumentInfo from an argparse Action. - - Parameters - ---------- - action : argparse.Action - The argparse action to extract information from. - - Returns - ------- - ArgumentInfo - Structured argument information. - - Examples - -------- - >>> parser = argparse.ArgumentParser() - >>> action = parser.add_argument( - ... "-v", "--verbose", - ... action="store_true", - ... help="Enable verbose mode", - ... ) - >>> info = _extract_argument(action) - >>> info.names - ['-v', '--verbose'] - >>> info.action - 'store_true' - """ - # Determine names - option_strings for optionals, dest for positionals - names = list(action.option_strings) if action.option_strings else [action.dest] - - # Determine if required - required = action.required if hasattr(action, "required") else False - # Positional arguments are required by default (unless nargs makes them optional) - if not action.option_strings: - required = action.nargs not in ("?", "*", argparse.REMAINDER) - - # Format metavar - metavar = action.metavar - if isinstance(metavar, tuple): - metavar = " ".join(metavar) - - # Handle default - default = action.default - default_string = _format_default(default) - - return ArgumentInfo( - names=names, - help=action.help if action.help != argparse.SUPPRESS else None, - default=default if default is not argparse.SUPPRESS else NO_DEFAULT, - default_string=default_string, - choices=list(action.choices) if action.choices else None, - required=required, - metavar=metavar, - nargs=action.nargs, - action=_get_action_name(action), - type_name=_get_type_name(action), - const=action.const, - dest=action.dest, - ) - - -def _extract_mutex_groups( - parser: argparse.ArgumentParser, -) -> dict[int, MutuallyExclusiveGroup]: - """Extract mutually exclusive groups from a parser. - - Parameters - ---------- - parser : argparse.ArgumentParser - The parser to extract from. - - Returns - ------- - dict[int, MutuallyExclusiveGroup] - Mapping from action id to the MutuallyExclusiveGroup it belongs to. - - Examples - -------- - Extract mutually exclusive groups from a parser with one group: - - >>> parser = argparse.ArgumentParser() - >>> group = parser.add_mutually_exclusive_group() - >>> _ = group.add_argument("--foo", help="Use foo") - >>> _ = group.add_argument("--bar", help="Use bar") - >>> mutex_map = _extract_mutex_groups(parser) - >>> len(mutex_map) - 2 - - Each action in the group maps to the same MutuallyExclusiveGroup: - - >>> values = list(mutex_map.values()) - >>> values[0] is values[1] - True - >>> len(values[0].arguments) - 2 - >>> [arg.names[0] for arg in values[0].arguments] - ['--foo', '--bar'] - - A parser without mutex groups returns an empty mapping: - - >>> parser2 = argparse.ArgumentParser() - >>> _ = parser2.add_argument("--verbose") - >>> _extract_mutex_groups(parser2) - {} - """ - mutex_map: dict[int, MutuallyExclusiveGroup] = {} - - for mutex_group in parser._mutually_exclusive_groups: - group_info = MutuallyExclusiveGroup( - arguments=[ - _extract_argument(action) - for action in mutex_group._group_actions - if action.help != argparse.SUPPRESS - ], - required=mutex_group.required, - ) - for action in mutex_group._group_actions: - mutex_map[id(action)] = group_info - - return mutex_map - - -def _extract_argument_groups( - parser: argparse.ArgumentParser, - hide_suppressed: bool = True, -) -> list[ArgumentGroup]: - """Extract argument groups from a parser. - - Parameters - ---------- - parser : argparse.ArgumentParser - The parser to extract from. - hide_suppressed : bool - Whether to hide arguments with SUPPRESS help. - - Returns - ------- - list[ArgumentGroup] - List of argument groups. - - Examples - -------- - >>> parser = argparse.ArgumentParser(description="Test") - >>> _ = parser.add_argument("filename", help="Input file") - >>> _ = parser.add_argument("-v", "--verbose", action="store_true") - >>> groups = _extract_argument_groups(parser) - >>> len(groups) >= 2 # positional and optional groups - True - """ - mutex_map = _extract_mutex_groups(parser) - seen_mutex: set[int] = set() - groups: list[ArgumentGroup] = [] - - for group in parser._action_groups: - arguments: list[ArgumentInfo] = [] - mutex_groups: list[MutuallyExclusiveGroup] = [] - - for action in group._group_actions: - # Skip help action and suppressed actions - if isinstance(action, argparse._HelpAction): - continue - if hide_suppressed and action.help == argparse.SUPPRESS: - continue - # Skip subparser actions - handled separately - if isinstance(action, argparse._SubParsersAction): - continue - - # Check if this action is in a mutex group - if id(action) in mutex_map: - mutex_info = mutex_map[id(action)] - mutex_id = id(mutex_info) - if mutex_id not in seen_mutex: - seen_mutex.add(mutex_id) - mutex_groups.append(mutex_info) - else: - arguments.append(_extract_argument(action)) - - # Skip empty groups - if not arguments and not mutex_groups: - continue - - groups.append( - ArgumentGroup( - title=group.title or "", - description=group.description, - arguments=arguments, - mutually_exclusive=mutex_groups, - ) - ) - - return groups - - -def _extract_subcommands( - parser: argparse.ArgumentParser, - hide_suppressed: bool = True, -) -> tuple[list[SubcommandInfo] | None, str | None]: - """Extract subcommands from a parser. - - Parameters - ---------- - parser : argparse.ArgumentParser - The parser to extract from. - hide_suppressed : bool - Whether to hide subcommands with SUPPRESS help. - - Returns - ------- - tuple[list[SubcommandInfo] | None, str | None] - Tuple of (subcommands list, destination variable name). - - Examples - -------- - >>> parser = argparse.ArgumentParser() - >>> subparsers = parser.add_subparsers(dest="command") - >>> _ = subparsers.add_parser("sync", help="Sync repos") - >>> _ = subparsers.add_parser("add", help="Add repo") - >>> subs, dest = _extract_subcommands(parser) - >>> dest - 'command' - >>> len(subs) - 2 - """ - for action in parser._actions: - if isinstance(action, argparse._SubParsersAction): - subcommands: list[SubcommandInfo] = [] - - # Get the choices (subparsers) - choices = action.choices or {} - - # Build reverse mapping of aliases - # action._parser_class might have name_parser_map with aliases - alias_map: dict[str, list[str]] = {} - seen_parsers: dict[int, str] = {} - - for name, subparser in choices.items(): - parser_id = id(subparser) - if parser_id in seen_parsers: - # This is an alias - primary = seen_parsers[parser_id] - if primary not in alias_map: - alias_map[primary] = [] - alias_map[primary].append(name) - else: - seen_parsers[parser_id] = name - - # Now extract subcommand info - processed: set[int] = set() - for name, subparser in choices.items(): - parser_id = id(subparser) - if parser_id in processed: - continue - processed.add(parser_id) - - # Get help text - help_text: str | None = None - if hasattr(action, "_choices_actions"): - for choice_action in action._choices_actions: - if choice_action.dest == name: - help_text = choice_action.help - break - - if hide_suppressed and help_text == argparse.SUPPRESS: - continue - - # Recursively extract parser info - sub_info = extract_parser(subparser, hide_suppressed=hide_suppressed) - - subcommands.append( - SubcommandInfo( - name=name, - aliases=alias_map.get(name, []), - help=help_text, - parser=sub_info, - ) - ) - - return subcommands, action.dest - - return None, None - - -def _generate_usage(parser: argparse.ArgumentParser) -> str: - """Generate the usage string for a parser. - - Parameters - ---------- - parser : argparse.ArgumentParser - The parser to generate usage for. - - Returns - ------- - str - The bare usage string (without "usage: " prefix). - - Examples - -------- - >>> parser = argparse.ArgumentParser(prog="myapp") - >>> _ = parser.add_argument("-v", "--verbose", action="store_true") - >>> usage = _generate_usage(parser) - >>> "myapp" in usage - True - """ - # Use argparse's built-in formatter to generate usage - formatter = parser._get_formatter() - formatter.add_usage( - parser.usage, parser._actions, parser._mutually_exclusive_groups - ) - usage: str = formatter.format_help().strip() - - # Strip ANSI codes before checking prefix (handles FORCE_COLOR edge case) - usage = strip_ansi(usage) - - # Remove "usage: " prefix if present - if usage.lower().startswith("usage:"): - usage = usage[6:].strip() - - return usage - - -def extract_parser( - parser: argparse.ArgumentParser, - hide_suppressed: bool = True, -) -> ParserInfo: - """Extract complete parser information. - - Parameters - ---------- - parser : argparse.ArgumentParser - The parser to extract information from. - hide_suppressed : bool - Whether to hide arguments/subcommands with SUPPRESS help. - - Returns - ------- - ParserInfo - Complete structured parser information. - - Examples - -------- - >>> parser = argparse.ArgumentParser( - ... prog="myapp", - ... description="My application", - ... ) - >>> _ = parser.add_argument("filename", help="Input file") - >>> _ = parser.add_argument("-v", "--verbose", action="store_true") - >>> info = extract_parser(parser) - >>> info.prog - 'myapp' - >>> info.description - 'My application' - >>> len(info.argument_groups) >= 1 - True - """ - subcommands, subcommand_dest = _extract_subcommands(parser, hide_suppressed) - - return ParserInfo( - prog=parser.prog, - usage=parser.usage, - bare_usage=_generate_usage(parser), - description=parser.description, - epilog=parser.epilog, - argument_groups=_extract_argument_groups(parser, hide_suppressed), - subcommands=subcommands, - subcommand_dest=subcommand_dest, - ) diff --git a/docs/_ext/sphinx_argparse_neo/renderer.py b/docs/_ext/sphinx_argparse_neo/renderer.py deleted file mode 100644 index f6c313f9f1..0000000000 --- a/docs/_ext/sphinx_argparse_neo/renderer.py +++ /dev/null @@ -1,604 +0,0 @@ -"""Renderer - convert ParserInfo to docutils nodes. - -This module provides the ArgparseRenderer class that transforms -structured parser information into docutils nodes for documentation. -""" - -from __future__ import annotations - -import dataclasses -import typing as t - -from docutils import nodes -from docutils.statemachine import StringList -from sphinx_argparse_neo.nodes import ( - argparse_argument, - argparse_group, - argparse_program, - argparse_subcommand, - argparse_subcommands, - argparse_usage, -) -from sphinx_argparse_neo.parser import ( - ArgumentGroup, - ArgumentInfo, - MutuallyExclusiveGroup, - ParserInfo, - SubcommandInfo, -) -from sphinx_argparse_neo.utils import escape_rst_emphasis - -if t.TYPE_CHECKING: - from docutils.parsers.rst.states import RSTState - from sphinx.config import Config - - -@dataclasses.dataclass -class RenderConfig: - """Configuration for the renderer. - - Examples - -------- - >>> config = RenderConfig() - >>> config.show_defaults - True - >>> config.group_title_prefix - '' - """ - - group_title_prefix: str = "" - show_defaults: bool = True - show_choices: bool = True - show_types: bool = True - - @classmethod - def from_sphinx_config(cls, config: Config) -> RenderConfig: - """Create RenderConfig from Sphinx configuration. - - Parameters - ---------- - config : Config - Sphinx configuration object. - - Returns - ------- - RenderConfig - Render configuration based on Sphinx config values. - """ - return cls( - group_title_prefix=getattr(config, "argparse_group_title_prefix", ""), - show_defaults=getattr(config, "argparse_show_defaults", True), - show_choices=getattr(config, "argparse_show_choices", True), - show_types=getattr(config, "argparse_show_types", True), - ) - - -class ArgparseRenderer: - """Render ParserInfo to docutils nodes. - - This class can be subclassed to customize rendering behavior. - Override individual methods to change how specific elements are rendered. - - Parameters - ---------- - config : RenderConfig - Rendering configuration. - state : RSTState | None - RST state for parsing nested RST content. - - Examples - -------- - >>> from sphinx_argparse_neo.parser import ParserInfo - >>> config = RenderConfig() - >>> renderer = ArgparseRenderer(config) - >>> info = ParserInfo( - ... prog="myapp", - ... usage=None, - ... bare_usage="myapp [-h]", - ... description="My app", - ... epilog=None, - ... argument_groups=[], - ... subcommands=None, - ... subcommand_dest=None, - ... ) - >>> result = renderer.render(info) - >>> isinstance(result, list) - True - """ - - def __init__( - self, - config: RenderConfig | None = None, - state: RSTState | None = None, - ) -> None: - """Initialize the renderer.""" - self.config = config or RenderConfig() - self.state = state - - @staticmethod - def _extract_id_prefix(prog: str) -> str: - """Extract subcommand from prog for unique section IDs. - - Parameters - ---------- - prog : str - The program name, potentially with subcommand (e.g., "tmuxp load"). - - Returns - ------- - str - The subcommand part for use as ID prefix, or empty string if none. - - Examples - -------- - >>> ArgparseRenderer._extract_id_prefix("tmuxp load") - 'load' - >>> ArgparseRenderer._extract_id_prefix("tmuxp") - '' - >>> ArgparseRenderer._extract_id_prefix("vcspull sync") - 'sync' - >>> ArgparseRenderer._extract_id_prefix("myapp sub cmd") - 'sub-cmd' - """ - parts = prog.split() - if len(parts) <= 1: - return "" - # Join remaining parts with hyphen for multi-level subcommands - return "-".join(parts[1:]) - - def render(self, parser_info: ParserInfo) -> list[nodes.Node]: - """Render a complete parser to docutils nodes. - - Parameters - ---------- - parser_info : ParserInfo - The parsed parser information. - - Returns - ------- - list[nodes.Node] - List of docutils nodes representing the documentation. - - Note - ---- - Sections for Usage and argument groups are emitted as siblings of - argparse_program rather than children. This allows Sphinx's - TocTreeCollector to discover them for inclusion in the table of - contents. - - The rendered structure is: - - - argparse_program (description only, no "examples:" part) - - section#usage (h3 "Usage" with usage block) - - section#positional-arguments (h3) - - section#options (h3) - - The "examples:" definition list in descriptions is left for - argparse_exemplar.py to transform into a proper Examples section. - """ - result: list[nodes.Node] = [] - - # Create program container for description only - program_node = argparse_program() - program_node["prog"] = parser_info.prog - - # Add description (may contain "examples:" definition list for later - # transformation by argparse_exemplar.py) - if parser_info.description: - desc_nodes = self._parse_text(parser_info.description) - program_node.extend(desc_nodes) - - result.append(program_node) - - # Extract ID prefix from prog for unique section IDs - # e.g., "tmuxp load" -> "load", "myapp" -> "" - id_prefix = self._extract_id_prefix(parser_info.prog) - - # Add Usage section as sibling (for TOC visibility) - usage_section = self.render_usage_section(parser_info, id_prefix=id_prefix) - result.append(usage_section) - - # Add argument groups as sibling sections (for TOC visibility) - for group in parser_info.argument_groups: - group_section = self.render_group_section(group, id_prefix=id_prefix) - result.append(group_section) - - # Add subcommands - if parser_info.subcommands: - subcommands_node = self.render_subcommands(parser_info.subcommands) - result.append(subcommands_node) - - # Add epilog - if parser_info.epilog: - epilog_nodes = self._parse_text(parser_info.epilog) - result.extend(epilog_nodes) - - return self.post_process(result) - - def render_usage(self, parser_info: ParserInfo) -> argparse_usage: - """Render the usage block. - - Parameters - ---------- - parser_info : ParserInfo - The parser information. - - Returns - ------- - argparse_usage - Usage node. - """ - usage_node = argparse_usage() - usage_node["usage"] = parser_info.bare_usage - return usage_node - - def render_usage_section( - self, parser_info: ParserInfo, *, id_prefix: str = "" - ) -> nodes.section: - """Render usage as a section with heading for TOC visibility. - - Creates a proper section node with "Usage" heading containing the - usage block. This structure allows Sphinx's TocTreeCollector to - discover it for the table of contents. - - Parameters - ---------- - parser_info : ParserInfo - The parser information. - id_prefix : str - Optional prefix for the section ID (e.g., "load" -> "load-usage"). - Used to ensure unique IDs when multiple argparse directives exist - on the same page. - - Returns - ------- - nodes.section - Section node containing the usage block with a "Usage" heading. - - Examples - -------- - >>> from sphinx_argparse_neo.parser import ParserInfo - >>> renderer = ArgparseRenderer() - >>> info = ParserInfo( - ... prog="myapp", - ... usage=None, - ... bare_usage="myapp [-h] command", - ... description=None, - ... epilog=None, - ... argument_groups=[], - ... subcommands=None, - ... subcommand_dest=None, - ... ) - >>> section = renderer.render_usage_section(info) - >>> section["ids"] - ['usage'] - - With prefix for subcommand pages: - - >>> section = renderer.render_usage_section(info, id_prefix="load") - >>> section["ids"] - ['load-usage'] - >>> section.children[0].astext() - 'Usage' - """ - section_id = f"{id_prefix}-usage" if id_prefix else "usage" - section = nodes.section() - section["ids"] = [section_id] - section["names"] = [nodes.fully_normalize_name("Usage")] - section += nodes.title("Usage", "Usage") - - usage_node = argparse_usage() - usage_node["usage"] = parser_info.bare_usage - section += usage_node - - return section - - def render_group_section( - self, group: ArgumentGroup, *, id_prefix: str = "" - ) -> nodes.section: - """Render an argument group wrapped in a section for TOC visibility. - - Creates a proper section node with the group title as heading, - containing the argparse_group node. This structure allows Sphinx's - TocTreeCollector to discover it for the table of contents. - - Parameters - ---------- - group : ArgumentGroup - The argument group to render. - id_prefix : str - Optional prefix for the section ID (e.g., "load" -> "load-options"). - Used to ensure unique IDs when multiple argparse directives exist - on the same page. - - Returns - ------- - nodes.section - Section node containing the group for TOC discovery. - - Examples - -------- - >>> from sphinx_argparse_neo.parser import ArgumentGroup - >>> renderer = ArgparseRenderer() - >>> group = ArgumentGroup( - ... title="positional arguments", - ... description=None, - ... arguments=[], - ... mutually_exclusive=[], - ... ) - >>> section = renderer.render_group_section(group) - >>> section["ids"] - ['positional-arguments'] - - With prefix for subcommand pages: - - >>> section = renderer.render_group_section(group, id_prefix="load") - >>> section["ids"] - ['load-positional-arguments'] - >>> section.children[0].astext() - 'Positional Arguments' - """ - # Title case the group title for proper display - raw_title = group.title or "Arguments" - title = raw_title.title() # "positional arguments" -> "Positional Arguments" - - if self.config.group_title_prefix: - title = f"{self.config.group_title_prefix}{title}" - - # Generate section ID from title (with optional prefix for uniqueness) - base_id = title.lower().replace(" ", "-") - section_id = f"{id_prefix}-{base_id}" if id_prefix else base_id - - # Create section wrapper for TOC discovery - section = nodes.section() - section["ids"] = [section_id] - section["names"] = [nodes.fully_normalize_name(title)] - - # Add title for TOC - Sphinx's TocTreeCollector looks for this - section += nodes.title(title, title) - - # Create the styled group container (with empty title - section provides it) - # Pass id_prefix to render_group so arguments get unique IDs - group_node = self.render_group(group, include_title=False, id_prefix=id_prefix) - section += group_node - - return section - - def render_group( - self, - group: ArgumentGroup, - include_title: bool = True, - *, - id_prefix: str = "", - ) -> argparse_group: - """Render an argument group. - - Parameters - ---------- - group : ArgumentGroup - The argument group to render. - include_title : bool - Whether to include the title in the group node. When False, - the title is assumed to come from a parent section node. - Default is True for backwards compatibility. - id_prefix : str - Optional prefix for argument IDs (e.g., "shell" -> "shell-h"). - Used to ensure unique IDs when multiple argparse directives exist - on the same page. - - Returns - ------- - argparse_group - Group node containing argument nodes. - """ - group_node = argparse_group() - - if include_title: - title = group.title - if self.config.group_title_prefix: - title = f"{self.config.group_title_prefix}{title}" - group_node["title"] = title - else: - # Title provided by parent section - group_node["title"] = "" - - group_node["description"] = group.description - - # Add individual arguments - for arg in group.arguments: - arg_node = self.render_argument(arg, id_prefix=id_prefix) - group_node.append(arg_node) - - # Add mutually exclusive groups - for mutex in group.mutually_exclusive: - mutex_nodes = self.render_mutex_group(mutex, id_prefix=id_prefix) - group_node.extend(mutex_nodes) - - return group_node - - def render_argument( - self, arg: ArgumentInfo, *, id_prefix: str = "" - ) -> argparse_argument: - """Render a single argument. - - Parameters - ---------- - arg : ArgumentInfo - The argument to render. - id_prefix : str - Optional prefix for the argument ID (e.g., "shell" -> "shell-L"). - Used to ensure unique IDs when multiple argparse directives exist - on the same page. - - Returns - ------- - argparse_argument - Argument node. - """ - arg_node = argparse_argument() - arg_node["names"] = arg.names - arg_node["help"] = arg.help - arg_node["metavar"] = arg.metavar - arg_node["required"] = arg.required - arg_node["id_prefix"] = id_prefix - - if self.config.show_defaults: - arg_node["default_string"] = arg.default_string - - if self.config.show_choices: - arg_node["choices"] = arg.choices - - if self.config.show_types: - arg_node["type_name"] = arg.type_name - - return arg_node - - def render_mutex_group( - self, mutex: MutuallyExclusiveGroup, *, id_prefix: str = "" - ) -> list[argparse_argument]: - """Render a mutually exclusive group. - - Parameters - ---------- - mutex : MutuallyExclusiveGroup - The mutually exclusive group. - id_prefix : str - Optional prefix for argument IDs (e.g., "shell" -> "shell-h"). - - Returns - ------- - list[argparse_argument] - List of argument nodes with mutex indicator. - """ - result: list[argparse_argument] = [] - for arg in mutex.arguments: - arg_node = self.render_argument(arg, id_prefix=id_prefix) - # Mark as part of mutex group - arg_node["mutex"] = True - arg_node["mutex_required"] = mutex.required - result.append(arg_node) - return result - - def render_subcommands( - self, subcommands: list[SubcommandInfo] - ) -> argparse_subcommands: - """Render subcommands section. - - Parameters - ---------- - subcommands : list[SubcommandInfo] - List of subcommand information. - - Returns - ------- - argparse_subcommands - Subcommands container node. - """ - container = argparse_subcommands() - container["title"] = "Sub-commands" - - for subcmd in subcommands: - subcmd_node = self.render_subcommand(subcmd) - container.append(subcmd_node) - - return container - - def render_subcommand(self, subcmd: SubcommandInfo) -> argparse_subcommand: - """Render a single subcommand. - - Parameters - ---------- - subcmd : SubcommandInfo - The subcommand information. - - Returns - ------- - argparse_subcommand - Subcommand node, potentially containing nested parser content. - """ - subcmd_node = argparse_subcommand() - subcmd_node["name"] = subcmd.name - subcmd_node["aliases"] = subcmd.aliases - subcmd_node["help"] = subcmd.help - - # Recursively render the subcommand's parser - if subcmd.parser: - nested_nodes = self.render(subcmd.parser) - subcmd_node.extend(nested_nodes) - - return subcmd_node - - def post_process(self, result_nodes: list[nodes.Node]) -> list[nodes.Node]: - """Post-process the rendered nodes. - - Override this method to apply transformations after rendering. - - Parameters - ---------- - result_nodes : list[nodes.Node] - The rendered nodes. - - Returns - ------- - list[nodes.Node] - Post-processed nodes. - """ - return result_nodes - - def _parse_text(self, text: str) -> list[nodes.Node]: - """Parse text as RST or MyST content. - - Parameters - ---------- - text : str - Text to parse. - - Returns - ------- - list[nodes.Node] - Parsed docutils nodes. - """ - if not text: - return [] - - # Escape RST emphasis patterns before parsing (e.g., "django-*" -> "django-\*") - text = escape_rst_emphasis(text) - - if self.state is None: - # No state machine available, return as paragraph - para = nodes.paragraph(text=text) - return [para] - - # Use the state machine to parse RST - container = nodes.container() - self.state.nested_parse( - StringList(text.split("\n")), - 0, - container, - ) - return list(container.children) - - -def create_renderer( - config: RenderConfig | None = None, - state: RSTState | None = None, - renderer_class: type[ArgparseRenderer] | None = None, -) -> ArgparseRenderer: - """Create a renderer instance. - - Parameters - ---------- - config : RenderConfig | None - Rendering configuration. - state : RSTState | None - RST state for parsing. - renderer_class : type[ArgparseRenderer] | None - Custom renderer class to use. - - Returns - ------- - ArgparseRenderer - Configured renderer instance. - """ - cls = renderer_class or ArgparseRenderer - return cls(config=config, state=state) diff --git a/docs/_ext/sphinx_argparse_neo/utils.py b/docs/_ext/sphinx_argparse_neo/utils.py deleted file mode 100644 index 468b1961fa..0000000000 --- a/docs/_ext/sphinx_argparse_neo/utils.py +++ /dev/null @@ -1,78 +0,0 @@ -"""Text processing utilities for sphinx_argparse_neo. - -This module provides utilities for cleaning argparse output before rendering: -- strip_ansi: Remove ANSI escape codes (for when FORCE_COLOR is set) -""" - -from __future__ import annotations - -import re - -# ANSI escape code pattern - matches CSI sequences like \033[32m, \033[1;34m, etc. -_ANSI_RE = re.compile(r"\033\[[;?0-9]*[a-zA-Z]") - - -def strip_ansi(text: str) -> str: - r"""Remove ANSI escape codes from text. - - When FORCE_COLOR is set in the environment, argparse may include ANSI - escape codes in its output. This function removes them so the output - renders correctly in Sphinx documentation. - - Parameters - ---------- - text : str - Text potentially containing ANSI codes. - - Returns - ------- - str - Text with ANSI codes removed. - - Examples - -------- - >>> strip_ansi("plain text") - 'plain text' - >>> strip_ansi("\033[32mgreen\033[0m") - 'green' - >>> strip_ansi("\033[1;34mbold blue\033[0m") - 'bold blue' - """ - return _ANSI_RE.sub("", text) - - -# RST emphasis pattern: matches -* that would trigger inline emphasis errors. -# Pattern matches: non-whitespace/non-backslash char, followed by -*, NOT followed by -# another * (which would be strong emphasis **). -_RST_EMPHASIS_RE = re.compile(r"(?<=[^\s\\])-\*(?!\*)") - - -def escape_rst_emphasis(text: str) -> str: - r"""Escape asterisks that would trigger RST inline emphasis. - - RST interprets ``*text*`` as emphasis. When argparse help text contains - glob patterns like ``django-*``, the ``-*`` sequence triggers RST - "Inline emphasis start-string without end-string" warnings. - - This function escapes such asterisks to prevent RST parsing errors. - - Parameters - ---------- - text : str - Text potentially containing problematic asterisks. - - Returns - ------- - str - Text with asterisks escaped where needed. - - Examples - -------- - >>> escape_rst_emphasis('tmuxp load "my-*"') - 'tmuxp load "my-\\*"' - >>> escape_rst_emphasis("plain text") - 'plain text' - >>> escape_rst_emphasis("*emphasis* is ok") - '*emphasis* is ok' - """ - return _RST_EMPHASIS_RE.sub(r"-\*", text) diff --git a/docs/_static/css/argparse-highlight.css b/docs/_static/css/argparse-highlight.css deleted file mode 100644 index f232c71c8c..0000000000 --- a/docs/_static/css/argparse-highlight.css +++ /dev/null @@ -1,437 +0,0 @@ -/* - * Argparse/CLI Highlighting Styles - * - * Styles for CLI inline roles and argparse help output highlighting. - * Uses "One Dark" inspired color palette (Python 3.14 argparse style). - * - * Color Palette: - * Background: #282C34 - * Default text: #CCCED4 - * Usage label: #61AFEF (blue, bold) - * Program name: #C678DD (purple, bold) - * Subcommands: #98C379 (green) - * Options: #56B6C2 (teal) - * Metavars: #E5C07B (yellow, italic) - * Choices: #98C379 (green) - * Headings: #E5E5E5 (bright, bold) - * Punctuation: #CCCED4 - */ - -/* ========================================================================== - Inline Role Styles - ========================================================================== */ - -/* - * Shared monospace font and code font-size for all CLI inline roles - */ -.cli-option, -.cli-metavar, -.cli-command, -.cli-default, -.cli-choice { - font-family: var(--font-stack--monospace); - font-size: var(--code-font-size); -} - -/* - * CLI Options - * - * Long options (--verbose) and short options (-h) both use teal color. - */ -.cli-option-long, -.cli-option-short { - color: #56b6c2; -} - -/* - * CLI Metavars - * - * Placeholder values like FILE, PATH, DIRECTORY. - * Yellow/amber to indicate "replace me" - distinct from flags (teal). - */ -.cli-metavar { - color: #e5c07b; - font-style: italic; -} - -/* - * CLI Commands and Choices - * - * Both use green to indicate valid enumerated values. - * Commands: subcommand names like sync, add, list - * Choices: enumeration values like json, yaml, table - */ -.cli-command, -.cli-choice { - color: #98c379; -} - -.cli-command { - font-weight: bold; -} - -/* - * CLI Default Values - * - * Default values shown in help text like None, "auto". - * Subtle styling to not distract from options. - */ -.cli-default { - color: #ccced4; - font-style: italic; -} - -/* ========================================================================== - Argparse Code Block Highlighting - ========================================================================== */ - -/* - * These styles apply within Pygments-highlighted code blocks using the - * argparse, argparse-usage, or argparse-help lexers. - */ - -/* Usage heading "usage:" - bold blue */ -.highlight-argparse .gh, -.highlight-argparse-usage .gh, -.highlight-argparse-help .gh { - color: #61afef; - font-weight: bold; -} - -/* Section headers like "positional arguments:", "options:" - neutral bright */ -.highlight-argparse .gs, -.highlight-argparse-help .gs { - color: #e5e5e5; - font-weight: bold; -} - -/* Long options --foo - teal */ -.highlight-argparse .nt, -.highlight-argparse-usage .nt, -.highlight-argparse-help .nt { - color: #56b6c2; - font-weight: normal; -} - -/* Short options -h - teal (same as long) */ -.highlight-argparse .na, -.highlight-argparse-usage .na, -.highlight-argparse-help .na { - color: #56b6c2; - font-weight: normal; -} - -/* Metavar placeholders FILE, PATH - yellow/amber italic */ -.highlight-argparse .nv, -.highlight-argparse-usage .nv, -.highlight-argparse-help .nv { - color: #e5c07b; - font-style: italic; -} - -/* Command/program names - purple bold */ -.highlight-argparse .nl, -.highlight-argparse-usage .nl, -.highlight-argparse-help .nl { - color: #c678dd; - font-weight: bold; -} - -/* Subcommands - bold green */ -.highlight-argparse .nf, -.highlight-argparse-usage .nf, -.highlight-argparse-help .nf { - color: #98c379; - font-weight: bold; -} - -/* Choice values - green */ -.highlight-argparse .no, -.highlight-argparse-usage .no, -.highlight-argparse-help .no, -.highlight-argparse .nc, -.highlight-argparse-usage .nc, -.highlight-argparse-help .nc { - color: #98c379; -} - -/* Punctuation [], {}, () - neutral gray */ -.highlight-argparse .p, -.highlight-argparse-usage .p, -.highlight-argparse-help .p { - color: #ccced4; -} - -/* Operators like | - neutral gray */ -.highlight-argparse .o, -.highlight-argparse-usage .o, -.highlight-argparse-help .o { - color: #ccced4; - font-weight: normal; -} - -/* ========================================================================== - Argparse Directive Highlighting (.. argparse:: output) - ========================================================================== */ - -/* - * These styles apply to the argparse directive output which uses custom - * nodes rendered by sphinx_argparse_neo. The directive adds highlight spans - * directly to the HTML output. - */ - -/* - * Usage Block (.argparse-usage) - * - * The usage block now has both .argparse-usage and .highlight-argparse-usage - * classes. The .highlight-argparse-usage selectors above already handle the - * token highlighting. These selectors provide fallback and ensure consistent - * styling. - */ - -/* Usage block container - match Pygments monokai background and code block styling */ -pre.argparse-usage { - background: var(--argparse-code-background); - font-size: var(--code-font-size); - padding: 0.625rem 0.875rem; - line-height: 1.5; - border-radius: 0.2rem; - scrollbar-color: var(--color-foreground-border) transparent; - scrollbar-width: thin; -} - -.argparse-usage .gh { - color: #61afef; - font-weight: bold; -} - -.argparse-usage .nt { - color: #56b6c2; - font-weight: normal; -} - -.argparse-usage .na { - color: #56b6c2; - font-weight: normal; -} - -.argparse-usage .nv { - color: #e5c07b; - font-style: italic; -} - -.argparse-usage .nl { - color: #c678dd; - font-weight: bold; -} - -.argparse-usage .nf { - color: #98c379; - font-weight: bold; -} - -.argparse-usage .no, -.argparse-usage .nc { - color: #98c379; -} - -.argparse-usage .o { - color: #ccced4; - font-weight: normal; -} - -.argparse-usage .p { - color: #ccced4; -} - -/* - * Argument Name (
) - * - * The argument names in the dl/dt structure are highlighted with - * semantic spans for options and metavars. - */ -.argparse-argument-name .nt { - color: #56b6c2; - font-weight: normal; -} - -.argparse-argument-name .na { - color: #56b6c2; - font-weight: normal; -} - -.argparse-argument-name .nv { - color: #e5c07b; - font-style: italic; -} - -.argparse-argument-name .nl { - color: #c678dd; - font-weight: bold; -} - -/* ========================================================================== - Argument Wrapper and Linking Styles - ========================================================================== */ - -/* - * Argparse-specific code background (monokai #272822) - * Uses a custom variable to avoid affecting Furo's --color-inline-code-background. - */ -:root { - --argparse-code-background: #272822; -} - -/* - * Background styling for argument names - subtle background like code.literal - * This provides visual weight and hierarchy for argument definitions. - */ -.argparse-argument-name { - background: var(--argparse-code-background); - border-radius: 0.2rem; - padding: 0.485rem 0.875rem; - font-family: var(--font-stack--monospace); - font-size: var(--code-font-size); - width: fit-content; - position: relative; -} - -/* - * Wrapper for linking - enables scroll-margin for fixed header navigation - * and :target pseudo-class for highlighting when linked. - */ -.argparse-argument-wrapper { - scroll-margin-top: 2.5rem; -} - -/* - * Headerlink anchor (¶) - hidden until hover - * Positioned outside the dt element to the right. - * Follows Sphinx documentation convention for linkable headings. - */ -.argparse-argument-name .headerlink { - visibility: hidden; - position: absolute; - right: -1.5rem; - top: 50%; - transform: translateY(-50%); - color: var(--color-foreground-secondary); - text-decoration: none; -} - -/* - * Show headerlink on hover or when targeted via URL fragment - */ -.argparse-argument-wrapper:hover .headerlink, -.argparse-argument-wrapper:target .headerlink { - visibility: visible; -} - -.argparse-argument-name .headerlink:hover:not(:visited) { - color: var(--color-foreground-primary); -} - -/* - * Light mode headerlink color overrides - * Needed because code block has dark background regardless of theme - */ -body[data-theme="light"] .argparse-argument-name .headerlink { - color: #9ca0a5; - - &:hover:not(:visited) { - color: #cfd0d0; - } -} - -@media (prefers-color-scheme: light) { - body:not([data-theme="dark"]) .argparse-argument-name .headerlink { - color: #9ca0a5; - - &:hover:not(:visited) { - color: #cfd0d0; - } - } -} - -/* - * Highlight when targeted via URL fragment - * Uses Furo's highlight-on-target color for consistency. - */ -.argparse-argument-wrapper:target .argparse-argument-name { - background-color: var(--color-highlight-on-target); -} - -/* - * Argument metadata definition list - * - * Renders metadata (Default, Type, Choices, Required) as a horizontal - * flexbox of key-value pairs and standalone tags. - */ -.argparse-argument-meta { - margin: 0.5rem 0 0 0; - padding: 0; - display: flex; - flex-wrap: wrap; - gap: 0.5rem 1rem; - align-items: center; -} - -.argparse-meta-item { - display: flex; - align-items: center; - gap: 0.25rem; -} - -.argparse-meta-key { - color: var(--color-foreground-secondary, #6c757d); - font-size: var(--code-font-size); -} - -.argparse-meta-key::after { - content: ":"; -} - -.argparse-meta-value .nv { - background: var(--argparse-code-background); - border-radius: 0.2rem; - padding: 0.1rem 0.3rem; - font-family: var(--font-stack--monospace); - font-size: var(--code-font-size); - color: #e5c07b; -} - -/* - * Meta tag (e.g., "Required") - follows Furo's guilabel pattern - * Uses semi-transparent amber background with border for visibility - * without the harshness of solid fills. Amber conveys "needs attention". - */ -.argparse-meta-tag { - background-color: #fef3c780; - border: 1px solid #fcd34d80; - color: var(--color-foreground-primary); - font-size: var(--code-font-size); - padding: 0.1rem 0.4rem; - border-radius: 0.2rem; - font-weight: 500; -} - -/* Dark mode: darker amber with adjusted border */ -body[data-theme="dark"] .argparse-meta-tag { - background-color: #78350f60; - border-color: #b4530980; -} - -@media (prefers-color-scheme: dark) { - body:not([data-theme="light"]) .argparse-meta-tag { - background-color: #78350f60; - border-color: #b4530980; - } -} - -/* - * Help text description - * Adds spacing above for visual separation from argument name. - */ -.argparse-argument-help { - padding-block-start: 0.5rem; -} diff --git a/docs/_static/css/custom.css b/docs/_static/css/custom.css deleted file mode 100644 index 00a15fdc22..0000000000 --- a/docs/_static/css/custom.css +++ /dev/null @@ -1,21 +0,0 @@ -.sidebar-tree p.indented-block { - padding: var(--sidebar-item-spacing-vertical) - var(--sidebar-item-spacing-horizontal) 0 - var(--sidebar-item-spacing-horizontal); - margin-bottom: 0; -} - -.sidebar-tree p.indented-block span.indent { - margin-left: var(--sidebar-item-spacing-horizontal); - display: block; -} - -.sidebar-tree p.indented-block .project-name { - font-size: var(--sidebar-item-font-size); - font-weight: bold; - margin-right: calc(var(--sidebar-item-spacing-horizontal) / 2.5); -} - -.sidebar-tree .active { - font-weight: bold; -} diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html deleted file mode 100644 index 2943238cf7..0000000000 --- a/docs/_templates/layout.html +++ /dev/null @@ -1,45 +0,0 @@ -{% extends "!layout.html" %} -{%- block extrahead %} - {{ super() }} - {%- if theme_show_meta_manifest_tag == true %} - - {% endif -%} - {%- if theme_show_meta_og_tags == true %} - - - - - - - - - - - - - - - - {% endif -%} - {%- if theme_show_meta_app_icon_tags == true %} - - - - - - - - - - - - - - - - - - - - {% endif -%} -{% endblock %} diff --git a/docs/_templates/sidebar/projects.html b/docs/_templates/sidebar/projects.html deleted file mode 100644 index 97420c1adf..0000000000 --- a/docs/_templates/sidebar/projects.html +++ /dev/null @@ -1,69 +0,0 @@ - - diff --git a/docs/about.md b/docs/about.md deleted file mode 100644 index 0d380728db..0000000000 --- a/docs/about.md +++ /dev/null @@ -1,104 +0,0 @@ -```{module} tmuxp - -``` - -(about)= - -# About - -tmuxp helps you manage tmux workspaces. - -Built on an object relational mapper for tmux. tmux users can reload common -workspaces from YAML, JSON and {py:obj}`dict` workspace files like -[tmuxinator] and [teamocil]. - -tmuxp is used by developers for tmux automation at great companies like -[Bugsnag], [Pragmatic Coders] and many others. - -To jump right in, see {ref}`quickstart` and {ref}`examples`. - -Interested in some kung-fu or joining the effort? {ref}`api` and -{ref}`developing`. - -[MIT-licensed]. Code on [github](http://github.com/tmux-python/tmuxp). - -[bugsnag]: https://blog.bugsnag.com/benefits-of-using-tmux/ -[pragmatic coders]: http://pragmaticcoders.com/blog/tmuxp-preconfigured-sessions/ - -## Compared to tmuxinator / teamocil - -### Similarities - -**Load sessions** Loads tmux sessions from config - -**YAML** Supports YAML format - -**Inlining / shorthand configuration** All three support short-hand and -simplified markup for panes that have one command. - -**Maturity and stability** As of 2016, all three are considered stable, -well tested and adopted. - -### Missing - -**Version support** tmuxp only supports `tmux >= 3.2`. Teamocil and -tmuxinator may have support for earlier versions. - -### Differences - -**Programming Language** python. teamocil and tmuxinator use ruby. - -**Workspace building process** teamocil and tmuxinator process configs -directly shell commands. tmuxp processes configuration via ORM layer. - -## Additional Features - -**CLI** tmuxp's CLI can attach and kill sessions with tab-completion -support. See {ref}`commands`. - -**Import config** import configs from Teamocil / Tmuxinator [^id4]. See -{ref}`cli-import`. - -**Session freezing** Supports session freezing into YAML and JSON -format [^id4]. See {ref}`cli-freeze`. - -**JSON config** JSON config support. See {ref}`Examples`. - -**ORM-based API** via [libtmux] - Utilizes tmux's unique IDs for -panes, windows and sessions to create an object relational view of the tmux -{class}`~libtmux.Server`, its {class}`~libtmux.Session`, -{class}`~libtmux.Window`, and {class}`~libtmux.Pane`. -See {ref}`libtmux's internals `. - -**Conversion** `$ tmuxp convert ` can convert files to and -from JSON and YAML. - -[^id4]: On freezing - - While freezing and importing sessions is a great way to save time, - tweaking will probably be required - There is no substitute to a - config made with love. - -[libtmux]: https://libtmux.git-pull.com - -## Minor tweaks - -- Unit tests against live tmux version to test statefulness of tmux - sessions, windows and panes. See {ref}`gh-actions`. -- Load + switch to new session from inside tmux. -- Resume session if config loaded. -- Pre-commands virtualenv / rvm / any other commands. -- Load config from anywhere `$ tmuxp load /full/file/path.json`. -- Load config `.tmuxp.yaml` or `.tmuxp.json` from current working - directory with `$ tmuxp load .`. -- `$ tmuxp -2`, `$ tmuxp -8` for forcing term colors a la - {term}`tmux(1)`. -- `$ tmuxp -L`, `$ tmuxp -S` for sockets and - `$ tmuxp -f ` for config file. - -[attempt at 1.7 test]: https://travis-ci.org/tmux-python/tmuxp/jobs/12348263 -[mit-licensed]: http://opensource.org/licenses/MIT -[tmuxinator]: https://github.com/aziz/tmuxinator -[teamocil]: https://github.com/remiprev/teamocil -[erb]: http://ruby-doc.org/stdlib-2.0.0/libdoc/erb/rdoc/ERB.html -[edit this page]: https://github.com/tmux-python/tmuxp/edit/master/doc/about.rst diff --git a/docs/about_tmux.md b/docs/about_tmux.md index 5f10e2275e..2db7b3c4d7 100644 --- a/docs/about_tmux.md +++ b/docs/about_tmux.md @@ -5,6 +5,7 @@ :::{figure} /\_static/tao-tmux-screenshot.png :scale: 60% :align: center +:loading: lazy ISC-licensed terminal multiplexer. diff --git a/docs/cli/completion.md b/docs/cli/completion.md index ee7f1f3fa1..410aed4ee3 100644 --- a/docs/cli/completion.md +++ b/docs/cli/completion.md @@ -32,8 +32,8 @@ $ uvx shtab --help :::{tab} bash -```bash -shtab --shell=bash -u tmuxp.cli.create_parser \ +```console +$ shtab --shell=bash -u tmuxp.cli.create_parser \ | sudo tee "$BASH_COMPLETION_COMPAT_DIR"/TMUXP ``` @@ -41,8 +41,8 @@ shtab --shell=bash -u tmuxp.cli.create_parser \ :::{tab} zsh -```zsh -shtab --shell=zsh -u tmuxp.cli.create_parser \ +```console +$ shtab --shell=zsh -u tmuxp.cli.create_parser \ | sudo tee /usr/local/share/zsh/site-functions/_TMUXP ``` @@ -50,8 +50,8 @@ shtab --shell=zsh -u tmuxp.cli.create_parser \ :::{tab} tcsh -```zsh -shtab --shell=tcsh -u tmuxp.cli.create_parser \ +```console +$ shtab --shell=tcsh -u tmuxp.cli.create_parser \ | sudo tee /etc/profile.d/TMUXP.completion.csh ``` diff --git a/docs/cli/exit-codes.md b/docs/cli/exit-codes.md new file mode 100644 index 0000000000..e8a80e892d --- /dev/null +++ b/docs/cli/exit-codes.md @@ -0,0 +1,30 @@ +(cli-exit-codes)= + +# Exit Codes + +tmuxp uses standard exit codes for scripting and automation. + +| Code | Meaning | +|------|---------| +| `0` | Success | +| `1` | General error (config validation, tmux command failure) | +| `2` | Usage error (invalid arguments, missing required options) | + +## Usage in Scripts + +```bash +#!/bin/bash +tmuxp load my-workspace.yaml +if [ $? -ne 0 ]; then + echo "Failed to load workspace" + exit 1 +fi +``` + +```bash +#!/bin/bash +tmuxp load -d my-workspace.yaml || { + echo "tmuxp failed with exit code $?" + exit 1 +} +``` diff --git a/docs/cli/index.md b/docs/cli/index.md index cb8bf94b3b..fd38b681ea 100644 --- a/docs/cli/index.md +++ b/docs/cli/index.md @@ -2,7 +2,48 @@ (commands)= -# Commands +# CLI Reference + +::::{grid} 1 1 2 2 +:gutter: 2 2 3 3 + +:::{grid-item-card} tmuxp load +:link: load +:link-type: doc +Load tmux sessions from workspace configs. +::: + +:::{grid-item-card} tmuxp shell +:link: shell +:link-type: doc +Interactive Python shell with tmux context. +::: + +:::{grid-item-card} tmuxp freeze +:link: freeze +:link-type: doc +Export running sessions to config files. +::: + +:::{grid-item-card} tmuxp convert +:link: convert +:link-type: doc +Convert between YAML and JSON formats. +::: + +:::{grid-item-card} Exit Codes +:link: exit-codes +:link-type: doc +Exit codes for scripting and automation. +::: + +:::{grid-item-card} Recipes +:link: recipes +:link-type: doc +Copy-pasteable command invocations. +::: + +:::: ```{toctree} :caption: General commands @@ -38,6 +79,14 @@ debug-info completion ``` +```{toctree} +:caption: Reference +:maxdepth: 1 + +exit-codes +recipes +``` + (cli-main)= (tmuxp-main)= diff --git a/docs/cli/recipes.md b/docs/cli/recipes.md new file mode 100644 index 0000000000..fdbdd7dad9 --- /dev/null +++ b/docs/cli/recipes.md @@ -0,0 +1,77 @@ +(cli-recipes)= + +# Recipes + +Copy-pasteable command invocations for common tasks. + +## Load a workspace + +```console +$ tmuxp load my-workspace.yaml +``` + +## Load in detached mode + +```console +$ tmuxp load -d my-workspace.yaml +``` + +## Load from a project directory + +```console +$ tmuxp load . +``` + +## Freeze a running session + +```console +$ tmuxp freeze my-session +``` + +## Convert YAML to JSON + +```console +$ tmuxp convert my-workspace.yaml +``` + +## Convert JSON to YAML + +```console +$ tmuxp convert my-workspace.json +``` + +## List available workspaces + +```console +$ tmuxp ls +``` + +## Search workspaces + +```console +$ tmuxp search my-project +``` + +## Edit a workspace config + +```console +$ tmuxp edit my-workspace +``` + +## Collect debug info + +```console +$ tmuxp debug-info +``` + +## Shell with tmux context + +```console +$ tmuxp shell +``` + +Access libtmux objects directly: + +```console +$ tmuxp shell --best --command 'print(server.sessions)' +``` diff --git a/docs/cli/shell.md b/docs/cli/shell.md index de47b9b9e1..586c305e54 100644 --- a/docs/cli/shell.md +++ b/docs/cli/shell.md @@ -23,7 +23,9 @@ $ tmuxp shell -c 'python code' ``` ```{image} ../_static/tmuxp-shell.gif -:width: 100% +:width: 878 +:height: 109 +:loading: lazy ``` ## Interactive usage diff --git a/docs/conf.py b/docs/conf.py index 9a1f957e6f..4afd921ad2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,19 +1,9 @@ -# flake8: NOQA: E501 """Sphinx documentation configuration for tmuxp.""" from __future__ import annotations -import contextlib -import inspect import pathlib import sys -import typing as t -from os.path import relpath - -import tmuxp - -if t.TYPE_CHECKING: - from sphinx.application import Sphinx # Get the project root dir, which is the parent dir of this cwd = pathlib.Path(__file__).parent @@ -21,215 +11,42 @@ src_root = project_root / "src" sys.path.insert(0, str(src_root)) -sys.path.insert(0, str(cwd / "_ext")) +sys.path.insert(0, str(cwd / "_ext")) # for local aafig extension # package data about: dict[str, str] = {} with (src_root / "tmuxp" / "__about__.py").open() as fp: exec(fp.read(), about) -extensions = [ - "sphinx.ext.autodoc", - "sphinx.ext.intersphinx", - "sphinx_autodoc_typehints", - "sphinx.ext.todo", - "sphinx.ext.napoleon", - "sphinx.ext.linkcode", - "aafig", - "argparse_exemplar", # Custom sphinx-argparse replacement - "sphinx_inline_tabs", - "sphinx_copybutton", - "sphinxext.opengraph", - "sphinxext.rediraffe", - "myst_parser", - "linkify_issues", -] - -myst_enable_extensions = [ - "colon_fence", - "substitution", - "replacements", - "strikethrough", - "linkify", -] - -templates_path = ["_templates"] - -source_suffix = {".rst": "restructuredtext", ".md": "markdown"} - -master_doc = "index" - -project = about["__title__"] -project_copyright = about["__copyright__"] - -version = "{}".format(".".join(about["__version__"].split("."))[:2]) -release = "{}".format(about["__version__"]) - -exclude_patterns = ["_build"] - -pygments_style = "monokai" -pygments_dark_style = "monokai" - -html_css_files = ["css/custom.css", "css/argparse-highlight.css"] -html_extra_path = ["manifest.json"] -html_static_path = ["_static"] -html_favicon = "_static/favicon.ico" -html_theme = "furo" -html_theme_path: list[str] = [] -html_theme_options: dict[str, str | list[dict[str, str]]] = { - "light_logo": "img/tmuxp.svg", - "dark_logo": "img/tmuxp.svg", - "footer_icons": [ - { - "name": "GitHub", - "url": about["__github__"], - "html": """ - - - - """, - "class": "", - }, - ], - "source_repository": f"{about['__github__']}/", - "source_branch": "master", - "source_directory": "docs/", -} -html_sidebars = { - "**": [ - "sidebar/scroll-start.html", - "sidebar/brand.html", - "sidebar/search.html", - "sidebar/navigation.html", - "sidebar/projects.html", - "sidebar/scroll-end.html", +from gp_sphinx.config import make_linkcode_resolve, merge_sphinx_config # noqa: E402 + +import tmuxp # noqa: E402 + +conf = merge_sphinx_config( + project=about["__title__"], + version=about["__version__"], + copyright=about["__copyright__"], + source_repository=f"{about['__github__']}/", + docs_url=about["__docs__"], + source_branch="master", + light_logo="img/tmuxp.svg", + dark_logo="img/tmuxp.svg", + extra_extensions=[ + "sphinx_autodoc_api_style", + "aafig", + "sphinx_autodoc_argparse.exemplar", ], -} - -# linkify_issues -issue_url_tpl = about["__github__"] + "/issues/{issue_id}" - -# sphinx.ext.autodoc -toc_object_entries_show_parents = "hide" -autodoc_default_options = { - "undoc-members": True, - "members": True, - "private-members": True, - "show-inheritance": True, - "member-order": "bysource", -} - -# sphinx-autodoc-typehints -# Suppress warnings for forward references that can't be resolved -# (types in TYPE_CHECKING blocks used for circular import avoidance) -suppress_warnings = [ - "sphinx_autodoc_typehints.forward_reference", -] - -# sphinxext.opengraph -ogp_site_url = about["__docs__"] -ogp_image = "_static/img/icons/icon-192x192.png" -ogp_site_name = about["__title__"] - -# sphinx-copybutton -copybutton_prompt_text = ( - r">>> |\.\.\. |> |\$ |\# | In \[\d*\]: | {2,5}\.\.\.: | {5,8}: " + intersphinx_mapping={ + "python": ("https://docs.python.org/", None), + "libtmux": ("https://libtmux.git-pull.com/", None), + }, + linkcode_resolve=make_linkcode_resolve(tmuxp, about["__github__"]), + # tmuxp-specific overrides + theme_options={"mask_icon": "/_static/img/tmuxp.svg"}, + html_extra_path=["manifest.json"], + html_favicon="_static/favicon.ico", + aafig_format={"latex": "pdf", "html": "gif"}, + aafig_default_options={"scale": 0.75, "aspect": 0.5, "proportional": True}, + rediraffe_redirects="redirects.txt", ) -copybutton_prompt_is_regexp = True -copybutton_remove_prompts = True - -# sphinxext-rediraffe -rediraffe_redirects = "redirects.txt" -rediraffe_branch = "master~1" - -# aafig format, try to get working with pdf -aafig_format = {"latex": "pdf", "html": "gif"} -aafig_default_options = {"scale": 0.75, "aspect": 0.5, "proportional": True} - -intersphinx_mapping = { - "python": ("https://docs.python.org/", None), - "libtmux": ("https://libtmux.git-pull.com/", None), -} - - -def linkcode_resolve(domain: str, info: dict[str, str]) -> None | str: - """ - Determine the URL corresponding to Python object. - - Notes - ----- - From https://github.com/numpy/numpy/blob/v1.15.1/doc/source/conf.py, 7c49cfa - on Jul 31. License BSD-3. https://github.com/numpy/numpy/blob/v1.15.1/LICENSE.txt - """ - if domain != "py": - return None - - modname = info["module"] - fullname = info["fullname"] - - submod = sys.modules.get(modname) - if submod is None: - return None - - obj = submod - for part in fullname.split("."): - try: - obj = getattr(obj, part) - except Exception: # NOQA: PERF203 - return None - - # strip decorators, which would resolve to the source of the decorator - # possibly an upstream bug in getsourcefile, bpo-1764286 - try: - unwrap = inspect.unwrap - except AttributeError: - pass - else: - if callable(obj): - obj = unwrap(obj) - - try: - fn = inspect.getsourcefile(obj) - except Exception: - fn = None - if not fn: - return None - - try: - source, lineno = inspect.getsourcelines(obj) - except Exception: - lineno = None - - linespec = f"#L{lineno}-L{lineno + len(source) - 1}" if lineno else "" - - fn = relpath(fn, start=pathlib.Path(tmuxp.__file__).parent) - - if "dev" in about["__version__"]: - return "{}/blob/master/{}/{}/{}{}".format( - about["__github__"], - "src", - about["__package_name__"], - fn, - linespec, - ) - return "{}/blob/v{}/{}/{}/{}{}".format( - about["__github__"], - about["__version__"], - "src", - about["__package_name__"], - fn, - linespec, - ) - - -def remove_tabs_js(app: Sphinx, exc: Exception) -> None: - """Fix for sphinx-inline-tabs#18.""" - if app.builder.format == "html" and not exc: - tabs_js = pathlib.Path(app.builder.outdir) / "_static" / "tabs.js" - with contextlib.suppress(FileNotFoundError): - tabs_js.unlink() # When python 3.7 deprecated, use missing_ok=True - - -def setup(app: Sphinx) -> None: - """Sphinx setup hook.""" - app.connect("build-finished", remove_tabs_js) +globals().update(conf) diff --git a/docs/configuration/index.md b/docs/configuration/index.md index aa59b017ee..619cb62710 100644 --- a/docs/configuration/index.md +++ b/docs/configuration/index.md @@ -6,6 +6,29 @@ # Workspace files +::::{grid} 1 2 3 3 +:gutter: 2 2 3 3 + +:::{grid-item-card} Top-level Options +:link: top-level +:link-type: doc +Session and window configuration keys. +::: + +:::{grid-item-card} Environment Variables +:link: environmental-variables +:link-type: doc +TMUXP_CONFIGDIR and other env vars. +::: + +:::{grid-item-card} Examples +:link: examples +:link-type: doc +Sample workspace configurations. +::: + +:::: + tmuxp loads your terminal workspace into tmux using workspace files. The workspace file can be JSON or YAML. It's declarative style resembles tmux's object hierarchy: session, window and panes. @@ -167,10 +190,33 @@ $ tmuxp load /opt/myapp ## Reference and usage +::::{grid} 1 2 3 3 +:gutter: 2 2 3 3 + +:::{grid-item-card} Top-level Options +:link: top-level +:link-type: doc +Session and window configuration keys. +::: + +:::{grid-item-card} Environment Variables +:link: environmental-variables +:link-type: doc +TMUXP_CONFIGDIR and other env vars. +::: + +:::{grid-item-card} Examples +:link: examples +:link-type: doc +Sample workspace configurations. +::: + +:::: + ```{toctree} +:hidden: top-level environmental-variables examples - ``` diff --git a/docs/index.md b/docs/index.md index 14b69dedb0..ddacba1bb4 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,41 +1,108 @@ (index)= -```{include} ../README.md -:end-before: GitHub ``` diff --git a/docs/api/internals/colors.md b/docs/internals/api/_internal/colors.md similarity index 100% rename from docs/api/internals/colors.md rename to docs/internals/api/_internal/colors.md diff --git a/docs/api/internals/config_reader.md b/docs/internals/api/_internal/config_reader.md similarity index 100% rename from docs/api/internals/config_reader.md rename to docs/internals/api/_internal/config_reader.md diff --git a/docs/api/internals/index.md b/docs/internals/api/_internal/index.md similarity index 90% rename from docs/api/internals/index.md rename to docs/internals/api/_internal/index.md index b96fc8657b..391a80b60f 100644 --- a/docs/api/internals/index.md +++ b/docs/internals/api/_internal/index.md @@ -1,6 +1,6 @@ -(internals)= +(api-internal)= -# Internals +# Internal Modules :::{warning} Be careful with these! Internal APIs are **not** covered by version policies. They can break or be removed between minor versions! diff --git a/docs/api/internals/private_path.md b/docs/internals/api/_internal/private_path.md similarity index 100% rename from docs/api/internals/private_path.md rename to docs/internals/api/_internal/private_path.md diff --git a/docs/api/internals/types.md b/docs/internals/api/_internal/types.md similarity index 100% rename from docs/api/internals/types.md rename to docs/internals/api/_internal/types.md diff --git a/docs/api/cli/convert.md b/docs/internals/api/cli/convert.md similarity index 100% rename from docs/api/cli/convert.md rename to docs/internals/api/cli/convert.md diff --git a/docs/api/cli/debug_info.md b/docs/internals/api/cli/debug_info.md similarity index 100% rename from docs/api/cli/debug_info.md rename to docs/internals/api/cli/debug_info.md diff --git a/docs/api/cli/edit.md b/docs/internals/api/cli/edit.md similarity index 100% rename from docs/api/cli/edit.md rename to docs/internals/api/cli/edit.md diff --git a/docs/api/cli/freeze.md b/docs/internals/api/cli/freeze.md similarity index 100% rename from docs/api/cli/freeze.md rename to docs/internals/api/cli/freeze.md diff --git a/docs/api/cli/import_config.md b/docs/internals/api/cli/import_config.md similarity index 100% rename from docs/api/cli/import_config.md rename to docs/internals/api/cli/import_config.md diff --git a/docs/api/cli/index.md b/docs/internals/api/cli/index.md similarity index 100% rename from docs/api/cli/index.md rename to docs/internals/api/cli/index.md diff --git a/docs/api/cli/load.md b/docs/internals/api/cli/load.md similarity index 100% rename from docs/api/cli/load.md rename to docs/internals/api/cli/load.md diff --git a/docs/api/cli/ls.md b/docs/internals/api/cli/ls.md similarity index 100% rename from docs/api/cli/ls.md rename to docs/internals/api/cli/ls.md diff --git a/docs/api/cli/progress.md b/docs/internals/api/cli/progress.md similarity index 100% rename from docs/api/cli/progress.md rename to docs/internals/api/cli/progress.md diff --git a/docs/api/cli/search.md b/docs/internals/api/cli/search.md similarity index 100% rename from docs/api/cli/search.md rename to docs/internals/api/cli/search.md diff --git a/docs/api/cli/shell.md b/docs/internals/api/cli/shell.md similarity index 100% rename from docs/api/cli/shell.md rename to docs/internals/api/cli/shell.md diff --git a/docs/api/cli/utils.md b/docs/internals/api/cli/utils.md similarity index 100% rename from docs/api/cli/utils.md rename to docs/internals/api/cli/utils.md diff --git a/docs/api/exc.md b/docs/internals/api/exc.md similarity index 100% rename from docs/api/exc.md rename to docs/internals/api/exc.md diff --git a/docs/api/index.md b/docs/internals/api/index.md similarity index 94% rename from docs/api/index.md rename to docs/internals/api/index.md index 89ec3d508b..2debbe3f26 100644 --- a/docs/api/index.md +++ b/docs/internals/api/index.md @@ -8,7 +8,7 @@ tmux via python API calls. ::: ```{toctree} -internals/index +_internal/index cli/index workspace/index exc diff --git a/docs/api/log.md b/docs/internals/api/log.md similarity index 100% rename from docs/api/log.md rename to docs/internals/api/log.md diff --git a/docs/api/plugin.md b/docs/internals/api/plugin.md similarity index 100% rename from docs/api/plugin.md rename to docs/internals/api/plugin.md diff --git a/docs/api/shell.md b/docs/internals/api/shell.md similarity index 100% rename from docs/api/shell.md rename to docs/internals/api/shell.md diff --git a/docs/api/types.md b/docs/internals/api/types.md similarity index 100% rename from docs/api/types.md rename to docs/internals/api/types.md diff --git a/docs/api/util.md b/docs/internals/api/util.md similarity index 100% rename from docs/api/util.md rename to docs/internals/api/util.md diff --git a/docs/api/workspace/builder.md b/docs/internals/api/workspace/builder.md similarity index 100% rename from docs/api/workspace/builder.md rename to docs/internals/api/workspace/builder.md diff --git a/docs/api/workspace/constants.md b/docs/internals/api/workspace/constants.md similarity index 100% rename from docs/api/workspace/constants.md rename to docs/internals/api/workspace/constants.md diff --git a/docs/api/workspace/finders.md b/docs/internals/api/workspace/finders.md similarity index 100% rename from docs/api/workspace/finders.md rename to docs/internals/api/workspace/finders.md diff --git a/docs/api/workspace/freezer.md b/docs/internals/api/workspace/freezer.md similarity index 100% rename from docs/api/workspace/freezer.md rename to docs/internals/api/workspace/freezer.md diff --git a/docs/api/workspace/importers.md b/docs/internals/api/workspace/importers.md similarity index 100% rename from docs/api/workspace/importers.md rename to docs/internals/api/workspace/importers.md diff --git a/docs/api/workspace/index.md b/docs/internals/api/workspace/index.md similarity index 100% rename from docs/api/workspace/index.md rename to docs/internals/api/workspace/index.md diff --git a/docs/api/workspace/loader.md b/docs/internals/api/workspace/loader.md similarity index 100% rename from docs/api/workspace/loader.md rename to docs/internals/api/workspace/loader.md diff --git a/docs/api/workspace/validation.md b/docs/internals/api/workspace/validation.md similarity index 100% rename from docs/api/workspace/validation.md rename to docs/internals/api/workspace/validation.md diff --git a/docs/internals/architecture.md b/docs/internals/architecture.md new file mode 100644 index 0000000000..7a104dbfea --- /dev/null +++ b/docs/internals/architecture.md @@ -0,0 +1,39 @@ +# Architecture + +How the tmuxp CLI dispatches commands to the underlying library. + +## Request Flow + +``` +tmuxp CLI (argparse) + │ + ├── tmuxp load ──→ workspace.loader ──→ workspace.builder ──→ libtmux + ├── tmuxp freeze ──→ workspace.freezer ──→ libtmux + ├── tmuxp convert ──→ _internal.config_reader + ├── tmuxp shell ──→ libtmux (interactive) + └── tmuxp ls/search ──→ workspace.finders +``` + +## Key Components + +### CLI Layer (`tmuxp.cli`) + +The CLI uses Python's `argparse` with a custom formatter ({mod}`tmuxp.cli._formatter`). +Each subcommand lives in its own module under `tmuxp.cli`. + +The entry point is `tmuxp.cli.cli()`, registered as a console script in `pyproject.toml`. + +### Workspace Layer (`tmuxp.workspace`) + +The workspace layer handles configuration lifecycle: + +1. **Finding**: {mod}`tmuxp.workspace.finders` locates config files +2. **Loading**: {mod}`tmuxp.workspace.loader` reads and validates configs +3. **Building**: {mod}`tmuxp.workspace.builder` creates tmux sessions via libtmux +4. **Freezing**: {mod}`tmuxp.workspace.freezer` exports running sessions + +### Library Layer (libtmux) + +tmuxp delegates all tmux operations to [libtmux](https://libtmux.git-pull.com/). +The `WorkspaceBuilder` creates libtmux `Server`, `Session`, `Window`, and `Pane` +objects to construct the requested workspace. diff --git a/docs/internals/index.md b/docs/internals/index.md new file mode 100644 index 0000000000..82a8c121c0 --- /dev/null +++ b/docs/internals/index.md @@ -0,0 +1,36 @@ +(internals)= + +# Internals + +```{warning} +Everything in this section is **internal implementation detail**. There is +no stability guarantee. Interfaces may change or be removed without notice +between any release. + +If you are building an application with tmuxp, use the [CLI](../cli/index.md) +or refer to the [libtmux API](https://libtmux.git-pull.com/api/). +``` + +::::{grid} 1 1 2 2 +:gutter: 2 2 3 3 + +:::{grid-item-card} Architecture +:link: architecture +:link-type: doc +How the CLI dispatches to the workspace builder and libtmux. +::: + +:::{grid-item-card} Python API +:link: api/index +:link-type: doc +Internal module reference for contributors and plugin authors. +::: + +:::: + +```{toctree} +:hidden: + +architecture +api/index +``` diff --git a/docs/justfile b/docs/justfile index 171766555a..afeec6b509 100644 --- a/docs/justfile +++ b/docs/justfile @@ -27,7 +27,7 @@ default: # Build HTML documentation [group: 'build'] html: - {{ sphinxbuild }} -b html {{ allsphinxopts }} {{ builddir }}/html + {{ sphinxbuild }} -b dirhtml {{ allsphinxopts }} {{ builddir }}/html @echo "" @echo "Build finished. The HTML pages are in {{ builddir }}/html." @@ -202,9 +202,9 @@ dev: # Start sphinx-autobuild server [group: 'dev'] start: - uv run sphinx-autobuild "{{ sourcedir }}" "{{ builddir }}" {{ sphinxopts }} --port {{ http_port }} + uv run sphinx-autobuild -b dirhtml "{{ sourcedir }}" "{{ builddir }}" {{ sphinxopts }} --port {{ http_port }} # Design mode: watch static files and disable incremental builds [group: 'dev'] design: - uv run sphinx-autobuild "{{ sourcedir }}" "{{ builddir }}" {{ sphinxopts }} --port {{ http_port }} --watch "." -a + uv run sphinx-autobuild -b dirhtml "{{ sourcedir }}" "{{ builddir }}" {{ sphinxopts }} --port {{ http_port }} --watch "." -a diff --git a/docs/project/code-style.md b/docs/project/code-style.md new file mode 100644 index 0000000000..1ea71dc8c3 --- /dev/null +++ b/docs/project/code-style.md @@ -0,0 +1,32 @@ +# Code Style + +## Formatting + +tmuxp uses [ruff](https://github.com/astral-sh/ruff) for both linting and formatting. + +```console +$ uv run ruff format . +``` + +```console +$ uv run ruff check . --fix --show-fixes +``` + +## Type Checking + +Strict [mypy](https://mypy-lang.org/) is enforced. + +```console +$ uv run mypy +``` + +## Docstrings + +All public functions and methods use NumPy-style docstrings. + +## Imports + +- Standard library: namespace imports (`import pathlib`, not `from pathlib import Path`) + - Exception: `from dataclasses import dataclass, field` +- Typing: `import typing as t`, access via `t.Optional`, `t.NamedTuple`, etc. +- All files: `from __future__ import annotations` diff --git a/docs/developing.md b/docs/project/contributing.md similarity index 98% rename from docs/developing.md rename to docs/project/contributing.md index 9f2e221cfd..cceb9eb61e 100644 --- a/docs/developing.md +++ b/docs/project/contributing.md @@ -175,13 +175,15 @@ $ env PYTEST_ADDOPTS="tests/workspace/test_builder.py" uv run make start Drop into `test_automatic_rename_option()` in `tests/workspace/test_builder.py`: ```console -$ env PYTEST_ADDOPTS="-s -x -vv tests/workspace/test_builder.py" uv run make start +$ env PYTEST_ADDOPTS="-s -x -vv tests/workspace/test_builder.py" \ + uv run make start ``` Drop into `test_automatic_rename_option()` in `tests/workspace/test_builder.py` and stop on first error: ```console -$ env PYTEST_ADDOPTS="-s -x -vv tests/workspace/test_builder.py::test_automatic_rename_option" uv run make start +$ env PYTEST_ADDOPTS="-s -x -vv tests/workspace/test_builder.py::test_automatic_rename_option" \ + uv run make start ``` Drop into `pdb` on first error: @@ -318,8 +320,10 @@ $ make SPHINXBUILD='uv run sphinx-build' watch ## tmuxp developer config ```{image} _static/tmuxp-dev-screenshot.png +:width: 1030 +:height: 605 :align: center - +:loading: lazy ``` After you {ref}`install-dev-env`, when inside the tmuxp checkout: diff --git a/docs/project/index.md b/docs/project/index.md new file mode 100644 index 0000000000..2b60711f5c --- /dev/null +++ b/docs/project/index.md @@ -0,0 +1,36 @@ +(project)= + +# Project + +Information for contributors and maintainers. + +::::{grid} 1 1 2 2 +:gutter: 2 2 3 3 + +:::{grid-item-card} Contributing +:link: contributing +:link-type: doc +Development setup, running tests, submitting PRs. +::: + +:::{grid-item-card} Code Style +:link: code-style +:link-type: doc +Ruff, mypy, NumPy docstrings, import conventions. +::: + +:::{grid-item-card} Releasing +:link: releasing +:link-type: doc +Release checklist and version policy. +::: + +:::: + +```{toctree} +:hidden: + +contributing +code-style +releasing +``` diff --git a/docs/project/releasing.md b/docs/project/releasing.md new file mode 100644 index 0000000000..37b31aa3a7 --- /dev/null +++ b/docs/project/releasing.md @@ -0,0 +1,50 @@ +# Releasing + +## Release Process + +Releases are triggered by git tags and published to PyPI via OIDC trusted publishing. + +1. Update `CHANGES` with the release notes + +2. Bump version in `src/tmuxp/__about__.py` + +3. Commit: + + ```console + $ git commit -m "tmuxp " + ``` + +4. Tag: + + ```console + $ git tag v + ``` + +5. Push: + + ```console + $ git push && git push --tags + ``` + +6. CI builds and publishes to PyPI automatically via trusted publishing + +## Changelog Format + +The `CHANGES` file uses this format: + +```text +tmuxp () +------------------------ + +### What's new + +- Description of feature (#issue) + +### Bug fixes + +- Description of fix (#issue) + +### Breaking changes + +- Description of break, migration path (#issue) +``` diff --git a/docs/redirects.txt b/docs/redirects.txt index 66d2cb860b..be9e1bdd10 100644 --- a/docs/redirects.txt +++ b/docs/redirects.txt @@ -1,5 +1,40 @@ -"cli.md" "commands/index.md" -"api.md" "api/index.md" +# "cli.md" → commands/index.md → cli/index.md: not needed with dirhtml (same output path) +# "api.md" → api/index.md: not needed with dirhtml (same output path as api/index.md redirect) "examples.md" "configuration/examples.md" "plugin_system.md" "plugins/index.md" "commands/index.md" "cli/index.md" +"api/index.md" "internals/api/index.md" +"api/exc.md" "internals/api/exc.md" +"api/log.md" "internals/api/log.md" +"api/plugin.md" "internals/api/plugin.md" +"api/shell.md" "internals/api/shell.md" +"api/types.md" "internals/api/types.md" +"api/util.md" "internals/api/util.md" +"api/cli/index.md" "internals/api/cli/index.md" +"api/cli/convert.md" "internals/api/cli/convert.md" +"api/cli/debug_info.md" "internals/api/cli/debug_info.md" +"api/cli/edit.md" "internals/api/cli/edit.md" +"api/cli/freeze.md" "internals/api/cli/freeze.md" +"api/cli/import_config.md" "internals/api/cli/import_config.md" +"api/cli/load.md" "internals/api/cli/load.md" +"api/cli/ls.md" "internals/api/cli/ls.md" +"api/cli/progress.md" "internals/api/cli/progress.md" +"api/cli/search.md" "internals/api/cli/search.md" +"api/cli/shell.md" "internals/api/cli/shell.md" +"api/cli/utils.md" "internals/api/cli/utils.md" +"api/workspace/index.md" "internals/api/workspace/index.md" +"api/workspace/builder.md" "internals/api/workspace/builder.md" +"api/workspace/constants.md" "internals/api/workspace/constants.md" +"api/workspace/finders.md" "internals/api/workspace/finders.md" +"api/workspace/freezer.md" "internals/api/workspace/freezer.md" +"api/workspace/importers.md" "internals/api/workspace/importers.md" +"api/workspace/loader.md" "internals/api/workspace/loader.md" +"api/workspace/validation.md" "internals/api/workspace/validation.md" +"api/internals/index.md" "internals/api/_internal/index.md" +"api/internals/colors.md" "internals/api/_internal/colors.md" +"api/internals/config_reader.md" "internals/api/_internal/config_reader.md" +"api/internals/private_path.md" "internals/api/_internal/private_path.md" +"api/internals/types.md" "internals/api/_internal/types.md" +"plugins/index.md" "topics/plugins.md" +"developing.md" "project/contributing.md" +"about.md" "topics/index.md" diff --git a/docs/topics/index.md b/docs/topics/index.md new file mode 100644 index 0000000000..050ddb9d58 --- /dev/null +++ b/docs/topics/index.md @@ -0,0 +1,52 @@ +(topics)= + +# Topics + +Conceptual guides and workflow documentation. + +::::{grid} 1 1 2 2 +:gutter: 2 2 3 3 + +:::{grid-item-card} Workflows +:link: workflows +:link-type: doc +CI integration, scripting, and automation patterns. +::: + +:::{grid-item-card} Plugins +:link: plugins +:link-type: doc +Plugin system for custom behavior. +::: + +:::{grid-item-card} Library vs CLI +:link: library-vs-cli +:link-type: doc +When to use tmuxp CLI vs libtmux directly. +::: + +:::{grid-item-card} Troubleshooting +:link: troubleshooting +:link-type: doc +Common shell, PATH, and tmux issues. +::: + +:::: + +## Compared to tmuxinator / teamocil + +tmuxp, [tmuxinator](https://github.com/aziz/tmuxinator), and +[teamocil](https://github.com/remiprev/teamocil) all load tmux sessions +from config files. Key differences: tmuxp is Python (not Ruby), builds +sessions through [libtmux](https://libtmux.git-pull.com/)'s ORM layer +instead of raw shell commands, supports JSON and YAML, and can +[freeze](../cli/freeze.md) running sessions back to config. + +```{toctree} +:hidden: + +workflows +plugins +library-vs-cli +troubleshooting +``` diff --git a/docs/topics/library-vs-cli.md b/docs/topics/library-vs-cli.md new file mode 100644 index 0000000000..8c431caa3d --- /dev/null +++ b/docs/topics/library-vs-cli.md @@ -0,0 +1,62 @@ +# Library vs CLI + +tmuxp is a CLI tool. [libtmux](https://libtmux.git-pull.com/) is the Python library it's built on. Both control tmux, but they serve different needs. + +## When to Use the CLI + +Use `tmuxp` when: + +- You want **declarative workspace configs** — define your layout in YAML, load it with one command +- You're setting up **daily development environments** — same windows, same panes, every time +- You need **CI/CD tmux sessions** — `tmuxp load -d` in a script +- You prefer **configuration over code** — no Python needed + +```console +$ tmuxp load my-workspace.yaml +``` + +## When to Use libtmux + +Use [libtmux](https://libtmux.git-pull.com/) directly when: + +- You need **dynamic logic** — conditionals, loops, branching based on state +- You want to **read pane output** — capture what's on screen and react to it +- You're **testing** tmux interactions — libtmux provides pytest fixtures +- You need **multi-server orchestration** — manage multiple tmux servers programmatically +- The CLI's config format **can't express** what you need + +```python +import libtmux + +server = libtmux.Server() +session = server.new_session("my-project") +window = session.new_window("editor") +pane = window.split() +pane.send_keys("vim .") +``` + +## Concept Mapping + +How tmuxp config keys map to libtmux API calls: + +| tmuxp YAML | libtmux equivalent | +|------------|-------------------| +| `session_name: foo` | `server.new_session(session_name="foo")` | +| `windows:` | `session.new_window(...)` | +| `panes:` | `window.split(...)` | +| `shell_command:` | `pane.send_keys(...)` | +| `layout: main-vertical` | `window.select_layout("main-vertical")` | +| `start_directory: ~/project` | `session.new_window(start_directory="~/project")` | +| `before_script:` | Run via `subprocess` before building | + +## What the CLI Can't Express + +tmuxp configs are static declarations. They can't: + +- **Branch on conditions** — "only create this pane if a file exists" +- **Read pane output** — "wait until the server is ready, then open the browser" +- **React to state** — "if this session already has 3 windows, add a 4th" +- **Orchestrate across servers** — "connect to both local and remote tmux" +- **Build layouts dynamically** — "create N panes based on a list of services" + +For these, use libtmux directly. See the [libtmux quickstart](https://libtmux.git-pull.com/quickstart.html). diff --git a/docs/plugins/index.md b/docs/topics/plugins.md similarity index 100% rename from docs/plugins/index.md rename to docs/topics/plugins.md diff --git a/docs/topics/troubleshooting.md b/docs/topics/troubleshooting.md new file mode 100644 index 0000000000..bfacad2c34 --- /dev/null +++ b/docs/topics/troubleshooting.md @@ -0,0 +1,40 @@ +# Troubleshooting + +## tmuxp command not found + +Ensure tmuxp is installed and on your `PATH`: + +```console +$ which tmuxp +``` + +If installed with `pip install --user`, ensure `~/.local/bin` is in your `PATH`. + +## tmux server not found + +tmuxp requires a running tmux server or will start one automatically. +Ensure tmux is installed: + +```console +$ tmux -V +``` + +Minimum required version: tmux 3.2a. + +## Configuration errors + +Use `tmuxp debug-info` to collect system information for bug reports: + +```console +$ tmuxp debug-info +``` + +## Session already exists + +If a session with the same name already exists, tmuxp will prompt you. +Use `tmuxp load -d` to load in detached mode alongside existing sessions. + +## Shell completion not working + +See [Shell Completion](../cli/completion.md) for setup instructions +for bash, zsh, and fish. diff --git a/docs/topics/workflows.md b/docs/topics/workflows.md new file mode 100644 index 0000000000..5965653c0d --- /dev/null +++ b/docs/topics/workflows.md @@ -0,0 +1,32 @@ +# Workflows + +## CI Integration + +tmuxp can set up tmux sessions in CI pipelines for integration testing: + +```console +$ tmuxp load -d my-workspace.yaml +``` + +The `-d` flag loads the session in detached mode, useful for headless environments. + +## Scripting + +tmuxp's exit codes enable scripting and error handling. See +[Exit Codes](../cli/exit-codes.md) for the complete list. + +## Automating Development Environments + +Use tmuxp to codify your development environment: + +1. Set up your ideal tmux layout manually +2. Freeze it: `tmuxp freeze my-session` +3. Edit the generated YAML to add commands +4. Load it on any machine: `tmuxp load my-workspace.yaml` + +## User-Level Configuration + +Workspace configs can be stored in: +- `~/.tmuxp/` (legacy) +- `~/.config/tmuxp/` (XDG default) +- Project-local `.tmuxp.yaml` or `.tmuxp/` directory diff --git a/pyproject.toml b/pyproject.toml index e394750acc..48272a5746 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "tmuxp" -version = "1.67.0" +version = "1.68.0" description = "Session manager for tmux, which allows users to save and load tmux sessions through simple configuration files." requires-python = ">=3.10,<4.0" authors = [ @@ -39,7 +39,7 @@ include = [ { path = "conftest.py", format = "sdist" }, ] dependencies = [ - "libtmux~=0.55.0", + "libtmux~=0.56.0", "PyYAML>=6.0" ] @@ -55,19 +55,13 @@ tmuxp = 'tmuxp:cli.cli' [dependency-groups] dev = [ # Docs - "aafigure", - "pillow", - "sphinx<9", - "furo", - "gp-libs", - "sphinx-autobuild", - "sphinx-autodoc-typehints", - "sphinx-inline-tabs", - "sphinxext-opengraph", - "sphinx-copybutton", - "sphinxext-rediraffe", - "myst-parser", - "linkify-it-py", + "aafigure", # https://launchpad.net/aafigure + "pillow", # https://pillow.readthedocs.io/ + "gp-sphinx==0.0.1a17", # https://gp-sphinx.git-pull.com/ + "sphinx-autodoc-argparse==0.0.1a17", # https://gp-sphinx.git-pull.com/ + "sphinx-autodoc-api-style==0.0.1a17", + "gp-libs", # https://gp-libs.git-pull.com/ + "sphinx-autobuild", # https://sphinx-extensions.readthedocs.io/en/latest/sphinx-autobuild.html # Testing "gp-libs", "pytest", @@ -87,19 +81,13 @@ dev = [ ] docs = [ - "aafigure", - "pillow", - "sphinx<9", - "furo", - "gp-libs", - "sphinx-autobuild", - "sphinx-autodoc-typehints", - "sphinx-inline-tabs", - "sphinxext-opengraph", - "sphinx-copybutton", - "sphinxext-rediraffe", - "myst-parser", - "linkify-it-py", + "aafigure", # https://launchpad.net/aafigure + "pillow", # https://pillow.readthedocs.io/ + "gp-sphinx==0.0.1a17", # https://gp-sphinx.git-pull.com/ + "sphinx-autodoc-argparse==0.0.1a17", # https://gp-sphinx.git-pull.com/ + "sphinx-autodoc-api-style==0.0.1a17", + "gp-libs", # https://gp-libs.git-pull.com/ + "sphinx-autobuild", # https://sphinx-extensions.readthedocs.io/en/latest/sphinx-autobuild.html ] testing = [ "gp-libs", @@ -125,6 +113,31 @@ lint = [ requires = ["hatchling"] build-backend = "hatchling.build" +[tool.uv.exclude-newer-package] +# git-pull packages release in lockstep with their workspaces, so a +# fresh release blocking on the 3-day cooldown blocks every +# contributor's `uv sync` until it clears. `false` exempts each +# from any `exclude-newer` constraint (global or per-user) without +# committing a date that would age into the lockfile. Mirrors the +# pattern at vcspull/pyproject.toml for libvcs. +gp-libs = false +gp-furo-theme = false +gp-sphinx = false +libtmux = false +sphinx-autodoc-api-style = false +sphinx-autodoc-argparse = false +sphinx-autodoc-docutils = false +sphinx-autodoc-fastmcp = false +sphinx-autodoc-pytest-fixtures = false +sphinx-autodoc-sphinx = false +sphinx-autodoc-typehints-gp = false +sphinx-fonts = false +sphinx-gp-opengraph = false +sphinx-gp-sitemap = false +sphinx-gp-theme = false +sphinx-ux-autodoc-layout = false +sphinx-ux-badges = false + [tool.coverage.run] branch = true source = [ @@ -158,28 +171,16 @@ files = [ "src/", "tests/", ] -exclude = [ - "tests/docs/", -] enable_incomplete_feature = [] [[tool.mypy.overrides]] module = [ "shtab", - "aafigure", "IPython.*", "ptpython.*", "prompt_toolkit.*", "bpython", - "sphinx_argparse_neo", - "sphinx_argparse_neo.*", - "cli_usage_lexer", - "argparse_lexer", - "argparse_roles", - "docutils", - "docutils.*", - "pygments", - "pygments.*", + "aafigure", ] ignore_missing_imports = true @@ -248,5 +249,5 @@ testpaths = [ ] filterwarnings = [ "ignore:The frontend.Option(Parser)? class.*:DeprecationWarning::", - "ignore:.*invalid escape sequence.*:SyntaxWarning:aafigure:", + "ignore:.*invalid escape sequence.*:SyntaxWarning:.*aafigure.*:", ] diff --git a/src/tmuxp/__about__.py b/src/tmuxp/__about__.py index 48ad3629a1..67acb4eed5 100644 --- a/src/tmuxp/__about__.py +++ b/src/tmuxp/__about__.py @@ -8,7 +8,7 @@ __title__ = "tmuxp" __package_name__ = "tmuxp" -__version__ = "1.67.0" +__version__ = "1.68.0" __description__ = "tmux session manager" __email__ = "tony@git-pull.com" __author__ = "Tony Narlock" diff --git a/tests/docs/__init__.py b/tests/docs/__init__.py deleted file mode 100644 index b6723bfd09..0000000000 --- a/tests/docs/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -"""Tests for documentation extensions.""" - -from __future__ import annotations diff --git a/tests/docs/_ext/__init__.py b/tests/docs/_ext/__init__.py deleted file mode 100644 index 56548488ec..0000000000 --- a/tests/docs/_ext/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -"""Tests for docs/_ext Sphinx extensions.""" - -from __future__ import annotations diff --git a/tests/docs/_ext/conftest.py b/tests/docs/_ext/conftest.py deleted file mode 100644 index fa2919bdcf..0000000000 --- a/tests/docs/_ext/conftest.py +++ /dev/null @@ -1,11 +0,0 @@ -"""Fixtures and configuration for docs extension tests.""" - -from __future__ import annotations - -import pathlib -import sys - -# Add docs/_ext to path so we can import the extension module -docs_ext_path = pathlib.Path(__file__).parent.parent.parent.parent / "docs" / "_ext" -if str(docs_ext_path) not in sys.path: - sys.path.insert(0, str(docs_ext_path)) diff --git a/tests/docs/_ext/sphinx_argparse_neo/__init__.py b/tests/docs/_ext/sphinx_argparse_neo/__init__.py deleted file mode 100644 index 259f37c931..0000000000 --- a/tests/docs/_ext/sphinx_argparse_neo/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -"""Tests for sphinx_argparse_neo extension.""" - -from __future__ import annotations diff --git a/tests/docs/_ext/sphinx_argparse_neo/conftest.py b/tests/docs/_ext/sphinx_argparse_neo/conftest.py deleted file mode 100644 index c805df1f30..0000000000 --- a/tests/docs/_ext/sphinx_argparse_neo/conftest.py +++ /dev/null @@ -1,237 +0,0 @@ -"""Fixtures and configuration for sphinx_argparse_neo tests.""" - -from __future__ import annotations - -import argparse -import pathlib -import sys - -import pytest - -# Add docs/_ext to path so we can import the extension module -docs_ext_path = ( - pathlib.Path(__file__).parent.parent.parent.parent.parent / "docs" / "_ext" -) -if str(docs_ext_path) not in sys.path: - sys.path.insert(0, str(docs_ext_path)) - - -@pytest.fixture -def simple_parser() -> argparse.ArgumentParser: - """Create a simple parser with basic arguments. - - Returns - ------- - argparse.ArgumentParser - Parser with a positional argument and a couple of options. - """ - parser = argparse.ArgumentParser( - prog="myapp", - description="A simple test application", - ) - parser.add_argument("filename", help="Input file to process") - parser.add_argument( - "-v", "--verbose", action="store_true", help="Enable verbose mode" - ) - parser.add_argument("-o", "--output", metavar="FILE", help="Output file") - return parser - - -@pytest.fixture -def parser_with_groups() -> argparse.ArgumentParser: - """Create a parser with custom argument groups. - - Returns - ------- - argparse.ArgumentParser - Parser with multiple argument groups. - """ - parser = argparse.ArgumentParser(prog="grouped", description="Parser with groups") - - input_group = parser.add_argument_group("Input Options", "Options for input") - input_group.add_argument("--input", "-i", required=True, help="Input file") - input_group.add_argument("--format", choices=["json", "yaml"], help="Input format") - - output_group = parser.add_argument_group("Output Options", "Options for output") - output_group.add_argument("--output", "-o", help="Output file") - output_group.add_argument("--pretty", action="store_true", help="Pretty print") - - return parser - - -@pytest.fixture -def parser_with_subcommands() -> argparse.ArgumentParser: - """Create a parser with subcommands. - - Returns - ------- - argparse.ArgumentParser - Parser with subparsers. - """ - parser = argparse.ArgumentParser(prog="cli", description="CLI with subcommands") - parser.add_argument("-v", "--verbose", action="store_true", help="Verbose mode") - - subparsers = parser.add_subparsers(dest="command", help="Available commands") - - # Sync subcommand - sync_parser = subparsers.add_parser("sync", help="Synchronize repositories") - sync_parser.add_argument("repo", nargs="?", help="Repository to sync") - sync_parser.add_argument("-f", "--force", action="store_true", help="Force sync") - - # Add subcommand - add_parser = subparsers.add_parser("add", aliases=["a"], help="Add a repository") - add_parser.add_argument("url", help="Repository URL") - add_parser.add_argument("-n", "--name", help="Repository name") - - return parser - - -@pytest.fixture -def parser_with_mutex() -> argparse.ArgumentParser: - """Create a parser with mutually exclusive arguments. - - Returns - ------- - argparse.ArgumentParser - Parser with mutually exclusive group. - """ - parser = argparse.ArgumentParser(prog="mutex", description="Parser with mutex") - - mutex = parser.add_mutually_exclusive_group(required=True) - mutex.add_argument("-v", "--verbose", action="store_true", help="Verbose output") - mutex.add_argument("-q", "--quiet", action="store_true", help="Quiet output") - - return parser - - -@pytest.fixture -def parser_with_all_actions() -> argparse.ArgumentParser: - """Create a parser with all action types. - - Returns - ------- - argparse.ArgumentParser - Parser demonstrating all action types. - """ - parser = argparse.ArgumentParser(prog="actions", description="All action types") - - # store (default) - parser.add_argument("--name", help="Store action") - - # store_const - parser.add_argument( - "--enable", action="store_const", const="enabled", help="Store const" - ) - - # store_true / store_false - parser.add_argument("--flag", action="store_true", help="Store true") - parser.add_argument("--no-flag", action="store_false", help="Store false") - - # append - parser.add_argument("--item", action="append", help="Append action") - - # append_const - parser.add_argument( - "--debug", - action="append_const", - const="debug", - dest="features", - help="Append const", - ) - - # count - parser.add_argument("-v", "--verbose", action="count", default=0, help="Count") - - # BooleanOptionalAction (Python 3.9+) - parser.add_argument( - "--option", action=argparse.BooleanOptionalAction, help="Boolean optional" - ) - - return parser - - -@pytest.fixture -def parser_with_types() -> argparse.ArgumentParser: - """Create a parser with typed arguments. - - Returns - ------- - argparse.ArgumentParser - Parser with various type specifications. - """ - parser = argparse.ArgumentParser(prog="types", description="Typed arguments") - - parser.add_argument("--count", type=int, help="Integer argument") - parser.add_argument("--ratio", type=float, help="Float argument") - parser.add_argument("--path", type=pathlib.Path, help="Path argument") - parser.add_argument("--choice", type=str, choices=["a", "b", "c"], help="Choices") - - return parser - - -@pytest.fixture -def parser_with_nargs() -> argparse.ArgumentParser: - """Create a parser demonstrating nargs variants. - - Returns - ------- - argparse.ArgumentParser - Parser with various nargs specifications. - """ - parser = argparse.ArgumentParser(prog="nargs", description="Nargs variants") - - parser.add_argument("single", help="Single positional") - parser.add_argument("optional", nargs="?", default="default", help="Optional") - parser.add_argument("zero_or_more", nargs="*", help="Zero or more") - parser.add_argument("--one-or-more", nargs="+", help="One or more") - parser.add_argument("--exactly-two", nargs=2, metavar=("A", "B"), help="Exactly 2") - parser.add_argument("remainder", nargs=argparse.REMAINDER, help="Remainder") - - return parser - - -@pytest.fixture -def parser_with_defaults() -> argparse.ArgumentParser: - """Create a parser with various default values. - - Returns - ------- - argparse.ArgumentParser - Parser demonstrating default handling. - """ - parser = argparse.ArgumentParser(prog="defaults") - - parser.add_argument("--none-default", default=None, help="None default") - parser.add_argument("--string-default", default="hello", help="String default") - parser.add_argument("--int-default", default=42, type=int, help="Int default") - parser.add_argument("--list-default", default=[1, 2], help="List default") - parser.add_argument("--suppress", default=argparse.SUPPRESS, help=argparse.SUPPRESS) - - return parser - - -@pytest.fixture -def nested_subcommands_parser() -> argparse.ArgumentParser: - """Create a parser with nested subcommands. - - Returns - ------- - argparse.ArgumentParser - Parser with multiple levels of subparsers. - """ - parser = argparse.ArgumentParser(prog="nested", description="Nested subcommands") - - level1 = parser.add_subparsers(dest="level1") - - # First level: repo - repo = level1.add_parser("repo", help="Repository commands") - repo_subs = repo.add_subparsers(dest="level2") - - # Second level: repo clone - clone = repo_subs.add_parser("clone", help="Clone a repository") - clone.add_argument("url", help="Repository URL") - - # Second level: repo list - repo_subs.add_parser("list", help="List repositories") - - return parser diff --git a/tests/docs/_ext/sphinx_argparse_neo/test_compat.py b/tests/docs/_ext/sphinx_argparse_neo/test_compat.py deleted file mode 100644 index 417e29a6e1..0000000000 --- a/tests/docs/_ext/sphinx_argparse_neo/test_compat.py +++ /dev/null @@ -1,330 +0,0 @@ -"""Tests for sphinx_argparse_neo.compat module.""" - -from __future__ import annotations - -import sys -import typing as t - -import pytest -from sphinx_argparse_neo.compat import ( - MockModule, - get_parser_from_entry_point, - get_parser_from_module, - import_module, - mock_imports, -) - -# --- MockModule tests --- - - -def test_mock_module_name() -> None: - """Test MockModule name attribute.""" - mock = MockModule("mypackage.submodule") - assert mock.__name__ == "mypackage.submodule" - - -def test_mock_module_repr() -> None: - """Test MockModule string representation.""" - mock = MockModule("mypackage") - assert repr(mock) == "" - - -def test_mock_module_getattr() -> None: - """Test MockModule attribute access.""" - mock = MockModule("mypackage") - child = mock.submodule - - assert isinstance(child, MockModule) - assert child.__name__ == "mypackage.submodule" - - -def test_mock_module_nested_getattr() -> None: - """Test MockModule nested attribute access.""" - mock = MockModule("pkg") - deep = mock.level1.level2.level3 - - assert deep.__name__ == "pkg.level1.level2.level3" - - -def test_mock_module_callable() -> None: - """Test MockModule is callable.""" - mock = MockModule("mypackage") - result = mock() - - assert result is mock - - -def test_mock_module_callable_with_args() -> None: - """Test MockModule callable with arguments.""" - mock = MockModule("mypackage") - result = mock(1, 2, 3, key="value") - - assert result is mock - - -def test_mock_module_chained_call() -> None: - """Test MockModule chained attribute access and call.""" - mock = MockModule("pkg") - result = mock.SomeClass() - - assert isinstance(result, MockModule) - - -# --- mock_imports context manager tests --- - - -def test_mock_imports_adds_to_sys_modules() -> None: - """Test that mock_imports adds modules to sys.modules.""" - module_name = "test_fake_module_xyz" - - assert module_name not in sys.modules - - with mock_imports([module_name]): - assert module_name in sys.modules - assert isinstance(sys.modules[module_name], MockModule) - - assert module_name not in sys.modules - - -def test_mock_imports_multiple_modules() -> None: - """Test mocking multiple modules.""" - modules = ["fake_a", "fake_b", "fake_c"] - - with mock_imports(modules): - for name in modules: - assert name in sys.modules - - for name in modules: - assert name not in sys.modules - - -def test_mock_imports_nested_modules() -> None: - """Test mocking nested module paths.""" - modules = ["fake_pkg", "fake_pkg.sub", "fake_pkg.sub.deep"] - - with mock_imports(modules): - for name in modules: - assert name in sys.modules - - for name in modules: - assert name not in sys.modules - - -def test_mock_imports_does_not_override_existing() -> None: - """Test that mock_imports doesn't override existing modules.""" - # argparse is already imported - original = sys.modules["argparse"] - - with mock_imports(["argparse"]): - # Should not be replaced - assert sys.modules["argparse"] is original - - assert sys.modules["argparse"] is original - - -def test_mock_imports_cleanup_on_exception() -> None: - """Test that mock_imports cleans up even on exception.""" - module_name = "fake_exception_test" - exc_msg = "Test exception" - - with pytest.raises(ValueError), mock_imports([module_name]): - assert module_name in sys.modules - raise ValueError(exc_msg) - - assert module_name not in sys.modules - - -def test_mock_imports_allows_import() -> None: - """Test that mocked modules can be imported.""" - module_name = "fake_importable" - - with mock_imports([module_name]): - # This should work without ImportError - import fake_importable # type: ignore[import-not-found] - - assert fake_importable.__name__ == "fake_importable" - - -# --- import_module tests --- - - -def test_import_module_builtin() -> None: - """Test importing a built-in module.""" - mod = import_module("argparse") - assert hasattr(mod, "ArgumentParser") - - -def test_import_module_stdlib() -> None: - """Test importing a stdlib module.""" - mod = import_module("os.path") - assert hasattr(mod, "join") - - -def test_import_module_not_found() -> None: - """Test importing a non-existent module.""" - with pytest.raises(ModuleNotFoundError): - import_module("nonexistent_module_xyz") - - -# --- get_parser_from_module tests --- - - -def test_get_parser_from_module_argparse() -> None: - """Test getting parser from argparse module itself.""" - # Create a test module with a parser factory - import types - - test_module = types.ModuleType("test_parser_module") - - def create_parser() -> t.Any: - import argparse - - return argparse.ArgumentParser(prog="test") - - test_module.create_parser = create_parser # type: ignore[attr-defined] - sys.modules["test_parser_module"] = test_module - - try: - parser = get_parser_from_module("test_parser_module", "create_parser") - assert parser.prog == "test" - finally: - del sys.modules["test_parser_module"] - - -def test_get_parser_from_module_with_mock() -> None: - """Test getting parser with mocked dependencies.""" - import types - - test_module = types.ModuleType("test_mock_parser") - - def create_parser() -> t.Any: - import argparse - - return argparse.ArgumentParser(prog="mocked") - - test_module.create_parser = create_parser # type: ignore[attr-defined] - sys.modules["test_mock_parser"] = test_module - - try: - parser = get_parser_from_module( - "test_mock_parser", - "create_parser", - mock_modules=["fake_dependency"], - ) - assert parser.prog == "mocked" - finally: - del sys.modules["test_mock_parser"] - - -def test_get_parser_from_module_dotted_path() -> None: - """Test getting parser from class method.""" - import types - - test_module = types.ModuleType("test_class_parser") - - class CLI: - @staticmethod - def create_parser() -> t.Any: - import argparse - - return argparse.ArgumentParser(prog="from_class") - - test_module.CLI = CLI # type: ignore[attr-defined] - sys.modules["test_class_parser"] = test_module - - try: - parser = get_parser_from_module("test_class_parser", "CLI.create_parser") - assert parser.prog == "from_class" - finally: - del sys.modules["test_class_parser"] - - -def test_get_parser_from_module_not_found() -> None: - """Test error when module not found.""" - with pytest.raises(ModuleNotFoundError): - get_parser_from_module("nonexistent_xyz", "func") - - -def test_get_parser_from_module_func_not_found() -> None: - """Test error when function not found.""" - with pytest.raises(AttributeError): - get_parser_from_module("argparse", "nonexistent_func") - - -# --- get_parser_from_entry_point tests --- - - -def test_get_parser_from_entry_point_valid() -> None: - """Test parsing valid entry point format.""" - import types - - test_module = types.ModuleType("test_entry_point") - - def get_parser() -> t.Any: - import argparse - - return argparse.ArgumentParser(prog="entry") - - test_module.get_parser = get_parser # type: ignore[attr-defined] - sys.modules["test_entry_point"] = test_module - - try: - parser = get_parser_from_entry_point("test_entry_point:get_parser") - assert parser.prog == "entry" - finally: - del sys.modules["test_entry_point"] - - -def test_get_parser_from_entry_point_invalid_format() -> None: - """Test error on invalid entry point format.""" - with pytest.raises(ValueError) as exc_info: - get_parser_from_entry_point("no_colon_separator") - - assert "Invalid entry point format" in str(exc_info.value) - - -def test_get_parser_from_entry_point_with_class() -> None: - """Test entry point with class method.""" - import types - - test_module = types.ModuleType("test_entry_class") - - class Factory: - @staticmethod - def parser() -> t.Any: - import argparse - - return argparse.ArgumentParser(prog="factory") - - test_module.Factory = Factory # type: ignore[attr-defined] - sys.modules["test_entry_class"] = test_module - - try: - parser = get_parser_from_entry_point("test_entry_class:Factory.parser") - assert parser.prog == "factory" - finally: - del sys.modules["test_entry_class"] - - -def test_get_parser_from_entry_point_with_mock() -> None: - """Test entry point with mocked modules.""" - import types - - test_module = types.ModuleType("test_entry_mock") - - def make_parser() -> t.Any: - import argparse - - return argparse.ArgumentParser(prog="with_mock") - - test_module.make_parser = make_parser # type: ignore[attr-defined] - sys.modules["test_entry_mock"] = test_module - - try: - parser = get_parser_from_entry_point( - "test_entry_mock:make_parser", - mock_modules=["some_optional_dep"], - ) - assert parser.prog == "with_mock" - finally: - del sys.modules["test_entry_mock"] diff --git a/tests/docs/_ext/sphinx_argparse_neo/test_nodes.py b/tests/docs/_ext/sphinx_argparse_neo/test_nodes.py deleted file mode 100644 index f00594c1dd..0000000000 --- a/tests/docs/_ext/sphinx_argparse_neo/test_nodes.py +++ /dev/null @@ -1,614 +0,0 @@ -"""Tests for sphinx_argparse_neo.nodes module.""" - -from __future__ import annotations - -import re -import typing as t - -import pytest -from docutils import nodes -from sphinx_argparse_neo.nodes import ( - _generate_argument_id, - argparse_argument, - argparse_group, - argparse_program, - argparse_subcommand, - argparse_subcommands, - argparse_usage, -) - -# --- Node creation tests --- - - -def test_argparse_program_creation() -> None: - """Test creating argparse_program node.""" - node = argparse_program() - node["prog"] = "myapp" - - assert node["prog"] == "myapp" - assert isinstance(node, nodes.General) - assert isinstance(node, nodes.Element) - - -def test_argparse_usage_creation() -> None: - """Test creating argparse_usage node.""" - node = argparse_usage() - node["usage"] = "myapp [-h] [--verbose] command" - - assert node["usage"] == "myapp [-h] [--verbose] command" - - -def test_argparse_group_creation() -> None: - """Test creating argparse_group node.""" - node = argparse_group() - node["title"] = "Output Options" - node["description"] = "Control output format" - - assert node["title"] == "Output Options" - assert node["description"] == "Control output format" - - -def test_argparse_argument_creation() -> None: - """Test creating argparse_argument node.""" - node = argparse_argument() - node["names"] = ["-v", "--verbose"] - node["help"] = "Enable verbose mode" - node["metavar"] = None - node["required"] = False - - assert node["names"] == ["-v", "--verbose"] - assert node["help"] == "Enable verbose mode" - - -def test_argparse_subcommands_creation() -> None: - """Test creating argparse_subcommands node.""" - node = argparse_subcommands() - node["title"] = "Commands" - - assert node["title"] == "Commands" - - -def test_argparse_subcommand_creation() -> None: - """Test creating argparse_subcommand node.""" - node = argparse_subcommand() - node["name"] = "sync" - node["aliases"] = ["s"] - node["help"] = "Synchronize repositories" - - assert node["name"] == "sync" - assert node["aliases"] == ["s"] - - -# --- Node nesting tests --- - - -def test_program_can_contain_usage() -> None: - """Test that program node can contain usage node.""" - program = argparse_program() - program["prog"] = "myapp" - - usage = argparse_usage() - usage["usage"] = "myapp [-h]" - - program.append(usage) - - assert len(program.children) == 1 - assert isinstance(program.children[0], argparse_usage) - - -def test_program_can_contain_groups() -> None: - """Test that program node can contain group nodes.""" - program = argparse_program() - - group1 = argparse_group() - group1["title"] = "Positional Arguments" - - group2 = argparse_group() - group2["title"] = "Optional Arguments" - - program.append(group1) - program.append(group2) - - assert len(program.children) == 2 - - -def test_group_can_contain_arguments() -> None: - """Test that group node can contain argument nodes.""" - group = argparse_group() - group["title"] = "Options" - - arg1 = argparse_argument() - arg1["names"] = ["-v"] - - arg2 = argparse_argument() - arg2["names"] = ["-o"] - - group.append(arg1) - group.append(arg2) - - assert len(group.children) == 2 - - -def test_subcommands_can_contain_subcommand() -> None: - """Test that subcommands container can contain subcommand nodes.""" - container = argparse_subcommands() - container["title"] = "Commands" - - sub1 = argparse_subcommand() - sub1["name"] = "sync" - - sub2 = argparse_subcommand() - sub2["name"] = "add" - - container.append(sub1) - container.append(sub2) - - assert len(container.children) == 2 - - -def test_subcommand_can_contain_program() -> None: - """Test that subcommand can contain nested program (for recursion).""" - subcommand = argparse_subcommand() - subcommand["name"] = "sync" - - nested_program = argparse_program() - nested_program["prog"] = "myapp sync" - - subcommand.append(nested_program) - - assert len(subcommand.children) == 1 - assert isinstance(subcommand.children[0], argparse_program) - - -# --- Attribute handling tests --- - - -def test_argument_with_all_attributes() -> None: - """Test argument node with all possible attributes.""" - node = argparse_argument() - node["names"] = ["-f", "--file"] - node["help"] = "Input file" - node["metavar"] = "FILE" - node["required"] = True - node["default_string"] = "input.txt" - node["choices"] = ["a", "b", "c"] - node["type_name"] = "str" - node["mutex"] = False - node["mutex_required"] = False - - assert node["names"] == ["-f", "--file"] - assert node["help"] == "Input file" - assert node["metavar"] == "FILE" - assert node["required"] is True - assert node["default_string"] == "input.txt" - assert node["choices"] == ["a", "b", "c"] - assert node["type_name"] == "str" - - -def test_argument_with_mutex_marker() -> None: - """Test argument node marked as part of mutex group.""" - node = argparse_argument() - node["names"] = ["-v"] - node["mutex"] = True - node["mutex_required"] = True - - assert node["mutex"] is True - assert node["mutex_required"] is True - - -def test_node_get_with_default() -> None: - """Test getting attributes with defaults.""" - node = argparse_argument() - node["names"] = ["-v"] - - # Attribute that exists - assert node.get("names") == ["-v"] - - # Attribute that doesn't exist - get() returns None - assert node.get("nonexistent") is None - - # Attribute with explicit default - assert node.get("help", "default help") == "default help" - - -# --- Full tree construction test --- - - -def test_full_node_tree() -> None: - """Test constructing a complete node tree.""" - # Root program - program = argparse_program() - program["prog"] = "myapp" - - # Usage - usage = argparse_usage() - usage["usage"] = "myapp [-h] [-v] command" - program.append(usage) - - # Positional group - pos_group = argparse_group() - pos_group["title"] = "Positional Arguments" - - cmd_arg = argparse_argument() - cmd_arg["names"] = ["command"] - cmd_arg["help"] = "Command to run" - pos_group.append(cmd_arg) - program.append(pos_group) - - # Optional group - opt_group = argparse_group() - opt_group["title"] = "Optional Arguments" - - verbose = argparse_argument() - verbose["names"] = ["-v", "--verbose"] - verbose["help"] = "Verbose mode" - opt_group.append(verbose) - program.append(opt_group) - - # Subcommands - subs = argparse_subcommands() - subs["title"] = "Commands" - - sync_sub = argparse_subcommand() - sync_sub["name"] = "sync" - sync_sub["help"] = "Sync repos" - subs.append(sync_sub) - - program.append(subs) - - # Verify tree structure - assert len(program.children) == 4 # usage, pos_group, opt_group, subs - assert isinstance(program.children[0], argparse_usage) - assert isinstance(program.children[1], argparse_group) - assert isinstance(program.children[2], argparse_group) - assert isinstance(program.children[3], argparse_subcommands) - - -# --- ID generation tests --- - - -def test_generate_argument_id_short_option() -> None: - """Test ID generation for short option.""" - assert _generate_argument_id(["-L"]) == "L" - - -def test_generate_argument_id_long_option() -> None: - """Test ID generation for long option.""" - assert _generate_argument_id(["--help"]) == "help" - - -def test_generate_argument_id_multiple_names() -> None: - """Test ID generation for argument with multiple names.""" - assert _generate_argument_id(["-v", "--verbose"]) == "v-verbose" - - -def test_generate_argument_id_with_prefix() -> None: - """Test ID generation with prefix for namespace isolation.""" - assert _generate_argument_id(["-L"], "shell") == "shell-L" - assert _generate_argument_id(["--help"], "load") == "load-help" - - -def test_generate_argument_id_positional() -> None: - """Test ID generation for positional argument.""" - assert _generate_argument_id(["filename"]) == "filename" - - -def test_generate_argument_id_empty() -> None: - """Test ID generation with empty names list.""" - assert _generate_argument_id([]) == "" - - -def test_generate_argument_id_prefix_no_names() -> None: - """Test that prefix alone doesn't create ID when no names.""" - assert _generate_argument_id([], "shell") == "" - - -# --- HTML rendering tests using NamedTuple for parametrization --- - - -class ArgumentHTMLCase(t.NamedTuple): - """Test case for argument HTML rendering.""" - - test_id: str - names: list[str] - metavar: str | None - help_text: str | None - default: str | None - type_name: str | None - required: bool - id_prefix: str - expected_patterns: list[str] # Regex patterns to match - - -ARGUMENT_HTML_CASES = [ - ArgumentHTMLCase( - test_id="short_option_with_metavar", - names=["-L"], - metavar="socket-name", - help_text="pass-through for tmux -L", - default="None", - type_name=None, - required=False, - id_prefix="shell", - expected_patterns=[ - r'
', - r'
', - r'-L', - r'socket-name', - r'', - r'
', - r'
Default
', - r'
None
', - r"
", - ], - ), - ArgumentHTMLCase( - test_id="long_option", - names=["--help"], - metavar=None, - help_text="show help", - default=None, - type_name=None, - required=False, - id_prefix="", - expected_patterns=[ - r'--help', - r'id="help"', - r'href="#help"', - ], - ), - ArgumentHTMLCase( - test_id="positional_argument", - names=["filename"], - metavar=None, - help_text="input file", - default=None, - type_name=None, - required=False, - id_prefix="", - expected_patterns=[ - r'filename', - r'id="filename"', - ], - ), - ArgumentHTMLCase( - test_id="multiple_names", - names=["-v", "--verbose"], - metavar=None, - help_text="Enable verbose mode", - default=None, - type_name=None, - required=False, - id_prefix="load", - expected_patterns=[ - r'id="load-v-verbose"', - r'-v', - r'--verbose', - r'href="#load-v-verbose"', - ], - ), - ArgumentHTMLCase( - test_id="metadata_definition_list", - names=["workspace_file"], - metavar="workspace-file", - help_text="checks current tmuxp for workspace files.", - default="None", - type_name="str", - required=True, - id_prefix="edit", - expected_patterns=[ - r'
', - r'
Default
', - r'
None
', - r'
Type
', - r'
str
', - r'
Required
', - r"
", - ], - ), -] - - -class MockTranslator: - """Mock HTML5Translator for testing HTML generation.""" - - def __init__(self) -> None: - """Initialize mock translator.""" - self.body: list[str] = [] - - def encode(self, text: str) -> str: - """HTML encode text.""" - return str(text).replace("&", "&").replace("<", "<").replace(">", ">") - - -def render_argument_to_html( - names: list[str], - metavar: str | None, - help_text: str | None, - default: str | None, - type_name: str | None, - required: bool, - id_prefix: str, -) -> str: - """Render an argument node to HTML string for testing. - - Parameters - ---------- - names - Argument names (e.g., ["-v", "--verbose"]). - metavar - Optional metavar (e.g., "FILE"). - help_text - Help text for the argument. - default - Default value string. - type_name - Type name for the argument (e.g., "str", "int"). - required - Whether the argument is required. - id_prefix - Prefix for the argument ID. - - Returns - ------- - str - HTML string from the mock translator's body. - """ - from sphinx_argparse_neo.nodes import ( - depart_argparse_argument_html, - visit_argparse_argument_html, - ) - - node = argparse_argument() - node["names"] = names - node["metavar"] = metavar - node["help"] = help_text - node["default_string"] = default - node["type_name"] = type_name - node["required"] = required - node["id_prefix"] = id_prefix - - translator = MockTranslator() - visit_argparse_argument_html(translator, node) - depart_argparse_argument_html(translator, node) - - return "".join(translator.body) - - -@pytest.mark.parametrize( - "case", - ARGUMENT_HTML_CASES, - ids=lambda c: c.test_id, -) -def test_argument_html_rendering(case: ArgumentHTMLCase) -> None: - """Test HTML output for argument nodes.""" - html = render_argument_to_html( - names=case.names, - metavar=case.metavar, - help_text=case.help_text, - default=case.default, - type_name=case.type_name, - required=case.required, - id_prefix=case.id_prefix, - ) - - for pattern in case.expected_patterns: - assert re.search(pattern, html), f"Pattern not found: {pattern}\nHTML: {html}" - - -def test_argument_wrapper_has_id() -> None: - """Verify wrapper div has correct ID attribute.""" - html = render_argument_to_html( - names=["-f", "--file"], - metavar="PATH", - help_text="Input file", - default=None, - type_name=None, - required=False, - id_prefix="convert", - ) - - assert 'id="convert-f-file"' in html - assert '
None: - """Verify headerlink anchor exists with correct href.""" - html = render_argument_to_html( - names=["--output"], - metavar="FILE", - help_text="Output file", - default=None, - type_name=None, - required=False, - id_prefix="freeze", - ) - - assert '' in html - - -def test_default_value_styled() -> None: - """Verify default value is wrapped in nv span within definition list.""" - html = render_argument_to_html( - names=["--format"], - metavar=None, - help_text="Output format", - default="json", - type_name=None, - required=False, - id_prefix="", - ) - - assert '
' in html - assert '
Default
' in html - assert '
json
' in html - - -def test_wrapper_div_closed() -> None: - """Verify wrapper div is properly closed.""" - html = render_argument_to_html( - names=["-v"], - metavar=None, - help_text="Verbose", - default=None, - type_name=None, - required=False, - id_prefix="", - ) - - # Count opening and closing div tags - open_divs = html.count("") - assert open_divs == close_divs, f"Unbalanced divs in HTML: {html}" - - -def test_argument_no_id_prefix() -> None: - """Test argument rendering without ID prefix.""" - html = render_argument_to_html( - names=["--debug"], - metavar=None, - help_text="Enable debug mode", - default=None, - type_name=None, - required=False, - id_prefix="", - ) - - assert 'id="debug"' in html - assert 'href="#debug"' in html - - -def test_metadata_uses_definition_list() -> None: - """Verify metadata renders as definition list, not inline paragraph.""" - html = render_argument_to_html( - names=["--format"], - metavar=None, - help_text="Output format", - default="json", - type_name="str", - required=False, - id_prefix="", - ) - - assert '
' in html - assert '
Default
' in html - assert '
json
' in html - assert '
Type
' in html - assert '
str
' in html - - -def test_required_renders_as_tag() -> None: - """Verify Required renders as standalone tag, not key-value.""" - html = render_argument_to_html( - names=["--config"], - metavar="FILE", - help_text="Config file", - default=None, - type_name=None, - required=True, - id_prefix="", - ) - - assert '
Required
' in html - # Should NOT have a matching dd for Required - assert 'argparse-meta-value">Required' not in html diff --git a/tests/docs/_ext/sphinx_argparse_neo/test_parser.py b/tests/docs/_ext/sphinx_argparse_neo/test_parser.py deleted file mode 100644 index 48f43d937f..0000000000 --- a/tests/docs/_ext/sphinx_argparse_neo/test_parser.py +++ /dev/null @@ -1,524 +0,0 @@ -"""Tests for sphinx_argparse_neo.parser module.""" - -from __future__ import annotations - -import argparse -import typing as t - -import pytest -from sphinx_argparse_neo.parser import ( - ArgumentInfo, - ParserInfo, - SubcommandInfo, - _extract_argument, - _format_default, - _get_action_name, - _get_type_name, - extract_parser, -) - -# --- _format_default tests --- - - -class FormatDefaultFixture(t.NamedTuple): - """Test fixture for _format_default function.""" - - test_id: str - default: t.Any - expected: str | None - - -FORMAT_DEFAULT_FIXTURES: list[FormatDefaultFixture] = [ - FormatDefaultFixture( - test_id="none_value", - default=None, - expected="None", - ), - FormatDefaultFixture( - test_id="string_value", - default="hello", - expected="hello", - ), - FormatDefaultFixture( - test_id="integer_value", - default=42, - expected="42", - ), - FormatDefaultFixture( - test_id="float_value", - default=3.14, - expected="3.14", - ), - FormatDefaultFixture( - test_id="list_value", - default=[1, 2, 3], - expected="[1, 2, 3]", - ), - FormatDefaultFixture( - test_id="suppress_value", - default=argparse.SUPPRESS, - expected=None, - ), - FormatDefaultFixture( - test_id="empty_string", - default="", - expected="", - ), - FormatDefaultFixture( - test_id="boolean_true", - default=True, - expected="True", - ), - FormatDefaultFixture( - test_id="boolean_false", - default=False, - expected="False", - ), -] - - -@pytest.mark.parametrize( - FormatDefaultFixture._fields, - FORMAT_DEFAULT_FIXTURES, - ids=[f.test_id for f in FORMAT_DEFAULT_FIXTURES], -) -def test_format_default(test_id: str, default: t.Any, expected: str | None) -> None: - """Test default value formatting.""" - assert _format_default(default) == expected - - -# --- _get_type_name tests --- - - -def test_get_type_name_int() -> None: - """Test type name extraction for int.""" - parser = argparse.ArgumentParser() - action = parser.add_argument("--count", type=int) - assert _get_type_name(action) == "int" - - -def test_get_type_name_float() -> None: - """Test type name extraction for float.""" - parser = argparse.ArgumentParser() - action = parser.add_argument("--ratio", type=float) - assert _get_type_name(action) == "float" - - -def test_get_type_name_str() -> None: - """Test type name extraction for str.""" - parser = argparse.ArgumentParser() - action = parser.add_argument("--name", type=str) - assert _get_type_name(action) == "str" - - -def test_get_type_name_none() -> None: - """Test type name extraction when no type specified.""" - parser = argparse.ArgumentParser() - action = parser.add_argument("--name") - assert _get_type_name(action) is None - - -def test_get_type_name_callable() -> None: - """Test type name extraction for callable types.""" - from pathlib import Path - - parser = argparse.ArgumentParser() - action = parser.add_argument("--path", type=Path) - assert _get_type_name(action) == "Path" - - -# --- _get_action_name tests --- - - -class ActionNameFixture(t.NamedTuple): - """Test fixture for _get_action_name function.""" - - test_id: str - action_kwargs: dict[str, t.Any] - expected: str - - -ACTION_NAME_FIXTURES: list[ActionNameFixture] = [ - ActionNameFixture( - test_id="store_default", - action_kwargs={"dest": "name"}, - expected="store", - ), - ActionNameFixture( - test_id="store_true", - action_kwargs={"action": "store_true", "dest": "flag"}, - expected="store_true", - ), - ActionNameFixture( - test_id="store_false", - action_kwargs={"action": "store_false", "dest": "flag"}, - expected="store_false", - ), - ActionNameFixture( - test_id="store_const", - action_kwargs={"action": "store_const", "const": "value", "dest": "const"}, - expected="store_const", - ), - ActionNameFixture( - test_id="append", - action_kwargs={"action": "append", "dest": "items"}, - expected="append", - ), - ActionNameFixture( - test_id="count", - action_kwargs={"action": "count", "dest": "verbose"}, - expected="count", - ), -] - - -@pytest.mark.parametrize( - ActionNameFixture._fields, - ACTION_NAME_FIXTURES, - ids=[f.test_id for f in ACTION_NAME_FIXTURES], -) -def test_get_action_name( - test_id: str, action_kwargs: dict[str, t.Any], expected: str -) -> None: - """Test action name extraction.""" - parser = argparse.ArgumentParser() - action = parser.add_argument("--test", **action_kwargs) - assert _get_action_name(action) == expected - - -# --- _extract_argument tests --- - - -def test_extract_argument_positional() -> None: - """Test extracting a positional argument.""" - parser = argparse.ArgumentParser() - action = parser.add_argument("filename", help="Input file") - info = _extract_argument(action) - - assert info.names == ["filename"] - assert info.help == "Input file" - assert info.is_positional is True - assert info.required is True - - -def test_extract_argument_optional() -> None: - """Test extracting an optional argument.""" - parser = argparse.ArgumentParser() - action = parser.add_argument("-v", "--verbose", action="store_true", help="Verbose") - info = _extract_argument(action) - - assert info.names == ["-v", "--verbose"] - assert info.action == "store_true" - assert info.is_positional is False - assert info.required is False - - -def test_extract_argument_with_choices() -> None: - """Test extracting argument with choices.""" - parser = argparse.ArgumentParser() - action = parser.add_argument("--format", choices=["json", "yaml", "xml"]) - info = _extract_argument(action) - - assert info.choices == ["json", "yaml", "xml"] - - -def test_extract_argument_with_metavar() -> None: - """Test extracting argument with metavar.""" - parser = argparse.ArgumentParser() - action = parser.add_argument("--output", metavar="FILE") - info = _extract_argument(action) - - assert info.metavar == "FILE" - - -def test_extract_argument_tuple_metavar() -> None: - """Test extracting argument with tuple metavar.""" - parser = argparse.ArgumentParser() - action = parser.add_argument("--range", nargs=2, metavar=("MIN", "MAX")) - info = _extract_argument(action) - - assert info.metavar == "MIN MAX" - - -def test_extract_argument_suppressed_help() -> None: - """Test extracting argument with suppressed help.""" - parser = argparse.ArgumentParser() - action = parser.add_argument("--secret", help=argparse.SUPPRESS) - info = _extract_argument(action) - - assert info.help is None - - -# --- extract_parser integration tests --- - - -def test_extract_parser_simple(simple_parser: argparse.ArgumentParser) -> None: - """Test extracting a simple parser.""" - info = extract_parser(simple_parser) - - assert info.prog == "myapp" - assert info.description == "A simple test application" - assert len(info.argument_groups) >= 1 - - # Find arguments - all_args = [arg for group in info.argument_groups for arg in group.arguments] - arg_names = [name for arg in all_args for name in arg.names] - - assert "filename" in arg_names - assert "--verbose" in arg_names or "-v" in arg_names - - -def test_extract_parser_with_groups( - parser_with_groups: argparse.ArgumentParser, -) -> None: - """Test extracting parser with custom groups.""" - info = extract_parser(parser_with_groups) - - group_titles = [g.title for g in info.argument_groups] - assert "Input Options" in group_titles - assert "Output Options" in group_titles - - -def test_extract_parser_with_subcommands( - parser_with_subcommands: argparse.ArgumentParser, -) -> None: - """Test extracting parser with subcommands.""" - info = extract_parser(parser_with_subcommands) - - assert info.subcommands is not None - assert len(info.subcommands) == 2 - - subcmd_names = [s.name for s in info.subcommands] - assert "sync" in subcmd_names - assert "add" in subcmd_names - - # Check alias - add_cmd = next(s for s in info.subcommands if s.name == "add") - assert "a" in add_cmd.aliases - - -def test_extract_parser_with_mutex(parser_with_mutex: argparse.ArgumentParser) -> None: - """Test extracting parser with mutually exclusive group.""" - info = extract_parser(parser_with_mutex) - - # Find the group with mutex - for group in info.argument_groups: - if group.mutually_exclusive: - mutex = group.mutually_exclusive[0] - assert mutex.required is True - assert len(mutex.arguments) == 2 - return - - pytest.fail("No mutually exclusive group found") - - -def test_extract_parser_with_all_actions( - parser_with_all_actions: argparse.ArgumentParser, -) -> None: - """Test extracting parser with all action types.""" - info = extract_parser(parser_with_all_actions) - - all_args = [arg for group in info.argument_groups for arg in group.arguments] - actions = {arg.dest: arg.action for arg in all_args} - - assert actions.get("name") == "store" - assert actions.get("enable") == "store_const" - assert actions.get("flag") == "store_true" - assert actions.get("item") == "append" - assert actions.get("verbose") == "count" - - -def test_extract_parser_with_types( - parser_with_types: argparse.ArgumentParser, -) -> None: - """Test extracting parser with typed arguments.""" - info = extract_parser(parser_with_types) - - all_args = [arg for group in info.argument_groups for arg in group.arguments] - types = {arg.dest: arg.type_name for arg in all_args} - - assert types.get("count") == "int" - assert types.get("ratio") == "float" - assert types.get("path") == "Path" - - -def test_extract_parser_with_nargs( - parser_with_nargs: argparse.ArgumentParser, -) -> None: - """Test extracting parser with nargs variants.""" - info = extract_parser(parser_with_nargs) - - all_args = [arg for group in info.argument_groups for arg in group.arguments] - nargs_map = {arg.dest: arg.nargs for arg in all_args} - - assert nargs_map.get("optional") == "?" - assert nargs_map.get("zero_or_more") == "*" - assert nargs_map.get("one_or_more") == "+" - assert nargs_map.get("exactly_two") == 2 - - -def test_extract_parser_with_defaults( - parser_with_defaults: argparse.ArgumentParser, -) -> None: - """Test extracting parser with various defaults.""" - info = extract_parser(parser_with_defaults) - - all_args = [arg for group in info.argument_groups for arg in group.arguments] - defaults = {arg.dest: arg.default_string for arg in all_args} - - assert defaults.get("none_default") == "None" - assert defaults.get("string_default") == "hello" - assert defaults.get("int_default") == "42" - # Suppressed default should have None default_string - assert "suppress" not in defaults or defaults.get("suppress") is None - - -def test_extract_parser_nested_subcommands( - nested_subcommands_parser: argparse.ArgumentParser, -) -> None: - """Test extracting parser with nested subcommands.""" - info = extract_parser(nested_subcommands_parser) - - assert info.subcommands is not None - assert len(info.subcommands) == 1 - - repo = info.subcommands[0] - assert repo.name == "repo" - assert repo.parser.subcommands is not None - assert len(repo.parser.subcommands) == 2 - - -def test_extract_parser_usage_generation() -> None: - """Test usage string generation.""" - parser = argparse.ArgumentParser(prog="test") - parser.add_argument("file") - parser.add_argument("-v", "--verbose", action="store_true") - - info = extract_parser(parser) - - assert "test" in info.bare_usage - assert "file" in info.bare_usage - - -def test_extract_parser_custom_usage() -> None: - """Test parser with custom usage string.""" - parser = argparse.ArgumentParser(prog="test", usage="test [options] file") - - info = extract_parser(parser) - - assert info.usage == "test [options] file" - - -def test_extract_parser_with_epilog() -> None: - """Test parser with epilog.""" - parser = argparse.ArgumentParser( - prog="test", - epilog="For more info, see docs.", - ) - - info = extract_parser(parser) - - assert info.epilog == "For more info, see docs." - - -# --- ArgumentInfo property tests --- - - -def test_argument_info_is_positional_true() -> None: - """Test is_positional for positional argument.""" - info = ArgumentInfo( - names=["filename"], - help=None, - default=None, - default_string=None, - choices=None, - required=True, - metavar=None, - nargs=None, - action="store", - type_name=None, - const=None, - dest="filename", - ) - assert info.is_positional is True - - -def test_argument_info_is_positional_false() -> None: - """Test is_positional for optional argument.""" - info = ArgumentInfo( - names=["-f", "--file"], - help=None, - default=None, - default_string=None, - choices=None, - required=False, - metavar=None, - nargs=None, - action="store", - type_name=None, - const=None, - dest="file", - ) - assert info.is_positional is False - - -def test_argument_info_empty_names() -> None: - """Test is_positional with empty names list.""" - info = ArgumentInfo( - names=[], - help=None, - default=None, - default_string=None, - choices=None, - required=False, - metavar=None, - nargs=None, - action="store", - type_name=None, - const=None, - dest="empty", - ) - assert info.is_positional is False - - -# --- Dataclass tests --- - - -def test_parser_info_dataclass() -> None: - """Test ParserInfo dataclass creation.""" - info = ParserInfo( - prog="test", - usage=None, - bare_usage="test [-h]", - description="Test description", - epilog="Test epilog", - argument_groups=[], - subcommands=None, - subcommand_dest=None, - ) - - assert info.prog == "test" - assert info.description == "Test description" - - -def test_subcommand_info_recursive() -> None: - """Test SubcommandInfo with nested parser.""" - nested_info = ParserInfo( - prog="nested", - usage=None, - bare_usage="nested [-h]", - description=None, - epilog=None, - argument_groups=[], - subcommands=None, - subcommand_dest=None, - ) - - sub = SubcommandInfo( - name="sub", - aliases=[], - help="Subcommand help", - parser=nested_info, - ) - - assert sub.parser.prog == "nested" diff --git a/tests/docs/_ext/sphinx_argparse_neo/test_renderer.py b/tests/docs/_ext/sphinx_argparse_neo/test_renderer.py deleted file mode 100644 index 0a0b0da698..0000000000 --- a/tests/docs/_ext/sphinx_argparse_neo/test_renderer.py +++ /dev/null @@ -1,498 +0,0 @@ -"""Tests for sphinx_argparse_neo.renderer module.""" - -from __future__ import annotations - -import argparse -import typing as t - -from docutils import nodes -from sphinx_argparse_neo.nodes import ( - argparse_argument, - argparse_group, - argparse_program, - argparse_subcommand, - argparse_subcommands, - argparse_usage, -) -from sphinx_argparse_neo.parser import ( - ArgumentGroup, - ArgumentInfo, - MutuallyExclusiveGroup, - ParserInfo, - SubcommandInfo, - extract_parser, -) -from sphinx_argparse_neo.renderer import ( - ArgparseRenderer, - RenderConfig, - create_renderer, -) - -# --- RenderConfig tests --- - - -def test_render_config_defaults() -> None: - """Test RenderConfig default values.""" - config = RenderConfig() - - assert config.group_title_prefix == "" - assert config.show_defaults is True - assert config.show_choices is True - assert config.show_types is True - - -def test_render_config_custom_values() -> None: - """Test RenderConfig with custom values.""" - config = RenderConfig( - group_title_prefix="CLI ", - show_defaults=False, - show_choices=False, - show_types=False, - ) - - assert config.group_title_prefix == "CLI " - assert config.show_defaults is False - assert config.show_choices is False - assert config.show_types is False - - -# --- ArgparseRenderer basic tests --- - - -def test_renderer_creation_default_config() -> None: - """Test creating renderer with default config.""" - renderer = ArgparseRenderer() - - assert renderer.config is not None - assert renderer.config.show_defaults is True - - -def test_renderer_creation_custom_config() -> None: - """Test creating renderer with custom config.""" - config = RenderConfig(group_title_prefix="CLI ") - renderer = ArgparseRenderer(config=config) - - assert renderer.config.group_title_prefix == "CLI " - - -def test_create_renderer_factory() -> None: - """Test create_renderer factory function.""" - renderer = create_renderer() - assert isinstance(renderer, ArgparseRenderer) - - -def test_create_renderer_with_config() -> None: - """Test create_renderer with custom config.""" - config = RenderConfig(show_types=False) - renderer = create_renderer(config=config) - - assert renderer.config.show_types is False - - -# --- Render method tests --- - - -def test_render_simple_parser(simple_parser: argparse.ArgumentParser) -> None: - """Test rendering a simple parser produces sibling nodes for TOC. - - The renderer now outputs sections as siblings of argparse_program: - - argparse_program (description only) - - section#usage - - section#positional-arguments - - section#options - """ - parser_info = extract_parser(simple_parser) - renderer = ArgparseRenderer() - rendered_nodes = renderer.render(parser_info) - - # Should have multiple nodes: program + usage section + group sections - assert len(rendered_nodes) >= 3 - - # First node is argparse_program - assert isinstance(rendered_nodes[0], argparse_program) - assert rendered_nodes[0]["prog"] == "myapp" - - # Second node should be usage section - assert isinstance(rendered_nodes[1], nodes.section) - assert "usage" in rendered_nodes[1]["ids"] - - -def test_render_includes_usage(simple_parser: argparse.ArgumentParser) -> None: - """Test that render includes usage as a sibling section.""" - parser_info = extract_parser(simple_parser) - renderer = ArgparseRenderer() - rendered_nodes = renderer.render(parser_info) - - # Find the usage section (sibling of program, not child) - usage_sections = [ - n - for n in rendered_nodes - if isinstance(n, nodes.section) and "usage" in n.get("ids", []) - ] - - assert len(usage_sections) == 1 - - # Usage section should contain argparse_usage node - usage_section = usage_sections[0] - usage_node = [c for c in usage_section.children if isinstance(c, argparse_usage)] - assert len(usage_node) == 1 - assert "myapp" in usage_node[0]["usage"] - - -def test_render_includes_groups(simple_parser: argparse.ArgumentParser) -> None: - """Test that render includes argument groups as sibling sections.""" - parser_info = extract_parser(simple_parser) - renderer = ArgparseRenderer() - rendered_nodes = renderer.render(parser_info) - - # Groups are now wrapped in sections and are siblings of program - # Find sections that contain argparse_group nodes - group_sections = [ - n - for n in rendered_nodes - if isinstance(n, nodes.section) - and any(isinstance(c, argparse_group) for c in n.children) - ] - - assert len(group_sections) >= 1 - - -def test_render_groups_contain_arguments( - simple_parser: argparse.ArgumentParser, -) -> None: - """Test that rendered groups contain argument nodes.""" - parser_info = extract_parser(simple_parser) - renderer = ArgparseRenderer() - rendered_nodes = renderer.render(parser_info) - - # Find sections that contain argparse_group nodes - group_sections = [ - n - for n in rendered_nodes - if isinstance(n, nodes.section) - and any(isinstance(c, argparse_group) for c in n.children) - ] - - # Collect all arguments from groups inside sections - all_args: list[argparse_argument] = [] - for section in group_sections: - for child in section.children: - if isinstance(child, argparse_group): - all_args.extend( - arg for arg in child.children if isinstance(arg, argparse_argument) - ) - - assert len(all_args) >= 1 - - -def test_render_with_subcommands( - parser_with_subcommands: argparse.ArgumentParser, -) -> None: - """Test rendering parser with subcommands.""" - parser_info = extract_parser(parser_with_subcommands) - renderer = ArgparseRenderer() - rendered_nodes = renderer.render(parser_info) - - # Subcommands node is a sibling of program - subcommands_nodes = [ - n for n in rendered_nodes if isinstance(n, argparse_subcommands) - ] - - assert len(subcommands_nodes) == 1 - - # Check subcommand children - subs_container = subcommands_nodes[0] - subcmd_nodes = [ - c for c in subs_container.children if isinstance(c, argparse_subcommand) - ] - assert len(subcmd_nodes) == 2 - - -# --- Config option effect tests --- - - -def _collect_args_from_rendered_nodes( - rendered_nodes: list[nodes.Node], -) -> list[argparse_argument]: - """Collect all argparse_argument nodes from rendered output.""" - all_args: list[argparse_argument] = [] - for node in rendered_nodes: - if isinstance(node, nodes.section): - for child in node.children: - if isinstance(child, argparse_group): - all_args.extend( - arg - for arg in child.children - if isinstance(arg, argparse_argument) - ) - return all_args - - -def test_render_group_title_prefix() -> None: - """Test that group_title_prefix is applied to section titles.""" - parser = argparse.ArgumentParser(prog="test") - parser.add_argument("--opt") - parser_info = extract_parser(parser) - - config = RenderConfig(group_title_prefix="CLI: ") - renderer = ArgparseRenderer(config=config) - rendered_nodes = renderer.render(parser_info) - - # Find sections that contain argparse_group - group_sections = [ - n - for n in rendered_nodes - if isinstance(n, nodes.section) - and any(isinstance(c, argparse_group) for c in n.children) - ] - - # Section IDs should include the prefix (normalized) - ids = [section["ids"][0] for section in group_sections if section["ids"]] - assert any("cli:" in id_str.lower() for id_str in ids) - - -def test_render_show_defaults_false() -> None: - """Test that show_defaults=False hides defaults.""" - parser = argparse.ArgumentParser(prog="test") - parser.add_argument("--opt", default="value") - parser_info = extract_parser(parser) - - config = RenderConfig(show_defaults=False) - renderer = ArgparseRenderer(config=config) - rendered_nodes = renderer.render(parser_info) - - all_args = _collect_args_from_rendered_nodes(rendered_nodes) - - # Default string should not be set - for arg in all_args: - assert arg.get("default_string") is None - - -def test_render_show_choices_false() -> None: - """Test that show_choices=False hides choices.""" - parser = argparse.ArgumentParser(prog="test") - parser.add_argument("--format", choices=["json", "yaml"]) - parser_info = extract_parser(parser) - - config = RenderConfig(show_choices=False) - renderer = ArgparseRenderer(config=config) - rendered_nodes = renderer.render(parser_info) - - all_args = _collect_args_from_rendered_nodes(rendered_nodes) - - # Choices should not be set - for arg in all_args: - assert arg.get("choices") is None - - -def test_render_show_types_false() -> None: - """Test that show_types=False hides type info.""" - parser = argparse.ArgumentParser(prog="test") - parser.add_argument("--count", type=int) - parser_info = extract_parser(parser) - - config = RenderConfig(show_types=False) - renderer = ArgparseRenderer(config=config) - rendered_nodes = renderer.render(parser_info) - - all_args = _collect_args_from_rendered_nodes(rendered_nodes) - - # Type name should not be set - for arg in all_args: - assert arg.get("type_name") is None - - -# --- Individual render method tests --- - - -def test_render_usage_method() -> None: - """Test render_usage method directly.""" - parser_info = ParserInfo( - prog="test", - usage=None, - bare_usage="test [-h] [-v]", - description=None, - epilog=None, - argument_groups=[], - subcommands=None, - subcommand_dest=None, - ) - - renderer = ArgparseRenderer() - usage_node = renderer.render_usage(parser_info) - - assert isinstance(usage_node, argparse_usage) - assert usage_node["usage"] == "test [-h] [-v]" - - -def test_render_argument_method() -> None: - """Test render_argument method directly.""" - arg_info = ArgumentInfo( - names=["-v", "--verbose"], - help="Enable verbose mode", - default=False, - default_string="False", - choices=None, - required=False, - metavar=None, - nargs=None, - action="store_true", - type_name=None, - const=True, - dest="verbose", - ) - - renderer = ArgparseRenderer() - arg_node = renderer.render_argument(arg_info) - - assert isinstance(arg_node, argparse_argument) - assert arg_node["names"] == ["-v", "--verbose"] - assert arg_node["help"] == "Enable verbose mode" - - -def test_render_group_method() -> None: - """Test render_group method directly.""" - group_info = ArgumentGroup( - title="Options", - description="Available options", - arguments=[ - ArgumentInfo( - names=["-v"], - help="Verbose", - default=False, - default_string="False", - choices=None, - required=False, - metavar=None, - nargs=None, - action="store_true", - type_name=None, - const=True, - dest="verbose", - ), - ], - mutually_exclusive=[], - ) - - renderer = ArgparseRenderer() - group_node = renderer.render_group(group_info) - - assert isinstance(group_node, argparse_group) - assert group_node["title"] == "Options" - assert group_node["description"] == "Available options" - assert len(group_node.children) == 1 - - -def test_render_mutex_group_method() -> None: - """Test render_mutex_group method.""" - mutex = MutuallyExclusiveGroup( - arguments=[ - ArgumentInfo( - names=["-v"], - help="Verbose", - default=False, - default_string="False", - choices=None, - required=False, - metavar=None, - nargs=None, - action="store_true", - type_name=None, - const=True, - dest="verbose", - ), - ArgumentInfo( - names=["-q"], - help="Quiet", - default=False, - default_string="False", - choices=None, - required=False, - metavar=None, - nargs=None, - action="store_true", - type_name=None, - const=True, - dest="quiet", - ), - ], - required=True, - ) - - renderer = ArgparseRenderer() - nodes = renderer.render_mutex_group(mutex) - - assert len(nodes) == 2 - assert all(isinstance(n, argparse_argument) for n in nodes) - assert all(n.get("mutex") is True for n in nodes) - assert all(n.get("mutex_required") is True for n in nodes) - - -def test_render_subcommand_method() -> None: - """Test render_subcommand method.""" - nested_parser = ParserInfo( - prog="myapp sub", - usage=None, - bare_usage="myapp sub [-h]", - description="Subcommand description", - epilog=None, - argument_groups=[], - subcommands=None, - subcommand_dest=None, - ) - - subcmd_info = SubcommandInfo( - name="sub", - aliases=["s"], - help="Subcommand help", - parser=nested_parser, - ) - - renderer = ArgparseRenderer() - subcmd_node = renderer.render_subcommand(subcmd_info) - - assert isinstance(subcmd_node, argparse_subcommand) - assert subcmd_node["name"] == "sub" - assert subcmd_node["aliases"] == ["s"] - assert subcmd_node["help"] == "Subcommand help" - - # Should have nested program - nested = [c for c in subcmd_node.children if isinstance(c, argparse_program)] - assert len(nested) == 1 - - -# --- Post-process hook test --- - - -def test_post_process_default() -> None: - """Test that default post_process returns nodes unchanged.""" - renderer = ArgparseRenderer() - - from docutils import nodes as dn - - input_nodes = [dn.paragraph(text="test")] - - result = renderer.post_process(input_nodes) - - assert result == input_nodes - - -def test_post_process_custom() -> None: - """Test custom post_process implementation.""" - - class CustomRenderer(ArgparseRenderer): # type: ignore[misc] - def post_process(self, result_nodes: list[t.Any]) -> list[t.Any]: - # Add a marker to each node - for node in result_nodes: - node["custom_marker"] = True - return result_nodes - - renderer = CustomRenderer() - - from docutils import nodes as dn - - input_nodes = [dn.paragraph(text="test")] - - result = renderer.post_process(input_nodes) - - assert result[0].get("custom_marker") is True diff --git a/tests/docs/_ext/sphinx_argparse_neo/test_utils.py b/tests/docs/_ext/sphinx_argparse_neo/test_utils.py deleted file mode 100644 index 129a8fd36d..0000000000 --- a/tests/docs/_ext/sphinx_argparse_neo/test_utils.py +++ /dev/null @@ -1,162 +0,0 @@ -"""Tests for sphinx_argparse_neo text processing utilities.""" - -from __future__ import annotations - -import typing as t - -import pytest -from sphinx_argparse_neo.utils import escape_rst_emphasis, strip_ansi - -# --- strip_ansi tests --- - - -class StripAnsiFixture(t.NamedTuple): - """Test fixture for strip_ansi function.""" - - test_id: str - input_text: str - expected: str - - -STRIP_ANSI_FIXTURES: list[StripAnsiFixture] = [ - StripAnsiFixture( - test_id="plain_text", - input_text="hello", - expected="hello", - ), - StripAnsiFixture( - test_id="green_color", - input_text="\033[32mgreen\033[0m", - expected="green", - ), - StripAnsiFixture( - test_id="bold_blue", - input_text="\033[1;34mbold\033[0m", - expected="bold", - ), - StripAnsiFixture( - test_id="multiple_codes", - input_text="\033[1m\033[32mtest\033[0m", - expected="test", - ), - StripAnsiFixture( - test_id="empty_string", - input_text="", - expected="", - ), - StripAnsiFixture( - test_id="mixed_content", - input_text="pre\033[31mred\033[0mpost", - expected="preredpost", - ), - StripAnsiFixture( - test_id="reset_only", - input_text="\033[0m", - expected="", - ), - StripAnsiFixture( - test_id="sgr_params", - input_text="\033[38;5;196mred256\033[0m", - expected="red256", - ), -] - - -@pytest.mark.parametrize( - StripAnsiFixture._fields, - STRIP_ANSI_FIXTURES, - ids=[f.test_id for f in STRIP_ANSI_FIXTURES], -) -def test_strip_ansi(test_id: str, input_text: str, expected: str) -> None: - """Test ANSI escape code stripping.""" - assert strip_ansi(input_text) == expected - - -# --- escape_rst_emphasis tests --- - - -class EscapeRstEmphasisFixture(t.NamedTuple): - """Test fixture for escape_rst_emphasis function.""" - - test_id: str - input_text: str - expected: str - - -ESCAPE_RST_EMPHASIS_FIXTURES: list[EscapeRstEmphasisFixture] = [ - EscapeRstEmphasisFixture( - test_id="glob_pattern_quoted", - input_text='tmuxp load "my-*"', - expected='tmuxp load "my-\\*"', - ), - EscapeRstEmphasisFixture( - test_id="glob_pattern_django", - input_text="django-*", - expected="django-\\*", - ), - EscapeRstEmphasisFixture( - test_id="glob_pattern_flask", - input_text="flask-*", - expected="flask-\\*", - ), - EscapeRstEmphasisFixture( - test_id="multiple_patterns", - input_text="match django-* or flask-* packages", - expected="match django-\\* or flask-\\* packages", - ), - EscapeRstEmphasisFixture( - test_id="plain_text", - input_text="plain text without patterns", - expected="plain text without patterns", - ), - EscapeRstEmphasisFixture( - test_id="rst_emphasis_unchanged", - input_text="*emphasis* is ok", - expected="*emphasis* is ok", - ), - EscapeRstEmphasisFixture( - test_id="already_escaped", - input_text="django-\\*", - expected="django-\\*", - ), - EscapeRstEmphasisFixture( - test_id="empty_string", - input_text="", - expected="", - ), - EscapeRstEmphasisFixture( - test_id="pattern_at_end", - input_text="ending with pattern-*", - expected="ending with pattern-\\*", - ), - EscapeRstEmphasisFixture( - test_id="hyphen_without_asterisk", - input_text="word-with-hyphens", - expected="word-with-hyphens", - ), - EscapeRstEmphasisFixture( - test_id="asterisk_without_hyphen", - input_text="asterisk * alone", - expected="asterisk * alone", - ), - EscapeRstEmphasisFixture( - test_id="double_asterisk", - input_text="glob-** pattern", - expected="glob-** pattern", - ), - EscapeRstEmphasisFixture( - test_id="space_after_asterisk", - input_text="word-* followed by space", - expected="word-\\* followed by space", - ), -] - - -@pytest.mark.parametrize( - EscapeRstEmphasisFixture._fields, - ESCAPE_RST_EMPHASIS_FIXTURES, - ids=[f.test_id for f in ESCAPE_RST_EMPHASIS_FIXTURES], -) -def test_escape_rst_emphasis(test_id: str, input_text: str, expected: str) -> None: - """Test RST emphasis escaping for glob patterns.""" - assert escape_rst_emphasis(input_text) == expected diff --git a/tests/docs/_ext/test_argparse_exemplar.py b/tests/docs/_ext/test_argparse_exemplar.py deleted file mode 100644 index 18679827d5..0000000000 --- a/tests/docs/_ext/test_argparse_exemplar.py +++ /dev/null @@ -1,1073 +0,0 @@ -"""Tests for argparse_exemplar sphinx extension. - -This tests the examples transformation functionality that converts argparse -epilog definition lists into proper documentation sections. - -Note: Tests for strip_ansi have moved to -tests/docs/_ext/sphinx_argparse_neo/test_utils.py since that utility -now lives in sphinx_argparse_neo.utils. -""" - -from __future__ import annotations - -import typing as t - -import pytest -from argparse_exemplar import ( # type: ignore[import-not-found] - ExemplarConfig, - _extract_sections_from_container, - _is_examples_section, - _is_usage_block, - _reorder_nodes, - is_base_examples_term, - is_examples_term, - make_section_id, - make_section_title, - process_node, - transform_definition_list, -) -from docutils import nodes - -# --- is_examples_term tests --- - - -class IsExamplesTermFixture(t.NamedTuple): - """Test fixture for is_examples_term function.""" - - test_id: str - term_text: str - expected: bool - - -IS_EXAMPLES_TERM_FIXTURES: list[IsExamplesTermFixture] = [ - IsExamplesTermFixture( - test_id="base_examples_colon", - term_text="examples:", - expected=True, - ), - IsExamplesTermFixture( - test_id="base_examples_no_colon", - term_text="examples", - expected=True, - ), - IsExamplesTermFixture( - test_id="prefixed_machine_readable", - term_text="Machine-readable output examples:", - expected=True, - ), - IsExamplesTermFixture( - test_id="prefixed_field_scoped", - term_text="Field-scoped search examples:", - expected=True, - ), - IsExamplesTermFixture( - test_id="colon_pattern", - term_text="Machine-readable output: examples:", - expected=True, - ), - IsExamplesTermFixture( - test_id="usage_not_examples", - term_text="Usage:", - expected=False, - ), - IsExamplesTermFixture( - test_id="arguments_not_examples", - term_text="Named Arguments:", - expected=False, - ), - IsExamplesTermFixture( - test_id="case_insensitive_upper", - term_text="EXAMPLES:", - expected=True, - ), - IsExamplesTermFixture( - test_id="case_insensitive_mixed", - term_text="Examples:", - expected=True, - ), -] - - -@pytest.mark.parametrize( - IsExamplesTermFixture._fields, - IS_EXAMPLES_TERM_FIXTURES, - ids=[f.test_id for f in IS_EXAMPLES_TERM_FIXTURES], -) -def test_is_examples_term(test_id: str, term_text: str, expected: bool) -> None: - """Test examples term detection.""" - assert is_examples_term(term_text) == expected - - -# --- is_base_examples_term tests --- - - -class IsBaseExamplesTermFixture(t.NamedTuple): - """Test fixture for is_base_examples_term function.""" - - test_id: str - term_text: str - expected: bool - - -IS_BASE_EXAMPLES_TERM_FIXTURES: list[IsBaseExamplesTermFixture] = [ - IsBaseExamplesTermFixture( - test_id="base_with_colon", - term_text="examples:", - expected=True, - ), - IsBaseExamplesTermFixture( - test_id="base_no_colon", - term_text="examples", - expected=True, - ), - IsBaseExamplesTermFixture( - test_id="uppercase", - term_text="EXAMPLES", - expected=True, - ), - IsBaseExamplesTermFixture( - test_id="mixed_case", - term_text="Examples:", - expected=True, - ), - IsBaseExamplesTermFixture( - test_id="prefixed_not_base", - term_text="Field-scoped examples:", - expected=False, - ), - IsBaseExamplesTermFixture( - test_id="output_examples_not_base", - term_text="Machine-readable output examples:", - expected=False, - ), - IsBaseExamplesTermFixture( - test_id="colon_pattern_not_base", - term_text="Output: examples:", - expected=False, - ), -] - - -@pytest.mark.parametrize( - IsBaseExamplesTermFixture._fields, - IS_BASE_EXAMPLES_TERM_FIXTURES, - ids=[f.test_id for f in IS_BASE_EXAMPLES_TERM_FIXTURES], -) -def test_is_base_examples_term(test_id: str, term_text: str, expected: bool) -> None: - """Test base examples term detection.""" - assert is_base_examples_term(term_text) == expected - - -# --- make_section_id tests --- - - -class MakeSectionIdFixture(t.NamedTuple): - """Test fixture for make_section_id function.""" - - test_id: str - term_text: str - counter: int - is_subsection: bool - expected: str - - -MAKE_SECTION_ID_FIXTURES: list[MakeSectionIdFixture] = [ - MakeSectionIdFixture( - test_id="base_examples", - term_text="examples:", - counter=0, - is_subsection=False, - expected="examples", - ), - MakeSectionIdFixture( - test_id="prefixed_standard", - term_text="Machine-readable output examples:", - counter=0, - is_subsection=False, - expected="machine-readable-output-examples", - ), - MakeSectionIdFixture( - test_id="subsection_omits_suffix", - term_text="Field-scoped examples:", - counter=0, - is_subsection=True, - expected="field-scoped", - ), - MakeSectionIdFixture( - test_id="with_counter", - term_text="examples:", - counter=2, - is_subsection=False, - expected="examples-2", - ), - MakeSectionIdFixture( - test_id="counter_zero_no_suffix", - term_text="examples:", - counter=0, - is_subsection=False, - expected="examples", - ), - MakeSectionIdFixture( - test_id="colon_pattern", - term_text="Machine-readable output: examples:", - counter=0, - is_subsection=False, - expected="machine-readable-output-examples", - ), - MakeSectionIdFixture( - test_id="subsection_with_counter", - term_text="Field-scoped examples:", - counter=1, - is_subsection=True, - expected="field-scoped-1", - ), -] - - -@pytest.mark.parametrize( - MakeSectionIdFixture._fields, - MAKE_SECTION_ID_FIXTURES, - ids=[f.test_id for f in MAKE_SECTION_ID_FIXTURES], -) -def test_make_section_id( - test_id: str, - term_text: str, - counter: int, - is_subsection: bool, - expected: str, -) -> None: - """Test section ID generation.""" - assert make_section_id(term_text, counter, is_subsection=is_subsection) == expected - - -def test_make_section_id_with_page_prefix() -> None: - """Test section ID generation with page_prefix for cross-page uniqueness.""" - # Base "examples:" with page_prefix becomes "sync-examples" - assert make_section_id("examples:", page_prefix="sync") == "sync-examples" - assert make_section_id("examples:", page_prefix="add") == "add-examples" - - # Prefixed examples already unique - page_prefix not added - assert ( - make_section_id("Machine-readable output examples:", page_prefix="sync") - == "machine-readable-output-examples" - ) - - # Subsection with page_prefix - result = make_section_id( - "Field-scoped examples:", is_subsection=True, page_prefix="sync" - ) - assert result == "field-scoped" - - # Empty page_prefix behaves like before - assert make_section_id("examples:", page_prefix="") == "examples" - - -# --- make_section_title tests --- - - -class MakeSectionTitleFixture(t.NamedTuple): - """Test fixture for make_section_title function.""" - - test_id: str - term_text: str - is_subsection: bool - expected: str - - -MAKE_SECTION_TITLE_FIXTURES: list[MakeSectionTitleFixture] = [ - MakeSectionTitleFixture( - test_id="base_examples", - term_text="examples:", - is_subsection=False, - expected="Examples", - ), - MakeSectionTitleFixture( - test_id="prefixed_with_examples_suffix", - term_text="Machine-readable output examples:", - is_subsection=False, - expected="Machine-Readable Output Examples", - ), - MakeSectionTitleFixture( - test_id="subsection_omits_examples", - term_text="Field-scoped examples:", - is_subsection=True, - expected="Field-Scoped", - ), - MakeSectionTitleFixture( - test_id="colon_pattern", - term_text="Machine-readable output: examples:", - is_subsection=False, - expected="Machine-Readable Output Examples", - ), - MakeSectionTitleFixture( - test_id="subsection_colon_pattern", - term_text="Machine-readable output: examples:", - is_subsection=True, - expected="Machine-Readable Output", - ), - MakeSectionTitleFixture( - test_id="base_examples_no_colon", - term_text="examples", - is_subsection=False, - expected="Examples", - ), -] - - -@pytest.mark.parametrize( - MakeSectionTitleFixture._fields, - MAKE_SECTION_TITLE_FIXTURES, - ids=[f.test_id for f in MAKE_SECTION_TITLE_FIXTURES], -) -def test_make_section_title( - test_id: str, - term_text: str, - is_subsection: bool, - expected: str, -) -> None: - """Test section title generation.""" - assert make_section_title(term_text, is_subsection=is_subsection) == expected - - -# --- transform_definition_list integration tests --- - - -def _make_dl_item(term: str, definition: str) -> nodes.definition_list_item: - """Create a definition list item for testing. - - Parameters - ---------- - term : str - The definition term text. - definition : str - The definition content text. - - Returns - ------- - nodes.definition_list_item - A definition list item with term and definition. - """ - item = nodes.definition_list_item() - term_node = nodes.term(text=term) - def_node = nodes.definition() - def_node += nodes.paragraph(text=definition) - item += term_node - item += def_node - return item - - -def test_transform_definition_list_single_examples() -> None: - """Single examples section creates one section node.""" - dl = nodes.definition_list() - dl += _make_dl_item("examples:", "vcspull ls") - - result = transform_definition_list(dl) - - assert len(result) == 1 - assert isinstance(result[0], nodes.section) - assert result[0]["ids"] == ["examples"] - - -def test_transform_definition_list_nested_examples() -> None: - """Base examples with category creates nested sections.""" - dl = nodes.definition_list() - dl += _make_dl_item("examples:", "vcspull ls") - dl += _make_dl_item("Machine-readable output examples:", "vcspull ls --json") - - result = transform_definition_list(dl) - - # Should have single parent section containing nested subsection - assert len(result) == 1 - parent = result[0] - assert isinstance(parent, nodes.section) - assert parent["ids"] == ["examples"] - - # Find nested subsection - subsections = [c for c in parent.children if isinstance(c, nodes.section)] - assert len(subsections) == 1 - assert subsections[0]["ids"] == ["machine-readable-output"] - - -def test_transform_definition_list_multiple_categories() -> None: - """Multiple example categories all nest under parent.""" - dl = nodes.definition_list() - dl += _make_dl_item("examples:", "vcspull ls") - dl += _make_dl_item("Field-scoped examples:", "vcspull ls --field name") - dl += _make_dl_item("Machine-readable output examples:", "vcspull ls --json") - - result = transform_definition_list(dl) - - assert len(result) == 1 - parent = result[0] - assert isinstance(parent, nodes.section) - - subsections = [c for c in parent.children if isinstance(c, nodes.section)] - assert len(subsections) == 2 - - -def test_transform_definition_list_preserves_non_examples() -> None: - """Non-example items preserved as definition list.""" - dl = nodes.definition_list() - dl += _make_dl_item("Usage:", "How to use this command") - dl += _make_dl_item("examples:", "vcspull ls") - - result = transform_definition_list(dl) - - # Should have both definition list (non-examples) and section (examples) - has_dl = any(isinstance(n, nodes.definition_list) for n in result) - has_section = any(isinstance(n, nodes.section) for n in result) - assert has_dl, "Non-example items should be preserved as definition list" - assert has_section, "Example items should become sections" - - -def test_transform_definition_list_no_examples() -> None: - """Definition list without examples returns empty list.""" - dl = nodes.definition_list() - dl += _make_dl_item("Usage:", "How to use") - dl += _make_dl_item("Options:", "Available options") - - result = transform_definition_list(dl) - - # All items are non-examples, should return definition list - assert len(result) == 1 - assert isinstance(result[0], nodes.definition_list) - - -def test_transform_definition_list_only_category_no_base() -> None: - """Single category example without base examples stays flat.""" - dl = nodes.definition_list() - dl += _make_dl_item("Machine-readable output examples:", "vcspull ls --json") - - result = transform_definition_list(dl) - - # Without base "examples:", no nesting - just single section - assert len(result) == 1 - assert isinstance(result[0], nodes.section) - # Should have full title since it's not nested - assert result[0]["ids"] == ["machine-readable-output-examples"] - - -def test_transform_definition_list_code_blocks_created() -> None: - """Each command line becomes a separate code block.""" - dl = nodes.definition_list() - dl += _make_dl_item("examples:", "cmd1\ncmd2\ncmd3") - - result = transform_definition_list(dl) - - section = result[0] - code_blocks = [c for c in section.children if isinstance(c, nodes.literal_block)] - assert len(code_blocks) == 3 - assert code_blocks[0].astext() == "$ cmd1" - assert code_blocks[1].astext() == "$ cmd2" - assert code_blocks[2].astext() == "$ cmd3" - - -# --- _is_usage_block tests --- - - -class IsUsageBlockFixture(t.NamedTuple): - """Test fixture for _is_usage_block function.""" - - test_id: str - node_type: str - node_text: str - expected: bool - - -IS_USAGE_BLOCK_FIXTURES: list[IsUsageBlockFixture] = [ - IsUsageBlockFixture( - test_id="literal_block_usage_lowercase", - node_type="literal_block", - node_text="usage: cmd [-h]", - expected=True, - ), - IsUsageBlockFixture( - test_id="literal_block_usage_uppercase", - node_type="literal_block", - node_text="Usage: vcspull sync", - expected=True, - ), - IsUsageBlockFixture( - test_id="literal_block_usage_leading_space", - node_type="literal_block", - node_text=" usage: cmd", - expected=True, - ), - IsUsageBlockFixture( - test_id="literal_block_not_usage", - node_type="literal_block", - node_text="some other text", - expected=False, - ), - IsUsageBlockFixture( - test_id="literal_block_usage_in_middle", - node_type="literal_block", - node_text="see usage: for more", - expected=False, - ), - IsUsageBlockFixture( - test_id="paragraph_with_usage", - node_type="paragraph", - node_text="usage: cmd", - expected=False, - ), - IsUsageBlockFixture( - test_id="section_node", - node_type="section", - node_text="", - expected=False, - ), -] - - -def _make_test_node(node_type: str, node_text: str) -> nodes.Node: - """Create a test node of the specified type. - - Parameters - ---------- - node_type : str - Type of node to create ("literal_block", "paragraph", "section"). - node_text : str - Text content for the node. - - Returns - ------- - nodes.Node - The created node. - """ - if node_type == "literal_block": - return nodes.literal_block(text=node_text) - if node_type == "paragraph": - return nodes.paragraph(text=node_text) - if node_type == "section": - return nodes.section() - msg = f"Unknown node type: {node_type}" - raise ValueError(msg) - - -@pytest.mark.parametrize( - IsUsageBlockFixture._fields, - IS_USAGE_BLOCK_FIXTURES, - ids=[f.test_id for f in IS_USAGE_BLOCK_FIXTURES], -) -def test_is_usage_block( - test_id: str, - node_type: str, - node_text: str, - expected: bool, -) -> None: - """Test usage block detection.""" - node = _make_test_node(node_type, node_text) - assert _is_usage_block(node) == expected - - -# --- _is_examples_section tests --- - - -class IsExamplesSectionFixture(t.NamedTuple): - """Test fixture for _is_examples_section function.""" - - test_id: str - node_type: str - section_ids: list[str] - expected: bool - - -IS_EXAMPLES_SECTION_FIXTURES: list[IsExamplesSectionFixture] = [ - IsExamplesSectionFixture( - test_id="section_with_examples_id", - node_type="section", - section_ids=["examples"], - expected=True, - ), - IsExamplesSectionFixture( - test_id="section_with_prefixed_examples", - node_type="section", - section_ids=["machine-readable-output-examples"], - expected=True, - ), - IsExamplesSectionFixture( - test_id="section_with_uppercase_examples", - node_type="section", - section_ids=["EXAMPLES"], - expected=True, - ), - IsExamplesSectionFixture( - test_id="section_without_examples", - node_type="section", - section_ids=["positional-arguments"], - expected=False, - ), - IsExamplesSectionFixture( - test_id="section_with_multiple_ids", - node_type="section", - section_ids=["main-id", "examples-alias"], - expected=True, - ), - IsExamplesSectionFixture( - test_id="section_empty_ids", - node_type="section", - section_ids=[], - expected=False, - ), - IsExamplesSectionFixture( - test_id="paragraph_node", - node_type="paragraph", - section_ids=[], - expected=False, - ), - IsExamplesSectionFixture( - test_id="literal_block_node", - node_type="literal_block", - section_ids=[], - expected=False, - ), -] - - -def _make_section_node(node_type: str, section_ids: list[str]) -> nodes.Node: - """Create a test node with optional section IDs. - - Parameters - ---------- - node_type : str - Type of node to create. - section_ids : list[str] - IDs to assign if creating a section. - - Returns - ------- - nodes.Node - The created node. - """ - if node_type == "section": - section = nodes.section() - section["ids"] = section_ids - return section - if node_type == "paragraph": - return nodes.paragraph() - if node_type == "literal_block": - return nodes.literal_block(text="examples") - msg = f"Unknown node type: {node_type}" - raise ValueError(msg) - - -@pytest.mark.parametrize( - IsExamplesSectionFixture._fields, - IS_EXAMPLES_SECTION_FIXTURES, - ids=[f.test_id for f in IS_EXAMPLES_SECTION_FIXTURES], -) -def test_is_examples_section( - test_id: str, - node_type: str, - section_ids: list[str], - expected: bool, -) -> None: - """Test examples section detection.""" - node = _make_section_node(node_type, section_ids) - assert _is_examples_section(node) == expected - - -# --- _reorder_nodes tests --- - - -def _make_usage_node(text: str = "usage: cmd [-h]") -> nodes.literal_block: - """Create a usage block node. - - Parameters - ---------- - text : str - Text content for the usage block. - - Returns - ------- - nodes.literal_block - A literal block node with usage text. - """ - return nodes.literal_block(text=text) - - -def _make_examples_section(section_id: str = "examples") -> nodes.section: - """Create an examples section node. - - Parameters - ---------- - section_id : str - The ID for the section. - - Returns - ------- - nodes.section - A section node with the specified ID. - """ - section = nodes.section() - section["ids"] = [section_id] - return section - - -def test_reorder_nodes_usage_after_examples() -> None: - """Usage block after examples gets moved before examples.""" - desc = nodes.paragraph(text="Description") - examples = _make_examples_section() - usage = _make_usage_node() - - # Create a non-examples section - args_section = nodes.section() - args_section["ids"] = ["arguments"] - - result = _reorder_nodes([desc, examples, usage, args_section]) - - # Should be: desc, usage, examples, args - assert len(result) == 4 - assert isinstance(result[0], nodes.paragraph) - assert isinstance(result[1], nodes.literal_block) - assert isinstance(result[2], nodes.section) - assert result[2]["ids"] == ["examples"] - assert isinstance(result[3], nodes.section) - assert result[3]["ids"] == ["arguments"] - - -def test_reorder_nodes_no_examples() -> None: - """Without examples, original order is preserved.""" - desc = nodes.paragraph(text="Description") - usage = _make_usage_node() - args = nodes.section() - args["ids"] = ["arguments"] - - result = _reorder_nodes([desc, usage, args]) - - # Order unchanged: desc, usage, args - assert len(result) == 3 - assert isinstance(result[0], nodes.paragraph) - assert isinstance(result[1], nodes.literal_block) - assert isinstance(result[2], nodes.section) - - -def test_reorder_nodes_usage_already_before_examples() -> None: - """When usage is already before examples, order is preserved.""" - desc = nodes.paragraph(text="Description") - usage = _make_usage_node() - examples = _make_examples_section() - args = nodes.section() - args["ids"] = ["arguments"] - - result = _reorder_nodes([desc, usage, examples, args]) - - # Order should be: desc, usage, examples, args - assert len(result) == 4 - assert isinstance(result[0], nodes.paragraph) - assert isinstance(result[1], nodes.literal_block) - assert isinstance(result[2], nodes.section) - assert result[2]["ids"] == ["examples"] - - -def test_reorder_nodes_empty_list() -> None: - """Empty input returns empty output.""" - result = _reorder_nodes([]) - assert result == [] - - -def test_reorder_nodes_multiple_usage_blocks() -> None: - """Multiple usage blocks are all moved before examples.""" - desc = nodes.paragraph(text="Description") - examples = _make_examples_section() - usage1 = _make_usage_node("usage: cmd1 [-h]") - usage2 = _make_usage_node("usage: cmd2 [-v]") - - result = _reorder_nodes([desc, examples, usage1, usage2]) - - # Should be: desc, usage1, usage2, examples - assert len(result) == 4 - assert isinstance(result[0], nodes.paragraph) - assert isinstance(result[1], nodes.literal_block) - assert isinstance(result[2], nodes.literal_block) - assert isinstance(result[3], nodes.section) - - -def test_reorder_nodes_multiple_examples_sections() -> None: - """Multiple examples sections are grouped together.""" - desc = nodes.paragraph(text="Description") - examples1 = _make_examples_section("examples") - usage = _make_usage_node() - examples2 = _make_examples_section("machine-readable-output-examples") - args = nodes.section() - args["ids"] = ["arguments"] - - result = _reorder_nodes([desc, examples1, usage, examples2, args]) - - # Should be: desc, usage, examples1, examples2, args - assert len(result) == 5 - assert isinstance(result[0], nodes.paragraph) - assert isinstance(result[1], nodes.literal_block) - assert result[2]["ids"] == ["examples"] - assert result[3]["ids"] == ["machine-readable-output-examples"] - assert result[4]["ids"] == ["arguments"] - - -def test_reorder_nodes_preserves_non_examples_after() -> None: - """Non-examples nodes after examples stay at the end.""" - desc = nodes.paragraph(text="Description") - examples = _make_examples_section() - usage = _make_usage_node() - epilog = nodes.paragraph(text="Epilog") - - result = _reorder_nodes([desc, examples, usage, epilog]) - - # Should be: desc, usage, examples, epilog - assert len(result) == 4 - assert result[0].astext() == "Description" - assert isinstance(result[1], nodes.literal_block) - assert isinstance(result[2], nodes.section) - assert result[3].astext() == "Epilog" - - -# --- ExemplarConfig tests --- - - -def test_exemplar_config_defaults() -> None: - """ExemplarConfig has sensible defaults.""" - config = ExemplarConfig() - - assert config.examples_term_suffix == "examples" - assert config.examples_base_term == "examples" - assert config.examples_section_title == "Examples" - assert config.usage_pattern == "usage:" - assert config.command_prefix == "$ " - assert config.code_language == "console" - assert config.code_classes == ("highlight-console",) - assert config.usage_code_language == "cli-usage" - assert config.reorder_usage_before_examples is True - - -def test_exemplar_config_custom_values() -> None: - """ExemplarConfig accepts custom values.""" - config = ExemplarConfig( - examples_term_suffix="demos", - examples_base_term="demos", - examples_section_title="Demos", - usage_pattern="synopsis:", - command_prefix="> ", - code_language="bash", - code_classes=("highlight-bash",), - usage_code_language="cli-synopsis", - reorder_usage_before_examples=False, - ) - - assert config.examples_term_suffix == "demos" - assert config.examples_base_term == "demos" - assert config.examples_section_title == "Demos" - assert config.usage_pattern == "synopsis:" - assert config.command_prefix == "> " - assert config.code_language == "bash" - assert config.code_classes == ("highlight-bash",) - assert config.usage_code_language == "cli-synopsis" - assert config.reorder_usage_before_examples is False - - -# --- Config integration tests --- - - -def test_is_examples_term_with_custom_config() -> None: - """is_examples_term respects custom config.""" - config = ExemplarConfig(examples_term_suffix="demos") - - # Custom term should match - assert is_examples_term("demos:", config=config) is True - assert is_examples_term("Machine-readable output demos:", config=config) is True - - # Default term should not match - assert is_examples_term("examples:", config=config) is False - - -def test_is_base_examples_term_with_custom_config() -> None: - """is_base_examples_term respects custom config.""" - config = ExemplarConfig(examples_base_term="demos") - - # Custom term should match - assert is_base_examples_term("demos:", config=config) is True - assert is_base_examples_term("Demos", config=config) is True - - # Default term should not match - assert is_base_examples_term("examples:", config=config) is False - - # Prefixed term should not match (not base) - assert is_base_examples_term("Output demos:", config=config) is False - - -def test_make_section_id_with_custom_config() -> None: - """make_section_id respects custom config.""" - config = ExemplarConfig(examples_term_suffix="demos") - - assert make_section_id("demos:", config=config) == "demos" - assert ( - make_section_id("Machine-readable output demos:", config=config) - == "machine-readable-output-demos" - ) - assert ( - make_section_id("Field-scoped demos:", is_subsection=True, config=config) - == "field-scoped" - ) - - -def test_make_section_title_with_custom_config() -> None: - """make_section_title respects custom config.""" - config = ExemplarConfig( - examples_base_term="demos", - examples_term_suffix="demos", - examples_section_title="Demos", - ) - - assert make_section_title("demos:", config=config) == "Demos" - assert ( - make_section_title("Machine-readable output demos:", config=config) - == "Machine-Readable Output Demos" - ) - assert ( - make_section_title("Field-scoped demos:", is_subsection=True, config=config) - == "Field-Scoped" - ) - - -def test_is_usage_block_with_custom_config() -> None: - """_is_usage_block respects custom config.""" - config = ExemplarConfig(usage_pattern="synopsis:") - - # Custom pattern should match - assert ( - _is_usage_block(nodes.literal_block(text="synopsis: cmd [-h]"), config=config) - is True - ) - assert ( - _is_usage_block(nodes.literal_block(text="Synopsis: cmd"), config=config) - is True - ) - - # Default pattern should not match - assert ( - _is_usage_block(nodes.literal_block(text="usage: cmd [-h]"), config=config) - is False - ) - - -def test_is_examples_section_with_custom_config() -> None: - """_is_examples_section respects custom config.""" - config = ExemplarConfig(examples_term_suffix="demos") - - # Custom term should match - demos_section = nodes.section() - demos_section["ids"] = ["demos"] - assert _is_examples_section(demos_section, config=config) is True - - prefixed_demos = nodes.section() - prefixed_demos["ids"] = ["output-demos"] - assert _is_examples_section(prefixed_demos, config=config) is True - - # Default term should not match - examples_section = nodes.section() - examples_section["ids"] = ["examples"] - assert _is_examples_section(examples_section, config=config) is False - - -def test_reorder_nodes_disabled_via_config() -> None: - """Reordering can be disabled via config.""" - config = ExemplarConfig(reorder_usage_before_examples=False) - - desc = nodes.paragraph(text="Description") - examples = _make_examples_section() - usage = _make_usage_node() - - # Original order: desc, examples, usage - result = _reorder_nodes([desc, examples, usage], config=config) - - # Order should be preserved (not reordered) - assert len(result) == 3 - assert isinstance(result[0], nodes.paragraph) - assert isinstance(result[1], nodes.section) # examples still in position 1 - assert isinstance(result[2], nodes.literal_block) # usage still at end - - -def test_transform_definition_list_with_custom_config() -> None: - """transform_definition_list respects custom config.""" - config = ExemplarConfig( - examples_term_suffix="demos", - examples_base_term="demos", - examples_section_title="Demos", - command_prefix="> ", - code_language="bash", - code_classes=("highlight-bash",), - ) - - dl = nodes.definition_list() - dl += _make_dl_item("demos:", "cmd1") - - result = transform_definition_list(dl, config=config) - - # Should create a section with "demos" id - assert len(result) == 1 - section = result[0] - assert isinstance(section, nodes.section) - assert section["ids"] == ["demos"] - - # Find the title - titles = [c for c in section.children if isinstance(c, nodes.title)] - assert len(titles) == 1 - assert titles[0].astext() == "Demos" - - # Find code blocks - code_blocks = [c for c in section.children if isinstance(c, nodes.literal_block)] - assert len(code_blocks) == 1 - assert code_blocks[0].astext() == "> cmd1" # Custom prefix - assert code_blocks[0]["language"] == "bash" - assert "highlight-bash" in code_blocks[0]["classes"] - - -# --- Parent reference maintenance tests --- - - -def test_process_node_maintains_parent_reference() -> None: - """Verify process_node maintains parent references after child replacement. - - When children are replaced in a container node, the docutils protocol - requires using extend() rather than direct assignment to node.children - to ensure parent-child relationships are properly maintained. - """ - # Create a container with ANSI-encoded text children - container = nodes.container() - text_with_ansi = nodes.Text("\033[32mgreen text\033[0m") - container += text_with_ansi - - # Process the node (will strip ANSI and replace children) - process_node(container) - - # Verify children have correct parent reference - for child in container.children: - assert child.parent is container, ( - f"Child {child!r} should have parent reference to container" - ) - - -def test_extract_sections_maintains_parent_reference() -> None: - """Verify _extract_sections_from_container maintains parent references. - - When remaining children are reassigned to the container, the docutils - protocol requires using extend() to maintain parent-child relationships. - """ - from sphinx_argparse_neo.nodes import argparse_program - - # Create container with mixed children - container = argparse_program() - para = nodes.paragraph(text="Description") - section = nodes.section() - section["ids"] = ["examples"] - - container += para - container += section - - # Extract sections - modified, _extracted = _extract_sections_from_container(container) - - # Verify remaining children have correct parent reference - for child in modified.children: - assert child.parent is modified, ( - f"Child {child!r} should have parent reference to modified container" - ) diff --git a/tests/docs/_ext/test_argparse_lexer.py b/tests/docs/_ext/test_argparse_lexer.py deleted file mode 100644 index 7a621f1093..0000000000 --- a/tests/docs/_ext/test_argparse_lexer.py +++ /dev/null @@ -1,825 +0,0 @@ -"""Tests for argparse_lexer Pygments extension.""" - -from __future__ import annotations - -import typing as t - -import pytest -from argparse_lexer import ( - ArgparseHelpLexer, - ArgparseLexer, - ArgparseUsageLexer, - tokenize_argparse, - tokenize_usage, -) - -# --- Helper to extract token type names --- - - -def get_tokens(text: str, lexer_class: type = ArgparseLexer) -> list[tuple[str, str]]: - """Get tokens as (type_name, value) tuples. - - Examples - -------- - >>> tokens = get_tokens("usage: cmd [-h]") - >>> any(t[0] == "Token.Name.Attribute" for t in tokens) - True - """ - lexer = lexer_class() - return [ - (str(tok_type), tok_value) for tok_type, tok_value in lexer.get_tokens(text) - ] - - -def get_usage_tokens(text: str) -> list[tuple[str, str]]: - """Get tokens using ArgparseUsageLexer. - - Examples - -------- - >>> tokens = get_usage_tokens("usage: cmd") - >>> tokens[0] - ('Token.Generic.Heading', 'usage:') - """ - return get_tokens(text, ArgparseUsageLexer) - - -def get_help_tokens(text: str) -> list[tuple[str, str]]: - """Get tokens using ArgparseHelpLexer. - - Examples - -------- - >>> tokens = get_help_tokens("positional arguments:") - >>> any("Subheading" in t[0] for t in tokens) - True - """ - return get_tokens(text, ArgparseHelpLexer) - - -# --- Token type fixtures --- - - -class TokenTypeFixture(t.NamedTuple): - """Test fixture for verifying specific token types.""" - - test_id: str - input_text: str - expected_token_type: str - expected_value: str - - -TOKEN_TYPE_FIXTURES: list[TokenTypeFixture] = [ - TokenTypeFixture( - test_id="usage_heading", - input_text="usage:", - expected_token_type="Token.Generic.Heading", - expected_value="usage:", - ), - TokenTypeFixture( - test_id="short_option", - input_text="-h", - expected_token_type="Token.Name.Attribute", - expected_value="-h", - ), - TokenTypeFixture( - test_id="short_option_v", - input_text="-v", - expected_token_type="Token.Name.Attribute", - expected_value="-v", - ), - TokenTypeFixture( - test_id="long_option", - input_text="--verbose", - expected_token_type="Token.Name.Tag", - expected_value="--verbose", - ), - TokenTypeFixture( - test_id="long_option_with_dashes", - input_text="--no-color", - expected_token_type="Token.Name.Tag", - expected_value="--no-color", - ), - TokenTypeFixture( - test_id="uppercase_metavar", - input_text="FILE", - expected_token_type="Token.Name.Variable", - expected_value="FILE", - ), - TokenTypeFixture( - test_id="uppercase_metavar_path", - input_text="PATH", - expected_token_type="Token.Name.Variable", - expected_value="PATH", - ), - TokenTypeFixture( - test_id="uppercase_metavar_with_underscore", - input_text="FILE_PATH", - expected_token_type="Token.Name.Variable", - expected_value="FILE_PATH", - ), - TokenTypeFixture( - test_id="command_name", - input_text="sync", - expected_token_type="Token.Name.Label", - expected_value="sync", - ), - TokenTypeFixture( - test_id="command_with_dash", - input_text="repo-name", - expected_token_type="Token.Name.Label", - expected_value="repo-name", - ), - TokenTypeFixture( - test_id="open_bracket", - input_text="[", - expected_token_type="Token.Punctuation", - expected_value="[", - ), - TokenTypeFixture( - test_id="close_bracket", - input_text="]", - expected_token_type="Token.Punctuation", - expected_value="]", - ), - TokenTypeFixture( - test_id="open_paren", - input_text="(", - expected_token_type="Token.Punctuation", - expected_value="(", - ), - TokenTypeFixture( - test_id="close_paren", - input_text=")", - expected_token_type="Token.Punctuation", - expected_value=")", - ), - TokenTypeFixture( - test_id="open_brace", - input_text="{", - expected_token_type="Token.Punctuation", - expected_value="{", - ), - TokenTypeFixture( - test_id="pipe_operator", - input_text="|", - expected_token_type="Token.Operator", - expected_value="|", - ), - TokenTypeFixture( - test_id="ellipsis", - input_text="...", - expected_token_type="Token.Punctuation", - expected_value="...", - ), -] - - -@pytest.mark.parametrize( - list(TokenTypeFixture._fields), - TOKEN_TYPE_FIXTURES, - ids=[f.test_id for f in TOKEN_TYPE_FIXTURES], -) -def test_token_type( - test_id: str, - input_text: str, - expected_token_type: str, - expected_value: str, -) -> None: - """Test individual token type detection.""" - tokens = get_usage_tokens(input_text) - # Find the expected token (skip whitespace) - non_ws_tokens = [(t, v) for t, v in tokens if "Whitespace" not in t and v.strip()] - assert len(non_ws_tokens) >= 1, f"No non-whitespace tokens found for '{input_text}'" - token_type, token_value = non_ws_tokens[0] - assert token_type == expected_token_type, ( - f"Expected {expected_token_type}, got {token_type}" - ) - assert token_value == expected_value - - -# --- Choice fixtures --- - - -class ChoiceFixture(t.NamedTuple): - """Test fixture for choice enumeration patterns.""" - - test_id: str - input_text: str - expected_choices: list[str] - - -CHOICE_FIXTURES: list[ChoiceFixture] = [ - ChoiceFixture( - test_id="simple_choices", - input_text="{json,yaml,table}", - expected_choices=["json", "yaml", "table"], - ), - ChoiceFixture( - test_id="numeric_choices", - input_text="{1,2,3}", - expected_choices=["1", "2", "3"], - ), - ChoiceFixture( - test_id="auto_always_never", - input_text="{auto,always,never}", - expected_choices=["auto", "always", "never"], - ), - ChoiceFixture( - test_id="two_choices", - input_text="{a,b}", - expected_choices=["a", "b"], - ), -] - - -@pytest.mark.parametrize( - list(ChoiceFixture._fields), - CHOICE_FIXTURES, - ids=[f.test_id for f in CHOICE_FIXTURES], -) -def test_choices( - test_id: str, - input_text: str, - expected_choices: list[str], -) -> None: - """Test choice enumeration tokenization.""" - tokens = get_usage_tokens(input_text) - # Extract choice values (Name.Constant tokens) - choice_tokens = [v for t, v in tokens if t == "Token.Name.Constant"] - assert choice_tokens == expected_choices - - -# --- Mutex group fixtures --- - - -class MutexGroupFixture(t.NamedTuple): - """Test fixture for mutually exclusive group patterns.""" - - test_id: str - input_text: str - expected_options: list[str] - is_required: bool - - -MUTEX_GROUP_FIXTURES: list[MutexGroupFixture] = [ - MutexGroupFixture( - test_id="optional_short", - input_text="[-a | -b | -c]", - expected_options=["-a", "-b", "-c"], - is_required=False, - ), - MutexGroupFixture( - test_id="optional_long", - input_text="[--foo FOO | --bar BAR]", - expected_options=["--foo", "--bar"], - is_required=False, - ), - MutexGroupFixture( - test_id="required_long", - input_text="(--foo | --bar)", - expected_options=["--foo", "--bar"], - is_required=True, - ), - MutexGroupFixture( - test_id="required_with_metavar", - input_text="(--input FILE | --stdin)", - expected_options=["--input", "--stdin"], - is_required=True, - ), - MutexGroupFixture( - test_id="optional_output_formats", - input_text="[--json | --ndjson | --table]", - expected_options=["--json", "--ndjson", "--table"], - is_required=False, - ), -] - - -@pytest.mark.parametrize( - list(MutexGroupFixture._fields), - MUTEX_GROUP_FIXTURES, - ids=[f.test_id for f in MUTEX_GROUP_FIXTURES], -) -def test_mutex_groups( - test_id: str, - input_text: str, - expected_options: list[str], - is_required: bool, -) -> None: - """Test mutually exclusive group tokenization.""" - tokens = get_usage_tokens(input_text) - - # Check for proper brackets (required uses parens, optional uses brackets) - if is_required: - assert ("Token.Punctuation", "(") in tokens - assert ("Token.Punctuation", ")") in tokens - else: - assert ("Token.Punctuation", "[") in tokens - assert ("Token.Punctuation", "]") in tokens - - # Check pipe operators present - pipe_count = sum(1 for t, v in tokens if t == "Token.Operator" and v == "|") - assert pipe_count == len(expected_options) - 1 - - # Check options are present - for opt in expected_options: - if opt.startswith("--"): - assert ("Token.Name.Tag", opt) in tokens - else: - assert ("Token.Name.Attribute", opt) in tokens - - -# --- Nargs pattern fixtures --- - - -class NargsFixture(t.NamedTuple): - """Test fixture for nargs/variadic patterns.""" - - test_id: str - input_text: str - has_ellipsis: bool - has_metavar: str | None - - -NARGS_FIXTURES: list[NargsFixture] = [ - NargsFixture( - test_id="nargs_plus", - input_text="FILE ...", - has_ellipsis=True, - has_metavar="FILE", - ), - NargsFixture( - test_id="nargs_star", - input_text="[FILE ...]", - has_ellipsis=True, - has_metavar="FILE", - ), - NargsFixture( - test_id="nargs_question", - input_text="[--foo [FOO]]", - has_ellipsis=False, - has_metavar="FOO", - ), - NargsFixture( - test_id="nargs_plus_with_option", - input_text="[--bar X [X ...]]", - has_ellipsis=True, - has_metavar="X", - ), -] - - -@pytest.mark.parametrize( - list(NargsFixture._fields), - NARGS_FIXTURES, - ids=[f.test_id for f in NARGS_FIXTURES], -) -def test_nargs_patterns( - test_id: str, - input_text: str, - has_ellipsis: bool, - has_metavar: str | None, -) -> None: - """Test nargs/variadic pattern tokenization.""" - tokens = get_usage_tokens(input_text) - - # Check ellipsis - ellipsis_present = ("Token.Punctuation", "...") in tokens - assert ellipsis_present == has_ellipsis - - # Check metavar - if has_metavar: - assert ("Token.Name.Variable", has_metavar) in tokens - - -# --- Long option with value fixtures --- - - -class LongOptionValueFixture(t.NamedTuple): - """Test fixture for long options with = values.""" - - test_id: str - input_text: str - option: str - value: str - - -LONG_OPTION_VALUE_FIXTURES: list[LongOptionValueFixture] = [ - LongOptionValueFixture( - test_id="config_file", - input_text="--config=FILE", - option="--config", - value="FILE", - ), - LongOptionValueFixture( - test_id="log_level", - input_text="--log-level=DEBUG", - option="--log-level", - value="DEBUG", - ), - LongOptionValueFixture( - test_id="lowercase_value", - input_text="--output=path", - option="--output", - value="path", - ), -] - - -@pytest.mark.parametrize( - list(LongOptionValueFixture._fields), - LONG_OPTION_VALUE_FIXTURES, - ids=[f.test_id for f in LONG_OPTION_VALUE_FIXTURES], -) -def test_long_option_with_equals_value( - test_id: str, - input_text: str, - option: str, - value: str, -) -> None: - """Test long option with = value tokenization.""" - tokens = get_usage_tokens(input_text) - non_ws_tokens = [(t, v) for t, v in tokens if "Whitespace" not in t] - - assert len(non_ws_tokens) >= 3 - assert non_ws_tokens[0] == ("Token.Name.Tag", option) - assert non_ws_tokens[1] == ("Token.Operator", "=") - assert non_ws_tokens[2][1] == value - - -# --- Short option with value fixtures --- - - -class ShortOptionValueFixture(t.NamedTuple): - """Test fixture for short options with space-separated values.""" - - test_id: str - input_text: str - option: str - value: str - - -SHORT_OPTION_VALUE_FIXTURES: list[ShortOptionValueFixture] = [ - ShortOptionValueFixture( - test_id="config_path", - input_text="-c config-path", - option="-c", - value="config-path", - ), - ShortOptionValueFixture( - test_id="directory", - input_text="-d DIRECTORY", - option="-d", - value="DIRECTORY", - ), - ShortOptionValueFixture( - test_id="simple_name", - input_text="-r name", - option="-r", - value="name", - ), - ShortOptionValueFixture( - test_id="underscore_metavar", - input_text="-L socket_name", - option="-L", - value="socket_name", - ), - ShortOptionValueFixture( - test_id="multiple_underscores", - input_text="-f tmux_config_file", - option="-f", - value="tmux_config_file", - ), -] - - -@pytest.mark.parametrize( - list(ShortOptionValueFixture._fields), - SHORT_OPTION_VALUE_FIXTURES, - ids=[f.test_id for f in SHORT_OPTION_VALUE_FIXTURES], -) -def test_short_option_with_value( - test_id: str, - input_text: str, - option: str, - value: str, -) -> None: - """Test short option followed by value tokenization.""" - tokens = get_usage_tokens(input_text) - non_ws_tokens = [(t, v) for t, v in tokens if "Whitespace" not in t] - - assert len(non_ws_tokens) >= 2 - assert non_ws_tokens[0] == ("Token.Name.Attribute", option) - assert non_ws_tokens[1][1] == value - - -# --- Full usage string fixtures --- - - -class UsageStringFixture(t.NamedTuple): - """Test fixture for full usage string tokenization.""" - - test_id: str - input_text: str - expected_contains: list[tuple[str, str]] - - -USAGE_STRING_FIXTURES: list[UsageStringFixture] = [ - UsageStringFixture( - test_id="simple_usage", - input_text="usage: cmd [-h]", - expected_contains=[ - ("Token.Generic.Heading", "usage:"), - ("Token.Name.Label", "cmd"), - ("Token.Punctuation", "["), - ("Token.Name.Attribute", "-h"), - ("Token.Punctuation", "]"), - ], - ), - UsageStringFixture( - test_id="mutually_exclusive", - input_text="[--json | --ndjson | --table]", - expected_contains=[ - ("Token.Name.Tag", "--json"), - ("Token.Operator", "|"), - ("Token.Name.Tag", "--ndjson"), - ("Token.Operator", "|"), - ("Token.Name.Tag", "--table"), - ], - ), - UsageStringFixture( - test_id="subcommand", - input_text="usage: vcspull sync", - expected_contains=[ - ("Token.Generic.Heading", "usage:"), - ("Token.Name.Label", "vcspull"), - # Subcommands now use Name.Function per 30ea233 - ("Token.Name.Function", "sync"), - ], - ), - UsageStringFixture( - test_id="with_choices", - input_text="usage: cmd {a,b,c}", - expected_contains=[ - ("Token.Generic.Heading", "usage:"), - ("Token.Name.Constant", "a"), - ("Token.Name.Constant", "b"), - ("Token.Name.Constant", "c"), - ], - ), - UsageStringFixture( - test_id="complex_usage", - input_text="usage: prog [-h] [--verbose] FILE ...", - expected_contains=[ - ("Token.Generic.Heading", "usage:"), - ("Token.Name.Label", "prog"), - ("Token.Name.Attribute", "-h"), - ("Token.Name.Tag", "--verbose"), - ("Token.Name.Variable", "FILE"), - ("Token.Punctuation", "..."), - ], - ), -] - - -@pytest.mark.parametrize( - list(UsageStringFixture._fields), - USAGE_STRING_FIXTURES, - ids=[f.test_id for f in USAGE_STRING_FIXTURES], -) -def test_usage_string( - test_id: str, - input_text: str, - expected_contains: list[tuple[str, str]], -) -> None: - """Test full usage string tokenization contains expected tokens.""" - tokens = get_usage_tokens(input_text) - for expected_type, expected_value in expected_contains: - assert (expected_type, expected_value) in tokens, ( - f"Expected ({expected_type}, {expected_value!r}) not found in tokens" - ) - - -# --- Section header fixtures --- - - -class SectionHeaderFixture(t.NamedTuple): - """Test fixture for section header recognition.""" - - test_id: str - input_text: str - expected_header: str - - -SECTION_HEADER_FIXTURES: list[SectionHeaderFixture] = [ - SectionHeaderFixture( - test_id="positional_arguments", - input_text="positional arguments:", - expected_header="positional arguments:", - ), - SectionHeaderFixture( - test_id="options", - input_text="options:", - expected_header="options:", - ), - SectionHeaderFixture( - test_id="optional_arguments", - input_text="optional arguments:", - expected_header="optional arguments:", - ), - SectionHeaderFixture( - test_id="custom_section", - input_text="advanced options:", - expected_header="advanced options:", - ), -] - - -@pytest.mark.parametrize( - list(SectionHeaderFixture._fields), - SECTION_HEADER_FIXTURES, - ids=[f.test_id for f in SECTION_HEADER_FIXTURES], -) -def test_section_headers( - test_id: str, - input_text: str, - expected_header: str, -) -> None: - """Test section header tokenization.""" - tokens = get_help_tokens(input_text) - # Section headers should be Generic.Subheading - # Strip newlines from token values (lexer may include trailing \n) - subheading_tokens = [ - v.strip() for t, v in tokens if t == "Token.Generic.Subheading" - ] - assert expected_header in subheading_tokens - - -# --- Full help output test --- - - -def test_full_help_output() -> None: - """Test full argparse -h output tokenization.""" - help_text = """\ -usage: vcspull sync [-h] [-c CONFIG] [-d DIRECTORY] - [--json | --ndjson | --table] - [repo-name] [path] - -positional arguments: - repo-name repository name filter - path path filter - -options: - -h, --help show this help message and exit - -c CONFIG, --config CONFIG - config file path - --json output as JSON -""" - tokens = get_help_tokens(help_text) - - # Check usage heading - assert ("Token.Generic.Heading", "usage:") in tokens - - # Check section headers - subheadings = [v for t, v in tokens if t == "Token.Generic.Subheading"] - assert "positional arguments:" in subheadings - assert "options:" in subheadings - - # Check options are recognized - assert ("Token.Name.Attribute", "-h") in tokens - assert ("Token.Name.Tag", "--help") in tokens - assert ("Token.Name.Tag", "--config") in tokens - assert ("Token.Name.Tag", "--json") in tokens - - # Check command/positional names - assert ("Token.Name.Label", "vcspull") in tokens - # Subcommands now use Name.Function per 30ea233 - assert ("Token.Name.Function", "sync") in tokens - - -# --- Real vcspull usage output test --- - - -def test_vcspull_sync_usage() -> None: - """Test real vcspull sync usage output tokenization.""" - usage_text = """\ -usage: vcspull sync [-h] [-c CONFIG] [-d DIRECTORY] - [--json | --ndjson | --table] [--color {auto,always,never}] - [--no-progress] [--verbose] - [repo-name] [path]""" - - tokens = get_usage_tokens(usage_text) - - expected = [ - ("Token.Generic.Heading", "usage:"), - ("Token.Name.Label", "vcspull"), - # Subcommands now use Name.Function per 30ea233 - ("Token.Name.Function", "sync"), - ("Token.Name.Attribute", "-h"), - ("Token.Name.Attribute", "-c"), - ("Token.Name.Variable", "CONFIG"), - ("Token.Name.Attribute", "-d"), - ("Token.Name.Variable", "DIRECTORY"), - ("Token.Name.Tag", "--json"), - ("Token.Name.Tag", "--ndjson"), - ("Token.Name.Tag", "--table"), - ("Token.Name.Tag", "--color"), - ("Token.Name.Tag", "--no-progress"), - ("Token.Name.Tag", "--verbose"), - # Optional positional args in brackets also use Name.Function per 30ea233 - ("Token.Name.Function", "repo-name"), - ("Token.Name.Function", "path"), - ] - - for expected_type, expected_value in expected: - assert (expected_type, expected_value) in tokens, ( - f"Expected ({expected_type}, {expected_value!r}) not in tokens" - ) - - # Check choices are properly tokenized - assert ("Token.Name.Constant", "auto") in tokens - assert ("Token.Name.Constant", "always") in tokens - assert ("Token.Name.Constant", "never") in tokens - - -# --- tokenize_argparse helper function test --- - - -def test_tokenize_argparse_helper() -> None: - """Test the tokenize_argparse helper function.""" - result = tokenize_argparse("usage: cmd [-h]") - - assert result[0] == ("Token.Generic.Heading", "usage:") - assert ("Token.Name.Label", "cmd") in result - assert ("Token.Name.Attribute", "-h") in result - - -def test_tokenize_usage_helper() -> None: - """Test the tokenize_usage helper function.""" - result = tokenize_usage("usage: cmd [-h]") - - assert result[0] == ("Token.Generic.Heading", "usage:") - assert ("Token.Name.Label", "cmd") in result - assert ("Token.Name.Attribute", "-h") in result - - -# --- Lexer class selection tests --- - - -def test_argparse_lexer_usage_detection() -> None: - """Test ArgparseLexer handles usage lines correctly.""" - lexer = ArgparseLexer() - tokens = list(lexer.get_tokens("usage: cmd [-h]")) - token_types = [str(t) for t, v in tokens] - assert "Token.Generic.Heading" in token_types - - -def test_argparse_lexer_section_detection() -> None: - """Test ArgparseLexer handles section headers correctly.""" - lexer = ArgparseLexer() - tokens = list(lexer.get_tokens("positional arguments:")) - token_types = [str(t) for t, v in tokens] - assert "Token.Generic.Subheading" in token_types - - -def test_argparse_usage_lexer_standalone() -> None: - """Test ArgparseUsageLexer works standalone.""" - lexer = ArgparseUsageLexer() - tokens = list(lexer.get_tokens("usage: cmd [-h] --foo FILE")) - token_types = [str(t) for t, v in tokens] - - assert "Token.Generic.Heading" in token_types - assert "Token.Name.Label" in token_types # cmd - assert "Token.Name.Attribute" in token_types # -h - assert "Token.Name.Tag" in token_types # --foo - - -def test_argparse_help_lexer_multiline() -> None: - """Test ArgparseHelpLexer handles multiline help.""" - lexer = ArgparseHelpLexer() - help_text = """usage: cmd - -options: - -h help -""" - tokens = list(lexer.get_tokens(help_text)) - token_values = [v for t, v in tokens] - - assert "usage:" in token_values - assert "options:" in token_values or any( - "options:" in v for v in token_values if isinstance(v, str) - ) - - -def test_lowercase_metavar_with_underscores() -> None: - """Test lowercase metavars with underscores are fully captured. - - Regression test: previously `socket_name` was tokenized as `socket` + `_name`. - Example from tmuxp load usage. - """ - usage = "usage: prog [-L socket_name] [-S socket_path] [-f config_file]" - tokens = get_usage_tokens(usage) - - # All underscore metavars should be fully captured - assert ("Token.Name.Variable", "socket_name") in tokens - assert ("Token.Name.Variable", "socket_path") in tokens - assert ("Token.Name.Variable", "config_file") in tokens diff --git a/tests/docs/_ext/test_argparse_roles.py b/tests/docs/_ext/test_argparse_roles.py deleted file mode 100644 index c31e12691a..0000000000 --- a/tests/docs/_ext/test_argparse_roles.py +++ /dev/null @@ -1,439 +0,0 @@ -"""Tests for argparse_roles docutils extension.""" - -from __future__ import annotations - -import typing as t - -import pytest -from argparse_roles import ( - cli_choice_role, - cli_command_role, - cli_default_role, - cli_metavar_role, - cli_option_role, - normalize_options, - register_roles, -) -from docutils import nodes - -# --- normalize_options tests --- - - -def test_normalize_options_none() -> None: - """Test normalize_options with None input.""" - assert normalize_options(None) == {} - - -def test_normalize_options_dict() -> None: - """Test normalize_options with dict input.""" - opts = {"class": "custom"} - assert normalize_options(opts) == {"class": "custom"} - - -def test_normalize_options_empty_dict() -> None: - """Test normalize_options with empty dict input.""" - assert normalize_options({}) == {} - - -# --- CLI Option Role Tests --- - - -class OptionRoleFixture(t.NamedTuple): - """Test fixture for CLI option role.""" - - test_id: str - text: str - expected_classes: list[str] - - -OPTION_ROLE_FIXTURES: list[OptionRoleFixture] = [ - OptionRoleFixture( - test_id="long_option", - text="--verbose", - expected_classes=["cli-option", "cli-option-long"], - ), - OptionRoleFixture( - test_id="long_option_with_dash", - text="--no-color", - expected_classes=["cli-option", "cli-option-long"], - ), - OptionRoleFixture( - test_id="short_option", - text="-h", - expected_classes=["cli-option", "cli-option-short"], - ), - OptionRoleFixture( - test_id="short_option_v", - text="-v", - expected_classes=["cli-option", "cli-option-short"], - ), - OptionRoleFixture( - test_id="no_dash_prefix", - text="option", - expected_classes=["cli-option"], - ), -] - - -@pytest.mark.parametrize( - list(OptionRoleFixture._fields), - OPTION_ROLE_FIXTURES, - ids=[f.test_id for f in OPTION_ROLE_FIXTURES], -) -def test_cli_option_role( - test_id: str, - text: str, - expected_classes: list[str], -) -> None: - """Test CLI option role generates correct node classes.""" - node_list, messages = cli_option_role( - "cli-option", - f":cli-option:`{text}`", - text, - 1, - None, - ) - - assert len(node_list) == 1 - assert len(messages) == 0 - - node = node_list[0] - assert isinstance(node, nodes.literal) - assert node.astext() == text - assert node["classes"] == expected_classes - - -def test_cli_option_role_with_options() -> None: - """Test CLI option role accepts options parameter.""" - node_list, _messages = cli_option_role( - "cli-option", - ":cli-option:`--test`", - "--test", - 1, - None, - options={"class": "extra"}, - ) - - assert len(node_list) == 1 - # Options are normalized but classes come from role logic - assert "cli-option" in node_list[0]["classes"] - - -# --- CLI Metavar Role Tests --- - - -class MetavarRoleFixture(t.NamedTuple): - """Test fixture for CLI metavar role.""" - - test_id: str - text: str - - -METAVAR_ROLE_FIXTURES: list[MetavarRoleFixture] = [ - MetavarRoleFixture(test_id="file", text="FILE"), - MetavarRoleFixture(test_id="path", text="PATH"), - MetavarRoleFixture(test_id="directory", text="DIRECTORY"), - MetavarRoleFixture(test_id="config", text="CONFIG"), - MetavarRoleFixture(test_id="lowercase", text="value"), -] - - -@pytest.mark.parametrize( - list(MetavarRoleFixture._fields), - METAVAR_ROLE_FIXTURES, - ids=[f.test_id for f in METAVAR_ROLE_FIXTURES], -) -def test_cli_metavar_role( - test_id: str, - text: str, -) -> None: - """Test CLI metavar role generates correct node.""" - node_list, messages = cli_metavar_role( - "cli-metavar", - f":cli-metavar:`{text}`", - text, - 1, - None, - ) - - assert len(node_list) == 1 - assert len(messages) == 0 - - node = node_list[0] - assert isinstance(node, nodes.literal) - assert node.astext() == text - assert node["classes"] == ["cli-metavar"] - - -# --- CLI Command Role Tests --- - - -class CommandRoleFixture(t.NamedTuple): - """Test fixture for CLI command role.""" - - test_id: str - text: str - - -COMMAND_ROLE_FIXTURES: list[CommandRoleFixture] = [ - CommandRoleFixture(test_id="sync", text="sync"), - CommandRoleFixture(test_id="add", text="add"), - CommandRoleFixture(test_id="vcspull", text="vcspull"), - CommandRoleFixture(test_id="list", text="list"), - CommandRoleFixture(test_id="with_dash", text="repo-add"), -] - - -@pytest.mark.parametrize( - list(CommandRoleFixture._fields), - COMMAND_ROLE_FIXTURES, - ids=[f.test_id for f in COMMAND_ROLE_FIXTURES], -) -def test_cli_command_role( - test_id: str, - text: str, -) -> None: - """Test CLI command role generates correct node.""" - node_list, messages = cli_command_role( - "cli-command", - f":cli-command:`{text}`", - text, - 1, - None, - ) - - assert len(node_list) == 1 - assert len(messages) == 0 - - node = node_list[0] - assert isinstance(node, nodes.literal) - assert node.astext() == text - assert node["classes"] == ["cli-command"] - - -# --- CLI Default Role Tests --- - - -class DefaultRoleFixture(t.NamedTuple): - """Test fixture for CLI default role.""" - - test_id: str - text: str - - -DEFAULT_ROLE_FIXTURES: list[DefaultRoleFixture] = [ - DefaultRoleFixture(test_id="none", text="None"), - DefaultRoleFixture(test_id="quoted_auto", text='"auto"'), - DefaultRoleFixture(test_id="number", text="0"), - DefaultRoleFixture(test_id="empty_string", text='""'), - DefaultRoleFixture(test_id="true", text="True"), - DefaultRoleFixture(test_id="false", text="False"), -] - - -@pytest.mark.parametrize( - list(DefaultRoleFixture._fields), - DEFAULT_ROLE_FIXTURES, - ids=[f.test_id for f in DEFAULT_ROLE_FIXTURES], -) -def test_cli_default_role( - test_id: str, - text: str, -) -> None: - """Test CLI default role generates correct node.""" - node_list, messages = cli_default_role( - "cli-default", - f":cli-default:`{text}`", - text, - 1, - None, - ) - - assert len(node_list) == 1 - assert len(messages) == 0 - - node = node_list[0] - assert isinstance(node, nodes.literal) - assert node.astext() == text - assert node["classes"] == ["cli-default"] - - -# --- CLI Choice Role Tests --- - - -class ChoiceRoleFixture(t.NamedTuple): - """Test fixture for CLI choice role.""" - - test_id: str - text: str - - -CHOICE_ROLE_FIXTURES: list[ChoiceRoleFixture] = [ - ChoiceRoleFixture(test_id="json", text="json"), - ChoiceRoleFixture(test_id="yaml", text="yaml"), - ChoiceRoleFixture(test_id="table", text="table"), - ChoiceRoleFixture(test_id="auto", text="auto"), - ChoiceRoleFixture(test_id="always", text="always"), - ChoiceRoleFixture(test_id="never", text="never"), -] - - -@pytest.mark.parametrize( - list(ChoiceRoleFixture._fields), - CHOICE_ROLE_FIXTURES, - ids=[f.test_id for f in CHOICE_ROLE_FIXTURES], -) -def test_cli_choice_role( - test_id: str, - text: str, -) -> None: - """Test CLI choice role generates correct node.""" - node_list, messages = cli_choice_role( - "cli-choice", - f":cli-choice:`{text}`", - text, - 1, - None, - ) - - assert len(node_list) == 1 - assert len(messages) == 0 - - node = node_list[0] - assert isinstance(node, nodes.literal) - assert node.astext() == text - assert node["classes"] == ["cli-choice"] - - -# --- Register Roles Test --- - - -def test_register_roles() -> None: - """Test register_roles doesn't raise errors.""" - # This should not raise any exceptions - register_roles() - - -# --- Role Return Type Tests --- - - -def test_all_roles_return_correct_types() -> None: - """Test all roles return proper tuple of (nodes, messages).""" - role_functions = [ - cli_option_role, - cli_metavar_role, - cli_command_role, - cli_default_role, - cli_choice_role, - ] - - for role_func in role_functions: - result = role_func("test", ":test:`value`", "value", 1, None) - - assert isinstance(result, tuple), f"{role_func.__name__} should return tuple" - assert len(result) == 2, f"{role_func.__name__} should return 2-tuple" - - node_list, messages = result - assert isinstance(node_list, list), ( - f"{role_func.__name__} first element should be list" - ) - assert isinstance(messages, list), ( - f"{role_func.__name__} second element should be list" - ) - assert len(node_list) == 1, f"{role_func.__name__} should return one node" - assert len(messages) == 0, ( - f"{role_func.__name__} should return no error messages" - ) - - -# --- Node Structure Tests --- - - -def test_cli_option_node_structure() -> None: - """Test CLI option node has expected structure.""" - node_list, _ = cli_option_role( - "cli-option", - ":cli-option:`--test`", - "--test", - 1, - None, - ) - - node = node_list[0] - - # Check node type - assert isinstance(node, nodes.literal) - - # Check rawsource is preserved - assert node.rawsource == ":cli-option:`--test`" - - # Check text content - assert len(node.children) == 1 - assert isinstance(node.children[0], nodes.Text) - assert str(node.children[0]) == "--test" - - -def test_roles_with_none_content_parameter() -> None: - """Test roles handle None content parameter correctly.""" - node_list, messages = cli_option_role( - "cli-option", - ":cli-option:`--test`", - "--test", - 1, - None, - options=None, - content=None, - ) - - assert len(node_list) == 1 - assert len(messages) == 0 - - -def test_roles_with_empty_content_parameter() -> None: - """Test roles handle empty content parameter correctly.""" - node_list, messages = cli_option_role( - "cli-option", - ":cli-option:`--test`", - "--test", - 1, - None, - options={}, - content=[], - ) - - assert len(node_list) == 1 - assert len(messages) == 0 - - -# --- Edge Case Tests --- - - -def test_cli_option_role_empty_text() -> None: - """Test CLI option role with empty text.""" - node_list, _messages = cli_option_role( - "cli-option", - ":cli-option:``", - "", - 1, - None, - ) - - assert len(node_list) == 1 - assert node_list[0].astext() == "" - # No dash prefix, so only base class - assert node_list[0]["classes"] == ["cli-option"] - - -def test_cli_option_role_special_characters() -> None: - """Test CLI option role with special characters in text.""" - node_list, _messages = cli_option_role( - "cli-option", - ":cli-option:`--foo-bar_baz`", - "--foo-bar_baz", - 1, - None, - ) - - assert len(node_list) == 1 - assert node_list[0].astext() == "--foo-bar_baz" - assert "cli-option-long" in node_list[0]["classes"] diff --git a/tests/docs/_ext/test_cli_usage_lexer.py b/tests/docs/_ext/test_cli_usage_lexer.py deleted file mode 100644 index 3c32ebac6d..0000000000 --- a/tests/docs/_ext/test_cli_usage_lexer.py +++ /dev/null @@ -1,358 +0,0 @@ -"""Tests for cli_usage_lexer Pygments extension.""" - -from __future__ import annotations - -import typing as t - -import pytest -from cli_usage_lexer import ( - CLIUsageLexer, - tokenize_usage, -) - -# --- Helper to extract token type names --- - - -def get_tokens(text: str) -> list[tuple[str, str]]: - """Get tokens as (type_name, value) tuples.""" - lexer = CLIUsageLexer() - return [ - (str(tok_type), tok_value) for tok_type, tok_value in lexer.get_tokens(text) - ] - - -# --- Token type fixtures --- - - -class TokenTypeFixture(t.NamedTuple): - """Test fixture for verifying specific token types.""" - - test_id: str - input_text: str - expected_token_type: str - expected_value: str - - -TOKEN_TYPE_FIXTURES: list[TokenTypeFixture] = [ - TokenTypeFixture( - test_id="usage_heading", - input_text="usage:", - expected_token_type="Token.Generic.Heading", - expected_value="usage:", - ), - TokenTypeFixture( - test_id="short_option", - input_text="-h", - expected_token_type="Token.Name.Attribute", - expected_value="-h", - ), - TokenTypeFixture( - test_id="long_option", - input_text="--verbose", - expected_token_type="Token.Name.Tag", - expected_value="--verbose", - ), - TokenTypeFixture( - test_id="long_option_with_dashes", - input_text="--no-color", - expected_token_type="Token.Name.Tag", - expected_value="--no-color", - ), - TokenTypeFixture( - test_id="uppercase_metavar", - input_text="COMMAND", - expected_token_type="Token.Name.Constant", - expected_value="COMMAND", - ), - TokenTypeFixture( - test_id="uppercase_metavar_with_underscore", - input_text="FILE_PATH", - expected_token_type="Token.Name.Constant", - expected_value="FILE_PATH", - ), - TokenTypeFixture( - test_id="positional_arg", - input_text="repo-name", - expected_token_type="Token.Name.Label", - expected_value="repo-name", - ), - TokenTypeFixture( - test_id="command_name", - input_text="vcspull", - expected_token_type="Token.Name.Label", - expected_value="vcspull", - ), - TokenTypeFixture( - test_id="open_bracket", - input_text="[", - expected_token_type="Token.Punctuation", - expected_value="[", - ), - TokenTypeFixture( - test_id="close_bracket", - input_text="]", - expected_token_type="Token.Punctuation", - expected_value="]", - ), - TokenTypeFixture( - test_id="pipe_operator", - input_text="|", - expected_token_type="Token.Operator", - expected_value="|", - ), -] - - -@pytest.mark.parametrize( - TokenTypeFixture._fields, - TOKEN_TYPE_FIXTURES, - ids=[f.test_id for f in TOKEN_TYPE_FIXTURES], -) -def test_token_type( - test_id: str, - input_text: str, - expected_token_type: str, - expected_value: str, -) -> None: - """Test individual token type detection.""" - tokens = get_tokens(input_text) - # Find the expected token (skip whitespace) - non_ws_tokens = [(t, v) for t, v in tokens if "Whitespace" not in t and v.strip()] - assert len(non_ws_tokens) >= 1, f"No non-whitespace tokens found for '{input_text}'" - token_type, token_value = non_ws_tokens[0] - assert token_type == expected_token_type, ( - f"Expected {expected_token_type}, got {token_type}" - ) - assert token_value == expected_value - - -# --- Short option with value fixtures --- - - -class ShortOptionValueFixture(t.NamedTuple): - """Test fixture for short options with values.""" - - test_id: str - input_text: str - option: str - value: str - - -SHORT_OPTION_VALUE_FIXTURES: list[ShortOptionValueFixture] = [ - ShortOptionValueFixture( - test_id="lowercase_value", - input_text="-c config-path", - option="-c", - value="config-path", - ), - ShortOptionValueFixture( - test_id="uppercase_value", - input_text="-d DIRECTORY", - option="-d", - value="DIRECTORY", - ), - ShortOptionValueFixture( - test_id="simple_value", - input_text="-r name", - option="-r", - value="name", - ), -] - - -@pytest.mark.parametrize( - ShortOptionValueFixture._fields, - SHORT_OPTION_VALUE_FIXTURES, - ids=[f.test_id for f in SHORT_OPTION_VALUE_FIXTURES], -) -def test_short_option_with_value( - test_id: str, - input_text: str, - option: str, - value: str, -) -> None: - """Test short option followed by value tokenization.""" - tokens = get_tokens(input_text) - non_ws_tokens = [(t, v) for t, v in tokens if "Whitespace" not in t] - - assert len(non_ws_tokens) >= 2 - assert non_ws_tokens[0] == ("Token.Name.Attribute", option) - # Value could be Name.Variable or Name.Constant depending on case - assert non_ws_tokens[1][1] == value - - -# --- Long option with value fixtures --- - - -class LongOptionValueFixture(t.NamedTuple): - """Test fixture for long options with = values.""" - - test_id: str - input_text: str - option: str - value: str - - -LONG_OPTION_VALUE_FIXTURES: list[LongOptionValueFixture] = [ - LongOptionValueFixture( - test_id="uppercase_value", - input_text="--config=FILE", - option="--config", - value="FILE", - ), - LongOptionValueFixture( - test_id="lowercase_value", - input_text="--output=path", - option="--output", - value="path", - ), -] - - -@pytest.mark.parametrize( - LongOptionValueFixture._fields, - LONG_OPTION_VALUE_FIXTURES, - ids=[f.test_id for f in LONG_OPTION_VALUE_FIXTURES], -) -def test_long_option_with_value( - test_id: str, - input_text: str, - option: str, - value: str, -) -> None: - """Test long option with = value tokenization.""" - tokens = get_tokens(input_text) - non_ws_tokens = [(t, v) for t, v in tokens if "Whitespace" not in t] - - assert len(non_ws_tokens) >= 3 - assert non_ws_tokens[0] == ("Token.Name.Tag", option) - assert non_ws_tokens[1] == ("Token.Operator", "=") - assert non_ws_tokens[2][1] == value - - -# --- Full usage string fixtures --- - - -class UsageStringFixture(t.NamedTuple): - """Test fixture for full usage string tokenization.""" - - test_id: str - input_text: str - expected_contains: list[tuple[str, str]] - - -USAGE_STRING_FIXTURES: list[UsageStringFixture] = [ - UsageStringFixture( - test_id="simple_usage", - input_text="usage: cmd [-h]", - expected_contains=[ - ("Token.Generic.Heading", "usage:"), - ("Token.Name.Label", "cmd"), - ("Token.Punctuation", "["), - ("Token.Name.Attribute", "-h"), - ("Token.Punctuation", "]"), - ], - ), - UsageStringFixture( - test_id="mutually_exclusive", - input_text="[--json | --ndjson | --table]", - expected_contains=[ - ("Token.Name.Tag", "--json"), - ("Token.Operator", "|"), - ("Token.Name.Tag", "--ndjson"), - ("Token.Operator", "|"), - ("Token.Name.Tag", "--table"), - ], - ), - UsageStringFixture( - test_id="subcommand", - input_text="usage: vcspull sync", - expected_contains=[ - ("Token.Generic.Heading", "usage:"), - ("Token.Name.Label", "vcspull"), - ("Token.Name.Label", "sync"), - ], - ), - UsageStringFixture( - test_id="positional_args", - input_text="[repo-name] [path]", - expected_contains=[ - ("Token.Punctuation", "["), - ("Token.Name.Label", "repo-name"), - ("Token.Punctuation", "]"), - ("Token.Punctuation", "["), - ("Token.Name.Label", "path"), - ("Token.Punctuation", "]"), - ], - ), -] - - -@pytest.mark.parametrize( - UsageStringFixture._fields, - USAGE_STRING_FIXTURES, - ids=[f.test_id for f in USAGE_STRING_FIXTURES], -) -def test_usage_string( - test_id: str, - input_text: str, - expected_contains: list[tuple[str, str]], -) -> None: - """Test full usage string tokenization contains expected tokens.""" - tokens = get_tokens(input_text) - for expected_type, expected_value in expected_contains: - assert (expected_type, expected_value) in tokens, ( - f"Expected ({expected_type}, {expected_value!r}) not found in tokens" - ) - - -# --- Real vcspull usage output test --- - - -def test_vcspull_sync_usage() -> None: - """Test real vcspull sync usage output tokenization.""" - usage_text = """\ -usage: vcspull sync [-h] [-c CONFIG] [-d DIRECTORY] - [--json | --ndjson | --table] [--color {auto,always,never}] - [--no-progress] [--verbose] - [repo-name] [path]""" - - tokens = get_tokens(usage_text) - - # Check key elements are present - # Note: DIRECTORY after -d is Name.Variable (option value), not Name.Constant - expected = [ - ("Token.Generic.Heading", "usage:"), - ("Token.Name.Label", "vcspull"), - ("Token.Name.Label", "sync"), - ("Token.Name.Attribute", "-h"), - ("Token.Name.Attribute", "-c"), - ("Token.Name.Variable", "CONFIG"), # Option value, not standalone metavar - ("Token.Name.Attribute", "-d"), - ("Token.Name.Variable", "DIRECTORY"), # Option value, not standalone metavar - ("Token.Name.Tag", "--json"), - ("Token.Name.Tag", "--ndjson"), - ("Token.Name.Tag", "--table"), - ("Token.Name.Tag", "--color"), - ("Token.Name.Tag", "--no-progress"), - ("Token.Name.Tag", "--verbose"), - ("Token.Name.Label", "repo-name"), - ("Token.Name.Label", "path"), - ] - - for expected_type, expected_value in expected: - assert (expected_type, expected_value) in tokens, ( - f"Expected ({expected_type}, {expected_value!r}) not in tokens" - ) - - -# --- tokenize_usage helper function test --- - - -def test_tokenize_usage_helper() -> None: - """Test the tokenize_usage helper function.""" - result = tokenize_usage("usage: cmd [-h]") - - assert result[0] == ("Token.Generic.Heading", "usage:") - assert ("Token.Name.Label", "cmd") in result - assert ("Token.Name.Attribute", "-h") in result diff --git a/uv.lock b/uv.lock index 5606904017..66783ec351 100644 --- a/uv.lock +++ b/uv.lock @@ -2,11 +2,34 @@ version = 1 revision = 3 requires-python = ">=3.10, <4.0" resolution-markers = [ - "python_full_version >= '3.12'", - "python_full_version == '3.11.*'", + "python_full_version >= '3.15'", + "python_full_version >= '3.11' and python_full_version < '3.15'", "python_full_version < '3.11'", ] +[options] +exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. +exclude-newer-span = "P3D" + +[options.exclude-newer-package] +libtmux = false +gp-sphinx = false +sphinx-autodoc-sphinx = false +sphinx-autodoc-api-style = false +sphinx-autodoc-fastmcp = false +sphinx-autodoc-pytest-fixtures = false +sphinx-ux-autodoc-layout = false +sphinx-autodoc-argparse = false +sphinx-ux-badges = false +gp-libs = false +sphinx-autodoc-docutils = false +gp-furo-theme = false +sphinx-gp-sitemap = false +sphinx-fonts = false +sphinx-autodoc-typehints-gp = false +sphinx-gp-opengraph = false +sphinx-gp-theme = false + [[package]] name = "aafigure" version = "0.6" @@ -39,16 +62,54 @@ wheels = [ [[package]] name = "anyio" -version = "4.12.1" +version = "4.13.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "idna" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" }, + { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, +] + +[[package]] +name = "ast-serialize" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/9d/912fefab0e30aee6a3af8a62bbea4a81b29afa4ba2c973d31170620a26de/ast_serialize-0.3.0.tar.gz", hash = "sha256:1bc3ca09a63a021376527c4e938deedd11d11d675ce850e6f9c7487f5889992b", size = 60689, upload-time = "2026-04-30T23:24:48.104Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/57/a54d4de491d6cdd7a4e4b0952cc3ca9f60dcefa7b5fb48d6d492debe1649/ast_serialize-0.3.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:3a867927df59f76a18dc1d874a0b2c079b42c58972dca637905576deb0912e14", size = 1182966, upload-time = "2026-04-30T23:23:57.376Z" }, + { url = "https://files.pythonhosted.org/packages/ee/9e/a5db014bb0f91b209236b57c429389e31290c0093532b8436d577699b2fa/ast_serialize-0.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a6fb063bf040abf8321e7b8113a0554eda445ffc508aa51287f8808886a5ae22", size = 1171316, upload-time = "2026-04-30T23:23:59.63Z" }, + { url = "https://files.pythonhosted.org/packages/15/59/fd55133e478c4326f60a11df02573bf7ccb2ac685810b50f1803d0f68053/ast_serialize-0.3.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5075cd8482573d743586779e5f9b652a015e37d4e95132d7e5a9bc5c8f483d8f", size = 1232234, upload-time = "2026-04-30T23:24:01.168Z" }, + { url = "https://files.pythonhosted.org/packages/cc/79/0ca1d26357ecb4a697d74d00b73ef3137f24c140424125393a0de820eb09/ast_serialize-0.3.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:41560b27794f4553b0f77811e9fb325b77db4a2b39018d437e09932275306e66", size = 1233437, upload-time = "2026-04-30T23:24:03.151Z" }, + { url = "https://files.pythonhosted.org/packages/53/3e/7078ec94dd6e124b8e028ac77016a4f13c83fa1c145790f2e68f3816998b/ast_serialize-0.3.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b967c01ca74909c5d90e0fe4393401e2cc5da5ebd9a6262a19e45ffd3757dec8", size = 1440188, upload-time = "2026-04-30T23:24:04.717Z" }, + { url = "https://files.pythonhosted.org/packages/21/16/cca7195ef55a012f8013c3442afa91d287a0a36dcf88b480b262475135b3/ast_serialize-0.3.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:424ebb8f46cd993f7cec4009d119312d8433dd90e6b0df0499cd2c91bdcc5af9", size = 1254211, upload-time = "2026-04-30T23:24:06.18Z" }, + { url = "https://files.pythonhosted.org/packages/a0/0f/f3d4dfae67dee6580534361a6343367d34217e7d25cff858bd1d8f03b8ed/ast_serialize-0.3.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d14b1d566b56e2ee70b11fec1de7e0b94ec7cd83717ec7d189967841a361190e", size = 1255973, upload-time = "2026-04-30T23:24:07.772Z" }, + { url = "https://files.pythonhosted.org/packages/14/41/55fbfe02c42f40fbe3e74eda167d977d555ff720ce1abfa08515236efd88/ast_serialize-0.3.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7ba30b18735f047ec11103d1ab92f4789cf1fea1e0dc89b04a2f5a0632fd79de", size = 1298629, upload-time = "2026-04-30T23:24:09.4Z" }, + { url = "https://files.pythonhosted.org/packages/28/36/7d2501cacc7989fb8504aa9da2a2022a174200a59d4e6639de4367a57fdd/ast_serialize-0.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e6ea0754cb7b0f682ebb005ffb0d18f8d17993490d9c289863cd69cacc4ab8df", size = 1408435, upload-time = "2026-04-30T23:24:11.013Z" }, + { url = "https://files.pythonhosted.org/packages/03/e7/54e3b469c3fa0bf9cd532fa643d1d33b73303f8d70beac3e366b68dd64b7/ast_serialize-0.3.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:a0c5aa1073a5ba7b2abaa4b54abe8b8d75c4d1e2d54a2ff70b0ca6222fea5728", size = 1508174, upload-time = "2026-04-30T23:24:12.635Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2a/9b9621865b02c60539e26d9b114a312b4fa46aa703e33e79317174bfea21/ast_serialize-0.3.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:4e52650d834c1ea7791969a361de2c54c13b2fb4c519ec79445fa8b9021a147d", size = 1502354, upload-time = "2026-04-30T23:24:14.186Z" }, + { url = "https://files.pythonhosted.org/packages/34/dd/f138bc5c43b0c414fdd12eefe15677839323078b6e75301ad7f96cd26d45/ast_serialize-0.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:15bd6af3f136c61dae27805eb6b8f3269e85a545c4c27ffe9e530ead78d2b36d", size = 1450504, upload-time = "2026-04-30T23:24:16.076Z" }, + { url = "https://files.pythonhosted.org/packages/68/cf/97ef9e1c315601db74365955c8edd3292e3055500d6317602815dbdf08ae/ast_serialize-0.3.0-cp314-cp314t-win32.whl", hash = "sha256:d188bfe37b674b49708497683051d4b571366a668799c9b8e8a94513694969d9", size = 1058662, upload-time = "2026-04-30T23:24:17.535Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d6/e2c3483c31580fdb623f92ad38d2f856cde4b9205a3e6bd84760f3de7d82/ast_serialize-0.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:5832c2fdf8f8a6cf682b4cfcf677f5eaf39b4ddbc490f5480cfccdd1e7ce8fa1", size = 1100349, upload-time = "2026-04-30T23:24:18.992Z" }, + { url = "https://files.pythonhosted.org/packages/ab/89/29abcb1fe18a429cda60c6e0bbd1d6e90499339842a2f548d7567542357e/ast_serialize-0.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:670f177188d128fb7f9f15b5ad0e1b553d22c34e3f584dcb83eb8077600437f0", size = 1072895, upload-time = "2026-04-30T23:24:20.706Z" }, + { url = "https://files.pythonhosted.org/packages/bc/93/72abad83966ed6235647c9f956417dc1e17e997696388521910e3d1fa3f4/ast_serialize-0.3.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:2ec2fafa5e4313cc8feed96e436ebe19ac7bc6fa41fbc2827e826c48b9e4c3a9", size = 1190024, upload-time = "2026-04-30T23:24:22.486Z" }, + { url = "https://files.pythonhosted.org/packages/85/4f/eb88584b2f0234e581762011208ca203252bf6c98e59b4769daa571f3576/ast_serialize-0.3.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ef6d3c08b7b4cd29b48410338e134764a00e76d25841eb02c1084e868c888ecc", size = 1178633, upload-time = "2026-04-30T23:24:24.35Z" }, + { url = "https://files.pythonhosted.org/packages/56/51/cf1ec1ff3e616373d0dcbd5fad502e0029dc541f13ab642259762a7d127f/ast_serialize-0.3.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d841424f41b886e98044abc80769c14a956e6e5ccd5fb5b0d9f5ead72be18a4", size = 1241351, upload-time = "2026-04-30T23:24:25.987Z" }, + { url = "https://files.pythonhosted.org/packages/0d/44/68fcf50478cf1093f2d423f034ae06453122c8b415d8e21a44668eca485d/ast_serialize-0.3.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d21453734ad39367ede5d37efe4f59f830ce1c09f432fc72a90e368f77a4a3e7", size = 1239582, upload-time = "2026-04-30T23:24:27.808Z" }, + { url = "https://files.pythonhosted.org/packages/9d/c1/a6c9fa284eceb5fc6f21347e968445a051d7ca2c4d34e6a04314646dbcee/ast_serialize-0.3.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f5e110cdce2a347e1dd987529c88ef54d26f67848dce3eba1b3b2cc2cf085c94", size = 1448853, upload-time = "2026-04-30T23:24:29.534Z" }, + { url = "https://files.pythonhosted.org/packages/23/5f/8ad3829a09e4e8c5328a53ce7d4711d660944e3e164c5f6abcc2c8f27167/ast_serialize-0.3.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b6e23a98e57560a055f5c4b68700a0fd5ce483d2814c23140b3638c7f5d1e61", size = 1262204, upload-time = "2026-04-30T23:24:31.482Z" }, + { url = "https://files.pythonhosted.org/packages/25/13/44aa28d97f10e25247e8576b5f6b2795d4fa1a80acc88acc942c508d06f7/ast_serialize-0.3.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1c9e763d70293d65ce1e1ea8c943140c68d0953f0268c7ee0998f2e07f77dd0", size = 1266458, upload-time = "2026-04-30T23:24:33.088Z" }, + { url = "https://files.pythonhosted.org/packages/d8/58/b3a8be3777cd3744324fd5cec0d80d37cd96fc7cbb0fb010e03dff1e870f/ast_serialize-0.3.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4388a1796c228f1ce5c391426f7d21a0003ad3b47f677dbeded9bd1a85c7209f", size = 1308700, upload-time = "2026-04-30T23:24:34.657Z" }, + { url = "https://files.pythonhosted.org/packages/13/03/f8312d6b57f5471a9dc7946f22b8798a1fc296d38c25766223aacadec42c/ast_serialize-0.3.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:5283cdcc0c64c3d8b9b688dc6aaa012d9c0cf1380a7f774a6bae6a1c01b3205a", size = 1416724, upload-time = "2026-04-30T23:24:36.562Z" }, + { url = "https://files.pythonhosted.org/packages/50/5d/13fc3789a7abac00559da2e2e9f386db4612aa1f84fc53d09bf714c37545/ast_serialize-0.3.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:f5ef88cc5842a5d7a6ac09dc0d5fc2c98f5d276c1f076f866d55047ce886785b", size = 1515441, upload-time = "2026-04-30T23:24:38.018Z" }, + { url = "https://files.pythonhosted.org/packages/eb/b9/7ab43fc7a23b1f970281093228f5f79bed6edeed7a3e672bde6d7a832a58/ast_serialize-0.3.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:cc14bf402bdc0978594ecce783793de2c7470cd4f5cd7eb286ca97ed8ff7cba9", size = 1510522, upload-time = "2026-04-30T23:24:39.798Z" }, + { url = "https://files.pythonhosted.org/packages/56/ec/d75fc2b788d319f1fad77c14156896f31afdfc68af85b505e5bdebcb9592/ast_serialize-0.3.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:11eae0cf1b7b3e0678133cc2daa974ea972caf02eb4b3aa062af6fa9acd52c57", size = 1460917, upload-time = "2026-04-30T23:24:41.305Z" }, + { url = "https://files.pythonhosted.org/packages/95/74/f99c81193a2725911e1911ae567ed27c2f2419332c7f3537366f9d238cac/ast_serialize-0.3.0-cp39-abi3-win32.whl", hash = "sha256:2db3dd99de5e6a5a11d7dda73de8750eb6e5baaf25245adf7bdcfe64b6108ae2", size = 1067804, upload-time = "2026-04-30T23:24:43.091Z" }, + { url = "https://files.pythonhosted.org/packages/16/81/76af00c47daa151e89f98ae21fbbcb2840aaa9f5766579c4da76a3c57188/ast_serialize-0.3.0-cp39-abi3-win_amd64.whl", hash = "sha256:a2cd125adccf7969470621905d302750cd25951f22ea430d9a25b7be031e5549", size = 1105561, upload-time = "2026-04-30T23:24:44.578Z" }, + { url = "https://files.pythonhosted.org/packages/bd/46/d3ec57ad500f598d1554bd14ce4df615960549ab2844961bc4e1f5fbd174/ast_serialize-0.3.0-cp39-abi3-win_arm64.whl", hash = "sha256:0dd00da29985f15f50dc35728b7e1e7c84507bccfea1d9914738530f1c72238a", size = 1077165, upload-time = "2026-04-30T23:24:46.377Z" }, ] [[package]] @@ -75,112 +136,128 @@ wheels = [ [[package]] name = "certifi" -version = "2026.2.25" +version = "2026.4.22" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } +sdist = { url = "https://files.pythonhosted.org/packages/25/ee/6caf7a40c36a1220410afe15a1cc64993a1f864871f698c0f93acb72842a/certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580", size = 137077, upload-time = "2026-04-22T11:26:11.191Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, + { url = "https://files.pythonhosted.org/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a", size = 135707, upload-time = "2026-04-22T11:26:09.372Z" }, ] [[package]] name = "charset-normalizer" -version = "3.4.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1d/35/02daf95b9cd686320bb622eb148792655c9412dbb9b67abb5694e5910a24/charset_normalizer-3.4.5.tar.gz", hash = "sha256:95adae7b6c42a6c5b5b559b1a99149f090a57128155daeea91732c8d970d8644", size = 134804, upload-time = "2026-03-06T06:03:19.46Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/21/a2b1505639008ba2e6ef03733a81fc6cfd6a07ea6139a2b76421230b8dad/charset_normalizer-3.4.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4167a621a9a1a986c73777dbc15d4b5eac8ac5c10393374109a343d4013ec765", size = 283319, upload-time = "2026-03-06T06:00:26.433Z" }, - { url = "https://files.pythonhosted.org/packages/70/67/df234c29b68f4e1e095885c9db1cb4b69b8aba49cf94fac041db4aaf1267/charset_normalizer-3.4.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f64c6bf8f32f9133b668c7f7a7cbdbc453412bc95ecdbd157f3b1e377a92990", size = 189974, upload-time = "2026-03-06T06:00:28.222Z" }, - { url = "https://files.pythonhosted.org/packages/df/7f/fc66af802961c6be42e2c7b69c58f95cbd1f39b0e81b3365d8efe2a02a04/charset_normalizer-3.4.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:568e3c34b58422075a1b49575a6abc616d9751b4d61b23f712e12ebb78fe47b2", size = 207866, upload-time = "2026-03-06T06:00:29.769Z" }, - { url = "https://files.pythonhosted.org/packages/c9/23/404eb36fac4e95b833c50e305bba9a241086d427bb2167a42eac7c4f7da4/charset_normalizer-3.4.5-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:036c079aa08a6a592b82487f97c60b439428320ed1b2ea0b3912e99d30c77765", size = 203239, upload-time = "2026-03-06T06:00:31.086Z" }, - { url = "https://files.pythonhosted.org/packages/4b/2f/8a1d989bfadd120c90114ab33e0d2a0cbde05278c1fc15e83e62d570f50a/charset_normalizer-3.4.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:340810d34ef83af92148e96e3e44cb2d3f910d2bf95e5618a5c467d9f102231d", size = 196529, upload-time = "2026-03-06T06:00:32.608Z" }, - { url = "https://files.pythonhosted.org/packages/a5/0c/c75f85ff7ca1f051958bb518cd43922d86f576c03947a050fbedfdfb4f15/charset_normalizer-3.4.5-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:cd2d0f0ec9aa977a27731a3209ebbcacebebaf41f902bd453a928bfd281cf7f8", size = 184152, upload-time = "2026-03-06T06:00:33.93Z" }, - { url = "https://files.pythonhosted.org/packages/f9/20/4ed37f6199af5dde94d4aeaf577f3813a5ec6635834cda1d957013a09c76/charset_normalizer-3.4.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0b362bcd27819f9c07cbf23db4e0e8cd4b44c5ecd900c2ff907b2b92274a7412", size = 195226, upload-time = "2026-03-06T06:00:35.469Z" }, - { url = "https://files.pythonhosted.org/packages/28/31/7ba1102178cba7c34dcc050f43d427172f389729e356038f0726253dd914/charset_normalizer-3.4.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:77be992288f720306ab4108fe5c74797de327f3248368dfc7e1a916d6ed9e5a2", size = 192933, upload-time = "2026-03-06T06:00:36.83Z" }, - { url = "https://files.pythonhosted.org/packages/4b/23/f86443ab3921e6a60b33b93f4a1161222231f6c69bc24fb18f3bee7b8518/charset_normalizer-3.4.5-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:8b78d8a609a4b82c273257ee9d631ded7fac0d875bdcdccc109f3ee8328cfcb1", size = 185647, upload-time = "2026-03-06T06:00:38.367Z" }, - { url = "https://files.pythonhosted.org/packages/82/44/08b8be891760f1f5a6d23ce11d6d50c92981603e6eb740b4f72eea9424e2/charset_normalizer-3.4.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ba20bdf69bd127f66d0174d6f2a93e69045e0b4036dc1ca78e091bcc765830c4", size = 209533, upload-time = "2026-03-06T06:00:41.931Z" }, - { url = "https://files.pythonhosted.org/packages/3b/5f/df114f23406199f8af711ddccfbf409ffbc5b7cdc18fa19644997ff0c9bb/charset_normalizer-3.4.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:76a9d0de4d0eab387822e7b35d8f89367dd237c72e82ab42b9f7bf5e15ada00f", size = 195901, upload-time = "2026-03-06T06:00:43.978Z" }, - { url = "https://files.pythonhosted.org/packages/07/83/71ef34a76fe8aa05ff8f840244bda2d61e043c2ef6f30d200450b9f6a1be/charset_normalizer-3.4.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8fff79bf5978c693c9b1a4d71e4a94fddfb5fe744eb062a318e15f4a2f63a550", size = 204950, upload-time = "2026-03-06T06:00:45.202Z" }, - { url = "https://files.pythonhosted.org/packages/58/40/0253be623995365137d7dc68e45245036207ab2227251e69a3d93ce43183/charset_normalizer-3.4.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c7e84e0c0005e3bdc1a9211cd4e62c78ba80bc37b2365ef4410cd2007a9047f2", size = 198546, upload-time = "2026-03-06T06:00:46.481Z" }, - { url = "https://files.pythonhosted.org/packages/ed/5c/5f3cb5b259a130895ef5ae16b38eaf141430fa3f7af50cd06c5d67e4f7b2/charset_normalizer-3.4.5-cp310-cp310-win32.whl", hash = "sha256:58ad8270cfa5d4bef1bc85bd387217e14ff154d6630e976c6f56f9a040757475", size = 132516, upload-time = "2026-03-06T06:00:47.924Z" }, - { url = "https://files.pythonhosted.org/packages/a5/c3/84fb174e7770f2df2e1a2115090771bfbc2227fb39a765c6d00568d1aab4/charset_normalizer-3.4.5-cp310-cp310-win_amd64.whl", hash = "sha256:02a9d1b01c1e12c27883b0c9349e0bcd9ae92e727ff1a277207e1a262b1cbf05", size = 142906, upload-time = "2026-03-06T06:00:49.389Z" }, - { url = "https://files.pythonhosted.org/packages/d7/b2/6f852f8b969f2cbd0d4092d2e60139ab1af95af9bb651337cae89ec0f684/charset_normalizer-3.4.5-cp310-cp310-win_arm64.whl", hash = "sha256:039215608ac7b358c4da0191d10fc76868567fbf276d54c14721bdedeb6de064", size = 133258, upload-time = "2026-03-06T06:00:51.051Z" }, - { url = "https://files.pythonhosted.org/packages/8f/9e/bcec3b22c64ecec47d39bf5167c2613efd41898c019dccd4183f6aa5d6a7/charset_normalizer-3.4.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:610f72c0ee565dfb8ae1241b666119582fdbfe7c0975c175be719f940e110694", size = 279531, upload-time = "2026-03-06T06:00:52.252Z" }, - { url = "https://files.pythonhosted.org/packages/58/12/81fd25f7e7078ab5d1eedbb0fac44be4904ae3370a3bf4533c8f2d159acd/charset_normalizer-3.4.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60d68e820af339df4ae8358c7a2e7596badeb61e544438e489035f9fbf3246a5", size = 188006, upload-time = "2026-03-06T06:00:53.8Z" }, - { url = "https://files.pythonhosted.org/packages/ae/6e/f2d30e8c27c1b0736a6520311982cf5286cfc7f6cac77d7bc1325e3a23f2/charset_normalizer-3.4.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:10b473fc8dca1c3ad8559985794815f06ca3fc71942c969129070f2c3cdf7281", size = 205085, upload-time = "2026-03-06T06:00:55.311Z" }, - { url = "https://files.pythonhosted.org/packages/d0/90/d12cefcb53b5931e2cf792a33718d7126efb116a320eaa0742c7059a95e4/charset_normalizer-3.4.5-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d4eb8ac7469b2a5d64b5b8c04f84d8bf3ad340f4514b98523805cbf46e3b3923", size = 200545, upload-time = "2026-03-06T06:00:56.532Z" }, - { url = "https://files.pythonhosted.org/packages/03/f4/44d3b830a20e89ff82a3134912d9a1cf6084d64f3b95dcad40f74449a654/charset_normalizer-3.4.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bcb3227c3d9aaf73eaaab1db7ccd80a8995c509ee9941e2aae060ca6e4e5d81", size = 193863, upload-time = "2026-03-06T06:00:57.823Z" }, - { url = "https://files.pythonhosted.org/packages/25/4b/f212119c18a6320a9d4a730d1b4057875cdeabf21b3614f76549042ef8a8/charset_normalizer-3.4.5-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:75ee9c1cce2911581a70a3c0919d8bccf5b1cbc9b0e5171400ec736b4b569497", size = 181827, upload-time = "2026-03-06T06:00:59.323Z" }, - { url = "https://files.pythonhosted.org/packages/74/00/b26158e48b425a202a92965f8069e8a63d9af1481dfa206825d7f74d2a3c/charset_normalizer-3.4.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d1401945cb77787dbd3af2446ff2d75912327c4c3a1526ab7955ecf8600687c", size = 191085, upload-time = "2026-03-06T06:01:00.546Z" }, - { url = "https://files.pythonhosted.org/packages/c4/c2/1c1737bf6fd40335fe53d28fe49afd99ee4143cc57a845e99635ce0b9b6d/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a45e504f5e1be0bd385935a8e1507c442349ca36f511a47057a71c9d1d6ea9e", size = 190688, upload-time = "2026-03-06T06:01:02.479Z" }, - { url = "https://files.pythonhosted.org/packages/5a/3d/abb5c22dc2ef493cd56522f811246a63c5427c08f3e3e50ab663de27fcf4/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e09f671a54ce70b79a1fc1dc6da3072b7ef7251fadb894ed92d9aa8218465a5f", size = 183077, upload-time = "2026-03-06T06:01:04.231Z" }, - { url = "https://files.pythonhosted.org/packages/44/33/5298ad4d419a58e25b3508e87f2758d1442ff00c2471f8e0403dab8edad5/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d01de5e768328646e6a3fa9e562706f8f6641708c115c62588aef2b941a4f88e", size = 206706, upload-time = "2026-03-06T06:01:05.773Z" }, - { url = "https://files.pythonhosted.org/packages/7b/17/51e7895ac0f87c3b91d276a449ef09f5532a7529818f59646d7a55089432/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:131716d6786ad5e3dc542f5cc6f397ba3339dc0fb87f87ac30e550e8987756af", size = 191665, upload-time = "2026-03-06T06:01:07.473Z" }, - { url = "https://files.pythonhosted.org/packages/90/8f/cce9adf1883e98906dbae380d769b4852bb0fa0004bc7d7a2243418d3ea8/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a374cc0b88aa710e8865dc1bd6edb3743c59f27830f0293ab101e4cf3ce9f85", size = 201950, upload-time = "2026-03-06T06:01:08.973Z" }, - { url = "https://files.pythonhosted.org/packages/08/ca/bce99cd5c397a52919e2769d126723f27a4c037130374c051c00470bcd38/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d31f0d1671e1534e395f9eb84a68e0fb670e1edb1fe819a9d7f564ae3bc4e53f", size = 195830, upload-time = "2026-03-06T06:01:10.155Z" }, - { url = "https://files.pythonhosted.org/packages/87/4f/2e3d023a06911f1281f97b8f036edc9872167036ca6f55cc874a0be6c12c/charset_normalizer-3.4.5-cp311-cp311-win32.whl", hash = "sha256:cace89841c0599d736d3d74a27bc5821288bb47c5441923277afc6059d7fbcb4", size = 132029, upload-time = "2026-03-06T06:01:11.706Z" }, - { url = "https://files.pythonhosted.org/packages/fe/1f/a853b73d386521fd44b7f67ded6b17b7b2367067d9106a5c4b44f9a34274/charset_normalizer-3.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:f8102ae93c0bc863b1d41ea0f4499c20a83229f52ed870850892df555187154a", size = 142404, upload-time = "2026-03-06T06:01:12.865Z" }, - { url = "https://files.pythonhosted.org/packages/b4/10/dba36f76b71c38e9d391abe0fd8a5b818790e053c431adecfc98c35cd2a9/charset_normalizer-3.4.5-cp311-cp311-win_arm64.whl", hash = "sha256:ed98364e1c262cf5f9363c3eca8c2df37024f52a8fa1180a3610014f26eac51c", size = 132796, upload-time = "2026-03-06T06:01:14.106Z" }, - { url = "https://files.pythonhosted.org/packages/9c/b6/9ee9c1a608916ca5feae81a344dffbaa53b26b90be58cc2159e3332d44ec/charset_normalizer-3.4.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed97c282ee4f994ef814042423a529df9497e3c666dca19be1d4cd1129dc7ade", size = 280976, upload-time = "2026-03-06T06:01:15.276Z" }, - { url = "https://files.pythonhosted.org/packages/f8/d8/a54f7c0b96f1df3563e9190f04daf981e365a9b397eedfdfb5dbef7e5c6c/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0294916d6ccf2d069727d65973c3a1ca477d68708db25fd758dd28b0827cff54", size = 189356, upload-time = "2026-03-06T06:01:16.511Z" }, - { url = "https://files.pythonhosted.org/packages/42/69/2bf7f76ce1446759a5787cb87d38f6a61eb47dbbdf035cfebf6347292a65/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dc57a0baa3eeedd99fafaef7511b5a6ef4581494e8168ee086031744e2679467", size = 206369, upload-time = "2026-03-06T06:01:17.853Z" }, - { url = "https://files.pythonhosted.org/packages/10/9c/949d1a46dab56b959d9a87272482195f1840b515a3380e39986989a893ae/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ed1a9a204f317ef879b32f9af507d47e49cd5e7f8e8d5d96358c98373314fc60", size = 203285, upload-time = "2026-03-06T06:01:19.473Z" }, - { url = "https://files.pythonhosted.org/packages/67/5c/ae30362a88b4da237d71ea214a8c7eb915db3eec941adda511729ac25fa2/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ad83b8f9379176c841f8865884f3514d905bcd2a9a3b210eaa446e7d2223e4d", size = 196274, upload-time = "2026-03-06T06:01:20.728Z" }, - { url = "https://files.pythonhosted.org/packages/b2/07/c9f2cb0e46cb6d64fdcc4f95953747b843bb2181bda678dc4e699b8f0f9a/charset_normalizer-3.4.5-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:a118e2e0b5ae6b0120d5efa5f866e58f2bb826067a646431da4d6a2bdae7950e", size = 184715, upload-time = "2026-03-06T06:01:22.194Z" }, - { url = "https://files.pythonhosted.org/packages/36/64/6b0ca95c44fddf692cd06d642b28f63009d0ce325fad6e9b2b4d0ef86a52/charset_normalizer-3.4.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:754f96058e61a5e22e91483f823e07df16416ce76afa4ebf306f8e1d1296d43f", size = 193426, upload-time = "2026-03-06T06:01:23.795Z" }, - { url = "https://files.pythonhosted.org/packages/50/bc/a730690d726403743795ca3f5bb2baf67838c5fea78236098f324b965e40/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0c300cefd9b0970381a46394902cd18eaf2aa00163f999590ace991989dcd0fc", size = 191780, upload-time = "2026-03-06T06:01:25.053Z" }, - { url = "https://files.pythonhosted.org/packages/97/4f/6c0bc9af68222b22951552d73df4532b5be6447cee32d58e7e8c74ecbb7b/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c108f8619e504140569ee7de3f97d234f0fbae338a7f9f360455071ef9855a95", size = 185805, upload-time = "2026-03-06T06:01:26.294Z" }, - { url = "https://files.pythonhosted.org/packages/dd/b9/a523fb9b0ee90814b503452b2600e4cbc118cd68714d57041564886e7325/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d1028de43596a315e2720a9849ee79007ab742c06ad8b45a50db8cdb7ed4a82a", size = 208342, upload-time = "2026-03-06T06:01:27.55Z" }, - { url = "https://files.pythonhosted.org/packages/4d/61/c59e761dee4464050713e50e27b58266cc8e209e518c0b378c1580c959ba/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:19092dde50335accf365cce21998a1c6dd8eafd42c7b226eb54b2747cdce2fac", size = 193661, upload-time = "2026-03-06T06:01:29.051Z" }, - { url = "https://files.pythonhosted.org/packages/1c/43/729fa30aad69783f755c5ad8649da17ee095311ca42024742701e202dc59/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4354e401eb6dab9aed3c7b4030514328a6c748d05e1c3e19175008ca7de84fb1", size = 204819, upload-time = "2026-03-06T06:01:30.298Z" }, - { url = "https://files.pythonhosted.org/packages/87/33/d9b442ce5a91b96fc0840455a9e49a611bbadae6122778d0a6a79683dd31/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a68766a3c58fde7f9aaa22b3786276f62ab2f594efb02d0a1421b6282e852e98", size = 198080, upload-time = "2026-03-06T06:01:31.478Z" }, - { url = "https://files.pythonhosted.org/packages/56/5a/b8b5a23134978ee9885cee2d6995f4c27cc41f9baded0a9685eabc5338f0/charset_normalizer-3.4.5-cp312-cp312-win32.whl", hash = "sha256:1827734a5b308b65ac54e86a618de66f935a4f63a8a462ff1e19a6788d6c2262", size = 132630, upload-time = "2026-03-06T06:01:33.056Z" }, - { url = "https://files.pythonhosted.org/packages/70/53/e44a4c07e8904500aec95865dc3f6464dc3586a039ef0df606eb3ac38e35/charset_normalizer-3.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:728c6a963dfab66ef865f49286e45239384249672cd598576765acc2a640a636", size = 142856, upload-time = "2026-03-06T06:01:34.489Z" }, - { url = "https://files.pythonhosted.org/packages/ea/aa/c5628f7cad591b1cf45790b7a61483c3e36cf41349c98af7813c483fd6e8/charset_normalizer-3.4.5-cp312-cp312-win_arm64.whl", hash = "sha256:75dfd1afe0b1647449e852f4fb428195a7ed0588947218f7ba929f6538487f02", size = 132982, upload-time = "2026-03-06T06:01:35.641Z" }, - { url = "https://files.pythonhosted.org/packages/f5/48/9f34ec4bb24aa3fdba1890c1bddb97c8a4be1bd84ef5c42ac2352563ad05/charset_normalizer-3.4.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ac59c15e3f1465f722607800c68713f9fbc2f672b9eb649fe831da4019ae9b23", size = 280788, upload-time = "2026-03-06T06:01:37.126Z" }, - { url = "https://files.pythonhosted.org/packages/0e/09/6003e7ffeb90cc0560da893e3208396a44c210c5ee42efff539639def59b/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:165c7b21d19365464e8f70e5ce5e12524c58b48c78c1f5a57524603c1ab003f8", size = 188890, upload-time = "2026-03-06T06:01:38.73Z" }, - { url = "https://files.pythonhosted.org/packages/42/1e/02706edf19e390680daa694d17e2b8eab4b5f7ac285e2a51168b4b22ee6b/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:28269983f25a4da0425743d0d257a2d6921ea7d9b83599d4039486ec5b9f911d", size = 206136, upload-time = "2026-03-06T06:01:40.016Z" }, - { url = "https://files.pythonhosted.org/packages/c7/87/942c3def1b37baf3cf786bad01249190f3ca3d5e63a84f831e704977de1f/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d27ce22ec453564770d29d03a9506d449efbb9fa13c00842262b2f6801c48cce", size = 202551, upload-time = "2026-03-06T06:01:41.522Z" }, - { url = "https://files.pythonhosted.org/packages/94/0a/af49691938dfe175d71b8a929bd7e4ace2809c0c5134e28bc535660d5262/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0625665e4ebdddb553ab185de5db7054393af8879fb0c87bd5690d14379d6819", size = 195572, upload-time = "2026-03-06T06:01:43.208Z" }, - { url = "https://files.pythonhosted.org/packages/20/ea/dfb1792a8050a8e694cfbde1570ff97ff74e48afd874152d38163d1df9ae/charset_normalizer-3.4.5-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:c23eb3263356d94858655b3e63f85ac5d50970c6e8febcdde7830209139cc37d", size = 184438, upload-time = "2026-03-06T06:01:44.755Z" }, - { url = "https://files.pythonhosted.org/packages/72/12/c281e2067466e3ddd0595bfaea58a6946765ace5c72dfa3edc2f5f118026/charset_normalizer-3.4.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e6302ca4ae283deb0af68d2fbf467474b8b6aedcd3dab4db187e07f94c109763", size = 193035, upload-time = "2026-03-06T06:01:46.051Z" }, - { url = "https://files.pythonhosted.org/packages/ba/4f/3792c056e7708e10464bad0438a44708886fb8f92e3c3d29ec5e2d964d42/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e51ae7d81c825761d941962450f50d041db028b7278e7b08930b4541b3e45cb9", size = 191340, upload-time = "2026-03-06T06:01:47.547Z" }, - { url = "https://files.pythonhosted.org/packages/e7/86/80ddba897127b5c7a9bccc481b0cd36c8fefa485d113262f0fe4332f0bf4/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:597d10dec876923e5c59e48dbd366e852eacb2b806029491d307daea6b917d7c", size = 185464, upload-time = "2026-03-06T06:01:48.764Z" }, - { url = "https://files.pythonhosted.org/packages/4d/00/b5eff85ba198faacab83e0e4b6f0648155f072278e3b392a82478f8b988b/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5cffde4032a197bd3b42fd0b9509ec60fb70918d6970e4cc773f20fc9180ca67", size = 208014, upload-time = "2026-03-06T06:01:50.371Z" }, - { url = "https://files.pythonhosted.org/packages/c8/11/d36f70be01597fd30850dde8a1269ebc8efadd23ba5785808454f2389bde/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2da4eedcb6338e2321e831a0165759c0c620e37f8cd044a263ff67493be8ffb3", size = 193297, upload-time = "2026-03-06T06:01:51.933Z" }, - { url = "https://files.pythonhosted.org/packages/1a/1d/259eb0a53d4910536c7c2abb9cb25f4153548efb42800c6a9456764649c0/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:65a126fb4b070d05340a84fc709dd9e7c75d9b063b610ece8a60197a291d0adf", size = 204321, upload-time = "2026-03-06T06:01:53.887Z" }, - { url = "https://files.pythonhosted.org/packages/84/31/faa6c5b9d3688715e1ed1bb9d124c384fe2fc1633a409e503ffe1c6398c1/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7a80a9242963416bd81f99349d5f3fce1843c303bd404f204918b6d75a75fd6", size = 197509, upload-time = "2026-03-06T06:01:56.439Z" }, - { url = "https://files.pythonhosted.org/packages/fd/a5/c7d9dd1503ffc08950b3260f5d39ec2366dd08254f0900ecbcf3a6197c7c/charset_normalizer-3.4.5-cp313-cp313-win32.whl", hash = "sha256:f1d725b754e967e648046f00c4facc42d414840f5ccc670c5670f59f83693e4f", size = 132284, upload-time = "2026-03-06T06:01:57.812Z" }, - { url = "https://files.pythonhosted.org/packages/b9/0f/57072b253af40c8aa6636e6de7d75985624c1eb392815b2f934199340a89/charset_normalizer-3.4.5-cp313-cp313-win_amd64.whl", hash = "sha256:e37bd100d2c5d3ba35db9c7c5ba5a9228cbcffe5c4778dc824b164e5257813d7", size = 142630, upload-time = "2026-03-06T06:01:59.062Z" }, - { url = "https://files.pythonhosted.org/packages/31/41/1c4b7cc9f13bd9d369ce3bc993e13d374ce25fa38a2663644283ecf422c1/charset_normalizer-3.4.5-cp313-cp313-win_arm64.whl", hash = "sha256:93b3b2cc5cf1b8743660ce77a4f45f3f6d1172068207c1defc779a36eea6bb36", size = 133254, upload-time = "2026-03-06T06:02:00.281Z" }, - { url = "https://files.pythonhosted.org/packages/43/be/0f0fd9bb4a7fa4fb5067fb7d9ac693d4e928d306f80a0d02bde43a7c4aee/charset_normalizer-3.4.5-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8197abe5ca1ffb7d91e78360f915eef5addff270f8a71c1fc5be24a56f3e4873", size = 280232, upload-time = "2026-03-06T06:02:01.508Z" }, - { url = "https://files.pythonhosted.org/packages/28/02/983b5445e4bef49cd8c9da73a8e029f0825f39b74a06d201bfaa2e55142a/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2aecdb364b8a1802afdc7f9327d55dad5366bc97d8502d0f5854e50712dbc5f", size = 189688, upload-time = "2026-03-06T06:02:02.857Z" }, - { url = "https://files.pythonhosted.org/packages/d0/88/152745c5166437687028027dc080e2daed6fe11cfa95a22f4602591c42db/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a66aa5022bf81ab4b1bebfb009db4fd68e0c6d4307a1ce5ef6a26e5878dfc9e4", size = 206833, upload-time = "2026-03-06T06:02:05.127Z" }, - { url = "https://files.pythonhosted.org/packages/cb/0f/ebc15c8b02af2f19be9678d6eed115feeeccc45ce1f4b098d986c13e8769/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d77f97e515688bd615c1d1f795d540f32542d514242067adcb8ef532504cb9ee", size = 202879, upload-time = "2026-03-06T06:02:06.446Z" }, - { url = "https://files.pythonhosted.org/packages/38/9c/71336bff6934418dc8d1e8a1644176ac9088068bc571da612767619c97b3/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01a1ed54b953303ca7e310fafe0fe347aab348bd81834a0bcd602eb538f89d66", size = 195764, upload-time = "2026-03-06T06:02:08.763Z" }, - { url = "https://files.pythonhosted.org/packages/b7/95/ce92fde4f98615661871bc282a856cf9b8a15f686ba0af012984660d480b/charset_normalizer-3.4.5-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:b2d37d78297b39a9eb9eb92c0f6df98c706467282055419df141389b23f93362", size = 183728, upload-time = "2026-03-06T06:02:10.137Z" }, - { url = "https://files.pythonhosted.org/packages/1c/e7/f5b4588d94e747ce45ae680f0f242bc2d98dbd4eccfab73e6160b6893893/charset_normalizer-3.4.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e71bbb595973622b817c042bd943c3f3667e9c9983ce3d205f973f486fec98a7", size = 192937, upload-time = "2026-03-06T06:02:11.663Z" }, - { url = "https://files.pythonhosted.org/packages/f9/29/9d94ed6b929bf9f48bf6ede6e7474576499f07c4c5e878fb186083622716/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4cd966c2559f501c6fd69294d082c2934c8dd4719deb32c22961a5ac6db0df1d", size = 192040, upload-time = "2026-03-06T06:02:13.489Z" }, - { url = "https://files.pythonhosted.org/packages/15/d2/1a093a1cf827957f9445f2fe7298bcc16f8fc5e05c1ed2ad1af0b239035e/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d5e52d127045d6ae01a1e821acfad2f3a1866c54d0e837828538fabe8d9d1bd6", size = 184107, upload-time = "2026-03-06T06:02:14.83Z" }, - { url = "https://files.pythonhosted.org/packages/0f/7d/82068ce16bd36135df7b97f6333c5d808b94e01d4599a682e2337ed5fd14/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:30a2b1a48478c3428d047ed9690d57c23038dac838a87ad624c85c0a78ebeb39", size = 208310, upload-time = "2026-03-06T06:02:16.165Z" }, - { url = "https://files.pythonhosted.org/packages/84/4e/4dfb52307bb6af4a5c9e73e482d171b81d36f522b21ccd28a49656baa680/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d8ed79b8f6372ca4254955005830fd61c1ccdd8c0fac6603e2c145c61dd95db6", size = 192918, upload-time = "2026-03-06T06:02:18.144Z" }, - { url = "https://files.pythonhosted.org/packages/08/a4/159ff7da662cf7201502ca89980b8f06acf3e887b278956646a8aeb178ab/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:c5af897b45fa606b12464ccbe0014bbf8c09191e0a66aab6aa9d5cf6e77e0c94", size = 204615, upload-time = "2026-03-06T06:02:19.821Z" }, - { url = "https://files.pythonhosted.org/packages/d6/62/0dd6172203cb6b429ffffc9935001fde42e5250d57f07b0c28c6046deb6b/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1088345bcc93c58d8d8f3d783eca4a6e7a7752bbff26c3eee7e73c597c191c2e", size = 197784, upload-time = "2026-03-06T06:02:21.86Z" }, - { url = "https://files.pythonhosted.org/packages/c7/5e/1aab5cb737039b9c59e63627dc8bbc0d02562a14f831cc450e5f91d84ce1/charset_normalizer-3.4.5-cp314-cp314-win32.whl", hash = "sha256:ee57b926940ba00bca7ba7041e665cc956e55ef482f851b9b65acb20d867e7a2", size = 133009, upload-time = "2026-03-06T06:02:23.289Z" }, - { url = "https://files.pythonhosted.org/packages/40/65/e7c6c77d7aaa4c0d7974f2e403e17f0ed2cb0fc135f77d686b916bf1eead/charset_normalizer-3.4.5-cp314-cp314-win_amd64.whl", hash = "sha256:4481e6da1830c8a1cc0b746b47f603b653dadb690bcd851d039ffaefe70533aa", size = 143511, upload-time = "2026-03-06T06:02:26.195Z" }, - { url = "https://files.pythonhosted.org/packages/ba/91/52b0841c71f152f563b8e072896c14e3d83b195c188b338d3cc2e582d1d4/charset_normalizer-3.4.5-cp314-cp314-win_arm64.whl", hash = "sha256:97ab7787092eb9b50fb47fa04f24c75b768a606af1bcba1957f07f128a7219e4", size = 133775, upload-time = "2026-03-06T06:02:27.473Z" }, - { url = "https://files.pythonhosted.org/packages/c5/60/3a621758945513adfd4db86827a5bafcc615f913dbd0b4c2ed64a65731be/charset_normalizer-3.4.5-py3-none-any.whl", hash = "sha256:9db5e3fcdcee89a78c04dffb3fe33c79f77bd741a624946db2591c81b2fc85b0", size = 55455, upload-time = "2026-03-06T06:03:17.827Z" }, +version = "3.4.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/08/0f303cb0b529e456bb116f2d50565a482694fbb94340bf56d44677e7ed03/charset_normalizer-3.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d", size = 315182, upload-time = "2026-04-02T09:25:40.673Z" }, + { url = "https://files.pythonhosted.org/packages/24/47/b192933e94b546f1b1fe4df9cc1f84fcdbf2359f8d1081d46dd029b50207/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8", size = 209329, upload-time = "2026-04-02T09:25:42.354Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b4/01fa81c5ca6141024d89a8fc15968002b71da7f825dd14113207113fabbd/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790", size = 231230, upload-time = "2026-04-02T09:25:44.281Z" }, + { url = "https://files.pythonhosted.org/packages/20/f7/7b991776844dfa058017e600e6e55ff01984a063290ca5622c0b63162f68/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc", size = 225890, upload-time = "2026-04-02T09:25:45.475Z" }, + { url = "https://files.pythonhosted.org/packages/20/e7/bed0024a0f4ab0c8a9c64d4445f39b30c99bd1acd228291959e3de664247/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393", size = 216930, upload-time = "2026-04-02T09:25:46.58Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ab/b18f0ab31cdd7b3ddb8bb76c4a414aeb8160c9810fdf1bc62f269a539d87/charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153", size = 202109, upload-time = "2026-04-02T09:25:48.031Z" }, + { url = "https://files.pythonhosted.org/packages/82/e5/7e9440768a06dfb3075936490cb82dbf0ee20a133bf0dd8551fa096914ec/charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af", size = 214684, upload-time = "2026-04-02T09:25:49.245Z" }, + { url = "https://files.pythonhosted.org/packages/71/94/8c61d8da9f062fdf457c80acfa25060ec22bf1d34bbeaca4350f13bcfd07/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34", size = 212785, upload-time = "2026-04-02T09:25:50.671Z" }, + { url = "https://files.pythonhosted.org/packages/66/cd/6e9889c648e72c0ab2e5967528bb83508f354d706637bc7097190c874e13/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1", size = 203055, upload-time = "2026-04-02T09:25:51.802Z" }, + { url = "https://files.pythonhosted.org/packages/92/2e/7a951d6a08aefb7eb8e1b54cdfb580b1365afdd9dd484dc4bee9e5d8f258/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752", size = 232502, upload-time = "2026-04-02T09:25:53.388Z" }, + { url = "https://files.pythonhosted.org/packages/58/d5/abcf2d83bf8e0a1286df55cd0dc1d49af0da4282aa77e986df343e7de124/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53", size = 214295, upload-time = "2026-04-02T09:25:54.765Z" }, + { url = "https://files.pythonhosted.org/packages/47/3a/7d4cd7ed54be99973a0dc176032cba5cb1f258082c31fa6df35cff46acfc/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616", size = 227145, upload-time = "2026-04-02T09:25:55.904Z" }, + { url = "https://files.pythonhosted.org/packages/1d/98/3a45bf8247889cf28262ebd3d0872edff11565b2a1e3064ccb132db3fbb0/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a", size = 218884, upload-time = "2026-04-02T09:25:57.074Z" }, + { url = "https://files.pythonhosted.org/packages/ad/80/2e8b7f8915ed5c9ef13aa828d82738e33888c485b65ebf744d615040c7ea/charset_normalizer-3.4.7-cp310-cp310-win32.whl", hash = "sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374", size = 148343, upload-time = "2026-04-02T09:25:58.199Z" }, + { url = "https://files.pythonhosted.org/packages/35/1b/3b8c8c77184af465ee9ad88b5aea46ea6b2e1f7b9dc9502891e37af21e30/charset_normalizer-3.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943", size = 159174, upload-time = "2026-04-02T09:25:59.322Z" }, + { url = "https://files.pythonhosted.org/packages/be/c1/feb40dca40dbb21e0a908801782d9288c64fc8d8e562c2098e9994c8c21b/charset_normalizer-3.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008", size = 147805, upload-time = "2026-04-02T09:26:00.756Z" }, + { url = "https://files.pythonhosted.org/packages/c2/d7/b5b7020a0565c2e9fa8c09f4b5fa6232feb326b8c20081ccded47ea368fd/charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7", size = 309705, upload-time = "2026-04-02T09:26:02.191Z" }, + { url = "https://files.pythonhosted.org/packages/5a/53/58c29116c340e5456724ecd2fff4196d236b98f3da97b404bc5e51ac3493/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7", size = 206419, upload-time = "2026-04-02T09:26:03.583Z" }, + { url = "https://files.pythonhosted.org/packages/b2/02/e8146dc6591a37a00e5144c63f29fb7c97a734ea8a111190783c0e60ab63/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e", size = 227901, upload-time = "2026-04-02T09:26:04.738Z" }, + { url = "https://files.pythonhosted.org/packages/fb/73/77486c4cd58f1267bf17db420e930c9afa1b3be3fe8c8b8ebbebc9624359/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c", size = 222742, upload-time = "2026-04-02T09:26:06.36Z" }, + { url = "https://files.pythonhosted.org/packages/a1/fa/f74eb381a7d94ded44739e9d94de18dc5edc9c17fb8c11f0a6890696c0a9/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df", size = 214061, upload-time = "2026-04-02T09:26:08.347Z" }, + { url = "https://files.pythonhosted.org/packages/dc/92/42bd3cefcf7687253fb86694b45f37b733c97f59af3724f356fa92b8c344/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265", size = 199239, upload-time = "2026-04-02T09:26:09.823Z" }, + { url = "https://files.pythonhosted.org/packages/4c/3d/069e7184e2aa3b3cddc700e3dd267413dc259854adc3380421c805c6a17d/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4", size = 210173, upload-time = "2026-04-02T09:26:10.953Z" }, + { url = "https://files.pythonhosted.org/packages/62/51/9d56feb5f2e7074c46f93e0ebdbe61f0848ee246e2f0d89f8e20b89ebb8f/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e", size = 209841, upload-time = "2026-04-02T09:26:12.142Z" }, + { url = "https://files.pythonhosted.org/packages/d2/59/893d8f99cc4c837dda1fe2f1139079703deb9f321aabcb032355de13b6c7/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38", size = 200304, upload-time = "2026-04-02T09:26:13.711Z" }, + { url = "https://files.pythonhosted.org/packages/7d/1d/ee6f3be3464247578d1ed5c46de545ccc3d3ff933695395c402c21fa6b77/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c", size = 229455, upload-time = "2026-04-02T09:26:14.941Z" }, + { url = "https://files.pythonhosted.org/packages/54/bb/8fb0a946296ea96a488928bdce8ef99023998c48e4713af533e9bb98ef07/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b", size = 210036, upload-time = "2026-04-02T09:26:16.478Z" }, + { url = "https://files.pythonhosted.org/packages/9a/bc/015b2387f913749f82afd4fcba07846d05b6d784dd16123cb66860e0237d/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c", size = 224739, upload-time = "2026-04-02T09:26:17.751Z" }, + { url = "https://files.pythonhosted.org/packages/17/ab/63133691f56baae417493cba6b7c641571a2130eb7bceba6773367ab9ec5/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d", size = 216277, upload-time = "2026-04-02T09:26:18.981Z" }, + { url = "https://files.pythonhosted.org/packages/06/6d/3be70e827977f20db77c12a97e6a9f973631a45b8d186c084527e53e77a4/charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad", size = 147819, upload-time = "2026-04-02T09:26:20.295Z" }, + { url = "https://files.pythonhosted.org/packages/20/d9/5f67790f06b735d7c7637171bbfd89882ad67201891b7275e51116ed8207/charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00", size = 159281, upload-time = "2026-04-02T09:26:21.74Z" }, + { url = "https://files.pythonhosted.org/packages/ca/83/6413f36c5a34afead88ce6f66684d943d91f233d76dd083798f9602b75ae/charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1", size = 147843, upload-time = "2026-04-02T09:26:22.901Z" }, + { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" }, + { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" }, + { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" }, + { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" }, + { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" }, + { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" }, + { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" }, + { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" }, + { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" }, + { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" }, + { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" }, + { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" }, + { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" }, + { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" }, + { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" }, + { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" }, + { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" }, + { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" }, + { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" }, + { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" }, + { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" }, + { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" }, + { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, + { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, + { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, + { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, + { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, + { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, + { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, + { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, + { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, + { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, + { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, + { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, + { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, + { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, + { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, + { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, + { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, ] [[package]] name = "click" -version = "8.3.1" +version = "8.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/63/f9e1ea081ce35720d8b92acde70daaedace594dc93b693c869e0d5910718/click-8.3.3.tar.gz", hash = "sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2", size = 328061, upload-time = "2026-04-22T15:11:27.506Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, + { url = "https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl", hash = "sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613", size = 110502, upload-time = "2026-04-22T15:11:25.044Z" }, ] [[package]] @@ -207,115 +284,115 @@ wheels = [ [[package]] name = "coverage" -version = "7.13.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/24/56/95b7e30fa389756cb56630faa728da46a27b8c6eb46f9d557c68fff12b65/coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91", size = 827239, upload-time = "2026-02-09T12:59:03.86Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/44/d4/7827d9ffa34d5d4d752eec907022aa417120936282fc488306f5da08c292/coverage-7.13.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fc31c787a84f8cd6027eba44010517020e0d18487064cd3d8968941856d1415", size = 219152, upload-time = "2026-02-09T12:56:11.974Z" }, - { url = "https://files.pythonhosted.org/packages/35/b0/d69df26607c64043292644dbb9dc54b0856fabaa2cbb1eeee3331cc9e280/coverage-7.13.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a32ebc02a1805adf637fc8dec324b5cdacd2e493515424f70ee33799573d661b", size = 219667, upload-time = "2026-02-09T12:56:13.33Z" }, - { url = "https://files.pythonhosted.org/packages/82/a4/c1523f7c9e47b2271dbf8c2a097e7a1f89ef0d66f5840bb59b7e8814157b/coverage-7.13.4-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e24f9156097ff9dc286f2f913df3a7f63c0e333dcafa3c196f2c18b4175ca09a", size = 246425, upload-time = "2026-02-09T12:56:14.552Z" }, - { url = "https://files.pythonhosted.org/packages/f8/02/aa7ec01d1a5023c4b680ab7257f9bfde9defe8fdddfe40be096ac19e8177/coverage-7.13.4-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8041b6c5bfdc03257666e9881d33b1abc88daccaf73f7b6340fb7946655cd10f", size = 248229, upload-time = "2026-02-09T12:56:16.31Z" }, - { url = "https://files.pythonhosted.org/packages/35/98/85aba0aed5126d896162087ef3f0e789a225697245256fc6181b95f47207/coverage-7.13.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a09cfa6a5862bc2fc6ca7c3def5b2926194a56b8ab78ffcf617d28911123012", size = 250106, upload-time = "2026-02-09T12:56:18.024Z" }, - { url = "https://files.pythonhosted.org/packages/96/72/1db59bd67494bc162e3e4cd5fbc7edba2c7026b22f7c8ef1496d58c2b94c/coverage-7.13.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:296f8b0af861d3970c2a4d8c91d48eb4dd4771bcef9baedec6a9b515d7de3def", size = 252021, upload-time = "2026-02-09T12:56:19.272Z" }, - { url = "https://files.pythonhosted.org/packages/9d/97/72899c59c7066961de6e3daa142d459d47d104956db43e057e034f015c8a/coverage-7.13.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e101609bcbbfb04605ea1027b10dc3735c094d12d40826a60f897b98b1c30256", size = 247114, upload-time = "2026-02-09T12:56:21.051Z" }, - { url = "https://files.pythonhosted.org/packages/39/1f/f1885573b5970235e908da4389176936c8933e86cb316b9620aab1585fa2/coverage-7.13.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aa3feb8db2e87ff5e6d00d7e1480ae241876286691265657b500886c98f38bda", size = 248143, upload-time = "2026-02-09T12:56:22.585Z" }, - { url = "https://files.pythonhosted.org/packages/a8/cf/e80390c5b7480b722fa3e994f8202807799b85bc562aa4f1dde209fbb7be/coverage-7.13.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4fc7fa81bbaf5a02801b65346c8b3e657f1d93763e58c0abdf7c992addd81a92", size = 246152, upload-time = "2026-02-09T12:56:23.748Z" }, - { url = "https://files.pythonhosted.org/packages/44/bf/f89a8350d85572f95412debb0fb9bb4795b1d5b5232bd652923c759e787b/coverage-7.13.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:33901f604424145c6e9c2398684b92e176c0b12df77d52db81c20abd48c3794c", size = 249959, upload-time = "2026-02-09T12:56:25.209Z" }, - { url = "https://files.pythonhosted.org/packages/f7/6e/612a02aece8178c818df273e8d1642190c4875402ca2ba74514394b27aba/coverage-7.13.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:bb28c0f2cf2782508a40cec377935829d5fcc3ad9a3681375af4e84eb34b6b58", size = 246416, upload-time = "2026-02-09T12:56:26.475Z" }, - { url = "https://files.pythonhosted.org/packages/cb/98/b5afc39af67c2fa6786b03c3a7091fc300947387ce8914b096db8a73d67a/coverage-7.13.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9d107aff57a83222ddbd8d9ee705ede2af2cc926608b57abed8ef96b50b7e8f9", size = 247025, upload-time = "2026-02-09T12:56:27.727Z" }, - { url = "https://files.pythonhosted.org/packages/51/30/2bba8ef0682d5bd210c38fe497e12a06c9f8d663f7025e9f5c2c31ce847d/coverage-7.13.4-cp310-cp310-win32.whl", hash = "sha256:a6f94a7d00eb18f1b6d403c91a88fd58cfc92d4b16080dfdb774afc8294469bf", size = 221758, upload-time = "2026-02-09T12:56:29.051Z" }, - { url = "https://files.pythonhosted.org/packages/78/13/331f94934cf6c092b8ea59ff868eb587bc8fe0893f02c55bc6c0183a192e/coverage-7.13.4-cp310-cp310-win_amd64.whl", hash = "sha256:2cb0f1e000ebc419632bbe04366a8990b6e32c4e0b51543a6484ffe15eaeda95", size = 222693, upload-time = "2026-02-09T12:56:30.366Z" }, - { url = "https://files.pythonhosted.org/packages/b4/ad/b59e5b451cf7172b8d1043dc0fa718f23aab379bc1521ee13d4bd9bfa960/coverage-7.13.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d490ba50c3f35dd7c17953c68f3270e7ccd1c6642e2d2afe2d8e720b98f5a053", size = 219278, upload-time = "2026-02-09T12:56:31.673Z" }, - { url = "https://files.pythonhosted.org/packages/f1/17/0cb7ca3de72e5f4ef2ec2fa0089beafbcaaaead1844e8b8a63d35173d77d/coverage-7.13.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:19bc3c88078789f8ef36acb014d7241961dbf883fd2533d18cb1e7a5b4e28b11", size = 219783, upload-time = "2026-02-09T12:56:33.104Z" }, - { url = "https://files.pythonhosted.org/packages/ab/63/325d8e5b11e0eaf6d0f6a44fad444ae58820929a9b0de943fa377fe73e85/coverage-7.13.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3998e5a32e62fdf410c0dbd3115df86297995d6e3429af80b8798aad894ca7aa", size = 250200, upload-time = "2026-02-09T12:56:34.474Z" }, - { url = "https://files.pythonhosted.org/packages/76/53/c16972708cbb79f2942922571a687c52bd109a7bd51175aeb7558dff2236/coverage-7.13.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e264226ec98e01a8e1054314af91ee6cde0eacac4f465cc93b03dbe0bce2fd7", size = 252114, upload-time = "2026-02-09T12:56:35.749Z" }, - { url = "https://files.pythonhosted.org/packages/eb/c2/7ab36d8b8cc412bec9ea2d07c83c48930eb4ba649634ba00cb7e4e0f9017/coverage-7.13.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3aa4e7b9e416774b21797365b358a6e827ffadaaca81b69ee02946852449f00", size = 254220, upload-time = "2026-02-09T12:56:37.796Z" }, - { url = "https://files.pythonhosted.org/packages/d6/4d/cf52c9a3322c89a0e6febdfbc83bb45c0ed3c64ad14081b9503adee702e7/coverage-7.13.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:71ca20079dd8f27fcf808817e281e90220475cd75115162218d0e27549f95fef", size = 256164, upload-time = "2026-02-09T12:56:39.016Z" }, - { url = "https://files.pythonhosted.org/packages/78/e9/eb1dd17bd6de8289df3580e967e78294f352a5df8a57ff4671ee5fc3dcd0/coverage-7.13.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e2f25215f1a359ab17320b47bcdaca3e6e6356652e8256f2441e4ef972052903", size = 250325, upload-time = "2026-02-09T12:56:40.668Z" }, - { url = "https://files.pythonhosted.org/packages/71/07/8c1542aa873728f72267c07278c5cc0ec91356daf974df21335ccdb46368/coverage-7.13.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d65b2d373032411e86960604dc4edac91fdfb5dca539461cf2cbe78327d1e64f", size = 251913, upload-time = "2026-02-09T12:56:41.97Z" }, - { url = "https://files.pythonhosted.org/packages/74/d7/c62e2c5e4483a748e27868e4c32ad3daa9bdddbba58e1bc7a15e252baa74/coverage-7.13.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94eb63f9b363180aff17de3e7c8760c3ba94664ea2695c52f10111244d16a299", size = 249974, upload-time = "2026-02-09T12:56:43.323Z" }, - { url = "https://files.pythonhosted.org/packages/98/9f/4c5c015a6e98ced54efd0f5cf8d31b88e5504ecb6857585fc0161bb1e600/coverage-7.13.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e856bf6616714c3a9fbc270ab54103f4e685ba236fa98c054e8f87f266c93505", size = 253741, upload-time = "2026-02-09T12:56:45.155Z" }, - { url = "https://files.pythonhosted.org/packages/bd/59/0f4eef89b9f0fcd9633b5d350016f54126ab49426a70ff4c4e87446cabdc/coverage-7.13.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:65dfcbe305c3dfe658492df2d85259e0d79ead4177f9ae724b6fb245198f55d6", size = 249695, upload-time = "2026-02-09T12:56:46.636Z" }, - { url = "https://files.pythonhosted.org/packages/b5/2c/b7476f938deb07166f3eb281a385c262675d688ff4659ad56c6c6b8e2e70/coverage-7.13.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b507778ae8a4c915436ed5c2e05b4a6cecfa70f734e19c22a005152a11c7b6a9", size = 250599, upload-time = "2026-02-09T12:56:48.13Z" }, - { url = "https://files.pythonhosted.org/packages/b8/34/c3420709d9846ee3785b9f2831b4d94f276f38884032dca1457fa83f7476/coverage-7.13.4-cp311-cp311-win32.whl", hash = "sha256:784fc3cf8be001197b652d51d3fd259b1e2262888693a4636e18879f613a62a9", size = 221780, upload-time = "2026-02-09T12:56:50.479Z" }, - { url = "https://files.pythonhosted.org/packages/61/08/3d9c8613079d2b11c185b865de9a4c1a68850cfda2b357fae365cf609f29/coverage-7.13.4-cp311-cp311-win_amd64.whl", hash = "sha256:2421d591f8ca05b308cf0092807308b2facbefe54af7c02ac22548b88b95c98f", size = 222715, upload-time = "2026-02-09T12:56:51.815Z" }, - { url = "https://files.pythonhosted.org/packages/18/1a/54c3c80b2f056164cc0a6cdcb040733760c7c4be9d780fe655f356f433e4/coverage-7.13.4-cp311-cp311-win_arm64.whl", hash = "sha256:79e73a76b854d9c6088fe5d8b2ebe745f8681c55f7397c3c0a016192d681045f", size = 221385, upload-time = "2026-02-09T12:56:53.194Z" }, - { url = "https://files.pythonhosted.org/packages/d1/81/4ce2fdd909c5a0ed1f6dedb88aa57ab79b6d1fbd9b588c1ac7ef45659566/coverage-7.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459", size = 219449, upload-time = "2026-02-09T12:56:54.889Z" }, - { url = "https://files.pythonhosted.org/packages/5d/96/5238b1efc5922ddbdc9b0db9243152c09777804fb7c02ad1741eb18a11c0/coverage-7.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3", size = 219810, upload-time = "2026-02-09T12:56:56.33Z" }, - { url = "https://files.pythonhosted.org/packages/78/72/2f372b726d433c9c35e56377cf1d513b4c16fe51841060d826b95caacec1/coverage-7.13.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5b856a8ccf749480024ff3bd7310adaef57bf31fd17e1bfc404b7940b6986634", size = 251308, upload-time = "2026-02-09T12:56:57.858Z" }, - { url = "https://files.pythonhosted.org/packages/5d/a0/2ea570925524ef4e00bb6c82649f5682a77fac5ab910a65c9284de422600/coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3", size = 254052, upload-time = "2026-02-09T12:56:59.754Z" }, - { url = "https://files.pythonhosted.org/packages/e8/ac/45dc2e19a1939098d783c846e130b8f862fbb50d09e0af663988f2f21973/coverage-7.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7b38448866e83176e28086674fe7368ab8590e4610fb662b44e345b86d63ffa", size = 255165, upload-time = "2026-02-09T12:57:01.287Z" }, - { url = "https://files.pythonhosted.org/packages/2d/4d/26d236ff35abc3b5e63540d3386e4c3b192168c1d96da5cb2f43c640970f/coverage-7.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de6defc1c9badbf8b9e67ae90fd00519186d6ab64e5cc5f3d21359c2a9b2c1d3", size = 257432, upload-time = "2026-02-09T12:57:02.637Z" }, - { url = "https://files.pythonhosted.org/packages/ec/55/14a966c757d1348b2e19caf699415a2a4c4f7feaa4bbc6326a51f5c7dd1b/coverage-7.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7eda778067ad7ffccd23ecffce537dface96212576a07924cbf0d8799d2ded5a", size = 251716, upload-time = "2026-02-09T12:57:04.056Z" }, - { url = "https://files.pythonhosted.org/packages/77/33/50116647905837c66d28b2af1321b845d5f5d19be9655cb84d4a0ea806b4/coverage-7.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e87f6c587c3f34356c3759f0420693e35e7eb0e2e41e4c011cb6ec6ecbbf1db7", size = 253089, upload-time = "2026-02-09T12:57:05.503Z" }, - { url = "https://files.pythonhosted.org/packages/c2/b4/8efb11a46e3665d92635a56e4f2d4529de6d33f2cb38afd47d779d15fc99/coverage-7.13.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8248977c2e33aecb2ced42fef99f2d319e9904a36e55a8a68b69207fb7e43edc", size = 251232, upload-time = "2026-02-09T12:57:06.879Z" }, - { url = "https://files.pythonhosted.org/packages/51/24/8cd73dd399b812cc76bb0ac260e671c4163093441847ffe058ac9fda1e32/coverage-7.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:25381386e80ae727608e662474db537d4df1ecd42379b5ba33c84633a2b36d47", size = 255299, upload-time = "2026-02-09T12:57:08.245Z" }, - { url = "https://files.pythonhosted.org/packages/03/94/0a4b12f1d0e029ce1ccc1c800944a9984cbe7d678e470bb6d3c6bc38a0da/coverage-7.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ee756f00726693e5ba94d6df2bdfd64d4852d23b09bb0bc700e3b30e6f333985", size = 250796, upload-time = "2026-02-09T12:57:10.142Z" }, - { url = "https://files.pythonhosted.org/packages/73/44/6002fbf88f6698ca034360ce474c406be6d5a985b3fdb3401128031eef6b/coverage-7.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdfc1e28e7c7cdce44985b3043bc13bbd9c747520f94a4d7164af8260b3d91f0", size = 252673, upload-time = "2026-02-09T12:57:12.197Z" }, - { url = "https://files.pythonhosted.org/packages/de/c6/a0279f7c00e786be75a749a5674e6fa267bcbd8209cd10c9a450c655dfa7/coverage-7.13.4-cp312-cp312-win32.whl", hash = "sha256:01d4cbc3c283a17fc1e42d614a119f7f438eabb593391283adca8dc86eff1246", size = 221990, upload-time = "2026-02-09T12:57:14.085Z" }, - { url = "https://files.pythonhosted.org/packages/77/4e/c0a25a425fcf5557d9abd18419c95b63922e897bc86c1f327f155ef234a9/coverage-7.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126", size = 222800, upload-time = "2026-02-09T12:57:15.944Z" }, - { url = "https://files.pythonhosted.org/packages/47/ac/92da44ad9a6f4e3a7debd178949d6f3769bedca33830ce9b1dcdab589a37/coverage-7.13.4-cp312-cp312-win_arm64.whl", hash = "sha256:b1ec7b6b6e93255f952e27ab58fbc68dcc468844b16ecbee881aeb29b6ab4d8d", size = 221415, upload-time = "2026-02-09T12:57:17.497Z" }, - { url = "https://files.pythonhosted.org/packages/db/23/aad45061a31677d68e47499197a131eea55da4875d16c1f42021ab963503/coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b66a2da594b6068b48b2692f043f35d4d3693fb639d5ea8b39533c2ad9ac3ab9", size = 219474, upload-time = "2026-02-09T12:57:19.332Z" }, - { url = "https://files.pythonhosted.org/packages/a5/70/9b8b67a0945f3dfec1fd896c5cefb7c19d5a3a6d74630b99a895170999ae/coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3599eb3992d814d23b35c536c28df1a882caa950f8f507cef23d1cbf334995ac", size = 219844, upload-time = "2026-02-09T12:57:20.66Z" }, - { url = "https://files.pythonhosted.org/packages/97/fd/7e859f8fab324cef6c4ad7cff156ca7c489fef9179d5749b0c8d321281c2/coverage-7.13.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93550784d9281e374fb5a12bf1324cc8a963fd63b2d2f223503ef0fd4aa339ea", size = 250832, upload-time = "2026-02-09T12:57:22.007Z" }, - { url = "https://files.pythonhosted.org/packages/e4/dc/b2442d10020c2f52617828862d8b6ee337859cd8f3a1f13d607dddda9cf7/coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b720ce6a88a2755f7c697c23268ddc47a571b88052e6b155224347389fdf6a3b", size = 253434, upload-time = "2026-02-09T12:57:23.339Z" }, - { url = "https://files.pythonhosted.org/packages/5a/88/6728a7ad17428b18d836540630487231f5470fb82454871149502f5e5aa2/coverage-7.13.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b322db1284a2ed3aa28ffd8ebe3db91c929b7a333c0820abec3d838ef5b3525", size = 254676, upload-time = "2026-02-09T12:57:24.774Z" }, - { url = "https://files.pythonhosted.org/packages/7c/bc/21244b1b8cedf0dff0a2b53b208015fe798d5f2a8d5348dbfece04224fff/coverage-7.13.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4594c67d8a7c89cf922d9df0438c7c7bb022ad506eddb0fdb2863359ff78242", size = 256807, upload-time = "2026-02-09T12:57:26.125Z" }, - { url = "https://files.pythonhosted.org/packages/97/a0/ddba7ed3251cff51006737a727d84e05b61517d1784a9988a846ba508877/coverage-7.13.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:53d133df809c743eb8bce33b24bcababb371f4441340578cd406e084d94a6148", size = 251058, upload-time = "2026-02-09T12:57:27.614Z" }, - { url = "https://files.pythonhosted.org/packages/9b/55/e289addf7ff54d3a540526f33751951bf0878f3809b47f6dfb3def69c6f7/coverage-7.13.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76451d1978b95ba6507a039090ba076105c87cc76fc3efd5d35d72093964d49a", size = 252805, upload-time = "2026-02-09T12:57:29.066Z" }, - { url = "https://files.pythonhosted.org/packages/13/4e/cc276b1fa4a59be56d96f1dabddbdc30f4ba22e3b1cd42504c37b3313255/coverage-7.13.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7f57b33491e281e962021de110b451ab8a24182589be17e12a22c79047935e23", size = 250766, upload-time = "2026-02-09T12:57:30.522Z" }, - { url = "https://files.pythonhosted.org/packages/94/44/1093b8f93018f8b41a8cf29636c9292502f05e4a113d4d107d14a3acd044/coverage-7.13.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1731dc33dc276dafc410a885cbf5992f1ff171393e48a21453b78727d090de80", size = 254923, upload-time = "2026-02-09T12:57:31.946Z" }, - { url = "https://files.pythonhosted.org/packages/8b/55/ea2796da2d42257f37dbea1aab239ba9263b31bd91d5527cdd6db5efe174/coverage-7.13.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:bd60d4fe2f6fa7dff9223ca1bbc9f05d2b6697bc5961072e5d3b952d46e1b1ea", size = 250591, upload-time = "2026-02-09T12:57:33.842Z" }, - { url = "https://files.pythonhosted.org/packages/d4/fa/7c4bb72aacf8af5020675aa633e59c1fbe296d22aed191b6a5b711eb2bc7/coverage-7.13.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9181a3ccead280b828fae232df12b16652702b49d41e99d657f46cc7b1f6ec7a", size = 252364, upload-time = "2026-02-09T12:57:35.743Z" }, - { url = "https://files.pythonhosted.org/packages/5c/38/a8d2ec0146479c20bbaa7181b5b455a0c41101eed57f10dd19a78ab44c80/coverage-7.13.4-cp313-cp313-win32.whl", hash = "sha256:f53d492307962561ac7de4cd1de3e363589b000ab69617c6156a16ba7237998d", size = 222010, upload-time = "2026-02-09T12:57:37.25Z" }, - { url = "https://files.pythonhosted.org/packages/e2/0c/dbfafbe90a185943dcfbc766fe0e1909f658811492d79b741523a414a6cc/coverage-7.13.4-cp313-cp313-win_amd64.whl", hash = "sha256:e6f70dec1cc557e52df5306d051ef56003f74d56e9c4dd7ddb07e07ef32a84dd", size = 222818, upload-time = "2026-02-09T12:57:38.734Z" }, - { url = "https://files.pythonhosted.org/packages/04/d1/934918a138c932c90d78301f45f677fb05c39a3112b96fd2c8e60503cdc7/coverage-7.13.4-cp313-cp313-win_arm64.whl", hash = "sha256:fb07dc5da7e849e2ad31a5d74e9bece81f30ecf5a42909d0a695f8bd1874d6af", size = 221438, upload-time = "2026-02-09T12:57:40.223Z" }, - { url = "https://files.pythonhosted.org/packages/52/57/ee93ced533bcb3e6df961c0c6e42da2fc6addae53fb95b94a89b1e33ebd7/coverage-7.13.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40d74da8e6c4b9ac18b15331c4b5ebc35a17069410cad462ad4f40dcd2d50c0d", size = 220165, upload-time = "2026-02-09T12:57:41.639Z" }, - { url = "https://files.pythonhosted.org/packages/c5/e0/969fc285a6fbdda49d91af278488d904dcd7651b2693872f0ff94e40e84a/coverage-7.13.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4223b4230a376138939a9173f1bdd6521994f2aff8047fae100d6d94d50c5a12", size = 220516, upload-time = "2026-02-09T12:57:44.215Z" }, - { url = "https://files.pythonhosted.org/packages/b1/b8/9531944e16267e2735a30a9641ff49671f07e8138ecf1ca13db9fd2560c7/coverage-7.13.4-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1d4be36a5114c499f9f1f9195e95ebf979460dbe2d88e6816ea202010ba1c34b", size = 261804, upload-time = "2026-02-09T12:57:45.989Z" }, - { url = "https://files.pythonhosted.org/packages/8a/f3/e63df6d500314a2a60390d1989240d5f27318a7a68fa30ad3806e2a9323e/coverage-7.13.4-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:200dea7d1e8095cc6e98cdabe3fd1d21ab17d3cee6dab00cadbb2fe35d9c15b9", size = 263885, upload-time = "2026-02-09T12:57:47.42Z" }, - { url = "https://files.pythonhosted.org/packages/f3/67/7654810de580e14b37670b60a09c599fa348e48312db5b216d730857ffe6/coverage-7.13.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8eb931ee8e6d8243e253e5ed7336deea6904369d2fd8ae6e43f68abbf167092", size = 266308, upload-time = "2026-02-09T12:57:49.345Z" }, - { url = "https://files.pythonhosted.org/packages/37/6f/39d41eca0eab3cc82115953ad41c4e77935286c930e8fad15eaed1389d83/coverage-7.13.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:75eab1ebe4f2f64d9509b984f9314d4aa788540368218b858dad56dc8f3e5eb9", size = 267452, upload-time = "2026-02-09T12:57:50.811Z" }, - { url = "https://files.pythonhosted.org/packages/50/6d/39c0fbb8fc5cd4d2090811e553c2108cf5112e882f82505ee7495349a6bf/coverage-7.13.4-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c35eb28c1d085eb7d8c9b3296567a1bebe03ce72962e932431b9a61f28facf26", size = 261057, upload-time = "2026-02-09T12:57:52.447Z" }, - { url = "https://files.pythonhosted.org/packages/a4/a2/60010c669df5fa603bb5a97fb75407e191a846510da70ac657eb696b7fce/coverage-7.13.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb88b316ec33760714a4720feb2816a3a59180fd58c1985012054fa7aebee4c2", size = 263875, upload-time = "2026-02-09T12:57:53.938Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d9/63b22a6bdbd17f1f96e9ed58604c2a6b0e72a9133e37d663bef185877cf6/coverage-7.13.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7d41eead3cc673cbd38a4417deb7fd0b4ca26954ff7dc6078e33f6ff97bed940", size = 261500, upload-time = "2026-02-09T12:57:56.012Z" }, - { url = "https://files.pythonhosted.org/packages/70/bf/69f86ba1ad85bc3ad240e4c0e57a2e620fbc0e1645a47b5c62f0e941ad7f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:fb26a934946a6afe0e326aebe0730cdff393a8bc0bbb65a2f41e30feddca399c", size = 265212, upload-time = "2026-02-09T12:57:57.5Z" }, - { url = "https://files.pythonhosted.org/packages/ae/f2/5f65a278a8c2148731831574c73e42f57204243d33bedaaf18fa79c5958f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:dae88bc0fc77edaa65c14be099bd57ee140cf507e6bfdeea7938457ab387efb0", size = 260398, upload-time = "2026-02-09T12:57:59.027Z" }, - { url = "https://files.pythonhosted.org/packages/ef/80/6e8280a350ee9fea92f14b8357448a242dcaa243cb2c72ab0ca591f66c8c/coverage-7.13.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:845f352911777a8e722bfce168958214951e07e47e5d5d9744109fa5fe77f79b", size = 262584, upload-time = "2026-02-09T12:58:01.129Z" }, - { url = "https://files.pythonhosted.org/packages/22/63/01ff182fc95f260b539590fb12c11ad3e21332c15f9799cb5e2386f71d9f/coverage-7.13.4-cp313-cp313t-win32.whl", hash = "sha256:2fa8d5f8de70688a28240de9e139fa16b153cc3cbb01c5f16d88d6505ebdadf9", size = 222688, upload-time = "2026-02-09T12:58:02.736Z" }, - { url = "https://files.pythonhosted.org/packages/a9/43/89de4ef5d3cd53b886afa114065f7e9d3707bdb3e5efae13535b46ae483d/coverage-7.13.4-cp313-cp313t-win_amd64.whl", hash = "sha256:9351229c8c8407645840edcc277f4a2d44814d1bc34a2128c11c2a031d45a5dd", size = 223746, upload-time = "2026-02-09T12:58:05.362Z" }, - { url = "https://files.pythonhosted.org/packages/35/39/7cf0aa9a10d470a5309b38b289b9bb07ddeac5d61af9b664fe9775a4cb3e/coverage-7.13.4-cp313-cp313t-win_arm64.whl", hash = "sha256:30b8d0512f2dc8c8747557e8fb459d6176a2c9e5731e2b74d311c03b78451997", size = 222003, upload-time = "2026-02-09T12:58:06.952Z" }, - { url = "https://files.pythonhosted.org/packages/92/11/a9cf762bb83386467737d32187756a42094927150c3e107df4cb078e8590/coverage-7.13.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:300deaee342f90696ed186e3a00c71b5b3d27bffe9e827677954f4ee56969601", size = 219522, upload-time = "2026-02-09T12:58:08.623Z" }, - { url = "https://files.pythonhosted.org/packages/d3/28/56e6d892b7b052236d67c95f1936b6a7cf7c3e2634bf27610b8cbd7f9c60/coverage-7.13.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29e3220258d682b6226a9b0925bc563ed9a1ebcff3cad30f043eceea7eaf2689", size = 219855, upload-time = "2026-02-09T12:58:10.176Z" }, - { url = "https://files.pythonhosted.org/packages/e5/69/233459ee9eb0c0d10fcc2fe425a029b3fa5ce0f040c966ebce851d030c70/coverage-7.13.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:391ee8f19bef69210978363ca930f7328081c6a0152f1166c91f0b5fdd2a773c", size = 250887, upload-time = "2026-02-09T12:58:12.503Z" }, - { url = "https://files.pythonhosted.org/packages/06/90/2cdab0974b9b5bbc1623f7876b73603aecac11b8d95b85b5b86b32de5eab/coverage-7.13.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0dd7ab8278f0d58a0128ba2fca25824321f05d059c1441800e934ff2efa52129", size = 253396, upload-time = "2026-02-09T12:58:14.615Z" }, - { url = "https://files.pythonhosted.org/packages/ac/15/ea4da0f85bf7d7b27635039e649e99deb8173fe551096ea15017f7053537/coverage-7.13.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78cdf0d578b15148b009ccf18c686aa4f719d887e76e6b40c38ffb61d264a552", size = 254745, upload-time = "2026-02-09T12:58:16.162Z" }, - { url = "https://files.pythonhosted.org/packages/99/11/bb356e86920c655ca4d61daee4e2bbc7258f0a37de0be32d233b561134ff/coverage-7.13.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:48685fee12c2eb3b27c62f2658e7ea21e9c3239cba5a8a242801a0a3f6a8c62a", size = 257055, upload-time = "2026-02-09T12:58:17.892Z" }, - { url = "https://files.pythonhosted.org/packages/c9/0f/9ae1f8cb17029e09da06ca4e28c9e1d5c1c0a511c7074592e37e0836c915/coverage-7.13.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4e83efc079eb39480e6346a15a1bcb3e9b04759c5202d157e1dd4303cd619356", size = 250911, upload-time = "2026-02-09T12:58:19.495Z" }, - { url = "https://files.pythonhosted.org/packages/89/3a/adfb68558fa815cbc29747b553bc833d2150228f251b127f1ce97e48547c/coverage-7.13.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecae9737b72408d6a950f7e525f30aca12d4bd8dd95e37342e5beb3a2a8c4f71", size = 252754, upload-time = "2026-02-09T12:58:21.064Z" }, - { url = "https://files.pythonhosted.org/packages/32/b1/540d0c27c4e748bd3cd0bd001076ee416eda993c2bae47a73b7cc9357931/coverage-7.13.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ae4578f8528569d3cf303fef2ea569c7f4c4059a38c8667ccef15c6e1f118aa5", size = 250720, upload-time = "2026-02-09T12:58:22.622Z" }, - { url = "https://files.pythonhosted.org/packages/c7/95/383609462b3ffb1fe133014a7c84fc0dd01ed55ac6140fa1093b5af7ebb1/coverage-7.13.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6fdef321fdfbb30a197efa02d48fcd9981f0d8ad2ae8903ac318adc653f5df98", size = 254994, upload-time = "2026-02-09T12:58:24.548Z" }, - { url = "https://files.pythonhosted.org/packages/f7/ba/1761138e86c81680bfc3c49579d66312865457f9fe405b033184e5793cb3/coverage-7.13.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b0f6ccf3dbe577170bebfce1318707d0e8c3650003cb4b3a9dd744575daa8b5", size = 250531, upload-time = "2026-02-09T12:58:26.271Z" }, - { url = "https://files.pythonhosted.org/packages/f8/8e/05900df797a9c11837ab59c4d6fe94094e029582aab75c3309a93e6fb4e3/coverage-7.13.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75fcd519f2a5765db3f0e391eb3b7d150cce1a771bf4c9f861aeab86c767a3c0", size = 252189, upload-time = "2026-02-09T12:58:27.807Z" }, - { url = "https://files.pythonhosted.org/packages/00/bd/29c9f2db9ea4ed2738b8a9508c35626eb205d51af4ab7bf56a21a2e49926/coverage-7.13.4-cp314-cp314-win32.whl", hash = "sha256:8e798c266c378da2bd819b0677df41ab46d78065fb2a399558f3f6cae78b2fbb", size = 222258, upload-time = "2026-02-09T12:58:29.441Z" }, - { url = "https://files.pythonhosted.org/packages/a7/4d/1f8e723f6829977410efeb88f73673d794075091c8c7c18848d273dc9d73/coverage-7.13.4-cp314-cp314-win_amd64.whl", hash = "sha256:245e37f664d89861cf2329c9afa2c1fe9e6d4e1a09d872c947e70718aeeac505", size = 223073, upload-time = "2026-02-09T12:58:31.026Z" }, - { url = "https://files.pythonhosted.org/packages/51/5b/84100025be913b44e082ea32abcf1afbf4e872f5120b7a1cab1d331b1e13/coverage-7.13.4-cp314-cp314-win_arm64.whl", hash = "sha256:ad27098a189e5838900ce4c2a99f2fe42a0bf0c2093c17c69b45a71579e8d4a2", size = 221638, upload-time = "2026-02-09T12:58:32.599Z" }, - { url = "https://files.pythonhosted.org/packages/a7/e4/c884a405d6ead1370433dad1e3720216b4f9fd8ef5b64bfd984a2a60a11a/coverage-7.13.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:85480adfb35ffc32d40918aad81b89c69c9cc5661a9b8a81476d3e645321a056", size = 220246, upload-time = "2026-02-09T12:58:34.181Z" }, - { url = "https://files.pythonhosted.org/packages/81/5c/4d7ed8b23b233b0fffbc9dfec53c232be2e695468523242ea9fd30f97ad2/coverage-7.13.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:79be69cf7f3bf9b0deeeb062eab7ac7f36cd4cc4c4dd694bd28921ba4d8596cc", size = 220514, upload-time = "2026-02-09T12:58:35.704Z" }, - { url = "https://files.pythonhosted.org/packages/2f/6f/3284d4203fd2f28edd73034968398cd2d4cb04ab192abc8cff007ea35679/coverage-7.13.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:caa421e2684e382c5d8973ac55e4f36bed6821a9bad5c953494de960c74595c9", size = 261877, upload-time = "2026-02-09T12:58:37.864Z" }, - { url = "https://files.pythonhosted.org/packages/09/aa/b672a647bbe1556a85337dc95bfd40d146e9965ead9cc2fe81bde1e5cbce/coverage-7.13.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14375934243ee05f56c45393fe2ce81fe5cc503c07cee2bdf1725fb8bef3ffaf", size = 264004, upload-time = "2026-02-09T12:58:39.492Z" }, - { url = "https://files.pythonhosted.org/packages/79/a1/aa384dbe9181f98bba87dd23dda436f0c6cf2e148aecbb4e50fc51c1a656/coverage-7.13.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25a41c3104d08edb094d9db0d905ca54d0cd41c928bb6be3c4c799a54753af55", size = 266408, upload-time = "2026-02-09T12:58:41.852Z" }, - { url = "https://files.pythonhosted.org/packages/53/5e/5150bf17b4019bc600799f376bb9606941e55bd5a775dc1e096b6ffea952/coverage-7.13.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f01afcff62bf9a08fb32b2c1d6e924236c0383c02c790732b6537269e466a72", size = 267544, upload-time = "2026-02-09T12:58:44.093Z" }, - { url = "https://files.pythonhosted.org/packages/e0/ed/f1de5c675987a4a7a672250d2c5c9d73d289dbf13410f00ed7181d8017dd/coverage-7.13.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eb9078108fbf0bcdde37c3f4779303673c2fa1fe8f7956e68d447d0dd426d38a", size = 260980, upload-time = "2026-02-09T12:58:45.721Z" }, - { url = "https://files.pythonhosted.org/packages/b3/e3/fe758d01850aa172419a6743fe76ba8b92c29d181d4f676ffe2dae2ba631/coverage-7.13.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e086334e8537ddd17e5f16a344777c1ab8194986ec533711cbe6c41cde841b6", size = 263871, upload-time = "2026-02-09T12:58:47.334Z" }, - { url = "https://files.pythonhosted.org/packages/b6/76/b829869d464115e22499541def9796b25312b8cf235d3bb00b39f1675395/coverage-7.13.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:725d985c5ab621268b2edb8e50dfe57633dc69bda071abc470fed55a14935fd3", size = 261472, upload-time = "2026-02-09T12:58:48.995Z" }, - { url = "https://files.pythonhosted.org/packages/14/9e/caedb1679e73e2f6ad240173f55218488bfe043e38da577c4ec977489915/coverage-7.13.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:3c06f0f1337c667b971ca2f975523347e63ec5e500b9aa5882d91931cd3ef750", size = 265210, upload-time = "2026-02-09T12:58:51.178Z" }, - { url = "https://files.pythonhosted.org/packages/3a/10/0dd02cb009b16ede425b49ec344aba13a6ae1dc39600840ea6abcb085ac4/coverage-7.13.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:590c0ed4bf8e85f745e6b805b2e1c457b2e33d5255dd9729743165253bc9ad39", size = 260319, upload-time = "2026-02-09T12:58:53.081Z" }, - { url = "https://files.pythonhosted.org/packages/92/8e/234d2c927af27c6d7a5ffad5bd2cf31634c46a477b4c7adfbfa66baf7ebb/coverage-7.13.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eb30bf180de3f632cd043322dad5751390e5385108b2807368997d1a92a509d0", size = 262638, upload-time = "2026-02-09T12:58:55.258Z" }, - { url = "https://files.pythonhosted.org/packages/2f/64/e5547c8ff6964e5965c35a480855911b61509cce544f4d442caa759a0702/coverage-7.13.4-cp314-cp314t-win32.whl", hash = "sha256:c4240e7eded42d131a2d2c4dec70374b781b043ddc79a9de4d55ca71f8e98aea", size = 223040, upload-time = "2026-02-09T12:58:56.936Z" }, - { url = "https://files.pythonhosted.org/packages/c7/96/38086d58a181aac86d503dfa9c47eb20715a79c3e3acbdf786e92e5c09a8/coverage-7.13.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4c7d3cc01e7350f2f0f6f7036caaf5673fb56b6998889ccfe9e1c1fe75a9c932", size = 224148, upload-time = "2026-02-09T12:58:58.645Z" }, - { url = "https://files.pythonhosted.org/packages/ce/72/8d10abd3740a0beb98c305e0c3faf454366221c0f37a8bcf8f60020bb65a/coverage-7.13.4-cp314-cp314t-win_arm64.whl", hash = "sha256:23e3f687cf945070d1c90f85db66d11e3025665d8dafa831301a0e0038f3db9b", size = 222172, upload-time = "2026-02-09T12:59:00.396Z" }, - { url = "https://files.pythonhosted.org/packages/0d/4a/331fe2caf6799d591109bb9c08083080f6de90a823695d412a935622abb2/coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0", size = 211242, upload-time = "2026-02-09T12:59:02.032Z" }, +version = "7.13.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/e0/70553e3000e345daff267cec284ce4cbf3fc141b6da229ac52775b5428f1/coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179", size = 915967, upload-time = "2026-03-17T10:33:18.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/33/e8c48488c29a73fd089f9d71f9653c1be7478f2ad6b5bc870db11a55d23d/coverage-7.13.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0723d2c96324561b9aa76fb982406e11d93cdb388a7a7da2b16e04719cf7ca5", size = 219255, upload-time = "2026-03-17T10:29:51.081Z" }, + { url = "https://files.pythonhosted.org/packages/da/bd/b0ebe9f677d7f4b74a3e115eec7ddd4bcf892074963a00d91e8b164a6386/coverage-7.13.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52f444e86475992506b32d4e5ca55c24fc88d73bcbda0e9745095b28ef4dc0cf", size = 219772, upload-time = "2026-03-17T10:29:52.867Z" }, + { url = "https://files.pythonhosted.org/packages/48/cc/5cb9502f4e01972f54eedd48218bb203fe81e294be606a2bc93970208013/coverage-7.13.5-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:704de6328e3d612a8f6c07000a878ff38181ec3263d5a11da1db294fa6a9bdf8", size = 246532, upload-time = "2026-03-17T10:29:54.688Z" }, + { url = "https://files.pythonhosted.org/packages/7d/d8/3217636d86c7e7b12e126e4f30ef1581047da73140614523af7495ed5f2d/coverage-7.13.5-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a1a6d79a14e1ec1832cabc833898636ad5f3754a678ef8bb4908515208bf84f4", size = 248333, upload-time = "2026-03-17T10:29:56.221Z" }, + { url = "https://files.pythonhosted.org/packages/2b/30/2002ac6729ba2d4357438e2ed3c447ad8562866c8c63fc16f6dfc33afe56/coverage-7.13.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79060214983769c7ba3f0cee10b54c97609dca4d478fa1aa32b914480fd5738d", size = 250211, upload-time = "2026-03-17T10:29:57.938Z" }, + { url = "https://files.pythonhosted.org/packages/6c/85/552496626d6b9359eb0e2f86f920037c9cbfba09b24d914c6e1528155f7d/coverage-7.13.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:356e76b46783a98c2a2fe81ec79df4883a1e62895ea952968fb253c114e7f930", size = 252125, upload-time = "2026-03-17T10:29:59.388Z" }, + { url = "https://files.pythonhosted.org/packages/44/21/40256eabdcbccdb6acf6b381b3016a154399a75fe39d406f790ae84d1f3c/coverage-7.13.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0cef0cdec915d11254a7f549c1170afecce708d30610c6abdded1f74e581666d", size = 247219, upload-time = "2026-03-17T10:30:01.199Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e8/96e2a6c3f21a0ea77d7830b254a1542d0328acc8d7bdf6a284ba7e529f77/coverage-7.13.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dc022073d063b25a402454e5712ef9e007113e3a676b96c5f29b2bda29352f40", size = 248248, upload-time = "2026-03-17T10:30:03.317Z" }, + { url = "https://files.pythonhosted.org/packages/da/ba/8477f549e554827da390ec659f3c38e4b6d95470f4daafc2d8ff94eaa9c2/coverage-7.13.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9b74db26dfea4f4e50d48a4602207cd1e78be33182bc9cbf22da94f332f99878", size = 246254, upload-time = "2026-03-17T10:30:04.832Z" }, + { url = "https://files.pythonhosted.org/packages/55/59/bc22aef0e6aa179d5b1b001e8b3654785e9adf27ef24c93dc4228ebd5d68/coverage-7.13.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ad146744ca4fd09b50c482650e3c1b1f4dfa1d4792e0a04a369c7f23336f0400", size = 250067, upload-time = "2026-03-17T10:30:06.535Z" }, + { url = "https://files.pythonhosted.org/packages/de/1b/c6a023a160806a5137dca53468fd97530d6acad24a22003b1578a9c2e429/coverage-7.13.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:c555b48be1853fe3997c11c4bd521cdd9a9612352de01fa4508f16ec341e6fe0", size = 246521, upload-time = "2026-03-17T10:30:08.486Z" }, + { url = "https://files.pythonhosted.org/packages/2d/3f/3532c85a55aa2f899fa17c186f831cfa1aa434d88ff792a709636f64130e/coverage-7.13.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7034b5c56a58ae5e85f23949d52c14aca2cfc6848a31764995b7de88f13a1ea0", size = 247126, upload-time = "2026-03-17T10:30:09.966Z" }, + { url = "https://files.pythonhosted.org/packages/aa/2e/b9d56af4a24ef45dfbcda88e06870cb7d57b2b0bfa3a888d79b4c8debd76/coverage-7.13.5-cp310-cp310-win32.whl", hash = "sha256:eb7fdf1ef130660e7415e0253a01a7d5a88c9c4d158bcf75cbbd922fd65a5b58", size = 221860, upload-time = "2026-03-17T10:30:11.393Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cc/d938417e7a4d7f0433ad4edee8bb2acdc60dc7ac5af19e2a07a048ecbee3/coverage-7.13.5-cp310-cp310-win_amd64.whl", hash = "sha256:3e1bb5f6c78feeb1be3475789b14a0f0a5b47d505bfc7267126ccbd50289999e", size = 222788, upload-time = "2026-03-17T10:30:12.886Z" }, + { url = "https://files.pythonhosted.org/packages/4b/37/d24c8f8220ff07b839b2c043ea4903a33b0f455abe673ae3c03bbdb7f212/coverage-7.13.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66a80c616f80181f4d643b0f9e709d97bcea413ecd9631e1dedc7401c8e6695d", size = 219381, upload-time = "2026-03-17T10:30:14.68Z" }, + { url = "https://files.pythonhosted.org/packages/35/8b/cd129b0ca4afe886a6ce9d183c44d8301acbd4ef248622e7c49a23145605/coverage-7.13.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:145ede53ccbafb297c1c9287f788d1bc3efd6c900da23bf6931b09eafc931587", size = 219880, upload-time = "2026-03-17T10:30:16.231Z" }, + { url = "https://files.pythonhosted.org/packages/55/2f/e0e5b237bffdb5d6c530ce87cc1d413a5b7d7dfd60fb067ad6d254c35c76/coverage-7.13.5-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0672854dc733c342fa3e957e0605256d2bf5934feeac328da9e0b5449634a642", size = 250303, upload-time = "2026-03-17T10:30:17.748Z" }, + { url = "https://files.pythonhosted.org/packages/92/be/b1afb692be85b947f3401375851484496134c5554e67e822c35f28bf2fbc/coverage-7.13.5-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ec10e2a42b41c923c2209b846126c6582db5e43a33157e9870ba9fb70dc7854b", size = 252218, upload-time = "2026-03-17T10:30:19.804Z" }, + { url = "https://files.pythonhosted.org/packages/da/69/2f47bb6fa1b8d1e3e5d0c4be8ccb4313c63d742476a619418f85740d597b/coverage-7.13.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be3d4bbad9d4b037791794ddeedd7d64a56f5933a2c1373e18e9e568b9141686", size = 254326, upload-time = "2026-03-17T10:30:21.321Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d0/79db81da58965bd29dabc8f4ad2a2af70611a57cba9d1ec006f072f30a54/coverage-7.13.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d2afbc5cc54d286bfb54541aa50b64cdb07a718227168c87b9e2fb8f25e1743", size = 256267, upload-time = "2026-03-17T10:30:23.094Z" }, + { url = "https://files.pythonhosted.org/packages/e5/32/d0d7cc8168f91ddab44c0ce4806b969df5f5fdfdbb568eaca2dbc2a04936/coverage-7.13.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3ad050321264c49c2fa67bb599100456fc51d004b82534f379d16445da40fb75", size = 250430, upload-time = "2026-03-17T10:30:25.311Z" }, + { url = "https://files.pythonhosted.org/packages/4d/06/a055311d891ddbe231cd69fdd20ea4be6e3603ffebddf8704b8ca8e10a3c/coverage-7.13.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7300c8a6d13335b29bb76d7651c66af6bd8658517c43499f110ddc6717bfc209", size = 252017, upload-time = "2026-03-17T10:30:27.284Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f6/d0fd2d21e29a657b5f77a2fe7082e1568158340dceb941954f776dce1b7b/coverage-7.13.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:eb07647a5738b89baab047f14edd18ded523de60f3b30e75c2acc826f79c839a", size = 250080, upload-time = "2026-03-17T10:30:29.481Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ab/0d7fb2efc2e9a5eb7ddcc6e722f834a69b454b7e6e5888c3a8567ecffb31/coverage-7.13.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9adb6688e3b53adffefd4a52d72cbd8b02602bfb8f74dcd862337182fd4d1a4e", size = 253843, upload-time = "2026-03-17T10:30:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/ba/6f/7467b917bbf5408610178f62a49c0ed4377bb16c1657f689cc61470da8ce/coverage-7.13.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7c8d4bc913dd70b93488d6c496c77f3aff5ea99a07e36a18f865bca55adef8bd", size = 249802, upload-time = "2026-03-17T10:30:33.358Z" }, + { url = "https://files.pythonhosted.org/packages/75/2c/1172fb689df92135f5bfbbd69fc83017a76d24ea2e2f3a1154007e2fb9f8/coverage-7.13.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0e3c426ffc4cd952f54ee9ffbdd10345709ecc78a3ecfd796a57236bfad0b9b8", size = 250707, upload-time = "2026-03-17T10:30:35.2Z" }, + { url = "https://files.pythonhosted.org/packages/67/21/9ac389377380a07884e3b48ba7a620fcd9dbfaf1d40565facdc6b36ec9ef/coverage-7.13.5-cp311-cp311-win32.whl", hash = "sha256:259b69bb83ad9894c4b25be2528139eecba9a82646ebdda2d9db1ba28424a6bf", size = 221880, upload-time = "2026-03-17T10:30:36.775Z" }, + { url = "https://files.pythonhosted.org/packages/af/7f/4cd8a92531253f9d7c1bbecd9fa1b472907fb54446ca768c59b531248dc5/coverage-7.13.5-cp311-cp311-win_amd64.whl", hash = "sha256:258354455f4e86e3e9d0d17571d522e13b4e1e19bf0f8596bcf9476d61e7d8a9", size = 222816, upload-time = "2026-03-17T10:30:38.891Z" }, + { url = "https://files.pythonhosted.org/packages/12/a6/1d3f6155fb0010ca68eba7fe48ca6c9da7385058b77a95848710ecf189b1/coverage-7.13.5-cp311-cp311-win_arm64.whl", hash = "sha256:bff95879c33ec8da99fc9b6fe345ddb5be6414b41d6d1ad1c8f188d26f36e028", size = 221483, upload-time = "2026-03-17T10:30:40.463Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c3/a396306ba7db865bf96fc1fb3b7fd29bcbf3d829df642e77b13555163cd6/coverage-7.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01", size = 219554, upload-time = "2026-03-17T10:30:42.208Z" }, + { url = "https://files.pythonhosted.org/packages/a6/16/a68a19e5384e93f811dccc51034b1fd0b865841c390e3c931dcc4699e035/coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422", size = 219908, upload-time = "2026-03-17T10:30:43.906Z" }, + { url = "https://files.pythonhosted.org/packages/29/72/20b917c6793af3a5ceb7fb9c50033f3ec7865f2911a1416b34a7cfa0813b/coverage-7.13.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e3370441f4513c6252bf042b9c36d22491142385049243253c7e48398a15a9f", size = 251419, upload-time = "2026-03-17T10:30:45.545Z" }, + { url = "https://files.pythonhosted.org/packages/8c/49/cd14b789536ac6a4778c453c6a2338bc0a2fb60c5a5a41b4008328b9acc1/coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5", size = 254159, upload-time = "2026-03-17T10:30:47.204Z" }, + { url = "https://files.pythonhosted.org/packages/9d/00/7b0edcfe64e2ed4c0340dac14a52ad0f4c9bd0b8b5e531af7d55b703db7c/coverage-7.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376", size = 255270, upload-time = "2026-03-17T10:30:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/93/89/7ffc4ba0f5d0a55c1e84ea7cee39c9fc06af7b170513d83fbf3bbefce280/coverage-7.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256", size = 257538, upload-time = "2026-03-17T10:30:50.77Z" }, + { url = "https://files.pythonhosted.org/packages/81/bd/73ddf85f93f7e6fa83e77ccecb6162d9415c79007b4bc124008a4995e4a7/coverage-7.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8dd02af98971bdb956363e4827d34425cb3df19ee550ef92855b0acb9c7ce51c", size = 251821, upload-time = "2026-03-17T10:30:52.5Z" }, + { url = "https://files.pythonhosted.org/packages/a0/81/278aff4e8dec4926a0bcb9486320752811f543a3ce5b602cc7a29978d073/coverage-7.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f08fd75c50a760c7eb068ae823777268daaf16a80b918fa58eea888f8e3919f5", size = 253191, upload-time = "2026-03-17T10:30:54.543Z" }, + { url = "https://files.pythonhosted.org/packages/70/ee/fe1621488e2e0a58d7e94c4800f0d96f79671553488d401a612bebae324b/coverage-7.13.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:843ea8643cf967d1ac7e8ecd4bb00c99135adf4816c0c0593fdcc47b597fcf09", size = 251337, upload-time = "2026-03-17T10:30:56.663Z" }, + { url = "https://files.pythonhosted.org/packages/37/a6/f79fb37aa104b562207cc23cb5711ab6793608e246cae1e93f26b2236ed9/coverage-7.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9", size = 255404, upload-time = "2026-03-17T10:30:58.427Z" }, + { url = "https://files.pythonhosted.org/packages/75/f0/ed15262a58ec81ce457ceb717b7f78752a1713556b19081b76e90896e8d4/coverage-7.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf", size = 250903, upload-time = "2026-03-17T10:31:00.093Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e9/9129958f20e7e9d4d56d51d42ccf708d15cac355ff4ac6e736e97a9393d2/coverage-7.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c", size = 252780, upload-time = "2026-03-17T10:31:01.916Z" }, + { url = "https://files.pythonhosted.org/packages/a4/d7/0ad9b15812d81272db94379fe4c6df8fd17781cc7671fdfa30c76ba5ff7b/coverage-7.13.5-cp312-cp312-win32.whl", hash = "sha256:bdba0a6b8812e8c7df002d908a9a2ea3c36e92611b5708633c50869e6d922fdf", size = 222093, upload-time = "2026-03-17T10:31:03.642Z" }, + { url = "https://files.pythonhosted.org/packages/29/3d/821a9a5799fac2556bcf0bd37a70d1d11fa9e49784b6d22e92e8b2f85f18/coverage-7.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810", size = 222900, upload-time = "2026-03-17T10:31:05.651Z" }, + { url = "https://files.pythonhosted.org/packages/d4/fa/2238c2ad08e35cf4f020ea721f717e09ec3152aea75d191a7faf3ef009a8/coverage-7.13.5-cp312-cp312-win_arm64.whl", hash = "sha256:bf69236a9a81bdca3bff53796237aab096cdbf8d78a66ad61e992d9dac7eb2de", size = 221515, upload-time = "2026-03-17T10:31:07.293Z" }, + { url = "https://files.pythonhosted.org/packages/74/8c/74fedc9663dcf168b0a059d4ea756ecae4da77a489048f94b5f512a8d0b3/coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ec4af212df513e399cf11610cc27063f1586419e814755ab362e50a85ea69c1", size = 219576, upload-time = "2026-03-17T10:31:09.045Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3", size = 219942, upload-time = "2026-03-17T10:31:10.708Z" }, + { url = "https://files.pythonhosted.org/packages/5f/13/93419671cee82b780bab7ea96b67c8ef448f5f295f36bf5031154ec9a790/coverage-7.13.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:da305e9937617ee95c2e39d8ff9f040e0487cbf1ac174f777ed5eddd7a7c1f26", size = 250935, upload-time = "2026-03-17T10:31:12.392Z" }, + { url = "https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3", size = 253541, upload-time = "2026-03-17T10:31:14.247Z" }, + { url = "https://files.pythonhosted.org/packages/4e/5e/3ee3b835647be646dcf3c65a7c6c18f87c27326a858f72ab22c12730773d/coverage-7.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02ca0eed225b2ff301c474aeeeae27d26e2537942aa0f87491d3e147e784a82b", size = 254780, upload-time = "2026-03-17T10:31:16.193Z" }, + { url = "https://files.pythonhosted.org/packages/44/b3/cb5bd1a04cfcc49ede6cd8409d80bee17661167686741e041abc7ee1b9a9/coverage-7.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04690832cbea4e4663d9149e05dba142546ca05cb1848816760e7f58285c970a", size = 256912, upload-time = "2026-03-17T10:31:17.89Z" }, + { url = "https://files.pythonhosted.org/packages/1b/66/c1dceb7b9714473800b075f5c8a84f4588f887a90eb8645282031676e242/coverage-7.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0590e44dd2745c696a778f7bab6aa95256de2cbc8b8cff4f7db8ff09813d6969", size = 251165, upload-time = "2026-03-17T10:31:19.605Z" }, + { url = "https://files.pythonhosted.org/packages/b7/62/5502b73b97aa2e53ea22a39cf8649ff44827bef76d90bf638777daa27a9d/coverage-7.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d7cfad2d6d81dd298ab6b89fe72c3b7b05ec7544bdda3b707ddaecff8d25c161", size = 252908, upload-time = "2026-03-17T10:31:21.312Z" }, + { url = "https://files.pythonhosted.org/packages/7d/37/7792c2d69854397ca77a55c4646e5897c467928b0e27f2d235d83b5d08c6/coverage-7.13.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e092b9499de38ae0fbfbc603a74660eb6ff3e869e507b50d85a13b6db9863e15", size = 250873, upload-time = "2026-03-17T10:31:23.565Z" }, + { url = "https://files.pythonhosted.org/packages/a3/23/bc866fb6163be52a8a9e5d708ba0d3b1283c12158cefca0a8bbb6e247a43/coverage-7.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:48c39bc4a04d983a54a705a6389512883d4a3b9862991b3617d547940e9f52b1", size = 255030, upload-time = "2026-03-17T10:31:25.58Z" }, + { url = "https://files.pythonhosted.org/packages/7d/8b/ef67e1c222ef49860701d346b8bbb70881bef283bd5f6cbba68a39a086c7/coverage-7.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2d3807015f138ffea1ed9afeeb8624fd781703f2858b62a8dd8da5a0994c57b6", size = 250694, upload-time = "2026-03-17T10:31:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/46/0d/866d1f74f0acddbb906db212e096dee77a8e2158ca5e6bb44729f9d93298/coverage-7.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee2aa19e03161671ec964004fb74b2257805d9710bf14a5c704558b9d8dbaf17", size = 252469, upload-time = "2026-03-17T10:31:29.472Z" }, + { url = "https://files.pythonhosted.org/packages/7a/f5/be742fec31118f02ce42b21c6af187ad6a344fed546b56ca60caacc6a9a0/coverage-7.13.5-cp313-cp313-win32.whl", hash = "sha256:ce1998c0483007608c8382f4ff50164bfc5bd07a2246dd272aa4043b75e61e85", size = 222112, upload-time = "2026-03-17T10:31:31.526Z" }, + { url = "https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b", size = 222923, upload-time = "2026-03-17T10:31:33.633Z" }, + { url = "https://files.pythonhosted.org/packages/48/af/fea819c12a095781f6ccd504890aaddaf88b8fab263c4940e82c7b770124/coverage-7.13.5-cp313-cp313-win_arm64.whl", hash = "sha256:f4cd16206ad171cbc2470dbea9103cf9a7607d5fe8c242fdf1edf36174020664", size = 221540, upload-time = "2026-03-17T10:31:35.445Z" }, + { url = "https://files.pythonhosted.org/packages/23/d2/17879af479df7fbbd44bd528a31692a48f6b25055d16482fdf5cdb633805/coverage-7.13.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0428cbef5783ad91fe240f673cc1f76b25e74bbfe1a13115e4aa30d3f538162d", size = 220262, upload-time = "2026-03-17T10:31:37.184Z" }, + { url = "https://files.pythonhosted.org/packages/5b/4c/d20e554f988c8f91d6a02c5118f9abbbf73a8768a3048cb4962230d5743f/coverage-7.13.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e0b216a19534b2427cc201a26c25da4a48633f29a487c61258643e89d28200c0", size = 220617, upload-time = "2026-03-17T10:31:39.245Z" }, + { url = "https://files.pythonhosted.org/packages/29/9c/f9f5277b95184f764b24e7231e166dfdb5780a46d408a2ac665969416d61/coverage-7.13.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:972a9cd27894afe4bc2b1480107054e062df08e671df7c2f18c205e805ccd806", size = 261912, upload-time = "2026-03-17T10:31:41.324Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f6/7f1ab39393eeb50cfe4747ae8ef0e4fc564b989225aa1152e13a180d74f8/coverage-7.13.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b59148601efcd2bac8c4dbf1f0ad6391693ccf7a74b8205781751637076aee3", size = 263987, upload-time = "2026-03-17T10:31:43.724Z" }, + { url = "https://files.pythonhosted.org/packages/a0/d7/62c084fb489ed9c6fbdf57e006752e7c516ea46fd690e5ed8b8617c7d52e/coverage-7.13.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:505d7083c8b0c87a8fa8c07370c285847c1f77739b22e299ad75a6af6c32c5c9", size = 266416, upload-time = "2026-03-17T10:31:45.769Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f6/df63d8660e1a0bff6125947afda112a0502736f470d62ca68b288ea762d8/coverage-7.13.5-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:60365289c3741e4db327e7baff2a4aaacf22f788e80fa4683393891b70a89fbd", size = 267558, upload-time = "2026-03-17T10:31:48.293Z" }, + { url = "https://files.pythonhosted.org/packages/5b/02/353ca81d36779bd108f6d384425f7139ac3c58c750dcfaafe5d0bee6436b/coverage-7.13.5-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1b88c69c8ef5d4b6fe7dea66d6636056a0f6a7527c440e890cf9259011f5e606", size = 261163, upload-time = "2026-03-17T10:31:50.125Z" }, + { url = "https://files.pythonhosted.org/packages/2c/16/2e79106d5749bcaf3aee6d309123548e3276517cd7851faa8da213bc61bf/coverage-7.13.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5b13955d31d1633cf9376908089b7cebe7d15ddad7aeaabcbe969a595a97e95e", size = 263981, upload-time = "2026-03-17T10:31:51.961Z" }, + { url = "https://files.pythonhosted.org/packages/29/c7/c29e0c59ffa6942030ae6f50b88ae49988e7e8da06de7ecdbf49c6d4feae/coverage-7.13.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f70c9ab2595c56f81a89620e22899eea8b212a4041bd728ac6f4a28bf5d3ddd0", size = 261604, upload-time = "2026-03-17T10:31:53.872Z" }, + { url = "https://files.pythonhosted.org/packages/40/48/097cdc3db342f34006a308ab41c3a7c11c3f0d84750d340f45d88a782e00/coverage-7.13.5-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:084b84a8c63e8d6fc7e3931b316a9bcafca1458d753c539db82d31ed20091a87", size = 265321, upload-time = "2026-03-17T10:31:55.997Z" }, + { url = "https://files.pythonhosted.org/packages/bb/1f/4994af354689e14fd03a75f8ec85a9a68d94e0188bbdab3fc1516b55e512/coverage-7.13.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ad14385487393e386e2ea988b09d62dd42c397662ac2dabc3832d71253eee479", size = 260502, upload-time = "2026-03-17T10:31:58.308Z" }, + { url = "https://files.pythonhosted.org/packages/22/c6/9bb9ef55903e628033560885f5c31aa227e46878118b63ab15dc7ba87797/coverage-7.13.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f2c47b36fe7709a6e83bfadf4eefb90bd25fbe4014d715224c4316f808e59a2", size = 262688, upload-time = "2026-03-17T10:32:00.141Z" }, + { url = "https://files.pythonhosted.org/packages/14/4f/f5df9007e50b15e53e01edea486814783a7f019893733d9e4d6caad75557/coverage-7.13.5-cp313-cp313t-win32.whl", hash = "sha256:67e9bc5449801fad0e5dff329499fb090ba4c5800b86805c80617b4e29809b2a", size = 222788, upload-time = "2026-03-17T10:32:02.246Z" }, + { url = "https://files.pythonhosted.org/packages/e1/98/aa7fccaa97d0f3192bec013c4e6fd6d294a6ed44b640e6bb61f479e00ed5/coverage-7.13.5-cp313-cp313t-win_amd64.whl", hash = "sha256:da86cdcf10d2519e10cabb8ac2de03da1bcb6e4853790b7fbd48523332e3a819", size = 223851, upload-time = "2026-03-17T10:32:04.416Z" }, + { url = "https://files.pythonhosted.org/packages/3d/8b/e5c469f7352651e5f013198e9e21f97510b23de957dd06a84071683b4b60/coverage-7.13.5-cp313-cp313t-win_arm64.whl", hash = "sha256:0ecf12ecb326fe2c339d93fc131816f3a7367d223db37817208905c89bded911", size = 222104, upload-time = "2026-03-17T10:32:06.65Z" }, + { url = "https://files.pythonhosted.org/packages/8e/77/39703f0d1d4b478bfd30191d3c14f53caf596fac00efb3f8f6ee23646439/coverage-7.13.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fbabfaceaeb587e16f7008f7795cd80d20ec548dc7f94fbb0d4ec2e038ce563f", size = 219621, upload-time = "2026-03-17T10:32:08.589Z" }, + { url = "https://files.pythonhosted.org/packages/e2/3e/51dff36d99ae14639a133d9b164d63e628532e2974d8b1edb99dd1ebc733/coverage-7.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9bb2a28101a443669a423b665939381084412b81c3f8c0fcfbac57f4e30b5b8e", size = 219953, upload-time = "2026-03-17T10:32:10.507Z" }, + { url = "https://files.pythonhosted.org/packages/6a/6c/1f1917b01eb647c2f2adc9962bd66c79eb978951cab61bdc1acab3290c07/coverage-7.13.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bd3a2fbc1c6cccb3c5106140d87cc6a8715110373ef42b63cf5aea29df8c217a", size = 250992, upload-time = "2026-03-17T10:32:12.41Z" }, + { url = "https://files.pythonhosted.org/packages/22/e5/06b1f88f42a5a99df42ce61208bdec3bddb3d261412874280a19796fc09c/coverage-7.13.5-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6c36ddb64ed9d7e496028d1d00dfec3e428e0aabf4006583bb1839958d280510", size = 253503, upload-time = "2026-03-17T10:32:14.449Z" }, + { url = "https://files.pythonhosted.org/packages/80/28/2a148a51e5907e504fa7b85490277734e6771d8844ebcc48764a15e28155/coverage-7.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:380e8e9084d8eb38db3a9176a1a4f3c0082c3806fa0dc882d1d87abc3c789247", size = 254852, upload-time = "2026-03-17T10:32:16.56Z" }, + { url = "https://files.pythonhosted.org/packages/61/77/50e8d3d85cc0b7ebe09f30f151d670e302c7ff4a1bf6243f71dd8b0981fa/coverage-7.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e808af52a0513762df4d945ea164a24b37f2f518cbe97e03deaa0ee66139b4d6", size = 257161, upload-time = "2026-03-17T10:32:19.004Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c4/b5fd1d4b7bf8d0e75d997afd3925c59ba629fc8616f1b3aae7605132e256/coverage-7.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e301d30dd7e95ae068671d746ba8c34e945a82682e62918e41b2679acd2051a0", size = 251021, upload-time = "2026-03-17T10:32:21.344Z" }, + { url = "https://files.pythonhosted.org/packages/f8/66/6ea21f910e92d69ef0b1c3346ea5922a51bad4446c9126db2ae96ee24c4c/coverage-7.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:800bc829053c80d240a687ceeb927a94fd108bbdc68dfbe505d0d75ab578a882", size = 252858, upload-time = "2026-03-17T10:32:23.506Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ea/879c83cb5d61aa2a35fb80e72715e92672daef8191b84911a643f533840c/coverage-7.13.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:0b67af5492adb31940ee418a5a655c28e48165da5afab8c7fa6fd72a142f8740", size = 250823, upload-time = "2026-03-17T10:32:25.516Z" }, + { url = "https://files.pythonhosted.org/packages/8a/fb/616d95d3adb88b9803b275580bdeee8bd1b69a886d057652521f83d7322f/coverage-7.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c9136ff29c3a91e25b1d1552b5308e53a1e0653a23e53b6366d7c2dcbbaf8a16", size = 255099, upload-time = "2026-03-17T10:32:27.944Z" }, + { url = "https://files.pythonhosted.org/packages/1c/93/25e6917c90ec1c9a56b0b26f6cad6408e5f13bb6b35d484a0d75c9cf000d/coverage-7.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:cff784eef7f0b8f6cb28804fbddcfa99f89efe4cc35fb5627e3ac58f91ed3ac0", size = 250638, upload-time = "2026-03-17T10:32:29.914Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7b/dc1776b0464145a929deed214aef9fb1493f159b59ff3c7eeeedf91eddd0/coverage-7.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:68a4953be99b17ac3c23b6efbc8a38330d99680c9458927491d18700ef23ded0", size = 252295, upload-time = "2026-03-17T10:32:31.981Z" }, + { url = "https://files.pythonhosted.org/packages/ea/fb/99cbbc56a26e07762a2740713f3c8f9f3f3106e3a3dd8cc4474954bccd34/coverage-7.13.5-cp314-cp314-win32.whl", hash = "sha256:35a31f2b1578185fbe6aa2e74cea1b1d0bbf4c552774247d9160d29b80ed56cc", size = 222360, upload-time = "2026-03-17T10:32:34.233Z" }, + { url = "https://files.pythonhosted.org/packages/8d/b7/4758d4f73fb536347cc5e4ad63662f9d60ba9118cb6785e9616b2ce5d7fa/coverage-7.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:2aa055ae1857258f9e0045be26a6d62bdb47a72448b62d7b55f4820f361a2633", size = 223174, upload-time = "2026-03-17T10:32:36.369Z" }, + { url = "https://files.pythonhosted.org/packages/2c/f2/24d84e1dfe70f8ac9fdf30d338239860d0d1d5da0bda528959d0ebc9da28/coverage-7.13.5-cp314-cp314-win_arm64.whl", hash = "sha256:1b11eef33edeae9d142f9b4358edb76273b3bfd30bc3df9a4f95d0e49caf94e8", size = 221739, upload-time = "2026-03-17T10:32:38.736Z" }, + { url = "https://files.pythonhosted.org/packages/60/5b/4a168591057b3668c2428bff25dd3ebc21b629d666d90bcdfa0217940e84/coverage-7.13.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10a0c37f0b646eaff7cce1874c31d1f1ccb297688d4c747291f4f4c70741cc8b", size = 220351, upload-time = "2026-03-17T10:32:41.196Z" }, + { url = "https://files.pythonhosted.org/packages/f5/21/1fd5c4dbfe4a58b6b99649125635df46decdfd4a784c3cd6d410d303e370/coverage-7.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b5db73ba3c41c7008037fa731ad5459fc3944cb7452fc0aa9f822ad3533c583c", size = 220612, upload-time = "2026-03-17T10:32:43.204Z" }, + { url = "https://files.pythonhosted.org/packages/d6/fe/2a924b3055a5e7e4512655a9d4609781b0d62334fa0140c3e742926834e2/coverage-7.13.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:750db93a81e3e5a9831b534be7b1229df848b2e125a604fe6651e48aa070e5f9", size = 261985, upload-time = "2026-03-17T10:32:45.514Z" }, + { url = "https://files.pythonhosted.org/packages/d7/0d/c8928f2bd518c45990fe1a2ab8db42e914ef9b726c975facc4282578c3eb/coverage-7.13.5-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ddb4f4a5479f2539644be484da179b653273bca1a323947d48ab107b3ed1f29", size = 264107, upload-time = "2026-03-17T10:32:47.971Z" }, + { url = "https://files.pythonhosted.org/packages/ef/ae/4ae35bbd9a0af9d820362751f0766582833c211224b38665c0f8de3d487f/coverage-7.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8a7a2049c14f413163e2bdabd37e41179b1d1ccb10ffc6ccc4b7a718429c607", size = 266513, upload-time = "2026-03-17T10:32:50.1Z" }, + { url = "https://files.pythonhosted.org/packages/9c/20/d326174c55af36f74eac6ae781612d9492f060ce8244b570bb9d50d9d609/coverage-7.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1c85e0b6c05c592ea6d8768a66a254bfb3874b53774b12d4c89c481eb78cb90", size = 267650, upload-time = "2026-03-17T10:32:52.391Z" }, + { url = "https://files.pythonhosted.org/packages/7a/5e/31484d62cbd0eabd3412e30d74386ece4a0837d4f6c3040a653878bfc019/coverage-7.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:777c4d1eff1b67876139d24288aaf1817f6c03d6bae9c5cc8d27b83bcfe38fe3", size = 261089, upload-time = "2026-03-17T10:32:54.544Z" }, + { url = "https://files.pythonhosted.org/packages/e9/d8/49a72d6de146eebb0b7e48cc0f4bc2c0dd858e3d4790ab2b39a2872b62bd/coverage-7.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6697e29b93707167687543480a40f0db8f356e86d9f67ddf2e37e2dfd91a9dab", size = 263982, upload-time = "2026-03-17T10:32:56.803Z" }, + { url = "https://files.pythonhosted.org/packages/06/3b/0351f1bd566e6e4dd39e978efe7958bde1d32f879e85589de147654f57bb/coverage-7.13.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8fdf453a942c3e4d99bd80088141c4c6960bb232c409d9c3558e2dbaa3998562", size = 261579, upload-time = "2026-03-17T10:32:59.466Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ce/796a2a2f4017f554d7810f5c573449b35b1e46788424a548d4d19201b222/coverage-7.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:32ca0c0114c9834a43f045a87dcebd69d108d8ffb666957ea65aa132f50332e2", size = 265316, upload-time = "2026-03-17T10:33:01.847Z" }, + { url = "https://files.pythonhosted.org/packages/3d/16/d5ae91455541d1a78bc90abf495be600588aff8f6db5c8b0dae739fa39c9/coverage-7.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8769751c10f339021e2638cd354e13adeac54004d1941119b2c96fe5276d45ea", size = 260427, upload-time = "2026-03-17T10:33:03.945Z" }, + { url = "https://files.pythonhosted.org/packages/48/11/07f413dba62db21fb3fad5d0de013a50e073cc4e2dc4306e770360f6dfc8/coverage-7.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cec2d83125531bd153175354055cdb7a09987af08a9430bd173c937c6d0fba2a", size = 262745, upload-time = "2026-03-17T10:33:06.285Z" }, + { url = "https://files.pythonhosted.org/packages/91/15/d792371332eb4663115becf4bad47e047d16234b1aff687b1b18c58d60ae/coverage-7.13.5-cp314-cp314t-win32.whl", hash = "sha256:0cd9ed7a8b181775459296e402ca4fb27db1279740a24e93b3b41942ebe4b215", size = 223146, upload-time = "2026-03-17T10:33:08.756Z" }, + { url = "https://files.pythonhosted.org/packages/db/51/37221f59a111dca5e85be7dbf09696323b5b9f13ff65e0641d535ed06ea8/coverage-7.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:301e3b7dfefecaca37c9f1aa6f0049b7d4ab8dd933742b607765d757aca77d43", size = 224254, upload-time = "2026-03-17T10:33:11.174Z" }, + { url = "https://files.pythonhosted.org/packages/54/83/6acacc889de8987441aa7d5adfbdbf33d288dad28704a67e574f1df9bcbb/coverage-7.13.5-cp314-cp314t-win_arm64.whl", hash = "sha256:9dacc2ad679b292709e0f5fc1ac74a6d4d5562e424058962c7bb0c658ad25e45", size = 222276, upload-time = "2026-03-17T10:33:13.466Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ee/a4cf96b8ce1e566ed238f0659ac2d3f007ed1d14b181bcb684e19561a69a/coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61", size = 211346, upload-time = "2026-03-17T10:33:15.691Z" }, ] [package.optional-dependencies] @@ -345,8 +422,8 @@ wheels = [ ] [[package]] -name = "furo" -version = "2025.12.19" +name = "gp-furo-theme" +version = "0.0.1a17" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "accessible-pygments" }, @@ -356,9 +433,9 @@ dependencies = [ { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "sphinx-basic-ng" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ec/20/5f5ad4da6a5a27c80f2ed2ee9aee3f9e36c66e56e21c00fde467b2f8f88f/furo-2025.12.19.tar.gz", hash = "sha256:188d1f942037d8b37cd3985b955839fea62baa1730087dc29d157677c857e2a7", size = 1661473, upload-time = "2025-12-19T17:34:40.889Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/f1/1a75cc2d02a2c9da9063304270fd81012cccf0529657c897861d01f0f853/gp_furo_theme-0.0.1a17.tar.gz", hash = "sha256:3d3d634d9df214990a5d2aa33ab31ce64886ad2e631c76287ce9b1ea25d0a341", size = 33431, upload-time = "2026-05-09T10:05:57.41Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/b2/50e9b292b5cac13e9e81272c7171301abc753a60460d21505b606e15cf21/furo-2025.12.19-py3-none-any.whl", hash = "sha256:bb0ead5309f9500130665a26bee87693c41ce4dbdff864dbfb6b0dae4673d24f", size = 339262, upload-time = "2025-12-19T17:34:38.905Z" }, + { url = "https://files.pythonhosted.org/packages/5c/ad/b75738a1a8ce51b2371bdb4c556dbc9f83c2192511c52bffff4dc76d6b31/gp_furo_theme-0.0.1a17-py3-none-any.whl", hash = "sha256:2868c310f62b03e27a4daa191fc95f57d4a7baacac0a614b103a810119be1af0", size = 42735, upload-time = "2026-05-09T10:05:33.412Z" }, ] [[package]] @@ -375,6 +452,34 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/18/f9/5d78d1dda9cb0f27d6f2305e95a58edbff935a62d53ec3227a3518cb4f72/gp_libs-0.0.17-py3-none-any.whl", hash = "sha256:7ce96d5e09980c0dc82062ab3e3b911600bd44da97a64fb78379f1af9a79d4d3", size = 16157, upload-time = "2025-12-07T22:44:48.036Z" }, ] +[[package]] +name = "gp-sphinx" +version = "0.0.1a17" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "gp-libs" }, + { name = "linkify-it-py" }, + { name = "myst-parser", version = "4.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "myst-parser", version = "5.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "sphinx-autodoc-typehints-gp" }, + { name = "sphinx-copybutton" }, + { name = "sphinx-design", version = "0.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx-design", version = "0.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "sphinx-fonts" }, + { name = "sphinx-gp-opengraph" }, + { name = "sphinx-gp-sitemap" }, + { name = "sphinx-gp-theme" }, + { name = "sphinx-inline-tabs" }, + { name = "sphinxext-rediraffe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/b9/5d99d315c8fb36a9af2cb3901f7d1a4cdaa4fcbcd1c271b927aff0b056d3/gp_sphinx-0.0.1a17.tar.gz", hash = "sha256:976b3ddc529cc2792b2d31acd8f24083e7db18ffab60ee7151d40371c45d3570", size = 18516, upload-time = "2026-05-09T10:05:58.692Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/57/96ddfeab4af002be9cc7a40cd39a8730c4d99b0448e5dcef18a01b324275/gp_sphinx-0.0.1a17-py3-none-any.whl", hash = "sha256:a4bb3311e2748f91da9a1299d5b6e9c71475024824e563611b20ce6e37174eba", size = 18980, upload-time = "2026-05-09T10:05:35.153Z" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -386,11 +491,11 @@ wheels = [ [[package]] name = "idna" -version = "3.11" +version = "3.13" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/cc/762dfb036166873f0059f3b7de4565e1b5bc3d6f28a414c13da27e442f99/idna-3.13.tar.gz", hash = "sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242", size = 194210, upload-time = "2026-04-22T16:42:42.314Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, + { url = "https://files.pythonhosted.org/packages/5d/13/ad7d7ca3808a898b4612b6fe93cde56b53f3034dcde235acb1f0e1df24c6/idna-3.13-py3-none-any.whl", hash = "sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3", size = 68629, upload-time = "2026-04-22T16:42:40.909Z" }, ] [[package]] @@ -425,96 +530,96 @@ wheels = [ [[package]] name = "librt" -version = "0.8.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/56/9c/b4b0c54d84da4a94b37bd44151e46d5e583c9534c7e02250b961b1b6d8a8/librt-0.8.1.tar.gz", hash = "sha256:be46a14693955b3bd96014ccbdb8339ee8c9346fbe11c1b78901b55125f14c73", size = 177471, upload-time = "2026-02-17T16:13:06.101Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/5f/63f5fa395c7a8a93558c0904ba8f1c8d1b997ca6a3de61bc7659970d66bf/librt-0.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:81fd938344fecb9373ba1b155968c8a329491d2ce38e7ddb76f30ffb938f12dc", size = 65697, upload-time = "2026-02-17T16:11:06.903Z" }, - { url = "https://files.pythonhosted.org/packages/ff/e0/0472cf37267b5920eff2f292ccfaede1886288ce35b7f3203d8de00abfe6/librt-0.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5db05697c82b3a2ec53f6e72b2ed373132b0c2e05135f0696784e97d7f5d48e7", size = 68376, upload-time = "2026-02-17T16:11:08.395Z" }, - { url = "https://files.pythonhosted.org/packages/c8/be/8bd1359fdcd27ab897cd5963294fa4a7c83b20a8564678e4fd12157e56a5/librt-0.8.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d56bc4011975f7460bea7b33e1ff425d2f1adf419935ff6707273c77f8a4ada6", size = 197084, upload-time = "2026-02-17T16:11:09.774Z" }, - { url = "https://files.pythonhosted.org/packages/e2/fe/163e33fdd091d0c2b102f8a60cc0a61fd730ad44e32617cd161e7cd67a01/librt-0.8.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdc0f588ff4b663ea96c26d2a230c525c6fc62b28314edaaaca8ed5af931ad0", size = 207337, upload-time = "2026-02-17T16:11:11.311Z" }, - { url = "https://files.pythonhosted.org/packages/01/99/f85130582f05dcf0c8902f3d629270231d2f4afdfc567f8305a952ac7f14/librt-0.8.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:97c2b54ff6717a7a563b72627990bec60d8029df17df423f0ed37d56a17a176b", size = 219980, upload-time = "2026-02-17T16:11:12.499Z" }, - { url = "https://files.pythonhosted.org/packages/6f/54/cb5e4d03659e043a26c74e08206412ac9a3742f0477d96f9761a55313b5f/librt-0.8.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8f1125e6bbf2f1657d9a2f3ccc4a2c9b0c8b176965bb565dd4d86be67eddb4b6", size = 212921, upload-time = "2026-02-17T16:11:14.484Z" }, - { url = "https://files.pythonhosted.org/packages/b1/81/a3a01e4240579c30f3487f6fed01eb4bc8ef0616da5b4ebac27ca19775f3/librt-0.8.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8f4bb453f408137d7581be309b2fbc6868a80e7ef60c88e689078ee3a296ae71", size = 221381, upload-time = "2026-02-17T16:11:17.459Z" }, - { url = "https://files.pythonhosted.org/packages/08/b0/fc2d54b4b1c6fb81e77288ff31ff25a2c1e62eaef4424a984f228839717b/librt-0.8.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c336d61d2fe74a3195edc1646d53ff1cddd3a9600b09fa6ab75e5514ba4862a7", size = 216714, upload-time = "2026-02-17T16:11:19.197Z" }, - { url = "https://files.pythonhosted.org/packages/96/96/85daa73ffbd87e1fb287d7af6553ada66bf25a2a6b0de4764344a05469f6/librt-0.8.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:eb5656019db7c4deacf0c1a55a898c5bb8f989be904597fcb5232a2f4828fa05", size = 214777, upload-time = "2026-02-17T16:11:20.443Z" }, - { url = "https://files.pythonhosted.org/packages/12/9c/c3aa7a2360383f4bf4f04d98195f2739a579128720c603f4807f006a4225/librt-0.8.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c25d9e338d5bed46c1632f851babf3d13c78f49a225462017cf5e11e845c5891", size = 237398, upload-time = "2026-02-17T16:11:22.083Z" }, - { url = "https://files.pythonhosted.org/packages/61/19/d350ea89e5274665185dabc4bbb9c3536c3411f862881d316c8b8e00eb66/librt-0.8.1-cp310-cp310-win32.whl", hash = "sha256:aaab0e307e344cb28d800957ef3ec16605146ef0e59e059a60a176d19543d1b7", size = 54285, upload-time = "2026-02-17T16:11:23.27Z" }, - { url = "https://files.pythonhosted.org/packages/4f/d6/45d587d3d41c112e9543a0093d883eb57a24a03e41561c127818aa2a6bcc/librt-0.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:56e04c14b696300d47b3bc5f1d10a00e86ae978886d0cee14e5714fafb5df5d2", size = 61352, upload-time = "2026-02-17T16:11:24.207Z" }, - { url = "https://files.pythonhosted.org/packages/1d/01/0e748af5e4fee180cf7cd12bd12b0513ad23b045dccb2a83191bde82d168/librt-0.8.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:681dc2451d6d846794a828c16c22dc452d924e9f700a485b7ecb887a30aad1fd", size = 65315, upload-time = "2026-02-17T16:11:25.152Z" }, - { url = "https://files.pythonhosted.org/packages/9d/4d/7184806efda571887c798d573ca4134c80ac8642dcdd32f12c31b939c595/librt-0.8.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3b4350b13cc0e6f5bec8fa7caf29a8fb8cdc051a3bae45cfbfd7ce64f009965", size = 68021, upload-time = "2026-02-17T16:11:26.129Z" }, - { url = "https://files.pythonhosted.org/packages/ae/88/c3c52d2a5d5101f28d3dc89298444626e7874aa904eed498464c2af17627/librt-0.8.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ac1e7817fd0ed3d14fd7c5df91daed84c48e4c2a11ee99c0547f9f62fdae13da", size = 194500, upload-time = "2026-02-17T16:11:27.177Z" }, - { url = "https://files.pythonhosted.org/packages/d6/5d/6fb0a25b6a8906e85b2c3b87bee1d6ed31510be7605b06772f9374ca5cb3/librt-0.8.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:747328be0c5b7075cde86a0e09d7a9196029800ba75a1689332348e998fb85c0", size = 205622, upload-time = "2026-02-17T16:11:28.242Z" }, - { url = "https://files.pythonhosted.org/packages/b2/a6/8006ae81227105476a45691f5831499e4d936b1c049b0c1feb17c11b02d1/librt-0.8.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0af2bd2bc204fa27f3d6711d0f360e6b8c684a035206257a81673ab924aa11e", size = 218304, upload-time = "2026-02-17T16:11:29.344Z" }, - { url = "https://files.pythonhosted.org/packages/ee/19/60e07886ad16670aae57ef44dada41912c90906a6fe9f2b9abac21374748/librt-0.8.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d480de377f5b687b6b1bc0c0407426da556e2a757633cc7e4d2e1a057aa688f3", size = 211493, upload-time = "2026-02-17T16:11:30.445Z" }, - { url = "https://files.pythonhosted.org/packages/9c/cf/f666c89d0e861d05600438213feeb818c7514d3315bae3648b1fc145d2b6/librt-0.8.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d0ee06b5b5291f609ddb37b9750985b27bc567791bc87c76a569b3feed8481ac", size = 219129, upload-time = "2026-02-17T16:11:32.021Z" }, - { url = "https://files.pythonhosted.org/packages/8f/ef/f1bea01e40b4a879364c031476c82a0dc69ce068daad67ab96302fed2d45/librt-0.8.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9e2c6f77b9ad48ce5603b83b7da9ee3e36b3ab425353f695cba13200c5d96596", size = 213113, upload-time = "2026-02-17T16:11:33.192Z" }, - { url = "https://files.pythonhosted.org/packages/9b/80/cdab544370cc6bc1b72ea369525f547a59e6938ef6863a11ab3cd24759af/librt-0.8.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:439352ba9373f11cb8e1933da194dcc6206daf779ff8df0ed69c5e39113e6a99", size = 212269, upload-time = "2026-02-17T16:11:34.373Z" }, - { url = "https://files.pythonhosted.org/packages/9d/9c/48d6ed8dac595654f15eceab2035131c136d1ae9a1e3548e777bb6dbb95d/librt-0.8.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:82210adabbc331dbb65d7868b105185464ef13f56f7f76688565ad79f648b0fe", size = 234673, upload-time = "2026-02-17T16:11:36.063Z" }, - { url = "https://files.pythonhosted.org/packages/16/01/35b68b1db517f27a01be4467593292eb5315def8900afad29fabf56304ba/librt-0.8.1-cp311-cp311-win32.whl", hash = "sha256:52c224e14614b750c0a6d97368e16804a98c684657c7518752c356834fff83bb", size = 54597, upload-time = "2026-02-17T16:11:37.544Z" }, - { url = "https://files.pythonhosted.org/packages/71/02/796fe8f02822235966693f257bf2c79f40e11337337a657a8cfebba5febc/librt-0.8.1-cp311-cp311-win_amd64.whl", hash = "sha256:c00e5c884f528c9932d278d5c9cbbea38a6b81eb62c02e06ae53751a83a4d52b", size = 61733, upload-time = "2026-02-17T16:11:38.691Z" }, - { url = "https://files.pythonhosted.org/packages/28/ad/232e13d61f879a42a4e7117d65e4984bb28371a34bb6fb9ca54ec2c8f54e/librt-0.8.1-cp311-cp311-win_arm64.whl", hash = "sha256:f7cdf7f26c2286ffb02e46d7bac56c94655540b26347673bea15fa52a6af17e9", size = 52273, upload-time = "2026-02-17T16:11:40.308Z" }, - { url = "https://files.pythonhosted.org/packages/95/21/d39b0a87ac52fc98f621fb6f8060efb017a767ebbbac2f99fbcbc9ddc0d7/librt-0.8.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a28f2612ab566b17f3698b0da021ff9960610301607c9a5e8eaca62f5e1c350a", size = 66516, upload-time = "2026-02-17T16:11:41.604Z" }, - { url = "https://files.pythonhosted.org/packages/69/f1/46375e71441c43e8ae335905e069f1c54febee63a146278bcee8782c84fd/librt-0.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:60a78b694c9aee2a0f1aaeaa7d101cf713e92e8423a941d2897f4fa37908dab9", size = 68634, upload-time = "2026-02-17T16:11:43.268Z" }, - { url = "https://files.pythonhosted.org/packages/0a/33/c510de7f93bf1fa19e13423a606d8189a02624a800710f6e6a0a0f0784b3/librt-0.8.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:758509ea3f1eba2a57558e7e98f4659d0ea7670bff49673b0dde18a3c7e6c0eb", size = 198941, upload-time = "2026-02-17T16:11:44.28Z" }, - { url = "https://files.pythonhosted.org/packages/dd/36/e725903416409a533d92398e88ce665476f275081d0d7d42f9c4951999e5/librt-0.8.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:039b9f2c506bd0ab0f8725aa5ba339c6f0cd19d3b514b50d134789809c24285d", size = 209991, upload-time = "2026-02-17T16:11:45.462Z" }, - { url = "https://files.pythonhosted.org/packages/30/7a/8d908a152e1875c9f8eac96c97a480df425e657cdb47854b9efaa4998889/librt-0.8.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bb54f1205a3a6ab41a6fd71dfcdcbd278670d3a90ca502a30d9da583105b6f7", size = 224476, upload-time = "2026-02-17T16:11:46.542Z" }, - { url = "https://files.pythonhosted.org/packages/a8/b8/a22c34f2c485b8903a06f3fe3315341fe6876ef3599792344669db98fcff/librt-0.8.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:05bd41cdee35b0c59c259f870f6da532a2c5ca57db95b5f23689fcb5c9e42440", size = 217518, upload-time = "2026-02-17T16:11:47.746Z" }, - { url = "https://files.pythonhosted.org/packages/79/6f/5c6fea00357e4f82ba44f81dbfb027921f1ab10e320d4a64e1c408d035d9/librt-0.8.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adfab487facf03f0d0857b8710cf82d0704a309d8ffc33b03d9302b4c64e91a9", size = 225116, upload-time = "2026-02-17T16:11:49.298Z" }, - { url = "https://files.pythonhosted.org/packages/f2/a0/95ced4e7b1267fe1e2720a111685bcddf0e781f7e9e0ce59d751c44dcfe5/librt-0.8.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:153188fe98a72f206042be10a2c6026139852805215ed9539186312d50a8e972", size = 217751, upload-time = "2026-02-17T16:11:50.49Z" }, - { url = "https://files.pythonhosted.org/packages/93/c2/0517281cb4d4101c27ab59472924e67f55e375bc46bedae94ac6dc6e1902/librt-0.8.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dd3c41254ee98604b08bd5b3af5bf0a89740d4ee0711de95b65166bf44091921", size = 218378, upload-time = "2026-02-17T16:11:51.783Z" }, - { url = "https://files.pythonhosted.org/packages/43/e8/37b3ac108e8976888e559a7b227d0ceac03c384cfd3e7a1c2ee248dbae79/librt-0.8.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e0d138c7ae532908cbb342162b2611dbd4d90c941cd25ab82084aaf71d2c0bd0", size = 241199, upload-time = "2026-02-17T16:11:53.561Z" }, - { url = "https://files.pythonhosted.org/packages/4b/5b/35812d041c53967fedf551a39399271bbe4257e681236a2cf1a69c8e7fa1/librt-0.8.1-cp312-cp312-win32.whl", hash = "sha256:43353b943613c5d9c49a25aaffdba46f888ec354e71e3529a00cca3f04d66a7a", size = 54917, upload-time = "2026-02-17T16:11:54.758Z" }, - { url = "https://files.pythonhosted.org/packages/de/d1/fa5d5331b862b9775aaf2a100f5ef86854e5d4407f71bddf102f4421e034/librt-0.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:ff8baf1f8d3f4b6b7257fcb75a501f2a5499d0dda57645baa09d4d0d34b19444", size = 62017, upload-time = "2026-02-17T16:11:55.748Z" }, - { url = "https://files.pythonhosted.org/packages/c7/7c/c614252f9acda59b01a66e2ddfd243ed1c7e1deab0293332dfbccf862808/librt-0.8.1-cp312-cp312-win_arm64.whl", hash = "sha256:0f2ae3725904f7377e11cc37722d5d401e8b3d5851fb9273d7f4fe04f6b3d37d", size = 52441, upload-time = "2026-02-17T16:11:56.801Z" }, - { url = "https://files.pythonhosted.org/packages/c5/3c/f614c8e4eaac7cbf2bbdf9528790b21d89e277ee20d57dc6e559c626105f/librt-0.8.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7e6bad1cd94f6764e1e21950542f818a09316645337fd5ab9a7acc45d99a8f35", size = 66529, upload-time = "2026-02-17T16:11:57.809Z" }, - { url = "https://files.pythonhosted.org/packages/ab/96/5836544a45100ae411eda07d29e3d99448e5258b6e9c8059deb92945f5c2/librt-0.8.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cf450f498c30af55551ba4f66b9123b7185362ec8b625a773b3d39aa1a717583", size = 68669, upload-time = "2026-02-17T16:11:58.843Z" }, - { url = "https://files.pythonhosted.org/packages/06/53/f0b992b57af6d5531bf4677d75c44f095f2366a1741fb695ee462ae04b05/librt-0.8.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:eca45e982fa074090057132e30585a7e8674e9e885d402eae85633e9f449ce6c", size = 199279, upload-time = "2026-02-17T16:11:59.862Z" }, - { url = "https://files.pythonhosted.org/packages/f3/ad/4848cc16e268d14280d8168aee4f31cea92bbd2b79ce33d3e166f2b4e4fc/librt-0.8.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c3811485fccfda840861905b8c70bba5ec094e02825598bb9d4ca3936857a04", size = 210288, upload-time = "2026-02-17T16:12:00.954Z" }, - { url = "https://files.pythonhosted.org/packages/52/05/27fdc2e95de26273d83b96742d8d3b7345f2ea2bdbd2405cc504644f2096/librt-0.8.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e4af413908f77294605e28cfd98063f54b2c790561383971d2f52d113d9c363", size = 224809, upload-time = "2026-02-17T16:12:02.108Z" }, - { url = "https://files.pythonhosted.org/packages/7a/d0/78200a45ba3240cb042bc597d6f2accba9193a2c57d0356268cbbe2d0925/librt-0.8.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5212a5bd7fae98dae95710032902edcd2ec4dc994e883294f75c857b83f9aba0", size = 218075, upload-time = "2026-02-17T16:12:03.631Z" }, - { url = "https://files.pythonhosted.org/packages/af/72/a210839fa74c90474897124c064ffca07f8d4b347b6574d309686aae7ca6/librt-0.8.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e692aa2d1d604e6ca12d35e51fdc36f4cda6345e28e36374579f7ef3611b3012", size = 225486, upload-time = "2026-02-17T16:12:04.725Z" }, - { url = "https://files.pythonhosted.org/packages/a3/c1/a03cc63722339ddbf087485f253493e2b013039f5b707e8e6016141130fa/librt-0.8.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4be2a5c926b9770c9e08e717f05737a269b9d0ebc5d2f0060f0fe3fe9ce47acb", size = 218219, upload-time = "2026-02-17T16:12:05.828Z" }, - { url = "https://files.pythonhosted.org/packages/58/f5/fff6108af0acf941c6f274a946aea0e484bd10cd2dc37610287ce49388c5/librt-0.8.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fd1a720332ea335ceb544cf0a03f81df92abd4bb887679fd1e460976b0e6214b", size = 218750, upload-time = "2026-02-17T16:12:07.09Z" }, - { url = "https://files.pythonhosted.org/packages/71/67/5a387bfef30ec1e4b4f30562c8586566faf87e47d696768c19feb49e3646/librt-0.8.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2af9e01e0ef80d95ae3c720be101227edae5f2fe7e3dc63d8857fadfc5a1d", size = 241624, upload-time = "2026-02-17T16:12:08.43Z" }, - { url = "https://files.pythonhosted.org/packages/d4/be/24f8502db11d405232ac1162eb98069ca49c3306c1d75c6ccc61d9af8789/librt-0.8.1-cp313-cp313-win32.whl", hash = "sha256:086a32dbb71336627e78cc1d6ee305a68d038ef7d4c39aaff41ae8c9aa46e91a", size = 54969, upload-time = "2026-02-17T16:12:09.633Z" }, - { url = "https://files.pythonhosted.org/packages/5c/73/c9fdf6cb2a529c1a092ce769a12d88c8cca991194dfe641b6af12fa964d2/librt-0.8.1-cp313-cp313-win_amd64.whl", hash = "sha256:e11769a1dbda4da7b00a76cfffa67aa47cfa66921d2724539eee4b9ede780b79", size = 62000, upload-time = "2026-02-17T16:12:10.632Z" }, - { url = "https://files.pythonhosted.org/packages/d3/97/68f80ca3ac4924f250cdfa6e20142a803e5e50fca96ef5148c52ee8c10ea/librt-0.8.1-cp313-cp313-win_arm64.whl", hash = "sha256:924817ab3141aca17893386ee13261f1d100d1ef410d70afe4389f2359fea4f0", size = 52495, upload-time = "2026-02-17T16:12:11.633Z" }, - { url = "https://files.pythonhosted.org/packages/c9/6a/907ef6800f7bca71b525a05f1839b21f708c09043b1c6aa77b6b827b3996/librt-0.8.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6cfa7fe54fd4d1f47130017351a959fe5804bda7a0bc7e07a2cdbc3fdd28d34f", size = 66081, upload-time = "2026-02-17T16:12:12.766Z" }, - { url = "https://files.pythonhosted.org/packages/1b/18/25e991cd5640c9fb0f8d91b18797b29066b792f17bf8493da183bf5caabe/librt-0.8.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:228c2409c079f8c11fb2e5d7b277077f694cb93443eb760e00b3b83cb8b3176c", size = 68309, upload-time = "2026-02-17T16:12:13.756Z" }, - { url = "https://files.pythonhosted.org/packages/a4/36/46820d03f058cfb5a9de5940640ba03165ed8aded69e0733c417bb04df34/librt-0.8.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7aae78ab5e3206181780e56912d1b9bb9f90a7249ce12f0e8bf531d0462dd0fc", size = 196804, upload-time = "2026-02-17T16:12:14.818Z" }, - { url = "https://files.pythonhosted.org/packages/59/18/5dd0d3b87b8ff9c061849fbdb347758d1f724b9a82241aa908e0ec54ccd0/librt-0.8.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:172d57ec04346b047ca6af181e1ea4858086c80bdf455f61994c4aa6fc3f866c", size = 206907, upload-time = "2026-02-17T16:12:16.513Z" }, - { url = "https://files.pythonhosted.org/packages/d1/96/ef04902aad1424fd7299b62d1890e803e6ab4018c3044dca5922319c4b97/librt-0.8.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b1977c4ea97ce5eb7755a78fae68d87e4102e4aaf54985e8b56806849cc06a3", size = 221217, upload-time = "2026-02-17T16:12:17.906Z" }, - { url = "https://files.pythonhosted.org/packages/6d/ff/7e01f2dda84a8f5d280637a2e5827210a8acca9a567a54507ef1c75b342d/librt-0.8.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:10c42e1f6fd06733ef65ae7bebce2872bcafd8d6e6b0a08fe0a05a23b044fb14", size = 214622, upload-time = "2026-02-17T16:12:19.108Z" }, - { url = "https://files.pythonhosted.org/packages/1e/8c/5b093d08a13946034fed57619742f790faf77058558b14ca36a6e331161e/librt-0.8.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4c8dfa264b9193c4ee19113c985c95f876fae5e51f731494fc4e0cf594990ba7", size = 221987, upload-time = "2026-02-17T16:12:20.331Z" }, - { url = "https://files.pythonhosted.org/packages/d3/cc/86b0b3b151d40920ad45a94ce0171dec1aebba8a9d72bb3fa00c73ab25dd/librt-0.8.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:01170b6729a438f0dedc4a26ed342e3dc4f02d1000b4b19f980e1877f0c297e6", size = 215132, upload-time = "2026-02-17T16:12:21.54Z" }, - { url = "https://files.pythonhosted.org/packages/fc/be/8588164a46edf1e69858d952654e216a9a91174688eeefb9efbb38a9c799/librt-0.8.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:7b02679a0d783bdae30d443025b94465d8c3dc512f32f5b5031f93f57ac32071", size = 215195, upload-time = "2026-02-17T16:12:23.073Z" }, - { url = "https://files.pythonhosted.org/packages/f5/f2/0b9279bea735c734d69344ecfe056c1ba211694a72df10f568745c899c76/librt-0.8.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:190b109bb69592a3401fe1ffdea41a2e73370ace2ffdc4a0e8e2b39cdea81b78", size = 237946, upload-time = "2026-02-17T16:12:24.275Z" }, - { url = "https://files.pythonhosted.org/packages/e9/cc/5f2a34fbc8aeb35314a3641f9956fa9051a947424652fad9882be7a97949/librt-0.8.1-cp314-cp314-win32.whl", hash = "sha256:e70a57ecf89a0f64c24e37f38d3fe217a58169d2fe6ed6d70554964042474023", size = 50689, upload-time = "2026-02-17T16:12:25.766Z" }, - { url = "https://files.pythonhosted.org/packages/a0/76/cd4d010ab2147339ca2b93e959c3686e964edc6de66ddacc935c325883d7/librt-0.8.1-cp314-cp314-win_amd64.whl", hash = "sha256:7e2f3edca35664499fbb36e4770650c4bd4a08abc1f4458eab9df4ec56389730", size = 57875, upload-time = "2026-02-17T16:12:27.465Z" }, - { url = "https://files.pythonhosted.org/packages/84/0f/2143cb3c3ca48bd3379dcd11817163ca50781927c4537345d608b5045998/librt-0.8.1-cp314-cp314-win_arm64.whl", hash = "sha256:0d2f82168e55ddefd27c01c654ce52379c0750ddc31ee86b4b266bcf4d65f2a3", size = 48058, upload-time = "2026-02-17T16:12:28.556Z" }, - { url = "https://files.pythonhosted.org/packages/d2/0e/9b23a87e37baf00311c3efe6b48d6b6c168c29902dfc3f04c338372fd7db/librt-0.8.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2c74a2da57a094bd48d03fa5d196da83d2815678385d2978657499063709abe1", size = 68313, upload-time = "2026-02-17T16:12:29.659Z" }, - { url = "https://files.pythonhosted.org/packages/db/9a/859c41e5a4f1c84200a7d2b92f586aa27133c8243b6cac9926f6e54d01b9/librt-0.8.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a355d99c4c0d8e5b770313b8b247411ed40949ca44e33e46a4789b9293a907ee", size = 70994, upload-time = "2026-02-17T16:12:31.516Z" }, - { url = "https://files.pythonhosted.org/packages/4c/28/10605366ee599ed34223ac2bf66404c6fb59399f47108215d16d5ad751a8/librt-0.8.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2eb345e8b33fb748227409c9f1233d4df354d6e54091f0e8fc53acdb2ffedeb7", size = 220770, upload-time = "2026-02-17T16:12:33.294Z" }, - { url = "https://files.pythonhosted.org/packages/af/8d/16ed8fd452dafae9c48d17a6bc1ee3e818fd40ef718d149a8eff2c9f4ea2/librt-0.8.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9be2f15e53ce4e83cc08adc29b26fb5978db62ef2a366fbdf716c8a6c8901040", size = 235409, upload-time = "2026-02-17T16:12:35.443Z" }, - { url = "https://files.pythonhosted.org/packages/89/1b/7bdf3e49349c134b25db816e4a3db6b94a47ac69d7d46b1e682c2c4949be/librt-0.8.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:785ae29c1f5c6e7c2cde2c7c0e148147f4503da3abc5d44d482068da5322fd9e", size = 246473, upload-time = "2026-02-17T16:12:36.656Z" }, - { url = "https://files.pythonhosted.org/packages/4e/8a/91fab8e4fd2a24930a17188c7af5380eb27b203d72101c9cc000dbdfd95a/librt-0.8.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d3a7da44baf692f0c6aeb5b2a09c5e6fc7a703bca9ffa337ddd2e2da53f7732", size = 238866, upload-time = "2026-02-17T16:12:37.849Z" }, - { url = "https://files.pythonhosted.org/packages/b9/e0/c45a098843fc7c07e18a7f8a24ca8496aecbf7bdcd54980c6ca1aaa79a8e/librt-0.8.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5fc48998000cbc39ec0d5311312dda93ecf92b39aaf184c5e817d5d440b29624", size = 250248, upload-time = "2026-02-17T16:12:39.445Z" }, - { url = "https://files.pythonhosted.org/packages/82/30/07627de23036640c952cce0c1fe78972e77d7d2f8fd54fa5ef4554ff4a56/librt-0.8.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e96baa6820280077a78244b2e06e416480ed859bbd8e5d641cf5742919d8beb4", size = 240629, upload-time = "2026-02-17T16:12:40.889Z" }, - { url = "https://files.pythonhosted.org/packages/fb/c1/55bfe1ee3542eba055616f9098eaf6eddb966efb0ca0f44eaa4aba327307/librt-0.8.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:31362dbfe297b23590530007062c32c6f6176f6099646bb2c95ab1b00a57c382", size = 239615, upload-time = "2026-02-17T16:12:42.446Z" }, - { url = "https://files.pythonhosted.org/packages/2b/39/191d3d28abc26c9099b19852e6c99f7f6d400b82fa5a4e80291bd3803e19/librt-0.8.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc3656283d11540ab0ea01978378e73e10002145117055e03722417aeab30994", size = 263001, upload-time = "2026-02-17T16:12:43.627Z" }, - { url = "https://files.pythonhosted.org/packages/b9/eb/7697f60fbe7042ab4e88f4ee6af496b7f222fffb0a4e3593ef1f29f81652/librt-0.8.1-cp314-cp314t-win32.whl", hash = "sha256:738f08021b3142c2918c03692608baed43bc51144c29e35807682f8070ee2a3a", size = 51328, upload-time = "2026-02-17T16:12:45.148Z" }, - { url = "https://files.pythonhosted.org/packages/7c/72/34bf2eb7a15414a23e5e70ecb9440c1d3179f393d9349338a91e2781c0fb/librt-0.8.1-cp314-cp314t-win_amd64.whl", hash = "sha256:89815a22daf9c51884fb5dbe4f1ef65ee6a146e0b6a8df05f753e2e4a9359bf4", size = 58722, upload-time = "2026-02-17T16:12:46.85Z" }, - { url = "https://files.pythonhosted.org/packages/b2/c8/d148e041732d631fc76036f8b30fae4e77b027a1e95b7a84bb522481a940/librt-0.8.1-cp314-cp314t-win_arm64.whl", hash = "sha256:bf512a71a23504ed08103a13c941f763db13fb11177beb3d9244c98c29fb4a61", size = 48755, upload-time = "2026-02-17T16:12:47.943Z" }, +version = "0.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/39/cb/c1945e506893b5b8577fb45a60c80e3ffe4a82092a04a6f29b0b951d9a24/librt-0.10.0.tar.gz", hash = "sha256:1aba1e8aa4e3307a7be68a74149545fde7451964dc0235a8bec5704a17bdda42", size = 191799, upload-time = "2026-05-05T16:31:23.535Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/18/827e5c1262a88c2602e86f99aee0f288ffea3280dbd2ff448858ef9dc6e9/librt-0.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7dc99f9642100b86e5f6bb14cdc9970009e31a9ef7d64df6704b7018451524a3", size = 76461, upload-time = "2026-05-05T16:29:00.422Z" }, + { url = "https://files.pythonhosted.org/packages/ee/90/54254e30287f5a5abec6fef22d976987476e966be5fdff51fe8c2d5d73d1/librt-0.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8298cedfcfaff3790000bd057aaaa3df1b0ab54cf7b48eeab16184cbb1bc66b9", size = 79740, upload-time = "2026-05-05T16:29:01.926Z" }, + { url = "https://files.pythonhosted.org/packages/4c/20/e93264b52113669d98d3b63ff94d4ce0c4dd49ae0503f1788440a884e5f0/librt-0.10.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee7dbe312dbf76468255b79a7ba311236fde620f2f7055fc09d421e31340314e", size = 243472, upload-time = "2026-05-05T16:29:03.373Z" }, + { url = "https://files.pythonhosted.org/packages/35/ad/34a5141178e8b18a4cfa45d1a0d523c84397e2abd5d06fea2d846da687e8/librt-0.10.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:56ed90c48c19249012dadfd79a1bc13bd5168ea60a70722d330a3a600c0b1852", size = 232073, upload-time = "2026-05-05T16:29:04.815Z" }, + { url = "https://files.pythonhosted.org/packages/97/1f/67240e910cd9f9ab1498c1470738345fc29dce5dc9719db1e0e09d1e861f/librt-0.10.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d74ca0f4b2b09c117f913d4df01f6b934dff8a271096b35167d5264a31649f0", size = 256956, upload-time = "2026-05-05T16:29:06.516Z" }, + { url = "https://files.pythonhosted.org/packages/22/50/3a2b3482c27d607f6e8216d913c6bc592b9a2141d96990309452340a78e3/librt-0.10.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8eb2daa9375f93c0e55ff5e44a4bbe98f39e5fe52e1abf9c97acb67743b61bf8", size = 250593, upload-time = "2026-05-05T16:29:08.324Z" }, + { url = "https://files.pythonhosted.org/packages/e7/1c/07dba133d79f93322fa17514062f1a2a50d6bdfb7baec4acf78193d7fad1/librt-0.10.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7b09b90e634e6dff57978cd358070046071e2b120501f10787aeb35425f504f6", size = 263582, upload-time = "2026-05-05T16:29:09.866Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ac/033f2c6d6ab0b48f15f02e5bf065521b11a51922806017f8b6274df30d69/librt-0.10.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:2cf22fd379d60c739b800d4295ed34045f8b04aa8df9c12bd2f8f43f7fe672b7", size = 259307, upload-time = "2026-05-05T16:29:11.675Z" }, + { url = "https://files.pythonhosted.org/packages/6e/10/679046cd75d5a52c0104c890d8f69574ef4e619c683e59c15584d03a2457/librt-0.10.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:74c798793fcf29a84d442278ebe0bb1fff79fe58ac4106eeff7019cbba861423", size = 257342, upload-time = "2026-05-05T16:29:13.14Z" }, + { url = "https://files.pythonhosted.org/packages/1e/d5/dbaac9c0884f78a53dda22b9ec92bb788e1400e762ed7623fa96928c8da5/librt-0.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dc4f1573401e8dbe6c26511fe027620b0fb30ae9a7ab814e02e510626b8b5f9c", size = 280141, upload-time = "2026-05-05T16:29:14.922Z" }, + { url = "https://files.pythonhosted.org/packages/cc/81/71f18cf8eb340d9fda011498870910f6a8697aeb50833005d3d8107653fd/librt-0.10.0-cp310-cp310-win32.whl", hash = "sha256:e1428275f5fe3d4db6822e58d8b005a5b28ffca55e8433ebc051247fbe46429f", size = 62257, upload-time = "2026-05-05T16:29:16.226Z" }, + { url = "https://files.pythonhosted.org/packages/df/52/6bcebc2f870c4836bcb372be885fae7f17a1d25037d3a8250ef79fbe0124/librt-0.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:0708e9408f585b0f065081680583a577652099680ccf820c7538904322b679c3", size = 70321, upload-time = "2026-05-05T16:29:17.41Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a3/1472717d2325adacc8d335ba2e4078015c09d75b599f3cf48e967b3d306e/librt-0.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:01b4500ca3a625450c032a9142a8e843923ce263fa8a92ad1b38927cabe2fe72", size = 76045, upload-time = "2026-05-05T16:29:18.731Z" }, + { url = "https://files.pythonhosted.org/packages/a6/31/bfe32355d4b369aef3d7aa442df663bb5558c2ffa2de286cb2956346bc24/librt-0.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6b7e42d1b3e300d20bfc87e72ffd62f0a92a2cb3c35f7bf90df90c9d2a49f74c", size = 79466, upload-time = "2026-05-05T16:29:20.052Z" }, + { url = "https://files.pythonhosted.org/packages/e9/f1/83f8a2c715ba2cac9b7387a5a5cea25f717f7184320cfe48b36bed9c58e9/librt-0.10.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8ef7b8c61ce3a1b597cd3e15348ff1574325165c2e7ce09a718154cde2a7950", size = 242283, upload-time = "2026-05-05T16:29:21.596Z" }, + { url = "https://files.pythonhosted.org/packages/cc/94/c3a4ce94857f0004a542f86662806383611858f522722db58efaec0a1472/librt-0.10.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:e73c84f72d1fa0d6eaa7a1930b436ba8d2c90c58d77bfabb09995a69ad35f6c0", size = 230735, upload-time = "2026-05-05T16:29:23.335Z" }, + { url = "https://files.pythonhosted.org/packages/d1/41/e962bb26c7728eb7b3a69e490d0c800fd9968a6970e390c1f18ddb56093d/librt-0.10.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9728cb98713bd862fb8f4fd6a642d1896c86058a41d77c70f3d5cee75e725275", size = 256606, upload-time = "2026-05-05T16:29:24.91Z" }, + { url = "https://files.pythonhosted.org/packages/66/3a/4e46a707b1ecc993fd691071623b9beab89703a63bd21cc7807e06c28209/librt-0.10.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:648b7e941d20acd72f9652115e0e53facd98156d61f9ebf7a812bdef8bdccea9", size = 249739, upload-time = "2026-05-05T16:29:26.648Z" }, + { url = "https://files.pythonhosted.org/packages/b2/f5/dc5b7eb294656ad23d4ff4cf8514208d54fe1026b909d726a0dc026689c9/librt-0.10.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c3e33747c068e86a9007c20fdb777eb5ba8d3d19136d7812f88e69a713041b6f", size = 261414, upload-time = "2026-05-05T16:29:28.702Z" }, + { url = "https://files.pythonhosted.org/packages/58/e4/990ed8d12c7f114ac8f8ccd47f7d9bd9704ef61acfcb1df4a05047da7710/librt-0.10.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d509c745bf7e77d1107cf05e6abb249dc03fad13eb39f2286a49deedaeb2bcd7", size = 256614, upload-time = "2026-05-05T16:29:30.357Z" }, + { url = "https://files.pythonhosted.org/packages/60/eb/52d2726c7fb22818507dc3cc166c8f36dd4a4b68a7be67f12006ac8777c1/librt-0.10.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:786ad5a15e99d0e0e74f3adbeecc198a5ac58f340be07e984723d1e0074838de", size = 255144, upload-time = "2026-05-05T16:29:32.106Z" }, + { url = "https://files.pythonhosted.org/packages/bc/df/bd5591a78f7531fce4b6eb9962aadc6adc9560a01570442a884b6e554abe/librt-0.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:075582d877a97ee3d8e77bda3689dbe617b14f6469224a2d80b4b6c38e3951aa", size = 279121, upload-time = "2026-05-05T16:29:33.688Z" }, + { url = "https://files.pythonhosted.org/packages/fd/df/7c2b838dfc89a1762dd156d8b0c39848a7a2845d725a50be5a6e021fb8ba/librt-0.10.0-cp311-cp311-win32.whl", hash = "sha256:75ecdc3f5a90065aa2af2e574706c5495adc392520762dcf10b1aa716f0b8090", size = 62593, upload-time = "2026-05-05T16:29:35.152Z" }, + { url = "https://files.pythonhosted.org/packages/91/19/22ff572981049a9d436a083dbea1572d0f5dc068b7353637d2dd9977c8f1/librt-0.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:b6f6084884131d8a52cb9d7095ff2aa52c1e786d9fdaefab1fb4515415e9e083", size = 70914, upload-time = "2026-05-05T16:29:36.407Z" }, + { url = "https://files.pythonhosted.org/packages/12/22/1697cc64f4a5c7e9bce55e99c6d234a346beaedaefcd1e2ca90dd285f98c/librt-0.10.0-cp311-cp311-win_arm64.whl", hash = "sha256:0140bd62151160047e89b2730cb6f8506cdac5127baa1afb9231e4dd3fe7f681", size = 61176, upload-time = "2026-05-05T16:29:37.62Z" }, + { url = "https://files.pythonhosted.org/packages/12/8e/cbb5b6f6e45e65c10a42449a69eaccc44d73e6a081ea752fbc5221c6dc1c/librt-0.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b4b58a44b407e91f633dafee008de9ddea6aa2a555ed94929c099260910bd0ba", size = 77327, upload-time = "2026-05-05T16:29:38.919Z" }, + { url = "https://files.pythonhosted.org/packages/e9/3d/8233cbee8e99e6a8992f02bfc2dec8d787509566a511d1fde2574ee7473f/librt-0.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:950b79b11762531bdf45a9df909d2f9a2a8445c70c88665c01d14c8511a27dc5", size = 79971, upload-time = "2026-05-05T16:29:40.96Z" }, + { url = "https://files.pythonhosted.org/packages/87/6f/5264b298cef2b72fc97d2dde56c66181eda35204bf5dcd1ed0c3d0a0a782/librt-0.10.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4538453f51be197633b425912c150e25b0667252d3741c53e8368176d98d9d37", size = 246559, upload-time = "2026-05-05T16:29:42.701Z" }, + { url = "https://files.pythonhosted.org/packages/07/7b/19b1b859cc60d5f99276cc2b3144d91556c6d1b1e4ebb50359696bebf7a8/librt-0.10.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:70b955f091beac93e994a0b7ec616934f63b3ea5c3d6d7af847562f935aceca7", size = 235216, upload-time = "2026-05-05T16:29:44.193Z" }, + { url = "https://files.pythonhosted.org/packages/6e/56/a2f40717142a8af46289f57874ef914353d8faccd5e4f8e594ab1e16e8c7/librt-0.10.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:483e685e06b6163728ba6c85d74315176be7190f432ec2a41226e5e14355d5f0", size = 263108, upload-time = "2026-05-05T16:29:46.365Z" }, + { url = "https://files.pythonhosted.org/packages/67/ca/15c625c3bdc0167c01e04ef8878317e9713f3bfa788438342f7a94c7b22c/librt-0.10.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7ac53d946a009d1a38c44a60812708c9458fb2a239a5f630d8e625571386650f", size = 255280, upload-time = "2026-05-05T16:29:48.087Z" }, + { url = "https://files.pythonhosted.org/packages/ed/c5/ba301d571d9e05844e2435b73aba30bee77bb75ce155c9affcfd2173dd03/librt-0.10.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bc8771c9fcf0ea894ca41fdc2abd83572c2fbda221f232d86e718614e57ff513", size = 268829, upload-time = "2026-05-05T16:29:49.628Z" }, + { url = "https://files.pythonhosted.org/packages/8b/60/af70e135bc1f1fe15dd3894b1e4bbefc7ecdf911749a925a39eb86ceb2a1/librt-0.10.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:70805dbc5257892ac572f86290a61e3c8d90224ecce1a8b2d1f7ed51965417f4", size = 262051, upload-time = "2026-05-05T16:29:51.244Z" }, + { url = "https://files.pythonhosted.org/packages/83/c2/c8236eb8b421bac5a172ba208f965abaa89805da2a3fa112bdf1764caf8f/librt-0.10.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d3b4f300f7bcba6e2ff73fb8bef1898479e9772bfa2682998c636391633ec826", size = 264347, upload-time = "2026-05-05T16:29:53.013Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f5/15b6d32bc25dacd4a60886a683d8128d6219910c122202b995a40dd4f8d2/librt-0.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:943bc943f92f4fb3408fae62485c6a3ad68ce4f2ee205643a39641525c19a276", size = 286482, upload-time = "2026-05-05T16:29:54.675Z" }, + { url = "https://files.pythonhosted.org/packages/fb/8e/b1b959bacd323eb4360579db992513e1406d1c6ef7edb57b5511fd0666fd/librt-0.10.0-cp312-cp312-win32.whl", hash = "sha256:6065c1a758fba1010b41401013903d3d5d2750eab425ddedd584abac31d0630e", size = 62955, upload-time = "2026-05-05T16:29:56.39Z" }, + { url = "https://files.pythonhosted.org/packages/9e/4c/d4cd6e4b9fc24098e63cc85537d1b6689682aee96809c38f08072067cc2b/librt-0.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:d788ecbe208ab352dab0e105cc06057bf9a2fc7e58cabb0d751ad9e30062b9e2", size = 71191, upload-time = "2026-05-05T16:29:57.682Z" }, + { url = "https://files.pythonhosted.org/packages/2b/19/8641da1f63d24b92354a492f893c022d6b3a0df44e70c8eff49364613983/librt-0.10.0-cp312-cp312-win_arm64.whl", hash = "sha256:6003d1f295bdba02656dc81308208fc060d0a51d8c0d0a6db70f7f3c57b9ba0a", size = 61432, upload-time = "2026-05-05T16:29:58.971Z" }, + { url = "https://files.pythonhosted.org/packages/e5/29/681a75c82f4cc90d29e4b257a3299b79fe13fe927a04c57b8109d70b6957/librt-0.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f0ede79d682e73f91c1b599a76d78b7464b9b5d213754cedb13372d9df36e596", size = 77299, upload-time = "2026-05-05T16:30:00.209Z" }, + { url = "https://files.pythonhosted.org/packages/62/24/0c7ca445a55d04be79cac19819437fd094782347fa116f6681844fa6143e/librt-0.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e0ba0b131fdb336c8b9c948e397f4a7e649d0f783b529f07b647bf4961df392e", size = 79930, upload-time = "2026-05-05T16:30:01.555Z" }, + { url = "https://files.pythonhosted.org/packages/fe/1f/1e2b8f6443ef9e9a81e89486ca70e22f3684f93db003ce6eaefc3d0839b9/librt-0.10.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2728117da2afb96fb957768725ee43dc9a2d73b031e02da424b818a3cdd3a275", size = 246195, upload-time = "2026-05-05T16:30:03.261Z" }, + { url = "https://files.pythonhosted.org/packages/74/61/9dc9e03de0439ad84c1c240aac8b747f12c90cb797ea6042f7bdb8d3410f/librt-0.10.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:723ba80594c49cdf0584196fc430752262605dc9449902fc9bd3d9b79976cb77", size = 234951, upload-time = "2026-05-05T16:30:04.881Z" }, + { url = "https://files.pythonhosted.org/packages/55/f4/635223117d7590875bca441275065a3bf491203ad4208bd1cc3ffd90c5a1/librt-0.10.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7292edaaca294a61a978c53a3c7d6130d099b0dfbc8f0a65916cdc6b891b9852", size = 262768, upload-time = "2026-05-05T16:30:06.638Z" }, + { url = "https://files.pythonhosted.org/packages/e5/66/b04152d0cd8b6ca2b428a8bd3230343230c35ed304a932f35b5375f2f828/librt-0.10.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:89fe9d539f2c10a1666633eeeac507ce95dd06d9ecc58de3c6390dba156a3d3a", size = 255075, upload-time = "2026-05-05T16:30:08.216Z" }, + { url = "https://files.pythonhosted.org/packages/35/1e/25bac4c7f2ca36f0e612cade186970683cf79153d96beccc3a11a9e19b97/librt-0.10.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4efa7b9587503fa5b67f40593302b9c8836d211d222ff9f7cafe67be5f8f0b10", size = 268559, upload-time = "2026-05-05T16:30:10.1Z" }, + { url = "https://files.pythonhosted.org/packages/18/54/4601faab35b6632a13200faa146ca62bfd111ffbe2568be430d65c89493a/librt-0.10.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:22dc982ef59df0136df36092ccbdbb570ced8aafb33e49585739b2f1de1c13b6", size = 261753, upload-time = "2026-05-05T16:30:11.912Z" }, + { url = "https://files.pythonhosted.org/packages/1b/cf/39f4023509e94fade8b074666fa3292db9cb6b34ea5dcbe7af53df9fca1d/librt-0.10.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:6f2e5f3606253a84cea719c94a3bb1c54487b5d617d0254d46e0920d8a06be3f", size = 264055, upload-time = "2026-05-05T16:30:13.465Z" }, + { url = "https://files.pythonhosted.org/packages/8e/00/40247209fc46a8e308a91412d5206aedf8efb667ee89eb625820106a5c2f/librt-0.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:40884bfaa1e29f6b6a9be255007d8f359bfc9e61d68bdef8ed3158bfcbc95df9", size = 286190, upload-time = "2026-05-05T16:30:15.073Z" }, + { url = "https://files.pythonhosted.org/packages/d8/6e/5566beb94431a985abe1787af5ef86e087750172ff9d0bbf20f93e88132d/librt-0.10.0-cp313-cp313-win32.whl", hash = "sha256:3cd34cd8254eba756660bff6c2da91278248184301054fe3e4feb073bdd49b14", size = 62949, upload-time = "2026-05-05T16:30:16.503Z" }, + { url = "https://files.pythonhosted.org/packages/d0/c2/3ea3301d6c8dff51d39dbe8ed75db3dc92896947d4afb5eeadf821c1e67f/librt-0.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:7baac5313e2d8dce1386f97777a8d03ab28f5fe1e780b3b9ac2ee7544551fedc", size = 71152, upload-time = "2026-05-05T16:30:17.766Z" }, + { url = "https://files.pythonhosted.org/packages/3c/de/5d49cb92cadcbc77d3abc27b93fd6030ed8437487dde2eae38cab5e6704d/librt-0.10.0-cp313-cp313-win_arm64.whl", hash = "sha256:afc5b4406c8e2515698d922a5c7823a009312835ea58196671fff40e35cb8166", size = 61336, upload-time = "2026-05-05T16:30:19.021Z" }, + { url = "https://files.pythonhosted.org/packages/6a/64/7165e08108cc185a13a9c069f0685e6ef92e70e07fddf7edf5e7348c6316/librt-0.10.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:f09588a30e6a22ec624090d72a3ab1a6d4d5485c3ed739603e76aa3c16efa688", size = 76794, upload-time = "2026-05-05T16:30:20.392Z" }, + { url = "https://files.pythonhosted.org/packages/ae/ef/bf8613febf651b90c5222ee79dea5ae58d4cc2b544df69d3033424448934/librt-0.10.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:131ade118d12bd7a0adc4e655474a553f1b76cf78385868885944d21d51e45e0", size = 79662, upload-time = "2026-05-05T16:30:22.025Z" }, + { url = "https://files.pythonhosted.org/packages/b6/67/9eddd165c1d8397bdf99b38bf12b5a55b3def5035b49eedb49f2775d1430/librt-0.10.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8b9ab28e40d011c373a189eae900c916e66d6fbecf7983e9e4883089ee085ef", size = 242390, upload-time = "2026-05-05T16:30:23.51Z" }, + { url = "https://files.pythonhosted.org/packages/10/d1/d95da80334501866cd37004ab5d7483220d05862fab4b5405394f0264f0d/librt-0.10.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:67c39bb30da73bae1f293d1ed8bc2f8f6642649dd0928d3600aeff3041ac23d6", size = 232603, upload-time = "2026-05-05T16:30:25.198Z" }, + { url = "https://files.pythonhosted.org/packages/0c/fa/e6d64d28718bc1be4e1736fcb037ca1c4dfca927e7167df75a7d5215665e/librt-0.10.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8c3273c6b774614f093c8927c2bf1b077d0fefde988fe98f46a333734e5597ab", size = 259187, upload-time = "2026-05-05T16:30:26.772Z" }, + { url = "https://files.pythonhosted.org/packages/72/3f/3fdb77e7f937dad59cfd76b720be7e7643400ec76b2da35befab8d66ba30/librt-0.10.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9dd7c1b86a4baa583ab5db977484b93a2c474e69e96ef3e9538387ea54229cb9", size = 251846, upload-time = "2026-05-05T16:30:28.56Z" }, + { url = "https://files.pythonhosted.org/packages/18/ca/f4d49133dd86a6f55d79eca30bf412fa722f511a9abe67f62f57aa64e66a/librt-0.10.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a77385c5a202e831149f7ad03be9e67cf80e957e52c614e83dcb822c95222eb8", size = 264936, upload-time = "2026-05-05T16:30:30.491Z" }, + { url = "https://files.pythonhosted.org/packages/de/66/a8df2fbadc1f6c1827a096d11c40175bd526133480bd3bc88ec64a03d257/librt-0.10.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c6a5eafa74b5655bad59886138ed68426f098a6beb8cb95a71f2cc3cd8bb33fe", size = 258699, upload-time = "2026-05-05T16:30:32.002Z" }, + { url = "https://files.pythonhosted.org/packages/bb/73/1e3c83613fe05451bb969e27b68a573d177f08d5f63533cc29fec0989658/librt-0.10.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:1fc93d0439204c50ab4d1512611ce2c206f1b369b419f69c7c27c761561e3291", size = 259825, upload-time = "2026-05-05T16:30:35.077Z" }, + { url = "https://files.pythonhosted.org/packages/09/24/5e2f926ee9d3ef348d9339526d7062abb5c44d8419e3179528c01d78c102/librt-0.10.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:79e713c178bc7a744adfbee6b4619a288eecc0c914da2a9313a20255abe2f0cf", size = 282548, upload-time = "2026-05-05T16:30:36.639Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7d/3e89ed6ad0162561fa8bef9df3195e24263104c955713cd0237d3711fad2/librt-0.10.0-cp314-cp314-win32.whl", hash = "sha256:2eba9d955a68c41d9f326be3da42f163ec3518b7ab20f1c826224e7bed71e0bf", size = 58970, upload-time = "2026-05-05T16:30:38.183Z" }, + { url = "https://files.pythonhosted.org/packages/76/25/579e731c94a7086a268bfa3e7a4945cd47836bebd3cbf3faeafd2e7eaef9/librt-0.10.0-cp314-cp314-win_amd64.whl", hash = "sha256:cbfaf7f5145e9917f5d18bffa298eff6a19d74e7b8b11dabdca95785befe8dbf", size = 67260, upload-time = "2026-05-05T16:30:39.804Z" }, + { url = "https://files.pythonhosted.org/packages/6e/f8/235822b7ae0b2334f12ee18bcf2476d07924077a5efeea57dbe927704be2/librt-0.10.0-cp314-cp314-win_arm64.whl", hash = "sha256:8d6d385d1969849a6b1397114df22714b6ded917bada98668e3e974dc663477e", size = 57156, upload-time = "2026-05-05T16:30:41.412Z" }, + { url = "https://files.pythonhosted.org/packages/9f/e3/9b919cbf1e8eb770bf91bb7df28125e0f1daf4587169afefd95402636e9a/librt-0.10.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:6c3a82d3bd32631ef5c79922dfc028520c9ad840255979ab4d908271818039ee", size = 79150, upload-time = "2026-05-05T16:30:42.761Z" }, + { url = "https://files.pythonhosted.org/packages/6a/f5/72a944aa3bc3498169a168087eff58ca48b58bf1b704e59d091fd30739f3/librt-0.10.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d64cc66005dc324c9bb1fa3fc2841f529002f6eb15966d55e46d430f56955a6a", size = 82304, upload-time = "2026-05-05T16:30:44.082Z" }, + { url = "https://files.pythonhosted.org/packages/9c/e3/fcc290a33e295019759472dfa794d204e43504b276ac65eab7fd9da20ea3/librt-0.10.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9bb562cd28c88cd2c6a9a6c78f99dc39348d6b16c94adc25de0e574acf1176e9", size = 272556, upload-time = "2026-05-05T16:30:45.497Z" }, + { url = "https://files.pythonhosted.org/packages/fd/54/546975e4c997573885e7f040a05012f8838e06fb12b0c3c1fbb76254e9d7/librt-0.10.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:b809aa2854d019c28773b03605df22adc675ee4f3f4402d673581313e8906119", size = 256941, upload-time = "2026-05-05T16:30:47.059Z" }, + { url = "https://files.pythonhosted.org/packages/70/8c/f1d03401571b331653acddbd4e8cd955c06d945241dd08b25192fac0d04b/librt-0.10.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cc15acabdd519bd4176fdadc2119e5e3093485d86f89138daf47e5b4cedb983a", size = 285855, upload-time = "2026-05-05T16:30:48.86Z" }, + { url = "https://files.pythonhosted.org/packages/0c/08/62cf80ff046c339faf56718b3a940244d4beb70f1c6407289b5830ec11e9/librt-0.10.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b1b2d835307d08ddadd94568e2369648ec9173bd3eea6d7f52a1abe717c81f98", size = 275321, upload-time = "2026-05-05T16:30:50.63Z" }, + { url = "https://files.pythonhosted.org/packages/d9/ea/da5918d4070362e9a4d2ee9cd34f9dc84902daad8fd4275f8504a727ff4e/librt-0.10.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d261c6a2f93335a5167887fb0223e8b98ffce20ee3fde242e8e58a37ece6d0e5", size = 293993, upload-time = "2026-05-05T16:30:52.577Z" }, + { url = "https://files.pythonhosted.org/packages/c9/8d/68b6086bed1fcdc314c640ea04e31e52d18052e08059fa595409d66a51a9/librt-0.10.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e2ffd44963f8e7f68995504d90f9881d64e94dc1d8e310039b9526108fc0c0f7", size = 284254, upload-time = "2026-05-05T16:30:55.086Z" }, + { url = "https://files.pythonhosted.org/packages/06/c8/b810f1d84ec34a5a7ed93d7b510ab04164d75fbdf23088d5c3fbe6b08357/librt-0.10.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:5f285f6455ed495791c4d8630e5af732960adea93cac4c893d15619f2eae53e8", size = 284925, upload-time = "2026-05-05T16:30:56.728Z" }, + { url = "https://files.pythonhosted.org/packages/5a/00/3c82d4158c5a2c62528b8fccce65a8c9ad700e480e86f9389387435089a5/librt-0.10.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f6034ff52e663d34c7b82ef2aa2f94ad7c1d939e2368e63b06844bc4d127d2e1", size = 307830, upload-time = "2026-05-05T16:30:58.377Z" }, + { url = "https://files.pythonhosted.org/packages/99/3a/9c635ac3e8a00383ff689161d3eac8a30b3b2ddc711b40471e6b8983ea29/librt-0.10.0-cp314-cp314t-win32.whl", hash = "sha256:657860fd877fba6a241ea088ef99f63ca819945d3c715265da670bad56c37ebe", size = 60147, upload-time = "2026-05-05T16:31:00.293Z" }, + { url = "https://files.pythonhosted.org/packages/dc/e8/6f65f3e565d4ac212cddddd552eacc8035ffdf941ca0ad6fe945a211d41f/librt-0.10.0-cp314-cp314t-win_amd64.whl", hash = "sha256:56ded2d66010203a0cb5af063b609e3f079531a0e5e576d618dece859fd2e1af", size = 68649, upload-time = "2026-05-05T16:31:01.778Z" }, + { url = "https://files.pythonhosted.org/packages/51/78/a0705a67cacd81e5fa01a5035b3adbdfbb43a7b8d4bd27e2b282ae61baf2/librt-0.10.0-cp314-cp314t-win_arm64.whl", hash = "sha256:1ee63f30abf18ed4830fdbaf87b2b6f4bba1e198d46085c314edde4045e56715", size = 58247, upload-time = "2026-05-05T16:31:03.191Z" }, ] [[package]] name = "libtmux" -version = "0.55.0" +version = "0.56.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f7/85/99932ac9ddb90821778f8cabe32b81bbbec280dd1a14a457c512693fb11b/libtmux-0.55.0.tar.gz", hash = "sha256:cdc4aa564b2325618d73d57cb0d7d92475d02026dba2b96a94f87ad328e7e79d", size = 420859, upload-time = "2026-03-08T00:57:55.788Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/62/896e1e0412dd76c88926604d5a231feb9b116d6f32abe19054e244504dbc/libtmux-0.56.0.tar.gz", hash = "sha256:bddf52214405e4f64850826d44cbc958d4a01c53432983cee0e2856bdbbaaedb", size = 476168, upload-time = "2026-05-10T13:40:23.774Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/34/b11ab24abb78c73a1b82f6471c2d71bdd1bf2c8f30768ed2f26f1dddc083/libtmux-0.55.0-py3-none-any.whl", hash = "sha256:4b746533856e022c759e5c5cae97f4932e85dae316a2afd4391d6d0e891d6ab8", size = 80094, upload-time = "2026-03-08T00:57:54.141Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ce/4319c912164fa142956c73ba50ed6f2aac2ca7cced2e96c8320114f1c937/libtmux-0.56.0-py3-none-any.whl", hash = "sha256:ddf70de0f287666fb0f02082732f28eed46450de1828c995da3de2b12042ab60", size = 97768, upload-time = "2026-05-10T13:40:22.189Z" }, ] [[package]] @@ -546,18 +651,18 @@ wheels = [ [[package]] name = "markdown-it-py" -version = "4.0.0" +version = "4.2.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.12'", - "python_full_version == '3.11.*'", + "python_full_version >= '3.15'", + "python_full_version >= '3.11' and python_full_version < '3.15'", ] dependencies = [ { name = "mdurl", marker = "python_full_version >= '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, + { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" }, ] [[package]] @@ -647,15 +752,15 @@ wheels = [ [[package]] name = "mdit-py-plugins" -version = "0.5.0" +version = "0.6.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "markdown-it-py", version = "4.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "markdown-it-py", version = "4.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b2/fd/a756d36c0bfba5f6e39a1cdbdbfdd448dc02692467d83816dff4592a1ebc/mdit_py_plugins-0.5.0.tar.gz", hash = "sha256:f4918cb50119f50446560513a8e311d574ff6aaed72606ddae6d35716fe809c6", size = 44655, upload-time = "2025-08-11T07:25:49.083Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/3d/e0e8d9d1cee04f758120915e2b2a3a07eb41f8cf4654b4734788a522bcd1/mdit_py_plugins-0.6.0.tar.gz", hash = "sha256:2436f14a7295837ac9228a36feeabda867c4abc488c8d019ad5c0bda88eee040", size = 56025, upload-time = "2026-05-07T12:20:42.295Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl", hash = "sha256:07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f", size = 57205, upload-time = "2025-08-11T07:25:47.597Z" }, + { url = "https://files.pythonhosted.org/packages/71/d6/48f5b9e44e2e760855d7b489b1317cd7620e82dcb73197961e5cc1391348/mdit_py_plugins-0.6.0-py3-none-any.whl", hash = "sha256:f7e7a25d8b616fee99cb1e330da73451d11a8061baf39bb9663ab9ce0e005b90", size = 66655, upload-time = "2026-05-07T12:20:41.226Z" }, ] [[package]] @@ -669,48 +774,61 @@ wheels = [ [[package]] name = "mypy" -version = "1.19.1" +version = "2.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "ast-serialize" }, { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, { name = "mypy-extensions" }, { name = "pathspec" }, { name = "tomli", marker = "python_full_version < '3.11'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/63/e499890d8e39b1ff2df4c0c6ce5d371b6844ee22b8250687a99fd2f657a8/mypy-1.19.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f05aa3d375b385734388e844bc01733bd33c644ab48e9684faa54e5389775ec", size = 13101333, upload-time = "2025-12-15T05:03:03.28Z" }, - { url = "https://files.pythonhosted.org/packages/72/4b/095626fc136fba96effc4fd4a82b41d688ab92124f8c4f7564bffe5cf1b0/mypy-1.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:022ea7279374af1a5d78dfcab853fe6a536eebfda4b59deab53cd21f6cd9f00b", size = 12164102, upload-time = "2025-12-15T05:02:33.611Z" }, - { url = "https://files.pythonhosted.org/packages/0c/5b/952928dd081bf88a83a5ccd49aaecfcd18fd0d2710c7ff07b8fb6f7032b9/mypy-1.19.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee4c11e460685c3e0c64a4c5de82ae143622410950d6be863303a1c4ba0e36d6", size = 12765799, upload-time = "2025-12-15T05:03:28.44Z" }, - { url = "https://files.pythonhosted.org/packages/2a/0d/93c2e4a287f74ef11a66fb6d49c7a9f05e47b0a4399040e6719b57f500d2/mypy-1.19.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de759aafbae8763283b2ee5869c7255391fbc4de3ff171f8f030b5ec48381b74", size = 13522149, upload-time = "2025-12-15T05:02:36.011Z" }, - { url = "https://files.pythonhosted.org/packages/7b/0e/33a294b56aaad2b338d203e3a1d8b453637ac36cb278b45005e0901cf148/mypy-1.19.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ab43590f9cd5108f41aacf9fca31841142c786827a74ab7cc8a2eacb634e09a1", size = 13810105, upload-time = "2025-12-15T05:02:40.327Z" }, - { url = "https://files.pythonhosted.org/packages/0e/fd/3e82603a0cb66b67c5e7abababce6bf1a929ddf67bf445e652684af5c5a0/mypy-1.19.1-cp310-cp310-win_amd64.whl", hash = "sha256:2899753e2f61e571b3971747e302d5f420c3fd09650e1951e99f823bc3089dac", size = 10057200, upload-time = "2025-12-15T05:02:51.012Z" }, - { url = "https://files.pythonhosted.org/packages/ef/47/6b3ebabd5474d9cdc170d1342fbf9dddc1b0ec13ec90bf9004ee6f391c31/mypy-1.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288", size = 13028539, upload-time = "2025-12-15T05:03:44.129Z" }, - { url = "https://files.pythonhosted.org/packages/5c/a6/ac7c7a88a3c9c54334f53a941b765e6ec6c4ebd65d3fe8cdcfbe0d0fd7db/mypy-1.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab", size = 12083163, upload-time = "2025-12-15T05:03:37.679Z" }, - { url = "https://files.pythonhosted.org/packages/67/af/3afa9cf880aa4a2c803798ac24f1d11ef72a0c8079689fac5cfd815e2830/mypy-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6", size = 12687629, upload-time = "2025-12-15T05:02:31.526Z" }, - { url = "https://files.pythonhosted.org/packages/2d/46/20f8a7114a56484ab268b0ab372461cb3a8f7deed31ea96b83a4e4cfcfca/mypy-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331", size = 13436933, upload-time = "2025-12-15T05:03:15.606Z" }, - { url = "https://files.pythonhosted.org/packages/5b/f8/33b291ea85050a21f15da910002460f1f445f8007adb29230f0adea279cb/mypy-1.19.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925", size = 13661754, upload-time = "2025-12-15T05:02:26.731Z" }, - { url = "https://files.pythonhosted.org/packages/fd/a3/47cbd4e85bec4335a9cd80cf67dbc02be21b5d4c9c23ad6b95d6c5196bac/mypy-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042", size = 10055772, upload-time = "2025-12-15T05:03:26.179Z" }, - { url = "https://files.pythonhosted.org/packages/06/8a/19bfae96f6615aa8a0604915512e0289b1fad33d5909bf7244f02935d33a/mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1", size = 13206053, upload-time = "2025-12-15T05:03:46.622Z" }, - { url = "https://files.pythonhosted.org/packages/a5/34/3e63879ab041602154ba2a9f99817bb0c85c4df19a23a1443c8986e4d565/mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e", size = 12219134, upload-time = "2025-12-15T05:03:24.367Z" }, - { url = "https://files.pythonhosted.org/packages/89/cc/2db6f0e95366b630364e09845672dbee0cbf0bbe753a204b29a944967cd9/mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2", size = 12731616, upload-time = "2025-12-15T05:02:44.725Z" }, - { url = "https://files.pythonhosted.org/packages/00/be/dd56c1fd4807bc1eba1cf18b2a850d0de7bacb55e158755eb79f77c41f8e/mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8", size = 13620847, upload-time = "2025-12-15T05:03:39.633Z" }, - { url = "https://files.pythonhosted.org/packages/6d/42/332951aae42b79329f743bf1da088cd75d8d4d9acc18fbcbd84f26c1af4e/mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a", size = 13834976, upload-time = "2025-12-15T05:03:08.786Z" }, - { url = "https://files.pythonhosted.org/packages/6f/63/e7493e5f90e1e085c562bb06e2eb32cae27c5057b9653348d38b47daaecc/mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13", size = 10118104, upload-time = "2025-12-15T05:03:10.834Z" }, - { url = "https://files.pythonhosted.org/packages/de/9f/a6abae693f7a0c697dbb435aac52e958dc8da44e92e08ba88d2e42326176/mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250", size = 13201927, upload-time = "2025-12-15T05:02:29.138Z" }, - { url = "https://files.pythonhosted.org/packages/9a/a4/45c35ccf6e1c65afc23a069f50e2c66f46bd3798cbe0d680c12d12935caa/mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b", size = 12206730, upload-time = "2025-12-15T05:03:01.325Z" }, - { url = "https://files.pythonhosted.org/packages/05/bb/cdcf89678e26b187650512620eec8368fded4cfd99cfcb431e4cdfd19dec/mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e", size = 12724581, upload-time = "2025-12-15T05:03:20.087Z" }, - { url = "https://files.pythonhosted.org/packages/d1/32/dd260d52babf67bad8e6770f8e1102021877ce0edea106e72df5626bb0ec/mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef", size = 13616252, upload-time = "2025-12-15T05:02:49.036Z" }, - { url = "https://files.pythonhosted.org/packages/71/d0/5e60a9d2e3bd48432ae2b454b7ef2b62a960ab51292b1eda2a95edd78198/mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75", size = 13840848, upload-time = "2025-12-15T05:02:55.95Z" }, - { url = "https://files.pythonhosted.org/packages/98/76/d32051fa65ecf6cc8c6610956473abdc9b4c43301107476ac03559507843/mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd", size = 10135510, upload-time = "2025-12-15T05:02:58.438Z" }, - { url = "https://files.pythonhosted.org/packages/de/eb/b83e75f4c820c4247a58580ef86fcd35165028f191e7e1ba57128c52782d/mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1", size = 13199744, upload-time = "2025-12-15T05:03:30.823Z" }, - { url = "https://files.pythonhosted.org/packages/94/28/52785ab7bfa165f87fcbb61547a93f98bb20e7f82f90f165a1f69bce7b3d/mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718", size = 12215815, upload-time = "2025-12-15T05:02:42.323Z" }, - { url = "https://files.pythonhosted.org/packages/0a/c6/bdd60774a0dbfb05122e3e925f2e9e846c009e479dcec4821dad881f5b52/mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b", size = 12740047, upload-time = "2025-12-15T05:03:33.168Z" }, - { url = "https://files.pythonhosted.org/packages/32/2a/66ba933fe6c76bd40d1fe916a83f04fed253152f451a877520b3c4a5e41e/mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045", size = 13601998, upload-time = "2025-12-15T05:03:13.056Z" }, - { url = "https://files.pythonhosted.org/packages/e3/da/5055c63e377c5c2418760411fd6a63ee2b96cf95397259038756c042574f/mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957", size = 13807476, upload-time = "2025-12-15T05:03:17.977Z" }, - { url = "https://files.pythonhosted.org/packages/cd/09/4ebd873390a063176f06b0dbf1f7783dd87bd120eae7727fa4ae4179b685/mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f", size = 10281872, upload-time = "2025-12-15T05:03:05.549Z" }, - { url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/cf/dc/7e6d49f04fca40b9dd5c752a51a432ffe67fb45200702bc9eee0cb4bbb26/mypy-2.0.0.tar.gz", hash = "sha256:1a9e3900ac5c40f1fe813506c7739da6e6f0eab2729067ebd94bfb0bbba53532", size = 3869036, upload-time = "2026-05-06T19:26:43.22Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/1e/9983d2d5b5d2dc3677177bcf0fa6b25185ecf750cc0559e02199625a31c5/mypy-2.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:65d6f22d643bccaeb182d41d2a9f0990a05a871673c4ae3f97d4931eca0d2294", size = 14663140, upload-time = "2026-05-06T19:25:59.474Z" }, + { url = "https://files.pythonhosted.org/packages/69/bc/b4009c91d3ced13c8f406acf47bbe56365025cd21bf6585cd1e87375a708/mypy-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:106650bce72114f43019bf72197296f51c2cd47adfa9d073ea2976c247a404c5", size = 13526733, upload-time = "2026-05-06T19:22:56.425Z" }, + { url = "https://files.pythonhosted.org/packages/f0/99/2403cb0ceeb1552f70e70e779e3d0713b24f84c7ca0e9e14b2b7bc684cf0/mypy-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c734b7eb89a4cc4ec347f8187ffa730e2b59693407bc93dcb878183037f80a17", size = 13951940, upload-time = "2026-05-06T19:24:43.45Z" }, + { url = "https://files.pythonhosted.org/packages/1d/f7/4848a14c2667b6eb62841c9aeb7e1f6479613b1ef9a65564fe1f5518a35b/mypy-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd9e60388944d0f1432a2419ab938a78d5658df1d143a7172cfe1a197276cf49", size = 14833983, upload-time = "2026-05-06T19:23:16.827Z" }, + { url = "https://files.pythonhosted.org/packages/ec/28/c51831f9f1c6e46cbce765bd0a18981b84696e40bd1eea14e0a08494af44/mypy-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f95e3890666c3be41af7a7179f4872341c08e90c161ba8e7a08a21f9be92c131", size = 15135591, upload-time = "2026-05-06T19:24:32.96Z" }, + { url = "https://files.pythonhosted.org/packages/40/7f/3c25e503a94f9ec18352464551bc6c506dee2bca93c6d0e0b5568eefc269/mypy-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:e8e8709ce1b1046b8aad77a506dd01491157102dd727128c0b374b5025c7d769", size = 10983019, upload-time = "2026-05-06T19:20:30.942Z" }, + { url = "https://files.pythonhosted.org/packages/75/da/5cf833fd3b53fd4b5797e55dc16fb7efab16fddbc7205d49ff65b15d554e/mypy-2.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:0165968759c99ab79dc1a9f8aaec18e93a1bedcf7c13edd70e68dd3d5faf17cb", size = 9914165, upload-time = "2026-05-06T19:21:49.165Z" }, + { url = "https://files.pythonhosted.org/packages/8b/1e/268b81393b81d64683f670680215553e70ae92c55805915b3440080e05e4/mypy-2.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c17b7222e9fdfd352e61fb3131da117e55cc465f701ff232f1bd97a02bbad91f", size = 14580849, upload-time = "2026-05-06T19:23:06.567Z" }, + { url = "https://files.pythonhosted.org/packages/6e/32/d159a8002d9e5c44e59ece9d641a26956c89be5b6827f819d9a9dc678c65/mypy-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cc0a61adea1a5ffc2d47a4dc4bb180d8103f477fc2a90a1cdcbb168c2cc6caff", size = 13444955, upload-time = "2026-05-06T19:25:11.982Z" }, + { url = "https://files.pythonhosted.org/packages/cd/5d/3b28d5a2799591da0ee5490418e94497eaf5d701e42d8b001b5e17a9b3d6/mypy-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8578f857b519993d065e5805290b71467ebfae772407a5f57e823755e4fdb850", size = 13873124, upload-time = "2026-05-06T19:20:39.684Z" }, + { url = "https://files.pythonhosted.org/packages/60/23/f40f723955617b814d5ddc1154d8938b77aaf6926c2dbf72846e8943a0b7/mypy-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:33f668a37a650df60f7b825c1ac61e6baadd4ac3c89519e929badde58d28edf5", size = 14748822, upload-time = "2026-05-06T19:25:30.972Z" }, + { url = "https://files.pythonhosted.org/packages/d6/16/eded971224a483e422a141ffd580c00e1b919df8e529f06d03a4a987878c/mypy-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29ea6da86c8c5e9addd48fa6e624f467341b3814f54ded871b28980468686dea", size = 14992675, upload-time = "2026-05-06T19:23:34.511Z" }, + { url = "https://files.pythonhosted.org/packages/ea/6a/1cbd7290f00b4dbaa4c4502e53ac05645ea635e4d1e3dcd42687c2fc39cd/mypy-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:904baa0124ebbccf0c7ba94f722cf9186ee30478f5e5b11432ffc8929248ee55", size = 10983628, upload-time = "2026-05-06T19:26:39.48Z" }, + { url = "https://files.pythonhosted.org/packages/83/3f/8caa9bcc2636cd512642050747466b695fa2540d7040544fd7ddb721d671/mypy-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:440165501295e523bf1e5d3e411b62b367b901c65610938e75f0e56ba0462461", size = 9906041, upload-time = "2026-05-06T19:24:03.199Z" }, + { url = "https://files.pythonhosted.org/packages/f6/4b/f6cd12ef1eb63be1c342da3e8ca811d2280276177f6de4ef20cb2366d79b/mypy-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:660790551c988e69d8bf7a35c8b4149edeb22f4a339165702be843532e9dcdb5", size = 14756610, upload-time = "2026-05-06T19:26:19.221Z" }, + { url = "https://files.pythonhosted.org/packages/32/73/67d09ca28bee21feaca264b2a680cf2d300bcc2071136ad064928324c843/mypy-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7a15bf92cd8781f8e72f69ffa7e30d1f434402d065ee1ecd5223ef2ef100f914", size = 13554270, upload-time = "2026-05-06T19:26:08.977Z" }, + { url = "https://files.pythonhosted.org/packages/61/b3/44718b5c6b1b5a27440ff2effe6a1be0fa2a190c0f4e2e21a83728416f95/mypy-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4ff370b43d7def05bbcd2f5267f0bcda72dd6a552ef2ea9375b02d6fe06da270", size = 13924663, upload-time = "2026-05-06T19:21:24.932Z" }, + { url = "https://files.pythonhosted.org/packages/6a/2b/bbb9cc5773f946846a7c340097e59bcf84095437dda0d56bb4f6cf1f6541/mypy-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:37bd246590a018e5a11703b7b09c39d47ede3df5ba3fa863c5b8590b465beb01", size = 14946862, upload-time = "2026-05-06T19:24:23.023Z" }, + { url = "https://files.pythonhosted.org/packages/43/25/e9318566f443a5130b4ff0ad3367ee6c4c4c49ff083fe5214a7318c18282/mypy-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cce87e92214fac8bf8feb8a680d0c1b6fb748d50e9b57fbb13e4b1d83a3ed19b", size = 15175090, upload-time = "2026-05-06T19:26:28.794Z" }, + { url = "https://files.pythonhosted.org/packages/67/65/2ec28c834f21e164c33bc296a7db538ad50c74f83e517c7a0be95ff6de86/mypy-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:e19e9cb69b66a4141009d24898259914fa2b71d026de0b46edf9fafdbf4fd46e", size = 11052899, upload-time = "2026-05-06T19:25:39.084Z" }, + { url = "https://files.pythonhosted.org/packages/9e/72/d1ec625cfc9bd101c07a6834ef1f94e820296f8fdbad2eb03f50e0983f8c/mypy-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:b021614cb08d44785b025982163ec3c39c94bff766ead071fa9e82b4ef6f62cd", size = 9972935, upload-time = "2026-05-06T19:23:24.204Z" }, + { url = "https://files.pythonhosted.org/packages/e5/c6/996a1e535e5d0d597c3b1460fc962733091f885f312e749350eb2ac10965/mypy-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ef5f581b61240d1cc629b12f8df6565ed6ffac0d82ed745eef7833222ab50b9", size = 14737259, upload-time = "2026-05-06T19:20:23.081Z" }, + { url = "https://files.pythonhosted.org/packages/94/c5/0f9460e26b77f434bd53f47d1ce32a3cd4580c92a5331fa5dfc059f9421a/mypy-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:20e3470a165dbc249bdfbe8d1c5172727ef22688cffc279f8c3aa264ab9d4d9a", size = 13538377, upload-time = "2026-05-06T19:21:08.804Z" }, + { url = "https://files.pythonhosted.org/packages/b2/3e/8ea2f8dd1e5c9c279fb3c28193bdb850adf4d3d8172880abad829eced609/mypy-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:224ba142eee8b4d65d4db657cb1fc22abec30b135ded6ab297302ba1f62e505d", size = 13914264, upload-time = "2026-05-06T19:24:12.875Z" }, + { url = "https://files.pythonhosted.org/packages/be/ce/78bd3b8520f676acee9dab48ea71473e68f6d5cf14b59fbd800bea50a92b/mypy-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e879ad8a03908ff74d15e8a9b42bf049918e6798d52c011011f1873d0b5877e", size = 14926761, upload-time = "2026-05-06T19:20:12.846Z" }, + { url = "https://files.pythonhosted.org/packages/61/ef/b52fa340522da3d22e669117c3b83155c2660f7cdc035856958fbfffb224/mypy-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:65c5c15bcbd18d6fe927cc55c459597a3517d69cc3123f067be3b020010e115e", size = 15157014, upload-time = "2026-05-06T19:25:49.78Z" }, + { url = "https://files.pythonhosted.org/packages/7a/0c/dde7614250c6d017936c7aa3bb63b9b52c7cfd298d3f1be9be45f307870b/mypy-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:d1a068acd7c9fb77e9f8923f1556f2f49d6d7895821121b8d97fa5642b9c52f5", size = 11067049, upload-time = "2026-05-06T19:21:16.116Z" }, + { url = "https://files.pythonhosted.org/packages/27/ec/1d6af4830a94a285442db19caa02f160cc1a255e4f324eec5458e6c2bafb/mypy-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:ef9d96da1ddffbc21f27d3939319b6846d12393baa17c4d2f3e81e040e73ce2c", size = 9967903, upload-time = "2026-05-06T19:22:15.52Z" }, + { url = "https://files.pythonhosted.org/packages/ce/2c/6fefe954207860aed6eeb91776795e64a257d3ce0360862288984ce121f5/mypy-2.0.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c918c64e8ce36557851b0347f84eb12f1965d3a06813c36df253eb0c0afd1d82", size = 14729633, upload-time = "2026-05-06T19:24:53.383Z" }, + { url = "https://files.pythonhosted.org/packages/23/d6/d336f5b820af189eb0390cce21de62d264c0a4e64713dfbe81bfc4fc7739/mypy-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:301f1a8ccc7d79b542ee218b28bb49443a83e194eb3d10da63ff1649e5aa5d34", size = 13559524, upload-time = "2026-05-06T19:22:24.906Z" }, + { url = "https://files.pythonhosted.org/packages/af/a6/d7bb54fde1770f0484e5fbdbdce37a41e95ed0a1cd493ec60ead111e356c/mypy-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fdf4ef489d44ce350bac3fd699907834e551d4c934e9cc862ef201215ab1558d", size = 13936018, upload-time = "2026-05-06T19:25:02.992Z" }, + { url = "https://files.pythonhosted.org/packages/7d/ba/5be51316b91e6a6bf6e3a8adb3de500e7e1fb5bf9491743b8cbc81a34a2c/mypy-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9cde2d0989f912fc850890f727d0d76495e7a6c5bdd9912a1efdb64952b4398d", size = 14910712, upload-time = "2026-05-06T19:25:21.83Z" }, + { url = "https://files.pythonhosted.org/packages/b7/37/e2c8c3b373e20ebfb66e6c83a99027fd67df4ec43b08879f74e822d2dc4c/mypy-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cdf05693c231a14fe37dbfce192a3a1372c26a833af4a80f550547742952e719", size = 15141499, upload-time = "2026-05-06T19:20:50.924Z" }, + { url = "https://files.pythonhosted.org/packages/12/36/07756f933e00416d912e35878cfcf89a593a3350a885691c0bb85ae0226a/mypy-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:73aee2da33a2237e66cbe84a94780e53599847e86bb3aa7b93e405e8cd9905f2", size = 11240511, upload-time = "2026-05-06T19:21:32.39Z" }, + { url = "https://files.pythonhosted.org/packages/70/05/79ac1f20f2397353f3845f7b8bb5d8006cda7c8ef9092f04f9de3c6135f2/mypy-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:1f6dcd8f39971f41edab2728c877c4ac8b50ad3c387ff2770423b79a05d23910", size = 10149336, upload-time = "2026-05-06T19:22:08.383Z" }, + { url = "https://files.pythonhosted.org/packages/53/e0/0db84e0ebbad6e99e566c68e4b465784f2a2294f7719e8db9d509ef23087/mypy-2.0.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:a04e980b9275c76159da66c6e1723c7798306f9802b31bdaf9358d0c84030ce8", size = 15797362, upload-time = "2026-05-06T19:22:00.835Z" }, + { url = "https://files.pythonhosted.org/packages/0a/a4/14cc0768164dd53bec48aa41a20270b18df9bf72aa5054278bf133608315/mypy-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:33f9cf4825469b2bc73c53ba55f6d9a9b4cdb60f9e6e228745581520f29b8771", size = 14635914, upload-time = "2026-05-06T19:23:43.675Z" }, + { url = "https://files.pythonhosted.org/packages/08/48/d866a3e23b4dc5974c77d9cf65a435bf22de01a84dd4620917950e233960/mypy-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:191675c3c7dc2a5c7722a035a6909c277f14046c5e4e02aa5fbf65f8524f08ad", size = 15270866, upload-time = "2026-05-06T19:22:34.756Z" }, + { url = "https://files.pythonhosted.org/packages/71/eb/de9ef94958eb2078a6b908ceb247757dc384d3a238d3bd6ed7d81de5eaf8/mypy-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c3d26c4321a3b06fc9f04c741e0733af693f82d823f8e64e47b2e63b7f19fa84", size = 16093131, upload-time = "2026-05-06T19:23:56.541Z" }, + { url = "https://files.pythonhosted.org/packages/ad/07/0ab2c1a9d26e90942612724cbd5788f16b7810c5dd39bfcf79286c6c4524/mypy-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:bbcbc4d5917ca6ce12de70e051de7f533e3bf92d548b41a38a2232a6fe356525", size = 16330685, upload-time = "2026-05-06T19:21:42.037Z" }, + { url = "https://files.pythonhosted.org/packages/a6/8f/46f85d1371a5be642dad263828118ae1efd536d91d8bd2000c68acff3920/mypy-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:dbc6ba6d40572ae49268531565793a8f07eac7fc65ad76d482c9b4c8765b6043", size = 12752017, upload-time = "2026-05-06T19:22:44.002Z" }, + { url = "https://files.pythonhosted.org/packages/7a/e6/94ca48800cac19eb28a58188a768aaec0d16cac0f373915f073058ab0855/mypy-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:77926029dfcb7e1a3ecb0acb2ddbb24ca36be03f7d623e1759ad5376be8f6c01", size = 10527097, upload-time = "2026-05-06T19:20:58.973Z" }, + { url = "https://files.pythonhosted.org/packages/5c/14/fd0694aa594d6e9f9fd16ce821be2eff295197a273262ef56ddcc1388d68/mypy-2.0.0-py3-none-any.whl", hash = "sha256:8a92b2be3146b4fa1f062af7eb05574cbf3e6eb8e1f14704af1075423144e4e5", size = 2673434, upload-time = "2026-05-06T19:26:32.856Z" }, ] [[package]] @@ -747,13 +865,13 @@ name = "myst-parser" version = "5.0.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.12'", - "python_full_version == '3.11.*'", + "python_full_version >= '3.15'", + "python_full_version >= '3.11' and python_full_version < '3.15'", ] dependencies = [ { name = "docutils", marker = "python_full_version >= '3.11'" }, { name = "jinja2", marker = "python_full_version >= '3.11'" }, - { name = "markdown-it-py", version = "4.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "markdown-it-py", version = "4.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "mdit-py-plugins", marker = "python_full_version >= '3.11'" }, { name = "pyyaml", marker = "python_full_version >= '3.11'" }, { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, @@ -765,118 +883,118 @@ wheels = [ [[package]] name = "packaging" -version = "26.0" +version = "26.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, ] [[package]] name = "pathspec" -version = "1.0.4" +version = "1.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, + { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, ] [[package]] name = "pillow" -version = "12.1.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1f/42/5c74462b4fd957fcd7b13b04fb3205ff8349236ea74c7c375766d6c82288/pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4", size = 46980264, upload-time = "2026-02-11T04:23:07.146Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/30/5bd3d794762481f8c8ae9c80e7b76ecea73b916959eb587521358ef0b2f9/pillow-12.1.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1f1625b72740fdda5d77b4def688eb8fd6490975d06b909fd19f13f391e077e0", size = 5304099, upload-time = "2026-02-11T04:20:06.13Z" }, - { url = "https://files.pythonhosted.org/packages/bd/c1/aab9e8f3eeb4490180e357955e15c2ef74b31f64790ff356c06fb6cf6d84/pillow-12.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:178aa072084bd88ec759052feca8e56cbb14a60b39322b99a049e58090479713", size = 4657880, upload-time = "2026-02-11T04:20:09.291Z" }, - { url = "https://files.pythonhosted.org/packages/f1/0a/9879e30d56815ad529d3985aeff5af4964202425c27261a6ada10f7cbf53/pillow-12.1.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b66e95d05ba806247aaa1561f080abc7975daf715c30780ff92a20e4ec546e1b", size = 6222587, upload-time = "2026-02-11T04:20:10.82Z" }, - { url = "https://files.pythonhosted.org/packages/5a/5f/a1b72ff7139e4f89014e8d451442c74a774d5c43cd938fb0a9f878576b37/pillow-12.1.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:89c7e895002bbe49cdc5426150377cbbc04767d7547ed145473f496dfa40408b", size = 8027678, upload-time = "2026-02-11T04:20:12.455Z" }, - { url = "https://files.pythonhosted.org/packages/e2/c2/c7cb187dac79a3d22c3ebeae727abee01e077c8c7d930791dc592f335153/pillow-12.1.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a5cbdcddad0af3da87cb16b60d23648bc3b51967eb07223e9fed77a82b457c4", size = 6335777, upload-time = "2026-02-11T04:20:14.441Z" }, - { url = "https://files.pythonhosted.org/packages/0c/7b/f9b09a7804ec7336effb96c26d37c29d27225783dc1501b7d62dcef6ae25/pillow-12.1.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9f51079765661884a486727f0729d29054242f74b46186026582b4e4769918e4", size = 7027140, upload-time = "2026-02-11T04:20:16.387Z" }, - { url = "https://files.pythonhosted.org/packages/98/b2/2fa3c391550bd421b10849d1a2144c44abcd966daadd2f7c12e19ea988c4/pillow-12.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:99c1506ea77c11531d75e3a412832a13a71c7ebc8192ab9e4b2e355555920e3e", size = 6449855, upload-time = "2026-02-11T04:20:18.554Z" }, - { url = "https://files.pythonhosted.org/packages/96/ff/9caf4b5b950c669263c39e96c78c0d74a342c71c4f43fd031bb5cb7ceac9/pillow-12.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:36341d06738a9f66c8287cf8b876d24b18db9bd8740fa0672c74e259ad408cff", size = 7151329, upload-time = "2026-02-11T04:20:20.646Z" }, - { url = "https://files.pythonhosted.org/packages/7b/f8/4b24841f582704da675ca535935bccb32b00a6da1226820845fac4a71136/pillow-12.1.1-cp310-cp310-win32.whl", hash = "sha256:6c52f062424c523d6c4db85518774cc3d50f5539dd6eed32b8f6229b26f24d40", size = 6325574, upload-time = "2026-02-11T04:20:22.43Z" }, - { url = "https://files.pythonhosted.org/packages/f8/f9/9f6b01c0881d7036063aa6612ef04c0e2cad96be21325a1e92d0203f8e91/pillow-12.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:c6008de247150668a705a6338156efb92334113421ceecf7438a12c9a12dab23", size = 7032347, upload-time = "2026-02-11T04:20:23.932Z" }, - { url = "https://files.pythonhosted.org/packages/79/13/c7922edded3dcdaf10c59297540b72785620abc0538872c819915746757d/pillow-12.1.1-cp310-cp310-win_arm64.whl", hash = "sha256:1a9b0ee305220b392e1124a764ee4265bd063e54a751a6b62eff69992f457fa9", size = 2453457, upload-time = "2026-02-11T04:20:25.392Z" }, - { url = "https://files.pythonhosted.org/packages/2b/46/5da1ec4a5171ee7bf1a0efa064aba70ba3d6e0788ce3f5acd1375d23c8c0/pillow-12.1.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e879bb6cd5c73848ef3b2b48b8af9ff08c5b71ecda8048b7dd22d8a33f60be32", size = 5304084, upload-time = "2026-02-11T04:20:27.501Z" }, - { url = "https://files.pythonhosted.org/packages/78/93/a29e9bc02d1cf557a834da780ceccd54e02421627200696fcf805ebdc3fb/pillow-12.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:365b10bb9417dd4498c0e3b128018c4a624dc11c7b97d8cc54effe3b096f4c38", size = 4657866, upload-time = "2026-02-11T04:20:29.827Z" }, - { url = "https://files.pythonhosted.org/packages/13/84/583a4558d492a179d31e4aae32eadce94b9acf49c0337c4ce0b70e0a01f2/pillow-12.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d4ce8e329c93845720cd2014659ca67eac35f6433fd3050393d85f3ecef0dad5", size = 6232148, upload-time = "2026-02-11T04:20:31.329Z" }, - { url = "https://files.pythonhosted.org/packages/d5/e2/53c43334bbbb2d3b938978532fbda8e62bb6e0b23a26ce8592f36bcc4987/pillow-12.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc354a04072b765eccf2204f588a7a532c9511e8b9c7f900e1b64e3e33487090", size = 8038007, upload-time = "2026-02-11T04:20:34.225Z" }, - { url = "https://files.pythonhosted.org/packages/b8/a6/3d0e79c8a9d58150dd98e199d7c1c56861027f3829a3a60b3c2784190180/pillow-12.1.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e7976bf1910a8116b523b9f9f58bf410f3e8aa330cd9a2bb2953f9266ab49af", size = 6345418, upload-time = "2026-02-11T04:20:35.858Z" }, - { url = "https://files.pythonhosted.org/packages/a2/c8/46dfeac5825e600579157eea177be43e2f7ff4a99da9d0d0a49533509ac5/pillow-12.1.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:597bd9c8419bc7c6af5604e55847789b69123bbe25d65cc6ad3012b4f3c98d8b", size = 7034590, upload-time = "2026-02-11T04:20:37.91Z" }, - { url = "https://files.pythonhosted.org/packages/af/bf/e6f65d3db8a8bbfeaf9e13cc0417813f6319863a73de934f14b2229ada18/pillow-12.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2c1fc0f2ca5f96a3c8407e41cca26a16e46b21060fe6d5b099d2cb01412222f5", size = 6458655, upload-time = "2026-02-11T04:20:39.496Z" }, - { url = "https://files.pythonhosted.org/packages/f9/c2/66091f3f34a25894ca129362e510b956ef26f8fb67a0e6417bc5744e56f1/pillow-12.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:578510d88c6229d735855e1f278aa305270438d36a05031dfaae5067cc8eb04d", size = 7159286, upload-time = "2026-02-11T04:20:41.139Z" }, - { url = "https://files.pythonhosted.org/packages/7b/5a/24bc8eb526a22f957d0cec6243146744966d40857e3d8deb68f7902ca6c1/pillow-12.1.1-cp311-cp311-win32.whl", hash = "sha256:7311c0a0dcadb89b36b7025dfd8326ecfa36964e29913074d47382706e516a7c", size = 6328663, upload-time = "2026-02-11T04:20:43.184Z" }, - { url = "https://files.pythonhosted.org/packages/31/03/bef822e4f2d8f9d7448c133d0a18185d3cce3e70472774fffefe8b0ed562/pillow-12.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:fbfa2a7c10cc2623f412753cddf391c7f971c52ca40a3f65dc5039b2939e8563", size = 7031448, upload-time = "2026-02-11T04:20:44.696Z" }, - { url = "https://files.pythonhosted.org/packages/49/70/f76296f53610bd17b2e7d31728b8b7825e3ac3b5b3688b51f52eab7c0818/pillow-12.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:b81b5e3511211631b3f672a595e3221252c90af017e399056d0faabb9538aa80", size = 2453651, upload-time = "2026-02-11T04:20:46.243Z" }, - { url = "https://files.pythonhosted.org/packages/07/d3/8df65da0d4df36b094351dce696f2989bec731d4f10e743b1c5f4da4d3bf/pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052", size = 5262803, upload-time = "2026-02-11T04:20:47.653Z" }, - { url = "https://files.pythonhosted.org/packages/d6/71/5026395b290ff404b836e636f51d7297e6c83beceaa87c592718747e670f/pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984", size = 4657601, upload-time = "2026-02-11T04:20:49.328Z" }, - { url = "https://files.pythonhosted.org/packages/b1/2e/1001613d941c67442f745aff0f7cc66dd8df9a9c084eb497e6a543ee6f7e/pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79", size = 6234995, upload-time = "2026-02-11T04:20:51.032Z" }, - { url = "https://files.pythonhosted.org/packages/07/26/246ab11455b2549b9233dbd44d358d033a2f780fa9007b61a913c5b2d24e/pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293", size = 8045012, upload-time = "2026-02-11T04:20:52.882Z" }, - { url = "https://files.pythonhosted.org/packages/b2/8b/07587069c27be7535ac1fe33874e32de118fbd34e2a73b7f83436a88368c/pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397", size = 6349638, upload-time = "2026-02-11T04:20:54.444Z" }, - { url = "https://files.pythonhosted.org/packages/ff/79/6df7b2ee763d619cda2fb4fea498e5f79d984dae304d45a8999b80d6cf5c/pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0", size = 7041540, upload-time = "2026-02-11T04:20:55.97Z" }, - { url = "https://files.pythonhosted.org/packages/2c/5e/2ba19e7e7236d7529f4d873bdaf317a318896bac289abebd4bb00ef247f0/pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3", size = 6462613, upload-time = "2026-02-11T04:20:57.542Z" }, - { url = "https://files.pythonhosted.org/packages/03/03/31216ec124bb5c3dacd74ce8efff4cc7f52643653bad4825f8f08c697743/pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35", size = 7166745, upload-time = "2026-02-11T04:20:59.196Z" }, - { url = "https://files.pythonhosted.org/packages/1f/e7/7c4552d80052337eb28653b617eafdef39adfb137c49dd7e831b8dc13bc5/pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a", size = 6328823, upload-time = "2026-02-11T04:21:01.385Z" }, - { url = "https://files.pythonhosted.org/packages/3d/17/688626d192d7261bbbf98846fc98995726bddc2c945344b65bec3a29d731/pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6", size = 7033367, upload-time = "2026-02-11T04:21:03.536Z" }, - { url = "https://files.pythonhosted.org/packages/ed/fe/a0ef1f73f939b0eca03ee2c108d0043a87468664770612602c63266a43c4/pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523", size = 2453811, upload-time = "2026-02-11T04:21:05.116Z" }, - { url = "https://files.pythonhosted.org/packages/d5/11/6db24d4bd7685583caeae54b7009584e38da3c3d4488ed4cd25b439de486/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e", size = 4062689, upload-time = "2026-02-11T04:21:06.804Z" }, - { url = "https://files.pythonhosted.org/packages/33/c0/ce6d3b1fe190f0021203e0d9b5b99e57843e345f15f9ef22fcd43842fd21/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9", size = 4138535, upload-time = "2026-02-11T04:21:08.452Z" }, - { url = "https://files.pythonhosted.org/packages/a0/c6/d5eb6a4fb32a3f9c21a8c7613ec706534ea1cf9f4b3663e99f0d83f6fca8/pillow-12.1.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6", size = 3601364, upload-time = "2026-02-11T04:21:10.194Z" }, - { url = "https://files.pythonhosted.org/packages/14/a1/16c4b823838ba4c9c52c0e6bbda903a3fe5a1bdbf1b8eb4fff7156f3e318/pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60", size = 5262561, upload-time = "2026-02-11T04:21:11.742Z" }, - { url = "https://files.pythonhosted.org/packages/bb/ad/ad9dc98ff24f485008aa5cdedaf1a219876f6f6c42a4626c08bc4e80b120/pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2", size = 4657460, upload-time = "2026-02-11T04:21:13.786Z" }, - { url = "https://files.pythonhosted.org/packages/9e/1b/f1a4ea9a895b5732152789326202a82464d5254759fbacae4deea3069334/pillow-12.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5bddd742a44b7e6b1e773ab5db102bd7a94c32555ba656e76d319d19c3850", size = 6232698, upload-time = "2026-02-11T04:21:15.949Z" }, - { url = "https://files.pythonhosted.org/packages/95/f4/86f51b8745070daf21fd2e5b1fe0eb35d4db9ca26e6d58366562fb56a743/pillow-12.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc44ef1f3de4f45b50ccf9136999d71abb99dca7706bc75d222ed350b9fd2289", size = 8041706, upload-time = "2026-02-11T04:21:17.723Z" }, - { url = "https://files.pythonhosted.org/packages/29/9b/d6ecd956bb1266dd1045e995cce9b8d77759e740953a1c9aad9502a0461e/pillow-12.1.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a8eb7ed8d4198bccbd07058416eeec51686b498e784eda166395a23eb99138e", size = 6346621, upload-time = "2026-02-11T04:21:19.547Z" }, - { url = "https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717", size = 7038069, upload-time = "2026-02-11T04:21:21.378Z" }, - { url = "https://files.pythonhosted.org/packages/94/0e/58cb1a6bc48f746bc4cb3adb8cabff73e2742c92b3bf7a220b7cf69b9177/pillow-12.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:518a48c2aab7ce596d3bf79d0e275661b846e86e4d0e7dec34712c30fe07f02a", size = 6460040, upload-time = "2026-02-11T04:21:23.148Z" }, - { url = "https://files.pythonhosted.org/packages/6c/57/9045cb3ff11eeb6c1adce3b2d60d7d299d7b273a2e6c8381a524abfdc474/pillow-12.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a550ae29b95c6dc13cf69e2c9dc5747f814c54eeb2e32d683e5e93af56caa029", size = 7164523, upload-time = "2026-02-11T04:21:25.01Z" }, - { url = "https://files.pythonhosted.org/packages/73/f2/9be9cb99f2175f0d4dbadd6616ce1bf068ee54a28277ea1bf1fbf729c250/pillow-12.1.1-cp313-cp313-win32.whl", hash = "sha256:a003d7422449f6d1e3a34e3dd4110c22148336918ddbfc6a32581cd54b2e0b2b", size = 6332552, upload-time = "2026-02-11T04:21:27.238Z" }, - { url = "https://files.pythonhosted.org/packages/3f/eb/b0834ad8b583d7d9d42b80becff092082a1c3c156bb582590fcc973f1c7c/pillow-12.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1", size = 7040108, upload-time = "2026-02-11T04:21:29.462Z" }, - { url = "https://files.pythonhosted.org/packages/d5/7d/fc09634e2aabdd0feabaff4a32f4a7d97789223e7c2042fd805ea4b4d2c2/pillow-12.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c0dd1636633e7e6a0afe7bf6a51a14992b7f8e60de5789018ebbdfae55b040a", size = 2453712, upload-time = "2026-02-11T04:21:31.072Z" }, - { url = "https://files.pythonhosted.org/packages/19/2a/b9d62794fc8a0dd14c1943df68347badbd5511103e0d04c035ffe5cf2255/pillow-12.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0330d233c1a0ead844fc097a7d16c0abff4c12e856c0b325f231820fee1f39da", size = 5264880, upload-time = "2026-02-11T04:21:32.865Z" }, - { url = "https://files.pythonhosted.org/packages/26/9d/e03d857d1347fa5ed9247e123fcd2a97b6220e15e9cb73ca0a8d91702c6e/pillow-12.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dae5f21afb91322f2ff791895ddd8889e5e947ff59f71b46041c8ce6db790bc", size = 4660616, upload-time = "2026-02-11T04:21:34.97Z" }, - { url = "https://files.pythonhosted.org/packages/f7/ec/8a6d22afd02570d30954e043f09c32772bfe143ba9285e2fdb11284952cd/pillow-12.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e0c664be47252947d870ac0d327fea7e63985a08794758aa8af5b6cb6ec0c9c", size = 6269008, upload-time = "2026-02-11T04:21:36.623Z" }, - { url = "https://files.pythonhosted.org/packages/3d/1d/6d875422c9f28a4a361f495a5f68d9de4a66941dc2c619103ca335fa6446/pillow-12.1.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:691ab2ac363b8217f7d31b3497108fb1f50faab2f75dfb03284ec2f217e87bf8", size = 8073226, upload-time = "2026-02-11T04:21:38.585Z" }, - { url = "https://files.pythonhosted.org/packages/a1/cd/134b0b6ee5eda6dc09e25e24b40fdafe11a520bc725c1d0bbaa5e00bf95b/pillow-12.1.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9e8064fb1cc019296958595f6db671fba95209e3ceb0c4734c9baf97de04b20", size = 6380136, upload-time = "2026-02-11T04:21:40.562Z" }, - { url = "https://files.pythonhosted.org/packages/7a/a9/7628f013f18f001c1b98d8fffe3452f306a70dc6aba7d931019e0492f45e/pillow-12.1.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:472a8d7ded663e6162dafdf20015c486a7009483ca671cece7a9279b512fcb13", size = 7067129, upload-time = "2026-02-11T04:21:42.521Z" }, - { url = "https://files.pythonhosted.org/packages/1e/f8/66ab30a2193b277785601e82ee2d49f68ea575d9637e5e234faaa98efa4c/pillow-12.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:89b54027a766529136a06cfebeecb3a04900397a3590fd252160b888479517bf", size = 6491807, upload-time = "2026-02-11T04:21:44.22Z" }, - { url = "https://files.pythonhosted.org/packages/da/0b/a877a6627dc8318fdb84e357c5e1a758c0941ab1ddffdafd231983788579/pillow-12.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:86172b0831b82ce4f7877f280055892b31179e1576aa00d0df3bb1bbf8c3e524", size = 7190954, upload-time = "2026-02-11T04:21:46.114Z" }, - { url = "https://files.pythonhosted.org/packages/83/43/6f732ff85743cf746b1361b91665d9f5155e1483817f693f8d57ea93147f/pillow-12.1.1-cp313-cp313t-win32.whl", hash = "sha256:44ce27545b6efcf0fdbdceb31c9a5bdea9333e664cda58a7e674bb74608b3986", size = 6336441, upload-time = "2026-02-11T04:21:48.22Z" }, - { url = "https://files.pythonhosted.org/packages/3b/44/e865ef3986611bb75bfabdf94a590016ea327833f434558801122979cd0e/pillow-12.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a285e3eb7a5a45a2ff504e31f4a8d1b12ef62e84e5411c6804a42197c1cf586c", size = 7045383, upload-time = "2026-02-11T04:21:50.015Z" }, - { url = "https://files.pythonhosted.org/packages/a8/c6/f4fb24268d0c6908b9f04143697ea18b0379490cb74ba9e8d41b898bd005/pillow-12.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cc7d296b5ea4d29e6570dabeaed58d31c3fea35a633a69679fb03d7664f43fb3", size = 2456104, upload-time = "2026-02-11T04:21:51.633Z" }, - { url = "https://files.pythonhosted.org/packages/03/d0/bebb3ffbf31c5a8e97241476c4cf8b9828954693ce6744b4a2326af3e16b/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af", size = 4062652, upload-time = "2026-02-11T04:21:53.19Z" }, - { url = "https://files.pythonhosted.org/packages/2d/c0/0e16fb0addda4851445c28f8350d8c512f09de27bbb0d6d0bbf8b6709605/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f", size = 4138823, upload-time = "2026-02-11T04:22:03.088Z" }, - { url = "https://files.pythonhosted.org/packages/6b/fb/6170ec655d6f6bb6630a013dd7cf7bc218423d7b5fa9071bf63dc32175ae/pillow-12.1.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642", size = 3601143, upload-time = "2026-02-11T04:22:04.909Z" }, - { url = "https://files.pythonhosted.org/packages/59/04/dc5c3f297510ba9a6837cbb318b87dd2b8f73eb41a43cc63767f65cb599c/pillow-12.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd", size = 5266254, upload-time = "2026-02-11T04:22:07.656Z" }, - { url = "https://files.pythonhosted.org/packages/05/30/5db1236b0d6313f03ebf97f5e17cda9ca060f524b2fcc875149a8360b21c/pillow-12.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202", size = 4657499, upload-time = "2026-02-11T04:22:09.613Z" }, - { url = "https://files.pythonhosted.org/packages/6f/18/008d2ca0eb612e81968e8be0bbae5051efba24d52debf930126d7eaacbba/pillow-12.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f", size = 6232137, upload-time = "2026-02-11T04:22:11.434Z" }, - { url = "https://files.pythonhosted.org/packages/70/f1/f14d5b8eeb4b2cd62b9f9f847eb6605f103df89ef619ac68f92f748614ea/pillow-12.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f", size = 8042721, upload-time = "2026-02-11T04:22:13.321Z" }, - { url = "https://files.pythonhosted.org/packages/5a/d6/17824509146e4babbdabf04d8171491fa9d776f7061ff6e727522df9bd03/pillow-12.1.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f", size = 6347798, upload-time = "2026-02-11T04:22:15.449Z" }, - { url = "https://files.pythonhosted.org/packages/d1/ee/c85a38a9ab92037a75615aba572c85ea51e605265036e00c5b67dfafbfe2/pillow-12.1.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e", size = 7039315, upload-time = "2026-02-11T04:22:17.24Z" }, - { url = "https://files.pythonhosted.org/packages/ec/f3/bc8ccc6e08a148290d7523bde4d9a0d6c981db34631390dc6e6ec34cacf6/pillow-12.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0", size = 6462360, upload-time = "2026-02-11T04:22:19.111Z" }, - { url = "https://files.pythonhosted.org/packages/f6/ab/69a42656adb1d0665ab051eec58a41f169ad295cf81ad45406963105408f/pillow-12.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb", size = 7165438, upload-time = "2026-02-11T04:22:21.041Z" }, - { url = "https://files.pythonhosted.org/packages/02/46/81f7aa8941873f0f01d4b55cc543b0a3d03ec2ee30d617a0448bf6bd6dec/pillow-12.1.1-cp314-cp314-win32.whl", hash = "sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f", size = 6431503, upload-time = "2026-02-11T04:22:22.833Z" }, - { url = "https://files.pythonhosted.org/packages/40/72/4c245f7d1044b67affc7f134a09ea619d4895333d35322b775b928180044/pillow-12.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15", size = 7176748, upload-time = "2026-02-11T04:22:24.64Z" }, - { url = "https://files.pythonhosted.org/packages/e4/ad/8a87bdbe038c5c698736e3348af5c2194ffb872ea52f11894c95f9305435/pillow-12.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f", size = 2544314, upload-time = "2026-02-11T04:22:26.685Z" }, - { url = "https://files.pythonhosted.org/packages/6c/9d/efd18493f9de13b87ede7c47e69184b9e859e4427225ea962e32e56a49bc/pillow-12.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8", size = 5268612, upload-time = "2026-02-11T04:22:29.884Z" }, - { url = "https://files.pythonhosted.org/packages/f8/f1/4f42eb2b388eb2ffc660dcb7f7b556c1015c53ebd5f7f754965ef997585b/pillow-12.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9", size = 4660567, upload-time = "2026-02-11T04:22:31.799Z" }, - { url = "https://files.pythonhosted.org/packages/01/54/df6ef130fa43e4b82e32624a7b821a2be1c5653a5fdad8469687a7db4e00/pillow-12.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60", size = 6269951, upload-time = "2026-02-11T04:22:33.921Z" }, - { url = "https://files.pythonhosted.org/packages/a9/48/618752d06cc44bb4aae8ce0cd4e6426871929ed7b46215638088270d9b34/pillow-12.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7", size = 8074769, upload-time = "2026-02-11T04:22:35.877Z" }, - { url = "https://files.pythonhosted.org/packages/c3/bd/f1d71eb39a72fa088d938655afba3e00b38018d052752f435838961127d8/pillow-12.1.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f", size = 6381358, upload-time = "2026-02-11T04:22:37.698Z" }, - { url = "https://files.pythonhosted.org/packages/64/ef/c784e20b96674ed36a5af839305f55616f8b4f8aa8eeccf8531a6e312243/pillow-12.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586", size = 7068558, upload-time = "2026-02-11T04:22:39.597Z" }, - { url = "https://files.pythonhosted.org/packages/73/cb/8059688b74422ae61278202c4e1ad992e8a2e7375227be0a21c6b87ca8d5/pillow-12.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce", size = 6493028, upload-time = "2026-02-11T04:22:42.73Z" }, - { url = "https://files.pythonhosted.org/packages/c6/da/e3c008ed7d2dd1f905b15949325934510b9d1931e5df999bb15972756818/pillow-12.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8", size = 7191940, upload-time = "2026-02-11T04:22:44.543Z" }, - { url = "https://files.pythonhosted.org/packages/01/4a/9202e8d11714c1fc5951f2e1ef362f2d7fbc595e1f6717971d5dd750e969/pillow-12.1.1-cp314-cp314t-win32.whl", hash = "sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36", size = 6438736, upload-time = "2026-02-11T04:22:46.347Z" }, - { url = "https://files.pythonhosted.org/packages/f3/ca/cbce2327eb9885476b3957b2e82eb12c866a8b16ad77392864ad601022ce/pillow-12.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b", size = 7182894, upload-time = "2026-02-11T04:22:48.114Z" }, - { url = "https://files.pythonhosted.org/packages/ec/d2/de599c95ba0a973b94410477f8bf0b6f0b5e67360eb89bcb1ad365258beb/pillow-12.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334", size = 2546446, upload-time = "2026-02-11T04:22:50.342Z" }, - { url = "https://files.pythonhosted.org/packages/56/11/5d43209aa4cb58e0cc80127956ff1796a68b928e6324bbf06ef4db34367b/pillow-12.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:600fd103672b925fe62ed08e0d874ea34d692474df6f4bf7ebe148b30f89f39f", size = 5228606, upload-time = "2026-02-11T04:22:52.106Z" }, - { url = "https://files.pythonhosted.org/packages/5f/d5/3b005b4e4fda6698b371fa6c21b097d4707585d7db99e98d9b0b87ac612a/pillow-12.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:665e1b916b043cef294bc54d47bf02d87e13f769bc4bc5fa225a24b3a6c5aca9", size = 4622321, upload-time = "2026-02-11T04:22:53.827Z" }, - { url = "https://files.pythonhosted.org/packages/df/36/ed3ea2d594356fd8037e5a01f6156c74bc8d92dbb0fa60746cc96cabb6e8/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:495c302af3aad1ca67420ddd5c7bd480c8867ad173528767d906428057a11f0e", size = 5247579, upload-time = "2026-02-11T04:22:56.094Z" }, - { url = "https://files.pythonhosted.org/packages/54/9a/9cc3e029683cf6d20ae5085da0dafc63148e3252c2f13328e553aaa13cfb/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8fd420ef0c52c88b5a035a0886f367748c72147b2b8f384c9d12656678dfdfa9", size = 6989094, upload-time = "2026-02-11T04:22:58.288Z" }, - { url = "https://files.pythonhosted.org/packages/00/98/fc53ab36da80b88df0967896b6c4b4cd948a0dc5aa40a754266aa3ae48b3/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f975aa7ef9684ce7e2c18a3aa8f8e2106ce1e46b94ab713d156b2898811651d3", size = 5313850, upload-time = "2026-02-11T04:23:00.554Z" }, - { url = "https://files.pythonhosted.org/packages/30/02/00fa585abfd9fe9d73e5f6e554dc36cc2b842898cbfc46d70353dae227f8/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8089c852a56c2966cf18835db62d9b34fef7ba74c726ad943928d494fa7f4735", size = 5963343, upload-time = "2026-02-11T04:23:02.934Z" }, - { url = "https://files.pythonhosted.org/packages/f2/26/c56ce33ca856e358d27fda9676c055395abddb82c35ac0f593877ed4562e/pillow-12.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:cb9bb857b2d057c6dfc72ac5f3b44836924ba15721882ef103cecb40d002d80e", size = 7029880, upload-time = "2026-02-11T04:23:04.783Z" }, +version = "12.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/21/c2bcdd5906101a30244eaffc1b6e6ce71a31bd0742a01eb89e660ebfac2d/pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5", size = 46987819, upload-time = "2026-04-01T14:46:17.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/aa/d0b28e1c811cd4d5f5c2bfe2e022292bd255ae5744a3b9ac7d6c8f72dd75/pillow-12.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:a4e8f36e677d3336f35089648c8955c51c6d386a13cf6ee9c189c5f5bd713a9f", size = 5354355, upload-time = "2026-04-01T14:42:15.402Z" }, + { url = "https://files.pythonhosted.org/packages/27/8e/1d5b39b8ae2bd7650d0c7b6abb9602d16043ead9ebbfef4bc4047454da2a/pillow-12.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e589959f10d9824d39b350472b92f0ce3b443c0a3442ebf41c40cb8361c5b97", size = 4695871, upload-time = "2026-04-01T14:42:18.234Z" }, + { url = "https://files.pythonhosted.org/packages/f0/c5/dcb7a6ca6b7d3be41a76958e90018d56c8462166b3ef223150360850c8da/pillow-12.2.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a52edc8bfff4429aaabdf4d9ee0daadbbf8562364f940937b941f87a4290f5ff", size = 6269734, upload-time = "2026-04-01T14:42:20.608Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f1/aa1bb13b2f4eba914e9637893c73f2af8e48d7d4023b9d3750d4c5eb2d0c/pillow-12.2.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:975385f4776fafde056abb318f612ef6285b10a1f12b8570f3647ad0d74b48ec", size = 8076080, upload-time = "2026-04-01T14:42:23.095Z" }, + { url = "https://files.pythonhosted.org/packages/a1/2a/8c79d6a53169937784604a8ae8d77e45888c41537f7f6f65ed1f407fe66d/pillow-12.2.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd9c0c7a0c681a347b3194c500cb1e6ca9cab053ea4d82a5cf45b6b754560136", size = 6382236, upload-time = "2026-04-01T14:42:25.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/42/bbcb6051030e1e421d103ce7a8ecadf837aa2f39b8f82ef1a8d37c3d4ebc/pillow-12.2.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:88d387ff40b3ff7c274947ed3125dedf5262ec6919d83946753b5f3d7c67ea4c", size = 7070220, upload-time = "2026-04-01T14:42:28.68Z" }, + { url = "https://files.pythonhosted.org/packages/3f/e1/c2a7d6dd8cfa6b231227da096fd2d58754bab3603b9d73bf609d3c18b64f/pillow-12.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:51c4167c34b0d8ba05b547a3bb23578d0ba17b80a5593f93bd8ecb123dd336a3", size = 6493124, upload-time = "2026-04-01T14:42:31.579Z" }, + { url = "https://files.pythonhosted.org/packages/5f/41/7c8617da5d32e1d2f026e509484fdb6f3ad7efaef1749a0c1928adbb099e/pillow-12.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:34c0d99ecccea270c04882cb3b86e7b57296079c9a4aff88cb3b33563d95afaa", size = 7194324, upload-time = "2026-04-01T14:42:34.615Z" }, + { url = "https://files.pythonhosted.org/packages/2d/de/a777627e19fd6d62f84070ee1521adde5eeda4855b5cf60fe0b149118bca/pillow-12.2.0-cp310-cp310-win32.whl", hash = "sha256:b85f66ae9eb53e860a873b858b789217ba505e5e405a24b85c0464822fe88032", size = 6376363, upload-time = "2026-04-01T14:42:37.19Z" }, + { url = "https://files.pythonhosted.org/packages/e7/34/fc4cb5204896465842767b96d250c08410f01f2f28afc43b257de842eed5/pillow-12.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:673aa32138f3e7531ccdbca7b3901dba9b70940a19ccecc6a37c77d5fdeb05b5", size = 7083523, upload-time = "2026-04-01T14:42:39.62Z" }, + { url = "https://files.pythonhosted.org/packages/2d/a0/32852d36bc7709f14dc3f64f929a275e958ad8c19a6deba9610d458e28b3/pillow-12.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:3e080565d8d7c671db5802eedfb438e5565ffa40115216eabb8cd52d0ecce024", size = 2463318, upload-time = "2026-04-01T14:42:42.063Z" }, + { url = "https://files.pythonhosted.org/packages/68/e1/748f5663efe6edcfc4e74b2b93edfb9b8b99b67f21a854c3ae416500a2d9/pillow-12.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:8be29e59487a79f173507c30ddf57e733a357f67881430449bb32614075a40ab", size = 5354347, upload-time = "2026-04-01T14:42:44.255Z" }, + { url = "https://files.pythonhosted.org/packages/47/a1/d5ff69e747374c33a3b53b9f98cca7889fce1fd03d79cdc4e1bccc6c5a87/pillow-12.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71cde9a1e1551df7d34a25462fc60325e8a11a82cc2e2f54578e5e9a1e153d65", size = 4695873, upload-time = "2026-04-01T14:42:46.452Z" }, + { url = "https://files.pythonhosted.org/packages/df/21/e3fbdf54408a973c7f7f89a23b2cb97a7ef30c61ab4142af31eee6aebc88/pillow-12.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f490f9368b6fc026f021db16d7ec2fbf7d89e2edb42e8ec09d2c60505f5729c7", size = 6280168, upload-time = "2026-04-01T14:42:49.228Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f1/00b7278c7dd52b17ad4329153748f87b6756ec195ff786c2bdf12518337d/pillow-12.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8bd7903a5f2a4545f6fd5935c90058b89d30045568985a71c79f5fd6edf9b91e", size = 8088188, upload-time = "2026-04-01T14:42:51.735Z" }, + { url = "https://files.pythonhosted.org/packages/ad/cf/220a5994ef1b10e70e85748b75649d77d506499352be135a4989c957b701/pillow-12.2.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3997232e10d2920a68d25191392e3a4487d8183039e1c74c2297f00ed1c50705", size = 6394401, upload-time = "2026-04-01T14:42:54.343Z" }, + { url = "https://files.pythonhosted.org/packages/e9/bd/e51a61b1054f09437acfbc2ff9106c30d1eb76bc1453d428399946781253/pillow-12.2.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e74473c875d78b8e9d5da2a70f7099549f9eb37ded4e2f6a463e60125bccd176", size = 7079655, upload-time = "2026-04-01T14:42:56.954Z" }, + { url = "https://files.pythonhosted.org/packages/6b/3d/45132c57d5fb4b5744567c3817026480ac7fc3ce5d4c47902bc0e7f6f853/pillow-12.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:56a3f9c60a13133a98ecff6197af34d7824de9b7b38c3654861a725c970c197b", size = 6503105, upload-time = "2026-04-01T14:42:59.847Z" }, + { url = "https://files.pythonhosted.org/packages/7d/2e/9df2fc1e82097b1df3dce58dc43286aa01068e918c07574711fcc53e6fb4/pillow-12.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:90e6f81de50ad6b534cab6e5aef77ff6e37722b2f5d908686f4a5c9eba17a909", size = 7203402, upload-time = "2026-04-01T14:43:02.664Z" }, + { url = "https://files.pythonhosted.org/packages/bd/2e/2941e42858ebb67e50ae741473de81c2984e6eff7b397017623c676e2e8d/pillow-12.2.0-cp311-cp311-win32.whl", hash = "sha256:8c984051042858021a54926eb597d6ee3012393ce9c181814115df4c60b9a808", size = 6378149, upload-time = "2026-04-01T14:43:05.274Z" }, + { url = "https://files.pythonhosted.org/packages/69/42/836b6f3cd7f3e5fa10a1f1a5420447c17966044c8fbf589cc0452d5502db/pillow-12.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e6b2a0c538fc200b38ff9eb6628228b77908c319a005815f2dde585a0664b60", size = 7082626, upload-time = "2026-04-01T14:43:08.557Z" }, + { url = "https://files.pythonhosted.org/packages/c2/88/549194b5d6f1f494b485e493edc6693c0a16f4ada488e5bd974ed1f42fad/pillow-12.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:9a8a34cc89c67a65ea7437ce257cea81a9dad65b29805f3ecee8c8fe8ff25ffe", size = 2463531, upload-time = "2026-04-01T14:43:10.743Z" }, + { url = "https://files.pythonhosted.org/packages/58/be/7482c8a5ebebbc6470b3eb791812fff7d5e0216c2be3827b30b8bb6603ed/pillow-12.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d192a155bbcec180f8564f693e6fd9bccff5a7af9b32e2e4bf8c9c69dbad6b5", size = 5308279, upload-time = "2026-04-01T14:43:13.246Z" }, + { url = "https://files.pythonhosted.org/packages/d8/95/0a351b9289c2b5cbde0bacd4a83ebc44023e835490a727b2a3bd60ddc0f4/pillow-12.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3f40b3c5a968281fd507d519e444c35f0ff171237f4fdde090dd60699458421", size = 4695490, upload-time = "2026-04-01T14:43:15.584Z" }, + { url = "https://files.pythonhosted.org/packages/de/af/4e8e6869cbed569d43c416fad3dc4ecb944cb5d9492defaed89ddd6fe871/pillow-12.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:03e7e372d5240cc23e9f07deca4d775c0817bffc641b01e9c3af208dbd300987", size = 6284462, upload-time = "2026-04-01T14:43:18.268Z" }, + { url = "https://files.pythonhosted.org/packages/e9/9e/c05e19657fd57841e476be1ab46c4d501bffbadbafdc31a6d665f8b737b6/pillow-12.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b86024e52a1b269467a802258c25521e6d742349d760728092e1bc2d135b4d76", size = 8094744, upload-time = "2026-04-01T14:43:20.716Z" }, + { url = "https://files.pythonhosted.org/packages/2b/54/1789c455ed10176066b6e7e6da1b01e50e36f94ba584dc68d9eebfe9156d/pillow-12.2.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7371b48c4fa448d20d2714c9a1f775a81155050d383333e0a6c15b1123dda005", size = 6398371, upload-time = "2026-04-01T14:43:23.443Z" }, + { url = "https://files.pythonhosted.org/packages/43/e3/fdc657359e919462369869f1c9f0e973f353f9a9ee295a39b1fea8ee1a77/pillow-12.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62f5409336adb0663b7caa0da5c7d9e7bdbaae9ce761d34669420c2a801b2780", size = 7087215, upload-time = "2026-04-01T14:43:26.758Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f8/2f6825e441d5b1959d2ca5adec984210f1ec086435b0ed5f52c19b3b8a6e/pillow-12.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:01afa7cf67f74f09523699b4e88c73fb55c13346d212a59a2db1f86b0a63e8c5", size = 6509783, upload-time = "2026-04-01T14:43:29.56Z" }, + { url = "https://files.pythonhosted.org/packages/67/f9/029a27095ad20f854f9dba026b3ea6428548316e057e6fc3545409e86651/pillow-12.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc3d34d4a8fbec3e88a79b92e5465e0f9b842b628675850d860b8bd300b159f5", size = 7212112, upload-time = "2026-04-01T14:43:32.091Z" }, + { url = "https://files.pythonhosted.org/packages/be/42/025cfe05d1be22dbfdb4f264fe9de1ccda83f66e4fc3aac94748e784af04/pillow-12.2.0-cp312-cp312-win32.whl", hash = "sha256:58f62cc0f00fd29e64b29f4fd923ffdb3859c9f9e6105bfc37ba1d08994e8940", size = 6378489, upload-time = "2026-04-01T14:43:34.601Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7b/25a221d2c761c6a8ae21bfa3874988ff2583e19cf8a27bf2fee358df7942/pillow-12.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f84204dee22a783350679a0333981df803dac21a0190d706a50475e361c93f5", size = 7084129, upload-time = "2026-04-01T14:43:37.213Z" }, + { url = "https://files.pythonhosted.org/packages/10/e1/542a474affab20fd4a0f1836cb234e8493519da6b76899e30bcc5d990b8b/pillow-12.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:af73337013e0b3b46f175e79492d96845b16126ddf79c438d7ea7ff27783a414", size = 2463612, upload-time = "2026-04-01T14:43:39.421Z" }, + { url = "https://files.pythonhosted.org/packages/4a/01/53d10cf0dbad820a8db274d259a37ba50b88b24768ddccec07355382d5ad/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8297651f5b5679c19968abefd6bb84d95fe30ef712eb1b2d9b2d31ca61267f4c", size = 4100837, upload-time = "2026-04-01T14:43:41.506Z" }, + { url = "https://files.pythonhosted.org/packages/0f/98/f3a6657ecb698c937f6c76ee564882945f29b79bad496abcba0e84659ec5/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:50d8520da2a6ce0af445fa6d648c4273c3eeefbc32d7ce049f22e8b5c3daecc2", size = 4176528, upload-time = "2026-04-01T14:43:43.773Z" }, + { url = "https://files.pythonhosted.org/packages/69/bc/8986948f05e3ea490b8442ea1c1d4d990b24a7e43d8a51b2c7d8b1dced36/pillow-12.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:766cef22385fa1091258ad7e6216792b156dc16d8d3fa607e7545b2b72061f1c", size = 3640401, upload-time = "2026-04-01T14:43:45.87Z" }, + { url = "https://files.pythonhosted.org/packages/34/46/6c717baadcd62bc8ed51d238d521ab651eaa74838291bda1f86fe1f864c9/pillow-12.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5d2fd0fa6b5d9d1de415060363433f28da8b1526c1c129020435e186794b3795", size = 5308094, upload-time = "2026-04-01T14:43:48.438Z" }, + { url = "https://files.pythonhosted.org/packages/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f", size = 4695402, upload-time = "2026-04-01T14:43:51.292Z" }, + { url = "https://files.pythonhosted.org/packages/73/dd/42107efcb777b16fa0393317eac58f5b5cf30e8392e266e76e51cff28c3d/pillow-12.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f1c943e96e85df3d3478f7b691f229887e143f81fedab9b20205349ab04d73ed", size = 6280005, upload-time = "2026-04-01T14:43:54.242Z" }, + { url = "https://files.pythonhosted.org/packages/a8/68/b93e09e5e8549019e61acf49f65b1a8530765a7f812c77a7461bca7e4494/pillow-12.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03f6fab9219220f041c74aeaa2939ff0062bd5c364ba9ce037197f4c6d498cd9", size = 8090669, upload-time = "2026-04-01T14:43:57.335Z" }, + { url = "https://files.pythonhosted.org/packages/4b/6e/3ccb54ce8ec4ddd1accd2d89004308b7b0b21c4ac3d20fa70af4760a4330/pillow-12.2.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdfebd752ec52bf5bb4e35d9c64b40826bc5b40a13df7c3cda20a2c03a0f5ed", size = 6395194, upload-time = "2026-04-01T14:43:59.864Z" }, + { url = "https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3", size = 7082423, upload-time = "2026-04-01T14:44:02.74Z" }, + { url = "https://files.pythonhosted.org/packages/78/5f/e9f86ab0146464e8c133fe85df987ed9e77e08b29d8d35f9f9f4d6f917ba/pillow-12.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:00a2865911330191c0b818c59103b58a5e697cae67042366970a6b6f1b20b7f9", size = 6505667, upload-time = "2026-04-01T14:44:05.381Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1e/409007f56a2fdce61584fd3acbc2bbc259857d555196cedcadc68c015c82/pillow-12.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e1757442ed87f4912397c6d35a0db6a7b52592156014706f17658ff58bbf795", size = 7208580, upload-time = "2026-04-01T14:44:08.39Z" }, + { url = "https://files.pythonhosted.org/packages/23/c4/7349421080b12fb35414607b8871e9534546c128a11965fd4a7002ccfbee/pillow-12.2.0-cp313-cp313-win32.whl", hash = "sha256:144748b3af2d1b358d41286056d0003f47cb339b8c43a9ea42f5fea4d8c66b6e", size = 6375896, upload-time = "2026-04-01T14:44:11.197Z" }, + { url = "https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b", size = 7081266, upload-time = "2026-04-01T14:44:13.947Z" }, + { url = "https://files.pythonhosted.org/packages/c3/25/f968f618a062574294592f668218f8af564830ccebdd1fa6200f598e65c5/pillow-12.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:8023abc91fba39036dbce14a7d6535632f99c0b857807cbbbf21ecc9f4717f06", size = 2463508, upload-time = "2026-04-01T14:44:16.312Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a4/b342930964e3cb4dce5038ae34b0eab4653334995336cd486c5a8c25a00c/pillow-12.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:042db20a421b9bafecc4b84a8b6e444686bd9d836c7fd24542db3e7df7baad9b", size = 5309927, upload-time = "2026-04-01T14:44:18.89Z" }, + { url = "https://files.pythonhosted.org/packages/9f/de/23198e0a65a9cf06123f5435a5d95cea62a635697f8f03d134d3f3a96151/pillow-12.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd025009355c926a84a612fecf58bb315a3f6814b17ead51a8e48d3823d9087f", size = 4698624, upload-time = "2026-04-01T14:44:21.115Z" }, + { url = "https://files.pythonhosted.org/packages/01/a6/1265e977f17d93ea37aa28aa81bad4fa597933879fac2520d24e021c8da3/pillow-12.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88ddbc66737e277852913bd1e07c150cc7bb124539f94c4e2df5344494e0a612", size = 6321252, upload-time = "2026-04-01T14:44:23.663Z" }, + { url = "https://files.pythonhosted.org/packages/3c/83/5982eb4a285967baa70340320be9f88e57665a387e3a53a7f0db8231a0cd/pillow-12.2.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d362d1878f00c142b7e1a16e6e5e780f02be8195123f164edf7eddd911eefe7c", size = 8126550, upload-time = "2026-04-01T14:44:26.772Z" }, + { url = "https://files.pythonhosted.org/packages/4e/48/6ffc514adce69f6050d0753b1a18fd920fce8cac87620d5a31231b04bfc5/pillow-12.2.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c727a6d53cb0018aadd8018c2b938376af27914a68a492f59dfcaca650d5eea", size = 6433114, upload-time = "2026-04-01T14:44:29.615Z" }, + { url = "https://files.pythonhosted.org/packages/36/a3/f9a77144231fb8d40ee27107b4463e205fa4677e2ca2548e14da5cf18dce/pillow-12.2.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd8c21c98c5cc60653bcb311bef2ce0401642b7ce9d09e03a7da87c878289d4", size = 7115667, upload-time = "2026-04-01T14:44:32.773Z" }, + { url = "https://files.pythonhosted.org/packages/c1/fc/ac4ee3041e7d5a565e1c4fd72a113f03b6394cc72ab7089d27608f8aaccb/pillow-12.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f08483a632889536b8139663db60f6724bfcb443c96f1b18855860d7d5c0fd4", size = 6538966, upload-time = "2026-04-01T14:44:35.252Z" }, + { url = "https://files.pythonhosted.org/packages/c0/a8/27fb307055087f3668f6d0a8ccb636e7431d56ed0750e07a60547b1e083e/pillow-12.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dac8d77255a37e81a2efcbd1fc05f1c15ee82200e6c240d7e127e25e365c39ea", size = 7238241, upload-time = "2026-04-01T14:44:37.875Z" }, + { url = "https://files.pythonhosted.org/packages/ad/4b/926ab182c07fccae9fcb120043464e1ff1564775ec8864f21a0ebce6ac25/pillow-12.2.0-cp313-cp313t-win32.whl", hash = "sha256:ee3120ae9dff32f121610bb08e4313be87e03efeadfc6c0d18f89127e24d0c24", size = 6379592, upload-time = "2026-04-01T14:44:40.336Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c4/f9e476451a098181b30050cc4c9a3556b64c02cf6497ea421ac047e89e4b/pillow-12.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:325ca0528c6788d2a6c3d40e3568639398137346c3d6e66bb61db96b96511c98", size = 7085542, upload-time = "2026-04-01T14:44:43.251Z" }, + { url = "https://files.pythonhosted.org/packages/00/a4/285f12aeacbe2d6dc36c407dfbbe9e96d4a80b0fb710a337f6d2ad978c75/pillow-12.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453", size = 2465765, upload-time = "2026-04-01T14:44:45.996Z" }, + { url = "https://files.pythonhosted.org/packages/bf/98/4595daa2365416a86cb0d495248a393dfc84e96d62ad080c8546256cb9c0/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8", size = 4100848, upload-time = "2026-04-01T14:44:48.48Z" }, + { url = "https://files.pythonhosted.org/packages/0b/79/40184d464cf89f6663e18dfcf7ca21aae2491fff1a16127681bf1fa9b8cf/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b", size = 4176515, upload-time = "2026-04-01T14:44:51.353Z" }, + { url = "https://files.pythonhosted.org/packages/b0/63/703f86fd4c422a9cf722833670f4f71418fb116b2853ff7da722ea43f184/pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295", size = 3640159, upload-time = "2026-04-01T14:44:53.588Z" }, + { url = "https://files.pythonhosted.org/packages/71/e0/fb22f797187d0be2270f83500aab851536101b254bfa1eae10795709d283/pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed", size = 5312185, upload-time = "2026-04-01T14:44:56.039Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae", size = 4695386, upload-time = "2026-04-01T14:44:58.663Z" }, + { url = "https://files.pythonhosted.org/packages/70/62/98f6b7f0c88b9addd0e87c217ded307b36be024d4ff8869a812b241d1345/pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601", size = 6280384, upload-time = "2026-04-01T14:45:01.5Z" }, + { url = "https://files.pythonhosted.org/packages/5e/03/688747d2e91cfbe0e64f316cd2e8005698f76ada3130d0194664174fa5de/pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be", size = 8091599, upload-time = "2026-04-01T14:45:04.5Z" }, + { url = "https://files.pythonhosted.org/packages/f6/35/577e22b936fcdd66537329b33af0b4ccfefaeabd8aec04b266528cddb33c/pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f", size = 6396021, upload-time = "2026-04-01T14:45:07.117Z" }, + { url = "https://files.pythonhosted.org/packages/11/8d/d2532ad2a603ca2b93ad9f5135732124e57811d0168155852f37fbce2458/pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286", size = 7083360, upload-time = "2026-04-01T14:45:09.763Z" }, + { url = "https://files.pythonhosted.org/packages/5e/26/d325f9f56c7e039034897e7380e9cc202b1e368bfd04d4cbe6a441f02885/pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50", size = 6507628, upload-time = "2026-04-01T14:45:12.378Z" }, + { url = "https://files.pythonhosted.org/packages/5f/f7/769d5632ffb0988f1c5e7660b3e731e30f7f8ec4318e94d0a5d674eb65a4/pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104", size = 7209321, upload-time = "2026-04-01T14:45:15.122Z" }, + { url = "https://files.pythonhosted.org/packages/6a/7a/c253e3c645cd47f1aceea6a8bacdba9991bf45bb7dfe927f7c893e89c93c/pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7", size = 6479723, upload-time = "2026-04-01T14:45:17.797Z" }, + { url = "https://files.pythonhosted.org/packages/cd/8b/601e6566b957ca50e28725cb6c355c59c2c8609751efbecd980db44e0349/pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150", size = 7217400, upload-time = "2026-04-01T14:45:20.529Z" }, + { url = "https://files.pythonhosted.org/packages/d6/94/220e46c73065c3e2951bb91c11a1fb636c8c9ad427ac3ce7d7f3359b9b2f/pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1", size = 2554835, upload-time = "2026-04-01T14:45:23.162Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ab/1b426a3974cb0e7da5c29ccff4807871d48110933a57207b5a676cccc155/pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463", size = 5314225, upload-time = "2026-04-01T14:45:25.637Z" }, + { url = "https://files.pythonhosted.org/packages/19/1e/dce46f371be2438eecfee2a1960ee2a243bbe5e961890146d2dee1ff0f12/pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3", size = 4698541, upload-time = "2026-04-01T14:45:28.355Z" }, + { url = "https://files.pythonhosted.org/packages/55/c3/7fbecf70adb3a0c33b77a300dc52e424dc22ad8cdc06557a2e49523b703d/pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166", size = 6322251, upload-time = "2026-04-01T14:45:30.924Z" }, + { url = "https://files.pythonhosted.org/packages/1c/3c/7fbc17cfb7e4fe0ef1642e0abc17fc6c94c9f7a16be41498e12e2ba60408/pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe", size = 8127807, upload-time = "2026-04-01T14:45:33.908Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c3/a8ae14d6defd2e448493ff512fae903b1e9bd40b72efb6ec55ce0048c8ce/pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd", size = 6433935, upload-time = "2026-04-01T14:45:36.623Z" }, + { url = "https://files.pythonhosted.org/packages/6e/32/2880fb3a074847ac159d8f902cb43278a61e85f681661e7419e6596803ed/pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e", size = 7116720, upload-time = "2026-04-01T14:45:39.258Z" }, + { url = "https://files.pythonhosted.org/packages/46/87/495cc9c30e0129501643f24d320076f4cc54f718341df18cc70ec94c44e1/pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06", size = 6540498, upload-time = "2026-04-01T14:45:41.879Z" }, + { url = "https://files.pythonhosted.org/packages/18/53/773f5edca692009d883a72211b60fdaf8871cbef075eaa9d577f0a2f989e/pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43", size = 7239413, upload-time = "2026-04-01T14:45:44.705Z" }, + { url = "https://files.pythonhosted.org/packages/c9/e4/4b64a97d71b2a83158134abbb2f5bd3f8a2ea691361282f010998f339ec7/pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354", size = 6482084, upload-time = "2026-04-01T14:45:47.568Z" }, + { url = "https://files.pythonhosted.org/packages/ba/13/306d275efd3a3453f72114b7431c877d10b1154014c1ebbedd067770d629/pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1", size = 7225152, upload-time = "2026-04-01T14:45:50.032Z" }, + { url = "https://files.pythonhosted.org/packages/ff/6e/cf826fae916b8658848d7b9f38d88da6396895c676e8086fc0988073aaf8/pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb", size = 2556579, upload-time = "2026-04-01T14:45:52.529Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b7/2437044fb910f499610356d1352e3423753c98e34f915252aafecc64889f/pillow-12.2.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0538bd5e05efec03ae613fd89c4ce0368ecd2ba239cc25b9f9be7ed426b0af1f", size = 5273969, upload-time = "2026-04-01T14:45:55.538Z" }, + { url = "https://files.pythonhosted.org/packages/f6/f4/8316e31de11b780f4ac08ef3654a75555e624a98db1056ecb2122d008d5a/pillow-12.2.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:394167b21da716608eac917c60aa9b969421b5dcbbe02ae7f013e7b85811c69d", size = 4659674, upload-time = "2026-04-01T14:45:58.093Z" }, + { url = "https://files.pythonhosted.org/packages/d4/37/664fca7201f8bb2aa1d20e2c3d5564a62e6ae5111741966c8319ca802361/pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5d04bfa02cc2d23b497d1e90a0f927070043f6cbf303e738300532379a4b4e0f", size = 5288479, upload-time = "2026-04-01T14:46:01.141Z" }, + { url = "https://files.pythonhosted.org/packages/49/62/5b0ed78fce87346be7a5cfcfaaad91f6a1f98c26f86bdbafa2066c647ef6/pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0c838a5125cee37e68edec915651521191cef1e6aa336b855f495766e77a366e", size = 7032230, upload-time = "2026-04-01T14:46:03.874Z" }, + { url = "https://files.pythonhosted.org/packages/c3/28/ec0fc38107fc32536908034e990c47914c57cd7c5a3ece4d8d8f7ffd7e27/pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a6c9fa44005fa37a91ebfc95d081e8079757d2e904b27103f4f5fa6f0bf78c0", size = 5355404, upload-time = "2026-04-01T14:46:06.33Z" }, + { url = "https://files.pythonhosted.org/packages/5e/8b/51b0eddcfa2180d60e41f06bd6d0a62202b20b59c68f5a132e615b75aecf/pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25373b66e0dd5905ed63fa3cae13c82fbddf3079f2c8bf15c6fb6a35586324c1", size = 6002215, upload-time = "2026-04-01T14:46:08.83Z" }, + { url = "https://files.pythonhosted.org/packages/bc/60/5382c03e1970de634027cee8e1b7d39776b778b81812aaf45b694dfe9e28/pillow-12.2.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bfa9c230d2fe991bed5318a5f119bd6780cda2915cca595393649fc118ab895e", size = 7080946, upload-time = "2026-04-01T14:46:11.734Z" }, ] [[package]] @@ -890,16 +1008,16 @@ wheels = [ [[package]] name = "pygments" -version = "2.19.2" +version = "2.20.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] [[package]] name = "pytest" -version = "9.0.2" +version = "9.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -910,23 +1028,23 @@ dependencies = [ { name = "pygments" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, + { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, ] [[package]] name = "pytest-cov" -version = "7.0.0" +version = "7.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "coverage", extra = ["toml"] }, { name = "pluggy" }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, + { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, ] [[package]] @@ -1033,7 +1151,7 @@ wheels = [ [[package]] name = "requests" -version = "2.32.5" +version = "2.33.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -1041,9 +1159,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", size = 134120, upload-time = "2026-03-30T16:09:15.531Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, + { url = "https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a", size = 64947, upload-time = "2026-03-30T16:09:13.83Z" }, ] [[package]] @@ -1069,27 +1187,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/77/9b/840e0039e65fcf12758adf684d2289024d6140cde9268cc59887dc55189c/ruff-0.15.5.tar.gz", hash = "sha256:7c3601d3b6d76dce18c5c824fc8d06f4eef33d6df0c21ec7799510cde0f159a2", size = 4574214, upload-time = "2026-03-05T20:06:34.946Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/47/20/5369c3ce21588c708bcbe517a8fbe1a8dfdb5dfd5137e14790b1da71612c/ruff-0.15.5-py3-none-linux_armv6l.whl", hash = "sha256:4ae44c42281f42e3b06b988e442d344a5b9b72450ff3c892e30d11b29a96a57c", size = 10478185, upload-time = "2026-03-05T20:06:29.093Z" }, - { url = "https://files.pythonhosted.org/packages/44/ed/e81dd668547da281e5dce710cf0bc60193f8d3d43833e8241d006720e42b/ruff-0.15.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6edd3792d408ebcf61adabc01822da687579a1a023f297618ac27a5b51ef0080", size = 10859201, upload-time = "2026-03-05T20:06:32.632Z" }, - { url = "https://files.pythonhosted.org/packages/c4/8f/533075f00aaf19b07c5cd6aa6e5d89424b06b3b3f4583bfa9c640a079059/ruff-0.15.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:89f463f7c8205a9f8dea9d658d59eff49db05f88f89cc3047fb1a02d9f344010", size = 10184752, upload-time = "2026-03-05T20:06:40.312Z" }, - { url = "https://files.pythonhosted.org/packages/66/0e/ba49e2c3fa0395b3152bad634c7432f7edfc509c133b8f4529053ff024fb/ruff-0.15.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba786a8295c6574c1116704cf0b9e6563de3432ac888d8f83685654fe528fd65", size = 10534857, upload-time = "2026-03-05T20:06:19.581Z" }, - { url = "https://files.pythonhosted.org/packages/59/71/39234440f27a226475a0659561adb0d784b4d247dfe7f43ffc12dd02e288/ruff-0.15.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd4b801e57955fe9f02b31d20375ab3a5c4415f2e5105b79fb94cf2642c91440", size = 10309120, upload-time = "2026-03-05T20:06:00.435Z" }, - { url = "https://files.pythonhosted.org/packages/f5/87/4140aa86a93df032156982b726f4952aaec4a883bb98cb6ef73c347da253/ruff-0.15.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:391f7c73388f3d8c11b794dbbc2959a5b5afe66642c142a6effa90b45f6f5204", size = 11047428, upload-time = "2026-03-05T20:05:51.867Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f7/4953e7e3287676f78fbe85e3a0ca414c5ca81237b7575bdadc00229ac240/ruff-0.15.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dc18f30302e379fe1e998548b0f5e9f4dff907f52f73ad6da419ea9c19d66c8", size = 11914251, upload-time = "2026-03-05T20:06:22.887Z" }, - { url = "https://files.pythonhosted.org/packages/77/46/0f7c865c10cf896ccf5a939c3e84e1cfaeed608ff5249584799a74d33835/ruff-0.15.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc6e7f90087e2d27f98dc34ed1b3ab7c8f0d273cc5431415454e22c0bd2a681", size = 11333801, upload-time = "2026-03-05T20:05:57.168Z" }, - { url = "https://files.pythonhosted.org/packages/d3/01/a10fe54b653061585e655f5286c2662ebddb68831ed3eaebfb0eb08c0a16/ruff-0.15.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1cb7169f53c1ddb06e71a9aebd7e98fc0fea936b39afb36d8e86d36ecc2636a", size = 11206821, upload-time = "2026-03-05T20:06:03.441Z" }, - { url = "https://files.pythonhosted.org/packages/7a/0d/2132ceaf20c5e8699aa83da2706ecb5c5dcdf78b453f77edca7fb70f8a93/ruff-0.15.5-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9b037924500a31ee17389b5c8c4d88874cc6ea8e42f12e9c61a3d754ff72f1ca", size = 11133326, upload-time = "2026-03-05T20:06:25.655Z" }, - { url = "https://files.pythonhosted.org/packages/72/cb/2e5259a7eb2a0f87c08c0fe5bf5825a1e4b90883a52685524596bfc93072/ruff-0.15.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:65bb414e5b4eadd95a8c1e4804f6772bbe8995889f203a01f77ddf2d790929dd", size = 10510820, upload-time = "2026-03-05T20:06:37.79Z" }, - { url = "https://files.pythonhosted.org/packages/ff/20/b67ce78f9e6c59ffbdb5b4503d0090e749b5f2d31b599b554698a80d861c/ruff-0.15.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d20aa469ae3b57033519c559e9bc9cd9e782842e39be05b50e852c7c981fa01d", size = 10302395, upload-time = "2026-03-05T20:05:54.504Z" }, - { url = "https://files.pythonhosted.org/packages/5f/e5/719f1acccd31b720d477751558ed74e9c88134adcc377e5e886af89d3072/ruff-0.15.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:15388dd28c9161cdb8eda68993533acc870aa4e646a0a277aa166de9ad5a8752", size = 10754069, upload-time = "2026-03-05T20:06:06.422Z" }, - { url = "https://files.pythonhosted.org/packages/c3/9c/d1db14469e32d98f3ca27079dbd30b7b44dbb5317d06ab36718dee3baf03/ruff-0.15.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b30da330cbd03bed0c21420b6b953158f60c74c54c5f4c1dabbdf3a57bf355d2", size = 11304315, upload-time = "2026-03-05T20:06:10.867Z" }, - { url = "https://files.pythonhosted.org/packages/28/3a/950367aee7c69027f4f422059227b290ed780366b6aecee5de5039d50fa8/ruff-0.15.5-py3-none-win32.whl", hash = "sha256:732e5ee1f98ba5b3679029989a06ca39a950cced52143a0ea82a2102cb592b74", size = 10551676, upload-time = "2026-03-05T20:06:13.705Z" }, - { url = "https://files.pythonhosted.org/packages/b8/00/bf077a505b4e649bdd3c47ff8ec967735ce2544c8e4a43aba42ee9bf935d/ruff-0.15.5-py3-none-win_amd64.whl", hash = "sha256:821d41c5fa9e19117616c35eaa3f4b75046ec76c65e7ae20a333e9a8696bc7fe", size = 11678972, upload-time = "2026-03-05T20:06:45.379Z" }, - { url = "https://files.pythonhosted.org/packages/fe/4e/cd76eca6db6115604b7626668e891c9dd03330384082e33662fb0f113614/ruff-0.15.5-py3-none-win_arm64.whl", hash = "sha256:b498d1c60d2fe5c10c45ec3f698901065772730b411f164ae270bb6bfcc4740b", size = 10965572, upload-time = "2026-03-05T20:06:16.984Z" }, +version = "0.15.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/99/43/3291f1cc9106f4c63bdce7a8d0df5047fe8422a75b091c16b5e9355e0b11/ruff-0.15.12.tar.gz", hash = "sha256:ecea26adb26b4232c0c2ca19ccbc0083a68344180bba2a600605538ce51a40a6", size = 4643852, upload-time = "2026-04-24T18:17:14.305Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/6e/e78ffb61d4686f3d96ba3df2c801161843746dcbcbb17a1e927d4829312b/ruff-0.15.12-py3-none-linux_armv6l.whl", hash = "sha256:f86f176e188e94d6bdbc09f09bfd9dc729059ad93d0e7390b5a73efe19f8861c", size = 10640713, upload-time = "2026-04-24T18:17:22.841Z" }, + { url = "https://files.pythonhosted.org/packages/ae/08/a317bc231fb9e7b93e4ef3089501e51922ff88d6936ce5cf870c4fe55419/ruff-0.15.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e3bcd123364c3770b8e1b7baaf343cc99a35f197c5c6e8af79015c666c423a6c", size = 11069267, upload-time = "2026-04-24T18:17:30.105Z" }, + { url = "https://files.pythonhosted.org/packages/aa/a4/f828e9718d3dce1f5f11c39c4f65afd32783c8b2aebb2e3d259e492c47bd/ruff-0.15.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fe87510d000220aa1ed530d4448a7c696a0cae1213e5ec30e5874287b66557b5", size = 10397182, upload-time = "2026-04-24T18:17:07.177Z" }, + { url = "https://files.pythonhosted.org/packages/71/e0/3310fc6d1b5e1fdea22bf3b1b807c7e187b581021b0d7d4514cccdb5fb71/ruff-0.15.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84a1630093121375a3e2a95b4a6dc7b59e2b4ee76216e32d81aae550a832d002", size = 10758012, upload-time = "2026-04-24T18:16:55.759Z" }, + { url = "https://files.pythonhosted.org/packages/11/c1/a606911aee04c324ddaa883ae418f3569792fd3c4a10c50e0dd0a2311e1e/ruff-0.15.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fb129f40f114f089ebe0ca56c0d251cf2061b17651d464bb6478dc01e69f11f5", size = 10447479, upload-time = "2026-04-24T18:16:51.677Z" }, + { url = "https://files.pythonhosted.org/packages/9d/68/4201e8444f0894f21ab4aeeaee68aa4f10b51613514a20d80bd628d57e88/ruff-0.15.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0c862b172d695db7598426b8af465e7e9ac00a3ea2a3630ee67eb82e366aaa6", size = 11234040, upload-time = "2026-04-24T18:17:16.529Z" }, + { url = "https://files.pythonhosted.org/packages/34/ff/8a6d6cf4ccc23fd67060874e832c18919d1557a0611ebef03fdb01fff11e/ruff-0.15.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2849ea9f3484c3aca43a82f484210370319e7170df4dfe4843395ddf6c57bc33", size = 12087377, upload-time = "2026-04-24T18:17:04.944Z" }, + { url = "https://files.pythonhosted.org/packages/85/f6/c669cf73f5152f623d34e69866a46d5e6185816b19fcd5b6dd8a2d299922/ruff-0.15.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e77c7e51c07fe396826d5969a5b846d9cd4c402535835fb6e21ce8b28fef847", size = 11367784, upload-time = "2026-04-24T18:17:25.409Z" }, + { url = "https://files.pythonhosted.org/packages/e8/39/c61d193b8a1daaa8977f7dea9e8d8ba866e02ea7b65d32f6861693aa4c12/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b2f4f2f3b1026b5fb449b467d9264bf22067b600f7b6f41fc5958909f449d0", size = 11344088, upload-time = "2026-04-24T18:17:12.258Z" }, + { url = "https://files.pythonhosted.org/packages/c2/8d/49afab3645e31e12c590acb6d3b5b69d7aab5b81926dbaf7461f9441f37a/ruff-0.15.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9ba3b8f1afd7e2e43d8943e55f249e13f9682fde09711644a6e7290eb4f3e339", size = 11271770, upload-time = "2026-04-24T18:17:02.457Z" }, + { url = "https://files.pythonhosted.org/packages/46/06/33f41fe94403e2b755481cdfb9b7ef3e4e0ed031c4581124658d935d52b4/ruff-0.15.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e852ba9fdc890655e1d78f2df1499efbe0e54126bd405362154a75e2bde159c5", size = 10719355, upload-time = "2026-04-24T18:17:27.648Z" }, + { url = "https://files.pythonhosted.org/packages/0d/59/18aa4e014debbf559670e4048e39260a85c7fcee84acfd761ac01e7b8d35/ruff-0.15.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dd8aed930da53780d22fc70bdf84452c843cf64f8cb4eb38984319c24c5cd5fd", size = 10462758, upload-time = "2026-04-24T18:17:32.347Z" }, + { url = "https://files.pythonhosted.org/packages/25/e7/cc9f16fd0f3b5fddcbd7ec3d6ae30c8f3fde1047f32a4093a98d633c6570/ruff-0.15.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:01da3988d225628b709493d7dc67c3b9b12c0210016b08690ef9bd27970b262b", size = 10953498, upload-time = "2026-04-24T18:17:20.674Z" }, + { url = "https://files.pythonhosted.org/packages/72/7a/a9ba7f98c7a575978698f4230c5e8cc54bbc761af34f560818f933dafa0c/ruff-0.15.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9cae0f92bd5700d1213188b31cd3bdd2b315361296d10b96b8e2337d3d11f53e", size = 11447765, upload-time = "2026-04-24T18:17:09.755Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f9/0ae446942c846b8266059ad8a30702a35afae55f5cdc54c5adf8d7afdc27/ruff-0.15.12-py3-none-win32.whl", hash = "sha256:d0185894e038d7043ba8fd6aee7499ece6462dc0ea9f1e260c7451807c714c20", size = 10657277, upload-time = "2026-04-24T18:17:18.591Z" }, + { url = "https://files.pythonhosted.org/packages/33/f1/9614e03e1cdcbf9437570b5400ced8a720b5db22b28d8e0f1bda429f660d/ruff-0.15.12-py3-none-win_amd64.whl", hash = "sha256:c87a162d61ab3adca47c03f7f717c68672edec7d1b5499e652331780fe74950d", size = 11837758, upload-time = "2026-04-24T18:17:00.113Z" }, + { url = "https://files.pythonhosted.org/packages/c0/98/6beb4b351e472e5f4c4613f7c35a5290b8be2497e183825310c4c3a3984b/ruff-0.15.12-py3-none-win_arm64.whl", hash = "sha256:a538f7a82d061cee7be55542aca1d86d1393d55d81d4fcc314370f4340930d4f", size = 11120821, upload-time = "2026-04-24T18:16:57.979Z" }, ] [[package]] @@ -1146,8 +1264,8 @@ name = "sphinx" version = "8.2.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.12'", - "python_full_version == '3.11.*'", + "python_full_version >= '3.15'", + "python_full_version >= '3.11' and python_full_version < '3.15'", ] dependencies = [ { name = "alabaster", marker = "python_full_version >= '3.11'" }, @@ -1198,8 +1316,8 @@ name = "sphinx-autobuild" version = "2025.8.25" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.12'", - "python_full_version == '3.11.*'", + "python_full_version >= '3.15'", + "python_full_version >= '3.11' and python_full_version < '3.15'", ] dependencies = [ { name = "colorama", marker = "python_full_version >= '3.11'" }, @@ -1215,34 +1333,46 @@ wheels = [ ] [[package]] -name = "sphinx-autodoc-typehints" -version = "3.0.1" +name = "sphinx-autodoc-api-style" +version = "0.0.1a17" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.11'", -] dependencies = [ { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "sphinx-ux-autodoc-layout" }, + { name = "sphinx-ux-badges" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/26/f0/43c6a5ff3e7b08a8c3b32f81b859f1b518ccc31e45f22e2b41ced38be7b9/sphinx_autodoc_typehints-3.0.1.tar.gz", hash = "sha256:b9b40dd15dee54f6f810c924f863f9cf1c54f9f3265c495140ea01be7f44fa55", size = 36282, upload-time = "2025-01-16T18:25:30.958Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/6e/0818edd3024997e53be5db981bba6e40cc9b176b326e3a67ddc8d3b40ba9/sphinx_autodoc_api_style-0.0.1a17.tar.gz", hash = "sha256:10b58f1071498b34b68fea77df2787efe39a6335beb42acf621fc88028154517", size = 8296, upload-time = "2026-05-09T10:06:00.279Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/dc/dc46c5c7c566b7ec5e8f860f9c89533bf03c0e6aadc96fb9b337867e4460/sphinx_autodoc_typehints-3.0.1-py3-none-any.whl", hash = "sha256:4b64b676a14b5b79cefb6628a6dc8070e320d4963e8ff640a2f3e9390ae9045a", size = 20245, upload-time = "2025-01-16T18:25:27.394Z" }, + { url = "https://files.pythonhosted.org/packages/46/f6/aca230523ab055b4e8e652034802237a80cb250648f83a2e3b97fb3d4bfa/sphinx_autodoc_api_style-0.0.1a17-py3-none-any.whl", hash = "sha256:85848088c93adb18f757752856ecbf7f8f0db26939701099ed917dadc9694de8", size = 8308, upload-time = "2026-05-09T10:05:36.797Z" }, ] [[package]] -name = "sphinx-autodoc-typehints" -version = "3.5.2" +name = "sphinx-autodoc-argparse" +version = "0.0.1a17" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12'", - "python_full_version == '3.11.*'", +dependencies = [ + { name = "docutils" }, + { name = "pygments" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/45/4c2710e16893305675589c9a439fc938fe9d01ad4a2444b44617ff689d1e/sphinx_autodoc_argparse-0.0.1a17.tar.gz", hash = "sha256:c6ab49e9a15e20bf20c5d3bca11e4478b3af72546a890df162f904a4882d4a50", size = 42459, upload-time = "2026-05-09T10:06:01.218Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/d3/2b00ae5f73a1deb838da9fd5f30a5328c85559b8281612df3f0683dba215/sphinx_autodoc_argparse-0.0.1a17-py3-none-any.whl", hash = "sha256:f6355aef4493173eab4ddcedbdab0b5376a48e7a557d07b8a062ee025fc9552b", size = 47275, upload-time = "2026-05-09T10:05:38.422Z" }, ] + +[[package]] +name = "sphinx-autodoc-typehints-gp" +version = "0.0.1a17" +source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/34/4f/4fd5583678bb7dc8afa69e9b309e6a99ee8d79ad3a4728f4e52fd7cb37c7/sphinx_autodoc_typehints-3.5.2.tar.gz", hash = "sha256:5fcd4a3eb7aa89424c1e2e32bedca66edc38367569c9169a80f4b3e934171fdb", size = 37839, upload-time = "2025-10-16T00:50:15.743Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/2b/e1341a8be6c457a9fbab67e6cc91dfdcfa20f6aad7f8311b0e94246a4b07/sphinx_autodoc_typehints_gp-0.0.1a17.tar.gz", hash = "sha256:eb74bb993a8437b97bf3ad6b84b990646a032ad0123f76dfc2d046ac17ad4265", size = 18587, upload-time = "2026-05-09T10:06:06.99Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/05/f2/9657c98a66973b7c35bfd48ba65d1922860de9598fbb535cd96e3f58a908/sphinx_autodoc_typehints-3.5.2-py3-none-any.whl", hash = "sha256:0accd043619f53c86705958e323b419e41667917045ac9215d7be1b493648d8c", size = 21184, upload-time = "2025-10-16T00:50:13.973Z" }, + { url = "https://files.pythonhosted.org/packages/3b/1c/ab3a668ada94871728582d161ef376095665958ec51d663e6337fb7dc1d1/sphinx_autodoc_typehints_gp-0.0.1a17-py3-none-any.whl", hash = "sha256:7452c6e6740e2d9defbe7a22e3273586927a8b132ba2a0fd52bca1f68e1d8231", size = 19009, upload-time = "2026-05-09T10:05:45.776Z" }, ] [[package]] @@ -1271,6 +1401,90 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9e/48/1ea60e74949eecb12cdd6ac43987f9fd331156388dcc2319b45e2ebb81bf/sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e", size = 13343, upload-time = "2023-04-14T08:10:20.844Z" }, ] +[[package]] +name = "sphinx-design" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2b/69/b34e0cb5336f09c6866d53b4a19d76c227cdec1bbc7ac4de63ca7d58c9c7/sphinx_design-0.6.1.tar.gz", hash = "sha256:b44eea3719386d04d765c1a8257caca2b3e6f8421d7b3a5e742c0fd45f84e632", size = 2193689, upload-time = "2024-08-02T13:48:44.277Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/43/65c0acbd8cc6f50195a3a1fc195c404988b15c67090e73c7a41a9f57d6bd/sphinx_design-0.6.1-py3-none-any.whl", hash = "sha256:b11f37db1a802a183d61b159d9a202314d4d2fe29c163437001324fe2f19549c", size = 2215338, upload-time = "2024-08-02T13:48:42.106Z" }, +] + +[[package]] +name = "sphinx-design" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15'", + "python_full_version >= '3.11' and python_full_version < '3.15'", +] +dependencies = [ + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/13/7b/804f311da4663a4aecc6cf7abd83443f3d4ded970826d0c958edc77d4527/sphinx_design-0.7.0.tar.gz", hash = "sha256:d2a3f5b19c24b916adb52f97c5f00efab4009ca337812001109084a740ec9b7a", size = 2203582, upload-time = "2026-01-19T13:12:53.297Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/cf/45dd359f6ca0c3762ce0490f681da242f0530c49c81050c035c016bfdd3a/sphinx_design-0.7.0-py3-none-any.whl", hash = "sha256:f82bf179951d58f55dca78ab3706aeafa496b741a91b1911d371441127d64282", size = 2220350, upload-time = "2026-01-19T13:12:51.077Z" }, +] + +[[package]] +name = "sphinx-fonts" +version = "0.0.1a17" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/72/e1b365ebeffd7b3c494628e6730b91ed2dfbe4fb2cac6d496263de383344/sphinx_fonts-0.0.1a17.tar.gz", hash = "sha256:c5138145cd2b3af3024e155add69d9eb9201dfb0e887af77fe8d0e2990f4ba3b", size = 5788, upload-time = "2026-05-09T10:06:07.934Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/ae/0ee78cab53f2d864e3d765c141f577a0c9928cb764fd77eca340cce51262/sphinx_fonts-0.0.1a17-py3-none-any.whl", hash = "sha256:835d74e89aae56a96f034ea002fa322ba6dc9b87304c15cfc514d1ff5f7733d1", size = 4363, upload-time = "2026-05-09T10:05:47.376Z" }, +] + +[[package]] +name = "sphinx-gp-opengraph" +version = "0.0.1a17" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/fa/c0f3d81529d7f1bd25e58ffbbf9a033f3a812edf9b47c20e494f03916769/sphinx_gp_opengraph-0.0.1a17.tar.gz", hash = "sha256:7977856c45d7b60cbb14d7a892e17d2ea306dcd713062108aede8a790a592a4b", size = 11947, upload-time = "2026-05-09T10:06:08.832Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/7d/719d93a65c363a687eb1dca92a6bb093acbdf64cb0671adbf83c0ab3df98/sphinx_gp_opengraph-0.0.1a17-py3-none-any.whl", hash = "sha256:769b00d1eea11c44ccd5bd67fc02554905751d96ba2a2f342e96ab1a009f0a33", size = 12184, upload-time = "2026-05-09T10:05:48.761Z" }, +] + +[[package]] +name = "sphinx-gp-sitemap" +version = "0.0.1a17" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/7d/2119503abf54b6dc8baadced9a854ffc78a085b25bf8a24740437b1d4859/sphinx_gp_sitemap-0.0.1a17.tar.gz", hash = "sha256:588709e5e84497803528a09d90f2dd3b9536026f4a80f9dd45dad076f107198b", size = 9956, upload-time = "2026-05-09T10:06:09.726Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/d0/1d82d17811b189d30f953ba1630f17d959efa53849fe12b2efc350328892/sphinx_gp_sitemap-0.0.1a17-py3-none-any.whl", hash = "sha256:214b903689f8027c3e10906a5dbf873f1f5151b859c9cf47a75cf521322528c6", size = 8985, upload-time = "2026-05-09T10:05:50.033Z" }, +] + +[[package]] +name = "sphinx-gp-theme" +version = "0.0.1a17" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "gp-furo-theme" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/47/07/13c5ef75c0034732e79dc9fec7295d80419b5f325d210abb6f4ac60c54bb/sphinx_gp_theme-0.0.1a17.tar.gz", hash = "sha256:b27dd1071bb070d946b2d54f2ebae1f6ac3e96a752bd16d5449e6da72e50d54d", size = 18040, upload-time = "2026-05-09T10:06:10.888Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/86/95bc9410ba5df636c756ea2500a04061ed6e9ad960dfe01e04909e6085fc/sphinx_gp_theme-0.0.1a17-py3-none-any.whl", hash = "sha256:f49aa5f34ca534140a2f17149d912de2836237f59441678d0f4965edb6a9304b", size = 19610, upload-time = "2026-05-09T10:05:51.215Z" }, +] + [[package]] name = "sphinx-inline-tabs" version = "2025.12.21.14" @@ -1284,6 +1498,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/02/2b/e64e7de34663cff1df029ba4f05a86124315bd9eba3d3b78e64904bea7e0/sphinx_inline_tabs-2025.12.21.14-py3-none-any.whl", hash = "sha256:e685c782b58d4e01490bcc4e2367cf7135ec28e7283a05e89095394e4ca6e81a", size = 7082, upload-time = "2025-12-21T13:30:50.142Z" }, ] +[[package]] +name = "sphinx-ux-autodoc-layout" +version = "0.0.1a17" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0d/20/ad0b557f63a4d708f24d20347de53f1e592b1142ec01a19ffae673c0d470/sphinx_ux_autodoc_layout-0.0.1a17.tar.gz", hash = "sha256:001333f0ce166630ccb41b4500d4d2e1f837d5e060cbe2e7b2aa669b394e34e6", size = 21467, upload-time = "2026-05-09T10:06:11.86Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/59/7d0930f5c23dc03d5bd0c9e33d94d3a06dbd803138d167161f36b1a7d342/sphinx_ux_autodoc_layout-0.0.1a17-py3-none-any.whl", hash = "sha256:8fec752202d683608ac91e2d58bace793c870be83a90cb391f132922e076110d", size = 25144, upload-time = "2026-05-09T10:05:52.592Z" }, +] + +[[package]] +name = "sphinx-ux-badges" +version = "0.0.1a17" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/6b/753df32ceb23f0457ae2119b4668aaf6f7c568b25718ab9dd4d931a0e6ac/sphinx_ux_badges-0.0.1a17.tar.gz", hash = "sha256:0833fdae7e5b443eb298712170929bb8b19d4cd027033c7348573693a2940a06", size = 15417, upload-time = "2026-05-09T10:06:12.823Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/46/a07c367fab1420e342d6e04d49268b1c33d410097c313cd725947e0008b6/sphinx_ux_badges-0.0.1a17-py3-none-any.whl", hash = "sha256:3e5d01b493c9b73078d17036840706b394c4f542ac8f4647dcf8b2a69efbee9c", size = 16280, upload-time = "2026-05-09T10:05:54.239Z" }, +] + [[package]] name = "sphinxcontrib-applehelp" version = "2.0.0" @@ -1338,19 +1578,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, ] -[[package]] -name = "sphinxext-opengraph" -version = "0.13.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f6/c0/eb6838e3bae624ce6c8b90b245d17e84252863150e95efdb88f92c8aa3fb/sphinxext_opengraph-0.13.0.tar.gz", hash = "sha256:103335d08567ad8468faf1425f575e3b698e9621f9323949a6c8b96d9793e80b", size = 1026875, upload-time = "2025-08-29T12:20:31.066Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/a4/66c1fd4f8fab88faf71cee04a945f9806ba0fef753f2cfc8be6353f64508/sphinxext_opengraph-0.13.0-py3-none-any.whl", hash = "sha256:936c07828edc9ad9a7b07908b29596dc84ed0b3ceaa77acdf51282d232d4d80e", size = 1004152, upload-time = "2025-08-29T12:20:29.072Z" }, -] - [[package]] name = "sphinxext-rediraffe" version = "0.3.0" @@ -1366,20 +1593,20 @@ wheels = [ [[package]] name = "starlette" -version = "0.52.1" +version = "1.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c4/68/79977123bb7be889ad680d79a40f339082c1978b5cfcf62c2d8d196873ac/starlette-0.52.1.tar.gz", hash = "sha256:834edd1b0a23167694292e94f597773bc3f89f362be6effee198165a35d62933", size = 2653702, upload-time = "2026-01-18T13:34:11.062Z" } +sdist = { url = "https://files.pythonhosted.org/packages/81/69/17425771797c36cded50b7fe44e850315d039f28b15901ab44839e70b593/starlette-1.0.0.tar.gz", hash = "sha256:6a4beaf1f81bb472fd19ea9b918b50dc3a77a6f2e190a12954b25e6ed5eea149", size = 2655289, upload-time = "2026-03-22T18:29:46.779Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/0d/13d1d239a25cbfb19e740db83143e95c772a1fe10202dda4b76792b114dd/starlette-0.52.1-py3-none-any.whl", hash = "sha256:0029d43eb3d273bc4f83a08720b4912ea4b071087a3b48db01b7c839f7954d74", size = 74272, upload-time = "2026-01-18T13:34:09.188Z" }, + { url = "https://files.pythonhosted.org/packages/0b/c9/584bc9651441b4ba60cc4d557d8a547b5aff901af35bda3a4ee30c819b82/starlette-1.0.0-py3-none-any.whl", hash = "sha256:d3ec55e0bb321692d275455ddfd3df75fff145d009685eb40dc91fc66b03d38b", size = 72651, upload-time = "2026-03-22T18:29:45.111Z" }, ] [[package]] name = "tmuxp" -version = "1.67.0" +version = "1.68.0" source = { editable = "." } dependencies = [ { name = "libtmux" }, @@ -1396,12 +1623,9 @@ dev = [ { name = "aafigure" }, { name = "codecov" }, { name = "coverage" }, - { name = "furo" }, { name = "gp-libs" }, - { name = "linkify-it-py" }, + { name = "gp-sphinx" }, { name = "mypy" }, - { name = "myst-parser", version = "4.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "myst-parser", version = "5.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "pillow" }, { name = "pytest" }, { name = "pytest-cov" }, @@ -1409,38 +1633,23 @@ dev = [ { name = "pytest-rerunfailures" }, { name = "pytest-watcher" }, { name = "ruff" }, - { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "sphinx-autobuild", version = "2024.10.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "sphinx-autobuild", version = "2025.8.25", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "sphinx-autodoc-typehints", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "sphinx-autodoc-typehints", version = "3.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "sphinx-copybutton" }, - { name = "sphinx-inline-tabs" }, - { name = "sphinxext-opengraph" }, - { name = "sphinxext-rediraffe" }, + { name = "sphinx-autodoc-api-style" }, + { name = "sphinx-autodoc-argparse" }, { name = "types-docutils" }, { name = "types-pygments" }, { name = "types-pyyaml" }, ] docs = [ { name = "aafigure" }, - { name = "furo" }, { name = "gp-libs" }, - { name = "linkify-it-py" }, - { name = "myst-parser", version = "4.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "myst-parser", version = "5.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "gp-sphinx" }, { name = "pillow" }, - { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "sphinx-autobuild", version = "2024.10.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "sphinx-autobuild", version = "2025.8.25", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "sphinx-autodoc-typehints", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "sphinx-autodoc-typehints", version = "3.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "sphinx-copybutton" }, - { name = "sphinx-inline-tabs" }, - { name = "sphinxext-opengraph" }, - { name = "sphinxext-rediraffe" }, + { name = "sphinx-autodoc-api-style" }, + { name = "sphinx-autodoc-argparse" }, ] lint = [ { name = "mypy" }, @@ -1459,7 +1668,7 @@ testing = [ [package.metadata] requires-dist = [ - { name = "libtmux", specifier = "~=0.55.0" }, + { name = "libtmux", specifier = "~=0.56.0" }, { name = "pyyaml", specifier = ">=6.0" }, ] @@ -1473,11 +1682,9 @@ dev = [ { name = "aafigure" }, { name = "codecov" }, { name = "coverage" }, - { name = "furo" }, { name = "gp-libs" }, - { name = "linkify-it-py" }, + { name = "gp-sphinx", specifier = "==0.0.1a17" }, { name = "mypy" }, - { name = "myst-parser" }, { name = "pillow" }, { name = "pytest" }, { name = "pytest-cov" }, @@ -1485,31 +1692,21 @@ dev = [ { name = "pytest-rerunfailures" }, { name = "pytest-watcher" }, { name = "ruff" }, - { name = "sphinx", specifier = "<9" }, { name = "sphinx-autobuild" }, - { name = "sphinx-autodoc-typehints" }, - { name = "sphinx-copybutton" }, - { name = "sphinx-inline-tabs" }, - { name = "sphinxext-opengraph" }, - { name = "sphinxext-rediraffe" }, + { name = "sphinx-autodoc-api-style", specifier = "==0.0.1a17" }, + { name = "sphinx-autodoc-argparse", specifier = "==0.0.1a17" }, { name = "types-docutils" }, { name = "types-pygments" }, { name = "types-pyyaml" }, ] docs = [ { name = "aafigure" }, - { name = "furo" }, { name = "gp-libs" }, - { name = "linkify-it-py" }, - { name = "myst-parser" }, + { name = "gp-sphinx", specifier = "==0.0.1a17" }, { name = "pillow" }, - { name = "sphinx", specifier = "<9" }, { name = "sphinx-autobuild" }, - { name = "sphinx-autodoc-typehints" }, - { name = "sphinx-copybutton" }, - { name = "sphinx-inline-tabs" }, - { name = "sphinxext-opengraph" }, - { name = "sphinxext-rediraffe" }, + { name = "sphinx-autodoc-api-style", specifier = "==0.0.1a17" }, + { name = "sphinx-autodoc-argparse", specifier = "==0.0.1a17" }, ] lint = [ { name = "mypy" }, @@ -1528,86 +1725,86 @@ testing = [ [[package]] name = "tomli" -version = "2.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", size = 17477, upload-time = "2026-01-11T11:22:38.165Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867", size = 153663, upload-time = "2026-01-11T11:21:45.27Z" }, - { url = "https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9", size = 148469, upload-time = "2026-01-11T11:21:46.873Z" }, - { url = "https://files.pythonhosted.org/packages/d6/c2/506e44cce89a8b1b1e047d64bd495c22c9f71f21e05f380f1a950dd9c217/tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95", size = 236039, upload-time = "2026-01-11T11:21:48.503Z" }, - { url = "https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76", size = 243007, upload-time = "2026-01-11T11:21:49.456Z" }, - { url = "https://files.pythonhosted.org/packages/9c/6f/6e39ce66b58a5b7ae572a0f4352ff40c71e8573633deda43f6a379d56b3e/tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d", size = 240875, upload-time = "2026-01-11T11:21:50.755Z" }, - { url = "https://files.pythonhosted.org/packages/aa/ad/cb089cb190487caa80204d503c7fd0f4d443f90b95cf4ef5cf5aa0f439b0/tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576", size = 246271, upload-time = "2026-01-11T11:21:51.81Z" }, - { url = "https://files.pythonhosted.org/packages/0b/63/69125220e47fd7a3a27fd0de0c6398c89432fec41bc739823bcc66506af6/tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a", size = 96770, upload-time = "2026-01-11T11:21:52.647Z" }, - { url = "https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa", size = 107626, upload-time = "2026-01-11T11:21:53.459Z" }, - { url = "https://files.pythonhosted.org/packages/2f/6d/77be674a3485e75cacbf2ddba2b146911477bd887dda9d8c9dfb2f15e871/tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614", size = 94842, upload-time = "2026-01-11T11:21:54.831Z" }, - { url = "https://files.pythonhosted.org/packages/3c/43/7389a1869f2f26dba52404e1ef13b4784b6b37dac93bac53457e3ff24ca3/tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1", size = 154894, upload-time = "2026-01-11T11:21:56.07Z" }, - { url = "https://files.pythonhosted.org/packages/e9/05/2f9bf110b5294132b2edf13fe6ca6ae456204f3d749f623307cbb7a946f2/tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8", size = 149053, upload-time = "2026-01-11T11:21:57.467Z" }, - { url = "https://files.pythonhosted.org/packages/e8/41/1eda3ca1abc6f6154a8db4d714a4d35c4ad90adc0bcf700657291593fbf3/tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a", size = 243481, upload-time = "2026-01-11T11:21:58.661Z" }, - { url = "https://files.pythonhosted.org/packages/d2/6d/02ff5ab6c8868b41e7d4b987ce2b5f6a51d3335a70aa144edd999e055a01/tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1", size = 251720, upload-time = "2026-01-11T11:22:00.178Z" }, - { url = "https://files.pythonhosted.org/packages/7b/57/0405c59a909c45d5b6f146107c6d997825aa87568b042042f7a9c0afed34/tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b", size = 247014, upload-time = "2026-01-11T11:22:01.238Z" }, - { url = "https://files.pythonhosted.org/packages/2c/0e/2e37568edd944b4165735687cbaf2fe3648129e440c26d02223672ee0630/tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51", size = 251820, upload-time = "2026-01-11T11:22:02.727Z" }, - { url = "https://files.pythonhosted.org/packages/5a/1c/ee3b707fdac82aeeb92d1a113f803cf6d0f37bdca0849cb489553e1f417a/tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729", size = 97712, upload-time = "2026-01-11T11:22:03.777Z" }, - { url = "https://files.pythonhosted.org/packages/69/13/c07a9177d0b3bab7913299b9278845fc6eaaca14a02667c6be0b0a2270c8/tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da", size = 108296, upload-time = "2026-01-11T11:22:04.86Z" }, - { url = "https://files.pythonhosted.org/packages/18/27/e267a60bbeeee343bcc279bb9e8fbed0cbe224bc7b2a3dc2975f22809a09/tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3", size = 94553, upload-time = "2026-01-11T11:22:05.854Z" }, - { url = "https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", size = 154915, upload-time = "2026-01-11T11:22:06.703Z" }, - { url = "https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", size = 149038, upload-time = "2026-01-11T11:22:07.56Z" }, - { url = "https://files.pythonhosted.org/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", size = 242245, upload-time = "2026-01-11T11:22:08.344Z" }, - { url = "https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", size = 250335, upload-time = "2026-01-11T11:22:09.951Z" }, - { url = "https://files.pythonhosted.org/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", size = 245962, upload-time = "2026-01-11T11:22:11.27Z" }, - { url = "https://files.pythonhosted.org/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", size = 250396, upload-time = "2026-01-11T11:22:12.325Z" }, - { url = "https://files.pythonhosted.org/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", size = 97530, upload-time = "2026-01-11T11:22:13.865Z" }, - { url = "https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", size = 108227, upload-time = "2026-01-11T11:22:15.224Z" }, - { url = "https://files.pythonhosted.org/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", size = 94748, upload-time = "2026-01-11T11:22:16.009Z" }, - { url = "https://files.pythonhosted.org/packages/f3/c4/84047a97eb1004418bc10bdbcfebda209fca6338002eba2dc27cc6d13563/tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6", size = 154725, upload-time = "2026-01-11T11:22:17.269Z" }, - { url = "https://files.pythonhosted.org/packages/a8/5d/d39038e646060b9d76274078cddf146ced86dc2b9e8bbf737ad5983609a0/tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc", size = 148901, upload-time = "2026-01-11T11:22:18.287Z" }, - { url = "https://files.pythonhosted.org/packages/73/e5/383be1724cb30f4ce44983d249645684a48c435e1cd4f8b5cded8a816d3c/tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66", size = 243375, upload-time = "2026-01-11T11:22:19.154Z" }, - { url = "https://files.pythonhosted.org/packages/31/f0/bea80c17971c8d16d3cc109dc3585b0f2ce1036b5f4a8a183789023574f2/tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d", size = 250639, upload-time = "2026-01-11T11:22:20.168Z" }, - { url = "https://files.pythonhosted.org/packages/2c/8f/2853c36abbb7608e3f945d8a74e32ed3a74ee3a1f468f1ffc7d1cb3abba6/tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702", size = 246897, upload-time = "2026-01-11T11:22:21.544Z" }, - { url = "https://files.pythonhosted.org/packages/49/f0/6c05e3196ed5337b9fe7ea003e95fd3819a840b7a0f2bf5a408ef1dad8ed/tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8", size = 254697, upload-time = "2026-01-11T11:22:23.058Z" }, - { url = "https://files.pythonhosted.org/packages/f3/f5/2922ef29c9f2951883525def7429967fc4d8208494e5ab524234f06b688b/tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776", size = 98567, upload-time = "2026-01-11T11:22:24.033Z" }, - { url = "https://files.pythonhosted.org/packages/7b/31/22b52e2e06dd2a5fdbc3ee73226d763b184ff21fc24e20316a44ccc4d96b/tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475", size = 108556, upload-time = "2026-01-11T11:22:25.378Z" }, - { url = "https://files.pythonhosted.org/packages/48/3d/5058dff3255a3d01b705413f64f4306a141a8fd7a251e5a495e3f192a998/tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2", size = 96014, upload-time = "2026-01-11T11:22:26.138Z" }, - { url = "https://files.pythonhosted.org/packages/b8/4e/75dab8586e268424202d3a1997ef6014919c941b50642a1682df43204c22/tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9", size = 163339, upload-time = "2026-01-11T11:22:27.143Z" }, - { url = "https://files.pythonhosted.org/packages/06/e3/b904d9ab1016829a776d97f163f183a48be6a4deb87304d1e0116a349519/tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0", size = 159490, upload-time = "2026-01-11T11:22:28.399Z" }, - { url = "https://files.pythonhosted.org/packages/e3/5a/fc3622c8b1ad823e8ea98a35e3c632ee316d48f66f80f9708ceb4f2a0322/tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df", size = 269398, upload-time = "2026-01-11T11:22:29.345Z" }, - { url = "https://files.pythonhosted.org/packages/fd/33/62bd6152c8bdd4c305ad9faca48f51d3acb2df1f8791b1477d46ff86e7f8/tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d", size = 276515, upload-time = "2026-01-11T11:22:30.327Z" }, - { url = "https://files.pythonhosted.org/packages/4b/ff/ae53619499f5235ee4211e62a8d7982ba9e439a0fb4f2f351a93d67c1dd2/tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f", size = 273806, upload-time = "2026-01-11T11:22:32.56Z" }, - { url = "https://files.pythonhosted.org/packages/47/71/cbca7787fa68d4d0a9f7072821980b39fbb1b6faeb5f5cf02f4a5559fa28/tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b", size = 281340, upload-time = "2026-01-11T11:22:33.505Z" }, - { url = "https://files.pythonhosted.org/packages/f5/00/d595c120963ad42474cf6ee7771ad0d0e8a49d0f01e29576ee9195d9ecdf/tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087", size = 108106, upload-time = "2026-01-11T11:22:34.451Z" }, - { url = "https://files.pythonhosted.org/packages/de/69/9aa0c6a505c2f80e519b43764f8b4ba93b5a0bbd2d9a9de6e2b24271b9a5/tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd", size = 120504, upload-time = "2026-01-11T11:22:35.764Z" }, - { url = "https://files.pythonhosted.org/packages/b3/9f/f1668c281c58cfae01482f7114a4b88d345e4c140386241a1a24dcc9e7bc/tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4", size = 99561, upload-time = "2026-01-11T11:22:36.624Z" }, - { url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" }, +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, + { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, + { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, + { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, + { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, + { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, + { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, + { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, + { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, + { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" }, + { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" }, + { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" }, + { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" }, + { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" }, + { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" }, + { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" }, + { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" }, + { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" }, + { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" }, + { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" }, + { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" }, + { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, + { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, ] [[package]] name = "types-docutils" -version = "0.22.3.20260223" +version = "0.22.3.20260408" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/80/33/92c0129283363e3b3ba270bf6a2b7d077d949d2f90afc4abaf6e73578563/types_docutils-0.22.3.20260223.tar.gz", hash = "sha256:e90e868da82df615ea2217cf36dff31f09660daa15fc0f956af53f89c1364501", size = 57230, upload-time = "2026-02-23T04:11:21.806Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3c/49/48a386fe15539556de085b87a69568b028cca2fa4b92596a3d4f79ac6784/types_docutils-0.22.3.20260408.tar.gz", hash = "sha256:22d5d45e4e0d65a1bc8280987a73e28669bb1cc9d16b18d0afc91713d1be26da", size = 57383, upload-time = "2026-04-08T04:27:26.924Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/c7/a4ae6a75d5b07d63089d5c04d450a0de4a5d48ffcb84b95659b22d3885fe/types_docutils-0.22.3.20260223-py3-none-any.whl", hash = "sha256:cc2d6b7560a28e351903db0989091474aa619ad287843a018324baee9c4d9a8f", size = 91969, upload-time = "2026-02-23T04:11:20.966Z" }, + { url = "https://files.pythonhosted.org/packages/08/47/1667fda6e9fcb044f8fb797f6dc4367b88dc2ab40f1a035e387f5405e870/types_docutils-0.22.3.20260408-py3-none-any.whl", hash = "sha256:2545a86966022cdf1468d430b0007eba0837be77974a7f3fafa1b04a6815d531", size = 91981, upload-time = "2026-04-08T04:27:25.934Z" }, ] [[package]] name = "types-pygments" -version = "2.19.0.20251121" +version = "2.20.0.20260408" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "types-docutils" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/90/3b/cd650700ce9e26b56bd1a6aa4af397bbbc1784e22a03971cb633cdb0b601/types_pygments-2.19.0.20251121.tar.gz", hash = "sha256:eef114fde2ef6265365522045eac0f8354978a566852f69e75c531f0553822b1", size = 18590, upload-time = "2025-11-21T03:03:46.623Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/89/4b443128fa540c54a8f7ecdeec225aab4818534167c4a2d133099dc00fa6/types_pygments-2.20.0.20260408.tar.gz", hash = "sha256:e8a56a3ab1aee7f4ed8f1876d2f62c96e0f41ede52405a7d30c888f3989d8f00", size = 21115, upload-time = "2026-04-08T04:34:24.29Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/99/8a/9244b21f1d60dcc62e261435d76b02f1853b4771663d7ec7d287e47a9ba9/types_pygments-2.19.0.20251121-py3-none-any.whl", hash = "sha256:cb3bfde34eb75b984c98fb733ce4f795213bd3378f855c32e75b49318371bb25", size = 25674, upload-time = "2025-11-21T03:03:45.72Z" }, + { url = "https://files.pythonhosted.org/packages/5e/d8/30924b38eef70caef6b05af5440c84d7673cea2a042e206f404c8100a88d/types_pygments-2.20.0.20260408-py3-none-any.whl", hash = "sha256:6d347d5967b5f0654b659a8b8461a870b207b7e60cd4d646bbc047f6a8db8e1e", size = 29055, upload-time = "2026-04-08T04:34:23.412Z" }, ] [[package]] name = "types-pyyaml" -version = "6.0.12.20250915" +version = "6.0.12.20260408" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7e/69/3c51b36d04da19b92f9e815be12753125bd8bc247ba0470a982e6979e71c/types_pyyaml-6.0.12.20250915.tar.gz", hash = "sha256:0f8b54a528c303f0e6f7165687dd33fafa81c807fcac23f632b63aa624ced1d3", size = 17522, upload-time = "2025-09-15T03:01:00.728Z" } +sdist = { url = "https://files.pythonhosted.org/packages/74/73/b759b1e413c31034cc01ecdfb96b38115d0ab4db55a752a3929f0cd449fd/types_pyyaml-6.0.12.20260408.tar.gz", hash = "sha256:92a73f2b8d7f39ef392a38131f76b970f8c66e4c42b3125ae872b7c93b556307", size = 17735, upload-time = "2026-04-08T04:30:50.974Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/e0/1eed384f02555dde685fff1a1ac805c1c7dcb6dd019c916fe659b1c1f9ec/types_pyyaml-6.0.12.20250915-py3-none-any.whl", hash = "sha256:e7d4d9e064e89a3b3cae120b4990cd370874d2bf12fa5f46c97018dd5d3c9ab6", size = 20338, upload-time = "2025-09-15T03:00:59.218Z" }, + { url = "https://files.pythonhosted.org/packages/1c/f0/c391068b86abb708882c6d75a08cd7d25b2c7227dab527b3a3685a3c635b/types_pyyaml-6.0.12.20260408-py3-none-any.whl", hash = "sha256:fbc42037d12159d9c801ebfcc79ebd28335a7c13b08a4cfbc6916df78fee9384", size = 20339, upload-time = "2026-04-08T04:30:50.113Z" }, ] [[package]] @@ -1639,16 +1836,16 @@ wheels = [ [[package]] name = "uvicorn" -version = "0.41.0" +version = "0.46.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/32/ce/eeb58ae4ac36fe09e3842eb02e0eb676bf2c53ae062b98f1b2531673efdd/uvicorn-0.41.0.tar.gz", hash = "sha256:09d11cf7008da33113824ee5a1c6422d89fbc2ff476540d69a34c87fab8b571a", size = 82633, upload-time = "2026-02-16T23:07:24.1Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/93/041fca8274050e40e6791f267d82e0e2e27dd165627bd640d3e0e378d877/uvicorn-0.46.0.tar.gz", hash = "sha256:fb9da0926999cc6cb22dc7cd71a94a632f078e6ae47ff683c5c420750fb7413d", size = 88758, upload-time = "2026-04-23T07:16:00.151Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/83/e4/d04a086285c20886c0daad0e026f250869201013d18f81d9ff5eada73a88/uvicorn-0.41.0-py3-none-any.whl", hash = "sha256:29e35b1d2c36a04b9e180d4007ede3bcb32a85fbdfd6c6aeb3f26839de088187", size = 68783, upload-time = "2026-02-16T23:07:22.357Z" }, + { url = "https://files.pythonhosted.org/packages/31/a3/5b1562db76a5a488274b2332a97199b32d0442aca0ed193697fd47786316/uvicorn-0.46.0-py3-none-any.whl", hash = "sha256:bbebbcbed972d162afca128605223022bedd345b7bc7855ce66deb31487a9048", size = 70926, upload-time = "2026-04-23T07:15:58.355Z" }, ] [[package]]