From 5d65f50826ed4b14b6f1bbfa4b45d934e9d6c422 Mon Sep 17 00:00:00 2001 From: Rene Moser Date: Sun, 20 Sep 2026 23:53:16 +0200 Subject: [PATCH] mkdocs for docs --- .github/workflows/build.yml | 27 + .github/workflows/deploy.yml | 52 ++ .gitignore | 4 + README.md | 46 ++ docs/about/contributing.md | 96 +++ docs/about/license.md | 31 + docs/getting-started/first-deployment.md | 102 +++ docs/getting-started/implementations.md | 76 ++ docs/getting-started/index.md | 41 ++ docs/getting-started/install.md | 179 +++++ docs/guide/ci-cd.md | 121 ++++ docs/guide/configuration.md | 143 ++++ docs/guide/download.md | 78 ++ docs/guide/hooks-and-locking.md | 91 +++ docs/guide/protocols.md | 168 +++++ docs/guide/scopes.md | 71 ++ docs/guide/selecting-files.md | 131 ++++ docs/guide/troubleshooting.md | 144 ++++ docs/index.md | 141 ++++ docs/reference/compatibility.md | 139 ++++ docs/reference/manual-bash.md | 692 ++++++++++++++++++ docs/reference/manual-python.md | 374 ++++++++++ docs/stylesheets/extra.css | 29 + images/bg_hr.png | Bin 943 -> 0 bytes images/bkg.png | Bin 4261 -> 0 bytes images/blacktocat.png | Bin 1266 -> 0 bytes images/body-bg.jpg | Bin 2281 -> 0 bytes images/download-button.png | Bin 27151 -> 0 bytes images/github-button.png | Bin 1550 -> 0 bytes images/header-bg.jpg | Bin 9461 -> 0 bytes images/highlight-bg.jpg | Bin 30991 -> 0 bytes images/icon_download.png | Bin 1162 -> 0 bytes images/sidebar-bg.jpg | Bin 2270 -> 0 bytes images/sprite_download.png | Bin 16799 -> 0 bytes index.html | 117 --- javascripts/main.js | 1 - mkdocs.yml | 115 +++ params.json | 7 - pyproject.toml | 11 + scripts/sync_upstream_docs.py | 219 ++++++ stylesheets/github-light.css | 116 --- stylesheets/print.css | 228 ------ stylesheets/pygment_trac.css | 69 -- stylesheets/stylesheet.css | 881 ----------------------- uv.lock | 642 +++++++++++++++++ 45 files changed, 3963 insertions(+), 1419 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/deploy.yml create mode 100644 .gitignore create mode 100644 README.md create mode 100644 docs/about/contributing.md create mode 100644 docs/about/license.md create mode 100644 docs/getting-started/first-deployment.md create mode 100644 docs/getting-started/implementations.md create mode 100644 docs/getting-started/index.md create mode 100644 docs/getting-started/install.md create mode 100644 docs/guide/ci-cd.md create mode 100644 docs/guide/configuration.md create mode 100644 docs/guide/download.md create mode 100644 docs/guide/hooks-and-locking.md create mode 100644 docs/guide/protocols.md create mode 100644 docs/guide/scopes.md create mode 100644 docs/guide/selecting-files.md create mode 100644 docs/guide/troubleshooting.md create mode 100644 docs/index.md create mode 100644 docs/reference/compatibility.md create mode 100644 docs/reference/manual-bash.md create mode 100644 docs/reference/manual-python.md create mode 100644 docs/stylesheets/extra.css delete mode 100644 images/bg_hr.png delete mode 100644 images/bkg.png delete mode 100644 images/blacktocat.png delete mode 100644 images/body-bg.jpg delete mode 100644 images/download-button.png delete mode 100644 images/github-button.png delete mode 100644 images/header-bg.jpg delete mode 100644 images/highlight-bg.jpg delete mode 100644 images/icon_download.png delete mode 100644 images/sidebar-bg.jpg delete mode 100644 images/sprite_download.png delete mode 100644 index.html delete mode 100644 javascripts/main.js create mode 100644 mkdocs.yml delete mode 100644 params.json create mode 100644 pyproject.toml create mode 100755 scripts/sync_upstream_docs.py delete mode 100644 stylesheets/github-light.css delete mode 100644 stylesheets/print.css delete mode 100644 stylesheets/pygment_trac.css delete mode 100644 stylesheets/stylesheet.css create mode 100644 uv.lock diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..5c3aadb --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,27 @@ +name: Build site + +on: + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - name: Install uv + uses: astral-sh/setup-uv@v10 + with: + enable-cache: true + + - run: uv sync --frozen + + - name: Sync the manuals from the tool repositories + run: uv run scripts/sync_upstream_docs.py + + - name: Build + run: uv run mkdocs build --strict diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..1ba5116 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,52 @@ +name: Deploy site + +on: + push: + branches: [master] + # Pick up manual page changes in the tool repositories once a day. + schedule: + - cron: "17 4 * * *" + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - name: Install uv + uses: astral-sh/setup-uv@v10 + with: + enable-cache: true + + - name: Install dependencies + run: uv sync --frozen + + - name: Sync the manuals from the tool repositories + run: uv run scripts/sync_upstream_docs.py + + - name: Build + run: uv run mkdocs build --strict + + - uses: actions/upload-pages-artifact@v5 + with: + path: site + + deploy: + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - id: deployment + uses: actions/deploy-pages@v5 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1943907 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +site/ +.venv/ +__pycache__/ +.cache/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..29c5630 --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +# git-ftp.github.io + +The documentation site for both git-ftp implementations, +[git-ftp-py](https://github.com/git-ftp/git-ftp-py) (Python) and +[git-ftp](https://github.com/git-ftp/git-ftp) (Bash), published at +. + +Built with [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/) +and deployed by GitHub Pages on every push to `master`. + +## Working on it + +```sh +uv sync +uv run mkdocs serve # http://127.0.0.1:8000 +uv run mkdocs build --strict +``` + +## Where the content lives + +Everything under `docs/` is written by hand, except the three pages in +`docs/reference/`. Those are generated from the manual pages in the tool +repositories: + +| Page | Source | +|---|---| +| `reference/manual-python.md` | `git-ftp-py:docs/git-ftp.1.md` | +| `reference/manual-bash.md` | `git-ftp:man/git-ftp.1.md` | +| `reference/compatibility.md` | `git-ftp-py:COMPATIBILITY.md` | + +```sh +uv run scripts/sync_upstream_docs.py # fetch from GitHub +uv run scripts/sync_upstream_docs.py --local # from ../git-ftp, ../git-ftp-py +uv run scripts/sync_upstream_docs.py --check # fail if they are outdated +``` + +The deploy workflow runs the sync before every build, and once a day on a +schedule, so a manual page fixed upstream appears here without a commit in this +repository. Fix those pages upstream, not here. + +## Deployment + +`.github/workflows/deploy.yml` builds the site and publishes it with +`actions/deploy-pages`. This needs **Settings → Pages → Source: GitHub +Actions** in the repository settings; no `gh-pages` branch is involved. +`.github/workflows/build.yml` builds pull requests without deploying. diff --git a/docs/about/contributing.md b/docs/about/contributing.md new file mode 100644 index 0000000..33f0e30 --- /dev/null +++ b/docs/about/contributing.md @@ -0,0 +1,96 @@ +--- +title: Contributing +--- + +# Contributing + +Both implementations are maintained on GitHub, and both take patches. + +
+ +- :fontawesome-brands-python: **[git-ftp-py][py]** + + --- + + The Python implementation. + + [Issues][py-issues] · [Pull requests][py-prs] · [Changelog][py-changelog] + +- :material-bash: **[git-ftp][sh]** + + --- + + The original Bash implementation. + + [Issues][sh-issues] · [Pull requests][sh-prs] · [Changelog][sh-changelog] + +
+ +## Reporting a bug + +Include the command you ran, the output of `git ftp push -vv` with credentials +removed, the version (`git ftp --version`) and the server software if you know +it. FTP servers differ wildly in what they accept, so naming yours often solves +the riddle straight away. + +## Working on the Python implementation + +```sh +git clone https://github.com/git-ftp/git-ftp-py.git +cd git-ftp-py +uv sync --all-groups +make lint typecheck test # ruff, mypy, pytest +make test-docker # against pure-ftpd containers, Linux only +``` + +The test suite starts real FTP, FTPS and SFTP servers in-process, so most +changes can be covered by a test without any server of your own. The manual +page source is `docs/git-ftp.1.md`. + +Anything that changes behaviour compared to the Bash original belongs in +`COMPATIBILITY.md` — that file is the contract between the two implementations +and is published [here](../reference/compatibility.md). + +## Working on the Bash implementation + +```sh +git clone https://github.com/git-ftp/git-ftp.git +cd git-ftp +make test +``` + +The core functionality is unit tested with +[shunit2](https://github.com/kward/shunit2); the tests are in `tests/`. The +manual page source is `man/git-ftp.1.md`. + +Add yourself to the [AUTHORS](https://github.com/git-ftp/git-ftp/blob/master/AUTHORS) +file with your first patch. + +## This website + +The site is built with [MkDocs](https://www.mkdocs.org/) and +[Material for MkDocs](https://squidfunk.github.io/mkdocs-material/) from the +[git-ftp.github.io](https://github.com/git-ftp/git-ftp.github.io) repository +and published by GitHub Pages on every push. + +```sh +git clone https://github.com/git-ftp/git-ftp.github.io.git +cd git-ftp.github.io +uv sync +uv run mkdocs serve +``` + +The three reference pages are generated from the tool repositories by +`scripts/sync_upstream_docs.py` and refreshed at build time — fix a manual page +upstream, not here. Everything else under `docs/` is written by hand; there is +an :material-pencil: link at the top of every page that takes you straight to +its source. + +[py]: https://github.com/git-ftp/git-ftp-py +[py-issues]: https://github.com/git-ftp/git-ftp-py/issues +[py-prs]: https://github.com/git-ftp/git-ftp-py/pulls +[py-changelog]: https://github.com/git-ftp/git-ftp-py/blob/main/CHANGELOG.md +[sh]: https://github.com/git-ftp/git-ftp +[sh-issues]: https://github.com/git-ftp/git-ftp/issues +[sh-prs]: https://github.com/git-ftp/git-ftp/pulls +[sh-changelog]: https://github.com/git-ftp/git-ftp/blob/master/CHANGELOG.md diff --git a/docs/about/license.md b/docs/about/license.md new file mode 100644 index 0000000..09fab81 --- /dev/null +++ b/docs/about/license.md @@ -0,0 +1,31 @@ +--- +title: License +--- + +# License + +Both implementations of git-ftp are free software, licensed under the +[GNU General Public License, Version 3.0](https://www.gnu.org/licenses/gpl-3.0-standalone.html) +or later. + +- [LICENSE — git-ftp (Python)](https://github.com/git-ftp/git-ftp-py/blob/main/LICENSE) +- [LICENSE — git-ftp (Bash)](https://github.com/git-ftp/git-ftp/blob/master/LICENSE) + +This means you may use, study, share and modify git-ftp, and that anything you +distribute based on it carries the same freedoms. + +## Authors + +git-ftp was started by René Moser and has since been maintained with +[Maikel Linke](https://github.com/mlinke-ai) and a long list of contributors on +GitHub. The Python port is written by René Moser and the git-ftp contributors. + +The [AUTHORS](https://github.com/git-ftp/git-ftp/blob/master/AUTHORS) file has +an (incomplete) list of everyone who helped. Add yourself when you send a +patch. + +## This documentation + +The contents of this site are part of the +[git-ftp.github.io](https://github.com/git-ftp/git-ftp.github.io) repository +and are covered by the same license. diff --git a/docs/getting-started/first-deployment.md b/docs/getting-started/first-deployment.md new file mode 100644 index 0000000..6f25fe7 --- /dev/null +++ b/docs/getting-started/first-deployment.md @@ -0,0 +1,102 @@ +--- +title: First deployment +--- + +# First deployment + +## 1. Tell git-ftp where the server is + +Run this once inside your repository: + +```sh +git config git-ftp.url "ftp://example.com/public_html" +git config git-ftp.user "alice" +git config git-ftp.password "s3cret" +``` + +The settings land in `.git/config`, which is not part of the repository, so the +password is not committed. If you would rather not store it at all, see +[credentials](../guide/protocols.md#credentials) for `-P`, `--password-command`, +`~/.netrc`, the macOS keychain and environment variables. + +## 2. Upload everything once + +If the server is empty, or its contents should be replaced by what is in Git: + +```sh +git ftp init +``` + +This uploads every tracked file that is not ignored and writes the commit id +into `.git-ftp.log` on the server. + +If the files are **already** on the server and match your working tree, do not +upload them again — just record the commit: + +```sh +git ftp catchup +``` + +!!! tip "Look before you leap" + + `--dry-run` prints what would be transferred and touches nothing: + + ```sh + git ftp init --dry-run + ``` + +## 3. Deploy your changes + +From now on, every deployment is one command: + +```sh +echo "new content" >> index.txt +git commit index.txt -m "Add new content" +git ftp push +``` + +```text +1 file to sync: +[1 of 1] Buffered for upload 'index.txt'. +Uploading ... +Last deployment changed from 1f2a3b4 to ded01b2. +``` + +git-ftp reads the commit recorded on the server, diffs it against `HEAD` and +transfers exactly the files that were added, changed or deleted. Only when +every upload succeeded does it update the log, so an interrupted deployment +never claims a commit it did not finish. + +## 4. Check what is deployed + +```sh +git ftp show # git show of the deployed commit +git ftp log # git log starting at the deployed commit +``` + +## Going back, and other branches + +Because the state on the server is just a commit, moving around in history is +ordinary Git work: + +```sh +# deploy the state of three commits ago +git checkout HEAD~3 +git ftp push + +# deploy another branch without checking it out +git ftp push -b staging + +# upload the difference between develop and master +git checkout develop +git ftp push --commit master +``` + +## Where to go next + +- [Configuration](../guide/configuration.md) — every setting, and where to put it. +- [Scopes](../guide/scopes.md) — staging and production in one repository. +- [Selecting files](../guide/selecting-files.md) — `.git-ftp-ignore`, + `.git-ftp-include`, `--syncroot`. +- [Continuous deployment](../guide/ci-cd.md) — deploy from GitHub Actions, + GitLab CI or Bitbucket Pipelines. diff --git a/docs/getting-started/implementations.md b/docs/getting-started/implementations.md new file mode 100644 index 0000000..5ca2c39 --- /dev/null +++ b/docs/getting-started/implementations.md @@ -0,0 +1,76 @@ +--- +title: Which implementation +--- + +# Which implementation + +git-ftp exists twice: as the original Bash script and as a native Python port. +Both deploy the same way and can take over from each other at any time. + +## Short answer + +Install the **Python** implementation unless you have a reason not to: + +```sh +pip install git-ftp +``` + +It transfers files in parallel, needs no `lftp` for downloading, verifies SFTP +host keys, keeps passwords out of the process list and fixes a long list of +[upstream bugs](../reference/compatibility.md#upstream-bugs-that-are-fixed). + +Use the **Bash** implementation when Python is inconvenient — a minimal +container, a shared host where only a shell script may be dropped into `bin`, +or a system whose distribution already packages `git-ftp` and where that is the +easiest thing to audit. + +## Side by side + +| | git-ftp (Python) | git-ftp (Bash) | +|---|---|---| +| Source | [git-ftp/git-ftp-py][py] | [git-ftp/git-ftp][sh] | +| Install | `pip install git-ftp` | distribution package or one script | +| Runtime | Python 3.10+, `git` | POSIX shell, `curl`, `git` | +| FTP, FTPS, FTPES | libcurl through pycurl | curl | +| SFTP | paramiko, host keys verified | curl, host key not checked | +| Transfers | parallel, `--jobs` (default 4) | one file after another | +| `download`, `pull`, `snapshot` | built in | needs `lftp` installed | +| Passwords | passed to libcurl in memory | may appear in the process list | +| Encrypted SSH keys | `--key-passphrase`, prompt, `ssh-agent` | not supported | +| Progress | spinner with a done/total count on a terminal | plain lines | +| `--worktree` | yes — edits during upload cannot leak in | no | +| Shell completion | bash, zsh, fish | no | + +## What they share + +The port's rule is that everything on the wire and in configuration stays +identical: + +- the deployed-commit file `.git-ftp.log`, at the same place with the same + content; +- the `git-ftp.*` configuration keys, the scope keys and `.git-ftp-config`, + with the same precedence; +- `.git-ftp-ignore` and `.git-ftp-include`, with the same glob and + `target:source` semantics; +- the `pre-ftp-push` and `post-ftp-push` hooks, with the same arguments and + stdin format; +- the exit codes and the progress messages. + +So you can deploy with the Bash script today and with the Python program +tomorrow, from another machine, without touching the server. + +The full list of what is identical, what was fixed and what deliberately +differs is on the [compatibility page](../reference/compatibility.md). + +## Switching + +There is nothing to migrate. Install the other implementation and run +`git ftp push` — it reads the same configuration and the same remote log. If +you want to be sure first: + +```sh +git ftp push --dry-run +``` + +[py]: https://github.com/git-ftp/git-ftp-py +[sh]: https://github.com/git-ftp/git-ftp diff --git a/docs/getting-started/index.md b/docs/getting-started/index.md new file mode 100644 index 0000000..3a2be4e --- /dev/null +++ b/docs/getting-started/index.md @@ -0,0 +1,41 @@ +--- +title: Getting started +--- + +# Getting started + +Three short pages get you from nothing to a deployed site: + +
+ +- :material-download: **[Install](install.md)** + + --- + + Pick an implementation and get `git ftp` onto your `PATH`. + +- :material-rocket-launch-outline: **[First deployment](first-deployment.md)** + + --- + + Configure a server, upload everything once, then push only what changed. + +- :material-scale-balance: **[Which implementation](implementations.md)** + + --- + + What the Python port and the Bash original do differently. + +
+ +Everything after that is in the [guide](../guide/configuration.md), and every +option is listed in the [manuals](../reference/manual-python.md). + +## What you need + +- A Git repository whose files you want on a server. +- An account on that server, reachable over FTP, FTPS, FTPES or SFTP. +- Write permission in the directory you deploy into — git-ftp keeps its log + file (`.git-ftp.log`) there. + +Nothing is installed on the server, and the server needs no Git. diff --git a/docs/getting-started/install.md b/docs/getting-started/install.md new file mode 100644 index 0000000..7192751 --- /dev/null +++ b/docs/getting-started/install.md @@ -0,0 +1,179 @@ +--- +title: Install +--- + +# Install + +=== "Python (recommended)" + + The Python implementation is published on + [PyPI](https://pypi.org/project/git-ftp/) and needs Python 3.10 or newer. + + ```sh + pip install git-ftp + ``` + + Prefer an isolated installation, so the tool does not share a virtual + environment with anything else: + + ```sh + pipx install git-ftp # or + uv tool install git-ftp + ``` + + Check it: + + ```sh + git ftp --version + ``` + + Git runs the `git-ftp` program as `git ftp` as soon as it is on your + `PATH`. libcurl comes bundled with the `pycurl` wheel and SFTP is spoken by + `paramiko`, so the only other thing you need is `git` itself. + + Upgrade with `pip install --upgrade git-ftp` (`pipx upgrade git-ftp`, + `uv tool upgrade git-ftp`). + +=== "Bash" + + The original is a single shell script that needs `git` and `curl`. + + **Debian, Ubuntu and friends** + + ```sh + sudo apt-get install git-ftp + ``` + + The distribution package can lag behind. For the newest release maintained + by the project, add the PPA: + + ```sh + sudo -s + add-apt-repository ppa:git-ftp/ppa + + # On Debian, point the PPA at the precise series + source /etc/*-release + if [ "$ID" = "debian" ]; then + dist="$(echo /etc/apt/sources.list.d/git-ftp-ppa-*.list | sed 's/^.*ppa-\(.*\)\.list$/\1/')" + sed -i.backup "s/$dist/precise/g" /etc/apt/sources.list.d/git-ftp-ppa-*.list + fi + + apt-get update + apt-get install git-ftp + ``` + + **macOS** + + ```sh + brew install git git-ftp + ``` + + **Arch Linux** + + Unofficial packages are in the + [AUR](https://aur.archlinux.org/packages?K=git-ftp). + + **From source, any Unix** + + ```sh + git clone https://github.com/git-ftp/git-ftp.git + cd git-ftp + + # check out the newest release tag + git checkout "$(git tag | grep '^[0-9]*\.[0-9]*\.[0-9]*$' | tail -1)" + sudo make install + ``` + + To update, `git fetch`, check out the new tag and run `sudo make install` + again. + + **The script alone** + + ```sh + curl https://raw.githubusercontent.com/git-ftp/git-ftp/master/git-ftp > /bin/git-ftp + chmod 755 /bin/git-ftp + ``` + + This installs the current development state. Replace `master` with a + version tag to pin a release. Uninstall by deleting the file. + +## Windows + +=== "Python" + + Install Python from [python.org](https://www.python.org/downloads/) or the + Microsoft Store, then: + + ```powershell + pip install git-ftp + git ftp --version + ``` + + All four protocols work out of the box; there is no separate curl to + install. + + + ## Shell completion + + The Python implementation ships completions: + + ```sh + eval "$(_GIT_FTP_COMPLETE=bash_source git-ftp)" # zsh: zsh_source, fish: fish_source + ``` + + Add the line to your shell's startup file to keep it. + +=== "Bash" + + Install [Git for Windows](https://gitforwindows.org/), open Git Bash as + administrator and run: + + ```bash + curl https://raw.githubusercontent.com/git-ftp/git-ftp/master/git-ftp > /bin/git-ftp + chmod 755 /bin/git-ftp + ``` + + `/bin` here is an alias, by default `C:\Program Files\Git\usr\bin`. + + For SFTP you need a [curl build](https://curl.se/windows/) with SFTP + support: install it and remove `bin/curl.exe` from the Git for Windows + directory so the new one is found. + + Cygwin works too — install its `curl` package and use the same two + commands. + + ??? warning "Git for Windows and Cygwin side by side" + + If both are installed you may see an error about a path starting with + `/cygdrive/`: + + creating `/cygdrive/c/TEMP/git-ftp-m7GH/delete_tmp': No such file or directory + + git-ftp then mixes commands from both installations, which disagree on + path prefixes. Edit `\etc\fstab` and change + + none /cygdrive/ cygdrive binary,posix=0,user 0 0 + + to + + none / cygdrive binary,posix=0,user 0 0 + + Close all consoles and try again. + + ## SFTP on macOS with the Bash version + + The curl that ships with macOS has no SFTP support and reports + `Protocol sftp not supported or disabled in libcurl`. Either use the + [Python implementation](implementations.md), which speaks SFTP through paramiko, or + build curl yourself: + + ```sh + # in the unpacked curl source from https://curl.se/download.html + brew install openssl libssh2 + ./configure -q --with-libssh2 --with-ssl=/usr/local/opt/openssl + make + make install + ``` + + `curl --version` should then list `sftp` among the protocols. If the system + curl still wins, put `/usr/local/bin` first in your `PATH`. diff --git a/docs/guide/ci-cd.md b/docs/guide/ci-cd.md new file mode 100644 index 0000000..2be1fe2 --- /dev/null +++ b/docs/guide/ci-cd.md @@ -0,0 +1,121 @@ +--- +title: Continuous deployment +--- + +# Continuous deployment + +git-ftp needs nothing but Git and a network connection, which makes it a +comfortable deploy step in any CI system. + +!!! warning "Fetch the whole history" + + Most CI systems clone with `--depth 1`. git-ftp has to diff against the + commit recorded on the server, and that commit is usually not in a shallow + clone — the deployment then uploads everything, or fails. Configure a full + clone, as shown below. + +Keep the credentials in the CI system's secret store and pass them through the +environment; never commit them. + +## GitHub Actions + +```yaml title=".github/workflows/deploy.yml" +name: Deploy + +on: + push: + branches: [main] + +concurrency: deploy # one deployment at a time + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 # git-ftp needs the history + + - uses: actions/setup-python@v7 + with: + python-version: "3.x" + + - run: pip install git-ftp + + - name: Deploy + env: + GIT_FTP_URL: ftp://example.com/public_html + GIT_FTP_USER: ${{ secrets.FTP_USER }} + GIT_FTP_PASSWORD: ${{ secrets.FTP_PASSWORD }} + run: git ftp push --auto-init +``` + +`--auto-init` uploads everything the first time, so the very first run works +without a manual `git ftp init`. + +There is also a ready-made action built on the Bash implementation: +[FTP Deploy](https://github.com/marketplace/actions/ftp-deploy). + +## GitLab CI + +```yaml title=".gitlab-ci.yml" +deploy: + stage: deploy + image: python:3-slim + variables: + GIT_DEPTH: 0 # git-ftp needs the history + only: + - main + before_script: + - apt-get update && apt-get install -y --no-install-recommends git + - pip install git-ftp + script: + - git ftp push --auto-init +``` + +Set `GIT_FTP_URL`, `GIT_FTP_USER` and `GIT_FTP_PASSWORD` as masked, protected +CI/CD variables in the project settings. + +## Bitbucket Pipelines + +```yaml title="bitbucket-pipelines.yml" +image: python:3-slim + +clone: + depth: full # git-ftp needs the history + +pipelines: + branches: + main: + - step: + name: Deploy + deployment: production + script: + - apt-get update && apt-get install -y --no-install-recommends git + - pip install git-ftp + - git ftp push --auto-init +``` + +There is a [video walkthrough](https://www.youtube.com/watch?v=8HZhHtZebdw) of +the same setup with the Bash implementation. + +## Deploying per environment + +If scopes are named after branches, one step covers all of them: + +```sh +git ftp push -s # scope = current branch name +``` + +See [scopes](scopes.md). + +## Things worth adding + +- **Serialise deployments.** Two jobs deploying at once corrupt each other's + state. Use the CI system's concurrency control, and `--lock` as a second net. +- **Build first.** Compile assets in an earlier step and list the results in + [`.git-ftp-include`](selecting-files.md#uploading-untracked-files). +- **Look before pushing.** A `git ftp push --dry-run` step on pull requests + shows what a merge would deploy. +- **Fail loudly.** git-ftp's [exit codes](../reference/manual-python.md#exit-codes) + distinguish a login failure (4) from a locked remote (7) and a hook veto (9). diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md new file mode 100644 index 0000000..b14909a --- /dev/null +++ b/docs/guide/configuration.md @@ -0,0 +1,143 @@ +--- +title: Configuration +--- + +# Configuration + +Every option can be stored, so that a deployment is just `git ftp push`. + +## Where settings come from + +git-ftp reads a setting from the first source that has it: + +1. the command line; +2. the environment — `GIT_FTP_URL`, `GIT_FTP_USER`, `GIT_FTP_PASSWORD` + (Python implementation only); +3. `.git-ftp-config` in the repository root, if present; +4. the Git configuration, scope keys (`git-ftp..`) before plain + keys (`git-ftp.`). + +`.git/config` is local to your clone, so credentials put there are not +committed. `.git-ftp-config` *is* part of the repository — keep passwords out +of it. + +## Setting defaults + +```sh +git config git-ftp.url "ftp://example.com/public_html" +git config git-ftp.user "alice" +git config git-ftp.password "s3cret" +``` + +After that: + +```sh +git ftp push +``` + +## The keys + +| Key | Meaning | +|---|---| +| `git-ftp.url` | Server URL, for example `ftp://example.com/public_html` | +| `git-ftp.user` | Login name | +| `git-ftp.password` | Password | +| `git-ftp.password-command` | Command whose first output line is the password :material-language-python: | +| `git-ftp.keychain` | macOS keychain entry, `account@host` | +| `git-ftp.syncroot` | Deploy only this directory, as if it were the repository root | +| `git-ftp.remote-root` | Remote directory to deploy into, replacing the URL's path | +| `git-ftp.deployedsha1file` | Name of the log file on the server (default `.git-ftp.log`) | +| `git-ftp.branch` | Branch to deploy | +| `git-ftp.insecure` | Do not verify TLS certificates or SFTP host keys | +| `git-ftp.cacert` | CA certificate bundle for FTPS/FTPES | +| `git-ftp.key` / `git-ftp.pubkey` | SFTP private and public key file | +| `git-ftp.key-passphrase` | Passphrase of an encrypted SFTP key :material-language-python: | +| `git-ftp.disable-epsv` | Use PASV instead of EPSV | +| `git-ftp.proxy` | Proxy URL | +| `git-ftp.jobs` | Number of parallel connections, default 4 :material-language-python: | +| `git-ftp.worktree` | Upload from a throwaway worktree :material-language-python: | +| `git-ftp.no-commit` | `pull` merges without committing | + +:material-language-python: — Python implementation only. + +Booleans are read the way `git config` reads them: `true`, `yes`, `on`, `1` +against `false`, `no`, `off`, `0`. + +## A config file in the repository + +Instead of `git config`, the same keys can live in `.git-ftp-config` in the +repository root, which takes precedence over the Git configuration: + +```ini +[git-ftp] + url = ftp://example.com/public_html + user = alice + syncroot = public + jobs = 8 +``` + +This is the place for settings that belong to the project rather than to your +machine. Credentials belong in `.git/config`, in the environment or in a +password manager — see [credentials](protocols.md#credentials). + +## Environment variables + +The Python implementation reads `GIT_FTP_URL`, `GIT_FTP_USER` and +`GIT_FTP_PASSWORD` after the command line and before the Git configuration, +which is what you want in CI: + +```sh +GIT_FTP_URL="ftp://example.com/htdocs" \ +GIT_FTP_USER="$FTP_USER" \ +GIT_FTP_PASSWORD="$FTP_PASSWORD" \ +git ftp push +``` + +Nothing is written to a file and no password appears on a command line. + +## The log file on the server + +git-ftp stores the deployed commit id in `.git-ftp.log` in the deployment +directory. Some hardened FTP servers refuse to write names starting with a dot; +give the file another name then: + +```sh +git config git-ftp.deployedsha1file gitftp.log +``` + +Both implementations use the same file, so changing the name affects any +machine deploying this repository. + +## Consistent uploads while editing + +git-ftp reads the files it uploads from the working tree. If you edit them +while a long deployment runs, the server can end up with contents that do not +match the commit. + +The Python implementation can read them from a throwaway Git worktree checked +out at the commit being deployed instead: + +```sh +git ftp push --worktree +git config git-ftp.worktree true # or always +``` + +The worktree shares the object store, so only a working copy is written to +disk, and it is removed when the deployment finishes. Untracked files added +through `.git-ftp-include` are not part of the commit and are still read from +the live working tree. + +## Parallel transfers + +The Python implementation uses four connections at once. Change that per run or +per repository: + +```sh +git ftp push --jobs 8 +git config git-ftp.jobs 1 # sequential, like the Bash version +``` + +Uploads run first, then deletes, and the log is written last, only when every +upload succeeded. The first failed upload stops the deployment with exit +code 4 and leaves the log untouched; a failed delete is a warning. Ctrl-C stops +promptly and exits with 130. diff --git a/docs/guide/download.md b/docs/guide/download.md new file mode 100644 index 0000000..72dbb83 --- /dev/null +++ b/docs/guide/download.md @@ -0,0 +1,78 @@ +--- +title: Downloading from the server +--- + +# Downloading from the server + +Sometimes the server is ahead: someone edited a file over FTP, or a CMS wrote +something. Three actions bring that back into your repository. + +!!! warning "These actions change your working tree" + + They delete local files that are not on the server, except ignored files, + `.git` and git-ftp's own files. Commit or stash your work first, and make + sure `.git-ftp-ignore` covers what must stay. + + In the Bash implementation these actions are experimental and need + [`lftp`](https://lftp.yar.ru/) installed. The Python implementation does it + itself. + +## download + +```sh +git ftp download +``` + +Mirrors the remote directory into the working tree and stops there, so you can +inspect the result with `git diff`. It refuses to run while the working tree +has untracked files. + +## pull + +`git ftp download` compares against whatever you have checked out, which is the +wrong baseline if you have local commits that were never deployed. `pull` uses +the deployed commit instead: + +```sh +git ftp pull +``` + +It does the equivalent of: + +```sh +git checkout +git ftp download +git add --all +git commit -m '[git-ftp] remotely untracked modifications' +git ftp catchup +git checkout +git merge +``` + +To look at the merge before it is committed: + +```sh +git ftp pull --no-commit +# inspect, then +git commit +# or +git merge --abort +``` + +After an abort the downloaded changes live on in an unreferenced commit until +Git's garbage collector runs. The commit id is printed, so you can tag it or +branch from it. + +`--changed-only` limits the transfer to files that also changed locally. + +## snapshot + +To turn a server that has never seen git-ftp into a repository: + +```sh +git ftp snapshot ftp://example.com/public_html projects/example +``` + +Downloads the remote directory into a new repository, commits it and records +that commit on the server, so the next `git ftp push` behaves as if you had run +`init`. diff --git a/docs/guide/hooks-and-locking.md b/docs/guide/hooks-and-locking.md new file mode 100644 index 0000000..13f7758 --- /dev/null +++ b/docs/guide/hooks-and-locking.md @@ -0,0 +1,91 @@ +--- +title: Hooks and locking +--- + +# Hooks and locking + +## Hooks + +git-ftp runs two client-side hooks during `init` and `push`, from the +repository's hooks directory (`.git/hooks`, or `core.hooksPath` if set). + +`pre-ftp-push` +: Runs after the list of changed files has been built, just before the first + upload. A non-zero exit aborts the deployment with exit code 9. Skipped + with `--no-verify`. + +`post-ftp-push` +: Runs after the transfer finished. Its exit status is ignored unless + `--enable-post-errors` is given. The Python implementation can skip it with + `--no-post-hooks`. + +Both receive four arguments: + +| | | +|---|---| +| `$1` | the scope name, or the host if no scope is used | +| `$2` | the URL being deployed to | +| `$3` | the local commit being uploaded | +| `$4` | the commit currently recorded on the server | + +`pre-ftp-push` additionally gets the change list on standard input: `A ` or +`D ` followed by the path, entries separated by NUL bytes. `A` means the file +is scheduled for upload, `D` for deletion. This list is not the same as +`git diff` — `.git-ftp-include` and `.git-ftp-ignore` have already been applied +to it, and it contains the syncroot if one is set. + +### An example + +`.git/hooks/pre-ftp-push`, executable, refusing to deploy a file that still +contains `TODO`: + +```bash +#!/bin/bash +# +# $1 -- scope name or host $3 -- local commit +# $2 -- URL $4 -- commit on the server +# +# stdin: NUL separated " " entries + +while read -r -d '' status file +do + if [ "$status" = "A" ]; then + if grep -q 'TODO' "$file"; then + echo "TODO found in $file, not uploading." + exit 1 + fi + fi +done + +exit 0 +``` + +Typical uses: building assets, clearing a cache through an HTTP call, posting a +deployment notice to chat, or refusing to deploy anything but a release tag. + +!!! note "Experimental in the Bash implementation" + + The interface may still change there. The Python implementation runs the + same hooks with the same arguments. + +## Locking + +Two deployments running at once would fight over the same files. `--lock` +prevents that: + +```sh +git ftp push --lock +``` + +Before the transfer, git-ftp writes `git-ftp.lck` on the server containing the +commit id and the user. While it exists, a deployment of a *different* commit +refuses to run and exits with code 7. A deployment of the same commit is +treated as your own and proceeds. The file is removed when the deployment +finishes. + +If a deployment was interrupted and left the lock behind: + +```sh +git ftp unlock # Python implementation +git ftp push --force # ignore the lock and deploy anyway +``` diff --git a/docs/guide/protocols.md b/docs/guide/protocols.md new file mode 100644 index 0000000..b0836b4 --- /dev/null +++ b/docs/guide/protocols.md @@ -0,0 +1,168 @@ +--- +title: Protocols and credentials +--- + +# Protocols and credentials + +## The URL + +```text +protocol://host.domain.tld:port/path +``` + +```sh +git ftp push ftp://host.example.com:2121/mypath +``` + +| URL | Transport | Encryption | +|---|---|---| +| `ftp://host/path` | curl / libcurl | none — the password crosses the network in clear | +| `ftpes://host/path` | curl / libcurl | explicit TLS (`AUTH TLS`) | +| `ftps://host/path` | curl / libcurl | implicit TLS, usually port 990 | +| `sftp://host/path` | curl (Bash) / paramiko (Python) | SSH | + +`ftp://` is the default when no protocol is given, and `host:port/path` without +a scheme is understood as FTP. + +Credentials may be part of the URL — `ftp://alice:s3cret@example.com/htdocs` — +which is how `add-scope` stores them. The Python implementation never prints +them back or passes them on a command line. + +!!! tip "Prefer an encrypted protocol" + + Plain FTP sends the password and every file unencrypted. If the server + offers FTPES or SFTP, use it. + +## SFTP paths + +Relative and absolute paths are not the same thing over SSH: + +```sh +sftp://example.com/public_html # relative to the login directory +sftp://example.com/~/public_html # relative to the home directory +sftp://example.com//var/www # absolute +``` + +## SFTP keys and host keys + +```sh +git ftp push -u alice --key ~/.ssh/id_ed25519 sftp://example.com/~/public_html +``` + +The public key is guessed as `.pub`; `--pubkey` sets it explicitly. The +Python implementation also takes `--key-passphrase` for an encrypted key, asks +for the passphrase interactively, and uses a running `ssh-agent` +(`SSH_AUTH_SOCK`) if there is one. + +It verifies the server's host key against `~/.ssh/known_hosts` and +`/etc/ssh/ssh_known_hosts`. Add an unknown server first: + +```sh +ssh-keyscan -p 22 example.com >> ~/.ssh/known_hosts +``` + +The Bash implementation accepts any host key silently. + +## TLS certificates + +Certificates are verified. For a private CA: + +```sh +git ftp push --cacert /etc/ssl/certs/ca.pem +``` + +`--insecure` turns verification off — for TLS certificates and, in the Python +implementation, for SFTP host keys as well. It leaves you open to a +man-in-the-middle, but it is still better than plain FTP. + +## Passive, active, EPSV + +FTP data connections default to EPSV. Depending on the network that can fail: + +```sh +git ftp push --disable-epsv # use PASV +git ftp push --active # the original active mode +``` + +Neither applies to SFTP. A proxy is passed through with `-x`: + +```sh +git ftp push -x http://proxy.example.com:3128 +``` + +## Credentials + +There are more ways to hand over a password than putting it into the config. + +### Ask interactively + +```sh +git ftp push -u alice -P ftp://example.com/htdocs +``` + +### From a password manager + +The Python implementation runs a command and uses the first line of its output: + +```sh +git config git-ftp.password-command "pass show example.com/ftp" +``` + +### From the environment + +```sh +GIT_FTP_USER=alice GIT_FTP_PASSWORD="$SECRET" git ftp push +``` + +### From `~/.netrc` + +Both implementations fall back to `~/.netrc` when no user is given: + +```text +machine ftp.example.com +login alice +password SECRET +``` + +```sh +git ftp init ftp.example.com +``` + +### From the macOS keychain + +```sh +git ftp init --keychain account@host ftpes://host +git config git-ftp.keychain alice@example.com +``` + +Without a value, the account and host are guessed from the user and the URL. +Add an entry like this — the entry has to be an *internet* password, not the +generic one the Keychain Access app creates: + +```sh +security add-internet-password -a alice -r "ftp " -s example.com -w secr3t +``` + +`-r` is the protocol and must be exactly four characters: `"ftp "` for FTP and +for SSH with password authentication, `ftps` for FTPS and FTPES. The keychain +cannot unlock a password-protected SSH key. + +### Passwords with special characters + +Quote them, in single quotes: + +```sh +git ftp push --passwd '#my$fancy!secret' +git config git-ftp.password '#my$fancy!secret' +``` + +A password starting with a dash defeats quoting in the Bash implementation +([by design](https://github.com/git-ftp/git-ftp/issues/468)); use `git config`, +`-P` or `~/.netrc` instead. The Python implementation accepts it as +`-p=-secret`. + +!!! danger "Passwords on the command line" + + Anything in `argv` is visible to other users through `ps`. The Bash + implementation also hands the password to curl that way. Prefer `-P`, + `--password-command`, the environment or `~/.netrc` on shared machines. diff --git a/docs/guide/scopes.md b/docs/guide/scopes.md new file mode 100644 index 0000000..7f93466 --- /dev/null +++ b/docs/guide/scopes.md @@ -0,0 +1,71 @@ +--- +title: Scopes +--- + +# Scopes + +A scope is a named set of settings in the same repository — one for staging, +one for production, one for a customer's server. + +## Creating a scope + +The `add-scope` action takes everything from a URL: + +```sh +git ftp add-scope production ftp://alice:s3cret@live.example.com/htdocs +git ftp add-scope staging ftp://alice:s3cret@staging.example.com/htdocs +``` + +Or set the keys yourself: + +```sh +git config git-ftp.production.url "ftp://live.example.com/htdocs" +git config git-ftp.production.user "manager" +git config git-ftp.production.password "n0tThatSimp3l" +``` + +## Deploying to a scope + +```sh +git ftp push -s production +git ftp init -s staging +``` + +Keys the scope does not define fall back to the plain `git-ftp.*` keys, so +shared settings are written once: + +```sh +git config git-ftp.user alice # used by every scope +git config git-ftp.staging.url ftp://staging.example.com/htdocs +``` + +An explicitly empty scope value masks the plain value: + +```sh +git config git-ftp.staging.syncroot "" # no syncroot for staging +``` + +## The branch as the scope + +If a scope is named after a branch, `-s` without a value uses the current +branch's name: + +```sh +git checkout production +git ftp push -s # pushes to the "production" scope +``` + +That makes a deploy step in CI identical for every environment. + +## Removing a scope + +```sh +git ftp remove-scope staging +``` + +!!! warning "Scopes hold credentials" + + `add-scope` writes the password from the URL into `.git/config`. That file + is not committed, but it is readable by anyone with access to your + checkout. On shared machines and in CI, prefer + [other ways of passing a password](protocols.md#credentials). diff --git a/docs/guide/selecting-files.md b/docs/guide/selecting-files.md new file mode 100644 index 0000000..c0fb4ee --- /dev/null +++ b/docs/guide/selecting-files.md @@ -0,0 +1,131 @@ +--- +title: Selecting files +--- + +# Selecting files + +By default git-ftp uploads every file Git tracks. Three mechanisms change that: +`.git-ftp-ignore` removes files, `.git-ftp-include` adds untracked ones and +`--syncroot` deploys a subdirectory. + +## Ignoring files + +Put shell glob patterns into `.git-ftp-ignore` in the repository root, one per +line. Matching files are never uploaded and never deleted on the server. + +```text +config/* +*.txt +foobar.txt +.gitignore +*/.gitkeep +.git-ftp-ignore +.git-ftp-include +.gitlab-ci.yml +``` + +Two details differ from `.gitignore`: + +- `*` also matches `/`, so `config/*` covers everything below `config/`; +- a pattern has to match the **whole** path, not a part of it. To catch a file + in any directory, write `*/.gitkeep`, not `.gitkeep`. + +Paths are the Git paths, including the syncroot if one is set. + +!!! note "Since version 1.1.0" + + Older Bash versions interpreted the patterns as regular expressions. + Everything current treats them as globs. + +## Uploading untracked files + +Build output is often not in Git but still has to reach the server. +`.git-ftp-include` lists those files. + +**Always upload a file** — prefix it with `!`: + +```text +!VERSION.txt +``` + +**Upload a file when a tracked file changed** — `target:source`: + +```text +css/style.css:scss/style.scss +``` + +Whenever `scss/style.scss` changed since the deployed commit, `css/style.css` +is uploaded. List several sources for the same target if more than one can +trigger it: + +```text +css/style.css:scss/style.scss +css/style.css:scss/mixins.scss +``` + +**Upload a whole directory** — a target ending in `/`: + +```text +vendor/:composer.lock +``` + +Everything below `vendor/` is uploaded whenever `composer.lock` changed. Note +that this uploads all of it, including files already on the server, and never +deletes anything from that directory. + +If the target is missing locally, a change to its source deletes it on the +server. + +Paths are relative to the Git working directory. With `--syncroot`, the +**source** (right of the colon) is relative to the syncroot: + +```text +# syncroot "html": upload html/style.css when html/style.scss changed +html/style.css:style.scss +``` + +Prefix the source with `/` to point outside the syncroot, relative to the +repository root: + +```text +# syncroot "dist": upload dist/style.css when src/style.scss changed +dist/style.css:/src/style.scss +``` + +Include rules are applied first, ignore patterns afterwards — an ignored file +stays ignored even if an include rule names it. + +## Deploying a subdirectory + +If only one directory belongs on the server, and it should become the server's +root: + +```sh +git ftp push --syncroot public +git config git-ftp.syncroot public # or once and for all +``` + +`public/index.html` then lands as `index.html` on the server. + +`--remote-root` is the counterpart on the other side: it sets the directory to +deploy **into**, replacing the path of the URL. + +```sh +git ftp push --remote-root htdocs +``` + +## Checking what will happen + +`--dry-run` prints the plan and transfers nothing: + +```sh +git ftp push --dry-run +``` + +Other useful selections: + +```sh +git ftp push -a # upload everything, not only the changes +git ftp push -c 1f2a3b4 # treat this commit as the deployed one +git ftp push -b staging # deploy another branch +``` diff --git a/docs/guide/troubleshooting.md b/docs/guide/troubleshooting.md new file mode 100644 index 0000000..6327a48 --- /dev/null +++ b/docs/guide/troubleshooting.md @@ -0,0 +1,144 @@ +--- +title: Troubleshooting +--- + +# Troubleshooting + +Start by asking git-ftp what it is doing: + +```sh +git ftp push -v # what happens, step by step +git ftp push -vv # everything, including the protocol trace +git ftp push --dry-run +``` + +## It wants me to run `git ftp init` + +```text +Could not get last commit. Network down? Wrong URL? Use 'git ftp init' for the initial push. +``` + +git-ftp could not read `.git-ftp.log` from the server. Either it really is not +there — then `git ftp init` (or `git ftp catchup`, if the files are already +uploaded) is right — or the server could not be reached at all. The Python +implementation distinguishes the two and tells you which one it is; with the +Bash implementation, check the URL, the credentials and the path first. + +If the server forbids files starting with a dot, give the log another name: + +```sh +git config git-ftp.deployedsha1file gitftp.log +``` + +## The connection hangs or times out + +Usually the FTP data connection, not the login. Try, in this order: + +```sh +git ftp push --disable-epsv # PASV instead of EPSV +git ftp push --active # active mode; needs an FTP-aware firewall +``` + +SFTP has none of these problems, if the server offers it. + +## Certificate or host key errors + +For FTPS and FTPES with a private or self-signed certificate: + +```sh +git ftp push --cacert /path/to/ca.pem +``` + +For SFTP with the Python implementation, an unknown host key is refused. Add +the server once: + +```sh +ssh-keyscan -p 22 example.com >> ~/.ssh/known_hosts +``` + +`--insecure` skips both checks. It is a last resort, not a fix. + +## `Protocol sftp not supported or disabled in libcurl` + +The curl in use has no SFTP support — the usual case on macOS with the Bash +implementation. Use the Python implementation, which speaks SFTP through +paramiko, or [build curl with libssh2](../getting-started/install.md#sftp-on-macos-with-the-bash-version). + +## Everything gets uploaded every time + +The commit recorded on the server is not in your clone, so git-ftp cannot +compute a difference. In CI this is almost always a shallow clone — see +[continuous deployment](ci-cd.md). Locally it happens after a force-push or a +rebase that dropped the deployed commit; `git ftp catchup` on the right commit +sets the state straight. + +## Files appear with mangled names + +Names containing `%`, `?`, `[`, `]`, `"` or a backslash were mangled by the +Bash implementation on the way into curl. The Python implementation transfers +them intact. + +## The password is not accepted + +- Quote it in single quotes: `--passwd '#my$fancy!secret'`. +- A password starting with a dash cannot be passed with `--passwd` in the Bash + implementation; use `git config`, `-P` or `~/.netrc`. +- `-P` asks interactively and avoids all quoting questions. +- Check that no scope is overriding the password: `git config --get-regexp '^git-ftp\.'`. + +## A deployment was interrupted and now everything is locked + +```text +Fatal: Repository is locked by +``` + +```sh +git ftp unlock # Python implementation +git ftp push --force # ignore the lock +``` + +The lock file is `git-ftp.lck` in the deployment directory; deleting it by hand +works too. + +## The uploaded files do not match the commit + +Something edited the working tree while the upload ran. Deploy from a +consistent snapshot instead: + +```sh +git ftp push --worktree # Python implementation +``` + +## Large files are uploaded as text stubs + +With Git LFS, git-ftp uploads what Git tracks, which is the pointer file. Fetch +the real contents first: + +```sh +git lfs pull +git ftp push +``` + +## Exit codes + +| Code | Meaning | +|---|---| +| 0 | success | +| 1 | unexpected error | +| 2 | wrong usage | +| 3 | missing argument | +| 4 | error while uploading (also: remote unreachable, login failed) | +| 5 | error while downloading (also: `push` before `init`) | +| 6 | unknown protocol | +| 7 | remote locked | +| 8 | Git error — not a repository, dirty working tree, bad branch | +| 9 | hook failed | +| 10 | local filesystem error | +| 130 | interrupted | + +## Still stuck? + +Open an issue with the output of `git ftp push -vv`, with credentials removed: + +- [git-ftp (Python) issues](https://github.com/git-ftp/git-ftp-py/issues) +- [git-ftp (Bash) issues](https://github.com/git-ftp/git-ftp/issues) diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..33e1049 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,141 @@ +--- +title: Upload to FTP servers the Git way +--- + +# Git-ftp + +**Upload to FTP servers the Git way.** + +If you use Git and you need to get your files onto an FTP server, Git-ftp saves +you time and bandwidth by uploading only the files that changed since the last +upload. + +It records the deployed commit in a log file on the server and uses Git to work +out which local files differ from it. Nothing has to be installed on the server, +and you can just as well deploy another branch or go back in history to upload +an older version. + +
+ +- :material-download: **Install** + + --- + + `pip install git-ftp`, a package from your distribution, or a single shell + script. + + [:octicons-arrow-right-24: Installation](getting-started/install.md) + +- :material-rocket-launch-outline: **First deployment** + + --- + + From an empty server to `git ftp push` in five commands. + + [:octicons-arrow-right-24: Get started](getting-started/first-deployment.md) + +- :material-cog-outline: **Configuration** + + --- + + Store the URL, the user and the password once, then deploy with a single + command. + + [:octicons-arrow-right-24: Configuration](guide/configuration.md) + +- :material-book-open-variant: **Manual** + + --- + + Every action, option and configuration key of both implementations. + + [:octicons-arrow-right-24: Reference](reference/manual-python.md) + +
+ +## In a nutshell + +=== "Python" + + ```sh + # Install + pip install git-ftp + + # Setup + git config git-ftp.url "ftp://ftp.example.net:21/public_html" + git config git-ftp.user "ftp-user" + git config git-ftp.password "secr3t" + + # Upload all files + git ftp init + + # Or, if the files are already on the server + git ftp catchup + + # Work and deploy + echo "new content" >> index.txt + git commit index.txt -m "Add new content" + git ftp push + # 1 file to sync: + # [1 of 1] Buffered for upload 'index.txt'. + # Uploading ... + # Last deployment changed from 1f2a3b4 to ded01b27e5c785fb251150805308d3d0f8117387. + ``` + +=== "Bash" + + ```sh + # Setup + git config git-ftp.url "ftp://ftp.example.net:21/public_html" + git config git-ftp.user "ftp-user" + git config git-ftp.password "secr3t" + + # Upload all files + git ftp init + + # Or, if the files are already on the server + git ftp catchup + + # Work and deploy + echo "new content" >> index.txt + git commit index.txt -m "Add new content" + git ftp push + # 1 file to sync: + # [1 of 1] Buffered for upload 'index.txt'. + # Uploading ... + # Last deployment changed to ded01b27e5c785fb251150805308d3d0f8117387. + ``` + +If something goes wrong, add `-v` or `-vv` to see what happens on the wire. + +## Two implementations, one deployment + +There are two git-ftp programs, and this site documents both. + +| | [git-ftp][py] (Python) | [git-ftp][sh] (Bash) | +|---|---|---| +| Install | `pip install git-ftp` | package manager, or one shell script | +| Needs | Python 3.10+, `git` | `curl`, `git`, a POSIX shell | +| Protocols | FTP, FTPS, FTPES, SFTP | FTP, FTPS, FTPES, SFTP | +| Transfers | parallel (`--jobs`, default 4) | sequential | +| `download` / `pull` / `snapshot` | built in | needs `lftp` | +| Status | actively developed | the classic, in most distributions | + +They read the same configuration, write the same `.git-ftp.log` and honour the +same `.git-ftp-ignore` and `.git-ftp-include` files, so a deployment made with +one can be continued with the other. + +[:octicons-arrow-right-24: Which one should I use?](getting-started/implementations.md) + +## Limitations + +- **git-ftp is not a centralised deployment tool.** While a commit is being + uploaded, the files belonging to it must stay untouched — no commits, no + checkouts, no edits — or the uploaded contents will not match the commit. + The Python implementation can take that worry away with + [`--worktree`](guide/configuration.md#consistent-uploads-while-editing). +- **Windows and macOS are lightly tested**, especially for the Bash version. + Bug reports and fixes for those platforms are very welcome. + +[py]: https://github.com/git-ftp/git-ftp-py +[sh]: https://github.com/git-ftp/git-ftp diff --git a/docs/reference/compatibility.md b/docs/reference/compatibility.md new file mode 100644 index 0000000..a173bfc --- /dev/null +++ b/docs/reference/compatibility.md @@ -0,0 +1,139 @@ +--- +title: Compatibility between the implementations +--- + +# Compatibility between the implementations + +What the Python port keeps identical to the Bash original, which of its bugs it fixes, and where it deliberately behaves differently. + +!!! info "Synced from upstream" + + This page is generated from [its source](https://github.com/git-ftp/git-ftp-py/blob/main/COMPATIBILITY.md) + in the tool's repository and is refreshed every time the site + is built. Edit it there, not here. + +## Compatibility with git-ftp 1.6.0 + +This Python git-ftp is a port of the Bash [git-ftp](https://github.com/git-ftp/git-ftp), +version 1.6.0. The rule of the port is: **keep everything on the wire and in +configuration identical, fix the bugs, and write down every deviation here.** + +A remote deployed with the Bash git-ftp can be picked up by this port and vice +versa. The deployed-commit file (`.git-ftp.log`), its content, the config keys, +the `.git-ftp-ignore` and `.git-ftp-include` formats, the hook names and +arguments, the exit codes and the default progress messages are unchanged. + +### Unchanged + +| Area | Status | +|---|---| +| Actions `init`, `push`, `catchup`, `show`, `log`, `download`, `pull`, `snapshot`, `add-scope`, `remove-scope`, `help`, `version` | Same names, same semantics | +| Options | Every 1.6.0 option is accepted with the same spelling | +| Config keys `git-ftp.*` and `git-ftp..*`, `.git-ftp-config` | Same keys, same precedence, an explicit empty value still overrides | +| `.git-ftp.log` (or `git-ftp.deployedsha1file`) | Same location and content (commit id plus newline) | +| `.git-ftp-ignore` | Same glob semantics: `*` crosses `/`, patterns match the whole Git path including the syncroot | +| `.git-ftp-include` | Same `!target` and `target:source` forms, same directory/missing-target rules, applied before ignore | +| Hooks `pre-ftp-push`, `post-ftp-push` | Same arguments and stdin format | +| Exit codes | 2 usage, 3 missing argument, 4 upload, 5 download, 6 unknown protocol, 7 locked, 8 git, 9 hook, 10 filesystem | +| Progress output | `N file(s) to sync:`, `[i of N] Buffered for upload '…'.`, `Uploading ...`, `Last deployment changed from … to ….`, etc. | +| `~/.netrc` fallback when no user is given | Same, now also for sftp | +| Remote lock semantics | Same file (`git-ftp.lck`), checked and written only with `--lock`, same "a lock for my own commit is mine" rule | +| Empty remote directories | Kept, as upstream since 1.1.0 (issue #168) | + +### Upstream bugs that are fixed + +These are behaviours of the Bash script that are clearly unintended. Each is +fixed rather than reproduced. + +- `--no-verify` and `--enable-post-errors` swallowed the argument after them. +- `--key=FILE`, `--pubkey=FILE` and `--branch=NAME` were not parsed. +- `--silent` also suppressed fatal error messages, and `-v` sent them to stdout. + Fatal errors now always go to stderr, at every verbosity. +- `--remote-root` was prepended to the URL's path although the manual says it + replaces it. It replaces it. +- `--lock` toggled: giving it twice turned locking off. It is a plain flag. +- The lock file's two lines were joined by a literal `\n`. A real newline is + written; both forms are read. +- The lock file was named after the script's own file name. The name is fixed. +- `ftp://user@host/path` took `user` for the host. Userinfo in a URL is parsed + as credentials. +- `host:2121/path` without a scheme was rejected as an unknown protocol even + though the built-in help shows that form. It is accepted as ftp. +- `add-scope` gave up on a URL whose password contained a colon or an at-sign. +- File names containing `%`, `?`, `[`, `]`, `"` or a backslash were mangled on + the way into curl. They are transferred intact. So is a file named `-`. +- `git-ftp.no-commit false` enabled the option. Booleans are parsed as Git + parses them (`true/yes/on/1` and `false/no/off/0`). +- A push whose remote log could not be read for *any* reason said + "use 'git ftp init'". That message is now reserved for a genuinely missing + log; a wrong password or an unreachable host is reported as what it is. +- The delete phase ran a directory listing of the remote root just to have a + transfer to attach `-Q` commands to. Deletes are plain `DELE` commands. +- Files whose type changed (a file becoming a symlink or vice versa, `T` in + `git diff`) were never uploaded. They are. +- Running from a subdirectory of the repository resolved `--syncroot` and the + submodule list against the wrong directory. +- `catchup` with more than one submodule only handled the first one, and + `cd`-ed deeper with every iteration. +- Passwords were visible on the curl command line (`ps`). Credentials are + passed to libcurl in memory and never appear in URLs, logs or argv. +- Temporary files were left behind on Ctrl-C. There are no temporary files. + +### New + +- **Parallel transfers**: `--jobs N` / `git-ftp.jobs` (default 4, `1` is + sequential). Uploads run first, then deletes, and the commit log is written + last, only when every upload succeeded. The first failed upload stops the + deploy (exit 4); failed deletes are warnings, as upstream. +- Native `download`, `pull` and `snapshot`: no `lftp` needed. Listings use + `MLSD` with a `LIST` fallback; downloads are parallel too. +- Submodules are deployed in-process (no recursive `git-ftp` invocation). +- Environment variables `GIT_FTP_URL`, `GIT_FTP_USER`, `GIT_FTP_PASSWORD`, + read after the command line and before Git configuration. +- `--password-command` / `git-ftp.password-command`: a shell command whose + first output line is the password. +- `unlock` action to remove a stale lock left by an interrupted deploy. +- `--key-passphrase` / `git-ftp.key-passphrase`, and an interactive prompt, + for encrypted SFTP keys. Upstream cannot use an encrypted key at all. +- SFTP authentication through a running `ssh-agent` (`SSH_AUTH_SOCK`). +- `--password` as an alias of `--passwd`; `--no-post-hooks`. +- Ctrl-C stops the transfers promptly and exits with 130. +- `version -v` prints the libcurl and paramiko versions in use. +- An interactive terminal shows a progress spinner with a `done/total` count + while files upload, delete or download; it renders on stderr and is silent + when output is not a terminal or under `-n`. +- `--worktree` / `git-ftp.worktree`: `init` and `push` read the upload from a + temporary Git worktree checked out at the deployed commit, so edits to the + working tree during the upload cannot leak in. Untracked files added by + `.git-ftp-include` are read from the live working tree, since a worktree of the + commit cannot contain them. + +### Behaviour that differs on purpose + +- **SFTP host keys are verified** against `~/.ssh/known_hosts` and + `/etc/ssh/ssh_known_hosts`. Upstream accepted any host key silently. An + unknown host is refused with a pointer to `ssh-keyscan`; `--insecure` turns + verification off, as it does for TLS. +- **SFTP is spoken by paramiko**, not libcurl. FTP, FTPS and FTPES use libcurl + through pycurl. +- **`git` is required** at run time (the port shells out to it, as upstream). +- A valueless boolean key such as a bare `insecure` line in `.git-ftp-config` + reads as **true**, exactly as `git config` reads it. +- Diagnostics printed with `-v` go to stderr with a timestamp; progress lines + stay on stdout. Warnings (`WARNING: …`) go to stderr at every verbosity. +- `ftps://` and `ftpes://` require TLS 1.2 or newer and encrypt the data + connection as well as the control connection. +- `-u`, `-s` and `-k` without an argument work as upstream (local user, current + branch, default keychain item). An action name after a bare flag (`-u push`) + is recognised as the action. +- The `Insecure is 'N'.` and `Disable EPSV is '1'.` diagnostics are printed + once per repository (twice with one submodule), as upstream. +- `download --changed-only` uses the deployed commit to pick the files + (upstream ignored the flag for `download`). +- The macOS keychain is read through the `security` command on macOS and + ignored elsewhere, as upstream. Keychain entries cannot unlock SSH keys. + +### Not implemented + +- `download`, `pull` and `snapshot` never print lftp's transfer log; they print + a one-line summary instead (`Downloaded N file(s), deleted M local file(s).`). diff --git a/docs/reference/manual-bash.md b/docs/reference/manual-bash.md new file mode 100644 index 0000000..c6c9b94 --- /dev/null +++ b/docs/reference/manual-bash.md @@ -0,0 +1,692 @@ +--- +title: Manual — git-ftp (Bash) +--- + +# Manual — git-ftp (Bash) + +The complete manual page of the original Bash implementation, the one `git ftp help` and `man git-ftp` show. + +!!! info "Synced from upstream" + + This page is generated from [its source](https://github.com/git-ftp/git-ftp/blob/master/man/git-ftp.1.md) + in the tool's repository and is refreshed every time the site + is built. Edit it there, not here. + +## NAME + +Git-ftp - Git powered FTP client written as shell script. + +## SYNOPSIS + +*git-ftp* <action> [<options>] [<url>] + +## DESCRIPTION + +Git-ftp is an FTP client using [Git] to determine which local files to upload or +which files to delete on the remote host. + +It saves the deployed state by uploading the SHA1 hash in the `.git-ftp.log` +file. There is no need for Git to be installed on the remote host. + +Even if you play with different branches, git-ftp knows which files are +different and handles only those files. That saves time and bandwidth. + +## ACTIONS + +`init` +: Uploads all git-tracked non-ignored files to the remote server and + creates the `.git-ftp.log` file containing the SHA1 of the latest + commit. + +`catchup` +: Creates or updates the `.git-ftp.log` file on the remote host. + It assumes that you uploaded all other files already. + You might have done that with another program. + +`push` +: Uploads files that have changed and + deletes files that have been deleted since the last upload. + If you are using GIT LFS, this uploads LFS link files, + not large files (stored on LFS server). + To upload the LFS tracked files, run `git lfs pull` + before `git ftp push`: LFS link files will be replaced with + large files so they can be uploaded. + +`download` (EXPERIMENTAL) +: Downloads changes from the remote host into your working tree. + This feature needs lftp to be installed and does not use any power of + Git. + WARNING: It can delete local untracked files that are not listed in + your `.git-ftp-ignore` file. + +`pull` (EXPERIMENTAL) +: Downloads changes from the remote host into a separate commit + and merges that into your current branch. + If you just want to download the files without a merge, consider `download`. + This feature needs lftp to be installed. + +`snapshot` (EXPERIMENTAL) +: Downloads files into a new Git repository. Takes an additional + argument as local destination directory. Example: + `git-ftp snapshot ftp://example.com/public_html projects/example` + This feature needs lftp to be installed. + +`show` +: Downloads last uploaded SHA1 from log and hooks `git show`. + +`log` +: Downloads last uploaded SHA1 from log and hooks `git log`. + +`add-scope ` +: Creates a new scope (e.g. dev, production, testing, foobar). + This is a wrapper action over git-config. + See **SCOPES** section for more information. + +`remove-scope ` +: Remove a scope. + +`help` +: Shows a help screen. + +## OPTIONS + +`-u [username]`, `--user [username]` +: FTP login name. If no argument is given, local user will be taken. + +`-p [password]`, `--passwd [password]` +: FTP password. See `-P` for interactive password prompt. ([note](#passwords)) + +`-P`, `--ask-passwd` +: Ask for FTP password interactively. + +`-k [[account]@[host]]`, `--keychain [[account]@[host]]` +: FTP password from KeyChain (macOS only). + +`-a`, `--all` +: Uploads all files of current Git checkout. + +`-c`, `--commit` +: Sets SHA1 hash of last deployed commit by option. + +`-A`, `--active` +: Uses FTP active mode. This works only if you have either no firewall + and a direct connection to the server or an FTP aware firewall. If you + don't know what it means, you probably won't need it. + +`-b [branch]`, `--branch [branch]` +: Push a specific branch + +`-s [scope]`, `--scope [scope]` +: Using a scope (e.g. dev, production, testing, foobar). See **SCOPE** + and **DEFAULTS** section for more information. + +`-l`, `--lock` +: Enable remote locking. + +`-D`, `--dry-run` +: Does not upload or delete anything, but tries to get the `.git-ftp.log` + file from remote host. + +`-f`, `--force` +: Does not ask any questions, it just does. + +`-n`, `--silent` +: Be silent. + +`-h`, `--help` +: Prints some usage information. + +`-v`, `--verbose` +: Be verbose. + +`-vv` +: Be as verbose as possible. Useful for debug information. + +`--remote-root` +: Specifies the remote root directory to deploy to. + The remote path in the URL is ignored. + +`--syncroot` +: Specifies a local directory to sync from as if it were the git project + root path. + +`--key` +: SSH private key file name for SFTP. + +`--pubkey` +: SSH public key file name. Used with --key option. + +`--insecure` +: Don't verify server's certificate. + +`--cacert ` +: Use as CA certificate store. + Useful when a server has a self-signed certificate. + +`--disable-epsv` +: Tell curl to disable the use of the EPSV command when doing passive FTP + transfers. + Curl will normally always first attempt to use EPSV before PASV, + but with this option, it will not try using EPSV. + +`--no-commit` +: Stop while merging downloaded changes during the pull action. + A commit is made anyway, but the merge is interrupted. + If you just want to download the files you could also consider the action + `download`. + +`--changed-only` +: During the ftp mirror operation during a pull command, consider only + the files changed since the deployed commit. + +`--no-verify` +: Bypass the pre-ftp-push hook. See **HOOKS** section. + +`--enable-post-errors` +: Fails if post-ftp-push raises an error. + +`--auto-init` +: Automatically run init action when running push action + +`--version` +: Prints version. + +`-x [protocol://]host[:port]`, `--proxy [protocol://]host[:port]` +: Use the specified proxy. This option is passed to curl. + See the curl manual for more information. + +## URL + +The scheme of an URL is what you would expect + + protocol://host.domain.tld:port/path + +Below a full featured URL to *host.example.com* on port *2121* to path *mypath* +using protocol *ftp*: + + ftp://host.example.com:2121/mypath + +But, there is not just FTP. Supported protocols are: + +`ftp://...` +: FTP (default if no protocol is set) + +`sftp://...` +: SFTP + +`ftps://...` +: FTPS + +`ftpes://...` +: FTP over explicit SSL (FTPES) protocol + +## EXAMPLES + +### FIRST UPLOADS + +Upload your files to an FTP server the first time: + + $ git ftp init -u "john" -P "ftp://example.com/public_html" + +It will authenticate with the username `john` and ask for the +password. By default, it tries to transfer data in EPSV mode. +Depending on the network and server configuration, that may fail. +You can try to add the `--disable-epsv` option to use the IPv4 passive FTP +connection (PASV). In rare circumstances, you can use `--active` for the +original FTP transfer mode. These options do not apply to SFTP. + +You are less likely to face connection problems with SFTP. +But be aware of the different +handling of relative and absolute paths. If the directory `public_html` is in +the home directory on the server, then upload like this: + + $ git ftp init -u "john" --key "$HOME/.ssh/id_rsa" "sftp://example.com/~/public_html" + +Otherwise it will use an absolute path, for example: + + $ git ftp init -u "john" --key "$HOME/.ssh/id_rsa" "sftp://example.com/var/www" + +On some systems Git-ftp fails to verify the server's fingerprint. +You can then use the `--insecure` option to skip the verification. +That will leave you vulnerable to man-in-the-middle attacks, but is still more +secure than plain FTP. + +Git-ftp guesses the path of the public key file corresponding to your private +key file. If you just have a private key, for example a .pem file, you need +Git-ftp version 1.3.4 and Curl version 7.39.0 or newer. +If you have an older version of Git-ftp or Curl, you can +create the public key with the ssh-keygen command: + + $ ssh-keygen -y -f key.pem > key.pem.pub + +### RESET THE UPLOADED STATE + +Many people already uploaded their files to the server. +If you want to mark the uploaded version as the same as your local branch: + + $ git ftp catchup + +This example omits options like `--user`, `--password` and `url`. +See DEFAULTS below to learn how to store your configuration so that you don't +need to repeat it. + +After you stored the commit id of the uploaded commit via `init` or +`catchup`, you can then upload any new commits: + + $ git ftp push + +If you discovered a bug in the last uploaded version and you want to go back +by three commits: + + $ git checkout HEAD~3 + $ git ftp push + +Or maybe some files got changed on the server and you want to upload all +changes between branch `master` and branch `develop`: + + $ git checkout develop # This is the version which is uploaded. + $ git ftp push --commit master # Upload changes compared to master. + +## DEFAULTS + +Don't repeat yourself. Setting config defaults for git-ftp in .git/config + + $ git config git-ftp.<(url|user|password|syncroot|cacert|keychain|...)> + +Everyone likes examples: + + $ git config git-ftp.user john + $ git config git-ftp.url ftp.example.com + $ git config git-ftp.password secr3t + $ git config git-ftp.syncroot path/dir + $ git config git-ftp.cacert caCertStore + $ git config git-ftp.deployedsha1file mySHA1File + $ git config git-ftp.insecure 1 + $ git config git-ftp.key ~/.ssh/id_rsa + $ git config git-ftp.keychain user@example.com + $ git config git-ftp.remote-root htdocs + $ git config git-ftp.disable-epsv 1 + $ git config git-ftp.no-commit 1 + +After setting those defaults, push to *john@ftp.example.com* is as simple as + + $ git ftp push + +If you run into issues with setting up your password please check this [note](#passwords). + +## SCOPES + +Need different config defaults per each system or environment? Use the so +called scope feature. + +Useful if you use multi environment development. Like a development, testing +and a production environment. + + $ git config git-ftp..<(url|user|password|syncroot|cacert)> + +So in the case below you would set a testing scope and a production scope. + +Here we set the params for the scope "testing" + + $ git config git-ftp.testing.url ftp.testing.com:8080/foobar-path + $ git config git-ftp.testing.password simp3l + +Here we set the params for the scope "production" + + $ git config git-ftp.production.user manager + $ git config git-ftp.production.url live.example.com + $ git config git-ftp.production.password n0tThatSimp3l + +Pushing to scope *testing* alias *john@ftp.testing.com:8080/foobar-path* +using password *simp3l* + + $ git ftp push -s testing + +*Note:* The **SCOPE** feature can be mixed with the **DEFAULTS** feature. +Because we didn't set the user for this scope, +git-ftp uses *john* as user as set before in **DEFAULTS**. + +Pushing to scope *production* alias *manager@live.example.com* using +password *n0tThatSimp3l* + + $ git ftp push -s production + +*Hint:* If your scope name is identical with your branch name. You can skip the +scope argument, e.g. if your current branch is "production": + + $ git ftp push -s + +You can also create scopes using the add-scope action. +All settings can be defined in the URL. +Here we create the *production* scope using add-scope + + $ git ftp add-scope production ftp://manager:n0tThatSimp3l@live.example.com/foobar-path + +Deleting scopes is easy using the `remove-scope` action. + + $ git ftp remove-scope production + +## IGNORING FILES TO BE SYNCED + +Add patterns to `.git-ftp-ignore` and all matching file names will be ignored. +The patterns are interpreted as shell glob patterns since version 1.1.0. +Before version 1.1.0, patterns were interpreted as regular expressions. +Here are some glob pattern examples: + +Ignoring everything in a directory named `config`: + + config/* + +Ignoring all files having extension `.txt`: + + *.txt + +Ignoring a single file called `foobar.txt`: + + foobar.txt + +Ignoring Git related files: + + .gitignore + */.gitignore # ignore files in sub directories + */.gitkeep + .git-ftp-ignore + .git-ftp-include + .gitlab-ci.yml + +## SYNCING UNTRACKED FILES + +The `.git-ftp-include` file specifies intentionally untracked files that +Git-ftp should upload. +If you have a file that should always be uploaded, add a line beginning with ! +followed by the file's name. +For example, if you have a file called VERSION.txt then add the following line: + + !VERSION.txt + +If you have a file that should be uploaded whenever a tracked file changes, add +a line beginning with the untracked file's name followed by a colon and the +tracked file's name. +For example, if you have a CSS file compiled from an SCSS file then add the +following line: + + css/style.css:scss/style.scss + +If you have multiple source files, you can add multiple lines for each of them. +Whenever one of the tracked files changes, the upload of the paired untracked +file will be triggered. + + css/style.css:scss/style.scss + css/style.css:scss/mixins.scss + +If a local untracked file is deleted, any change of a paired tracked file will +trigger the deletion of the remote file on the server. + +All paths are usually relative to the Git working directory. +When using the `--syncroot` option, paths of tracked files +(right side of the colon) are relative to the set syncroot. +Example: + + # upload "html/style.css" triggered by html/style.scss + # with syncroot "html" + html/style.css:style.scss + +If your *source* file is outside the syncroot, +prefix it with a / and define a path relative +to the Git working directory. For example: + + # upload "dist/style.css" with syncroot "dist" + dist/style.css:/src/style.scss + +It is also possible to upload whole directories. +For example, if you use a package manager like composer, you can upload all +vendor packages when the file composer.lock changes: + + vendor/:composer.lock + +But keep in mind that this will upload all files in the vendor folder, even +those that are on the server already. +And it will not delete files from that directory if local files are deleted. + +## DOWNLOADING FILES (EXPERIMENTAL) + +**WARNING:** It can delete local untracked files that are not listed in your +`.git-ftp-ignore` file. + +You can use git-ftp to download from the remote host into your repository. +You will need to install the lftp command line tool for that. + + git ftp download + +It uses lftp's mirror command to download all files that are different on the +remote host. You can inspect the changes with git-diff. +But if you have some local commits that have not been uploaded to the remote +host, you may not compare to the right version. +You need to compare the downloaded files to the commit that was uploaded last. +This magic is done automatically by + + git ftp pull + +It does the following steps for you: + + git checkout + git ftp download + git add --all + git commit -m '[git-ftp] remotely untracked modifications' + git ftp catchup + git checkout + git merge + +If you want to inspect the downloaded changes before merging them into your +current branch, add the option `--no-commit`. +It will stop during the merge at the end of the pull action. +You can inspect the merge result first and can then decide to continue or +abort. + + git ftp pull --no-commit + # inspect the result and commit them + git commit + # or abort the merge + git merge --abort + +If you abort the merge, the downloaded changes will stay in an unreferenced +commit until the Git garbage collector is run. +The commit id will be printed so that you can tag it or create a new branch. + +## HOOKS (EXPERIMENTAL) + +**This feature is experimental. The interface may change.** + +Git-ftp supports client-side hook scripts during the init and the push action. + +`pre-ftp-push` is called just before the upload to the server starts, but after +the changeset of files was generated. It can be bypassed with the --no-verify +option. + +The hook is called with four parameters. +The first is the used scope or the host name if no scope is used. +The second parameter is the destination URL. +The third is the local commit id which is going to be uploaded and +the fourth is the remote commit id on the server which is going to be updated. + +The standard input is a list of all filenames to sync. Each file is preceeded +by A or D followed by a space. A means that this file is scheduled for upload, +D means it's scheduled for deletion. All entries are separated by the NUL byte. +This list is different to git diff, because +it has been changed by the rules of the `.git-ftp-include` file and the +`.git-ftp-ignore` file. + +Exiting with non-zero status from this script causes +Git-ftp to abort and exit with status 9. + +An example script is: + +```bash +##!/bin/bash +## +## An example hook script to verify what is about to be uploaded. +## +## Called by "git ftp push" after it has checked the remote status, but before +## anything has been pushed. If this script exits with a non-zero status nothing +## will be pushed. +## +## This hook is called with the following parameters: +## +## $1 -- Scope name if set or host name of the remote +## $2 -- URL to which the upload is being done +## $3 -- Local commit id which is being uploaded +## $4 -- Remote commit id which is on the server +## +## Information about the files which are being uploaded or deleted is supplied +## as NUL separated entries to the standard input in the form: +## +## +## +## The status is either A for upload or D for delete. The path contains the +## path to the local file. It contains the syncroot if set. +## +## This sample shows how to prevent upload of files containing the word TODO. + +remote="$1" +url="$2" +local_sha="$3" +remote_sha="$4" + +while read -r -d '' status file +do + if [ "$status" = "A" ] + then + if grep 'TODO' "$file"; then + echo "TODO found in file $file, not uploading." + exit 1 + fi + fi +done + +exit 0 +``` + +`post-ftp-push` is called after the transfer has been finished. The standard +input is empty, but the parameters are the same as given to the `pre-ftp-push` +hook. This hook is **not** bypassed by the --no-verify option. +It is meant primarily for notification and its exit status does not have any +effect. + +## PASSWORDS + +If your password contains special characters you have to take it with care. In most cases it is a good idea to quote passwords with single quotes: + + --passwd '#my$fancy!secret' + +Mostly `--ask-passwd` works even if `--passwd` does not work. So maybe you can give this a try. + +If your password starts with a hyphen/dash (`-`) even quoting might fail. +This is [by design](https://github.com/git-ftp/git-ftp/issues/468) and will not be fixed. +In this case you can use one of the other options to set your password: the defaults feature using `git config`, `--ask-passwd` or `~/.netrc`. + +Quoting also works if a [default](#defaults) is set with `git config`: + + $ git config git-ftp.password '#my$fancy!secret' + +### NETRC + +In the backend, Git-ftp uses curl. +This means `~/.netrc` could be used beside the other options of Git-ftp +to authenticate. + + $ editor ~/.netrc + machine ftp.example.com + login john + password SECRET + +With git-ftp the credentials stored in this file are used if no username is set. +For example, if you set up your .netrc file like this you can just call + + git ftp init ftp.example.com + +Of course this can be combined with the [defaults feature](#defaults) to set config defaults for other options as well. + +### Keychain on macOS + +On macOS you can use the built in keychain to store and get your passwords. + +You can use this feature by using the option `--keychain` in your command: + + $ git ftp init --keychain account@host ftpes://host + +You can omit the value for this option. Then git-ftp will guess the account and hostname from user and url. + +Or you can set a config for this, so you don’t need to repeat yourself (see [defaults](#defaults) for details): + + $ git config git-ftp.keychain account@host + +You can omit the hostname here. If there is no `@` in the config value git-ftp will guess the hostname from url. + +If you run a command using the keychain feature, the system might ask you if git-ftp is allowed to access the keychain entry. +If the keychain is locked you have to enter the keychain password (not the value of the entry), sometimes twice. + +If your password is not in your keychain yet it is recommended adding it using the following command: + + $ security add-internet-password -a account -r "ftp " -s host -w secr3t + +The options are: +- `-a`: user account +- `-r`: protocol; has to be exactly 4 characters long, so if you use `FTP` it should be `"ftp "`, for `FTPS` and `FTPES` use `ftps` + and for SSH with password auth you can use `"ftp "` as well. +- `-s`: your host name; includes subdomains but no paths +- `-w`: password + +You can omit the option `-r` and everything will work fine, but the _Keychain Access_ Utility will not show the server in the field “Where:”. +This is only shown if `-r` and `-s` are set both. \ +If you create a keychain entry with the _Keychain Access_ Utility it creates a generic password and not an internet password. +Therefore, unfortunately, this will not work. + +Please not that the keychain entry can not be used for password protected private keys in SSH. + +## EXIT CODES + +There are a bunch of different error codes and their corresponding error +messages that may appear during bad conditions. +At the time of this writing, the exit codes are: + +`1` +: Unknown error + +`2` +: Wrong Usage + +`3` +: Missing arguments + +`4` +: Error while uploading + +`5` +: Error while downloading + +`6` +: Unknown protocol + +`7` +: Remote locked + +`8` +: Not a Git project + +`9` +: The `pre-ftp-push` hook failed + +`10` +: A local file operation like `cd` or `mkdir` failed + +## KNOWN ISSUES & BUGS + +The upstream BTS can be found at . + +[Git]: http://git-scm.org + +## AUTHORS + +Git-ftp was started by Rene Moser and is currently maintained by Maikel Linke. +Numerous contributions have come from GitHub users. +See the AUTHORS file for an incomplete list of contributors. diff --git a/docs/reference/manual-python.md b/docs/reference/manual-python.md new file mode 100644 index 0000000..6b46137 --- /dev/null +++ b/docs/reference/manual-python.md @@ -0,0 +1,374 @@ +--- +title: Manual — git-ftp (Python) +--- + +# Manual — git-ftp (Python) + +The complete manual page of the Python implementation, the one `git ftp help` and `man git-ftp` show. + +!!! info "Synced from upstream" + + This page is generated from [its source](https://github.com/git-ftp/git-ftp-py/blob/main/docs/git-ftp.1.md) + in the tool's repository and is refreshed every time the site + is built. Edit it there, not here. + +## NAME + +git-ftp - Git powered FTP, FTPS, FTPES and SFTP client + +## SYNOPSIS + +*git-ftp* <action> [<options>] [<url>] + +## DESCRIPTION + +This manual page documents *git-ftp*, a Python program that uploads the +files of a Git repository to a server over FTP, FTPS, FTPES or SFTP. Only the +files that changed since the last deployment are transferred. The deployed +commit is recorded in a file on the server, *.git-ftp.log* by default, so no +software has to be installed on the server. + +Git runs the program as *git ftp* when *git-ftp* is on the PATH. + +## ACTIONS + +*init* +: Uploads all tracked files that are not ignored and records the commit in + *.git-ftp.log* on the server. Fails when the server already has a log. + +*catchup* +: Only creates or updates *.git-ftp.log* on the server. Use it when the + server already holds the current files. + +*push* +: Uploads the files added or changed and deletes the files removed since the + deployed commit, then updates *.git-ftp.log*. With *--auto-init* a missing + log behaves like *init*. + +*download* +: Mirrors the remote directory into the working tree (files missing on the + server are removed locally; ignored files are left alone). Refuses to run + while the working tree has untracked files. + +*pull* +: Checks out the deployed commit, downloads the remote changes into a + commit, records that commit on the server and merges it into the current + branch. + +*snapshot* [<directory>] +: Downloads a remote directory that is not yet managed by git-ftp into a new + repository, commits it and records the commit on the server. + +*show* +: Runs *git show* on the deployed commit. + +*log* +: Runs *git log* on the deployed commit. + +*add-scope* <scope> <url> +: Stores the URL (and the credentials contained in it) under a scope name in + the repository's Git configuration. + +*remove-scope* <scope> +: Removes a scope from the Git configuration. + +*unlock* +: Removes a stale remote lock left by an interrupted deploy. + +*help* +: Prints the built-in help. + +*version* +: Prints the version; with *-v* also the libcurl and paramiko versions. + +## OPTIONS + +*-u [<username>]*, *--user [<username>]* +: FTP login name. Without a value the local user name is used. + +*-p <password>*, *--passwd <password>*, *--password <password>* +: FTP password. A value starting with a dash is accepted (*-p=-secret*). + +*-P*, *--ask-passwd* +: Ask for the password interactively. + +*--password-command <command>* +: Run a shell command and use the first line of its output as the password. + +*-k [[<account>]@[<host>]]*, *--keychain [[<account>]@[<host>]]* +: Read the password from the macOS keychain (ignored on other systems). + +*-a*, *--all* +: Upload all files instead of only the changed ones. + +*-c <commit>*, *--commit <commit>* +: Treat the given commit as the deployed one instead of reading the log. + +*-A*, *--active* +: Use FTP active mode. + +*-b [<branch>]*, *--branch [<branch>]* +: Deploy the given branch and switch back afterwards. + +*-s [<scope>]*, *--scope [<scope>]* +: Use the configuration of the given scope. Without a value the current + branch name is the scope. + +*-l*, *--lock* +: Write a lock file on the server for the duration of the deploy. + +*-D*, *--dry-run* +: Print what would be transferred without transferring anything. + +*-f*, *--force* +: Skip the lock check and the question about an unknown deployed commit. + +*-n*, *--silent* +: Print nothing but fatal errors. + +*-v*, *--verbose* +: Print diagnostics on stderr. *-vv* also prints the protocol trace. + +*-j <n>*, *--jobs <n>* +: Number of parallel connections (default 4). *1* transfers sequentially. + +*--remote-root <directory>* +: Remote directory to deploy into, replacing the path of the URL. + +*--syncroot <directory>* +: Deploy only this directory; it becomes the remote root. + +*--key <file>* +: SFTP private key. + +*--pubkey <file>* +: SFTP public key (defaults to *<key>.pub*). + +*--key-passphrase <text>* +: Passphrase of an encrypted SFTP private key. + +*--insecure* +: Do not verify TLS certificates or SFTP host keys. + +*--cacert <file>* +: CA certificate bundle used to verify FTPS/FTPES servers. + +*--disable-epsv* +: Use PASV instead of EPSV. + +*-x <url>*, *--proxy <url>* +: Proxy URL (also read from *git-ftp.proxy* and *http.proxy*). + +*--no-commit* +: *pull*: merge without committing. + +*--changed-only* +: *download*, *pull*: only transfer files that changed locally as well. + +*--no-verify* +: Skip the *pre-ftp-push* hook. + +*--no-post-hooks* +: Skip the *post-ftp-push* hook. + +*--enable-post-errors* +: Fail when the *post-ftp-push* hook fails. + +*--auto-init* +: *push*: behave like *init* when the server has no log yet. + +*--worktree* +: Read the files to upload from a temporary Git worktree checked out at the + commit being deployed, so edits to the working tree during the upload are + ignored. + +*--version* +: Print the version and exit. + +*-h*, *--help* +: Print the help and exit. + +## URL + +The URL has the form *protocol://host.domain.tld:port/path*. Supported +protocols are *ftp://* (the default when none is given), *ftpes://* (explicit +TLS), *ftps://* (implicit TLS) and *sftp://*. *host:port/path* without a +protocol is accepted as FTP. Credentials may be given as +*ftp://user:password@host/path*; the password never appears in any output. + +For SFTP, *sftp://host/dir* is relative to the login directory, +*sftp://host/~/dir* to the home directory and *sftp://host//dir* is absolute. + +## DEFAULTS + +Every option can be stored in the Git configuration: + + git config git-ftp.url ftp://example.com/public_html + git config git-ftp.user alice + git config git-ftp.password s3cret + git config git-ftp.password-command "pass show example.com/ftp" + git config git-ftp.syncroot public_html + git config git-ftp.remote-root htdocs + git config git-ftp.cacert /etc/ssl/certs/ca.pem + git config git-ftp.insecure true + git config git-ftp.disable-epsv true + git config git-ftp.proxy http://proxy:3128 + git config git-ftp.key ~/.ssh/id_ed25519 + git config git-ftp.pubkey ~/.ssh/id_ed25519.pub + git config git-ftp.key-passphrase ... + git config git-ftp.keychain alice@example.com + git config git-ftp.branch main + git config git-ftp.no-commit true + git config git-ftp.deployedsha1file .git-ftp.log + git config git-ftp.jobs 8 + git config git-ftp.worktree true + +The same keys may be placed in a *.git-ftp-config* file in the repository, +which takes precedence over the Git configuration. + +## ENVIRONMENT + +*GIT_FTP_URL*, *GIT_FTP_USER*, *GIT_FTP_PASSWORD* +: Read after the command line and before the Git configuration. + +*NETRC* +: Alternative *~/.netrc* file. The netrc file is consulted only when neither + a user nor a password was given by other means. + +*SSH_AUTH_SOCK* +: A running ssh-agent is used for SFTP authentication. + +## SCOPES + +Scopes hold a separate set of settings, for example for staging and +production: + + git config git-ftp.production.url ftp://live.example.com/htdocs + git config git-ftp.production.password s3cret + git ftp push -s production + +Unset scope keys fall back to the plain keys. An explicitly empty scope value +masks the plain value. *git ftp push -s* uses the current branch name as the +scope name. *add-scope* and *remove-scope* manage scopes from the command line. + +## IGNORING FILES + +*.git-ftp-ignore* in the repository root lists shell glob patterns, one per +line, of Git paths that are never uploaded (and never deleted). As in the +original, *\** also matches */* and the pattern has to match the whole path: + + config/* + *.txt + foobar.txt + .gitignore + */.gitkeep + .git-ftp-ignore + .git-ftp-include + +## SYNCING UNTRACKED FILES + +*.git-ftp-include* uploads files that are not tracked by Git: + + !VERSION.txt + css/style.css:scss/style.scss + css/style.css:scss/mixins.scss + vendor/:composer.lock + dist/style.css:/src/style.scss + +A line starting with *!* always uploads the file. *target:source* uploads +*target* whenever the tracked *source* changed since the deployed commit. With +*--syncroot* the source is relative to the syncroot unless it starts with */*. +A directory target uploads everything below it. A target that no longer exists +locally is deleted on the server. Ignore patterns still apply afterwards. + +## PARALLEL TRANSFERS + +Files are transferred over several connections at once (*--jobs*, default 4). +Uploads happen first, then deletes, then the log is written. The first failed +upload aborts the deploy with exit code 4 and the log is left untouched; a +failed delete is only a warning. Ctrl-C stops the transfers and exits with 130. + +## WORKTREE + +With *--worktree* (or *git config git-ftp.worktree true*) *init* and *push* check +the deployed commit out into a throwaway Git worktree and read the uploaded file +contents from there. Editing the working tree while a long upload is running then +cannot change what is deployed. The worktree shares the object store, so only a +working copy is written to disk; it is removed when the deploy finishes. Untracked +files added through *.git-ftp-include* are not part of the commit and are read from +the live working tree as before. + +## LOCKING + +With *--lock* a file *git-ftp.lck* containing the commit id and the user is +written on the server before the transfer and removed afterwards. A deploy of +a different commit refuses to run while the lock exists (exit code 7); *--force* +ignores the lock and *unlock* removes it. + +## DOWNLOADING + +*download*, *pull* and *snapshot* list the remote directory (MLSD, falling back +to LIST) and download files that are missing locally or differ in size or +modification time. Files missing on the server are deleted locally unless they +are ignored, part of *.git* or one of git-ftp's own files. + +## HOOKS + +*pre-ftp-push* and *post-ftp-push* in the repository's hooks directory +(*core.hooksPath* is honoured) are run with four arguments: the scope name or +host, the display URL, the local commit and the deployed commit (the previous +one for the post hook). The pre hook receives the NUL separated change list +(*A path* / *D path*) on stdin; a non-zero exit aborts the deploy with exit code +9. The post hook's exit status is ignored unless *--enable-post-errors* is +given. + +## SFTP HOST KEYS + +Host keys are verified against *~/.ssh/known_hosts* and +*/etc/ssh/ssh_known_hosts*. Add a server with + + ssh-keyscan -p 22 host >> ~/.ssh/known_hosts + +or pass *--insecure* to skip the verification. + +## EXIT CODES + +0 +: success + +1 +: unexpected error + +2 +: wrong usage + +3 +: missing argument + +4 +: error while uploading (also: remote not reachable, login failed) + +5 +: error while downloading (also: *push* before *init*) + +6 +: unknown protocol + +7 +: remote locked + +8 +: not a Git project, dirty working tree or invalid branch + +9 +: hook failed + +10 +: local filesystem error + +130 +: interrupted + +## SEE ALSO + +git(1), curl(1). Upstream: diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css new file mode 100644 index 0000000..a5e2973 --- /dev/null +++ b/docs/stylesheets/extra.css @@ -0,0 +1,29 @@ +/* A slightly denser code font size, since most pages are command lines. */ +.md-typeset pre > code { + font-size: 0.72rem; +} + +/* Cards on the landing page keep an even height in a row. */ +.md-typeset .grid.cards > ul > li { + display: flex; + flex-direction: column; +} + +.md-typeset .grid.cards > ul > li > hr { + margin-block: 0.6rem; +} + +/* Definition lists carry the manual pages; make the term stand out. */ +.md-typeset dl dt { + font-weight: 600; +} + +.md-typeset dl dd { + margin-bottom: 0.8em; +} + +/* The tables comparing the two implementations read better left aligned + and with room to breathe. */ +.md-typeset table:not([class]) th { + white-space: nowrap; +} diff --git a/images/bg_hr.png b/images/bg_hr.png deleted file mode 100644 index 7973bd69888c7e10ccad1111d555ceabb7cd99b6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 943 zcmaJ=O^ee&7!FiK7FWCot{@Ck@nrMW&tx0B-6VAbrk1u~FTzffX&bu9#AIsIdef8t z!QZfdz=K}>3m(LO;6X3qN}Y6@>cJYA%)G<%Jn!ec>9im1@7>wsIBwrMF}iHO!q%;8 zSJ@xEd~(FL18NRvkBsOXMVM>4WQc*~qcQGc17IjxRnj!O_^B1gan0x#EWT48PK->5B2>mI;LIx zC*FSw$Nfc!g)WZCEOJ=mM)}lLsOk|$ltg_(&ax_YCWMlBLPDVT%D_gB7o_$YZ`-OB z#1sV%whRq21>W;qwN$N?OUGtQQe;JvOsQrna;+v+j8dth=*?orHHb6waX>S!yXCgT zo!oR3{E&GzaOAzfZYv@_Sf{LdyJInS>TS60&R9%yCs$y>2x(*gYIJtRrYAja$Ceq} z!N&oc_K1!3-Ft`U>`CM;quEbB4KG%!MovB*9_3!QzFhqHwrbwK|Doo-y>auDJNSP6 T=d)j*_4El@X4^PFK7I8YBT*xD diff --git a/images/bkg.png b/images/bkg.png deleted file mode 100644 index fcebb5b22999adb792afee1f74bcf4af91db8441..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4261 zcmai24OG(S8mBsUyM3)~ZEIE-v#qwa1Vm(_WaZcF4$Tzl!!$x9YVt?^Am9ho*2lIQ zTie~ak)qq&rWX>8T6nEuDuzAQq;*m$U_enEjIf_wTdeFYT{JoQD5|@shXlvVcOKfW~DY zHWUE68K|@(8k4bwK`Rgy8yWEk#GD9L_Ez3j^7}+OP!vh??}-!^abRx*B0fRLq0#p- zc&ObBCW}pqaOzqkP^+4>M6iip9W9{ohX*|-IT#4SlFk8;5lDPj+g$04H80)Ze>fQtmU zOf)t&Hr5{_DrzmfV=X9R^Jv1gY;a{DLJ|X{b6Ff73t*%C5ox;tK93XuBb_-y5$ADO zHu$JaaKX?*8V8My#Q2XC*hnUa-da@jcsIz~!gzxBse!?45r=`^!TNt2HYP=5@QE9vlA}}JO%C`b0d#&5gUt*07CiBd4(jWt zLJWhZKp#6>gL!$$qVD|Wzk7)}V zkBQGx@@9q_1yr6q017Ty@?&EzVH&nI8Gv!-^x=W~}{IQz$!k{zA98=Ti? z9cVfF`8lawJL&Vf$95Qs%Nvf1vh7mQMX#$ul8xKfXp?90UsgyjF)tIRCdvlh?KrbH zv$~{=QZbg@;?kDY?!_;sOnP!E$WC_y*L!11kJ$Bv5P=FueEclp=B)kG%NZ+7YOGoP zR-EH7=(Ixzm4*4r!U~*|*f}&ewbAzZg$rV7yy&TpH)pNSQqOg{CC4>}G40e2em2s* zKCkIZr2DpNqSZ@AS6r{w4k}39fVJzZXAy0G=<@V(rsuz5bVoeL zq+fMF{HYOh*>$N+ro3`@%}CQfL>5o_WPYe}`@$iaZYz~6GhYY36SpGGi3vX>Q!if_ zW9q{cdi5&xHzBM2OL>Yxeo@({8F}vGo)?H9zB*<}?_KK~<>Ca;lrUut0s0QfGvOOQ zX|CGP-t&oefY|ZSCUqIdWgV^RD9OM76AmFBk^+y@7AoJF~M91Md>YK-Oz2RcP>?)IpVnVhM_99yGk?`*T+XD zwBp#*t06#hT90_d;P)Bbq_WluNSRZ6dv$X^6I1e|_Ex>3C+}~eO6?xaH@&~Jr03}Q z0ll4KqB|?}$cD=Fj)(0>75P8jfk6WSLIxx#!0NJlW3XW4xh2*vK>a`=aGXCgp^dBC zWbsw8pkoUCTuEOjesHP=HT~O?9Z4p1!un#t@AR82drx@}=8y;C3Sro$7oR@K z@IaRLzRu~|mYn_qwVlpXQ4GdGlX91Qv1*s4X`3a?xBTl+6gjVY8?3u26I5R*7URK1 zE=Ac&mvpCFg3I5*AIz?JhVVgKZ%aQcCA1(@d7`&1KFgm|=7IU)9eu`jukwd}^DxEQ zG;~TIDm9)ab}d=d-5BROfvdYfco-jDRg|~dWgdF4?0Ij6Qg{#XJ0ZZVfM`CND7eoe z%RgC$+LKp`BM6{vjBl?JHK}!kDD{<2amH)l>0X#6e?t7&y3fzGT8is#^2aMbRTCk; zJ(sUXP5}^>+6yzxOEHYVr)^ywMD4BNP?p-pCJgbFKiOU??lf*qwM^j7+rsdqkb`_< zu8XfLVd}?QOz?^->D^mk<}$@nMGhUVuWEj*zsW&W*LTpG#BI`{j3U;d25*I>wQU<%>f=gRb zoB3nzuQZE1VsAI0q9Fu1XeEnWrw61}#&RdiUgeSC)MCg}l56y>n~zJy4~!oRrSk?+ z^|z_7X!7ri<33}WCtfkPM{XaK)*O=Vv)t7yh&CgA{MMuALtvt3t{B)Nx2!U`E3tjf z`teF@drzmAWoULxts_H3G-*w!78UQbn(XfG6=khyzw@p2!)9aEeaHEud<#kD>9RSO zv4-4-Lmar@`!;{Jr^WQv-8GcOR=etVt>w3~j-~BA%P8I!s&Ds3L;i|4t7Purz>$NZ zLdX8z;(M{+ihDk&gD&7dmuocXNUmXK-XYTv4@Mt2?u>v1tjpHoom4}Y)wInK=54oY zKa}v{yU`|fgO;#gLyBgiBOG!z#3@XkTfXu8c}+PK=2_c1mzO*scsQco@tHBs5OfZs z$8NNh_v)za2hh3d!{9GC=2^!u6$x6F3k_b)0HLi@ubw9%xBZ3Ommvims*{jB*0zSC zJ49_LfLb#7dYNlHqdQGMY}V}ji|0&vzYZYi^bR?WnGau-m&DBM#vB>^$Z&(5&gG#x zJ`xV$h>ipCjn@`3+01KQ+utac5>+Ws#n+nJvi`lWegz$?!qujlbLb{{(_)p_l1Z+( zdM>W^kZsfPn#(4ql5bnklzKaaX49VdUT_o$9aG z(}gb@7Cr`Hs63vrG-d~*-Ew3l)s6YthL%Ht*{ ze%1=aHvnw3uQBA!TwFcao}XkUs49=`tQ%O)vYY}elzL*VuTX(=8u_V>j)}r>S4Uh~ zh>GVehE`=Smd4(neDmas=pLEo)|t(XUs77pn;QoN(54u9Jv>@Lm8^V);kZT#8jv3` z&GiE#r51T-G5W5_uCs-YQIy9$Bn^A%)S!H7$u7ib&EeDgKaH>T|KlJnIXy|Wao30c E2cLQQbpQYW diff --git a/images/blacktocat.png b/images/blacktocat.png deleted file mode 100644 index 273d5710a2e0968d77584ad073e4a089fbfd7e68..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1266 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 diff --git a/images/download-button.png b/images/download-button.png deleted file mode 100644 index c5ffb3a891788af5ceff835cde7d305d723aaf21..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27151 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 t 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 az+X*J7T+u-ZHxU^ZlxJl66tU<@_zu4*#OM| diff --git a/images/highlight-bg.jpg b/images/highlight-bg.jpg deleted file mode 100644 index 355e089b2d7d7a6f78caa818e302c8a15c6c0e3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30991 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 diff --git a/images/icon_download.png b/images/icon_download.png deleted file mode 100644 index a2a287f642aaeeadf62d3819a16d6a55e12afc01..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1162 zcmeAS@N?(olHy`uVBq!ia0vp^av;pX1|+Qw)-3{3k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+n3Xa^B1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxKsVXI%s|1+P|wiV z#N6CmN5ROz&_Lh7NZ-&%*U;R`*vQJjKmiJrfVLH-q*(>IxIyg#@@$ndN=gc>^!3Zj z%k|2Q_413-^$jg8EkR}&8R-I5=oVMzl_XZ^<`pZ$OmImpPAEg{v+u2}(t{7puX=A(aKG z`a!A1`K3k4z=%sz23b{L-^Aq1JP;qO z-q+X4Gq1QLF)umQ)5TT^Xo6m5W{Q=$skw`#i#v$3O_v5UEZv#YC% zp@9obuSMv>2~2MaLa!N4y`ZF!TL84#CABECEH%ZgC_h&L>}9J=+-@<(X&zK> z3U0TU;MA)Rbc{YIVv!;mCIn19ASOK70y*%6pPC0u?M1+3t#h8?05D7Z^K@|xskoK& z=l_5E!ww8;ZH!Ed#V+%1n6Rkg{=V8A2QTsNE8^> zvHmCezoM^A29GnE>#ih4F*YzTGbm`! V-6~#faTQcLc)I$ztaD0e0svx+5pL)fzyB-LP;nSwTTrO-V^%6zZ79nHA?WI&{pCR5DWL`IUrWEABBq-zpvwlpS#%#=KaQ=!p8X5 z)N$}J_G)Px<;^Pyh?zIDp4Gg9gARSy3MB?s<9L0;6JkJi0dmwVER7h>LO`tClDU;^ z@W@i?W0nTyFCUm)^XuQ>V9HU{klw%kP0Z}8vTxy-W$>oi+KFY-D84RD+ZcTw%(3fq zxP4Vj;b_*bQx2az^#wI7=ZANIa`{vjY)r`OXpeTU3+SV&PKMaCTky)?t zsU^drHQ50WtCWhdvogixrA;9|ul2K+-#k7by(%(tTg2UYBF&XC3INH0q|Y6I{GB`y z+Tf@oihPNl!Y!)*LBIP)!K*!QQSK<3$Ut~eKzcMlzZ<5ub(}hQ>k0o5aqG~#P*1+e z1D(b<0h8}Bmb*1+pJ{lj`W5VuW?0eIBJ&L;oc`*<4Gjj}M115t%ZP7dtI~iS-?GyZ zoZpU3$TjW%NpFSEPa_rgozcZ)A7`uY`_{5;f#j4!{fVIVmARWpeM3A^S_>> zty95wbrfy&=uzK2=wGta3fPyXNsqHn3d`#n6n4fR?dPq2fR93yoAwjDmNF*0mgl3} z;7j1fDg)EJONOqAx_Z!Fk_VyKD@Lbty2H`)nRgbA{ADj&hY}I!a*J-sbQ;`!oEvYn z71*zDsP5shwSby;lX-B=ZlO?7}Q;_TZ(lb~79M$K@Ue zld=~Y2E%pwrf*D6H`#?KcE~w<7WzS#Z3(w_+TE|*{Z)!i$0a+>#c)XD6RJl z&}x8tE*~blc$V~5&Ce{5H#T>1mGjXfE>CMaR8vI-y_&}U9=gX-E7yzKYU{*~zwesK z^7m5~{_c_G;&9|cvapOXbeJ1?wOV`8r2YfcOJ>* zCwu2?&zFq1^$dRSs!l8y#;jRKi=%OxHw$~w%$D3AI}+=Ni>HrVKGq%YnPReVA(B^8 z!n*eyUWxiQ8cYm#FBoZ@wp=&wovP`x8$qv;oSi2Ubg~&4u{b@qc!SVG)@&^1bx5x5 zs#8m|Qm)9&tLaWI8N5Hq)%l-jI3nsHu!RqBm$jT`)yl|BI_tN4Dqoav?)kFK8J zw{J8dXm?>$`wA5-mgrJi6zZJfN5&IuiXxoeHWaQ#Va6}^AHd8JoduOV7O&GF;YtMa zUpY`spkzU-Dty8%9WA;$cU78oiJGCi=roa){(t7c`vSq0kF<9*&&Z2aM>;u{4Q63? vtA!HaK&Ic~GUPov+*4K(&>su=-cJkwg`X-D2y_ZKD*=9Gi}YXowB^OmrRYZ> diff --git a/images/sprite_download.png b/images/sprite_download.png deleted file mode 100644 index f2babd575dc1cbd6e9342cc58ca795377d35afdb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16799 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 diff --git a/index.html b/index.html deleted file mode 100644 index 35bcf7a..0000000 --- a/index.html +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - - - - - - - Git-ftp by git-ftp - - - -
-
-

Git-ftp

-

Upload to FTP servers the Git way

- View project on GitHub -
-
- -
-
-
-

If you use Git and you need to upload your files to an FTP server, -Git-ftp can save you some time and bandwidth by uploading only those files that -changed since the last upload.

- -

It keeps track of the uploaded files by storing the commit id -in a log file on the server. It uses Git to determine which local -files have changed.

- -

You can easily deploy another branch or go back in the Git history to upload -an older version.

- -
# Setup
-git config git-ftp.url ftp.example.net
-git config git-ftp.user ftp-user
-git config git-ftp.password secr3t
-
-# Upload all files
-git ftp init
-
-# Or if the files are already there
-git ftp catchup
-
-# Work and deploy
-echo "new content" >> index.txt
-git commit index.txt -m "Add new content"
-git ftp push
-# 1 file to sync:
-# [1 of 1] Buffered for upload 'index.txt'.
-# Uploading ...
-# Last deployment changed to ded01b27e5c785fb251150805308d3d0f8117387.
- -

-Further Reading

- - - -

-Limitations

- -
    -
  • Windows and OS X: I am very limited in testing on Windows and OS X. Thanks -for helping me out fixing bugs on these platforms.
  • -
  • git-ftp as deployment tool: git-ftp was not designed as centralized -deployment tool. While running git-ftp, you have to take care, no one pushes or -touches this repo (e.g. no commits, no checkouts, no file modifications)!
  • -
- -

-Contributions

- -

Don't hesitate to improve this tool. -Don't forget to add yourself to the AUTHORS file. -Core functionality is unit tested using shunit2. -You can find the tests in tests/.

- -

-Copyright

- -

This application is licensed under GNU General Public License, Version 3.0

-
- - -
-
- - - - diff --git a/javascripts/main.js b/javascripts/main.js deleted file mode 100644 index d8135d3..0000000 --- a/javascripts/main.js +++ /dev/null @@ -1 +0,0 @@ -console.log('This would be the main JS file.'); diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..0dcd26f --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,115 @@ +site_name: Git-ftp +site_description: Upload to FTP servers the Git way — deploy a Git repository over FTP, FTPS, FTPES or SFTP, uploading only the files that changed. +site_url: https://git-ftp.github.io/ +site_author: René Moser and the git-ftp contributors +copyright: Git-ftp is licensed under the GNU General Public License, Version 3.0 + +repo_url: https://github.com/git-ftp/git-ftp.github.io +repo_name: git-ftp.github.io +edit_uri: edit/master/docs/ + +theme: + name: material + language: en + icon: + repo: fontawesome/brands/github + logo: material/cloud-upload-outline + palette: + - media: "(prefers-color-scheme)" + toggle: + icon: material/brightness-auto + name: Follow system theme + - media: "(prefers-color-scheme: light)" + scheme: default + primary: indigo + accent: indigo + toggle: + icon: material/brightness-7 + name: Switch to dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: indigo + accent: indigo + toggle: + icon: material/brightness-4 + name: Switch to light mode + features: + - navigation.tabs + - navigation.sections + - navigation.top + - navigation.instant + - navigation.tracking + - navigation.indexes + - toc.follow + - search.suggest + - search.highlight + - content.code.copy + - content.code.annotate + - content.action.edit + - content.tabs.link + +extra_css: + - stylesheets/extra.css + +extra: + social: + - icon: fontawesome/brands/github + link: https://github.com/git-ftp + name: git-ftp on GitHub + - icon: fontawesome/brands/python + link: https://pypi.org/project/git-ftp/ + name: git-ftp on PyPI + +markdown_extensions: + - abbr + - admonition + - attr_list + - def_list + - footnotes + - md_in_html + - tables + - toc: + permalink: true + - pymdownx.details + - pymdownx.highlight: + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + - pymdownx.inlinehilite + - pymdownx.keys + - pymdownx.snippets + - pymdownx.superfences + - pymdownx.tabbed: + alternate_style: true + - pymdownx.tasklist: + custom_checkbox: true + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + +plugins: + - search + +nav: + - Home: index.md + - Getting started: + - getting-started/index.md + - Install: getting-started/install.md + - First deployment: getting-started/first-deployment.md + - Which implementation: getting-started/implementations.md + - Guide: + - Configuration: guide/configuration.md + - Scopes: guide/scopes.md + - Selecting files: guide/selecting-files.md + - Protocols and credentials: guide/protocols.md + - Hooks and locking: guide/hooks-and-locking.md + - Downloading from the server: guide/download.md + - Continuous deployment: guide/ci-cd.md + - Troubleshooting: guide/troubleshooting.md + - Reference: + - Manual (Python): reference/manual-python.md + - Manual (Bash): reference/manual-bash.md + - Compatibility: reference/compatibility.md + - About: + - Contributing: about/contributing.md + - License: about/license.md diff --git a/params.json b/params.json deleted file mode 100644 index 2200319..0000000 --- a/params.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "Git-ftp", - "tagline": "Upload to FTP servers the Git way", - "body": "If you use Git and you need to upload your files to an FTP server,\r\nGit-ftp can save you some time and bandwidth by uploading only those files that\r\nchanged since the last upload.\r\n\r\nIt keeps track of the uploaded files by storing the commit id\r\nin a log file on the server. It uses Git to determine which local\r\nfiles have changed.\r\n\r\nYou can easily deploy another branch or go back in the Git history to upload\r\nan older version.\r\n\r\n```sh\r\n# Setup\r\ngit config git-ftp.url ftp.example.net\r\ngit config git-ftp.user ftp-user\r\ngit config git-ftp.password secr3t\r\n\r\n# Upload all files\r\ngit ftp init\r\n\r\n# Or if the files are already there\r\ngit ftp catchup\r\n\r\n# Work and deploy\r\necho \"new content\" >> index.txt\r\ngit commit index.txt -m \"Add new content\"\r\ngit ftp push\r\n# 1 file to sync:\r\n# [1 of 1] Buffered for upload 'index.txt'.\r\n# Uploading ...\r\n# Last deployment changed to ded01b27e5c785fb251150805308d3d0f8117387.\r\n```\r\n\r\nFurther Reading\r\n---------------\r\n\r\n* Read the [manual](man/git-ftp.1.md) for more options, features and examples.\r\n* See the [installtion instructions](INSTALL.md) for your system.\r\n* Check [git-ftp issues on GitHub] for open issues.\r\n* Follow this project on twitter [@gitftp].\r\n\r\nLimitations\r\n-----------\r\n\r\n* Windows and OS X: I am very limited in testing on Windows and OS X. Thanks\r\nfor helping me out fixing bugs on these platforms.\r\n* git-ftp as deployment tool: git-ftp was not designed as centralized\r\ndeployment tool. While running git-ftp, you have to take care, no one pushes or\r\ntouches this repo (e.g. no commits, no checkouts, no file modifications)!\r\n\r\nContributions\r\n-------------\r\n\r\nDon't hesitate to improve this tool.\r\nDon't forget to add yourself to the [AUTHORS](AUTHORS) file.\r\nCore functionality is unit tested using shunit2.\r\nYou can find the tests in `tests/`.\r\n\r\nCopyright\r\n---------\r\n\r\nThis application is licensed under [GNU General Public License, Version 3.0]\r\n\r\n[git-ftp issues on GitHub]: http://github.com/git-ftp/git-ftp/issues\r\n[GNU General Public License, Version 3.0]:\r\n http://www.gnu.org/licenses/gpl-3.0-standalone.html\r\n[@gitftp]: https://twitter.com/gitftp\r\n", - "google": "", - "note": "Don't delete this file! It's used internally to help with page regeneration." -} \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..1e30ce2 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,11 @@ +[project] +name = "git-ftp-docs" +version = "0" +description = "Sources of the git-ftp documentation site at https://git-ftp.github.io" +requires-python = ">=3.10" +dependencies = [ + "mkdocs-material>=9.5,<10", +] + +[tool.uv] +package = false diff --git a/scripts/sync_upstream_docs.py b/scripts/sync_upstream_docs.py new file mode 100755 index 0000000..d768f43 --- /dev/null +++ b/scripts/sync_upstream_docs.py @@ -0,0 +1,219 @@ +#!/usr/bin/env python3 +"""Pull the manual pages of both git-ftp implementations into docs/reference/. + +The manuals live in the tool repositories, written as pandoc man page source. +This script fetches them (or copies them from a local checkout with --local) +and converts them to pages MkDocs can render: the pandoc title block goes, the +headings are demoted by one level so the page title is the only H1, tab indented +definition lists become space indented ones, and relative links are turned into +links to the upstream repository. + + ./scripts/sync_upstream_docs.py # fetch from GitHub + ./scripts/sync_upstream_docs.py --local # from ../git-ftp and ../git-ftp-py +""" + +from __future__ import annotations + +import argparse +import re +import sys +from dataclasses import dataclass, field +from pathlib import Path +from urllib.request import urlopen + +ROOT = Path(__file__).resolve().parent.parent +REFERENCE = ROOT / "docs" / "reference" + +PY_REPO = "https://github.com/git-ftp/git-ftp-py" +SH_REPO = "https://github.com/git-ftp/git-ftp" +PY_RAW = "https://raw.githubusercontent.com/git-ftp/git-ftp-py/main" +SH_RAW = "https://raw.githubusercontent.com/git-ftp/git-ftp/master" + + +@dataclass +class Source: + """One upstream document and how to turn it into a page of this site.""" + + target: str + raw_url: str + local_path: Path + blob_url: str + title: str + nav_title: str + intro: str + link_base: str + drop: list[str] = field(default_factory=list) + links: dict[str, str] = field(default_factory=dict) + + +SOURCES = [ + Source( + target="manual-python.md", + raw_url=f"{PY_RAW}/docs/git-ftp.1.md", + local_path=Path("../git-ftp-py/docs/git-ftp.1.md"), + blob_url=f"{PY_REPO}/blob/main/docs/git-ftp.1.md", + title="Manual — git-ftp (Python)", + nav_title="Manual (Python)", + intro=( + "The complete manual page of the Python implementation, the one " + "`git ftp help` and `man git-ftp` show." + ), + link_base=f"{PY_REPO}/blob/main", + links={ + "COMPATIBILITY.md": "compatibility.md", + "CHANGELOG.md": f"{PY_REPO}/blob/main/CHANGELOG.md", + }, + ), + Source( + target="manual-bash.md", + raw_url=f"{SH_RAW}/man/git-ftp.1.md", + local_path=Path("../git-ftp/man/git-ftp.1.md"), + blob_url=f"{SH_REPO}/blob/master/man/git-ftp.1.md", + title="Manual — git-ftp (Bash)", + nav_title="Manual (Bash)", + intro=( + "The complete manual page of the original Bash implementation, " + "the one `git ftp help` and `man git-ftp` show." + ), + link_base=f"{SH_REPO}/blob/master", + drop=[ + r"^This is the manual for version", + r"^Please consider the \[changelog\]", + r"^the _Branch > Tags_ select above", + ], + links={ + "../CHANGELOG.md": f"{SH_REPO}/blob/master/CHANGELOG.md", + "../INSTALL.md": "../getting-started/install.md", + }, + ), + Source( + target="compatibility.md", + raw_url=f"{PY_RAW}/COMPATIBILITY.md", + local_path=Path("../git-ftp-py/COMPATIBILITY.md"), + blob_url=f"{PY_REPO}/blob/main/COMPATIBILITY.md", + title="Compatibility between the implementations", + nav_title="Compatibility", + intro=( + "What the Python port keeps identical to the Bash original, which " + "of its bugs it fixes, and where it deliberately behaves " + "differently." + ), + link_base=f"{PY_REPO}/blob/main", + ), +] + + +def read(source: Source, local: bool) -> str: + if local: + path = (ROOT / source.local_path).resolve() + if not path.is_file(): + sys.exit(f"missing local checkout: {path}") + return path.read_text(encoding="utf-8") + with urlopen(source.raw_url, timeout=30) as response: # noqa: S310 - fixed https URLs + return response.read().decode("utf-8") + + +def strip_title_block(text: str) -> str: + """Remove the leading pandoc title block ("% title" lines).""" + lines = text.splitlines() + while lines and (lines[0].startswith("%") or not lines[0].strip()): + lines.pop(0) + return "\n".join(lines) + + +def expand_tabs(line: str) -> str: + """Turn leading tabs into four spaces each, so code blocks and definition + list bodies keep their meaning in Python-Markdown.""" + stripped = line.lstrip("\t") + return " " * (len(line) - len(stripped)) + stripped + + +def convert(source: Source, text: str) -> str: + body = strip_title_block(text) + + out: list[str] = [] + for raw_line in body.splitlines(): + if any(re.search(pattern, raw_line) for pattern in source.drop): + continue + line = expand_tabs(raw_line) + # ":definition" has become ": definition"; a definition list + # marker wants the colon plus a space, with the body on column four. + line = re.sub(r"^:[ \t]+", ": ", line) + # pandoc man source escapes angle brackets and backticks; on the web + # they should be an entity and a code span. + line = line.replace("\\<", "<").replace("\\>", ">") + line = line.replace("\\`", "`") + # Demote headings so that the page title stays the only level one. + if line.startswith("#"): + line = "#" + line + out.append(line) + body = "\n".join(out).strip() + + for old, new in source.links.items(): + body = body.replace(f"]({old})", f"]({new})") + # Anything still pointing at a repository file goes to the repository. + body = re.sub( + r"\]\((?!https?:|#|\.\./|[a-z-]+\.md)([A-Za-z0-9_./-]+\.(?:md|sh|1))\)", + rf"]({source.link_base}/\1)", + body, + ) + + header = "\n".join( + [ + "---", + f"title: {source.title}", + "---", + "", + f"# {source.title}", + "", + source.intro, + "", + "!!! info \"Synced from upstream\"", + "", + f" This page is generated from [its source]({source.blob_url})", + " in the tool's repository and is refreshed every time the site", + " is built. Edit it there, not here.", + "", + ] + ) + return f"{header}\n{body}\n" + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--local", + action="store_true", + help="read from sibling checkouts instead of GitHub", + ) + parser.add_argument( + "--check", + action="store_true", + help="fail instead of writing when a page would change", + ) + args = parser.parse_args() + + REFERENCE.mkdir(parents=True, exist_ok=True) + changed = False + for source in SOURCES: + page = convert(source, read(source, args.local)) + target = REFERENCE / source.target + current = target.read_text(encoding="utf-8") if target.is_file() else None + if current == page: + print(f"unchanged docs/reference/{source.target}") + continue + changed = True + if args.check: + print(f"OUTDATED docs/reference/{source.target}") + continue + target.write_text(page, encoding="utf-8") + print(f"written docs/reference/{source.target}") + + if args.check and changed: + print("\nRun ./scripts/sync_upstream_docs.py to update.", file=sys.stderr) + return 1 + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/stylesheets/github-light.css b/stylesheets/github-light.css deleted file mode 100644 index 872a6f4..0000000 --- a/stylesheets/github-light.css +++ /dev/null @@ -1,116 +0,0 @@ -/* - Copyright 2014 GitHub Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -.pl-c /* comment */ { - color: #969896; -} - -.pl-c1 /* constant, markup.raw, meta.diff.header, meta.module-reference, meta.property-name, support, support.constant, support.variable, variable.other.constant */, -.pl-s .pl-v /* string variable */ { - color: #0086b3; -} - -.pl-e /* entity */, -.pl-en /* entity.name */ { - color: #795da3; -} - -.pl-s .pl-s1 /* string source */, -.pl-smi /* storage.modifier.import, storage.modifier.package, storage.type.java, variable.other, variable.parameter.function */ { - color: #333; -} - -.pl-ent /* entity.name.tag */ { - color: #63a35c; -} - -.pl-k /* keyword, storage, storage.type */ { - color: #a71d5d; -} - -.pl-pds /* punctuation.definition.string, string.regexp.character-class */, -.pl-s /* string */, -.pl-s .pl-pse .pl-s1 /* string punctuation.section.embedded source */, -.pl-sr /* string.regexp */, -.pl-sr .pl-cce /* string.regexp constant.character.escape */, -.pl-sr .pl-sra /* string.regexp string.regexp.arbitrary-repitition */, -.pl-sr .pl-sre /* string.regexp source.ruby.embedded */ { - color: #183691; -} - -.pl-v /* variable */ { - color: #ed6a43; -} - -.pl-id /* invalid.deprecated */ { - color: #b52a1d; -} - -.pl-ii /* invalid.illegal */ { - background-color: #b52a1d; - color: #f8f8f8; -} - -.pl-sr .pl-cce /* string.regexp constant.character.escape */ { - color: #63a35c; - font-weight: bold; -} - -.pl-ml /* markup.list */ { - color: #693a17; -} - -.pl-mh /* markup.heading */, -.pl-mh .pl-en /* markup.heading entity.name */, -.pl-ms /* meta.separator */ { - color: #1d3e81; - font-weight: bold; -} - -.pl-mq /* markup.quote */ { - color: #008080; -} - -.pl-mi /* markup.italic */ { - color: #333; - font-style: italic; -} - -.pl-mb /* markup.bold */ { - color: #333; - font-weight: bold; -} - -.pl-md /* markup.deleted, meta.diff.header.from-file */ { - background-color: #ffecec; - color: #bd2c00; -} - -.pl-mi1 /* markup.inserted, meta.diff.header.to-file */ { - background-color: #eaffea; - color: #55a532; -} - -.pl-mdr /* meta.diff.range */ { - color: #795da3; - font-weight: bold; -} - -.pl-mo /* meta.output */ { - color: #1d3e81; -} - diff --git a/stylesheets/print.css b/stylesheets/print.css deleted file mode 100644 index 7da6db0..0000000 --- a/stylesheets/print.css +++ /dev/null @@ -1,228 +0,0 @@ -html, body, div, span, applet, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -a, abbr, acronym, address, big, cite, code, -del, dfn, em, img, ins, kbd, q, s, samp, -small, strike, strong, sub, sup, tt, var, -b, u, i, center, -dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, -table, caption, tbody, tfoot, thead, tr, th, td, -article, aside, canvas, details, embed, -figure, figcaption, footer, header, hgroup, -menu, nav, output, ruby, section, summary, -time, mark, audio, video { - padding: 0; - margin: 0; - font: inherit; - font-size: 100%; - vertical-align: baseline; - border: 0; -} -/* HTML5 display-role reset for older browsers */ -article, aside, details, figcaption, figure, -footer, header, hgroup, menu, nav, section { - display: block; -} -body { - line-height: 1; -} -ol, ul { - list-style: none; -} -blockquote, q { - quotes: none; -} -blockquote:before, blockquote:after, -q:before, q:after { - content: ''; - content: none; -} -table { - border-spacing: 0; - border-collapse: collapse; -} -body { - font-family: 'Helvetica Neue', Helvetica, Arial, serif; - font-size: 13px; - line-height: 1.5; - color: #000; -} - -a { - font-weight: bold; - color: #d5000d; -} - -header { - padding-top: 35px; - padding-bottom: 10px; -} - -header h1 { - font-size: 48px; - font-weight: bold; - line-height: 1.2; - color: #303030; - letter-spacing: -1px; -} - -header h2 { - font-size: 24px; - font-weight: normal; - line-height: 1.3; - color: #aaa; - letter-spacing: -1px; -} -#downloads { - display: none; -} -#main_content { - padding-top: 20px; -} - -code, pre { - margin-bottom: 30px; - font-family: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal; - font-size: 12px; - color: #222; -} - -code { - padding: 0 3px; -} - -pre { - padding: 20px; - overflow: auto; - border: solid 1px #ddd; -} -pre code { - padding: 0; -} - -ul, ol, dl { - margin-bottom: 20px; -} - - -/* COMMON STYLES */ - -table { - width: 100%; - border: 1px solid #ebebeb; -} - -th { - font-weight: 500; -} - -td { - font-weight: 300; - text-align: center; - border: 1px solid #ebebeb; -} - -form { - padding: 20px; - background: #f2f2f2; - -} - - -/* GENERAL ELEMENT TYPE STYLES */ - -h1 { - font-size: 2.8em; -} - -h2 { - margin-bottom: 8px; - font-size: 22px; - font-weight: bold; - color: #303030; -} - -h3 { - margin-bottom: 8px; - font-size: 18px; - font-weight: bold; - color: #d5000d; -} - -h4 { - font-size: 16px; - font-weight: bold; - color: #303030; -} - -h5 { - font-size: 1em; - color: #303030; -} - -h6 { - font-size: .8em; - color: #303030; -} - -p { - margin-bottom: 20px; - font-weight: 300; -} - -a { - text-decoration: none; -} - -p a { - font-weight: 400; -} - -blockquote { - padding: 0 0 0 30px; - margin-bottom: 20px; - font-size: 1.6em; - border-left: 10px solid #e9e9e9; -} - -ul li { - padding-left: 20px; - list-style-position: inside; - list-style: disc; -} - -ol li { - padding-left: 3px; - list-style-position: inside; - list-style: decimal; -} - -dl dd { - font-style: italic; - font-weight: 100; -} - -footer { - padding-top: 20px; - padding-bottom: 30px; - margin-top: 40px; - font-size: 13px; - color: #aaa; -} - -footer a { - color: #666; -} - -/* MISC */ -.clearfix:after { - display: block; - height: 0; - clear: both; - visibility: hidden; - content: '.'; -} - -.clearfix {display: inline-block;} -* html .clearfix {height: 1%;} -.clearfix {display: block;} diff --git a/stylesheets/pygment_trac.css b/stylesheets/pygment_trac.css deleted file mode 100644 index c6a6452..0000000 --- a/stylesheets/pygment_trac.css +++ /dev/null @@ -1,69 +0,0 @@ -.highlight { background: #ffffff; } -.highlight .c { color: #999988; font-style: italic } /* Comment */ -.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ -.highlight .k { font-weight: bold } /* Keyword */ -.highlight .o { font-weight: bold } /* Operator */ -.highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */ -.highlight .cp { color: #999999; font-weight: bold } /* Comment.Preproc */ -.highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */ -.highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */ -.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ -.highlight .gd .x { color: #000000; background-color: #ffaaaa } /* Generic.Deleted.Specific */ -.highlight .ge { font-style: italic } /* Generic.Emph */ -.highlight .gr { color: #aa0000 } /* Generic.Error */ -.highlight .gh { color: #999999 } /* Generic.Heading */ -.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ -.highlight .gi .x { color: #000000; background-color: #aaffaa } /* Generic.Inserted.Specific */ -.highlight .go { color: #888888 } /* Generic.Output */ -.highlight .gp { color: #555555 } /* Generic.Prompt */ -.highlight .gs { font-weight: bold } /* Generic.Strong */ -.highlight .gu { color: #800080; font-weight: bold; } /* Generic.Subheading */ -.highlight .gt { color: #aa0000 } /* Generic.Traceback */ -.highlight .kc { font-weight: bold } /* Keyword.Constant */ -.highlight .kd { font-weight: bold } /* Keyword.Declaration */ -.highlight .kn { font-weight: bold } /* Keyword.Namespace */ -.highlight .kp { font-weight: bold } /* Keyword.Pseudo */ -.highlight .kr { font-weight: bold } /* Keyword.Reserved */ -.highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */ -.highlight .m { color: #009999 } /* Literal.Number */ -.highlight .s { color: #d14 } /* Literal.String */ -.highlight .na { color: #008080 } /* Name.Attribute */ -.highlight .nb { color: #0086B3 } /* Name.Builtin */ -.highlight .nc { color: #445588; font-weight: bold } /* Name.Class */ -.highlight .no { color: #008080 } /* Name.Constant */ -.highlight .ni { color: #800080 } /* Name.Entity */ -.highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */ -.highlight .nf { color: #990000; font-weight: bold } /* Name.Function */ -.highlight .nn { color: #555555 } /* Name.Namespace */ -.highlight .nt { color: #000080 } /* Name.Tag */ -.highlight .nv { color: #008080 } /* Name.Variable */ -.highlight .ow { font-weight: bold } /* Operator.Word */ -.highlight .w { color: #bbbbbb } /* Text.Whitespace */ -.highlight .mf { color: #009999 } /* Literal.Number.Float */ -.highlight .mh { color: #009999 } /* Literal.Number.Hex */ -.highlight .mi { color: #009999 } /* Literal.Number.Integer */ -.highlight .mo { color: #009999 } /* Literal.Number.Oct */ -.highlight .sb { color: #d14 } /* Literal.String.Backtick */ -.highlight .sc { color: #d14 } /* Literal.String.Char */ -.highlight .sd { color: #d14 } /* Literal.String.Doc */ -.highlight .s2 { color: #d14 } /* Literal.String.Double */ -.highlight .se { color: #d14 } /* Literal.String.Escape */ -.highlight .sh { color: #d14 } /* Literal.String.Heredoc */ -.highlight .si { color: #d14 } /* Literal.String.Interpol */ -.highlight .sx { color: #d14 } /* Literal.String.Other */ -.highlight .sr { color: #009926 } /* Literal.String.Regex */ -.highlight .s1 { color: #d14 } /* Literal.String.Single */ -.highlight .ss { color: #990073 } /* Literal.String.Symbol */ -.highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */ -.highlight .vc { color: #008080 } /* Name.Variable.Class */ -.highlight .vg { color: #008080 } /* Name.Variable.Global */ -.highlight .vi { color: #008080 } /* Name.Variable.Instance */ -.highlight .il { color: #009999 } /* Literal.Number.Integer.Long */ - -.type-csharp .highlight .k { color: #0000FF } -.type-csharp .highlight .kt { color: #0000FF } -.type-csharp .highlight .nf { color: #000000; font-weight: normal } -.type-csharp .highlight .nc { color: #2B91AF } -.type-csharp .highlight .nn { color: #000000 } -.type-csharp .highlight .s { color: #A31515 } -.type-csharp .highlight .sc { color: #A31515 } diff --git a/stylesheets/stylesheet.css b/stylesheets/stylesheet.css deleted file mode 100644 index 543c951..0000000 --- a/stylesheets/stylesheet.css +++ /dev/null @@ -1,881 +0,0 @@ -/*! normalize.css v3.0.2 | MIT License | git.io/normalize */ - -/** - * 1. Set default font family to sans-serif. - * 2. Prevent iOS text size adjust after orientation change, without disabling - * user zoom. - */ - -html { - font-family: sans-serif; /* 1 */ - -webkit-text-size-adjust: 100%; /* 2 */ - -ms-text-size-adjust: 100%; /* 2 */ -} - -/** - * Remove default margin. - */ - -body { - margin: 0; -} - -/* HTML5 display definitions - ========================================================================== */ - -/** - * Correct `block` display not defined for any HTML5 element in IE 8/9. - * Correct `block` display not defined for `details` or `summary` in IE 10/11 - * and Firefox. - * Correct `block` display not defined for `main` in IE 11. - */ - -article, -aside, -details, -figcaption, -figure, -footer, -header, -hgroup, -main, -menu, -nav, -section, -summary { - display: block; -} - -/** - * 1. Correct `inline-block` display not defined in IE 8/9. - * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. - */ - -audio, -canvas, -progress, -video { - display: inline-block; /* 1 */ - vertical-align: baseline; /* 2 */ -} - -/** - * Prevent modern browsers from displaying `audio` without controls. - * Remove excess height in iOS 5 devices. - */ - -audio:not([controls]) { - display: none; - height: 0; -} - -/** - * Address `[hidden]` styling not present in IE 8/9/10. - * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. - */ - -[hidden], -template { - display: none; -} - -/* Links - ========================================================================== */ - -/** - * Remove the gray background color from active links in IE 10. - */ - -a { - background-color: transparent; -} - -/** - * Improve readability when focused and also mouse hovered in all browsers. - */ - -a:active, -a:hover { - outline: 0; -} - -/* Text-level semantics - ========================================================================== */ - -/** - * Address styling not present in IE 8/9/10/11, Safari, and Chrome. - */ - -abbr[title] { - border-bottom: 1px dotted; -} - -/** - * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. - */ - -b, -strong { - font-weight: bold; -} - -/** - * Address styling not present in Safari and Chrome. - */ - -dfn { - font-style: italic; -} - -/** - * Address variable `h1` font-size and margin within `section` and `article` - * contexts in Firefox 4+, Safari, and Chrome. - */ - -h1 { - margin: 0.67em 0; - font-size: 2em; -} - -/** - * Address styling not present in IE 8/9. - */ - -mark { - color: #000; - background: #ff0; -} - -/** - * Address inconsistent and variable font size in all browsers. - */ - -small { - font-size: 80%; -} - -/** - * Prevent `sub` and `sup` affecting `line-height` in all browsers. - */ - -sub, -sup { - position: relative; - font-size: 75%; - line-height: 0; - vertical-align: baseline; -} - -sup { - top: -0.5em; -} - -sub { - bottom: -0.25em; -} - -/* Embedded content - ========================================================================== */ - -/** - * Remove border when inside `a` element in IE 8/9/10. - */ - -img { - border: 0; -} - -/** - * Correct overflow not hidden in IE 9/10/11. - */ - -svg:not(:root) { - overflow: hidden; -} - -/* Grouping content - ========================================================================== */ - -/** - * Address margin not present in IE 8/9 and Safari. - */ - -figure { - margin: 1em 40px; -} - -/** - * Address differences between Firefox and other browsers. - */ - -hr { - height: 0; - -moz-box-sizing: content-box; - box-sizing: content-box; -} - -/** - * Contain overflow in all browsers. - */ - -pre { - overflow: auto; -} - -/** - * Address odd `em`-unit font size rendering in all browsers. - */ - -code, -kbd, -pre, -samp { - font-family: monospace, monospace; - font-size: 1em; -} - -/* Forms - ========================================================================== */ - -/** - * Known limitation: by default, Chrome and Safari on OS X allow very limited - * styling of `select`, unless a `border` property is set. - */ - -/** - * 1. Correct color not being inherited. - * Known issue: affects color of disabled elements. - * 2. Correct font properties not being inherited. - * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. - */ - -button, -input, -optgroup, -select, -textarea { - margin: 0; /* 3 */ - font: inherit; /* 2 */ - color: inherit; /* 1 */ -} - -/** - * Address `overflow` set to `hidden` in IE 8/9/10/11. - */ - -button { - overflow: visible; -} - -/** - * Address inconsistent `text-transform` inheritance for `button` and `select`. - * All other form control elements do not inherit `text-transform` values. - * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. - * Correct `select` style inheritance in Firefox. - */ - -button, -select { - text-transform: none; -} - -/** - * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` - * and `video` controls. - * 2. Correct inability to style clickable `input` types in iOS. - * 3. Improve usability and consistency of cursor style between image-type - * `input` and others. - */ - -button, -html input[type="button"], /* 1 */ -input[type="reset"], -input[type="submit"] { - -webkit-appearance: button; /* 2 */ - cursor: pointer; /* 3 */ -} - -/** - * Re-set default cursor for disabled elements. - */ - -button[disabled], -html input[disabled] { - cursor: default; -} - -/** - * Remove inner padding and border in Firefox 4+. - */ - -button::-moz-focus-inner, -input::-moz-focus-inner { - padding: 0; - border: 0; -} - -/** - * Address Firefox 4+ setting `line-height` on `input` using `!important` in - * the UA stylesheet. - */ - -input { - line-height: normal; -} - -/** - * It's recommended that you don't attempt to style these elements. - * Firefox's implementation doesn't respect box-sizing, padding, or width. - * - * 1. Address box sizing set to `content-box` in IE 8/9/10. - * 2. Remove excess padding in IE 8/9/10. - */ - -input[type="checkbox"], -input[type="radio"] { - box-sizing: border-box; /* 1 */ - padding: 0; /* 2 */ -} - -/** - * Fix the cursor style for Chrome's increment/decrement buttons. For certain - * `font-size` values of the `input`, it causes the cursor style of the - * decrement button to change from `default` to `text`. - */ - -input[type="number"]::-webkit-inner-spin-button, -input[type="number"]::-webkit-outer-spin-button { - height: auto; -} - -/** - * 1. Address `appearance` set to `searchfield` in Safari and Chrome. - * 2. Address `box-sizing` set to `border-box` in Safari and Chrome - * (include `-moz` to future-proof). - */ - -input[type="search"] { - -webkit-box-sizing: content-box; /* 2 */ - -moz-box-sizing: content-box; - box-sizing: content-box; - -webkit-appearance: textfield; /* 1 */ -} - -/** - * Remove inner padding and search cancel button in Safari and Chrome on OS X. - * Safari (but not Chrome) clips the cancel button when the search input has - * padding (and `textfield` appearance). - */ - -input[type="search"]::-webkit-search-cancel-button, -input[type="search"]::-webkit-search-decoration { - -webkit-appearance: none; -} - -/** - * Define consistent border, margin, and padding. - */ - -fieldset { - padding: 0.35em 0.625em 0.75em; - margin: 0 2px; - border: 1px solid #c0c0c0; -} - -/** - * 1. Correct `color` not being inherited in IE 8/9/10/11. - * 2. Remove padding so people aren't caught out if they zero out fieldsets. - */ - -legend { - padding: 0; /* 2 */ - border: 0; /* 1 */ -} - -/** - * Remove default vertical scrollbar in IE 8/9/10/11. - */ - -textarea { - overflow: auto; -} - -/** - * Don't inherit the `font-weight` (applied by a rule above). - * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. - */ - -optgroup { - font-weight: bold; -} - -/* Tables - ========================================================================== */ - -/** - * Remove most spacing between table cells. - */ - -table { - border-spacing: 0; - border-collapse: collapse; -} - -td, -th { - padding: 0; -} - -/* LAYOUT STYLES */ -body { - font-family: 'Helvetica Neue', Helvetica, Arial, serif; - font-size: 15px; - font-weight: 400; - line-height: 1.5; - color: #666; - background: #fafafa url(../images/body-bg.jpg) 0 0 repeat; -} - -p { - margin-top: 0; -} - -a { - color: #2879d0; -} -a:hover { - color: #2268b2; -} - -header { - padding-top: 40px; - padding-bottom: 40px; - font-family: 'Architects Daughter', 'Helvetica Neue', Helvetica, Arial, serif; - background: #2e7bcf url(../images/header-bg.jpg) 0 0 repeat-x; - border-bottom: solid 1px #275da1; -} - -header h1 { - width: 540px; - margin-top: 0; - margin-bottom: 0.2em; - font-size: 72px; - font-weight: normal; - line-height: 1; - color: #fff; - letter-spacing: -1px; -} - -header h2 { - width: 540px; - margin-top: 0; - margin-bottom: 0; - font-size: 26px; - font-weight: normal; - line-height: 1.3; - color: #9ddcff; - letter-spacing: 0; -} - -.inner { - position: relative; - width: 940px; - margin: 0 auto; -} - -#content-wrapper { - padding-top: 30px; - border-top: solid 1px #fff; -} - -#main-content { - float: left; - width: 690px; -} - -#main-content img { - max-width: 100%; -} - -aside#sidebar { - float: right; - width: 200px; - min-height: 504px; - padding-left: 20px; - font-size: 12px; - line-height: 1.3; - background: transparent url(../images/sidebar-bg.jpg) 0 0 no-repeat; -} - -aside#sidebar p.repo-owner, -aside#sidebar p.repo-owner a { - font-weight: bold; -} - -#downloads { - margin-bottom: 40px; -} - -a.button { - width: 134px; - height: 58px; - padding-top: 22px; - padding-left: 68px; - font-family: 'Architects Daughter', 'Helvetica Neue', Helvetica, Arial, serif; - font-size: 23px; - line-height: 1.2; - color: #fff; -} -a.button small { - display: block; - font-size: 11px; -} -header a.button { - position: absolute; - top: 0; - right: 0; - background: transparent url(../images/github-button.png) 0 0 no-repeat; -} -aside a.button { - display: block; - width: 138px; - padding-left: 64px; - margin-bottom: 20px; - font-size: 21px; - background: transparent url(../images/download-button.png) 0 0 no-repeat; -} - -code, pre { - margin-bottom: 30px; - font-family: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal, monospace; - font-size: 13px; - color: #222; -} - -code { - padding: 0 3px; - background-color: #f2f8fc; - border: solid 1px #dbe7f3; -} - -pre { - padding: 20px; - overflow: auto; - text-shadow: none; - background: #fff; - border: solid 1px #f2f2f2; -} -pre code { - padding: 0; - color: #2879d0; - background-color: #fff; - border: none; -} - -ul, ol, dl { - margin-bottom: 20px; -} - - -/* COMMON STYLES */ - -hr { - height: 0; - margin-top: 1em; - margin-bottom: 1em; - border: 0; - border-top: solid 1px #ddd; -} - -table { - width: 100%; - border: 1px solid #ebebeb; -} - -th { - font-weight: 500; -} - -td { - font-weight: 300; - text-align: center; - border: 1px solid #ebebeb; -} - -form { - padding: 20px; - background: #f2f2f2; - -} - - -/* GENERAL ELEMENT TYPE STYLES */ - -#main-content h1 { - margin-top: 0; - margin-bottom: 0; - font-family: 'Architects Daughter', 'Helvetica Neue', Helvetica, Arial, serif; - font-size: 2.8em; - font-weight: normal; - color: #474747; - text-indent: 6px; - letter-spacing: -1px; -} - -#main-content h1:before { - padding-right: 0.3em; - margin-left: -0.9em; - color: #9ddcff; - content: "/"; -} - -#main-content h2 { - margin-bottom: 8px; - font-family: 'Architects Daughter', 'Helvetica Neue', Helvetica, Arial, serif; - font-size: 22px; - font-weight: bold; - color: #474747; - text-indent: 4px; -} -#main-content h2:before { - padding-right: 0.3em; - margin-left: -1.5em; - content: "//"; - color: #9ddcff; -} - -#main-content h3 { - margin-top: 24px; - margin-bottom: 8px; - font-family: 'Architects Daughter', 'Helvetica Neue', Helvetica, Arial, serif; - font-size: 18px; - font-weight: bold; - color: #474747; - text-indent: 3px; -} - -#main-content h3:before { - padding-right: 0.3em; - margin-left: -2em; - content: "///"; - color: #9ddcff; -} - -#main-content h4 { - margin-bottom: 8px; - font-family: 'Architects Daughter', 'Helvetica Neue', Helvetica, Arial, serif; - font-size: 15px; - font-weight: bold; - color: #474747; - text-indent: 3px; -} - -h4:before { - padding-right: 0.3em; - margin-left: -2.8em; - content: "////"; - color: #9ddcff; -} - -#main-content h5 { - margin-bottom: 8px; - font-family: 'Architects Daughter', 'Helvetica Neue', Helvetica, Arial, serif; - font-size: 14px; - color: #474747; - text-indent: 3px; -} -h5:before { - padding-right: 0.3em; - margin-left: -3.2em; - content: "/////"; - color: #9ddcff; -} - -#main-content h6 { - margin-bottom: 8px; - font-family: 'Architects Daughter', 'Helvetica Neue', Helvetica, Arial, serif; - font-size: .8em; - color: #474747; - text-indent: 3px; -} -h6:before { - padding-right: 0.3em; - margin-left: -3.7em; - content: "//////"; - color: #9ddcff; -} - -p { - margin-bottom: 20px; -} - -a { - text-decoration: none; -} - -p a { - font-weight: 400; -} - -blockquote { - padding: 0 0 0 30px; - margin-bottom: 20px; - font-size: 1.6em; - border-left: 10px solid #e9e9e9; -} - -ul { - list-style-position: inside; - list-style: disc; - padding-left: 20px; -} - -ol { - list-style-position: inside; - list-style: decimal; - padding-left: 3px; -} - -dl dd { - font-style: italic; - font-weight: 100; -} - -footer { - padding-top: 20px; - padding-bottom: 30px; - margin-top: 40px; - font-size: 13px; - color: #aaa; - background: transparent url('../images/hr.png') 0 0 no-repeat; -} - -footer a { - color: #666; -} -footer a:hover { - color: #444; -} - -/* MISC */ -.clearfix:after { - display: block; - height: 0; - clear: both; - visibility: hidden; - content: '.'; -} - -.clearfix {display: inline-block;} -* html .clearfix {height: 1%;} -.clearfix {display: block;} - -/* #Media Queries -================================================== */ - -/* Smaller than standard 960 (devices and browsers) */ -@media only screen and (max-width: 959px) { } - -/* Tablet Portrait size to standard 960 (devices and browsers) */ -@media only screen and (min-width: 768px) and (max-width: 959px) { - .inner { - width: 740px; - } - header h1, header h2 { - width: 340px; - } - header h1 { - font-size: 60px; - } - header h2 { - font-size: 30px; - } - #main-content { - width: 490px; - } - #main-content h1:before, - #main-content h2:before, - #main-content h3:before, - #main-content h4:before, - #main-content h5:before, - #main-content h6:before { - padding-right: 0; - margin-left: 0; - content: none; - } -} - -/* All Mobile Sizes (devices and browser) */ -@media only screen and (max-width: 767px) { - .inner { - width: 93%; - } - header { - padding: 20px 0; - } - header .inner { - position: relative; - } - header h1, header h2 { - width: 100%; - } - header h1 { - font-size: 48px; - } - header h2 { - font-size: 24px; - } - header a.button { - position: relative; - display: inline-block; - width: auto; - height: auto; - padding: 5px 10px; - margin-top: 15px; - font-size: 13px; - line-height: 1; - color: #2879d0; - text-align: center; - background-color: #9ddcff; - background-image: none; - border-radius: 5px; - -moz-border-radius: 5px; - -webkit-border-radius: 5px; - } - header a.button small { - display: inline; - font-size: 13px; - } - #main-content, - aside#sidebar { - float: none; - width: 100% ! important; - } - aside#sidebar { - min-height: 0; - padding: 20px 0; - margin-top: 20px; - background-image: none; - border-top: solid 1px #ddd; - } - aside#sidebar a.button { - display: none; - } - #main-content h1:before, - #main-content h2:before, - #main-content h3:before, - #main-content h4:before, - #main-content h5:before, - #main-content h6:before { - padding-right: 0; - margin-left: 0; - content: none; - } -} - -/* Mobile Landscape Size to Tablet Portrait (devices and browsers) */ -@media only screen and (min-width: 480px) and (max-width: 767px) { } - -/* Mobile Portrait Size to Mobile Landscape Size (devices and browsers) */ -@media only screen and (max-width: 479px) { } - diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..3b41afd --- /dev/null +++ b/uv.lock @@ -0,0 +1,642 @@ +version = 1 +revision = 3 +requires-python = ">=3.10" + +[[package]] +name = "babel" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, +] + +[[package]] +name = "backrefs" +version = "8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/56/4744bcd0c82184e80c52b0ac4076c261a8ffa1f1b343ff2f6e89ce0e1cef/backrefs-8.0.tar.gz", hash = "sha256:b556cd7d36c3a3a2f256b89590b176b8eddfb73bcfaee3a3ddd84ea66d21ce50", size = 7013081, upload-time = "2026-07-26T19:54:24.638Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/fd/9bf53b6a6f6f519ffaac765df2f2a25e5c2fc6d32cfd2b2747099e72c911/backrefs-8.0-py310-none-any.whl", hash = "sha256:4a627b817fd2dce43b79ab48da63613340509381cd8ce0897078a0bce79a2ab8", size = 380377, upload-time = "2026-07-26T19:54:17.457Z" }, + { url = "https://files.pythonhosted.org/packages/e1/29/4bd7ae72a2634da00379c2b3bcc5439e7c94620235c6afea8af15229a973/backrefs-8.0-py311-none-any.whl", hash = "sha256:f0c35cf0102ba6b6070c12a492be3c1c1d3f5839529784b9a9565d6d04569a01", size = 392169, upload-time = "2026-07-26T19:54:18.782Z" }, + { url = "https://files.pythonhosted.org/packages/29/13/232505664e8e2a0c7a2eb0c505cfade9d715538f89a5d62bc4c272968f62/backrefs-8.0-py312-none-any.whl", hash = "sha256:87f0fae8c5f207fe9f4b2887efc71d42f4900ac78faa1af08d675ef303692dc5", size = 398084, upload-time = "2026-07-26T19:54:19.954Z" }, + { url = "https://files.pythonhosted.org/packages/8a/69/47a3dc20abc4fa5486655fde681bd55e63211b46c886d8c02223d6468431/backrefs-8.0-py313-none-any.whl", hash = "sha256:601ce68ca12385dbda06ce264406b4c4210cf5b79fd0fd627592365c92f29a88", size = 400040, upload-time = "2026-07-26T19:54:21.194Z" }, + { url = "https://files.pythonhosted.org/packages/1c/cf/e5f9b68a5b0e939a2fb933a66c20180d0c9241bf8927f7a47fa48c1675e9/backrefs-8.0-py314-none-any.whl", hash = "sha256:9ec96efa080938be92323e8e730e57718c9c88eb15ad70bbef4e1766df591408", size = 411903, upload-time = "2026-07-26T19:54:23.221Z" }, +] + +[[package]] +name = "certifi" +version = "2026.7.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/3f/143b048436775b0f76ac3eec145c019e8173ccc2885c8f20319b996d5e83/charset_normalizer-3.5.1.tar.gz", hash = "sha256:6117b84ea48435e5356dc737f5121485c30920ba43375fa7b434fd753df0eac3", size = 171764, upload-time = "2026-08-15T08:20:44.807Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/aa/554e2614f38fc34c58ff1d0911ae8535ad2516440d5482d76fe59f1088b0/charset_normalizer-3.5.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1ee1e296209fdce05b81b663250eefa02213a2da7b41bf26f7829b8ba3545aa", size = 369072, upload-time = "2026-08-15T08:16:22.964Z" }, + { url = "https://files.pythonhosted.org/packages/03/6d/439231dfc3ccfa6f8c06477b7da2219cbd41a2de3d49084df8ec7b5100f2/charset_normalizer-3.5.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9fbdce1e47394b09bc9f26ab117dfc8d6491977a11d86f592bb42c779db2fda", size = 251142, upload-time = "2026-08-15T08:16:24.81Z" }, + { url = "https://files.pythonhosted.org/packages/55/53/7d819bd23a00ef45039146fa2cce1daa2f0771e758c5653ee1f6edac91ed/charset_normalizer-3.5.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:00668ebb0609751758682eb0b5857e7c35b9f00e84dfdef062e103244ec94d45", size = 240714, upload-time = "2026-08-15T08:16:26.392Z" }, + { url = "https://files.pythonhosted.org/packages/b2/2c/45847198c16f4b38090cc7423b2b6a9008e438704d8ab413211832498d31/charset_normalizer-3.5.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba2f37ee79e6338845261a3c5b1784e5d1acdff2c0785b284f1b633033d136ab", size = 279637, upload-time = "2026-08-15T08:16:27.961Z" }, + { url = "https://files.pythonhosted.org/packages/69/2b/d8be3523ddf9f0b0f3e56d1359034aa10653a4d11564c697f802b4775766/charset_normalizer-3.5.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ce854f5f478050ade5a238731c4ca985a7d3b3cb53ff600a9b5c3b689b5f0a7a", size = 276543, upload-time = "2026-08-15T08:16:29.399Z" }, + { url = "https://files.pythonhosted.org/packages/32/cd/4f564b8f132de25db594efc706897069f016790cea63a5669c9df2675f64/charset_normalizer-3.5.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:96eefc178f8636b9c760c5829345307fd81cfae9ab1e80997dbddeb0f54ee9a3", size = 261644, upload-time = "2026-08-15T08:16:30.722Z" }, + { url = "https://files.pythonhosted.org/packages/f5/e3/38b975422534a608f98c360e79c2f07c763d66dd4272300d45fb1fee54b0/charset_normalizer-3.5.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:366ec70f5547c640d3ce1985722490f23faf4eb5216a7eeba78277490e78dacb", size = 259609, upload-time = "2026-08-15T08:16:32.248Z" }, + { url = "https://files.pythonhosted.org/packages/87/bd/fbc24d825c66f1c74f6ccdea3742c3d8354a4888e86d1315a197fee69061/charset_normalizer-3.5.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:950f23cb393f85543777b0433f082cddd25b51ab398eac7971146495679efe5f", size = 252457, upload-time = "2026-08-15T08:16:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/b9/2d/918d0e98a0e679469ed05bb2d90c2088b4d315bb612969d8499f76fb5210/charset_normalizer-3.5.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c1dcc36dcb96abc02236e182d17e0f71430152a6c2c7447421da2d2dc144edea", size = 242240, upload-time = "2026-08-15T08:16:35.396Z" }, + { url = "https://files.pythonhosted.org/packages/20/c8/c36f6e0b2dfec351bd38cbc05362697e58bcd073d7dbd95154290c9714ce/charset_normalizer-3.5.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:07ffd07412fc5d5e84cd8952acf9ff7e4ed7a708e69d1bada19d8ba91711353f", size = 280308, upload-time = "2026-08-15T08:16:36.825Z" }, + { url = "https://files.pythonhosted.org/packages/ca/7b/311b3e02e8c4092400c449c850a760d8c45d900983c83a70cc07208c551d/charset_normalizer-3.5.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:f5542f9b941279d82d41eb0aa9f98eba36fe4df5c7086c651df7944935b37182", size = 258679, upload-time = "2026-08-15T08:16:38.22Z" }, + { url = "https://files.pythonhosted.org/packages/b9/90/082cc45599c392f28c036a497f49e0634041a785fc3849c80ccf396d096f/charset_normalizer-3.5.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a545775cfe815855ea32d7c27731d79da358ef2055b4a25830231b1622dd18aa", size = 277221, upload-time = "2026-08-15T08:16:39.62Z" }, + { url = "https://files.pythonhosted.org/packages/58/ad/b9aecf38d805cbcf84fa94f14c5d972a16561e20296a11dc799a5dcf3763/charset_normalizer-3.5.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:494b70049a4d69aec6e8137c13af4cf8db8c9f9820a1392ac293b0dd2987a818", size = 263799, upload-time = "2026-08-15T08:16:40.885Z" }, + { url = "https://files.pythonhosted.org/packages/b7/23/b38a20598d5a825f85d9d7636860e56ff0db1479f86497a6e485aa9326f7/charset_normalizer-3.5.1-cp310-cp310-win32.whl", hash = "sha256:94fbf1c0c6cc0d3d5e50f9a9313a8cdca90dd696d34b381cd1704f8c9e939f20", size = 182037, upload-time = "2026-08-15T08:16:42.198Z" }, + { url = "https://files.pythonhosted.org/packages/d2/21/83fffb77864408b8bf0fe1ca603926401d6f8775a8e150b39aacc9958f8a/charset_normalizer-3.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:be47f99644b208bff7766314013f9acf57b056b04191d570d68ad14022cf5b1d", size = 206030, upload-time = "2026-08-15T08:16:43.787Z" }, + { url = "https://files.pythonhosted.org/packages/86/2e/b93135b5034b1157fb29554b0d06d4844ce62282f0e0a14036f93d7ee2e7/charset_normalizer-3.5.1-cp310-cp310-win_arm64.whl", hash = "sha256:a6d095662e73e74f0a49988e0593373e243e3a52e27bfeea0a859e88acf4a0f5", size = 185092, upload-time = "2026-08-15T08:16:45.177Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b6/034f6802e9c3f6418966cfabb7db8c9252cc2429c5098f41cc43af804149/charset_normalizer-3.5.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eda059b6bc8bc0812d626fd91a7ce01bf583df0a61296eff390fd94141a34e30", size = 363585, upload-time = "2026-08-15T08:16:46.646Z" }, + { url = "https://files.pythonhosted.org/packages/d5/fa/6a7e2a7c4b5451912b8c417732df79574354443592a88d616de03da66ae5/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa2bb0b37202dca27175591f761108b5d34096ade1191ffe4808bdf6b1571488", size = 251189, upload-time = "2026-08-15T08:16:48.287Z" }, + { url = "https://files.pythonhosted.org/packages/a4/c8/ab42b07cfd82e919f427fcfaa7c41abae8242833ad1aad66d42bae40b669/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b2b1b3fa5670c127b246df1d0c059defd41f689a868a3b9d79df9b1cac42d22", size = 239724, upload-time = "2026-08-15T08:16:49.67Z" }, + { url = "https://files.pythonhosted.org/packages/e7/80/b9348b5d3041209f98b4cdad7655766369233f1d533f4f4f7558e9717bec/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6e5e4d73d588ca5ed09df1b7dcd1b203d1df3c542e3f50d126c947d432b10731", size = 280078, upload-time = "2026-08-15T08:16:51.228Z" }, + { url = "https://files.pythonhosted.org/packages/82/38/083a24028304bc85bb9e376fed801178423dcbb67495f73b6ea0624e1894/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b54e7e13267d49ffbfe68e25b3cbd774dab38fa37238f71265e91b36146eb21c", size = 276650, upload-time = "2026-08-15T08:16:52.625Z" }, + { url = "https://files.pythonhosted.org/packages/0d/35/731ac04aa0a097fc1c97f0994c375bdb230c6c96619db794208fe664e9ce/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7b742bf31c88566b4bb6335a7f393bb322e580b6bb98df7bd0c25e6e3519ce8", size = 262325, upload-time = "2026-08-15T08:16:54.085Z" }, + { url = "https://files.pythonhosted.org/packages/f5/28/c2028e7021fb89c6e56868ed0e387b8e9aa811abdd2ab3208d6578d2c930/charset_normalizer-3.5.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6ba32c4d2abf1d2fe7cf27d280f4cca5664233b0f885549c7761719eb977f486", size = 261140, upload-time = "2026-08-15T08:16:55.604Z" }, + { url = "https://files.pythonhosted.org/packages/28/f0/0c0ceec6d98b7daa62e361e418135d59685811d79ba11529aad5cdf15e84/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0722590aabf9dc6a6c0343d523c05458fa2b5047dbe6302fd526bb570600753f", size = 252791, upload-time = "2026-08-15T08:16:57.103Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3e/48f4cd187b1c33189d86039e9cbe4f92c05454175504b44ff81806d4d1bf/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:aa1099b956fb795e686d073568f6dc002a0bb89765ea6d5b055dd7d9bf1b116c", size = 240730, upload-time = "2026-08-15T08:16:58.418Z" }, + { url = "https://files.pythonhosted.org/packages/42/85/f9e22af69af67c54cce42be9455d9c81294f918b4ccc454db01f66efcac2/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bd6c173f04743d483881bffa1478d5a4624475b8cd1d2194956a75548e191c18", size = 280791, upload-time = "2026-08-15T08:16:59.918Z" }, + { url = "https://files.pythonhosted.org/packages/fd/4c/9044135f42127630b6fa742feb51256353f6ab87a78f2fdd1de3de955a7f/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:f298e218441525d3794428b4c8b8fb8662c6d3ea79925d4807ee6b9a96a3bca5", size = 259598, upload-time = "2026-08-15T08:17:01.421Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ed/1dd7cfebb4e75812934c49ca3b79757d11948053f7937ab7070c151f3c55/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:6e2912d4babbc65196ac13c2f53468dc57fb8b9c25ef913e8c59ddf7c6dc0e1b", size = 278217, upload-time = "2026-08-15T08:17:02.782Z" }, + { url = "https://files.pythonhosted.org/packages/bf/eb/239c84503cc9e3ba6eb34686a24bc66e84f3924efdd7e38e751a19f6bc10/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3d27167433c0d5f18dc850f07d0b3816221984fecdc405d6c157a6f0b8f8e9e6", size = 263417, upload-time = "2026-08-15T08:17:04.216Z" }, + { url = "https://files.pythonhosted.org/packages/37/ab/4e4510e1e288478e2c8333131d1c1382382ba8cd2165053c79e39d1da961/charset_normalizer-3.5.1-cp311-cp311-win32.whl", hash = "sha256:ac00177c4831ffa650f8609e4bdddd5fe09c03b1c0c47acece7e6ea20421598b", size = 181774, upload-time = "2026-08-15T08:17:05.58Z" }, + { url = "https://files.pythonhosted.org/packages/e3/57/32f0ccea59e8612057c61d6fd22ef2cb63cca93c9fe594094919696ac170/charset_normalizer-3.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:f9b1e28d0e8dbfa858abdba91d6b547beaf2df1a59bec6da6faae7b96a4991a9", size = 206653, upload-time = "2026-08-15T08:17:07.075Z" }, + { url = "https://files.pythonhosted.org/packages/17/d4/b65c433fc521e58b5f54293982a5e51c05cb5f2dd3f1c7a6acb65b75324e/charset_normalizer-3.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:ae31a1a1db2ee6cc2942fccaf695c934bc7f3db9f2133a3fef1f367cf1a4ab10", size = 185630, upload-time = "2026-08-15T08:17:08.502Z" }, + { url = "https://files.pythonhosted.org/packages/30/27/78873dc8b6a56357517b74b6bb9568b80450e7bb4f6ef7e3fa9d22aa0bd7/charset_normalizer-3.5.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5b6d1386bf0096d26d3a863dc0a487a5b4eb9aa93cf5ba69683d29dde6b9d60f", size = 344456, upload-time = "2026-08-15T08:17:10.072Z" }, + { url = "https://files.pythonhosted.org/packages/9a/4c/be49ada26b1f0232d57aa89bbebf997a5cc2332a5616b6eca26ff680044d/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4582c27e8c889d64811987b5967fbd3ae0c823fe1fd933b543d55ac20bb475fa", size = 238530, upload-time = "2026-08-15T08:17:11.563Z" }, + { url = "https://files.pythonhosted.org/packages/76/84/6f1290fa07ae6978d3960caa3eb1b8019bf9284ab7c2297b00c099ef4250/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1d1c7a53a6c2103925cdd6d7229f8c567379f211c869793df679f2e9f738c369", size = 230200, upload-time = "2026-08-15T08:17:12.919Z" }, + { url = "https://files.pythonhosted.org/packages/e7/a0/47b18adeed31c8f16ba9700f32c1b18594cfa09f47eb672a488c273c22bf/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e6621fb2a4988d6e53eedc455e5903e2679f3967b8acb3d639f1b63c14a2e893", size = 262222, upload-time = "2026-08-15T08:17:14.571Z" }, + { url = "https://files.pythonhosted.org/packages/38/fe/341861ac118dae06f3ec0eb487488af52128f2ef2faf0b11003944d22259/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7c0c10730342b0c9b35dd1d619beb8214e520bd96a1f870f452680b238aab3e0", size = 258951, upload-time = "2026-08-15T08:17:16.158Z" }, + { url = "https://files.pythonhosted.org/packages/6f/89/bb5108dc6c3651dca963f2b0a3ba19bbcb370c94e1b6d3e0e844a58e6dca/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b9af956078716df40d985fb0dfeb2c2120c5ca92ba4ff4b388acfd01cdc14d08", size = 248801, upload-time = "2026-08-15T08:17:17.683Z" }, + { url = "https://files.pythonhosted.org/packages/b1/ba/ef83ae3aca816393decfa3530976f38a79812d707b80b580ac33b83f9877/charset_normalizer-3.5.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f9f8405c2c758532c74fed975dbee57be1f31a6e865c031870c79a6ed3212ada", size = 244070, upload-time = "2026-08-15T08:17:19.191Z" }, + { url = "https://files.pythonhosted.org/packages/f6/0b/c5292a2462d69b7378ea89793bbb5b2b6fcf6f7dd6d1667f9619094ad553/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:96fef3e886d6a9874b14f27fc193fbdc69d5d8035783d86aa4e1cea594e695f9", size = 240110, upload-time = "2026-08-15T08:17:20.547Z" }, + { url = "https://files.pythonhosted.org/packages/46/22/111e5be3b740d5c2a5bfcedb3d237b6591e5c2e82ae9d6ffcb121fe0909c/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5d8531a6569d025f68e2321e7638fb7978f23db58e5f69f56913837aae03816e", size = 232836, upload-time = "2026-08-15T08:17:21.895Z" }, + { url = "https://files.pythonhosted.org/packages/f9/d2/d2aad6fe0dbb44b194bf3becb60f5a0ac48446ade999a47fe7bb41eb09a7/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:aae2ee51122d3ae968a3837d97dc24a0aeebb0dea23694422cd172bd30017cd6", size = 262712, upload-time = "2026-08-15T08:17:23.727Z" }, + { url = "https://files.pythonhosted.org/packages/35/5a/337e4663a5eae6de99db940ee8066d4145caafb61327db62deda15313cce/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7235dc28fc6dd9d832ac7c7bce95367dedb85929f17368a0c2bee1e080b9acbf", size = 242977, upload-time = "2026-08-15T08:17:25.157Z" }, + { url = "https://files.pythonhosted.org/packages/ca/85/f82f8a92e31c7519410e2e1afdc630f28ec47490ce2c09a11c1a43cbb459/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4abdc5f9ad448c1ecbfae2974b820535d6bc6e7eef63babbab3d81cf46968c71", size = 260207, upload-time = "2026-08-15T08:17:26.602Z" }, + { url = "https://files.pythonhosted.org/packages/b7/52/643d11ffd60e9ac2fd1fb87e167a19285b9eefeff4a40e63c87cbfbeab36/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ba501e667c17d8411f98e67a022d9604ef179aff0e459b7e292c796837c13573", size = 250562, upload-time = "2026-08-15T08:17:27.971Z" }, + { url = "https://files.pythonhosted.org/packages/62/16/46556278c2168d12df9da7fede5dc6fc70e60301b26a82bbeec238c9cfe3/charset_normalizer-3.5.1-cp312-cp312-win32.whl", hash = "sha256:cfa1c0cc3a8f9f53f1243a5a99ac36fd003880199383b37672e86ddda9cb07e2", size = 178507, upload-time = "2026-08-15T08:17:29.277Z" }, + { url = "https://files.pythonhosted.org/packages/9d/7a/4c6c298171e6b3e745633180ff59350fc0ca0db1ffd28df1e369e0579f71/charset_normalizer-3.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:3617ac3cfd8b9888f145ad89dd6e692285834b0201c6074a5eeaad3fd4d668c2", size = 200551, upload-time = "2026-08-15T08:17:30.668Z" }, + { url = "https://files.pythonhosted.org/packages/cd/d7/eb95a042f0dd22e304b0b6472b154f3546a1a039a9ee89ccb2a7f61591fc/charset_normalizer-3.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:88e85ab89cb822c1e635f51d6d32e488f94e002e70e2f492bdb8b945543f345a", size = 180700, upload-time = "2026-08-15T08:17:32.028Z" }, + { url = "https://files.pythonhosted.org/packages/bc/61/2cb6ad133dbbb449fa2d37ccae973232f4827e799af258d15e589a3d1e9e/charset_normalizer-3.5.1-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:4f298bdadb8f0b9e5672877f647d1be9373ef5320c9e2f049795e26cad28b6a9", size = 211584, upload-time = "2026-08-15T08:17:33.597Z" }, + { url = "https://files.pythonhosted.org/packages/18/57/a305c968be1ca13f3dd1b32f445877e97addf55d80b65c7cb35fac82b777/charset_normalizer-3.5.1-cp313-cp313-android_24_x86_64.whl", hash = "sha256:88ca277405c2d3b71c4e1c2ee0e7966e807bcba86a69d11e19ba199d18ae4491", size = 223359, upload-time = "2026-08-15T08:17:35.022Z" }, + { url = "https://files.pythonhosted.org/packages/09/0a/d3646670292ce8d8f8cc11ac067d44885e697a5591f57a9221128da5e7b3/charset_normalizer-3.5.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9362dd90aa7dab48c0054a21187791ccf05473f7dba5d92b8033ae62164675e7", size = 194464, upload-time = "2026-08-15T08:17:36.452Z" }, + { url = "https://files.pythonhosted.org/packages/de/93/d51ec556e01042fed6f993ea859311bc7917b466684182fbbceb6ca24762/charset_normalizer-3.5.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:977cdbd483a9cff38179bea4fd754289a6f2195c7abd414aba85410b3e66cc5e", size = 197676, upload-time = "2026-08-15T08:17:37.819Z" }, + { url = "https://files.pythonhosted.org/packages/a4/a0/562247944386f7d4ef94467e84876600cc1e0f1b93239aaa9213d2bc3cbd/charset_normalizer-3.5.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e90251c0c7bdd54a100a0dce3c07b7e637278c93af29dbf78ebb89a58c4bac7d", size = 340473, upload-time = "2026-08-15T08:17:39.303Z" }, + { url = "https://files.pythonhosted.org/packages/31/e7/1d994be1b93d41e9502b8b0460eaa88a1dd8df335df415db87d6c3e91ab2/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94d78ecec2605a8d0398b0f365d5f12a63248438516f5dac536a5eff7337df4a", size = 240156, upload-time = "2026-08-15T08:17:40.66Z" }, + { url = "https://files.pythonhosted.org/packages/09/53/27923ce5cc6cbccb832037b27dca98882d9c53e9b69e866bbbef4aae7fc8/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d59b75732e9b6f27388e10c14b0259cc5f2e48c78627d185e6a177b58ad3cffe", size = 228246, upload-time = "2026-08-15T08:17:42.003Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/5a97e84d63af1d55c07439cb80e56d99a8efb4295700eb4e18c0d1615d2c/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0d929fc574b4d6fd9e7c0f5c2ede8716a41911923aa7fa5fce38e0818aa4a1ac", size = 263660, upload-time = "2026-08-15T08:17:43.627Z" }, + { url = "https://files.pythonhosted.org/packages/7a/c2/071575791dcc88316c0a9a65ce38897a82e4cfe4a325f0f7fe1b1ac47bcf/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:394fea06235c8543390050ed5f529187074b029fb027213f6c46ac11ab5d950e", size = 260354, upload-time = "2026-08-15T08:17:45.094Z" }, + { url = "https://files.pythonhosted.org/packages/fb/af/63240b0c0248c075c2535a1f1bd992821d8251b9f173abc13329661d09e4/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62b55f6722735a6c472f88361cde6640608773d9443cebdbb51abf436a1fcdd3", size = 250638, upload-time = "2026-08-15T08:17:46.496Z" }, + { url = "https://files.pythonhosted.org/packages/4d/66/70dfad64f15be09c15ccfee81330a7e515895dbe296dd23114e9a231268a/charset_normalizer-3.5.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa48b1b63d639f9483e0633e092f5851e2348c352f1f9bb6c8182f87884ef876", size = 244583, upload-time = "2026-08-15T08:17:47.963Z" }, + { url = "https://files.pythonhosted.org/packages/c0/24/ef36367d38b9ddd4bccbf72888c342e8de1f5ae506fa0b2dcf970e2732a1/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c71fb0d56c920c269cd3e2e3fe7c610e3f1fdb21a6ce60efa6430ff63676cea6", size = 242038, upload-time = "2026-08-15T08:17:49.481Z" }, + { url = "https://files.pythonhosted.org/packages/db/ab/55e683ba0fff2e43adafc10daa3001eac90fdaa419a97227d5a7067eedde/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:485a0d363cafefcd2538a73c7c838daa2035f09b2c9f9b5e3133f80c6aeb84c2", size = 233677, upload-time = "2026-08-15T08:17:50.845Z" }, + { url = "https://files.pythonhosted.org/packages/bd/67/0f40eaf8d1b6e7cf15e82382a2965efaca787fc1c2794b7021d37aaf5036/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c0ea61a470e070686aa30892fed79e297d2c8d0ab46b8bcdf027d38c51da591", size = 264491, upload-time = "2026-08-15T08:17:52.61Z" }, + { url = "https://files.pythonhosted.org/packages/5c/64/12b4c2a11ee8df4fcc518c78b0d93e3a92bd3d5253d1617ce74ff0e8c7ef/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:90b7481fb62fbe172c558bc6fd1c4c98d82004a54a7551f20e11ac9bf0b8708c", size = 245196, upload-time = "2026-08-15T08:17:54.023Z" }, + { url = "https://files.pythonhosted.org/packages/37/2e/651d910af6d0fba325eee1cda37ec5443462ed25360e666c144166eb6091/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:35fe081843b35aad20ffeccec3eeffbe637b15d14f3fb22cc1b59cd8ec17e93c", size = 261660, upload-time = "2026-08-15T08:17:55.491Z" }, + { url = "https://files.pythonhosted.org/packages/90/c6/b09e05e6db7f64338e0dc067c79577b1138da86c1e38369096851d96be88/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fd0350afdc3aabd5576f60ea109228bd5538139713c7b094c5cd27c73a98bc6f", size = 252618, upload-time = "2026-08-15T08:17:57.025Z" }, + { url = "https://files.pythonhosted.org/packages/76/4e/362d4f9fdcdf5556fb2aa3ce7d4a58ebce03ed1ff03aa1d9aca8d02f13f3/charset_normalizer-3.5.1-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:9d9a0dc7cbe9bec24c3f767c9122c41fe5a1bc43f47cd099d00d393e09769de4", size = 140362, upload-time = "2026-08-15T08:17:58.425Z" }, + { url = "https://files.pythonhosted.org/packages/b4/d4/703be739b26acce318bd29eb3b25b7209e1b1f527f9eae3d1f1f01fdde2b/charset_normalizer-3.5.1-cp313-cp313-win32.whl", hash = "sha256:d63600d620ad0064c3a748b950ac5ea38a80190e5498532efefa4b7b3f1da1f3", size = 177755, upload-time = "2026-08-15T08:18:00.037Z" }, + { url = "https://files.pythonhosted.org/packages/8a/33/56d97ade41c8db611e727168c52ae46c9224c362ec28d4b65d7e9869e8da/charset_normalizer-3.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:aea996a6aba25260827c9ea511d1addfde2da9eb686ac961838509086188b7e6", size = 199295, upload-time = "2026-08-15T08:18:01.506Z" }, + { url = "https://files.pythonhosted.org/packages/5b/75/5b20dd1e6573a01a08158fe104104fa2c8abf941745596954185726cd46c/charset_normalizer-3.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:fd0a274c0e5f9a21565cd9d3dd749b61f96b7aa1e20a93aa1ba4029518f2e5c0", size = 179856, upload-time = "2026-08-15T08:18:02.929Z" }, + { url = "https://files.pythonhosted.org/packages/29/cd/2b812ce5e888f1ce69a5350281e58aab07ae64a958ecae8912f30865718e/charset_normalizer-3.5.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:774d157f112367ff4abd29019f38f023c24e00e56edc7829c20e358a5a913ad8", size = 212318, upload-time = "2026-08-15T08:18:04.403Z" }, + { url = "https://files.pythonhosted.org/packages/9e/4a/a6ee107430768a5334e6d63f31f148a04a1a491ef161a1ac9415a73f2fa8/charset_normalizer-3.5.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:26422d45fd13551cf564c58932f7d72b4f58b93b0fcf18c35ba6be12b46bb102", size = 224897, upload-time = "2026-08-15T08:18:05.997Z" }, + { url = "https://files.pythonhosted.org/packages/c3/d9/35ae3f64f29d0179c35c3baefe575904df2913dde519129c7f75995a2b1d/charset_normalizer-3.5.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:09a7bba9f739468c8e78c36a75c33768e53cb1959fc638f510454c14683f00d5", size = 194848, upload-time = "2026-08-15T08:18:07.397Z" }, + { url = "https://files.pythonhosted.org/packages/74/76/f2fc7380f056cc273a53af37f50d08ad54b2c59f61078f31432edcf1c2bd/charset_normalizer-3.5.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4c9548dc78002099910abaebc0a72ac58b7d30931869e0351c09b507dff4ece3", size = 198163, upload-time = "2026-08-15T08:18:08.989Z" }, + { url = "https://files.pythonhosted.org/packages/e9/40/095ce62fa078483cccc1fa2b36e6bc9580b85422a20ee9f925341c50e44f/charset_normalizer-3.5.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c428c6c31eb5f4277d7f8eccaf767fbd548ddd5ce3c8b4f4cbbfab3d96b5904c", size = 341823, upload-time = "2026-08-15T08:18:10.458Z" }, + { url = "https://files.pythonhosted.org/packages/f1/5a/0e58b1c04a1596e0256f407274a92d5fb2ee21324409d1fab1da48a65b5b/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2f06b7eae9dbe77fe1d644ca244dad508de8d302870a43f3c559b521270938a0", size = 242458, upload-time = "2026-08-15T08:18:11.989Z" }, + { url = "https://files.pythonhosted.org/packages/22/95/b4618ce912e6db0b1aae89ba788e38e8a7eba0f3025cc66e8c0699f977b2/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b7430cf5728e68f6c462254009a6ef4086e1bea43cf2f57aa9c55fb4f50ff96", size = 226717, upload-time = "2026-08-15T08:18:13.401Z" }, + { url = "https://files.pythonhosted.org/packages/8a/76/c681192bbda3d55356db5dadd64381d5202b37c6b598fcda5282e88b5d3d/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab743e9bc90c1f73552ec33e10e3331315acd2c397b36065b591b0181de533cc", size = 266111, upload-time = "2026-08-15T08:18:14.961Z" }, + { url = "https://files.pythonhosted.org/packages/88/be/55127bfca72c0cff6c022488d140d7c5b04c771e3b72e9bdb4836d54979d/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6f7deae3feb4edfa2efaf7c574fe88cbf055038a6abdb40188e4fff66d5699f", size = 263128, upload-time = "2026-08-15T08:18:16.515Z" }, + { url = "https://files.pythonhosted.org/packages/e0/91/39c3af510b0aa32bbda03374259200f28430febfd1bf5e511fe765282ce5/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15f024313246a4ed976c60f440bb8d257815513a681d212ff74fd46f7d715a90", size = 251240, upload-time = "2026-08-15T08:18:18.127Z" }, + { url = "https://files.pythonhosted.org/packages/1c/a5/cbe418bbc6ecdfc3e05a0116002897c4b403a5e838d697e64c78e9f0190d/charset_normalizer-3.5.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:823f82903d189af463d7df250ef1f7f696f3cee08cc8d91deb565e8d425f6506", size = 245282, upload-time = "2026-08-15T08:18:19.625Z" }, + { url = "https://files.pythonhosted.org/packages/cc/a4/689bb42e8e7cd492f3cb64907c6bc00ad247ec9a3628cd3f8eed126e8ae1/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:01e93745f7f219b703b60ba7afead36cfc4242782be5af484673fc500df12da5", size = 244597, upload-time = "2026-08-15T08:18:21.121Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ce/9962938e179cf9f699d3f1e7b3114b5d7642dee6a893745229f9dd04f274/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:329fc3ccb63ad22d867d84c2adea759a64079a37ba4a343433b02c7a2816871e", size = 231376, upload-time = "2026-08-15T08:18:22.57Z" }, + { url = "https://files.pythonhosted.org/packages/85/54/46000450ada53bd9eac5429a2c8c54cd2d9b39c0c255f229aea9af0948a5/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:bb57753e36e4855b8ca375069482250a6246372331a3e4f3407eaebb007443f5", size = 266715, upload-time = "2026-08-15T08:18:24.235Z" }, + { url = "https://files.pythonhosted.org/packages/3d/bb/618749d70f792b44252a777bf89bfb86823b9bbc1ea13fe8ce759b07f38a/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:fce8cbd4997efeb450bd298b54f755dcdff18d496f7a5ddbb4867c6d7c88fdc3", size = 245848, upload-time = "2026-08-15T08:18:25.726Z" }, + { url = "https://files.pythonhosted.org/packages/7e/3f/ffb64458527c7668031d5eb095d978de561958dc9f5b53f8e488a533e603/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:6c9cdde8becb25a7fde49924511aa2644d6f8081cc8df8e9452724303348d8e3", size = 264521, upload-time = "2026-08-15T08:18:27.193Z" }, + { url = "https://files.pythonhosted.org/packages/4f/ab/74a55fd803916a35ac461daf002708191aac19b546b80dc8cabfedc63d98/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9ac4444d8d4fd4c4bd08bf451ed3167aa9e7ec6cdb41b648794f1d1103652e36", size = 253054, upload-time = "2026-08-15T08:18:28.568Z" }, + { url = "https://files.pythonhosted.org/packages/a0/2a/6a9034b7d3c60b17499afb482df5878bf9fa20b50cc3887d5ef017a833db/charset_normalizer-3.5.1-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:f03ac127268b43ef4fe9e6ab6794a6794b49485a0cc0c1db79876d2f33f75bc7", size = 140580, upload-time = "2026-08-15T08:18:30.214Z" }, + { url = "https://files.pythonhosted.org/packages/f3/46/1d362e1a00d035d66b9869e1281eee115907f7e390a16a07824ab5737360/charset_normalizer-3.5.1-cp314-cp314-win32.whl", hash = "sha256:1f5883d77fd409a261abb5dc8ccbe335720d798b1de4abb3b1d47ccbbc76b53b", size = 180325, upload-time = "2026-08-15T08:18:31.877Z" }, + { url = "https://files.pythonhosted.org/packages/7a/7c/4938c329b6a9d446f6a59aa2092ff7118f274209b5ed0e26893d1d30a63c/charset_normalizer-3.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:c658c50ac0c98cd755a2dd50b7977d3bca7df401dcc47fbdfa87db53ef7d4e8b", size = 204175, upload-time = "2026-08-15T08:18:33.466Z" }, + { url = "https://files.pythonhosted.org/packages/ac/33/eeb384dbd8dec570661354592f4f2e1b2fcc92585624d146a000caf53841/charset_normalizer-3.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:4bea7f8ebe90bbd7f0e4a2de42ca6924ba23e3e76418c408ff82f1d46fabd687", size = 184123, upload-time = "2026-08-15T08:18:34.913Z" }, + { url = "https://files.pythonhosted.org/packages/1c/6c/c73fa9d5a85f6ab05395de61c5f6984e0a9ff40bb5ff888d46dff02526c6/charset_normalizer-3.5.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:fbc597639158fd7c14d55e808718848319540f51b0e6746e3eefa59723a4a348", size = 381682, upload-time = "2026-08-15T08:18:36.349Z" }, + { url = "https://files.pythonhosted.org/packages/30/c7/63565f860921457feba93bae6c86fb7746deb4cffeed2f375cb845318146/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e71c909f353863b2b89c83de2ebed71ea6d0df8a6ef65a128193c5e650766bef", size = 240826, upload-time = "2026-08-15T08:18:37.887Z" }, + { url = "https://files.pythonhosted.org/packages/06/ae/7ae8807410dfa33f8e6f1715740adeaafa8a816cc4cb33508f54b1f7c896/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7ac76cf9afd34929d76eb7fcb63be476a4853d8a96f0dcf2d0db68a0cbdf9885", size = 227861, upload-time = "2026-08-15T08:18:39.315Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a3/887c1642f0da26000b0e0652d91071113c0e72cea33952e225cf589f49a9/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a3a370082ce34d0612f421e15fe011c53bb1feff21a26d06ad4fb244dab5a375", size = 260758, upload-time = "2026-08-15T08:18:40.88Z" }, + { url = "https://files.pythonhosted.org/packages/3e/11/e6f5b9a3d0e55b0ef7505cd3765cdd48f22db89994c947b316f52f801fd8/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:256dd4d85d9e4dc595e2bc983c980e73f62ddeb3165c58b4c3dfe78c5c8548c1", size = 259950, upload-time = "2026-08-15T08:18:42.351Z" }, + { url = "https://files.pythonhosted.org/packages/1b/ee/e4e10a94d51cd1ee638aa7e00b65399e6b2a4e8376ab6d2eac9f95586671/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:58d4aa13a59c969dbfdf9e6a9560e242cbfd9e8a8f50c2747714df1a423adf65", size = 249329, upload-time = "2026-08-15T08:18:43.914Z" }, + { url = "https://files.pythonhosted.org/packages/c4/25/d5f4198819e6059735a84e8d0bfb72dc33976da67b97adcd3fb5a5e07ec6/charset_normalizer-3.5.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0c6dfb5ca6723eeed15aa8e564a014d69fcb8812f94eef11fe3631e0508199f5", size = 243137, upload-time = "2026-08-15T08:18:45.368Z" }, + { url = "https://files.pythonhosted.org/packages/a5/e9/e925ca7569cf9fb9701fd82503fee73eea5268fdb856bdd64947092d3daa/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c010f5581d9c612804cc59fcf7b524b707fbcb72828551237ab545bb5c7034af", size = 242820, upload-time = "2026-08-15T08:18:46.842Z" }, + { url = "https://files.pythonhosted.org/packages/34/17/672c251a888ed2aebcdd2fe830ad0104e25ff83c43f5c4f9c15e9fc6853c/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:52ec005752a56ae79547a05c0139ca2501a0c866390b6115008456b9f0e7cde1", size = 230504, upload-time = "2026-08-15T08:18:48.353Z" }, + { url = "https://files.pythonhosted.org/packages/3f/fc/f6a85abebd42ce4da2f1db0aa56cc6a0df1995e318b3875d14401b8381d1/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2bced4061f000f7187254a02ad3433ae17eaf991747ceea2f478422590a5bba9", size = 263087, upload-time = "2026-08-15T08:18:49.859Z" }, + { url = "https://files.pythonhosted.org/packages/98/66/7c42677e739ba66746b297e2046918d793078094dc239e1e72768cffccc6/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:9eea3ab2597a5e65fe65296e2d6a84570845a6b55532d90333d740d48bbc850a", size = 243269, upload-time = "2026-08-15T08:18:51.601Z" }, + { url = "https://files.pythonhosted.org/packages/de/d8/a50b79237f417af10f8c2a501ce8d1ca87829a22e69117891ca4ba20a69e/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:496846868fea80e479324862fa877f02411f2fd0f83b79ccee2607aa68b2a032", size = 258766, upload-time = "2026-08-15T08:18:53.23Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1d/0fc91aeaeb3c83b748f532399ce67cf84604b48297405d740000f7a9e786/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:85d5855daafc240cc045c026d7a15fd198a09b0fc8ff6f5ecbb5297b509cb11e", size = 250814, upload-time = "2026-08-15T08:18:54.768Z" }, + { url = "https://files.pythonhosted.org/packages/ae/10/3d8c777cf9024615295aa1b808324ad5b4a77855869c00824bad74ffaf8a/charset_normalizer-3.5.1-cp314-cp314t-win32.whl", hash = "sha256:58d3e12c88e0950bca850ae1f7c256055c097639c2edb9eb123af9807d8b15e4", size = 191074, upload-time = "2026-08-15T08:18:56.305Z" }, + { url = "https://files.pythonhosted.org/packages/4d/81/ae557d3c44d1a1d688696d60563413a0866a91b7ebc50f20df838be3d8c8/charset_normalizer-3.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:acaf604462bf330b0d07e7a07c1d6e4adac79e5fb13e9c5140590542cafacc00", size = 216476, upload-time = "2026-08-15T08:18:57.889Z" }, + { url = "https://files.pythonhosted.org/packages/27/e9/61c01fb8b804692569c036b3fc50495814502dcf13a60649c6055390b02c/charset_normalizer-3.5.1-cp314-cp314t-win_arm64.whl", hash = "sha256:fdb8a068947befafba9952162645dc2fecaeb400e64584829ed5e9b2fbe21a7f", size = 194115, upload-time = "2026-08-15T08:18:59.418Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4e/8544831ef59d8f27ce92c80871380fdacc8076a8a56ed62f82e54f991333/charset_normalizer-3.5.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:9085f87b0e38a2b92b8923059b4e8789fe40d9279712d15dcc670048d77079af", size = 342048, upload-time = "2026-08-15T08:19:01.054Z" }, + { url = "https://files.pythonhosted.org/packages/7f/a6/e3b46852424246065355644f4fb6dbccc0239a42a2eee27ecfc8957f0bcd/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2679de311c7946dde5d3b6f44941844133ff5c7cb86099c0061ab1e8901c20a8", size = 242997, upload-time = "2026-08-15T08:19:02.492Z" }, + { url = "https://files.pythonhosted.org/packages/03/3b/0cc9a26777334ab2f2e3089b948bbf4e4fe72ea70b897715ef6415043ec8/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:baf3775a2635e5a11fbd5e4e64ee69c7e86875d224a5c72aca4c141064589a90", size = 237014, upload-time = "2026-08-15T08:19:03.943Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c2/027335f0aa337a2a2e121bac1ad88c4f02ba6053ea0926802784f3db11af/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ac8c94b6539074e0f40899301273ac8402b9b3e01c7b7ba269ff30340aaaf20", size = 266174, upload-time = "2026-08-15T08:19:05.598Z" }, + { url = "https://files.pythonhosted.org/packages/86/d3/e367787febe4e74769dec0f406f2c3c8d1b955fce5aee1fd0f94e8367a45/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fe532b3c966d1fb794e0698e4589d0444017ae77fc0b31edea13c0e35bcc449", size = 263361, upload-time = "2026-08-15T08:19:07.251Z" }, + { url = "https://files.pythonhosted.org/packages/af/3d/391b193eb9f3e84b02f9314088c386debdc0debee843535aaea2e2c6715d/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5c84bec0ab5ae0c64bfe73a7d2adcb5ce73b467523fc27fd6a28ab2aa6cbe35a", size = 252143, upload-time = "2026-08-15T08:19:08.816Z" }, + { url = "https://files.pythonhosted.org/packages/2e/57/de221f1745a90d418199761967e2776bfe2c275a1194220985e8c1d37833/charset_normalizer-3.5.1-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:854066be00447fa8de2ccbbe893e2ffc4b123ef16d897af794c1e18bd4a714b0", size = 252086, upload-time = "2026-08-15T08:19:10.255Z" }, + { url = "https://files.pythonhosted.org/packages/c8/e3/d119f86a01f9331e8186175f24873b1d74a7ee9e2e4b4d68f9947dae5afd/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:21b82d8082f6f5e7f456ef0bd16323d08de1266efbfeb476e64b2a91d1471a4e", size = 245231, upload-time = "2026-08-15T08:19:11.807Z" }, + { url = "https://files.pythonhosted.org/packages/26/de/d8e48c135ae480879539cdb179c8d3b50c7879497d75dd899b5763b69cee/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:838648accb3a7fd9803fd45c87bce8509648eb0c11bc34e216141300977244f2", size = 241546, upload-time = "2026-08-15T08:19:13.416Z" }, + { url = "https://files.pythonhosted.org/packages/67/c4/217755fd1abc50d326c252922cd642002758095a81ff45010337b8b3ef65/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:195ce897c6153c0700078142cf8efe3e6454ca4cf4357499e4078dfd83396626", size = 267033, upload-time = "2026-08-15T08:19:14.981Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d7/34d8e404e358d2adcc5a228c2134643af00104c8fb0bf525f3688d756f05/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:978eab16f55b4ab2c2a745be9a0a840bf8f09a7f227d9c76eb30214d078865a5", size = 252045, upload-time = "2026-08-15T08:19:16.618Z" }, + { url = "https://files.pythonhosted.org/packages/5e/fa/40414471acf0aa0692ca77305aa00e434fcd8288f0941c93c30e9a5f8f2f/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:cc0329df4caaceb950d2f580b5ac716a377f7059624a0bafaeaf8a218c6ed774", size = 264866, upload-time = "2026-08-15T08:19:18.101Z" }, + { url = "https://files.pythonhosted.org/packages/32/90/fcc850bae791abd2e0c041847f13e270aa08692a79f3e00de6d2dce1cb50/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:687c9ca3035544b113bea2055e180af96fb63c0c476e22a9180f51925186e7b7", size = 253932, upload-time = "2026-08-15T08:19:19.734Z" }, + { url = "https://files.pythonhosted.org/packages/af/af/53afe99068b3c10b4cbae592a52ef72a7c92c0188440e83ee3a078fd8f75/charset_normalizer-3.5.1-cp315-cp315-win32.whl", hash = "sha256:706bfd38730a5ac7a365793269a00f4e988178cec121391f4248d84ad8c972e9", size = 180320, upload-time = "2026-08-15T08:19:21.37Z" }, + { url = "https://files.pythonhosted.org/packages/c9/bc/f46a132041b29e4a8779ed712d3df1bf112e94ca8de58b66d7ec2c0cf8b9/charset_normalizer-3.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:92caef967d287a407085d61176fce4012b1dd62daed4eb6d5ceb26d3d2538712", size = 204174, upload-time = "2026-08-15T08:19:23.088Z" }, + { url = "https://files.pythonhosted.org/packages/a1/5d/9ed554480eda8e447b673648628fdc29574d23dbad01fe11837adedd1cae/charset_normalizer-3.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:5fc45d653ea8c9a20479167e11d4a0f8cb2fa3470737ab6f9c827532313187b7", size = 184126, upload-time = "2026-08-15T08:19:24.471Z" }, + { url = "https://files.pythonhosted.org/packages/3b/32/9b8929bf384061ee1fe5d9c27c6f9776d3d824039ad4e14c88ec00c7808e/charset_normalizer-3.5.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:59171c6e45bf07d0d5cab3b0bf81d945035530f6873398b3b531c31184d46663", size = 381441, upload-time = "2026-08-15T08:19:26.038Z" }, + { url = "https://files.pythonhosted.org/packages/96/10/e9aa7923d3ddac652c99a1c5f7be494e737e151566a44abe018daf757f2c/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9dbdd9205662134957cf0c324f639bdc5031c0ca056e2369e238db75187c0f11", size = 241742, upload-time = "2026-08-15T08:19:27.532Z" }, + { url = "https://files.pythonhosted.org/packages/28/53/a2d249ebddf47b889a100c0bdcb61a2f9dbb8bc24ef325cc062e4f476877/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e4b018dc5a0eee4676e38fe84a47a427816c590b93b55d9025274ec4d6ffc2dc", size = 235298, upload-time = "2026-08-15T08:19:29.274Z" }, + { url = "https://files.pythonhosted.org/packages/7d/07/469f78af590f7d5cd48e20d8dbfa3d66deeff9ba37768c04d886b5afd45c/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ced3fdd71aaa83ce593746c2edb42b7a59cb4c19c8b5c407781c72e493aae55a", size = 262500, upload-time = "2026-08-15T08:19:30.955Z" }, + { url = "https://files.pythonhosted.org/packages/55/66/3bb56a47f7dcba014055b1a1d33c6f08bbe9c1e74dba154cfa25f90ae885/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:19a3dd5aa73cef1c99687c4fc57db016a9c17104ae1185da88ba566a5d3bebe4", size = 258888, upload-time = "2026-08-15T08:19:32.458Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c1/2adc2800903fb013210349313b710a5376856578d9e33e6b9a1d8b36714a/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cc5d36d96478aa9c60654bd932525bf32964c62a7281eafdf16d85003a8d6004", size = 250243, upload-time = "2026-08-15T08:19:33.94Z" }, + { url = "https://files.pythonhosted.org/packages/95/b5/a18d0dd1157ab655cc2cb14a545f4a4784bbad70ab3502412e36097502d9/charset_normalizer-3.5.1-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:04368edf83514385ffc3e1cfd4546e595f4f1272dd23ba437a93a9cc3741d47b", size = 249871, upload-time = "2026-08-15T08:19:35.413Z" }, + { url = "https://files.pythonhosted.org/packages/ad/c3/525f508cd1e58d0450ac55ed40ac75bc3a97482c59def5278456a5fbf03c/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:9b5db6052055d34d41230fb78d7c439c23dc536a9896f6cb039e8dd92cfc1263", size = 243580, upload-time = "2026-08-15T08:19:36.886Z" }, + { url = "https://files.pythonhosted.org/packages/7c/c1/49a91fe7e97c8140094ca5c64161ab623a70d9f636bf834eace14048acb5/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:252d099029bcbea642f2a06c4ed5046bdf8b5a8150b64afa5e027e88b106e5ee", size = 239807, upload-time = "2026-08-15T08:19:38.392Z" }, + { url = "https://files.pythonhosted.org/packages/d3/58/56a48c296601274c4689b864a8e2dfb209b81dfcb39472753ce95eea662b/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:6199d5606e2bbf2b096cf64d03f8b6790c91081d5ac866b8e7bb6422738cc60c", size = 264083, upload-time = "2026-08-15T08:19:39.856Z" }, + { url = "https://files.pythonhosted.org/packages/10/4c/dc48409274a1817ff349711d26c62aa0c597df865d4d69ef79160c859193/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:77efcff2b23071c349402ac1066667a3d011f62398d81408c9b88ad991747c9e", size = 250317, upload-time = "2026-08-15T08:19:41.53Z" }, + { url = "https://files.pythonhosted.org/packages/81/58/d325912115caec62d6bdd77bbab5e0b7da5d234a9f20affdffcbcb530d0b/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:a5cbd90ecf0fc62e64726917ad083b73001f0563657a87ec3c0b504e277dc90d", size = 258173, upload-time = "2026-08-15T08:19:43.07Z" }, + { url = "https://files.pythonhosted.org/packages/34/f7/b13b1ccae2c8ec63980d13be1890eb73f8aeabbfce02a24aabc0908788f5/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:4d26f14f041e83dd8edfd61f4cd4fa7285d31798b5bf1f28e70c367ba6c41d61", size = 251960, upload-time = "2026-08-15T08:19:44.587Z" }, + { url = "https://files.pythonhosted.org/packages/1e/25/ed3f9919c5aef8cc818be1f972f565f7610d7b2076b8ebb98839516ffc3c/charset_normalizer-3.5.1-cp315-cp315t-win32.whl", hash = "sha256:ac13b004224fb341e1e25a1ed5e19d32f57cdb2a403e01f003b46f051a550f6f", size = 191186, upload-time = "2026-08-15T08:19:46.293Z" }, + { url = "https://files.pythonhosted.org/packages/69/d5/43c2b3e9d8267092b913eb8b0603f0f71993c395632886bd37a7223f96cf/charset_normalizer-3.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:35aea775dc2bd5f54cd84a1cd2696cc3207c479cb9cf0bd346f0d343e4300ddb", size = 215947, upload-time = "2026-08-15T08:19:47.853Z" }, + { url = "https://files.pythonhosted.org/packages/a8/76/9aad3e9c8865e5e0efa9a7f6f81c37a67635a985145ecd44528a81e088ee/charset_normalizer-3.5.1-cp315-cp315t-win_arm64.whl", hash = "sha256:fb78f6e7fcd8ad785d28cd577168bc1aaee827b25bb8755638f694794ea98f0a", size = 193909, upload-time = "2026-08-15T08:19:49.383Z" }, + { url = "https://files.pythonhosted.org/packages/5b/97/fb4e82231aba271ffd775a1b4993b0defc4e3059f286ae41d9433409fe85/charset_normalizer-3.5.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:41876ee62a3dddf48ff1121ad8f0798032aa03f2fd35f21f34a4cab14f18d8d2", size = 331467, upload-time = "2026-08-15T08:19:50.959Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2f/fe3f187327aac18e2d54e9d2b08e15d27bf9b642d9e51c219f130fc34d1a/charset_normalizer-3.5.1-cp37-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6dac12ff6b846103483683f60c5f8fee205121adc58ffd87e90a90a3af69e99", size = 253057, upload-time = "2026-08-15T08:19:52.654Z" }, + { url = "https://files.pythonhosted.org/packages/d7/c7/9e48cee5c161fe24da823b61bf381921d77cb994a0a4de148e95018c1984/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cee5dd7c6fb5dd52a0fe2a740f9bc6e3593f5f8b1788bde49de02086f30182b2", size = 240930, upload-time = "2026-08-15T08:19:54.163Z" }, + { url = "https://files.pythonhosted.org/packages/49/e0/716601f3cc69be7b198951150c75ead1ece33c3c8036ff6ffa46029659a0/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:343fb4f2821043bd87095f7b08a1a181febc8e36ac64212143bbfd0a0e1bc235", size = 230822, upload-time = "2026-08-15T08:19:55.807Z" }, + { url = "https://files.pythonhosted.org/packages/d3/05/71bfc5caa0abcc45aea1f6a4d50ac68e59605ddc7666fe8494f4cd229665/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ae4a097991662cd4fff0ddc74e0fe7874f82e00042fa0ea00855645ed0c79598", size = 260037, upload-time = "2026-08-15T08:19:57.312Z" }, + { url = "https://files.pythonhosted.org/packages/c3/92/de7e32ed05341e7a9c4c877c318418197b7f2d66a3b68d561bf2ac57ca3e/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b599739b93b2cbeded49645ae3c8d1405c29ddfbceac1545c87a3f9580a9e96", size = 255097, upload-time = "2026-08-15T08:19:59.056Z" }, + { url = "https://files.pythonhosted.org/packages/f5/7b/ade0a122600319dfa0b1000ab0f9731c94a817904cf3c5de408c73a4ede7/charset_normalizer-3.5.1-cp37-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b39b69b347e5e47a3b5b8cfc005c68c1ba347474e3960236c4944a8ecd174962", size = 250166, upload-time = "2026-08-15T08:20:00.612Z" }, + { url = "https://files.pythonhosted.org/packages/75/9c/019fbb9f4834491a160951349b1a3714439376f66e5f7cf18b4f18f0c7aa/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a2028475ba855475b8b4d3cfeb4994269c967aea8b9892dfba907f4263a863a3", size = 241821, upload-time = "2026-08-15T08:20:02.321Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b8/11d4840bfc99330cc7fbcc2681ee5a044553a6e77655508d8f9b2bff7b34/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:36047af20e17097c3bb9476c2b7655f2f7aa51322c0ba58c07695bedf755a950", size = 232529, upload-time = "2026-08-15T08:20:04.008Z" }, + { url = "https://files.pythonhosted.org/packages/18/96/2b3a21492d9f65171ac75d872f5018260013d00bfa0ff70ec9f179148cbd/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4c4fb141a727957c93edfe5c32a26ceb6b5f6461d67146e2d39f51e16170bea8", size = 260348, upload-time = "2026-08-15T08:20:05.877Z" }, + { url = "https://files.pythonhosted.org/packages/d6/aa/a69a2028e8bd052476c245460ab19d7de595de084dd968f2d75cd50c3e25/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:2f293479cce755c75f1697e87c409b7ae4c555c7dfecb6e988ad13abba943031", size = 247234, upload-time = "2026-08-15T08:20:07.487Z" }, + { url = "https://files.pythonhosted.org/packages/35/8a/3d130aeabcaf3d2466af76b7b141c08d9e89c9016ab4b7cdd0f7dc2d1c62/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_s390x.whl", hash = "sha256:3588e376b3ea2eea84976f67273d679f229e24c66dce7b82ae45aef04ff6e072", size = 256917, upload-time = "2026-08-15T08:20:09.142Z" }, + { url = "https://files.pythonhosted.org/packages/80/c2/a7379b840292d0c1ab9fbd17d1f3967aa81794dc95bc74be8999d7fedcf7/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e199fb99720074809a7720f1c0b4d919eea8b87e88713e0f8f602f7bef543d9d", size = 254846, upload-time = "2026-08-15T08:20:10.727Z" }, + { url = "https://files.pythonhosted.org/packages/01/65/d43b714731bb2f40d4053dfa00ecfc1c5a301f8e3316c5db3a09af59fe94/charset_normalizer-3.5.1-cp37-abi3-win32.whl", hash = "sha256:dd732602a7009217f658d5863d12d79d373a4de0eebc111094bcdd3bb8e0a6cc", size = 174216, upload-time = "2026-08-15T08:20:12.334Z" }, + { url = "https://files.pythonhosted.org/packages/35/4f/b911ed898b26a09789eba9c9200c999aff6c61b4bafaf4838e56d1a1e1a3/charset_normalizer-3.5.1-cp37-abi3-win_amd64.whl", hash = "sha256:70055ff39b97c99e7ae40ea3e393fb62aa2e44dbd9b29f8d14f42fb0025c3959", size = 199764, upload-time = "2026-08-15T08:20:13.908Z" }, + { url = "https://files.pythonhosted.org/packages/f0/a7/920baf467bfd9bf689f3b318340f37aee4572a71f162bd8db51da55ba4fa/charset_normalizer-3.5.1-cp37-abi3-win_arm64.whl", hash = "sha256:87e4f41d375c0b9be2fb5251aee4b8a689169e134535aed81bf085c3b647451e", size = 287318, upload-time = "2026-08-15T08:20:15.551Z" }, + { url = "https://files.pythonhosted.org/packages/cc/61/d01fc49b8dea277640b55a9e15960dbca9fdc8c9fde18e572d39c59f4019/charset_normalizer-3.5.1-py3-none-any.whl", hash = "sha256:6df0ec430f9a831772c23ca5a224cba36517a58a84bb32c32bb59a9fa67c47f6", size = 68658, upload-time = "2026-08-15T08:20:43.306Z" }, +] + +[[package]] +name = "click" +version = "8.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/0e/7fa0ef50764b67090eca4114772a2abf8b6148198475e54c660b97caeee6/click-8.5.0.tar.gz", hash = "sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34", size = 382235, upload-time = "2026-08-26T13:33:14.56Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/50/6c0d534c5f134586a8e1ba4e330569e32f057e33372ae556463212fb4cd3/click-8.5.0-py3-none-any.whl", hash = "sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360", size = 125251, upload-time = "2026-08-26T13:33:12.928Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "ghp-import" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943, upload-time = "2022-05-02T15:47:16.11Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" }, +] + +[[package]] +name = "git-ftp-docs" +version = "0" +source = { virtual = "." } +dependencies = [ + { name = "mkdocs-material" }, +] + +[package.metadata] +requires-dist = [{ name = "mkdocs-material", specifier = ">=9.5,<10" }] + +[[package]] +name = "idna" +version = "3.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/08/8eea9d4b8302028f3abb2c0813953f7aec26d33b7a8960ed760e65ff29fa/idna-3.20.tar.gz", hash = "sha256:a7db850025b95ded1eae8a46181a1a6c56c92c96f0e2b005d9ff8dc0210cab44", size = 216463, upload-time = "2026-09-17T14:11:04.752Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/a2/bb081bab032533a855d44de1d56f8e8426114ff1ba5d1f07a438a0a654f8/idna-3.20-py3-none-any.whl", hash = "sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c", size = 69583, upload-time = "2026-09-17T14:11:03.168Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "markdown" +version = "3.10.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/29/6f/da4c6aea59b3001f2e8c0ec7497475aadaf3b021c10cab5b2858f0f32b26/markdown-3.10.3.tar.gz", hash = "sha256:3589362618f743188b4d955b874402bc814f4f83f544dc207719f4baa7d9c45f", size = 372596, upload-time = "2026-07-30T19:05:29.005Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/69/4a5af2bc115a9a33fefe51709749de8262be3f9ba063d1753a837cdbc49c/markdown-3.10.3-py3-none-any.whl", hash = "sha256:fa6c92a00a4a3c98b22728c64a935ae1928250ae65058a6ded814d2cc29a4cea", size = 110757, upload-time = "2026-07-30T19:05:27.883Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" }, + { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" }, + { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" }, + { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" }, + { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" }, + { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" }, + { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" }, + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "mergedeep" +version = "1.3.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661, upload-time = "2021-02-05T18:55:30.623Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354, upload-time = "2021-02-05T18:55:29.583Z" }, +] + +[[package]] +name = "mkdocs" +version = "1.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "ghp-import" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mergedeep" }, + { name = "mkdocs-get-deps" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "pyyaml" }, + { name = "pyyaml-env-tag" }, + { name = "watchdog" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159, upload-time = "2024-08-30T12:24:06.899Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451, upload-time = "2024-08-30T12:24:05.054Z" }, +] + +[[package]] +name = "mkdocs-get-deps" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mergedeep" }, + { name = "platformdirs" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/25/b3cccb187655b9393572bde9b09261d267c3bf2f2cdabe347673be5976a6/mkdocs_get_deps-0.2.2.tar.gz", hash = "sha256:8ee8d5f316cdbbb2834bc1df6e69c08fe769a83e040060de26d3c19fad3599a1", size = 11047, upload-time = "2026-03-10T02:46:33.632Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl", hash = "sha256:e7878cbeac04860b8b5e0ca31d3abad3df9411a75a32cde82f8e44b6c16ff650", size = 9555, upload-time = "2026-03-10T02:46:32.256Z" }, +] + +[[package]] +name = "mkdocs-material" +version = "9.7.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "babel" }, + { name = "backrefs" }, + { name = "colorama" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "mkdocs" }, + { name = "mkdocs-material-extensions" }, + { name = "paginate" }, + { name = "pygments" }, + { name = "pymdown-extensions" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/cd/c05d3a530ba7934f144fb45f7203cd236adc25c7bdcc34673d202f4b0278/mkdocs_material-9.7.7.tar.gz", hash = "sha256:c0649c065b1b0512d60aad8c10f947f8e455284475239b364b610f2deb4d0855", size = 4097923, upload-time = "2026-07-17T16:21:33.156Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/21/17c1bc9e6f47c972ad66fb2ac2568f99f90f1207eeb6fc3b34d094dba7b5/mkdocs_material-9.7.7-py3-none-any.whl", hash = "sha256:8ea9bb1737a5b524a5f9dcf2e1b4ebda8274ae3008aa7845720a97083bef708f", size = 9305438, upload-time = "2026-07-17T16:21:30.017Z" }, +] + +[[package]] +name = "mkdocs-material-extensions" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847, upload-time = "2023-11-22T19:09:45.208Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728, upload-time = "2023-11-22T19:09:43.465Z" }, +] + +[[package]] +name = "packaging" +version = "26.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, +] + +[[package]] +name = "paginate" +version = "0.5.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252, upload-time = "2024-08-25T14:17:24.139Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746, upload-time = "2024-08-25T14:17:22.55Z" }, +] + +[[package]] +name = "pathspec" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +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/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 = "platformdirs" +version = "4.11.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/13/f870dd0b42690138e4e37a76b5138e5690ed4365a77071bb092d59037da0/platformdirs-4.11.11.tar.gz", hash = "sha256:b0befe8a90759e4a9a8b9820d434ae226a6549063210b596da0038a7a05aede4", size = 39949, upload-time = "2026-09-19T01:18:47.259Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/be/3ae887e84c38c4dd1c549cd5825adc93f48069a94fe1cb8ac52a9da16f15/platformdirs-4.11.11-py3-none-any.whl", hash = "sha256:972ea6b2b387155a536226a0750e46923d731d459a387c4a255c583ed2f64547", size = 24992, upload-time = "2026-09-19T01:18:45.825Z" }, +] + +[[package]] +name = "pygments" +version = "2.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" }, +] + +[[package]] +name = "pymdown-extensions" +version = "12.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/7e/53144b512818c75d459523e3a91d9bf7b65cd6a1dfb964fe690adc8b3670/pymdown_extensions-12.0.1.tar.gz", hash = "sha256:88fe2c97fb9153b9f2cc1da59f1d7c7cb74971110daf33cccb2a5514e0f7e970", size = 866904, upload-time = "2026-09-15T23:20:33.187Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/30/c866b3ca8ad5cca79a3ee1a55c12d61d3e72381e5eadbfdaceb6006ebc84/pymdown_extensions-12.0.1-py3-none-any.whl", hash = "sha256:15d629e8035892b93d788a8424ccea754d4aa3cec247a64cbfe7b15089c9ad7f", size = 276957, upload-time = "2026-09-15T23:20:31.551Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "pyyaml-env-tag" +version = "1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737, upload-time = "2025-05-13T15:24:01.64Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" }, +] + +[[package]] +name = "requests" +version = "2.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "urllib3" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/05/b17359e1cefb4f909b5e40b1b90a496d987258916dbbf88e842c729f510e/urllib3-2.8.0.tar.gz", hash = "sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63", size = 458972, upload-time = "2026-09-15T19:29:36.253Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/9d/c4e665119135114480843e7ab388fa94d8480650450e6f8e26b70d323a4c/urllib3-2.8.0-py3-none-any.whl", hash = "sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3", size = 135717, upload-time = "2026-09-15T19:29:34.577Z" }, +] + +[[package]] +name = "watchdog" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/56/90994d789c61df619bfc5ce2ecdabd5eeff564e1eb47512bd01b5e019569/watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26", size = 96390, upload-time = "2024-11-01T14:06:24.793Z" }, + { url = "https://files.pythonhosted.org/packages/55/46/9a67ee697342ddf3c6daa97e3a587a56d6c4052f881ed926a849fcf7371c/watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112", size = 88389, upload-time = "2024-11-01T14:06:27.112Z" }, + { url = "https://files.pythonhosted.org/packages/44/65/91b0985747c52064d8701e1075eb96f8c40a79df889e59a399453adfb882/watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3", size = 89020, upload-time = "2024-11-01T14:06:29.876Z" }, + { url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393, upload-time = "2024-11-01T14:06:31.756Z" }, + { url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392, upload-time = "2024-11-01T14:06:32.99Z" }, + { url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019, upload-time = "2024-11-01T14:06:34.963Z" }, + { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471, upload-time = "2024-11-01T14:06:37.745Z" }, + { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449, upload-time = "2024-11-01T14:06:39.748Z" }, + { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054, upload-time = "2024-11-01T14:06:41.009Z" }, + { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" }, + { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" }, + { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" }, + { url = "https://files.pythonhosted.org/packages/30/ad/d17b5d42e28a8b91f8ed01cb949da092827afb9995d4559fd448d0472763/watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881", size = 87902, upload-time = "2024-11-01T14:06:53.119Z" }, + { url = "https://files.pythonhosted.org/packages/5c/ca/c3649991d140ff6ab67bfc85ab42b165ead119c9e12211e08089d763ece5/watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11", size = 88380, upload-time = "2024-11-01T14:06:55.19Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" }, + { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" }, + { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" }, + { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, +]