pax_global_header00006660000000000000000000000064147165016110014514gustar00rootroot0000000000000052 comment=d7706502440e0b8899482444e0ca4eca2ed46ab5 dunamai-1.23.0/000077500000000000000000000000001471650161100132155ustar00rootroot00000000000000dunamai-1.23.0/.coveragerc000066400000000000000000000000331471650161100153320ustar00rootroot00000000000000[run] source = dunamai dunamai-1.23.0/.editorconfig000066400000000000000000000002421471650161100156700ustar00rootroot00000000000000root = true [*] indent_style = space indent_size = 4 insert_final_newline = true trim_trailing_whitespace = true [*.{feature,json,md,yaml,yml}] indent_size = 2 dunamai-1.23.0/.flake8000066400000000000000000000000571471650161100143720ustar00rootroot00000000000000[flake8] exclude = .venv max-line-length = 100 dunamai-1.23.0/.gitattributes000066400000000000000000000000121471650161100161010ustar00rootroot00000000000000text=auto dunamai-1.23.0/.github/000077500000000000000000000000001471650161100145555ustar00rootroot00000000000000dunamai-1.23.0/.github/workflows/000077500000000000000000000000001471650161100166125ustar00rootroot00000000000000dunamai-1.23.0/.github/workflows/main.yaml000066400000000000000000000072561471650161100204340ustar00rootroot00000000000000on: - push - pull_request name: Main jobs: pre-commit: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: '3.7' - run: | pip install poetry poetry install poetry run pre-commit run --all-files --show-diff-on-failure test: runs-on: ubuntu-20.04 strategy: fail-fast: false matrix: python-version: - '3.5' - '3.6' - '3.7' - '3.8' - '3.9' - '3.10' - '3.11' - '3.12' - '3.13' git-version: - default # 2.28.0 or newer include: - python-version: '3.7' git-version: '2.21.0' # https://lore.kernel.org/git/CAKqNo6RJqp94uLMf8Biuo=ZvMZB9Mq6RRMrUgsLW4u1ks+mnOA@mail.gmail.com/T/#u - python-version: '3.7' git-version: '2.7.0' - python-version: '3.7' git-version: '2.2.0' - python-version: '3.7' git-version: '1.8.2.3' name: test (python = ${{ matrix.python-version }}, git = ${{ matrix.git-version }}) env: DARCS_EMAIL: foo steps: - if: ${{ matrix.python-version == '3.5' }} run: | echo "PIP_TRUSTED_HOST=pypi.python.org pypi.org files.pythonhosted.org" >> $GITHUB_ENV - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} # - uses: dtolnay/rust-toolchain@1.65.0 # - uses: Swatinem/rust-cache@v2 - if: ${{ matrix.git-version != 'default' }} env: NO_OPENSSL: 'yes' run: | sudo apt-get update sudo apt-get install dh-autoreconf libcurl4-gnutls-dev libexpat1-dev gettext libz-dev libssl-dev curl -o git.tar.gz https://mirrors.edge.kernel.org/pub/software/scm/git/git-${{ matrix.git-version }}.tar.gz tar -zxf git.tar.gz cd git-${{ matrix.git-version }} make configure ./configure --prefix=/usr sudo make install which git git --version - run: | export PATH="$PATH:/opt" sudo apt-get update sudo apt-get install -y darcs bzr # For Pijul: # sudo apt-get install -y libxxhash-dev libzstd-dev expect # rehosted because the official site deletes older artifacts: curl -L -o ~/fossil.tgz https://github.com/mtkennerly/storage/raw/06e29a4005b24a65bc7d639c0aa1fc152a85d0b7/software/fossil-linux-x64-2.13.tar.gz tar -xvf ~/fossil.tgz -C /opt # TODO: Re-enable Pijul tests once this is fixed: https://nest.pijul.com/pijul/pijul/discussions/777 # - name: Prepare Pijul # run: | # # Same as default features, but without openssl: # cargo install pijul --version 1.0.0-beta.2 --no-default-features --features keep-changes # expect -c 'spawn pijul key generate test ; expect "Password for the new key (press enter to leave it unencrypted):" ; send -- "\r" ; expect eof' - run: | export PATH="$PATH:/opt" git config --global user.name "foo" git config --global user.email "foo@example.com" bzr whoami 'foo ' - if: ${{ matrix.python-version == '3.5' || matrix.python-version == '3.6' }} run: | cp poetry.legacy.lock poetry.lock - if: ${{ matrix.python-version == '3.12' || matrix.python-version == '3.13' }} run: | rm poetry.lock - run: | pip install poetry poetry install poetry run pytest --verbose --cov --cov-report term-missing dunamai-1.23.0/.gitignore000066400000000000000000000003111471650161100152000ustar00rootroot00000000000000__pycache__/ .cache/ .coverage .coverage.* .env/ .idea/ .mypy_cache/ .pytest_cache/ .tox/ .venv/ *.egg-info/ /setup.py build/ dist/ htmlcov/ pip-wheel-metadata/ # Used by github codespaces pythonenv*/ dunamai-1.23.0/.pre-commit-config.yaml000066400000000000000000000006401471650161100174760ustar00rootroot00000000000000repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.2.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - repo: https://github.com/Lucas-C/pre-commit-hooks rev: v1.1.13 hooks: - id: forbid-tabs - repo: https://github.com/mtkennerly/pre-commit-hooks rev: v0.3.0 hooks: - id: poetry-black - id: poetry-mypy - id: poetry-ruff dunamai-1.23.0/.readthedocs.yaml000066400000000000000000000002461471650161100164460ustar00rootroot00000000000000version: 2 build: os: ubuntu-22.04 tools: python: "3.11" mkdocs: configuration: mkdocs.yaml python: install: - requirements: docs/requirements.txt dunamai-1.23.0/.vscode/000077500000000000000000000000001471650161100145565ustar00rootroot00000000000000dunamai-1.23.0/.vscode/extensions.json000066400000000000000000000004321471650161100176470ustar00rootroot00000000000000{ "recommendations": [ "codezombiech.gitignore", "EditorConfig.EditorConfig", "ms-python.mypy-type-checker", "ms-python.python", "redhat.vscode-yaml", "sidneys1.gitconfig", "streetsidesoftware.code-spell-checker", "tamasfe.even-better-toml", ] } dunamai-1.23.0/.vscode/settings.json000066400000000000000000000003421471650161100173100ustar00rootroot00000000000000{ "python.defaultInterpreterPath": "${workspaceFolder}/.venv", "yaml.format.enable": true, "python.testing.pytestArgs": [ "." ], "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true, } dunamai-1.23.0/CHANGELOG.md000066400000000000000000000401461471650161100150330ustar00rootroot00000000000000## v1.23.0 (2024-11-17) * Added: `{major}`, `{minor}`, and `{patch}` format placeholders. ## v1.22.0 (2024-08-07) * Fixed: The `--ignore-untracked` CLI flag was ignored. * Added: `--commit-length` option. ## v1.21.2 (2024-06-26) * Fixed: Some timestamps could fail to parse on Python 3.5 and 3.6. ## v1.21.1 (2024-05-03) * Fixed: Distance was calculated inconsistently for Git when there were some tags and none matched the version pattern. ## v1.21.0 (2024-04-29) * Generally, when Dunamai can detect the VCS in use, but there's no version set yet, then Dunamai uses 0.0.0 as a fallback, unless strict mode is enabled. This is useful for new projects that do not yet have a release. However, if there were some tags and none matched the version pattern, then Dunamai would yield an error. That wouldn't be helpful for a new project with some non-version tag, and it could be incorrect for a monorepo with different tags for different packages. Now, Dunamai will use 0.0.0 in this case as well, unless strict mode is enabled. * You can now specify a pattern prefix. For example, `--pattern default --pattern-prefix some-package-` would match tags like `some-package-v1.2.3`. This is useful if you just want a custom prefix without writing a whole pattern. * Added `--ignore-untracked` option to control checking whether the repository is dirty. ## v1.20.0 (2024-04-12) * Updated `Version.bump()` to add a `smart` argument, which only bumps when `distance != 0`. This will also make `Version.serialize()` use pre-release formatting automatically, like calling `Version.serialize(bump=True)`. ## v1.19.2 (2024-02-16) * Fixed an exception when a Git repository had a broken ref. Git would print a warning that Dunamai failed to parse. ## v1.19.1 (2024-02-07) * Relaxed Python bounds from `^3.5` to `>=3.5` since Python does not follow Semantic Versioning. * Fixed some `git log` commands that did not include `-c log.showsignature=false`. ([Contributed by pdecat](https://github.com/mtkennerly/dunamai/pull/75)) ## v1.19.0 (2023-10-04) * Added a `--path` option to inspect a directory other than the current one. The `Version.from_*` methods now also take a `path` argument. ## v1.18.1 (2023-09-22) * For Git 2.16+, `--decorate-refs=refs/tags/` is now specified for `git log` in case you've configured `log.excludeDecoration=refs/tags/`. ## v1.18.0 (2023-07-10) * Added a `vcs` attribute to `Version` to indicate which VCS was detected. ## v1.17.0 (2023-05-19) * The `from` command will print a warning for shallow Git repositories. This becomes an error with `--strict`. * The `Version` class has a new `concerns` field to indicate warnings with the version. Right now, the only possibility is `Concern.ShallowRepository`. ## v1.16.1 (2023-05-13) * Fixed outdated reference to `pkg_resources` in the docstring for `get_version`. * `CHANGELOG.md` and `tests` are now included in sdists. ## v1.16.0 (2023-02-21) * Updated `Version.parse` to better handle PEP 440 versions produced by Dunamai itself. Specifically, in `1.2.3.post4.dev5`, the post number becomes the distance and the dev number is ignored. In `1.2.3.dev5`, the dev number becomes the distance. * Added `increment` argument to `bump_version` and `Version.bump`. ([Contributed by legendof-selda](https://github.com/mtkennerly/dunamai/pull/54)) * Fixed Git detection when there is a "dubious ownership" error. Previously, `from git` would report that it was not a Git project, and `from any` would report that it could not detect a VCS. Now, both commands report that there is dubious ownership. * Improved error reporting for `from any` VCS detection. The error now specifies which VCSes were checked and which were not found to be installed. ## v1.15.0 (2022-12-02) * Added compatibility with Git versions as old as 1.8.2.3. ## v1.14.1 (2022-11-15) * Fixed Git 2.7.0 compatibility by changing `git log --no-show-signature` to `git -c log.showsignature=false log`. ## v1.14.0 (2022-11-07) * Added a `strict` option to prevent falling back to `0.0.0` when there are no tags. * Added support for `.git_archival.json` files created by `git archive`. * Added support for `.hg_archival.txt` files created by `hg archive`. ## v1.13.2 (2022-10-14) * Fixed an error when parsing Git output with `showSignature = true` configured. ([Contributed by riton](https://github.com/mtkennerly/dunamai/pull/51)) ## v1.13.1 (2022-09-25) * Made pattern-related error messages more readable by moving the pattern after the primary message instead of mixing them. ## v1.13.0 (2022-08-21) * Added support for [Pijul](https://pijul.org). ## v1.12.0 (2022-05-07) * Added `Pattern` type for named pattern presets. Currently, this includes: * `Pattern.Default` (CLI: `--pattern default`) for the existing default. * `Pattern.DefaultUnprefixed` (CLI: `--pattern default-unprefixed`) for the existing default, but without requiring the `v` prefix. * Added `tag_branch` option (CLI: `--tag-branch`) for Git repositories. This is particularly useful for Gitflow without fast forward, where `develop` does not contain the tag history, so you can specify `--tag-branch master`. * Added `full_commit` option (CLI: `--full-commit`) for Git and Mercurial repositories to obtain the full commit hash instead of the short form. * Fixed `Version.parse` so that it better handles versions without the `v` prefix when the pattern does not (or may not) require it. * Fixed error reporting when a custom pattern is an invalid regular expression, as well as when a custom format is malformed. It was fine when Dunamai was used as a library, but the error message lacked context on the CLI. * Fixed `from any` not passing the `--tag-dir` option along for Subversion repositories. ## v1.11.1 (2022-04-05) * Fixed the `--bump` CLI option and the `bump` argument of `Version.serialize` bumping even on a commit with a version tag. Now, no bumping occurs on such a commit. ## v1.11.0 (2022-03-15) * Explicitly specified `Optional[...]` typing on arguments with a default of `None`. ([Contributed by jonathangreen](https://github.com/mtkennerly/dunamai/pull/44)) * Made `VERSION_SOURCE_PATTERN` public for consumption by other tools. ## v1.10.0 (2022-03-08) * Added `branch` and `timestamp` to the `Version` class, along with associated format placeholders (`branch`, `branch_escaped`, `timestamp`). Branch info is not populated for Darcs and Subversion repositories. * Fixed validation for PEP 440, where the local segment was allowed to contain any characters. * Fixed validation for Semantic Versioning, where some segments were allowed to contain these additional characters: ``` [ \ ] ^ _ ` ``` ## v1.9.0 (2022-02-20) * Changed `Version.serialize`'s `format` argument to support passing a callback. ([Contributed by marnikow](https://github.com/mtkennerly/dunamai/pull/40)) * Added `ignore` option to `get_version()`. ([Contributed by marnikow](https://github.com/mtkennerly/dunamai/pull/39)) * Added `parser` option to `get_version()`. * Added `Version.parse()`. ([Contributed by marnikow](https://github.com/mtkennerly/dunamai/pull/41)) * Added `Version.bump()`. ([Contributed by marnikow](https://github.com/mtkennerly/dunamai/pull/38)) ## v1.8.0 (2022-01-27) * Changed the build backend to poetry-core. ([Contributed by fabaff](https://github.com/mtkennerly/dunamai/pull/35)) * Clarified serialization options that are ignored when using a custom format. * Relaxed dependency range of `importlib-metadata` for compatibility with Poetry. * Added `epoch` to `Version` class, default tag pattern, and format placeholders. * Fixed PEP 440 validation to allow multiple digits in the epoch. * Improved parsing of optional pattern groups so that we don't stop checking at the first one that's omitted. * Fixed handling of tags with `post`/`dev` stages so that they are serialized and bumped correctly when using PEP 440. ## v1.7.0 (2021-10-31) * Broadened the default version tag pattern to allow more separator styles recognized in PEP 440 pre-normalized forms (`-`, `.`, and `_`). * Enhanced `serialize_pep440()` to normalize the alternative prerelease names (`alpha` -> `a`, `beta` -> `b`, `c`/`pre`/`preview` -> `rc`) and capitalizations (`RC` -> `rc`, etc). * Added a `py.typed` file for PEP-561. ([Contributed by wwuck](https://github.com/mtkennerly/dunamai/pull/25)) * Replaced `pkg_resources` dependency with `packaging` and `importlib_metadata`. ([Contributed by flying-sheep](https://github.com/mtkennerly/dunamai/pull/29)) * Added some missing public items to `__all__`. ## v1.6.0 (2021-08-09) * Fixed an oversight where the default version tag pattern would only find tags with exactly three parts in the base (e.g., `v1.0.0` and `v1.2.3`). This is now relaxed so that `v1`, `v1.2.3.4`, and so on are also recognized. * Added support for execution via `python -m dunamai`. ([Contributed by jstriebel](https://github.com/mtkennerly/dunamai/pull/19)) ## v1.5.5 (2021-04-26) * Fixed handling of Git tags that contain slashes. ([Contributed by ioben](https://github.com/mtkennerly/dunamai/pull/17)) ## v1.5.4 (2021-01-20) * Fixed handling of Git tags that contain commas. ## v1.5.3 (2021-01-13) * Fixed Semantic Versioning enforcement to allow metadata segments with more than two dot-separated identifiers. ## v1.5.2 (2020-12-17) * For Git, avoided use of `--decorate-refs` to maintain compatibility with older Git versions. ## v1.5.1 (2020-12-16) * Improved ordering of Git tags, particularly when commit dates were not chronological. ([Contributed by mariusvniekerk](https://github.com/mtkennerly/dunamai/pull/9)) * Improved Subversion handling when in a subdirectory of the repository. ([Contributed by Spirotot](https://github.com/mtkennerly/dunamai/pull/10)) ## v1.5.0 (2020-12-02) * Added the `--tagged-metadata` option and corresponding attribute on the `Version` class. ([Contributed by mariusvniekerk](https://github.com/mtkennerly/dunamai/pull/8)) * Added explicit dependency on setuptools (because of using `pkg_resources`) for environments where it is not installed by default. ## v1.4.1 (2020-11-17) * For Git, replaced `--porcelain=v1` with `--porcelain` to maintain compatibility with older Git versions. ## v1.4.0 (2020-11-17) * Added the `--bump` command line option and the `bump` argument to `Version.serialize()`. * Fixed an issue with Git annotated tag sorting. When there was a newer annotated tag A on an older commit and an older annotated tag B on a newer commit, Dunamai would choose tag A, but will now correctly choose tag B because the commit is newer. * With Git, trigger the dirty flag when there are untracked files. ([Contributed by jpc4242](https://github.com/mtkennerly/dunamai/pull/6)) ## v1.3.1 (2020-09-27) * Fixed ambiguous reference error when using Git if a tag and branch name were identical. ## v1.3.0 (2020-07-04) * Previously, when there were not yet any version-like tags, the distance would be set to 0, so the only differentiator was the commit ID. Now, the distance will be set to the number of commits so far. For example: * No commits: base = 0.0.0, distance = 0 * 1 commit, no tags: base = 0.0.0, distance = 1 * 10 commits, no tags: base = 0.0.0, distance = 10 ## v1.2.0 (2020-06-12) * Added `--debug` flag. ## v1.1.0 (2020-03-22) * Added these functions to the public API: * `serialize_pep440` * `serialize_semver` * `serialize_pvp` * `bump_version` ## v1.0.0 (2019-10-26) * Changed the `Version` class to align with Dunamai's own semantics instead of PEP 440's semantics. Previously, `Version` implemented all of PEP 440's features, like epochs and dev releases, even though Dunamai itself did not use epochs (unless you created your own `Version` instance with one and serialized it) and always set dev to 0 in the `from_git`/etc methods. The `serialize` method then tried to generalize those PEP 440 concepts to other versioning schemes, as in `0.1.0-epoch.1` for Semantic Versioning, even though that doesn't have an equivalent meaning in that scheme. Now, the `Version` class implements the semantics used by Dunamai, giving it more power in the serialization to map those concepts in an appropriate way for each scheme. For example, `dev0` is now only added for PEP 440 (in order to be compatible with Pip's `--pre` flag), but `dev.0` is no longer added for Semantic Versioning because it served no purpose there. API changes: * `post` has been renamed to `distance`, and its type is simply `int` rather than `Optional[int]` * `epoch` and `dev` have been removed * `pre_type` has been renamed to `stage` * `pre_number` has been renamed to `revision`, and it is no longer required when specifying a stage * Improved error reporting when the version control system cannot be detected and when a specified VCS is unavailable. * Improved the default regular expression for tags: * It now requires a full match of the tag. * It now recognizes when the `base` and `stage` are separated by a hyphen. * It now recognizes when the `stage` and `revision` are separated by a dot. * It now allows a `stage` without a `revision`. ## v0.9.0 (2019-10-22) * Added Fossil support. * Fixed case with Git/Mercurial/Subversion/Bazaar where, if you checked out an older commit, then Dunamai would consider tags for commits both before and after the commit that was checked out. It now only considers tags for the checked out commit or one of its ancestors, making the results more deterministic. * Changed VCS detection to be based on the result of VCS commands rather than looking for VCS-specific directories/files. This avoids the risk of false positives and simplifies cases with inconsistent VCS files (e.g., Fossil uses `.fslckout` on Linux and `_FOSSIL_` on Windows) ## v0.8.1 (2019-08-30) * Fixed handling of annotated Git tags, which were previously ignored. ## v0.8.0 (2019-06-05) * Changed `Version.from_any_vcs` to accept the `tag_dir` argument, which will only be used if Subversion is the detected VCS. Likewise, `dunamai from any` now accepts `--tag-dir`. * Added `Version.from_vcs` to make it easier for other tools to map from a user's VCS configuration to the appropriate function. ## v0.7.1 (2019-05-16) * Fixed issue on Linux where shell commands were not interpreted correctly. ## v0.7.0 (2019-04-16) * Added Bazaar support. * Added the `dunamai check` command and the corresponding `check_version` function. * Added the option to check just the latest tag or to keep checking tags until a match is found. The default behavior is now to keep checking. * Added enforcement of Semantic Versioning rule against numeric segments with a leading zero. * Renamed the `with_metadata` and `with_dirty` arguments of `Version.serialize` to `metadata` and `dirty` respectively. * Fixed the equality and ordering of `Version` to consider all attributes. `dirty` and `commit` were ignored previously if neither `post` nor `dev` were set, and `dirty=None` and `dirty=False` were not distinguished. ## v0.6.0 (2019-04-14) * Added Subversion support. * Added support for the PVP style. * Changed the type of the `style` argument in `Version.serialize` from `str` to `Style`. ## v0.5.0 (2019-03-31) * Added built-in Semantic Versioning output style in addition to PEP 440. * Added style validation for custom output formats. * Added Darcs support. ## v0.4.0 (2019-03-29) * Added support for custom serialization formats. ## v0.3.0 (2019-03-29) * Added Mercurial support. * Added a CLI. * Renamed `Version.from_git_describe` to `Version.from_git`. * Changed behavior of `Version.serialize` argument `with_metadata` so that, by default, metadata is excluded when post and dev are not set. * Added `with_dirty` argument to `Version.serialize` and removed `flag_dirty` argument from `Version.from_git`. The information should always be collected, and it is up to the serialization step to decide what to do with it. * Added `Version.from_any_vcs`. * Removed `source` attribute of `Version` since some VCSes may require multiple commands in conjunction and therefore not have a single source string. ## v0.2.0 (2019-03-26) * Fixed a wrong Git command being used. * Made metadata serialization opt-in. ## v0.1.0 (2019-03-26) * Initial release. dunamai-1.23.0/CONTRIBUTING.md000066400000000000000000000020341471650161100154450ustar00rootroot00000000000000## Development This project is managed using [Poetry](https://poetry.eustace.io). Development requires Python 3.7+. * If you want to take advantage of the default VSCode integration, then first configure Poetry to make its virtual environment in the repository: ``` poetry config virtualenvs.in-project true ``` * After cloning the repository, activate the tooling: ``` poetry install poetry run pre-commit install ``` * Run unit tests: ``` poetry run pytest --cov ``` * Render documentation: ``` pipx install mkdocs pipx runpip mkdocs install -r docs/requirements.txt mkdocs serve ``` ## VCS setup Some of the VCS tools tested require a minimum configuration to work. Here is an example of how to configure them: * Git: ``` git config --global user.name "foo" git config --global user.email "foo@example.com" ``` * Darcs: * Set the `DARCS_EMAIL` environment variable (e.g., `foo `). * Bazaar: ``` bzr whoami 'foo ' ``` * Pijul: ``` pijul key generate test ``` dunamai-1.23.0/LICENSE000066400000000000000000000021011471650161100142140ustar00rootroot00000000000000MIT License Copyright (c) 2019 Matthew T. Kennerly (mtkennerly) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. dunamai-1.23.0/README.md000066400000000000000000000222451471650161100145010ustar00rootroot00000000000000 # Dunamai Dunamai is a Python 3.5+ library and command line tool for producing dynamic, standards-compliant version strings, derived from tags in your version control system. This facilitates uniquely identifying nightly or per-commit builds in continuous integration and releasing new versions of your software simply by creating a tag. Dunamai is also available as a [GitHub Action](https://github.com/marketplace/actions/run-dunamai). ## Features * Version control system support: * [Git](https://git-scm.com) (2.7.0+ is recommended, but versions as old as 1.8.2.3 will work with some reduced functionality) * [Mercurial](https://www.mercurial-scm.org) * [Darcs](http://darcs.net) * [Subversion](https://subversion.apache.org) * [Bazaar](https://bazaar.canonical.com/en) * [Fossil](https://www.fossil-scm.org/home/doc/trunk/www/index.wiki) * [Pijul](https://pijul.org) * Version styles: * [PEP 440](https://www.python.org/dev/peps/pep-0440) * [Semantic Versioning](https://semver.org) * [Haskell Package Versioning Policy](https://pvp.haskell.org) * Custom output formats * Can be used for projects written in any programming language. For Python, this means you do not need a setup.py. ## Usage ### Installation ``` pip install dunamai ``` ### CLI ```console # Suppose you are on commit g29045e8, 7 commits after the v0.2.0 tag. # Auto-detect the version control system and generate a version: $ dunamai from any 0.2.0.post7.dev0+g29045e8 # Or use an explicit VCS and style: $ dunamai from git --no-metadata --style semver 0.2.0-post.7 # Custom formats: $ dunamai from any --format "v{base}+{distance}.{commit}" v0.2.0+7.g29045e8 # If you'd prefer to frame the version in terms of progress toward the next # release rather than distance from the latest one, you can bump it: $ dunamai from any --bump 0.2.1.dev7+g29045e8 # Validation of custom formats: $ dunamai from any --format "v{base}" --style pep440 Version 'v0.2.0' does not conform to the PEP 440 style # Validate your own freeform versions: $ dunamai check 0.01.0 --style semver Version '0.01.0' does not conform to the Semantic Versioning style # More info: $ dunamai --help $ dunamai from --help $ dunamai from git --help ``` ### Library ```python from dunamai import Version, Style # Let's say you're on commit g644252b, which is tagged as v0.1.0. version = Version.from_git() assert version.serialize() == "0.1.0" # Let's say there was a v0.1.0rc5 tag 44 commits ago # and you have some uncommitted changes. version = Version.from_any_vcs() assert version.serialize() == "0.1.0rc5.post44.dev0+g644252b" assert version.serialize(metadata=False) == "0.1.0rc5.post44.dev0" assert version.serialize(dirty=True) == "0.1.0rc5.post44.dev0+g644252b.dirty" assert version.serialize(style=Style.SemVer) == "0.1.0-rc.5.post.44+g644252b" ``` The `serialize()` method gives you an opinionated, PEP 440-compliant default that ensures that versions for untagged commits are compatible with Pip's `--pre` flag. The individual parts of the version are also available for you to use and inspect as you please: ```python assert version.base == "0.1.0" assert version.stage == "rc" assert version.revision == 5 assert version.distance == 44 assert version.commit == "g644252b" assert version.dirty is True # Available if the latest tag includes metadata, like v0.1.0+linux: assert version.tagged_metadata == "linux" ``` ### Tips By default, the "v" prefix on the tag is required, unless you specify a custom tag pattern. You can either write a regular expression: * Console: ```console $ dunamai from any --pattern "(?P\d+\.\d+\.\d+)" ``` * Python: ```python from dunamai import Version version = Version.from_any_vcs(pattern=r"(?P\d+\.\d+\.\d+)") ``` ...or use a named preset: * Console: ```console $ dunamai from any --pattern default-unprefixed ``` * Python: ```python from dunamai import Version, Pattern version = Version.from_any_vcs(pattern=Pattern.DefaultUnprefixed) ``` You can also keep the default pattern and just specify a prefix. For example, this would match tags like `some-package-v1.2.3`: * Console: ```console $ dunamai from any --pattern-prefix some-package- ``` * Python: ```python from dunamai import Version version = Version.from_any_vcs(pattern_prefix="some-package-") ``` ### VCS archives Sometimes, you may only have access to an archive of a repository (e.g., a zip file) without the full history. Dunamai can still detect a version in some of these cases: * For Git, you can configure `git archive` to produce a file with some metadata for Dunamai. Add a `.git_archival.json` file to the root of your repository with this content: ``` { "hash-full": "$Format:%H$", "hash-short": "$Format:%h$", "timestamp": "$Format:%cI$", "refs": "$Format:%D$", "describe": "$Format:%(describe:tags=true,match=v[0-9]*)$" } ``` Add this line to your `.gitattributes` file. If you don't already have this file, add it to the root of your repository: ``` .git_archival.json export-subst ``` * For Mercurial, Dunamai will detect and use an `.hg_archival.txt` file created by `hg archive`. It will also recognize `.hgtags` if present. ### Custom formats Here are the available substitutions for custom formats. If you have a tag like `v9!0.1.2-beta.3+other`, then: * `{base}` = `0.1.2` * `{stage}` = `beta` * `{revision}` = `3` * `{distance}` is the number of commits since the last * `{commit}` is the commit hash (defaults to short form, unless you use `--full-commit`) * `{dirty}` expands to either "dirty" or "clean" if you have uncommitted modified files * `{tagged_metadata}` = `other` * `{epoch}` = `9` * `{branch}` = `feature/foo` * `{branch_escaped}` = `featurefoo` * `{timestamp}` is in the format `YYYYmmddHHMMSS` as UTC * `{major}` = `0` * `{minor}` = `1` * `{patch}` = `2` If you specify a substitution, its value will always be included in the output. For conditional formatting, you can do something like this (Bash): ```bash distance=$(dunamai from any --format "{distance}") if [ "$distance" = "0" ]; then dunamai from any --format "v{base}" else dunamai from any --format "v{base}+{distance}.{dirty}" fi ``` ## Comparison to Versioneer [Versioneer](https://github.com/warner/python-versioneer) is another great library for dynamic versions, but there are some design decisions that prompted the creation of Dunamai as an alternative: * Versioneer requires a setup.py file to exist, or else `versioneer install` will fail, rendering it incompatible with non-setuptools-based projects such as those using Poetry or Flit. Dunamai can be used regardless of the project's build system. * Versioneer has a CLI that generates Python code which needs to be committed into your repository, whereas Dunamai is just a normal importable library with an optional CLI to help statically include your version string. * Versioneer produces the version as an opaque string, whereas Dunamai provides a Version class with discrete parts that can then be inspected and serialized separately. * Versioneer provides customizability through a config file, whereas Dunamai aims to offer customizability through its library API and CLI for both scripting support and use in other libraries. ## Integration * Setting a `__version__` statically: ```console $ echo "__version__ = '$(dunamai from any)'" > your_library/_version.py ``` ```python # your_library/__init__.py from your_library._version import __version__ ``` Or dynamically (but Dunamai becomes a runtime dependency): ```python # your_library/__init__.py import dunamai as _dunamai __version__ = _dunamai.get_version("your-library", third_choice=_dunamai.Version.from_any_vcs).serialize() ``` * setup.py (no install-time dependency on Dunamai as long as you use wheels): ```python from setuptools import setup from dunamai import Version setup( name="your-library", version=Version.from_any_vcs().serialize(), ) ``` Or you could use a static inclusion approach as in the prior example. * [Poetry](https://poetry.eustace.io): ```console $ poetry version $(dunamai from any) ``` Or you can use the [poetry-dynamic-versioning](https://github.com/mtkennerly/poetry-dynamic-versioning) plugin. ## Other notes * Dunamai needs access to the full version history to find tags and compute distance. Be careful if your CI system does a shallow clone by default. * For GitHub workflows, invoke `actions/checkout@v3` with `fetch-depth: 0`. * For GitLab pipelines, set the `GIT_DEPTH` variable to 0. * For Docker builds, copy the VCS history (e.g., `.git` folder) into the container. For Git, you can also avoid doing a full clone by specifying a remote branch for tags (e.g., `--tag-branch remotes/origin/master`). * When using Git, remember that lightweight tags do not store their creation time. Therefore, if a commit has multiple lightweight tags, we cannot reliably determine which one should be considered the newest. The solution is to use annotated tags instead. * When using Git, the initial commit must **not** be both tagged and empty (i.e., created with `--allow-empty`). This is related to a reporting issue in Git. For more info, [click here](https://github.com/mtkennerly/dunamai/issues/14). dunamai-1.23.0/docs/000077500000000000000000000000001471650161100141455ustar00rootroot00000000000000dunamai-1.23.0/docs/dunamai.1000066400000000000000000000634041471650161100156540ustar00rootroot00000000000000.TH DUNAMAI "1" "2024\-08\-07" "dunamai 1.22.0" "Dunamai" .SH NAME dunamai .SH SYNOPSIS .B dunamai [-h] {from,check} ... .SH DESCRIPTION Generate dynamic versions .SH POSITIONAL ARGUMENTS .SS \fBdunamai from\fR Generate version from a particular VCS usage: dunamai from [\-h] {any,git,mercurial,darcs,subversion,bazaar,fossil,pijul} ... Generate version from a particular VCS .SS \fBdunamai from any\fR Generate version from any detected VCS usage: dunamai from any [\-h] [\-\-metadata] [\-\-no\-metadata] [\-\-dirty] [\-\-ignore\-untracked] [\-\-tagged\-metadata] [\-\-pattern PATTERN] [\-\-pattern\-prefix PATTERN_PREFIX] [\-\-format FORMAT] [\-\-style {pep440,semver,pvp}] [\-\-latest\-tag] [\-\-strict] [\-\-path PATH] [\-\-debug] [\-\-bump] [\-\-full\-commit] [\-\-commit\-length COMMIT_LENGTH] [\-\-tag\-branch TAG_BRANCH] [\-\-tag\-dir TAG_DIR] Generate version from any detected VCS options: .RS 7 .TP \fB\-\-metadata\fR Always include metadata. Ignored when \-\-format is used .TP \fB\-\-no\-metadata\fR Never include metadata. Ignored when \-\-format is used .TP \fB\-\-dirty\fR Include dirty flag if applicable. Ignored when \-\-format is used .TP \fB\-\-ignore\-untracked\fR Ignore untracked files when determining whether the repository is dirty (only: Git) .TP \fB\-\-tagged\-metadata\fR Include tagged metadata if applicable. Ignored when \-\-format is used .TP \fB\-\-pattern\fR \fI\,PATTERN\/\fR Regular expression matched against the version source. This must contain one capture group named `base` corresponding to the release segment of the source. Optionally, it may contain another two groups named `stage` and `revision` corresponding to a prerelease type (such as 'alpha' or 'rc') and number (such as in 'alpha\-2' or 'rc3'). It may also contain a group named `tagged_metadata` corresponding to extra metadata after the main part of the version (typically after a plus sign). There may also be a group named `epoch` for the PEP 440 concept. If the `base` group is not present, then instead this will be interpreted as a named preset, which may be one of the following: `default`, `default\-unprefixed` .TP \fB\-\-pattern\-prefix\fR \fI\,PATTERN_PREFIX\/\fR Insert this after the pattern's start anchor (`^`). .TP \fB\-\-format\fR \fI\,FORMAT\/\fR Custom output format. Available substitutions: {base}, {stage}, {revision}, {distance}, {commit}, {dirty}, {tagged_metadata}, {epoch}, {branch}, {branch_escaped}, {timestamp} .TP \fB\-\-style\fR \fI\,{pep440,semver,pvp}\/\fR Preconfigured output format. Will default to PEP 440 if not set and no custom format given. If you specify both a style and a custom format, then the format will be validated against the style's rules .TP \fB\-\-latest\-tag\fR Only inspect the latest tag on the latest tagged commit for a pattern match .TP \fB\-\-strict\fR Elevate warnings to errors. When there are no tags, fail instead of falling back to 0.0.0 .TP \fB\-\-path\fR \fI\,PATH\/\fR Directory to inspect, if not the current working directory .TP \fB\-\-debug\fR Display additional information on stderr for troubleshooting .TP \fB\-\-bump\fR Increment the last part of the version `base` by 1, unless the `stage` is set, in which case increment the `revision` by 1 or set it to a default of 2 if there was no `revision` Does nothing when on a commit with a version tag. .TP \fB\-\-full\-commit\fR Get the full commit hash instead of the short form (only: Git, Mercurial) .TP \fB\-\-commit\-length\fR \fI\,COMMIT_LENGTH\/\fR Use this many characters from the start of the full commit hash .TP \fB\-\-tag\-branch\fR \fI\,TAG_BRANCH\/\fR Branch on which to find tags, if different than the current branch (only: Git) .TP \fB\-\-tag\-dir\fR \fI\,TAG_DIR\/\fR Location of tags relative to the root (only: Subversion) .RE .SS \fBdunamai from git\fR Generate version from Git usage: dunamai from git [\-h] [\-\-metadata] [\-\-no\-metadata] [\-\-dirty] [\-\-ignore\-untracked] [\-\-tagged\-metadata] [\-\-pattern PATTERN] [\-\-pattern\-prefix PATTERN_PREFIX] [\-\-format FORMAT] [\-\-style {pep440,semver,pvp}] [\-\-latest\-tag] [\-\-strict] [\-\-path PATH] [\-\-debug] [\-\-bump] [\-\-full\-commit] [\-\-commit\-length COMMIT_LENGTH] [\-\-tag\-branch TAG_BRANCH] Generate version from Git options: .RS 7 .TP \fB\-\-metadata\fR Always include metadata. Ignored when \-\-format is used .TP \fB\-\-no\-metadata\fR Never include metadata. Ignored when \-\-format is used .TP \fB\-\-dirty\fR Include dirty flag if applicable. Ignored when \-\-format is used .TP \fB\-\-ignore\-untracked\fR Ignore untracked files when determining whether the repository is dirty (only: Git) .TP \fB\-\-tagged\-metadata\fR Include tagged metadata if applicable. Ignored when \-\-format is used .TP \fB\-\-pattern\fR \fI\,PATTERN\/\fR Regular expression matched against the version source. This must contain one capture group named `base` corresponding to the release segment of the source. Optionally, it may contain another two groups named `stage` and `revision` corresponding to a prerelease type (such as 'alpha' or 'rc') and number (such as in 'alpha\-2' or 'rc3'). It may also contain a group named `tagged_metadata` corresponding to extra metadata after the main part of the version (typically after a plus sign). There may also be a group named `epoch` for the PEP 440 concept. If the `base` group is not present, then instead this will be interpreted as a named preset, which may be one of the following: `default`, `default\-unprefixed` .TP \fB\-\-pattern\-prefix\fR \fI\,PATTERN_PREFIX\/\fR Insert this after the pattern's start anchor (`^`). .TP \fB\-\-format\fR \fI\,FORMAT\/\fR Custom output format. Available substitutions: {base}, {stage}, {revision}, {distance}, {commit}, {dirty}, {tagged_metadata}, {epoch}, {branch}, {branch_escaped}, {timestamp} .TP \fB\-\-style\fR \fI\,{pep440,semver,pvp}\/\fR Preconfigured output format. Will default to PEP 440 if not set and no custom format given. If you specify both a style and a custom format, then the format will be validated against the style's rules .TP \fB\-\-latest\-tag\fR Only inspect the latest tag on the latest tagged commit for a pattern match .TP \fB\-\-strict\fR Elevate warnings to errors. When there are no tags, fail instead of falling back to 0.0.0 .TP \fB\-\-path\fR \fI\,PATH\/\fR Directory to inspect, if not the current working directory .TP \fB\-\-debug\fR Display additional information on stderr for troubleshooting .TP \fB\-\-bump\fR Increment the last part of the version `base` by 1, unless the `stage` is set, in which case increment the `revision` by 1 or set it to a default of 2 if there was no `revision` Does nothing when on a commit with a version tag. .TP \fB\-\-full\-commit\fR Get the full commit hash instead of the short form (only: Git, Mercurial) .TP \fB\-\-commit\-length\fR \fI\,COMMIT_LENGTH\/\fR Use this many characters from the start of the full commit hash .TP \fB\-\-tag\-branch\fR \fI\,TAG_BRANCH\/\fR Branch on which to find tags, if different than the current branch (only: Git) .RE .SS \fBdunamai from mercurial\fR Generate version from Mercurial usage: dunamai from mercurial [\-h] [\-\-metadata] [\-\-no\-metadata] [\-\-dirty] [\-\-tagged\-metadata] [\-\-pattern PATTERN] [\-\-pattern\-prefix PATTERN_PREFIX] [\-\-format FORMAT] [\-\-style {pep440,semver,pvp}] [\-\-latest\-tag] [\-\-strict] [\-\-path PATH] [\-\-debug] [\-\-bump] [\-\-full\-commit] [\-\-commit\-length COMMIT_LENGTH] Generate version from Mercurial options: .RS 7 .TP \fB\-\-metadata\fR Always include metadata. Ignored when \-\-format is used .TP \fB\-\-no\-metadata\fR Never include metadata. Ignored when \-\-format is used .TP \fB\-\-dirty\fR Include dirty flag if applicable. Ignored when \-\-format is used .TP \fB\-\-tagged\-metadata\fR Include tagged metadata if applicable. Ignored when \-\-format is used .TP \fB\-\-pattern\fR \fI\,PATTERN\/\fR Regular expression matched against the version source. This must contain one capture group named `base` corresponding to the release segment of the source. Optionally, it may contain another two groups named `stage` and `revision` corresponding to a prerelease type (such as 'alpha' or 'rc') and number (such as in 'alpha\-2' or 'rc3'). It may also contain a group named `tagged_metadata` corresponding to extra metadata after the main part of the version (typically after a plus sign). There may also be a group named `epoch` for the PEP 440 concept. If the `base` group is not present, then instead this will be interpreted as a named preset, which may be one of the following: `default`, `default\-unprefixed` .TP \fB\-\-pattern\-prefix\fR \fI\,PATTERN_PREFIX\/\fR Insert this after the pattern's start anchor (`^`). .TP \fB\-\-format\fR \fI\,FORMAT\/\fR Custom output format. Available substitutions: {base}, {stage}, {revision}, {distance}, {commit}, {dirty}, {tagged_metadata}, {epoch}, {branch}, {branch_escaped}, {timestamp} .TP \fB\-\-style\fR \fI\,{pep440,semver,pvp}\/\fR Preconfigured output format. Will default to PEP 440 if not set and no custom format given. If you specify both a style and a custom format, then the format will be validated against the style's rules .TP \fB\-\-latest\-tag\fR Only inspect the latest tag on the latest tagged commit for a pattern match .TP \fB\-\-strict\fR Elevate warnings to errors. When there are no tags, fail instead of falling back to 0.0.0 .TP \fB\-\-path\fR \fI\,PATH\/\fR Directory to inspect, if not the current working directory .TP \fB\-\-debug\fR Display additional information on stderr for troubleshooting .TP \fB\-\-bump\fR Increment the last part of the version `base` by 1, unless the `stage` is set, in which case increment the `revision` by 1 or set it to a default of 2 if there was no `revision` Does nothing when on a commit with a version tag. .TP \fB\-\-full\-commit\fR Get the full commit hash instead of the short form (only: Git, Mercurial) .TP \fB\-\-commit\-length\fR \fI\,COMMIT_LENGTH\/\fR Use this many characters from the start of the full commit hash .RE .SS \fBdunamai from darcs\fR Generate version from Darcs usage: dunamai from darcs [\-h] [\-\-metadata] [\-\-no\-metadata] [\-\-dirty] [\-\-tagged\-metadata] [\-\-pattern PATTERN] [\-\-pattern\-prefix PATTERN_PREFIX] [\-\-format FORMAT] [\-\-style {pep440,semver,pvp}] [\-\-latest\-tag] [\-\-strict] [\-\-path PATH] [\-\-debug] [\-\-bump] [\-\-commit\-length COMMIT_LENGTH] Generate version from Darcs options: .RS 7 .TP \fB\-\-metadata\fR Always include metadata. Ignored when \-\-format is used .TP \fB\-\-no\-metadata\fR Never include metadata. Ignored when \-\-format is used .TP \fB\-\-dirty\fR Include dirty flag if applicable. Ignored when \-\-format is used .TP \fB\-\-tagged\-metadata\fR Include tagged metadata if applicable. Ignored when \-\-format is used .TP \fB\-\-pattern\fR \fI\,PATTERN\/\fR Regular expression matched against the version source. This must contain one capture group named `base` corresponding to the release segment of the source. Optionally, it may contain another two groups named `stage` and `revision` corresponding to a prerelease type (such as 'alpha' or 'rc') and number (such as in 'alpha\-2' or 'rc3'). It may also contain a group named `tagged_metadata` corresponding to extra metadata after the main part of the version (typically after a plus sign). There may also be a group named `epoch` for the PEP 440 concept. If the `base` group is not present, then instead this will be interpreted as a named preset, which may be one of the following: `default`, `default\-unprefixed` .TP \fB\-\-pattern\-prefix\fR \fI\,PATTERN_PREFIX\/\fR Insert this after the pattern's start anchor (`^`). .TP \fB\-\-format\fR \fI\,FORMAT\/\fR Custom output format. Available substitutions: {base}, {stage}, {revision}, {distance}, {commit}, {dirty}, {tagged_metadata}, {epoch}, {branch}, {branch_escaped}, {timestamp} .TP \fB\-\-style\fR \fI\,{pep440,semver,pvp}\/\fR Preconfigured output format. Will default to PEP 440 if not set and no custom format given. If you specify both a style and a custom format, then the format will be validated against the style's rules .TP \fB\-\-latest\-tag\fR Only inspect the latest tag on the latest tagged commit for a pattern match .TP \fB\-\-strict\fR Elevate warnings to errors. When there are no tags, fail instead of falling back to 0.0.0 .TP \fB\-\-path\fR \fI\,PATH\/\fR Directory to inspect, if not the current working directory .TP \fB\-\-debug\fR Display additional information on stderr for troubleshooting .TP \fB\-\-bump\fR Increment the last part of the version `base` by 1, unless the `stage` is set, in which case increment the `revision` by 1 or set it to a default of 2 if there was no `revision` Does nothing when on a commit with a version tag. .TP \fB\-\-commit\-length\fR \fI\,COMMIT_LENGTH\/\fR Use this many characters from the start of the full commit hash .RE .SS \fBdunamai from subversion\fR Generate version from Subversion usage: dunamai from subversion [\-h] [\-\-metadata] [\-\-no\-metadata] [\-\-dirty] [\-\-tagged\-metadata] [\-\-pattern PATTERN] [\-\-pattern\-prefix PATTERN_PREFIX] [\-\-format FORMAT] [\-\-style {pep440,semver,pvp}] [\-\-latest\-tag] [\-\-strict] [\-\-path PATH] [\-\-debug] [\-\-bump] [\-\-commit\-length COMMIT_LENGTH] [\-\-tag\-dir TAG_DIR] Generate version from Subversion options: .RS 7 .TP \fB\-\-metadata\fR Always include metadata. Ignored when \-\-format is used .TP \fB\-\-no\-metadata\fR Never include metadata. Ignored when \-\-format is used .TP \fB\-\-dirty\fR Include dirty flag if applicable. Ignored when \-\-format is used .TP \fB\-\-tagged\-metadata\fR Include tagged metadata if applicable. Ignored when \-\-format is used .TP \fB\-\-pattern\fR \fI\,PATTERN\/\fR Regular expression matched against the version source. This must contain one capture group named `base` corresponding to the release segment of the source. Optionally, it may contain another two groups named `stage` and `revision` corresponding to a prerelease type (such as 'alpha' or 'rc') and number (such as in 'alpha\-2' or 'rc3'). It may also contain a group named `tagged_metadata` corresponding to extra metadata after the main part of the version (typically after a plus sign). There may also be a group named `epoch` for the PEP 440 concept. If the `base` group is not present, then instead this will be interpreted as a named preset, which may be one of the following: `default`, `default\-unprefixed` .TP \fB\-\-pattern\-prefix\fR \fI\,PATTERN_PREFIX\/\fR Insert this after the pattern's start anchor (`^`). .TP \fB\-\-format\fR \fI\,FORMAT\/\fR Custom output format. Available substitutions: {base}, {stage}, {revision}, {distance}, {commit}, {dirty}, {tagged_metadata}, {epoch}, {branch}, {branch_escaped}, {timestamp} .TP \fB\-\-style\fR \fI\,{pep440,semver,pvp}\/\fR Preconfigured output format. Will default to PEP 440 if not set and no custom format given. If you specify both a style and a custom format, then the format will be validated against the style's rules .TP \fB\-\-latest\-tag\fR Only inspect the latest tag on the latest tagged commit for a pattern match .TP \fB\-\-strict\fR Elevate warnings to errors. When there are no tags, fail instead of falling back to 0.0.0 .TP \fB\-\-path\fR \fI\,PATH\/\fR Directory to inspect, if not the current working directory .TP \fB\-\-debug\fR Display additional information on stderr for troubleshooting .TP \fB\-\-bump\fR Increment the last part of the version `base` by 1, unless the `stage` is set, in which case increment the `revision` by 1 or set it to a default of 2 if there was no `revision` Does nothing when on a commit with a version tag. .TP \fB\-\-commit\-length\fR \fI\,COMMIT_LENGTH\/\fR Use this many characters from the start of the full commit hash .TP \fB\-\-tag\-dir\fR \fI\,TAG_DIR\/\fR Location of tags relative to the root (only: Subversion) .RE .SS \fBdunamai from bazaar\fR Generate version from Bazaar usage: dunamai from bazaar [\-h] [\-\-metadata] [\-\-no\-metadata] [\-\-dirty] [\-\-tagged\-metadata] [\-\-pattern PATTERN] [\-\-pattern\-prefix PATTERN_PREFIX] [\-\-format FORMAT] [\-\-style {pep440,semver,pvp}] [\-\-latest\-tag] [\-\-strict] [\-\-path PATH] [\-\-debug] [\-\-bump] [\-\-commit\-length COMMIT_LENGTH] Generate version from Bazaar options: .RS 7 .TP \fB\-\-metadata\fR Always include metadata. Ignored when \-\-format is used .TP \fB\-\-no\-metadata\fR Never include metadata. Ignored when \-\-format is used .TP \fB\-\-dirty\fR Include dirty flag if applicable. Ignored when \-\-format is used .TP \fB\-\-tagged\-metadata\fR Include tagged metadata if applicable. Ignored when \-\-format is used .TP \fB\-\-pattern\fR \fI\,PATTERN\/\fR Regular expression matched against the version source. This must contain one capture group named `base` corresponding to the release segment of the source. Optionally, it may contain another two groups named `stage` and `revision` corresponding to a prerelease type (such as 'alpha' or 'rc') and number (such as in 'alpha\-2' or 'rc3'). It may also contain a group named `tagged_metadata` corresponding to extra metadata after the main part of the version (typically after a plus sign). There may also be a group named `epoch` for the PEP 440 concept. If the `base` group is not present, then instead this will be interpreted as a named preset, which may be one of the following: `default`, `default\-unprefixed` .TP \fB\-\-pattern\-prefix\fR \fI\,PATTERN_PREFIX\/\fR Insert this after the pattern's start anchor (`^`). .TP \fB\-\-format\fR \fI\,FORMAT\/\fR Custom output format. Available substitutions: {base}, {stage}, {revision}, {distance}, {commit}, {dirty}, {tagged_metadata}, {epoch}, {branch}, {branch_escaped}, {timestamp} .TP \fB\-\-style\fR \fI\,{pep440,semver,pvp}\/\fR Preconfigured output format. Will default to PEP 440 if not set and no custom format given. If you specify both a style and a custom format, then the format will be validated against the style's rules .TP \fB\-\-latest\-tag\fR Only inspect the latest tag on the latest tagged commit for a pattern match .TP \fB\-\-strict\fR Elevate warnings to errors. When there are no tags, fail instead of falling back to 0.0.0 .TP \fB\-\-path\fR \fI\,PATH\/\fR Directory to inspect, if not the current working directory .TP \fB\-\-debug\fR Display additional information on stderr for troubleshooting .TP \fB\-\-bump\fR Increment the last part of the version `base` by 1, unless the `stage` is set, in which case increment the `revision` by 1 or set it to a default of 2 if there was no `revision` Does nothing when on a commit with a version tag. .TP \fB\-\-commit\-length\fR \fI\,COMMIT_LENGTH\/\fR Use this many characters from the start of the full commit hash .RE .SS \fBdunamai from fossil\fR Generate version from Fossil usage: dunamai from fossil [\-h] [\-\-metadata] [\-\-no\-metadata] [\-\-dirty] [\-\-tagged\-metadata] [\-\-pattern PATTERN] [\-\-pattern\-prefix PATTERN_PREFIX] [\-\-format FORMAT] [\-\-style {pep440,semver,pvp}] [\-\-latest\-tag] [\-\-strict] [\-\-path PATH] [\-\-debug] [\-\-bump] [\-\-commit\-length COMMIT_LENGTH] Generate version from Fossil options: .RS 7 .TP \fB\-\-metadata\fR Always include metadata. Ignored when \-\-format is used .TP \fB\-\-no\-metadata\fR Never include metadata. Ignored when \-\-format is used .TP \fB\-\-dirty\fR Include dirty flag if applicable. Ignored when \-\-format is used .TP \fB\-\-tagged\-metadata\fR Include tagged metadata if applicable. Ignored when \-\-format is used .TP \fB\-\-pattern\fR \fI\,PATTERN\/\fR Regular expression matched against the version source. This must contain one capture group named `base` corresponding to the release segment of the source. Optionally, it may contain another two groups named `stage` and `revision` corresponding to a prerelease type (such as 'alpha' or 'rc') and number (such as in 'alpha\-2' or 'rc3'). It may also contain a group named `tagged_metadata` corresponding to extra metadata after the main part of the version (typically after a plus sign). There may also be a group named `epoch` for the PEP 440 concept. If the `base` group is not present, then instead this will be interpreted as a named preset, which may be one of the following: `default`, `default\-unprefixed` .TP \fB\-\-pattern\-prefix\fR \fI\,PATTERN_PREFIX\/\fR Insert this after the pattern's start anchor (`^`). .TP \fB\-\-format\fR \fI\,FORMAT\/\fR Custom output format. Available substitutions: {base}, {stage}, {revision}, {distance}, {commit}, {dirty}, {tagged_metadata}, {epoch}, {branch}, {branch_escaped}, {timestamp} .TP \fB\-\-style\fR \fI\,{pep440,semver,pvp}\/\fR Preconfigured output format. Will default to PEP 440 if not set and no custom format given. If you specify both a style and a custom format, then the format will be validated against the style's rules .TP \fB\-\-latest\-tag\fR Only inspect the latest tag on the latest tagged commit for a pattern match .TP \fB\-\-strict\fR Elevate warnings to errors. When there are no tags, fail instead of falling back to 0.0.0 .TP \fB\-\-path\fR \fI\,PATH\/\fR Directory to inspect, if not the current working directory .TP \fB\-\-debug\fR Display additional information on stderr for troubleshooting .TP \fB\-\-bump\fR Increment the last part of the version `base` by 1, unless the `stage` is set, in which case increment the `revision` by 1 or set it to a default of 2 if there was no `revision` Does nothing when on a commit with a version tag. .TP \fB\-\-commit\-length\fR \fI\,COMMIT_LENGTH\/\fR Use this many characters from the start of the full commit hash .RE .SS \fBdunamai from pijul\fR Generate version from Pijul usage: dunamai from pijul [\-h] [\-\-metadata] [\-\-no\-metadata] [\-\-dirty] [\-\-tagged\-metadata] [\-\-pattern PATTERN] [\-\-pattern\-prefix PATTERN_PREFIX] [\-\-format FORMAT] [\-\-style {pep440,semver,pvp}] [\-\-latest\-tag] [\-\-strict] [\-\-path PATH] [\-\-debug] [\-\-bump] [\-\-commit\-length COMMIT_LENGTH] Generate version from Pijul options: .RS 7 .TP \fB\-\-metadata\fR Always include metadata. Ignored when \-\-format is used .TP \fB\-\-no\-metadata\fR Never include metadata. Ignored when \-\-format is used .TP \fB\-\-dirty\fR Include dirty flag if applicable. Ignored when \-\-format is used .TP \fB\-\-tagged\-metadata\fR Include tagged metadata if applicable. Ignored when \-\-format is used .TP \fB\-\-pattern\fR \fI\,PATTERN\/\fR Regular expression matched against the version source. This must contain one capture group named `base` corresponding to the release segment of the source. Optionally, it may contain another two groups named `stage` and `revision` corresponding to a prerelease type (such as 'alpha' or 'rc') and number (such as in 'alpha\-2' or 'rc3'). It may also contain a group named `tagged_metadata` corresponding to extra metadata after the main part of the version (typically after a plus sign). There may also be a group named `epoch` for the PEP 440 concept. If the `base` group is not present, then instead this will be interpreted as a named preset, which may be one of the following: `default`, `default\-unprefixed` .TP \fB\-\-pattern\-prefix\fR \fI\,PATTERN_PREFIX\/\fR Insert this after the pattern's start anchor (`^`). .TP \fB\-\-format\fR \fI\,FORMAT\/\fR Custom output format. Available substitutions: {base}, {stage}, {revision}, {distance}, {commit}, {dirty}, {tagged_metadata}, {epoch}, {branch}, {branch_escaped}, {timestamp} .TP \fB\-\-style\fR \fI\,{pep440,semver,pvp}\/\fR Preconfigured output format. Will default to PEP 440 if not set and no custom format given. If you specify both a style and a custom format, then the format will be validated against the style's rules .TP \fB\-\-latest\-tag\fR Only inspect the latest tag on the latest tagged commit for a pattern match .TP \fB\-\-strict\fR Elevate warnings to errors. When there are no tags, fail instead of falling back to 0.0.0 .TP \fB\-\-path\fR \fI\,PATH\/\fR Directory to inspect, if not the current working directory .TP \fB\-\-debug\fR Display additional information on stderr for troubleshooting .TP \fB\-\-bump\fR Increment the last part of the version `base` by 1, unless the `stage` is set, in which case increment the `revision` by 1 or set it to a default of 2 if there was no `revision` Does nothing when on a commit with a version tag. .TP \fB\-\-commit\-length\fR \fI\,COMMIT_LENGTH\/\fR Use this many characters from the start of the full commit hash .RE .SS \fBdunamai check\fR Check if a version is valid for a style usage: dunamai check [\-h] [\-\-style {pep440,semver,pvp}] [version] Check if a version is valid for a style arguments: .RS 7 .TP \fBversion\fR Version to check; may be piped in .RE options: .RS 7 .TP \fB\-\-style\fR \fI\,{pep440,semver,pvp}\/\fR Style against which to check .RE .SH AUTHOR .nf 'Matthew T. Kennerly (mtkennerly)' .fi .SH DISTRIBUTION The latest version of dunamai may be downloaded from .UR https://github.com/mtkennerly/dunamai .UE dunamai-1.23.0/docs/index.md000066400000000000000000000000231471650161100155710ustar00rootroot00000000000000# API ::: dunamai dunamai-1.23.0/docs/requirements.txt000066400000000000000000000000451471650161100174300ustar00rootroot00000000000000mkdocstrings[python] mkdocs-material dunamai-1.23.0/dunamai/000077500000000000000000000000001471650161100146335ustar00rootroot00000000000000dunamai-1.23.0/dunamai/__init__.py000066400000000000000000002507401471650161100167540ustar00rootroot00000000000000__all__ = [ "bump_version", "check_version", "get_version", "serialize_pep440", "serialize_pvp", "serialize_semver", "Style", "Vcs", "Version", ] import copy import datetime as dt import inspect import json import re import shlex import shutil import subprocess from collections import OrderedDict from enum import Enum from functools import total_ordering from pathlib import Path from typing import ( Any, Callable, List, Mapping, NamedTuple, Optional, Sequence, Set, Tuple, TypeVar, Union, ) from xml.etree import ElementTree VERSION_SOURCE_PATTERN = r""" (?x) (?# ignore whitespace) ^v((?P\d+)!)?(?P\d+(\.\d+)*) (?# v1.2.3 or v1!2000.1.2) ([-._]?((?P[a-zA-Z]+)[-._]?(?P\d+)?))? (?# b0) (\+(?P.+))?$ (?# +linux) """.strip() # Preserve old/private name for now in case it exists in the wild _VERSION_PATTERN = VERSION_SOURCE_PATTERN _VALID_PEP440 = r""" (?x) ^(\d+!)? \d+(\.\d+)* ((a|b|rc)\d+)? (\.post\d+)? (\.dev\d+)? (\+([a-zA-Z0-9]|[a-zA-Z0-9]{2}|[a-zA-Z0-9][a-zA-Z0-9.]+[a-zA-Z0-9]))?$ """.strip() _VALID_SEMVER = r""" (?x) ^\d+\.\d+\.\d+ (\-[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]+)*)? (\+[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]+)*)?$ """.strip() _VALID_PVP = r"^\d+(\.\d+)*(-[a-zA-Z0-9]+)*$" _TIMESTAMP_GENERIC = "%Y-%m-%dT%H:%M:%S.%f%z" _TIMESTAMP_GENERIC_SPACE = "%Y-%m-%d %H:%M:%S.%f%z" _TIMESTAMP_GIT_ISO_STRICT = "%Y-%m-%dT%H:%M:%S%z" _TIMESTAMP_GIT_ISO = "%Y-%m-%d %H:%M:%S %z" _T = TypeVar("_T") class Style(Enum): """ Standard styles for serializing a version. """ Pep440 = "pep440" """PEP 440""" SemVer = "semver" """Semantic Versioning""" Pvp = "pvp" """Haskell Package Versioning Policy""" class Vcs(Enum): """ Version control systems. """ Any = "any" """Automatically determine the VCS.""" Git = "git" """Git""" Mercurial = "mercurial" """Mercurial""" Darcs = "darcs" """Darcs""" Subversion = "subversion" """Subversion""" Bazaar = "bazaar" """Bazaar""" Fossil = "fossil" """Fossil""" Pijul = "pijul" """Pijul""" class Pattern(Enum): """ Regular expressions used to parse tags as versions. """ Default = "default" """Default pattern, including `v` prefix.""" DefaultUnprefixed = "default-unprefixed" """Default pattern, but without `v` prefix.""" def regex(self, prefix: Optional[str] = None) -> str: """ Get the regular expression for this preset pattern. :param prefix: Insert this after the pattern's start anchor (`^`). :returns: Regular expression. """ variants = { Pattern.Default: VERSION_SOURCE_PATTERN, Pattern.DefaultUnprefixed: VERSION_SOURCE_PATTERN.replace("^v", "^v?", 1), } out = variants[self] if prefix: out = out.replace("^", "^{}".format(prefix), 1) return out @staticmethod def parse(pattern: Union[str, "Pattern"], prefix: Optional[str] = None) -> str: """ Parse a pattern string into a regular expression. If the pattern contains the capture group `?P`, then it is returned as-is. Otherwise, it is interpreted as a variant of the `Pattern` enum. :param pattern: Pattern to parse. :param prefix: Insert this after the pattern's start anchor (`^`). :returns: Regular expression. """ if isinstance(pattern, str) and "?P" in pattern: if prefix: return pattern.replace("^", "^{}".format(prefix), 1) else: return pattern try: pattern = Pattern(pattern) except ValueError: raise ValueError( _pattern_error( ( "The pattern does not contain the capture group '?P'" " and is not a known preset like '{}'".format(Pattern.Default.value) ), pattern, ) ) return pattern.regex(prefix) class Concern(Enum): """ A concern/warning discovered while producing the version. """ ShallowRepository = "shallow-repository" """Repository does not contain full history""" def message(self) -> str: """ Produce a human-readable description of the concern. :returns: Concern description. """ if self == Concern.ShallowRepository: return "This is a shallow repository, so Dunamai may not produce the correct version." else: return "" def _pattern_error(primary: str, pattern: Union[str, Pattern], tags: Optional[Sequence[str]] = None) -> str: parts = [primary, "Pattern:\n{}".format(pattern)] if tags is not None: parts.append("Tags:\n{}".format(tags)) return "\n\n".join(parts) def _run_cmd( command: str, where: Optional[Path], codes: Sequence[int] = (0,), shell: bool = False, env: Optional[dict] = None, ) -> Tuple[int, str]: result = subprocess.run( shlex.split(command), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=str(where) if where is not None else None, shell=shell, env=env, ) output = result.stdout.decode().strip() if codes and result.returncode not in codes: raise RuntimeError("The command '{}' returned code {}. Output:\n{}".format(command, result.returncode, output)) return (result.returncode, output) _MatchedVersionPattern = NamedTuple( "_MatchedVersionPattern", [ ("matched_tag", str), ("base", str), ("stage_revision", Optional[Tuple[str, Optional[int]]]), ("newer_tags", Sequence[str]), ("tagged_metadata", Optional[str]), ("epoch", Optional[int]), ], ) def _match_version_pattern( pattern: Union[str, Pattern], sources: Sequence[str], latest_source: bool, strict: bool, pattern_prefix: Optional[str], ) -> Optional[_MatchedVersionPattern]: """ :returns: Tuple of: * matched tag * base segment * tuple of: * stage * revision * any newer unmatched tags * tagged_metadata matched section """ pattern_match = None base = None stage_revision = None newer_unmatched_tags = [] tagged_metadata = None epoch = None # type: Optional[Union[str, int]] pattern = Pattern.parse(pattern, pattern_prefix) for source in sources[:1] if latest_source else sources: try: pattern_match = re.search(pattern, source) except re.error as e: raise re.error( _pattern_error( "The pattern is an invalid regular expression: {}".format(e.msg), # type: ignore pattern, ), e.pattern, # type: ignore e.pos, # type: ignore ) if pattern_match is None: newer_unmatched_tags.append(source) continue try: base = pattern_match.group("base") if base is not None: break except IndexError: raise ValueError(_pattern_error("The pattern did not include required capture group 'base'", pattern)) if pattern_match is None or base is None: if latest_source: raise ValueError( _pattern_error( "The pattern did not match the latest tag '{}'".format(sources[0]), pattern, sources, ) ) elif strict: raise ValueError(_pattern_error("The pattern did not match any tags", pattern, sources)) else: return None stage = pattern_match.groupdict().get("stage") revision = pattern_match.groupdict().get("revision") tagged_metadata = pattern_match.groupdict().get("tagged_metadata") epoch = pattern_match.groupdict().get("epoch") if stage is not None: try: stage_revision = (stage, None if revision is None else int(revision)) except ValueError: raise ValueError("Revision '{}' is not a valid number".format(revision)) if epoch is not None: try: epoch = int(epoch) except ValueError: raise ValueError("Epoch '{}' is not a valid number".format(epoch)) return _MatchedVersionPattern(source, base, stage_revision, newer_unmatched_tags, tagged_metadata, epoch) def _blank(value: Optional[_T], default: _T) -> _T: return value if value is not None else default def _escape_branch(value: str) -> str: return re.sub(r"[^a-zA-Z0-9]", "", value) def _equal_if_set(x: _T, y: Optional[_T], unset: Sequence[Any] = (None,)) -> bool: if y in unset: return True return x == y def _get_git_version() -> List[int]: _, msg = _run_cmd("git version", where=None) result = re.search(r"git version (\d+(\.\d+)*)", msg.strip()) if result is not None: parts = result.group(1).split(".") return [int(x) for x in parts] return [] def _git_log(git_version: List[int]) -> str: if git_version < [2, 10]: return "git log" else: return "git -c log.showsignature=false log" def _detect_vcs(expected_vcs: Optional[Vcs], path: Optional[Path]) -> Vcs: checks = OrderedDict( [ (Vcs.Git, "git status"), (Vcs.Mercurial, "hg status"), (Vcs.Darcs, "darcs log"), (Vcs.Subversion, "svn log"), (Vcs.Bazaar, "bzr status"), (Vcs.Fossil, "fossil status"), (Vcs.Pijul, "pijul log"), ] ) dubious_ownership_flag = "detected dubious ownership" dubious_ownership_error = "Detected Git repository, but failed because of dubious ownership" if expected_vcs: command = checks[expected_vcs] program = command.split()[0] if not shutil.which(program): raise RuntimeError("Unable to find '{}' program".format(program)) code, msg = _run_cmd(command, path, codes=[]) if code != 0: if expected_vcs == Vcs.Git and dubious_ownership_flag in msg: raise RuntimeError(dubious_ownership_error) raise RuntimeError("This does not appear to be a {} project".format(expected_vcs.value.title())) return expected_vcs else: disproven = [] unavailable = [] for vcs, command in checks.items(): if shutil.which(command.split()[0]): code, msg = _run_cmd(command, path, codes=[]) if code == 0: return vcs elif vcs == Vcs.Git and dubious_ownership_flag in msg: raise RuntimeError(dubious_ownership_error) disproven.append(vcs.name) else: unavailable.append(vcs.name) error_parts = ["Unable to detect version control system."] if disproven: error_parts.append("Checked: {}.".format(", ".join(disproven))) if unavailable: error_parts.append("Not installed: {}.".format(", ".join(unavailable))) raise RuntimeError(" ".join(error_parts)) def _detect_vcs_from_archival(path: Optional[Path]) -> Optional[Vcs]: archival = _find_higher_file(".git_archival.json", path, ".git") if archival is not None: content = archival.read_text("utf8") if "$Format:" not in content: return Vcs.Git archival = _find_higher_file(".hg_archival.txt", path, ".hg") if archival is not None: return Vcs.Mercurial return None def _find_higher_file(name: str, start: Optional[Path], limit: Optional[str] = None) -> Optional[Path]: """ :param name: Bare name of a file we'd like to find. :param limit: Give up if we find a file/folder with this name. :param start: Begin recursing from this folder (default: `.`). """ if start is None: start = Path.cwd() for level in [start, *start.parents]: if (level / name).is_file(): return level / name if limit is not None and (level / limit).exists(): return None return None class _GitRefInfo: def __init__(self, ref: str, commit: str, creatordate: str, committerdate: str, taggerdate: str): self.fullref = ref self.commit = commit self.creatordate = self.normalize_git_dt(creatordate) self.committerdate = self.normalize_git_dt(committerdate) self.taggerdate = self.normalize_git_dt(taggerdate) self.tag_topo_lookup = {} # type: Mapping[str, int] def with_tag_topo_lookup(self, lookup: Mapping[str, int]) -> "_GitRefInfo": self.tag_topo_lookup = lookup return self @staticmethod def normalize_git_dt(timestamp: str) -> Optional[dt.datetime]: if timestamp == "": return None else: return _parse_git_timestamp_iso_strict(timestamp) def __repr__(self): return ("_GitRefInfo(ref={!r}, commit={!r}, creatordate={!r}, committerdate={!r}, taggerdate={!r})").format( self.fullref, self.commit_offset, self.creatordate, self.committerdate, self.taggerdate ) def best_date(self) -> Optional[dt.datetime]: if self.taggerdate is not None: return self.taggerdate elif self.committerdate is not None: return self.committerdate else: return self.creatordate @property def commit_offset(self) -> int: try: return self.tag_topo_lookup[self.fullref] except KeyError: raise RuntimeError( "Unable to determine commit offset for ref {} in data: {}".format(self.fullref, self.tag_topo_lookup) ) @property def sort_key(self) -> Tuple[int, Optional[dt.datetime]]: return (-self.commit_offset, self.best_date()) @property def ref(self) -> str: return self.fullref.replace("refs/tags/", "") @staticmethod def normalize_tag_ref(ref: str) -> str: if ref.startswith("refs/tags/"): return ref else: return "refs/tags/{}".format(ref) @staticmethod def from_git_tag_topo_order(tag_branch: str, git_version: List[int], path: Optional[Path]) -> Mapping[str, int]: cmd = ('{} --simplify-by-decoration --topo-order --decorate=full {} "--format=%H%d"').format( _git_log(git_version), tag_branch ) if git_version >= [2, 16]: cmd += " --decorate-refs=refs/tags/" code, logmsg = _run_cmd(cmd, path) filtered_lines = [x for x in logmsg.strip().splitlines(keepends=False) if " (" not in x or "tag: " in x] tag_lookup = {} for tag_offset, line in enumerate(filtered_lines): # lines have the pattern # (tag: refs/tags/v1.2.0b1, tag: refs/tags/v1.2.0) commit, _, tags = line.partition("(") commit = commit.strip() if tags: # remove trailing ')' tags = tags[:-1] taglist = [tag.strip() for tag in tags.split(", ") if tag.strip().startswith("tag: ")] taglist = [tag.split()[-1] for tag in taglist] taglist = [_GitRefInfo.normalize_tag_ref(tag) for tag in taglist] for tag in taglist: tag_lookup[tag] = tag_offset return tag_lookup @total_ordering class Version: """ A dynamic version. :ivar base: Release segment. :vartype base: str :ivar stage: Alphabetical part of prerelease segment. :vartype stage: Optional[str] :ivar revision: Numerical part of prerelease segment. :vartype revision: Optional[int] :ivar distance: Number of commits since the last tag. :vartype distance: int :ivar commit: Commit ID. :vartype commit: Optional[str] :ivar dirty: Whether there are uncommitted changes. :vartype dirty: Optional[bool] :ivar tagged_metadata: Any metadata segment from the tag itself. :vartype tagged_metadata: Optional[str] :ivar epoch: Optional PEP 440 epoch. :vartype epoch: Optional[int] :ivar branch: Name of the current branch. :vartype branch: Optional[str] :ivar timestamp: Timestamp of the current commit. :vartype timestamp: Optional[dt.datetime] :ivar concerns: Any concerns regarding the version. :vartype concerns: Optional[Set[Concern]] :ivar vcs: Version control system from which the version was detected. :vartype vcs: Vcs """ def __init__( self, base: str, *, stage: Optional[Tuple[str, Optional[int]]] = None, distance: int = 0, commit: Optional[str] = None, dirty: Optional[bool] = None, tagged_metadata: Optional[str] = None, epoch: Optional[int] = None, branch: Optional[str] = None, timestamp: Optional[dt.datetime] = None, concerns: Optional[Set[Concern]] = None, # fmt: off vcs: Vcs = Vcs.Any # fmt: on ) -> None: """ :param base: Release segment, such as 0.1.0. :param stage: Pair of release stage (e.g., "a", "alpha", "b", "rc") and an optional revision number. :param distance: Number of commits since the last tag. :param commit: Commit hash/identifier. :param dirty: True if the working directory does not match the commit. :param epoch: Optional PEP 440 epoch. :param branch: Name of the current branch. :param timestamp: Timestamp of the current commit. :param concerns: Any concerns regarding the version. """ self.base = base self.stage = None self.revision = None if stage is not None: self.stage, self.revision = stage self.distance = distance self.commit = commit self.dirty = dirty self.tagged_metadata = tagged_metadata self.epoch = epoch self.branch = branch try: self.timestamp = timestamp.astimezone(dt.timezone.utc) if timestamp else None except ValueError: # Will fail for naive timestamps before Python 3.6. self.timestamp = timestamp self.concerns = concerns or set() self.vcs = vcs self._matched_tag = None # type: Optional[str] self._newer_unmatched_tags = None # type: Optional[Sequence[str]] self._smart_bumped = False def __str__(self) -> str: return self.serialize() def __repr__(self) -> str: return ( "Version(base={!r}, stage={!r}, revision={!r}, distance={!r}, commit={!r}," " dirty={!r}, tagged_metadata={!r}, epoch={!r}, branch={!r}, timestamp={!r})" ).format( self.base, self.stage, self.revision, self.distance, self.commit, self.dirty, self.tagged_metadata, self.epoch, self.branch, self.timestamp, ) def __eq__(self, other: Any) -> bool: if not isinstance(other, Version): raise TypeError("Cannot compare Version with type {}".format(other.__class__.__qualname__)) return ( self.base == other.base and self.stage == other.stage and self.revision == other.revision and self.distance == other.distance and self.commit == other.commit and self.dirty == other.dirty and self.tagged_metadata == other.tagged_metadata and self.epoch == other.epoch and self.branch == other.branch and self.timestamp == other.timestamp ) def _matches_partial(self, other: "Version") -> bool: """ Compare this version to another version, but ignore None values in the other version. Distance is also ignored when `other.distance == 0`. :param other: The version to compare to. :returns: True if this version equals the other version. """ return ( _equal_if_set(self.base, other.base) and _equal_if_set(self.stage, other.stage) and _equal_if_set(self.revision, other.revision) and _equal_if_set(self.distance, other.distance, unset=[None, 0]) and _equal_if_set(self.commit, other.commit) and _equal_if_set(self.dirty, other.dirty) and _equal_if_set(self.tagged_metadata, other.tagged_metadata) and _equal_if_set(self.epoch, other.epoch) and _equal_if_set(self.branch, other.branch) and _equal_if_set(self.timestamp, other.timestamp) ) def __lt__(self, other: Any) -> bool: if not isinstance(other, Version): raise TypeError("Cannot compare Version with type {}".format(other.__class__.__qualname__)) import packaging.version as pv return ( pv.Version(self.base) < pv.Version(other.base) and _blank(self.stage, "") < _blank(other.stage, "") and _blank(self.revision, 0) < _blank(other.revision, 0) and _blank(self.distance, 0) < _blank(other.distance, 0) and _blank(self.commit, "") < _blank(other.commit, "") and bool(self.dirty) < bool(other.dirty) and _blank(self.tagged_metadata, "") < _blank(other.tagged_metadata, "") and _blank(self.epoch, 0) < _blank(other.epoch, 0) and _blank(self.branch, "") < _blank(other.branch, "") and _blank(self.timestamp, dt.datetime(0, 0, 0, 0, 0, 0)) < _blank(other.timestamp, dt.datetime(0, 0, 0, 0, 0, 0)) ) def serialize( self, metadata: Optional[bool] = None, dirty: bool = False, format: Optional[Union[str, Callable[["Version"], str]]] = None, style: Optional[Style] = None, bump: bool = False, tagged_metadata: bool = False, ) -> str: """ Create a string from the version info. :param metadata: Metadata (commit ID, dirty flag) is normally included in the metadata/local version part only if the distance is nonzero. Set this to True to always include metadata even with no distance, or set it to False to always exclude it. This is ignored when `format` is used. :param dirty: Set this to True to include a dirty flag in the metadata if applicable. Inert when metadata=False. This is ignored when `format` is used. :param format: Custom output format. It is either a formatted string or a callback. In the string you can use substitutions, such as "v{base}" to get "v0.1.0". Available substitutions: * {base} * {stage} * {revision} * {distance} * {commit} * {dirty} which expands to either "dirty" or "clean" * {tagged_metadata} * {epoch} * {branch} * {branch_escaped} which omits any non-letter/number characters * {timestamp} which expands to YYYYmmddHHMMSS as UTC * {major} (first part of `base` split on `.`, or 0) * {minor} (second part of `base` split on `.`, or 0) * {patch} (third part of `base` split on `.`, or 0) :param style: Built-in output formats. Will default to PEP 440 if not set and no custom format given. If you specify both a style and a custom format, then the format will be validated against the style's rules. :param bump: If true, increment the last part of the `base` by 1, unless `stage` is set, in which case either increment `revision` by 1 or set it to a default of 2 if there was no revision. Does nothing when on a commit with a version tag. :param tagged_metadata: If true, insert the `tagged_metadata` in the version as the first part of the metadata segment. This is ignored when `format` is used. :returns: Serialized version. """ base = self.base revision = self.revision if bump: bumped = self.bump(smart=True) base = bumped.base revision = bumped.revision if format is not None: if callable(format): new_version = copy.deepcopy(self) new_version.base = base new_version.revision = revision out = format(new_version) else: try: base_parts = base.split(".") out = format.format( base=base, stage=_blank(self.stage, ""), revision=_blank(revision, ""), distance=_blank(self.distance, ""), commit=_blank(self.commit, ""), tagged_metadata=_blank(self.tagged_metadata, ""), dirty="dirty" if self.dirty else "clean", epoch=_blank(self.epoch, ""), branch=_blank(self.branch, ""), branch_escaped=_escape_branch(_blank(self.branch, "")), timestamp=self.timestamp.strftime("%Y%m%d%H%M%S") if self.timestamp else "", major=base_parts[0] if len(base_parts) > 0 else "0", minor=base_parts[1] if len(base_parts) > 1 else "0", patch=base_parts[2] if len(base_parts) > 2 else "0", ) except KeyError as e: raise KeyError("Format contains invalid placeholder: {}".format(e)) except ValueError as e: raise ValueError("Format is invalid: {}".format(e)) if style is not None: check_version(out, style) return out if style is None: style = Style.Pep440 out = "" meta_parts = [] if metadata is not False: if tagged_metadata and self.tagged_metadata: meta_parts.append(self.tagged_metadata) if (metadata or self.distance > 0) and self.commit is not None: meta_parts.append(self.commit) if dirty and self.dirty: meta_parts.append("dirty") pre_parts = [] if self.stage is not None: pre_parts.append(self.stage) if revision is not None: pre_parts.append(str(revision)) if self.distance > 0: pre_parts.append("pre" if bump or self._smart_bumped else "post") pre_parts.append(str(self.distance)) if style == Style.Pep440: stage = self.stage post = None dev = None if stage == "post": stage = None post = revision elif stage == "dev": stage = None dev = revision if self.distance > 0: if bump or self._smart_bumped: if dev is None: dev = self.distance else: dev += self.distance else: if post is None and dev is None: post = self.distance dev = 0 elif dev is None: dev = self.distance else: dev += self.distance out = serialize_pep440( base, stage=stage, revision=revision, post=post, dev=dev, metadata=meta_parts, epoch=self.epoch, ) elif style == Style.SemVer: out = serialize_semver(base, pre=pre_parts, metadata=meta_parts) elif style == Style.Pvp: out = serialize_pvp(base, metadata=[*pre_parts, *meta_parts]) check_version(out, style) return out @classmethod def parse(cls, version: str, pattern: Union[str, Pattern] = Pattern.Default) -> "Version": """ Attempt to parse a string into a Version instance. This uses inexact heuristics, so its output may vary slightly between releases. Consider this a "best effort" conversion. :param version: Full version, such as 0.3.0a3+d7.gb6a9020.dirty. :param pattern: Regular expression matched against the version. Refer to `from_any_vcs` for more info. :returns: Parsed version. """ normalized = version if not version.startswith("v") and pattern in [VERSION_SOURCE_PATTERN, Pattern.Default]: normalized = "v{}".format(version) failed = False try: matched_pattern = _match_version_pattern(pattern, [normalized], True, strict=True, pattern_prefix=None) except ValueError: failed = True if failed or matched_pattern is None: replaced = re.sub(r"(\.post(\d+)\.dev\d+)", r".dev\2", version, 1) if replaced != version: alt = Version.parse(replaced, pattern) if alt.base != replaced: return alt return cls(version) base = matched_pattern.base stage = matched_pattern.stage_revision distance = None commit = None dirty = None tagged_metadata = matched_pattern.tagged_metadata epoch = matched_pattern.epoch if tagged_metadata: pop = [] # type: list parts = tagged_metadata.split(".") for i, value in enumerate(parts): if dirty is None: if value == "dirty": dirty = True pop.append(i) continue elif value == "clean": dirty = False pop.append(i) continue if distance is None: match = re.match(r"d?(\d+)", value) if match: distance = int(match.group(1)) pop.append(i) continue if commit is None: match = re.match(r"g?([\da-z]+)", value) if match: commit = match.group(1) pop.append(i) continue for i in reversed(sorted(pop)): parts.pop(i) tagged_metadata = ".".join(parts) if distance is None: distance = 0 if tagged_metadata is not None and tagged_metadata.strip() == "": tagged_metadata = None if stage is not None and stage[0] == "dev" and stage[1] is not None: distance += stage[1] stage = None return cls( base, stage=stage, distance=distance, commit=commit, dirty=dirty, tagged_metadata=tagged_metadata, epoch=epoch, ) def bump(self, index: int = -1, increment: int = 1, smart: bool = False) -> "Version": """ Increment the version. The base is bumped unless there is a stage defined, in which case, the revision is bumped instead. :param index: Numerical position to increment in the base. This follows Python indexing rules, so positive numbers start from the left side and count up from 0, while negative numbers start from the right side and count down from -1. Only has an effect when the base is bumped. :param increment: By how much to increment the relevant position. :param smart: If true, only bump when distance is not 0. This will also make `Version.serialize()` use pre-release formatting automatically, like calling `Version.serialize(bump=True)`. :returns: Bumped version. """ bumped = copy.deepcopy(self) if smart: if bumped.distance == 0: return bumped bumped._smart_bumped = True if bumped.stage is None: bumped.base = bump_version(bumped.base, index, increment) else: if bumped.revision is None: bumped.revision = 2 else: bumped.revision += increment return bumped @classmethod def _fallback( cls, strict: bool, *, stage: Optional[Tuple[str, Optional[int]]] = None, distance: int = 0, commit: Optional[str] = None, dirty: Optional[bool] = None, tagged_metadata: Optional[str] = None, epoch: Optional[int] = None, branch: Optional[str] = None, timestamp: Optional[dt.datetime] = None, concerns: Optional[Set[Concern]] = None, # fmt: off vcs: Vcs = Vcs.Any # fmt: on ): if strict: raise RuntimeError("No tags available and fallbacks disabled by strict mode") return cls( "0.0.0", stage=stage, distance=distance, commit=commit, dirty=dirty, tagged_metadata=tagged_metadata, epoch=epoch, branch=branch, timestamp=timestamp, concerns=concerns, vcs=vcs, ) @classmethod def from_git( cls, pattern: Union[str, Pattern] = Pattern.Default, latest_tag: bool = False, tag_branch: Optional[str] = None, full_commit: bool = False, strict: bool = False, path: Optional[Path] = None, pattern_prefix: Optional[str] = None, ignore_untracked: bool = False, commit_length: Optional[int] = None, ) -> "Version": r""" Determine a version based on Git tags. :param pattern: Regular expression matched against the version source. Refer to `from_any_vcs` for more info. :param latest_tag: If true, only inspect the latest tag on the latest tagged commit for a pattern match. If false, keep looking at tags until there is a match. :param tag_branch: Branch on which to find tags, if different than the current branch. :param full_commit: Get the full commit hash instead of the short form. :param strict: Elevate warnings to errors. When there are no tags, fail instead of falling back to 0.0.0. :param path: Directory to inspect, if not the current working directory. :param pattern_prefix: Insert this after the pattern's start anchor (`^`). :param ignore_untracked: Ignore untracked files when determining whether the repository is dirty. :param commit_length: Use this many characters from the start of the full commit hash. :returns: Detected version. """ vcs = Vcs.Git full_commit = full_commit or commit_length is not None archival = _find_higher_file(".git_archival.json", path, ".git") if archival is not None: content = archival.read_text("utf8") if "$Format:" not in content: data = json.loads(content) if full_commit: commit = data.get("hash-full") else: commit = data.get("hash-short") if commit is not None: commit = commit[:commit_length] timestamp = None raw_timestamp = data.get("timestamp") if raw_timestamp: timestamp = _parse_git_timestamp_iso_strict(raw_timestamp) branch = None refs = data.get("refs") if refs: parts = refs.split(" -> ") if len(parts) == 2: branch = parts[1].split(", ")[0] tag = None distance = 0 dirty = None describe = data.get("describe") if describe: if describe.endswith("-dirty"): dirty = True describe = describe[:-6] else: dirty = False parts = describe.rsplit("-", 2) tag = parts[0] if len(parts) > 1: distance = int(parts[1]) if tag is None: return cls._fallback( strict, distance=distance, commit=commit, dirty=dirty, branch=branch, timestamp=timestamp, vcs=vcs, ) matched_pattern = _match_version_pattern(pattern, [tag], latest_tag, strict, pattern_prefix) if matched_pattern is None: return cls._fallback( strict, distance=distance, commit=commit, dirty=dirty, branch=branch, timestamp=timestamp, vcs=vcs, ) tag, base, stage, unmatched, tagged_metadata, epoch = matched_pattern version = cls( base, stage=stage, distance=distance, commit=commit, dirty=dirty, tagged_metadata=tagged_metadata, epoch=epoch, branch=branch, timestamp=timestamp, vcs=vcs, ) version._matched_tag = tag version._newer_unmatched_tags = unmatched return version _detect_vcs(vcs, path) concerns = set() # type: Set[Concern] if tag_branch is None: tag_branch = "HEAD" git_version = _get_git_version() if git_version < [2, 15]: flag_file = _find_higher_file(".git/shallow", path) if flag_file: concerns.add(Concern.ShallowRepository) else: code, msg = _run_cmd("git rev-parse --is-shallow-repository", path) if msg.strip() == "true": concerns.add(Concern.ShallowRepository) if strict and concerns: raise RuntimeError("\n".join(x.message() for x in concerns)) code, msg = _run_cmd("git symbolic-ref --short HEAD", path, codes=[0, 128]) if code == 128: branch = None else: branch = msg code, msg = _run_cmd( '{} -n 1 --format="format:{}"'.format(_git_log(git_version), "%H" if full_commit else "%h"), path, codes=[0, 128], ) if code == 128: return cls._fallback(strict, distance=0, dirty=True, branch=branch, concerns=concerns, vcs=vcs) commit = msg[:commit_length] timestamp = None if git_version < [2, 2]: code, msg = _run_cmd('{} -n 1 --pretty=format:"%ci"'.format(_git_log(git_version)), path) timestamp = _parse_git_timestamp_iso(msg) else: code, msg = _run_cmd('{} -n 1 --pretty=format:"%cI"'.format(_git_log(git_version)), path) timestamp = _parse_git_timestamp_iso_strict(msg) code, msg = _run_cmd("git describe --always --dirty", path) dirty = msg.endswith("-dirty") if not dirty and not ignore_untracked: code, msg = _run_cmd("git status --porcelain", path) if msg.strip() != "": dirty = True if git_version < [2, 7]: code, msg = _run_cmd('git for-each-ref "refs/tags/**" --format "%(refname)" --sort -creatordate', path) if not msg: try: code, msg = _run_cmd("git rev-list --count HEAD", path) distance = int(msg) except Exception: distance = 0 return cls._fallback( strict, distance=distance, commit=commit, dirty=dirty, branch=branch, timestamp=timestamp, concerns=concerns, vcs=vcs, ) tags = [line.replace("refs/tags/", "") for line in msg.splitlines()] matched_pattern = _match_version_pattern(pattern, tags, latest_tag, strict, pattern_prefix) else: code, msg = _run_cmd( 'git for-each-ref "refs/tags/**" --merged {}'.format(tag_branch) + ' --format "%(refname)' "@{%(objectname)" "@{%(creatordate:iso-strict)" "@{%(*committerdate:iso-strict)" "@{%(taggerdate:iso-strict)" '"', path, ) if not msg: try: code, msg = _run_cmd("git rev-list --count HEAD", path) distance = int(msg) except Exception: distance = 0 return cls._fallback( strict, distance=distance, commit=commit, dirty=dirty, branch=branch, timestamp=timestamp, concerns=concerns, vcs=vcs, ) detailed_tags = [] # type: List[_GitRefInfo] tag_topo_lookup = _GitRefInfo.from_git_tag_topo_order(tag_branch, git_version, path) for line in msg.strip().splitlines(): parts = line.split("@{") if len(parts) != 5: continue detailed_tags.append(_GitRefInfo(*parts).with_tag_topo_lookup(tag_topo_lookup)) tags = [t.ref for t in sorted(detailed_tags, key=lambda x: x.sort_key, reverse=True)] matched_pattern = _match_version_pattern(pattern, tags, latest_tag, strict, pattern_prefix) if matched_pattern is None: try: code, msg = _run_cmd("git rev-list --count HEAD", path) distance = int(msg) except Exception: distance = 0 return cls._fallback( strict, distance=distance, commit=commit, dirty=dirty, branch=branch, timestamp=timestamp, concerns=concerns, vcs=vcs, ) tag, base, stage, unmatched, tagged_metadata, epoch = matched_pattern code, msg = _run_cmd("git rev-list --count refs/tags/{}..HEAD".format(tag), path) distance = int(msg) version = cls( base, stage=stage, distance=distance, commit=commit, dirty=dirty, tagged_metadata=tagged_metadata, epoch=epoch, branch=branch, timestamp=timestamp, concerns=concerns, vcs=vcs, ) version._matched_tag = tag version._newer_unmatched_tags = unmatched return version @classmethod def from_mercurial( cls, pattern: Union[str, Pattern] = Pattern.Default, latest_tag: bool = False, full_commit: bool = False, strict: bool = False, path: Optional[Path] = None, pattern_prefix: Optional[str] = None, commit_length: Optional[int] = None, ) -> "Version": r""" Determine a version based on Mercurial tags. :param pattern: Regular expression matched against the version source. Refer to `from_any_vcs` for more info. :param latest_tag: If true, only inspect the latest tag on the latest tagged commit for a pattern match. If false, keep looking at tags until there is a match. :param full_commit: Get the full commit hash instead of the short form. :param strict: Elevate warnings to errors. When there are no tags, fail instead of falling back to 0.0.0. :param path: Directory to inspect, if not the current working directory. :param pattern_prefix: Insert this after the pattern's start anchor (`^`). :param commit_length: Use this many characters from the start of the full commit hash. :returns: Detected version. """ vcs = Vcs.Mercurial full_commit = full_commit or commit_length is not None archival = _find_higher_file(".hg_archival.txt", path, ".hg") if archival is not None: content = archival.read_text("utf8") data = {} for line in content.splitlines(): parts = line.split(":", 1) if len(parts) != 2: continue data[parts[0].strip()] = parts[1].strip() tag = data.get("latesttag") # The distance is 1 on a new repo or on a tagged commit. distance = int(data.get("latesttagdistance", 1)) - 1 commit = data.get("node") if commit is not None: commit = commit[:commit_length] branch = data.get("branch") if tag is None or tag == "null": return cls._fallback(strict, distance=distance, commit=commit, branch=branch, vcs=vcs) all_tags_file = _find_higher_file(".hgtags", path, ".hg") if all_tags_file is None: all_tags = [tag] else: all_tags = [] all_tags_content = all_tags_file.read_text("utf8") for line in reversed(all_tags_content.splitlines()): parts = line.split(" ", 1) if len(parts) != 2: continue all_tags.append(parts[1]) matched_pattern = _match_version_pattern(pattern, all_tags, latest_tag, strict, pattern_prefix) if matched_pattern is None: return cls._fallback( strict, distance=distance, commit=commit, branch=branch, vcs=vcs, ) tag, base, stage, unmatched, tagged_metadata, epoch = matched_pattern version = cls( base, stage=stage, distance=distance, commit=commit, tagged_metadata=tagged_metadata, epoch=epoch, branch=branch, vcs=vcs, ) version._matched_tag = tag version._newer_unmatched_tags = unmatched return version _detect_vcs(vcs, path) code, msg = _run_cmd("hg summary", path) dirty = "commit: (clean)" not in msg.splitlines() code, msg = _run_cmd("hg branch", path) branch = msg code, msg = _run_cmd('hg id --template "{}"'.format("{id}" if full_commit else "{id|short}"), path) commit = msg[:commit_length] if set(msg) != {"0"} else None code, msg = _run_cmd('hg log --limit 1 --template "{date|rfc3339date}"', path) timestamp = _parse_git_timestamp_iso_strict(msg) if msg != "" else None code, msg = _run_cmd( 'hg log -r "sort(tag(){}, -rev)" --template "{{join(tags, \':\')}}\\n"'.format( " and ancestors({})".format(commit) if commit is not None else "" ), path, ) if not msg: try: code, msg = _run_cmd("hg id --num --rev tip", path) distance = int(msg) + 1 except Exception: distance = 0 return cls._fallback( strict, distance=distance, commit=commit, dirty=dirty, branch=branch, timestamp=timestamp, vcs=vcs, ) tags = [tag for tags in [line.split(":") for line in msg.splitlines()] for tag in tags] matched_pattern = _match_version_pattern(pattern, tags, latest_tag, strict, pattern_prefix) if matched_pattern is None: return cls._fallback( strict, distance=distance, commit=commit, dirty=dirty, branch=branch, timestamp=timestamp, vcs=vcs, ) tag, base, stage, unmatched, tagged_metadata, epoch = matched_pattern code, msg = _run_cmd('hg log -r "{0}::{1} - {0}" --template "."'.format(tag, commit), path) # The tag itself is in the list, so offset by 1. distance = max(len(msg) - 1, 0) version = cls( base, stage=stage, distance=distance, commit=commit, dirty=dirty, tagged_metadata=tagged_metadata, epoch=epoch, branch=branch, timestamp=timestamp, vcs=vcs, ) version._matched_tag = tag version._newer_unmatched_tags = unmatched return version @classmethod def from_darcs( cls, pattern: Union[str, Pattern] = Pattern.Default, latest_tag: bool = False, strict: bool = False, path: Optional[Path] = None, pattern_prefix: Optional[str] = None, commit_length: Optional[int] = None, ) -> "Version": r""" Determine a version based on Darcs tags. :param pattern: Regular expression matched against the version source. Refer to `from_any_vcs` for more info. :param latest_tag: If true, only inspect the latest tag on the latest tagged commit for a pattern match. If false, keep looking at tags until there is a match. :param strict: Elevate warnings to errors. When there are no tags, fail instead of falling back to 0.0.0. :param path: Directory to inspect, if not the current working directory. :param pattern_prefix: Insert this after the pattern's start anchor (`^`). :param commit_length: Use this many characters from the start of the full commit hash. :returns: Detected version. """ vcs = Vcs.Darcs _detect_vcs(vcs, path) code, msg = _run_cmd("darcs status", path, codes=[0, 1]) dirty = msg != "No changes!" code, msg = _run_cmd("darcs log --last 1 --xml-output", path) root = ElementTree.fromstring(msg) if len(root) == 0: commit = None timestamp = None else: commit = root[0].attrib["hash"] if commit is not None: commit = commit[:commit_length] timestamp = dt.datetime.strptime(root[0].attrib["date"] + "+0000", "%Y%m%d%H%M%S%z") code, msg = _run_cmd("darcs show tags", path) if not msg: try: code, msg = _run_cmd("darcs log --count", path) distance = int(msg) except Exception: distance = 0 return cls._fallback(strict, distance=distance, commit=commit, dirty=dirty, timestamp=timestamp, vcs=vcs) tags = msg.splitlines() matched_pattern = _match_version_pattern(pattern, tags, latest_tag, strict, pattern_prefix) if matched_pattern is None: return cls._fallback( strict, distance=distance, commit=commit, dirty=dirty, timestamp=timestamp, vcs=vcs, ) tag, base, stage, unmatched, tagged_metadata, epoch = matched_pattern code, msg = _run_cmd("darcs log --from-tag {} --count".format(tag), path) # The tag itself is in the list, so offset by 1. distance = int(msg) - 1 version = cls( base, stage=stage, distance=distance, commit=commit, dirty=dirty, tagged_metadata=tagged_metadata, epoch=epoch, timestamp=timestamp, vcs=vcs, ) version._matched_tag = tag version._newer_unmatched_tags = unmatched return version @classmethod def from_subversion( cls, pattern: Union[str, Pattern] = Pattern.Default, latest_tag: bool = False, tag_dir: str = "tags", strict: bool = False, path: Optional[Path] = None, pattern_prefix: Optional[str] = None, commit_length: Optional[int] = None, ) -> "Version": r""" Determine a version based on Subversion tags. :param pattern: Regular expression matched against the version source. Refer to `from_any_vcs` for more info. :param latest_tag: If true, only inspect the latest tag on the latest tagged commit for a pattern match. If false, keep looking at tags until there is a match. :param tag_dir: Location of tags relative to the root. :param strict: Elevate warnings to errors. When there are no tags, fail instead of falling back to 0.0.0. :param path: Directory to inspect, if not the current working directory. :param pattern_prefix: Insert this after the pattern's start anchor (`^`). :param commit_length: Use this many characters from the start of the full commit hash. :returns: Detected version. """ vcs = Vcs.Subversion _detect_vcs(vcs, path) tag_dir = tag_dir.strip("/") code, msg = _run_cmd("svn status", path) dirty = bool(msg) code, msg = _run_cmd("svn info --show-item repos-root-url", path) url = msg.strip("/") code, msg = _run_cmd("svn info --show-item revision", path) if not msg or msg == "0": commit = None else: commit = msg[:commit_length] timestamp = None if commit: code, msg = _run_cmd("svn info --show-item last-changed-date", path) timestamp = _parse_timestamp(msg) if not commit: return cls._fallback(strict, distance=0, commit=commit, dirty=dirty, timestamp=timestamp, vcs=vcs) code, msg = _run_cmd('svn ls -v -r {} "{}/{}"'.format(commit, url, tag_dir), path) lines = [line.split(maxsplit=5) for line in msg.splitlines()[1:]] tags_to_revs = {line[-1].strip("/"): int(line[0]) for line in lines} if not tags_to_revs: try: distance = int(commit) except Exception: distance = 0 return cls._fallback(strict, distance=distance, commit=commit, dirty=dirty, timestamp=timestamp, vcs=vcs) tags_to_sources_revs = {} for tag, rev in tags_to_revs.items(): code, msg = _run_cmd('svn log -v "{}/{}/{}" --stop-on-copy'.format(url, tag_dir, tag), path) for line in msg.splitlines(): match = re.search(r"A /{}/{} \(from .+?:(\d+)\)".format(tag_dir, tag), line) if match: source = int(match.group(1)) tags_to_sources_revs[tag] = (source, rev) tags = sorted(tags_to_sources_revs, key=lambda x: tags_to_sources_revs[x], reverse=True) matched_pattern = _match_version_pattern(pattern, tags, latest_tag, strict, pattern_prefix) if matched_pattern is None: return cls._fallback( strict, distance=distance, commit=commit, dirty=dirty, timestamp=timestamp, vcs=vcs, ) tag, base, stage, unmatched, tagged_metadata, epoch = matched_pattern source, rev = tags_to_sources_revs[tag] # The tag itself is in the list, so offset by 1. distance = int(commit) - 1 - source version = cls( base, stage=stage, distance=distance, commit=commit, dirty=dirty, tagged_metadata=tagged_metadata, epoch=epoch, timestamp=timestamp, vcs=vcs, ) version._matched_tag = tag version._newer_unmatched_tags = unmatched return version @classmethod def from_bazaar( cls, pattern: Union[str, Pattern] = Pattern.Default, latest_tag: bool = False, strict: bool = False, path: Optional[Path] = None, pattern_prefix: Optional[str] = None, commit_length: Optional[int] = None, ) -> "Version": r""" Determine a version based on Bazaar tags. :param pattern: Regular expression matched against the version source. Refer to `from_any_vcs` for more info. :param latest_tag: If true, only inspect the latest tag on the latest tagged commit for a pattern match. If false, keep looking at tags until there is a match. :param strict: Elevate warnings to errors. When there are no tags, fail instead of falling back to 0.0.0. :param path: Directory to inspect, if not the current working directory. :param pattern_prefix: Insert this after the pattern's start anchor (`^`). :param commit_length: Use this many characters from the start of the full commit hash. :returns: Detected version. """ vcs = Vcs.Bazaar _detect_vcs(vcs, path) code, msg = _run_cmd("bzr status", path) dirty = msg != "" code, msg = _run_cmd("bzr log --limit 1", path) commit = None branch = None timestamp = None for line in msg.splitlines(): info = line.split("revno: ", maxsplit=1) if len(info) == 2: commit = info[1][:commit_length] info = line.split("branch nick: ", maxsplit=1) if len(info) == 2: branch = info[1] info = line.split("timestamp: ", maxsplit=1) if len(info) == 2: timestamp = dt.datetime.strptime(info[1], "%a %Y-%m-%d %H:%M:%S %z") code, msg = _run_cmd("bzr tags", path) if not msg or not commit: try: distance = int(commit) if commit is not None else 0 except Exception: distance = 0 return cls._fallback( strict, distance=distance, commit=commit, dirty=dirty, branch=branch, timestamp=timestamp, vcs=vcs, ) tags_to_revs = {line.split()[0]: int(line.split()[1]) for line in msg.splitlines() if line.split()[1] != "?"} tags = [x[1] for x in sorted([(v, k) for k, v in tags_to_revs.items()], reverse=True)] matched_pattern = _match_version_pattern(pattern, tags, latest_tag, strict, pattern_prefix) if matched_pattern is None: return cls._fallback( strict, distance=distance, commit=commit, dirty=dirty, branch=branch, timestamp=timestamp, vcs=vcs, ) tag, base, stage, unmatched, tagged_metadata, epoch = matched_pattern distance = int(commit) - tags_to_revs[tag] version = cls( base, stage=stage, distance=distance, commit=commit, dirty=dirty, tagged_metadata=tagged_metadata, epoch=epoch, branch=branch, timestamp=timestamp, vcs=vcs, ) version._matched_tag = tag version._newer_unmatched_tags = unmatched return version @classmethod def from_fossil( cls, pattern: Union[str, Pattern] = Pattern.Default, latest_tag: bool = False, strict: bool = False, path: Optional[Path] = None, pattern_prefix: Optional[str] = None, commit_length: Optional[int] = None, ) -> "Version": r""" Determine a version based on Fossil tags. :param pattern: Regular expression matched against the version source. Refer to `from_any_vcs` for more info. :param latest_tag: If true, only inspect the latest tag for a pattern match. If false, keep looking at tags until there is a match. :param strict: Elevate warnings to errors. When there are no tags, fail instead of falling back to 0.0.0. :param path: Directory to inspect, if not the current working directory. :param pattern_prefix: Insert this after the pattern's start anchor (`^`). :param commit_length: Use this many characters from the start of the full commit hash. :returns: Detected version. """ vcs = Vcs.Fossil _detect_vcs(vcs, path) code, msg = _run_cmd("fossil changes --differ", path) dirty = bool(msg) code, msg = _run_cmd("fossil branch current", path) branch = msg code, msg = _run_cmd("fossil sql \"SELECT value FROM vvar WHERE name = 'checkout-hash' LIMIT 1\"", path) commit = msg.strip("'")[:commit_length] code, msg = _run_cmd( 'fossil sql "' "SELECT DATETIME(mtime) FROM event JOIN blob ON event.objid=blob.rid WHERE type = 'ci'" " AND uuid = (SELECT value FROM vvar WHERE name = 'checkout-hash' LIMIT 1) LIMIT 1\"", path, ) timestamp = dt.datetime.strptime(msg.strip("'") + "+0000", "%Y-%m-%d %H:%M:%S%z") code, msg = _run_cmd("fossil sql \"SELECT count() FROM event WHERE type = 'ci'\"", path) # The repository creation itself counts as a commit. total_commits = int(msg) - 1 if total_commits <= 0: return cls._fallback( strict, distance=0, commit=commit, dirty=dirty, branch=branch, timestamp=timestamp, vcs=vcs, ) # Based on `compute_direct_ancestors` from descendants.c in the # Fossil source code: query = """ CREATE TEMP TABLE IF NOT EXISTS dunamai_ancestor( rid INTEGER UNIQUE NOT NULL, generation INTEGER PRIMARY KEY ); DELETE FROM dunamai_ancestor; WITH RECURSIVE g(x, i) AS ( VALUES((SELECT value FROM vvar WHERE name = 'checkout' LIMIT 1), 1) UNION ALL SELECT plink.pid, g.i + 1 FROM plink, g WHERE plink.cid = g.x AND plink.isprim ) INSERT INTO dunamai_ancestor(rid, generation) SELECT x, i FROM g; SELECT tag.tagname, dunamai_ancestor.generation FROM tag JOIN tagxref ON tag.tagid = tagxref.tagid JOIN event ON tagxref.origid = event.objid JOIN dunamai_ancestor ON tagxref.origid = dunamai_ancestor.rid WHERE tagxref.tagtype = 1 ORDER BY event.mtime DESC, tagxref.mtime DESC; """ code, msg = _run_cmd('fossil sql "{}"'.format(" ".join(query.splitlines())), path) if not msg: try: distance = int(total_commits) except Exception: distance = 0 return cls._fallback( strict, distance=distance, commit=commit, dirty=dirty, branch=branch, timestamp=timestamp, vcs=vcs, ) tags_to_distance = [ (line.rsplit(",", 1)[0][5:-1], int(line.rsplit(",", 1)[1]) - 1) for line in msg.splitlines() ] matched_pattern = _match_version_pattern( pattern, [t for t, d in tags_to_distance], latest_tag, strict, pattern_prefix ) if matched_pattern is None: return cls._fallback( strict, distance=distance, commit=commit, dirty=dirty, branch=branch, timestamp=timestamp, vcs=vcs, ) tag, base, stage, unmatched, tagged_metadata, epoch = matched_pattern distance = dict(tags_to_distance)[tag] version = cls( base, stage=stage, distance=distance, commit=commit, dirty=dirty, tagged_metadata=tagged_metadata, epoch=epoch, branch=branch, timestamp=timestamp, vcs=vcs, ) version._matched_tag = tag version._newer_unmatched_tags = unmatched return version @classmethod def from_pijul( cls, pattern: Union[str, Pattern] = Pattern.Default, latest_tag: bool = False, strict: bool = False, path: Optional[Path] = None, pattern_prefix: Optional[str] = None, commit_length: Optional[int] = None, ) -> "Version": r""" Determine a version based on Pijul tags. :param pattern: Regular expression matched against the version source. Refer to `from_any_vcs` for more info. :param latest_tag: If true, only inspect the latest tag on the latest tagged commit for a pattern match. If false, keep looking at tags until there is a match. :param strict: Elevate warnings to errors. When there are no tags, fail instead of falling back to 0.0.0. :param path: Directory to inspect, if not the current working directory. :param pattern_prefix: Insert this after the pattern's start anchor (`^`). :param commit_length: Use this many characters from the start of the full commit hash. :returns: Detected version. """ vcs = Vcs.Pijul _detect_vcs(vcs, path) code, msg = _run_cmd("pijul diff --short", path) dirty = msg.strip() != "" code, msg = _run_cmd("pijul channel", path) branch = "main" for line in msg.splitlines(): if line.startswith("* "): branch = line.split("* ", 1)[1] break code, msg = _run_cmd("pijul log --limit 1 --output-format json", path) limited_commits = json.loads(msg) if len(limited_commits) == 0: return cls._fallback(strict, dirty=dirty, branch=branch, vcs=vcs) commit = limited_commits[0]["hash"][:commit_length] timestamp = _parse_timestamp(limited_commits[0]["timestamp"]) code, msg = _run_cmd("pijul log --output-format json", path) channel_commits = json.loads(msg) code, msg = _run_cmd("pijul tag", path) if not msg: # The channel creation is in the list, so offset by 1. distance = len(channel_commits) - 1 return cls._fallback( strict, distance=distance, commit=commit, dirty=dirty, branch=branch, timestamp=timestamp, vcs=vcs, ) tag_meta = [] # type: List[dict] tag_state = "" tag_timestamp = dt.datetime.now() tag_message = "" tag_after_header = False for line in msg.splitlines(): if not tag_after_header: if line.startswith("State "): tag_state = line.split("State ", 1)[1] elif line.startswith("Date:"): tag_timestamp = _parse_timestamp( line.split("Date: ", 1)[1].replace(" UTC", "Z"), format=_TIMESTAMP_GENERIC_SPACE ) elif line.startswith(" "): tag_message += line[4:] tag_after_header = True else: if line.startswith("State "): tag_meta.append( { "state": tag_state, "timestamp": tag_timestamp, "message": tag_message.strip(), } ) tag_state = line.split("State ", 1)[1] tag_timestamp = dt.datetime.now() tag_message = "" tag_after_header = False else: tag_message += line if tag_after_header: tag_meta.append({"state": tag_state, "timestamp": tag_timestamp, "message": tag_message.strip()}) tag_meta_by_msg = {} # type: dict for meta in tag_meta: if ( meta["message"] not in tag_meta_by_msg or meta["timestamp"] > tag_meta_by_msg[meta["message"]]["timestamp"] ): tag_meta_by_msg[meta["message"]] = meta tags = [t["message"] for t in sorted(tag_meta_by_msg.values(), key=lambda x: x["timestamp"], reverse=True)] matched_pattern = _match_version_pattern(pattern, tags, latest_tag, strict, pattern_prefix) if matched_pattern is None: return cls._fallback( strict, distance=distance, commit=commit, dirty=dirty, branch=branch, timestamp=timestamp, vcs=vcs, ) tag, base, stage, unmatched, tagged_metadata, epoch = matched_pattern tag_id = tag_meta_by_msg[tag]["state"] _run_cmd("pijul tag checkout {}".format(tag_id), path, codes=[0, 1]) code, msg = _run_cmd("pijul log --output-format json --channel {}".format(tag_id), path) if msg.strip() == "": tag_commits = [] # type: list else: tag_commits = json.loads(msg) distance = len(channel_commits) - len(tag_commits) version = cls( base, stage=stage, distance=distance, commit=commit, dirty=dirty, tagged_metadata=tagged_metadata, epoch=epoch, branch=branch, timestamp=timestamp, vcs=vcs, ) version._matched_tag = tag version._newer_unmatched_tags = unmatched return version @classmethod def from_any_vcs( cls, pattern: Union[str, Pattern] = Pattern.Default, latest_tag: bool = False, tag_dir: str = "tags", tag_branch: Optional[str] = None, full_commit: bool = False, strict: bool = False, path: Optional[Path] = None, pattern_prefix: Optional[str] = None, ignore_untracked: bool = False, commit_length: Optional[int] = None, ) -> "Version": r""" Determine a version based on a detected version control system. :param pattern: Regular expression matched against the version source. This must contain one capture group named `base` corresponding to the release segment of the source. Optionally, it may contain another two groups named `stage` and `revision` corresponding to a prerelease type (such as 'alpha' or 'rc') and number (such as in 'alpha-2' or 'rc3'). It may also contain a group named `tagged_metadata` corresponding to extra metadata after the main part of the version (typically after a plus sign). There may also be a group named `epoch` for the PEP 440 concept. If the `base` group is not included, then this will be interpreted as the name of a variant of the `Pattern` enum. For example, passing `"default"` is the same as passing `Pattern.Default`. :param latest_tag: If true, only inspect the latest tag on the latest tagged commit for a pattern match. If false, keep looking at tags until there is a match. :param tag_dir: Location of tags relative to the root. This is only used for Subversion. :param tag_branch: Branch on which to find tags, if different than the current branch. This is only used for Git currently. :param full_commit: Get the full commit hash instead of the short form. This is only used for Git and Mercurial. :param strict: Elevate warnings to errors. When there are no tags, fail instead of falling back to 0.0.0. :param path: Directory to inspect, if not the current working directory. :param pattern_prefix: Insert this after the pattern's start anchor (`^`). :param ignore_untracked: Ignore untracked files when determining whether the repository is dirty. This is only used for Git currently. :param commit_length: Use this many characters from the start of the full commit hash. :returns: Detected version. """ vcs = _detect_vcs_from_archival(path) if vcs is None: vcs = _detect_vcs(None, path) return cls._do_vcs_callback( vcs, pattern, latest_tag, tag_dir, tag_branch, full_commit, strict, path, pattern_prefix, ignore_untracked, commit_length, ) @classmethod def from_vcs( cls, vcs: Vcs, pattern: Union[str, Pattern] = Pattern.Default, latest_tag: bool = False, tag_dir: str = "tags", tag_branch: Optional[str] = None, full_commit: bool = False, strict: bool = False, path: Optional[Path] = None, pattern_prefix: Optional[str] = None, ignore_untracked: bool = False, commit_length: Optional[int] = None, ) -> "Version": r""" Determine a version based on a specific VCS setting. This is primarily intended for other tools that want to generically use some VCS setting based on user configuration, without having to maintain a mapping from the VCS name to the appropriate function. :param pattern: Regular expression matched against the version source. Refer to `from_any_vcs` for more info. :param latest_tag: If true, only inspect the latest tag on the latest tagged commit for a pattern match. If false, keep looking at tags until there is a match. :param tag_dir: Location of tags relative to the root. This is only used for Subversion. :param tag_branch: Branch on which to find tags, if different than the current branch. This is only used for Git currently. :param full_commit: Get the full commit hash instead of the short form. This is only used for Git and Mercurial. :param strict: Elevate warnings to errors. When there are no tags, fail instead of falling back to 0.0.0. :param path: Directory to inspect, if not the current working directory. :param pattern_prefix: Insert this after the pattern's start anchor (`^`). :param ignore_untracked: Ignore untracked files when determining whether the repository is dirty. This is only used for Git currently. :param commit_length: Use this many characters from the start of the full commit hash. :returns: Detected version. """ return cls._do_vcs_callback( vcs, pattern, latest_tag, tag_dir, tag_branch, full_commit, strict, path, pattern_prefix, ignore_untracked, commit_length, ) @classmethod def _do_vcs_callback( cls, vcs: Vcs, pattern: Union[str, Pattern], latest_tag: bool, tag_dir: str, tag_branch: Optional[str], full_commit: bool, strict: bool, path: Optional[Path], pattern_prefix: Optional[str] = None, ignore_untracked: bool = False, commit_length: Optional[int] = None, ) -> "Version": mapping = { Vcs.Any: cls.from_any_vcs, Vcs.Git: cls.from_git, Vcs.Mercurial: cls.from_mercurial, Vcs.Darcs: cls.from_darcs, Vcs.Subversion: cls.from_subversion, Vcs.Bazaar: cls.from_bazaar, Vcs.Fossil: cls.from_fossil, Vcs.Pijul: cls.from_pijul, } # type: Mapping[Vcs, Callable[..., "Version"]] kwargs = {} callback = mapping[vcs] for kwarg, value in [ ("pattern", pattern), ("latest_tag", latest_tag), ("tag_dir", tag_dir), ("tag_branch", tag_branch), ("full_commit", full_commit), ("strict", strict), ("path", path), ("pattern_prefix", pattern_prefix), ("ignore_untracked", ignore_untracked), ("commit_length", commit_length), ]: if kwarg in inspect.getfullargspec(callback).args: kwargs[kwarg] = value return callback(**kwargs) def check_version(version: str, style: Style = Style.Pep440) -> None: """ Check if a version is valid for a style. :param version: Version to check. :param style: Style against which to check. :raises ValueError: If the version is invalid. """ name, pattern = { Style.Pep440: ("PEP 440", _VALID_PEP440), Style.SemVer: ("Semantic Versioning", _VALID_SEMVER), Style.Pvp: ("PVP", _VALID_PVP), }[style] failure_message = "Version '{}' does not conform to the {} style".format(version, name) if not re.search(pattern, version): raise ValueError(failure_message) if style == Style.SemVer: parts = re.split(r"[.-]", version.split("+", 1)[0]) if any(re.search(r"^0[0-9]+$", x) for x in parts): raise ValueError(failure_message) def get_version( name: str, first_choice: Optional[Callable[[], Optional[Version]]] = None, third_choice: Optional[Callable[[], Optional[Version]]] = None, fallback: Version = Version("0.0.0"), ignore: Optional[Sequence[Version]] = None, parser: Callable[[str], Version] = Version, ) -> Version: """ Check importlib-metadata info or a fallback function to determine the version. This is intended as a convenient default for setting your `__version__` if you do not want to include a generated version statically during packaging. :param name: Installed package name. :param first_choice: Callback to determine a version before checking to see if the named package is installed. :param third_choice: Callback to determine a version if the installed package cannot be found by name. :param fallback: If no other matches found, use this version. :param ignore: Ignore a found version if it is part of this list. When comparing the found version to an ignored one, fields with None in the ignored version are not taken into account. If the ignored version has distance=0, then that field is also ignored. :param parser: Callback to convert a string into a Version instance. This will be used for the second choice. For example, you can pass `Version.parse` here. :returns: First available version. """ if ignore is None: ignore = [] if first_choice: first_ver = first_choice() if first_ver and not any(first_ver._matches_partial(v) for v in ignore): return first_ver try: import importlib.metadata as ilm except ImportError: import importlib_metadata as ilm # type: ignore try: ilm_version = parser(ilm.version(name)) if not any(ilm_version._matches_partial(v) for v in ignore): return ilm_version except ilm.PackageNotFoundError: pass if third_choice: third_ver = third_choice() if third_ver and not any(third_ver._matches_partial(v) for v in ignore): return third_ver return fallback def serialize_pep440( base: str, stage: Optional[str] = None, revision: Optional[int] = None, post: Optional[int] = None, dev: Optional[int] = None, epoch: Optional[int] = None, metadata: Optional[Sequence[Union[str, int]]] = None, ) -> str: """ Serialize a version based on PEP 440. Use this instead of `Version.serialize()` if you want more control over how the version is mapped. :param base: Release segment, such as 0.1.0. :param stage: Pre-release stage ("a", "b", or "rc"). :param revision: Pre-release revision (e.g., 1 as in "rc1"). This is ignored when `stage` is None. :param post: Post-release number. :param dev: Developmental release number. :param epoch: Epoch number. :param metadata: Any local version label segments. :returns: Serialized version. """ out = [] # type: list if epoch is not None: out.extend([epoch, "!"]) out.append(base) if stage is not None: alternative_stages = {"alpha": "a", "beta": "b", "c": "rc", "pre": "rc", "preview": "rc"} out.append(alternative_stages.get(stage.lower(), stage.lower())) if revision is None: # PEP 440 does not allow omitting the revision, so assume 0. out.append(0) else: out.append(revision) if post is not None: out.extend([".post", post]) if dev is not None: out.extend([".dev", dev]) if metadata is not None and len(metadata) > 0: out.extend(["+", ".".join(map(str, metadata))]) serialized = "".join(map(str, out)) check_version(serialized, Style.Pep440) return serialized def serialize_semver( base: str, pre: Optional[Sequence[Union[str, int]]] = None, metadata: Optional[Sequence[Union[str, int]]] = None, ) -> str: """ Serialize a version based on Semantic Versioning. Use this instead of `Version.serialize()` if you want more control over how the version is mapped. :param base: Version core, such as 0.1.0. :param pre: Pre-release identifiers. :param metadata: Build metadata identifiers. :returns: Serialized version. """ out = [base] if pre is not None and len(pre) > 0: out.extend(["-", ".".join(map(str, pre))]) if metadata is not None and len(metadata) > 0: out.extend(["+", ".".join(map(str, metadata))]) serialized = "".join(str(x) for x in out) check_version(serialized, Style.SemVer) return serialized def serialize_pvp(base: str, metadata: Optional[Sequence[Union[str, int]]] = None) -> str: """ Serialize a version based on the Haskell Package Versioning Policy. Use this instead of `Version.serialize()` if you want more control over how the version is mapped. :param base: Version core, such as 0.1.0. :param metadata: Version tag metadata. :returns: Serialized version. """ out = [base] if metadata is not None and len(metadata) > 0: out.extend(["-", "-".join(map(str, metadata))]) serialized = "".join(map(str, out)) check_version(serialized, Style.Pvp) return serialized def bump_version(base: str, index: int = -1, increment: int = 1) -> str: """ Increment one of the numerical positions of a version. :param base: Version core, such as 0.1.0. Do not include pre-release identifiers. :param index: Numerical position to increment. This follows Python indexing rules, so positive numbers start from the left side and count up from 0, while negative numbers start from the right side and count down from -1. :param increment: By how much the `index` needs to increment. :returns: Bumped version. """ bases = [int(x) for x in base.split(".")] bases[index] += increment limit = 0 if index < 0 else len(bases) i = index + 1 while i < limit: bases[i] = 0 i += 1 return ".".join(str(x) for x in bases) def _parse_git_timestamp_iso_strict(raw: str) -> dt.datetime: # Remove colon from timezone offset for pre-3.7 Python: compat = re.sub(r"(.*T.*[-+]\d+):(\d+)", r"\1\2", raw) return _parse_timestamp(compat, _TIMESTAMP_GIT_ISO_STRICT) def _parse_git_timestamp_iso(raw: str) -> dt.datetime: # Remove colon from timezone offset for pre-3.7 Python: compat = re.sub(r"(.* .* [-+]\d+):(\d+)", r"\1\2", raw) return _parse_timestamp(compat, _TIMESTAMP_GIT_ISO) def _parse_timestamp(raw: str, format: str = _TIMESTAMP_GENERIC) -> dt.datetime: # Normalize "Z" for pre-3.7 compatibility: normalized = re.sub(r"Z$", "+0000", raw) # Truncate %f to six digits: normalized = re.sub(r"\.(\d{6})\d+\+0000", r".\g<1>+0000", normalized) return dt.datetime.strptime(normalized, format) __version__ = get_version("dunamai").serialize() dunamai-1.23.0/dunamai/__main__.py000066400000000000000000000266011471650161100167320ustar00rootroot00000000000000import argparse import sys from pathlib import Path from typing import Mapping, Optional from dunamai import check_version, Version, Pattern, Style, Vcs, VERSION_SOURCE_PATTERN common_sub_args = [ { "triggers": ["--metadata"], "action": "store_true", "dest": "metadata", "default": None, "help": "Always include metadata. Ignored when --format is used", }, { "triggers": ["--no-metadata"], "action": "store_false", "dest": "metadata", "default": None, "help": "Never include metadata. Ignored when --format is used", }, { "triggers": ["--dirty"], "action": "store_true", "dest": "dirty", "help": "Include dirty flag if applicable. Ignored when --format is used", }, { "vcs": [Vcs.Git], "triggers": ["--ignore-untracked"], "action": "store_true", "dest": "ignore_untracked", "help": "Ignore untracked files when determining whether the repository is dirty", }, { "triggers": ["--tagged-metadata"], "action": "store_true", "dest": "tagged_metadata", "help": "Include tagged metadata if applicable. Ignored when --format is used", }, { "triggers": ["--pattern"], "default": VERSION_SOURCE_PATTERN, "help": ( "Regular expression matched against the version source." " This must contain one capture group named `base` corresponding to" " the release segment of the source." " Optionally, it may contain another two groups named `stage` and `revision`" " corresponding to a prerelease type (such as 'alpha' or 'rc') and number" " (such as in 'alpha-2' or 'rc3')." " It may also contain a group named `tagged_metadata` corresponding to extra" " metadata after the main part of the version (typically after a plus sign)." " There may also be a group named `epoch` for the PEP 440 concept." " If the `base` group is not present, then instead this will be interpreted" " as a named preset, which may be one of the following: {}" ).format(", ".join(["`{}`".format(x.value) for x in Pattern])), }, { "triggers": ["--pattern-prefix"], "help": "Insert this after the pattern's start anchor (`^`).", }, { "triggers": ["--format"], "help": ( "Custom output format. Available substitutions:" " {base}, {stage}, {revision}, {distance}, {commit}, {dirty}," " {tagged_metadata}, {epoch}, {branch}, {branch_escaped}, {timestamp}," " {major}, {minor}, {patch}" ), }, { "triggers": ["--style"], "choices": [x.value for x in Style], "help": ( "Preconfigured output format." " Will default to PEP 440 if not set and no custom format given." " If you specify both a style and a custom format, then the format" " will be validated against the style's rules" ), }, { "triggers": ["--latest-tag"], "action": "store_true", "dest": "latest_tag", "default": False, "help": "Only inspect the latest tag on the latest tagged commit for a pattern match", }, { "triggers": ["--strict"], "action": "store_true", "dest": "strict", "default": False, "help": ("Elevate warnings to errors. When there are no tags, fail instead of falling back to 0.0.0"), }, { "triggers": ["--path"], "help": "Directory to inspect, if not the current working directory", }, { "triggers": ["--debug"], "action": "store_true", "dest": "debug", "default": False, "help": "Display additional information on stderr for troubleshooting", }, { "triggers": ["--bump"], "action": "store_true", "dest": "bump", "default": False, "help": ( "Increment the last part of the version `base` by 1," " unless the `stage` is set, in which case increment the `revision`" " by 1 or set it to a default of 2 if there was no `revision`" " Does nothing when on a commit with a version tag." ), }, { "vcs": [Vcs.Git, Vcs.Mercurial], "triggers": ["--full-commit"], "action": "store_true", "dest": "full_commit", "default": False, "help": "Get the full commit hash instead of the short form", }, { "triggers": ["--commit-length"], "dest": "commit_length", "type": int, "help": "Use this many characters from the start of the full commit hash", }, { "vcs": [Vcs.Git], "triggers": ["--tag-branch"], "help": "Branch on which to find tags, if different than the current branch", }, { "vcs": [Vcs.Subversion], "triggers": ["--tag-dir"], "default": "tags", "help": "Location of tags relative to the root", }, ] cli_spec = { "description": "Generate dynamic versions", "sub_dest": "command", "sub": { "from": { "description": "Generate version from a particular VCS", "sub_dest": "vcs", "sub": { Vcs.Any.value: { "description": "Generate version from any detected VCS", "args": common_sub_args, }, Vcs.Git.value: { "description": "Generate version from Git", "args": common_sub_args, }, Vcs.Mercurial.value: { "description": "Generate version from Mercurial", "args": common_sub_args, }, Vcs.Darcs.value: { "description": "Generate version from Darcs", "args": common_sub_args, }, Vcs.Subversion.value: { "description": "Generate version from Subversion", "args": common_sub_args, }, Vcs.Bazaar.value: { "description": "Generate version from Bazaar", "args": common_sub_args, }, Vcs.Fossil.value: { "description": "Generate version from Fossil", "args": common_sub_args, }, Vcs.Pijul.value: { "description": "Generate version from Pijul", "args": common_sub_args, }, }, }, "check": { "description": "Check if a version is valid for a style", "args": [ { "triggers": [], "dest": "version", "help": "Version to check; may be piped in", "nargs": "?", }, { "triggers": ["--style"], "choices": [x.value for x in Style], "default": Style.Pep440.value, "help": "Style against which to check", }, ], }, }, } def build_parser( spec: Mapping, parser: Optional[argparse.ArgumentParser] = None, vcs: Optional[Vcs] = None ) -> argparse.ArgumentParser: if parser is None: parser = argparse.ArgumentParser( description=spec["description"], formatter_class=argparse.ArgumentDefaultsHelpFormatter ) if "args" in spec: for arg in spec["args"]: help = arg["help"] if "vcs" in arg: if vcs not in [*arg["vcs"], Vcs.Any]: continue help += " (only: {})".format(", ".join([x.name for x in arg["vcs"]])) triggers = arg["triggers"] parser.add_argument( *triggers, help=help, **{k: v for k, v in arg.items() if k not in ["triggers", "help", "vcs"]}, ) if "sub" in spec: subparsers = parser.add_subparsers(dest=spec["sub_dest"]) subparsers.required = True for name, sub_spec in spec["sub"].items(): subparser = subparsers.add_parser( name, description=sub_spec.get("description"), help=sub_spec.get("description"), formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) build_parser(sub_spec, subparser, Vcs(name) if spec["sub_dest"] == "vcs" else None) return parser def get_parser() -> argparse.ArgumentParser: return build_parser(cli_spec) def parse_args(argv=None) -> argparse.Namespace: return get_parser().parse_args(argv) def from_stdin(value: Optional[str]) -> Optional[str]: if value is not None: return value if not sys.stdin.isatty(): return sys.stdin.readline().strip() return None def from_vcs( vcs: Vcs, pattern: str, metadata: Optional[bool], dirty: bool, format: Optional[str], style: Optional[Style], latest_tag: bool, tag_dir: str, debug: bool, bump: bool, tagged_metadata: bool, tag_branch: Optional[str], full_commit: bool, strict: bool, path: Optional[Path], pattern_prefix: Optional[str], ignore_untracked: bool, commit_length: Optional[int], ) -> None: version = Version.from_vcs( vcs, pattern, latest_tag, tag_dir, tag_branch, full_commit, strict, path, pattern_prefix, ignore_untracked, commit_length, ) for concern in version.concerns: print("Warning: {}".format(concern.message()), file=sys.stderr) print(version.serialize(metadata, dirty, format, style, bump, tagged_metadata=tagged_metadata)) if debug: print("# Matched tag: {}".format(version._matched_tag), file=sys.stderr) print("# Newer unmatched tags: {}".format(version._newer_unmatched_tags), file=sys.stderr) def main() -> None: args = parse_args() try: if args.command == "from": tag_dir = getattr(args, "tag_dir", "tags") tag_branch = getattr(args, "tag_branch", None) full_commit = getattr(args, "full_commit", False) ignore_untracked = getattr(args, "ignore_untracked", False) commit_length = getattr(args, "commit_length", None) from_vcs( Vcs(args.vcs), args.pattern, args.metadata, args.dirty, args.format, Style(args.style) if args.style else None, args.latest_tag, tag_dir, args.debug, args.bump, args.tagged_metadata, tag_branch, full_commit, args.strict, Path(args.path) if args.path is not None else None, args.pattern_prefix, ignore_untracked, commit_length, ) elif args.command == "check": version = from_stdin(args.version) if version is None: raise ValueError("A version must be specified") check_version(version, Style(args.style)) except Exception as e: print(e, file=sys.stderr) sys.exit(1) if __name__ == "__main__": main() dunamai-1.23.0/dunamai/py.typed000066400000000000000000000000331471650161100163260ustar00rootroot00000000000000# Marker file for PEP 561. dunamai-1.23.0/mkdocs.yaml000066400000000000000000000003261471650161100153620ustar00rootroot00000000000000site_name: Dunamai Docs theme: name: material plugins: - search - mkdocstrings: handlers: python: options: show_root_toc_entry: false docstring_style: sphinx dunamai-1.23.0/poetry.legacy.lock000066400000000000000000001153601471650161100166620ustar00rootroot00000000000000[[package]] name = "appdirs" version = "1.4.4" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false python-versions = "*" [[package]] name = "aspy.yaml" version = "1.3.0" description = "A few extensions to pyyaml." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] pyyaml = "*" [[package]] name = "atomicwrites" version = "1.4.0" description = "Atomic file writes." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "attrs" version = "21.2.0" description = "Classes Without Boilerplate" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.extras] dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] [[package]] name = "backports.entry-points-selectable" version = "1.1.1" description = "Compatibility shim providing selectable entry points for older implementations" category = "dev" optional = false python-versions = ">=2.7" [package.dependencies] importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} [package.extras] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] testing = ["pytest", "pytest-flake8", "pytest-cov", "pytest-black (>=0.3.7)", "pytest-mypy", "pytest-checkdocs (>=2.4)", "pytest-enabler (>=1.0.1)"] [[package]] name = "black" version = "18.9b0" description = "The uncompromising code formatter." category = "dev" optional = false python-versions = ">=3.6" [package.dependencies] appdirs = "*" attrs = ">=17.4.0" click = ">=6.5" toml = ">=0.9.4" [package.extras] d = ["aiohttp (>=3.3.2)"] [[package]] name = "cfgv" version = "2.0.1" description = "Validate configuration and produce human readable error messages." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] six = "*" [[package]] name = "click" version = "8.0.3" description = "Composable command line interface toolkit" category = "dev" optional = false python-versions = ">=3.6" [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} [[package]] name = "colorama" version = "0.4.4" description = "Cross-platform colored terminal text." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "coverage" version = "5.5" description = "Code coverage measurement for Python" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] toml = ["toml"] [[package]] name = "distlib" version = "0.3.4" description = "Distribution utilities" category = "dev" optional = false python-versions = "*" [[package]] name = "filelock" version = "3.2.1" description = "A platform independent file lock." category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.extras] docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"] testing = ["coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"] [[package]] name = "flake8" version = "3.9.2" description = "the modular source code checker: pep8 pyflakes and co" category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.dependencies] importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} mccabe = ">=0.6.0,<0.7.0" pycodestyle = ">=2.7.0,<2.8.0" pyflakes = ">=2.3.0,<2.4.0" [[package]] name = "flake8-polyfill" version = "1.0.2" description = "Polyfill package for Flake8 plugins" category = "dev" optional = false python-versions = "*" [package.dependencies] flake8 = "*" [[package]] name = "identify" version = "1.6.2" description = "File identification library for Python" category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" [package.extras] license = ["editdistance"] [[package]] name = "importlib-metadata" version = "2.1.2" description = "Read metadata from Python packages" category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.dependencies] zipp = ">=0.5" [package.extras] docs = ["sphinx", "rst.linker"] testing = ["packaging", "pep517", "unittest2", "importlib-resources (>=1.3)"] [[package]] name = "importlib-resources" version = "3.2.1" description = "Read resources from Python packages" category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.dependencies] zipp = {version = ">=0.4", markers = "python_version < \"3.8\""} [package.extras] docs = ["sphinx", "rst.linker", "jaraco.packaging"] [[package]] name = "mccabe" version = "0.6.1" description = "McCabe checker, plugin for flake8" category = "dev" optional = false python-versions = "*" [[package]] name = "more-itertools" version = "8.12.0" description = "More routines for operating on iterables, beyond itertools" category = "dev" optional = false python-versions = ">=3.5" [[package]] name = "mypy" version = "0.740" description = "Optional static typing for Python" category = "dev" optional = false python-versions = ">=3.5" [package.dependencies] mypy-extensions = ">=0.4.0,<0.5.0" typed-ast = ">=1.4.0,<1.5.0" typing-extensions = ">=3.7.4" [package.extras] dmypy = ["psutil (>=4.0)"] [[package]] name = "mypy-extensions" version = "0.4.3" description = "Experimental type system extensions for programs checked with the mypy typechecker." category = "dev" optional = false python-versions = "*" [[package]] name = "nodeenv" version = "1.6.0" description = "Node.js virtual environment builder" category = "dev" optional = false python-versions = "*" [[package]] name = "packaging" version = "20.9" description = "Core utilities for Python packages" category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] pyparsing = ">=2.0.2" [[package]] name = "pathlib2" version = "2.3.6" description = "Object-oriented filesystem paths" category = "dev" optional = false python-versions = "*" [package.dependencies] six = "*" [[package]] name = "pep8-naming" version = "0.8.2" description = "Check PEP-8 naming conventions, plugin for flake8" category = "dev" optional = false python-versions = "*" [package.dependencies] flake8-polyfill = ">=1.0.2,<2" [[package]] name = "platformdirs" version = "2.0.2" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "pluggy" version = "0.13.1" description = "plugin and hook calling mechanisms for python" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} [package.extras] dev = ["pre-commit", "tox"] [[package]] name = "pre-commit" version = "1.21.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.dependencies] "aspy.yaml" = "*" cfgv = ">=2.0.0" identify = ">=1.0.0" importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} importlib-resources = {version = "*", markers = "python_version < \"3.7\""} nodeenv = ">=0.11.1" pyyaml = "*" six = "*" toml = "*" virtualenv = ">=15.2" [[package]] name = "py" version = "1.11.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "pycodestyle" version = "2.7.0" description = "Python style guide checker" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pyflakes" version = "2.3.1" description = "passive checker of Python programs" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pyparsing" version = "2.4.7" description = "Python parsing module" category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "pytest" version = "3.10.1" description = "pytest: simple powerful testing with Python" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] atomicwrites = ">=1.0" attrs = ">=17.4.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} more-itertools = ">=4.0.0" pathlib2 = {version = ">=2.2.0", markers = "python_version < \"3.6\""} pluggy = ">=0.7" py = ">=1.5.0" six = ">=1.10.0" [[package]] name = "pytest-cov" version = "2.9.0" description = "Pytest plugin for measuring coverage." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.dependencies] coverage = ">=4.4" pytest = ">=3.6" [package.extras] testing = ["fields", "hunter", "process-tests (==2.0.2)", "six", "pytest-xdist", "virtualenv"] [[package]] name = "pyyaml" version = "5.3.1" description = "YAML parser and emitter for Python" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "tox" version = "3.24.4" description = "tox is a generic virtualenv management and test command line tool" category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.dependencies] colorama = {version = ">=0.4.1", markers = "platform_system == \"Windows\""} filelock = ">=3.0.0" importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} packaging = ">=14" pluggy = ">=0.12.0" py = ">=1.4.17" six = ">=1.14.0" toml = ">=0.9.4" virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7" [package.extras] docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "towncrier (>=18.5.0)"] testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "psutil (>=5.6.1)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)", "pytest-xdist (>=1.22.2)", "pathlib2 (>=2.3.3)"] [[package]] name = "tox-venv" version = "0.4.0" description = "Use Python 3 venvs for Python 3 tox testenvs" category = "dev" optional = false python-versions = "*" [package.dependencies] tox = ">=3.8.1" [[package]] name = "typed-ast" version = "1.4.3" description = "a fork of Python 2 and 3 ast modules with type comment support" category = "dev" optional = false python-versions = "*" [[package]] name = "typing-extensions" version = "3.10.0.2" description = "Backported and Experimental Type Hints for Python 3.5+" category = "dev" optional = false python-versions = "*" [[package]] name = "virtualenv" version = "20.10.0" description = "Virtual Python Environment builder" category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.dependencies] "backports.entry-points-selectable" = ">=1.0.4" distlib = ">=0.3.1,<1" filelock = ">=3.2,<4" importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} importlib-resources = {version = ">=1.0", markers = "python_version < \"3.7\""} platformdirs = ">=2,<3" six = ">=1.9.0,<2" [package.extras] docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"] testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"] [[package]] name = "zipp" version = "1.2.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false python-versions = ">=2.7" [package.extras] docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] testing = ["pathlib2", "unittest2", "jaraco.itertools", "func-timeout"] [metadata] lock-version = "1.1" python-versions = "^3.5" content-hash = "1cdc2129a501d9e2aa43585a3e1d55adc762c47b5a024beac6ce1f2d91d8db3a" [metadata.files] appdirs = [ {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, ] "aspy.yaml" = [ {file = "aspy.yaml-1.3.0-py2.py3-none-any.whl", hash = "sha256:463372c043f70160a9ec950c3f1e4c3a82db5fca01d334b6bc89c7164d744bdc"}, {file = "aspy.yaml-1.3.0.tar.gz", hash = "sha256:e7c742382eff2caed61f87a39d13f99109088e5e93f04d76eb8d4b28aa143f45"}, ] atomicwrites = [ {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, ] attrs = [ {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, ] "backports.entry-points-selectable" = [ {file = "backports.entry_points_selectable-1.1.1-py2.py3-none-any.whl", hash = "sha256:7fceed9532a7aa2bd888654a7314f864a3c16a4e710b34a58cfc0f08114c663b"}, {file = "backports.entry_points_selectable-1.1.1.tar.gz", hash = "sha256:914b21a479fde881635f7af5adc7f6e38d6b274be32269070c53b698c60d5386"}, ] black = [ {file = "black-18.9b0-py36-none-any.whl", hash = "sha256:817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739"}, {file = "black-18.9b0.tar.gz", hash = "sha256:e030a9a28f542debc08acceb273f228ac422798e5215ba2a791a6ddeaaca22a5"}, ] cfgv = [ {file = "cfgv-2.0.1-py2.py3-none-any.whl", hash = "sha256:fbd93c9ab0a523bf7daec408f3be2ed99a980e20b2d19b50fc184ca6b820d289"}, {file = "cfgv-2.0.1.tar.gz", hash = "sha256:edb387943b665bf9c434f717bf630fa78aecd53d5900d2e05da6ad6048553144"}, ] click = [ {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}, ] colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] coverage = [ {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"}, {file = "coverage-5.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b"}, {file = "coverage-5.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669"}, {file = "coverage-5.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90"}, {file = "coverage-5.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c"}, {file = "coverage-5.5-cp27-cp27m-win32.whl", hash = "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a"}, {file = "coverage-5.5-cp27-cp27m-win_amd64.whl", hash = "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82"}, {file = "coverage-5.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905"}, {file = "coverage-5.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083"}, {file = "coverage-5.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5"}, {file = "coverage-5.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81"}, {file = "coverage-5.5-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6"}, {file = "coverage-5.5-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0"}, {file = "coverage-5.5-cp310-cp310-win_amd64.whl", hash = "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae"}, {file = "coverage-5.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb"}, {file = "coverage-5.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160"}, {file = "coverage-5.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"}, {file = "coverage-5.5-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701"}, {file = "coverage-5.5-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793"}, {file = "coverage-5.5-cp35-cp35m-win32.whl", hash = "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e"}, {file = "coverage-5.5-cp35-cp35m-win_amd64.whl", hash = "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3"}, {file = "coverage-5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066"}, {file = "coverage-5.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a"}, {file = "coverage-5.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465"}, {file = "coverage-5.5-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb"}, {file = "coverage-5.5-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821"}, {file = "coverage-5.5-cp36-cp36m-win32.whl", hash = "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45"}, {file = "coverage-5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184"}, {file = "coverage-5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a"}, {file = "coverage-5.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53"}, {file = "coverage-5.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d"}, {file = "coverage-5.5-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638"}, {file = "coverage-5.5-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3"}, {file = "coverage-5.5-cp37-cp37m-win32.whl", hash = "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a"}, {file = "coverage-5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a"}, {file = "coverage-5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6"}, {file = "coverage-5.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2"}, {file = "coverage-5.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759"}, {file = "coverage-5.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873"}, {file = "coverage-5.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a"}, {file = "coverage-5.5-cp38-cp38-win32.whl", hash = "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6"}, {file = "coverage-5.5-cp38-cp38-win_amd64.whl", hash = "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502"}, {file = "coverage-5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b"}, {file = "coverage-5.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529"}, {file = "coverage-5.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b"}, {file = "coverage-5.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff"}, {file = "coverage-5.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b"}, {file = "coverage-5.5-cp39-cp39-win32.whl", hash = "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6"}, {file = "coverage-5.5-cp39-cp39-win_amd64.whl", hash = "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03"}, {file = "coverage-5.5-pp36-none-any.whl", hash = "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079"}, {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"}, {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, ] distlib = [ {file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"}, {file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"}, ] filelock = [ {file = "filelock-3.2.1-py2.py3-none-any.whl", hash = "sha256:7f07b08d731907441ff40d0c5b81f9512cd968842e0b6264c8bd18a8ce877760"}, {file = "filelock-3.2.1.tar.gz", hash = "sha256:9cdd29c411ab196cf4c35a1da684f7b9da723696cb356efa45bf5eb1ff313ee3"}, ] flake8 = [ {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, ] flake8-polyfill = [ {file = "flake8-polyfill-1.0.2.tar.gz", hash = "sha256:e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda"}, {file = "flake8_polyfill-1.0.2-py2.py3-none-any.whl", hash = "sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9"}, ] identify = [ {file = "identify-1.6.2-py2.py3-none-any.whl", hash = "sha256:8f9879b5b7cca553878d31548a419ec2f227d3328da92fe8202bc5e546d5cbc3"}, {file = "identify-1.6.2.tar.gz", hash = "sha256:1c2014f6985ed02e62b2e6955578acf069cb2c54859e17853be474bfe7e13bed"}, ] importlib-metadata = [ {file = "importlib_metadata-2.1.2-py2.py3-none-any.whl", hash = "sha256:cd6a92d78385dd145f5f233b3a6919acf5e8e43922aa9b9dbe78573e3540eb56"}, {file = "importlib_metadata-2.1.2.tar.gz", hash = "sha256:09db40742204610ef6826af16e49f0479d11d0d54687d0169ff7fddf8b3f557f"}, ] importlib-resources = [ {file = "importlib_resources-3.2.1-py2.py3-none-any.whl", hash = "sha256:e2860cf0c4bc999947228d18be154fa3779c5dde0b882bd2d7b3f4d25e698bd6"}, {file = "importlib_resources-3.2.1.tar.gz", hash = "sha256:a9fe213ab6452708ec1b3f4ec6f2881b8ab3645cb4e5efb7fea2bbf05a91db3b"}, ] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] more-itertools = [ {file = "more-itertools-8.12.0.tar.gz", hash = "sha256:7dc6ad46f05f545f900dd59e8dfb4e84a4827b97b3cfecb175ea0c7d247f6064"}, {file = "more_itertools-8.12.0-py3-none-any.whl", hash = "sha256:43e6dd9942dffd72661a2c4ef383ad7da1e6a3e968a927ad7a6083ab410a688b"}, ] mypy = [ {file = "mypy-0.740-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:9371290aa2cad5ad133e4cdc43892778efd13293406f7340b9ffe99d5ec7c1d9"}, {file = "mypy-0.740-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b428f883d2b3fe1d052c630642cc6afddd07d5cd7873da948644508be3b9d4a7"}, {file = "mypy-0.740-cp35-cp35m-win_amd64.whl", hash = "sha256:ace6ac1d0f87d4072f05b5468a084a45b4eda970e4d26704f201e06d47ab2990"}, {file = "mypy-0.740-cp36-cp36m-macosx_10_6_x86_64.whl", hash = "sha256:d7574e283f83c08501607586b3167728c58e8442947e027d2d4c7dcd6d82f453"}, {file = "mypy-0.740-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d5bf0e6ec8ba346a2cf35cb55bf4adfddbc6b6576fcc9e10863daa523e418dbb"}, {file = "mypy-0.740-cp36-cp36m-win_amd64.whl", hash = "sha256:1521c186a3d200c399bd5573c828ea2db1362af7209b2adb1bb8532cea2fb36f"}, {file = "mypy-0.740-cp37-cp37m-macosx_10_6_x86_64.whl", hash = "sha256:dc889c84241a857c263a2b1cd1121507db7d5b5f5e87e77147097230f374d10b"}, {file = "mypy-0.740-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:6ed3b9b3fdc7193ea7aca6f3c20549b377a56f28769783a8f27191903a54170f"}, {file = "mypy-0.740-cp37-cp37m-win_amd64.whl", hash = "sha256:31a046ab040a84a0fc38bc93694876398e62bc9f35eca8ccbf6418b7297f4c00"}, {file = "mypy-0.740-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:672e418425d957e276c291930a3921b4a6413204f53fe7c37cad7bc57b9a3391"}, {file = "mypy-0.740-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:3b1a411909c84b2ae9b8283b58b48541654b918e8513c20a400bb946aa9111ae"}, {file = "mypy-0.740-cp38-cp38-win_amd64.whl", hash = "sha256:540c9caa57a22d0d5d3c69047cc9dd0094d49782603eb03069821b41f9e970e9"}, {file = "mypy-0.740-py3-none-any.whl", hash = "sha256:f4748697b349f373002656bf32fede706a0e713d67bfdcf04edf39b1f61d46eb"}, {file = "mypy-0.740.tar.gz", hash = "sha256:48c8bc99380575deb39f5d3400ebb6a8a1cb5cc669bbba4d3bb30f904e0a0e7d"}, ] mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] nodeenv = [ {file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"}, {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"}, ] packaging = [ {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, ] pathlib2 = [ {file = "pathlib2-2.3.6-py2.py3-none-any.whl", hash = "sha256:3a130b266b3a36134dcc79c17b3c7ac9634f083825ca6ea9d8f557ee6195c9c8"}, {file = "pathlib2-2.3.6.tar.gz", hash = "sha256:7d8bcb5555003cdf4a8d2872c538faa3a0f5d20630cb360e518ca3b981795e5f"}, ] pep8-naming = [ {file = "pep8-naming-0.8.2.tar.gz", hash = "sha256:01cb1dab2f3ce9045133d08449f1b6b93531dceacb9ef04f67087c11c723cea9"}, {file = "pep8_naming-0.8.2-py2.py3-none-any.whl", hash = "sha256:0ec891e59eea766efd3059c3d81f1da304d858220678bdc351aab73c533f2fbb"}, ] platformdirs = [ {file = "platformdirs-2.0.2-py2.py3-none-any.whl", hash = "sha256:0b9547541f599d3d242078ae60b927b3e453f0ad52f58b4d4bc3be86aed3ec41"}, {file = "platformdirs-2.0.2.tar.gz", hash = "sha256:3b00d081227d9037bbbca521a5787796b5ef5000faea1e43fd76f1d44b06fcfa"}, ] pluggy = [ {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, ] pre-commit = [ {file = "pre_commit-1.21.0-py2.py3-none-any.whl", hash = "sha256:f92a359477f3252452ae2e8d3029de77aec59415c16ae4189bcfba40b757e029"}, {file = "pre_commit-1.21.0.tar.gz", hash = "sha256:8f48d8637bdae6fa70cc97db9c1dd5aa7c5c8bf71968932a380628c25978b850"}, ] py = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, ] pycodestyle = [ {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, ] pyflakes = [ {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, ] pyparsing = [ {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, ] pytest = [ {file = "pytest-3.10.1-py2.py3-none-any.whl", hash = "sha256:3f193df1cfe1d1609d4c583838bea3d532b18d6160fd3f55c9447fdca30848ec"}, {file = "pytest-3.10.1.tar.gz", hash = "sha256:e246cf173c01169b9617fc07264b7b1316e78d7a650055235d6d897bc80d9660"}, ] pytest-cov = [ {file = "pytest-cov-2.9.0.tar.gz", hash = "sha256:b6a814b8ed6247bd81ff47f038511b57fe1ce7f4cc25b9106f1a4b106f1d9322"}, {file = "pytest_cov-2.9.0-py2.py3-none-any.whl", hash = "sha256:c87dfd8465d865655a8213859f1b4749b43448b5fae465cb981e16d52a811424"}, ] pyyaml = [ {file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"}, {file = "PyYAML-5.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76"}, {file = "PyYAML-5.3.1-cp35-cp35m-win32.whl", hash = "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2"}, {file = "PyYAML-5.3.1-cp35-cp35m-win_amd64.whl", hash = "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c"}, {file = "PyYAML-5.3.1-cp36-cp36m-win32.whl", hash = "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2"}, {file = "PyYAML-5.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648"}, {file = "PyYAML-5.3.1-cp37-cp37m-win32.whl", hash = "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"}, {file = "PyYAML-5.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf"}, {file = "PyYAML-5.3.1-cp38-cp38-win32.whl", hash = "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97"}, {file = "PyYAML-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee"}, {file = "PyYAML-5.3.1-cp39-cp39-win32.whl", hash = "sha256:ad9c67312c84def58f3c04504727ca879cb0013b2517c85a9a253f0cb6380c0a"}, {file = "PyYAML-5.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:6034f55dab5fea9e53f436aa68fa3ace2634918e8b5994d82f3621c04ff5ed2e"}, {file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"}, ] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] tox = [ {file = "tox-3.24.4-py2.py3-none-any.whl", hash = "sha256:5e274227a53dc9ef856767c21867377ba395992549f02ce55eb549f9fb9a8d10"}, {file = "tox-3.24.4.tar.gz", hash = "sha256:c30b57fa2477f1fb7c36aa1d83292d5c2336cd0018119e1b1c17340e2c2708ca"}, ] tox-venv = [ {file = "tox-venv-0.4.0.tar.gz", hash = "sha256:ea29dc7b21a03951e1e2bd7f3474bbf315657c5454224a5674b2896e9bbb795c"}, {file = "tox_venv-0.4.0-py2.py3-none-any.whl", hash = "sha256:22c2aba71a991d4adf6902253fa07b3a241d28e4e901cbc9dc86ee8eeaa8d4b4"}, ] typed-ast = [ {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"}, {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"}, {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"}, {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"}, {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"}, {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"}, {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"}, {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"}, {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"}, {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"}, {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"}, {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"}, {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"}, {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"}, {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"}, {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"}, {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"}, {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"}, {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"}, {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"}, {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"}, {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"}, {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"}, {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"}, {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"}, {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, ] typing-extensions = [ {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"}, {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, ] virtualenv = [ {file = "virtualenv-20.10.0-py2.py3-none-any.whl", hash = "sha256:4b02e52a624336eece99c96e3ab7111f469c24ba226a53ec474e8e787b365814"}, {file = "virtualenv-20.10.0.tar.gz", hash = "sha256:576d05b46eace16a9c348085f7d0dc8ef28713a2cabaa1cf0aea41e8f12c9218"}, ] zipp = [ {file = "zipp-1.2.0-py2.py3-none-any.whl", hash = "sha256:e0d9e63797e483a30d27e09fffd308c59a700d365ec34e93cc100844168bf921"}, {file = "zipp-1.2.0.tar.gz", hash = "sha256:c70410551488251b0fee67b460fb9a536af8d6f9f008ad10ac51f615b6a521b1"}, ] dunamai-1.23.0/poetry.lock000066400000000000000000001600351471650161100154160ustar00rootroot00000000000000# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. [[package]] name = "argparse-manpage" version = "4.6" description = "Build manual page from python's ArgumentParser object." optional = false python-versions = "*" files = [ {file = "argparse-manpage-4.6.tar.gz", hash = "sha256:0b659d70fd142876da41c2918bd6de4d027875720b0e4672d6443b51198dbb62"}, ] [package.dependencies] tomli = {version = "*", markers = "python_version < \"3.11\""} [package.extras] setuptools = ["setuptools"] [[package]] name = "atomicwrites" version = "1.4.1" description = "Atomic file writes." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, ] [[package]] name = "attrs" version = "22.1.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.5" files = [ {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, ] [package.extras] dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] [[package]] name = "black" version = "22.1.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.6.2" files = [ {file = "black-22.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1297c63b9e1b96a3d0da2d85d11cd9bf8664251fd69ddac068b98dc4f34f73b6"}, {file = "black-22.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2ff96450d3ad9ea499fc4c60e425a1439c2120cbbc1ab959ff20f7c76ec7e866"}, {file = "black-22.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e21e1f1efa65a50e3960edd068b6ae6d64ad6235bd8bfea116a03b21836af71"}, {file = "black-22.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f69158a7d120fd641d1fa9a921d898e20d52e44a74a6fbbcc570a62a6bc8ab"}, {file = "black-22.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:228b5ae2c8e3d6227e4bde5920d2fc66cc3400fde7bcc74f480cb07ef0b570d5"}, {file = "black-22.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b1a5ed73ab4c482208d20434f700d514f66ffe2840f63a6252ecc43a9bc77e8a"}, {file = "black-22.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35944b7100af4a985abfcaa860b06af15590deb1f392f06c8683b4381e8eeaf0"}, {file = "black-22.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7835fee5238fc0a0baf6c9268fb816b5f5cd9b8793423a75e8cd663c48d073ba"}, {file = "black-22.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dae63f2dbf82882fa3b2a3c49c32bffe144970a573cd68d247af6560fc493ae1"}, {file = "black-22.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fa1db02410b1924b6749c245ab38d30621564e658297484952f3d8a39fce7e8"}, {file = "black-22.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c8226f50b8c34a14608b848dc23a46e5d08397d009446353dad45e04af0c8e28"}, {file = "black-22.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2d6f331c02f0f40aa51a22e479c8209d37fcd520c77721c034517d44eecf5912"}, {file = "black-22.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:742ce9af3086e5bd07e58c8feb09dbb2b047b7f566eb5f5bc63fd455814979f3"}, {file = "black-22.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fdb8754b453fb15fad3f72cd9cad3e16776f0964d67cf30ebcbf10327a3777a3"}, {file = "black-22.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5660feab44c2e3cb24b2419b998846cbb01c23c7fe645fee45087efa3da2d61"}, {file = "black-22.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:6f2f01381f91c1efb1451998bd65a129b3ed6f64f79663a55fe0e9b74a5f81fd"}, {file = "black-22.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:efbadd9b52c060a8fc3b9658744091cb33c31f830b3f074422ed27bad2b18e8f"}, {file = "black-22.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8871fcb4b447206904932b54b567923e5be802b9b19b744fdff092bd2f3118d0"}, {file = "black-22.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccad888050f5393f0d6029deea2a33e5ae371fd182a697313bdbd835d3edaf9c"}, {file = "black-22.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07e5c049442d7ca1a2fc273c79d1aecbbf1bc858f62e8184abe1ad175c4f7cc2"}, {file = "black-22.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:373922fc66676133ddc3e754e4509196a8c392fec3f5ca4486673e685a421321"}, {file = "black-22.1.0-py3-none-any.whl", hash = "sha256:3524739d76b6b3ed1132422bf9d82123cd1705086723bc3e235ca39fd21c667d"}, {file = "black-22.1.0.tar.gz", hash = "sha256:a7c0192d35635f6fc1174be575cb7915e92e5dd629ee79fdaf0dcfa41a80afb5"}, ] [package.dependencies] click = ">=8.0.0" mypy-extensions = ">=0.4.3" pathspec = ">=0.9.0" platformdirs = ">=2" tomli = ">=1.1.0" typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} [package.extras] colorama = ["colorama (>=0.4.3)"] d = ["aiohttp (>=3.7.4)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "cfgv" version = "3.3.1" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.6.1" files = [ {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, ] [[package]] name = "click" version = "8.0.4" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.6" files = [ {file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"}, {file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"}, ] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} [[package]] name = "colorama" version = "0.4.5" description = "Cross-platform colored terminal text." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, ] [[package]] name = "coverage" version = "5.5" description = "Code coverage measurement for Python" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" files = [ {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"}, {file = "coverage-5.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b"}, {file = "coverage-5.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669"}, {file = "coverage-5.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90"}, {file = "coverage-5.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c"}, {file = "coverage-5.5-cp27-cp27m-win32.whl", hash = "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a"}, {file = "coverage-5.5-cp27-cp27m-win_amd64.whl", hash = "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82"}, {file = "coverage-5.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905"}, {file = "coverage-5.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083"}, {file = "coverage-5.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5"}, {file = "coverage-5.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81"}, {file = "coverage-5.5-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6"}, {file = "coverage-5.5-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0"}, {file = "coverage-5.5-cp310-cp310-win_amd64.whl", hash = "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae"}, {file = "coverage-5.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb"}, {file = "coverage-5.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160"}, {file = "coverage-5.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"}, {file = "coverage-5.5-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701"}, {file = "coverage-5.5-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793"}, {file = "coverage-5.5-cp35-cp35m-win32.whl", hash = "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e"}, {file = "coverage-5.5-cp35-cp35m-win_amd64.whl", hash = "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3"}, {file = "coverage-5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066"}, {file = "coverage-5.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a"}, {file = "coverage-5.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465"}, {file = "coverage-5.5-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb"}, {file = "coverage-5.5-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821"}, {file = "coverage-5.5-cp36-cp36m-win32.whl", hash = "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45"}, {file = "coverage-5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184"}, {file = "coverage-5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a"}, {file = "coverage-5.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53"}, {file = "coverage-5.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d"}, {file = "coverage-5.5-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638"}, {file = "coverage-5.5-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3"}, {file = "coverage-5.5-cp37-cp37m-win32.whl", hash = "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a"}, {file = "coverage-5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a"}, {file = "coverage-5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6"}, {file = "coverage-5.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2"}, {file = "coverage-5.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759"}, {file = "coverage-5.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873"}, {file = "coverage-5.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a"}, {file = "coverage-5.5-cp38-cp38-win32.whl", hash = "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6"}, {file = "coverage-5.5-cp38-cp38-win_amd64.whl", hash = "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502"}, {file = "coverage-5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b"}, {file = "coverage-5.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529"}, {file = "coverage-5.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b"}, {file = "coverage-5.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff"}, {file = "coverage-5.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b"}, {file = "coverage-5.5-cp39-cp39-win32.whl", hash = "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6"}, {file = "coverage-5.5-cp39-cp39-win_amd64.whl", hash = "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03"}, {file = "coverage-5.5-pp36-none-any.whl", hash = "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079"}, {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"}, {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, ] [package.extras] toml = ["toml"] [[package]] name = "coverage" version = "6.5.0" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.7" files = [ {file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"}, {file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"}, {file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"}, {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"}, {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"}, {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"}, {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"}, {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"}, {file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"}, {file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"}, {file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"}, {file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"}, {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"}, {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"}, {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"}, {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"}, {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"}, {file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"}, {file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"}, {file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"}, {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"}, {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"}, {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"}, {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"}, {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"}, {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"}, {file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"}, {file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"}, {file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"}, {file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"}, {file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"}, {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"}, {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"}, {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"}, {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"}, {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"}, {file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"}, {file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"}, {file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"}, {file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"}, {file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"}, {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"}, {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"}, {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"}, {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"}, {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"}, {file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"}, {file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"}, {file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"}, {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"}, ] [package.dependencies] tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] toml = ["tomli"] [[package]] name = "distlib" version = "0.3.6" description = "Distribution utilities" optional = false python-versions = "*" files = [ {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, ] [[package]] name = "exceptiongroup" version = "1.0.1" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ {file = "exceptiongroup-1.0.1-py3-none-any.whl", hash = "sha256:4d6c0aa6dd825810941c792f53d7b8d71da26f5e5f84f20f9508e8f2d33b140a"}, {file = "exceptiongroup-1.0.1.tar.gz", hash = "sha256:73866f7f842ede6cb1daa42c4af078e2035e5f7607f0e2c762cc51bb31bbe7b2"}, ] [package.extras] test = ["pytest (>=6)"] [[package]] name = "filelock" version = "3.8.0" description = "A platform independent file lock." optional = false python-versions = ">=3.7" files = [ {file = "filelock-3.8.0-py3-none-any.whl", hash = "sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4"}, {file = "filelock-3.8.0.tar.gz", hash = "sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc"}, ] [package.extras] docs = ["furo (>=2022.6.21)", "sphinx (>=5.1.1)", "sphinx-autodoc-typehints (>=1.19.1)"] testing = ["covdefaults (>=2.2)", "coverage (>=6.4.2)", "pytest (>=7.1.2)", "pytest-cov (>=3)", "pytest-timeout (>=2.1)"] [[package]] name = "identify" version = "2.5.8" description = "File identification library for Python" optional = false python-versions = ">=3.7" files = [ {file = "identify-2.5.8-py2.py3-none-any.whl", hash = "sha256:48b7925fe122720088aeb7a6c34f17b27e706b72c61070f27fe3789094233440"}, {file = "identify-2.5.8.tar.gz", hash = "sha256:7a214a10313b9489a0d61467db2856ae8d0b8306fc923e03a9effa53d8aedc58"}, ] [package.extras] license = ["ukkonen"] [[package]] name = "importlib-metadata" version = "2.1.3" description = "Read metadata from Python packages" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" files = [ {file = "importlib_metadata-2.1.3-py2.py3-none-any.whl", hash = "sha256:52e65a0856f9ba7ea8f2c4ced253fb6c88d1a8c352cb1e916cff4eb17d5a693d"}, {file = "importlib_metadata-2.1.3.tar.gz", hash = "sha256:02a9f62b02e9b1cc43871809ef99947e8f5d94771392d666ada2cafc4cd09d4f"}, ] [package.dependencies] zipp = ">=0.5" [package.extras] docs = ["rst.linker", "sphinx"] testing = ["importlib-resources (>=1.3)", "packaging", "pep517", "unittest2"] [[package]] name = "iniconfig" version = "1.1.1" description = "iniconfig: brain-dead simple config-ini parsing" optional = false python-versions = "*" files = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] [[package]] name = "more-itertools" version = "8.14.0" description = "More routines for operating on iterables, beyond itertools" optional = false python-versions = ">=3.5" files = [ {file = "more-itertools-8.14.0.tar.gz", hash = "sha256:c09443cd3d5438b8dafccd867a6bc1cb0894389e90cb53d227456b0b0bccb750"}, {file = "more_itertools-8.14.0-py3-none-any.whl", hash = "sha256:1bc4f91ee5b1b31ac7ceacc17c09befe6a40a503907baf9c839c229b5095cfd2"}, ] [[package]] name = "mypy" version = "0.982" description = "Optional static typing for Python" optional = false python-versions = ">=3.7" files = [ {file = "mypy-0.982-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5085e6f442003fa915aeb0a46d4da58128da69325d8213b4b35cc7054090aed5"}, {file = "mypy-0.982-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:41fd1cf9bc0e1c19b9af13a6580ccb66c381a5ee2cf63ee5ebab747a4badeba3"}, {file = "mypy-0.982-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f793e3dd95e166b66d50e7b63e69e58e88643d80a3dcc3bcd81368e0478b089c"}, {file = "mypy-0.982-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86ebe67adf4d021b28c3f547da6aa2cce660b57f0432617af2cca932d4d378a6"}, {file = "mypy-0.982-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:175f292f649a3af7082fe36620369ffc4661a71005aa9f8297ea473df5772046"}, {file = "mypy-0.982-cp310-cp310-win_amd64.whl", hash = "sha256:8ee8c2472e96beb1045e9081de8e92f295b89ac10c4109afdf3a23ad6e644f3e"}, {file = "mypy-0.982-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58f27ebafe726a8e5ccb58d896451dd9a662a511a3188ff6a8a6a919142ecc20"}, {file = "mypy-0.982-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6af646bd46f10d53834a8e8983e130e47d8ab2d4b7a97363e35b24e1d588947"}, {file = "mypy-0.982-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e7aeaa763c7ab86d5b66ff27f68493d672e44c8099af636d433a7f3fa5596d40"}, {file = "mypy-0.982-cp37-cp37m-win_amd64.whl", hash = "sha256:724d36be56444f569c20a629d1d4ee0cb0ad666078d59bb84f8f887952511ca1"}, {file = "mypy-0.982-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:14d53cdd4cf93765aa747a7399f0961a365bcddf7855d9cef6306fa41de01c24"}, {file = "mypy-0.982-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:26ae64555d480ad4b32a267d10cab7aec92ff44de35a7cd95b2b7cb8e64ebe3e"}, {file = "mypy-0.982-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6389af3e204975d6658de4fb8ac16f58c14e1bacc6142fee86d1b5b26aa52bda"}, {file = "mypy-0.982-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b35ce03a289480d6544aac85fa3674f493f323d80ea7226410ed065cd46f206"}, {file = "mypy-0.982-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c6e564f035d25c99fd2b863e13049744d96bd1947e3d3d2f16f5828864506763"}, {file = "mypy-0.982-cp38-cp38-win_amd64.whl", hash = "sha256:cebca7fd333f90b61b3ef7f217ff75ce2e287482206ef4a8b18f32b49927b1a2"}, {file = "mypy-0.982-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a705a93670c8b74769496280d2fe6cd59961506c64f329bb179970ff1d24f9f8"}, {file = "mypy-0.982-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:75838c649290d83a2b83a88288c1eb60fe7a05b36d46cbea9d22efc790002146"}, {file = "mypy-0.982-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:91781eff1f3f2607519c8b0e8518aad8498af1419e8442d5d0afb108059881fc"}, {file = "mypy-0.982-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaa97b9ddd1dd9901a22a879491dbb951b5dec75c3b90032e2baa7336777363b"}, {file = "mypy-0.982-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a692a8e7d07abe5f4b2dd32d731812a0175626a90a223d4b58f10f458747dd8a"}, {file = "mypy-0.982-cp39-cp39-win_amd64.whl", hash = "sha256:eb7a068e503be3543c4bd329c994103874fa543c1727ba5288393c21d912d795"}, {file = "mypy-0.982-py3-none-any.whl", hash = "sha256:1021c241e8b6e1ca5a47e4d52601274ac078a89845cfde66c6d5f769819ffa1d"}, {file = "mypy-0.982.tar.gz", hash = "sha256:85f7a343542dc8b1ed0a888cdd34dca56462654ef23aa673907305b260b3d746"}, ] [package.dependencies] mypy-extensions = ">=0.4.3" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} typing-extensions = ">=3.10" [package.extras] dmypy = ["psutil (>=4.0)"] python2 = ["typed-ast (>=1.4.0,<2)"] reports = ["lxml"] [[package]] name = "mypy-extensions" version = "0.4.3" description = "Experimental type system extensions for programs checked with the mypy typechecker." optional = false python-versions = "*" files = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] [[package]] name = "nodeenv" version = "1.7.0" description = "Node.js virtual environment builder" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" files = [ {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"}, {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"}, ] [package.dependencies] setuptools = "*" [[package]] name = "packaging" version = "20.9" description = "Core utilities for Python packages" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, ] [package.dependencies] pyparsing = ">=2.0.2" [[package]] name = "pathlib2" version = "2.3.7.post1" description = "Object-oriented filesystem paths" optional = false python-versions = "*" files = [ {file = "pathlib2-2.3.7.post1-py2.py3-none-any.whl", hash = "sha256:5266a0fd000452f1b3467d782f079a4343c63aaa119221fbdc4e39577489ca5b"}, {file = "pathlib2-2.3.7.post1.tar.gz", hash = "sha256:9fe0edad898b83c0c3e199c842b27ed216645d2e177757b2dd67384d4113c641"}, ] [package.dependencies] six = "*" [[package]] name = "pathspec" version = "0.9.0" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" files = [ {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, ] [[package]] name = "platformdirs" version = "2.4.0" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.6" files = [ {file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"}, {file = "platformdirs-2.4.0.tar.gz", hash = "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2"}, ] [package.extras] docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] [[package]] name = "pluggy" version = "0.13.1" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, ] [package.dependencies] importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} [package.extras] dev = ["pre-commit", "tox"] [[package]] name = "pluggy" version = "1.0.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.6" files = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] [package.dependencies] importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} [package.extras] dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" version = "2.20.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.7" files = [ {file = "pre_commit-2.20.0-py2.py3-none-any.whl", hash = "sha256:51a5ba7c480ae8072ecdb6933df22d2f812dc897d5fe848778116129a681aac7"}, {file = "pre_commit-2.20.0.tar.gz", hash = "sha256:a978dac7bc9ec0bcee55c18a277d553b0f419d259dadb4b9418ff2d00eb43959"}, ] [package.dependencies] cfgv = ">=2.0.0" identify = ">=1.0.0" importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} nodeenv = ">=0.11.1" pyyaml = ">=5.1" toml = "*" virtualenv = ">=20.0.8" [[package]] name = "py" version = "1.11.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, ] [[package]] name = "pyparsing" version = "2.4.7" description = "Python parsing module" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, ] [[package]] name = "pytest" version = "3.10.1" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ {file = "pytest-3.10.1-py2.py3-none-any.whl", hash = "sha256:3f193df1cfe1d1609d4c583838bea3d532b18d6160fd3f55c9447fdca30848ec"}, {file = "pytest-3.10.1.tar.gz", hash = "sha256:e246cf173c01169b9617fc07264b7b1316e78d7a650055235d6d897bc80d9660"}, ] [package.dependencies] atomicwrites = ">=1.0" attrs = ">=17.4.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} more-itertools = ">=4.0.0" pathlib2 = {version = ">=2.2.0", markers = "python_version < \"3.6\""} pluggy = ">=0.7" py = ">=1.5.0" setuptools = "*" six = ">=1.10.0" [[package]] name = "pytest" version = "7.2.0" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ {file = "pytest-7.2.0-py3-none-any.whl", hash = "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71"}, {file = "pytest-7.2.0.tar.gz", hash = "sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59"}, ] [package.dependencies] attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} iniconfig = "*" packaging = "*" pluggy = ">=0.12,<2.0" tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] [[package]] name = "pytest-cov" version = "2.9.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ {file = "pytest-cov-2.9.0.tar.gz", hash = "sha256:b6a814b8ed6247bd81ff47f038511b57fe1ce7f4cc25b9106f1a4b106f1d9322"}, {file = "pytest_cov-2.9.0-py2.py3-none-any.whl", hash = "sha256:c87dfd8465d865655a8213859f1b4749b43448b5fae465cb981e16d52a811424"}, ] [package.dependencies] coverage = ">=4.4" pytest = ">=3.6" [package.extras] testing = ["fields", "hunter", "process-tests (==2.0.2)", "pytest-xdist", "six", "virtualenv"] [[package]] name = "pytest-cov" version = "4.0.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.6" files = [ {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, ] [package.dependencies] coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] [[package]] name = "pyyaml" version = "6.0" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.6" files = [ {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, ] [[package]] name = "ruff" version = "0.0.272" description = "An extremely fast Python linter, written in Rust." optional = false python-versions = ">=3.7" files = [ {file = "ruff-0.0.272-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:ae9b57546e118660175d45d264b87e9b4c19405c75b587b6e4d21e6a17bf4fdf"}, {file = "ruff-0.0.272-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:1609b864a8d7ee75a8c07578bdea0a7db75a144404e75ef3162e0042bfdc100d"}, {file = "ruff-0.0.272-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee76b4f05fcfff37bd6ac209d1370520d509ea70b5a637bdf0a04d0c99e13dff"}, {file = "ruff-0.0.272-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:48eccf225615e106341a641f826b15224b8a4240b84269ead62f0afd6d7e2d95"}, {file = "ruff-0.0.272-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:677284430ac539bb23421a2b431b4ebc588097ef3ef918d0e0a8d8ed31fea216"}, {file = "ruff-0.0.272-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:9c4bfb75456a8e1efe14c52fcefb89cfb8f2a0d31ed8d804b82c6cf2dc29c42c"}, {file = "ruff-0.0.272-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86bc788245361a8148ff98667da938a01e1606b28a45e50ac977b09d3ad2c538"}, {file = "ruff-0.0.272-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:27b2ea68d2aa69fff1b20b67636b1e3e22a6a39e476c880da1282c3e4bf6ee5a"}, {file = "ruff-0.0.272-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd2bbe337a3f84958f796c77820d55ac2db1e6753f39d1d1baed44e07f13f96d"}, {file = "ruff-0.0.272-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d5a208f8ef0e51d4746930589f54f9f92f84bb69a7d15b1de34ce80a7681bc00"}, {file = "ruff-0.0.272-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:905ff8f3d6206ad56fcd70674453527b9011c8b0dc73ead27618426feff6908e"}, {file = "ruff-0.0.272-py3-none-musllinux_1_2_i686.whl", hash = "sha256:19643d448f76b1eb8a764719072e9c885968971bfba872e14e7257e08bc2f2b7"}, {file = "ruff-0.0.272-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:691d72a00a99707a4e0b2846690961157aef7b17b6b884f6b4420a9f25cd39b5"}, {file = "ruff-0.0.272-py3-none-win32.whl", hash = "sha256:dc406e5d756d932da95f3af082814d2467943631a587339ee65e5a4f4fbe83eb"}, {file = "ruff-0.0.272-py3-none-win_amd64.whl", hash = "sha256:a37ec80e238ead2969b746d7d1b6b0d31aa799498e9ba4281ab505b93e1f4b28"}, {file = "ruff-0.0.272-py3-none-win_arm64.whl", hash = "sha256:06b8ee4eb8711ab119db51028dd9f5384b44728c23586424fd6e241a5b9c4a3b"}, {file = "ruff-0.0.272.tar.gz", hash = "sha256:273a01dc8c3c4fd4c2af7ea7a67c8d39bb09bce466e640dd170034da75d14cab"}, ] [[package]] name = "setuptools" version = "65.5.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.7" files = [ {file = "setuptools-65.5.1-py3-none-any.whl", hash = "sha256:d0b9a8433464d5800cbe05094acf5c6d52a91bfac9b52bcfc4d41382be5d5d31"}, {file = "setuptools-65.5.1.tar.gz", hash = "sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] [[package]] name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] [[package]] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.7" files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] [[package]] name = "typed-ast" version = "1.5.4" description = "a fork of Python 2 and 3 ast modules with type comment support" optional = false python-versions = ">=3.6" files = [ {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"}, {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"}, {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"}, {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"}, {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"}, {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"}, {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"}, {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"}, {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"}, {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"}, {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"}, {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"}, {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"}, {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"}, {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"}, {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"}, {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"}, {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"}, {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"}, {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, ] [[package]] name = "typing-extensions" version = "4.4.0" description = "Backported and Experimental Type Hints for Python 3.7+" optional = false python-versions = ">=3.7" files = [ {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, ] [[package]] name = "virtualenv" version = "20.16.2" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.6" files = [ {file = "virtualenv-20.16.2-py2.py3-none-any.whl", hash = "sha256:635b272a8e2f77cb051946f46c60a54ace3cb5e25568228bd6b57fc70eca9ff3"}, {file = "virtualenv-20.16.2.tar.gz", hash = "sha256:0ef5be6d07181946891f5abc8047fda8bc2f0b4b9bf222c64e6e8963baee76db"}, ] [package.dependencies] distlib = ">=0.3.1,<1" filelock = ">=3.2,<4" importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} platformdirs = ">=2,<3" [package.extras] docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"] testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "packaging (>=20.0)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)"] [[package]] name = "zipp" version = "1.2.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=2.7" files = [ {file = "zipp-1.2.0-py2.py3-none-any.whl", hash = "sha256:e0d9e63797e483a30d27e09fffd308c59a700d365ec34e93cc100844168bf921"}, {file = "zipp-1.2.0.tar.gz", hash = "sha256:c70410551488251b0fee67b460fb9a536af8d6f9f008ad10ac51f615b6a521b1"}, ] [package.extras] docs = ["jaraco.packaging (>=3.2)", "rst.linker (>=1.9)", "sphinx"] testing = ["func-timeout", "jaraco.itertools", "pathlib2", "unittest2"] [metadata] lock-version = "2.0" python-versions = ">=3.5" content-hash = "b941a232720c8986e186d0b841f7e84ccc3c960effb6d5e8cb754c3d6b20790d" dunamai-1.23.0/pyproject.toml000066400000000000000000000031471471650161100161360ustar00rootroot00000000000000[tool.poetry] name = "dunamai" version = "1.23.0" description = "Dynamic version generation" license = "MIT" authors = ["Matthew T. Kennerly "] readme = "README.md" repository = "https://github.com/mtkennerly/dunamai" keywords = ["version", "versioning", "dynamic"] classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: End Users/Desktop", "License :: OSI Approved :: MIT License", "Natural Language :: English", "Programming Language :: Python", "Programming Language :: Python :: 3", ] include = [ { path = "CHANGELOG.md", format = "sdist" }, { path = "tests", format = "sdist" }, ] [tool.poetry.dependencies] python = ">=3.5" packaging = ">=20.9" # 20.9 is the last with Python 3.5 compat importlib-metadata = {version = ">=1.6.0", python = "<3.8"} # 2.1.1 is the last with Python 3.5 compat [tool.poetry.dev-dependencies] pytest = [ { version = "^7.2", python = "^3.7" }, { version = "^3.0", python = ">=3.5,<3.7" }, ] pre-commit = { version = "^2.20", python = "^3.7" } pytest-cov = [ { version = "^4.0", python = "^3.7" }, { version = "^2.6", python = ">=3.5,<3.7" }, ] black = { version = "22.1.0", python = "^3.7" } mypy = { version = "^0.982", python = "^3.7" } ruff = {version = "^0.0.272", python = "^3.7"} argparse-manpage = {version = "^4.6", python = ">=3.7"} [tool.poetry.scripts] dunamai = 'dunamai.__main__:main' [tool.black] line-length = 120 [tool.ruff] line-length = 120 extend-select = ["W605", "N"] ignore = ["E501"] [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" dunamai-1.23.0/tasks.py000066400000000000000000000023251471650161100147160ustar00rootroot00000000000000import shlex import shutil from pathlib import Path from invoke import task ROOT = Path(__file__).parent def get_version() -> str: for line in (ROOT / "pyproject.toml").read_text("utf-8").splitlines(): if line.startswith("version ="): return line.replace("version = ", "").strip('"') return "0.0.0" @task def install(ctx): ctx.run("pip uninstall -y dunamai") shutil.rmtree("dist", ignore_errors=True) ctx.run("poetry build") wheel = next(ROOT.glob("dist/*.whl")) ctx.run('pip install "{}"'.format(wheel)) @task def docs(ctx): version = get_version() manpage = "docs/dunamai.1" args = [ "poetry", "run", "argparse-manpage", "--pyfile", "dunamai/__main__.py", "--function", "get_parser", "--project-name", "dunamai", "--prog", "dunamai", "--version", version, "--author", '"Matthew T. Kennerly (mtkennerly)"', "--url", "https://github.com/mtkennerly/dunamai", "--format", "single-commands-section", "--output", manpage, "--manual-title", "Dunamai", ] ctx.run(shlex.join(args)) dunamai-1.23.0/tests/000077500000000000000000000000001471650161100143575ustar00rootroot00000000000000dunamai-1.23.0/tests/archival/000077500000000000000000000000001471650161100161505ustar00rootroot00000000000000dunamai-1.23.0/tests/archival/git-tagged-post/000077500000000000000000000000001471650161100211475ustar00rootroot00000000000000dunamai-1.23.0/tests/archival/git-tagged-post/.git_archival.json000066400000000000000000000003041471650161100245510ustar00rootroot00000000000000{ "hash-full": "1b57ff77e01728b301088b9d57e3922d156c7ec3", "hash-short": "1b57ff7", "timestamp": "2022-11-07T07:16:59+08:00", "refs": "HEAD -> master", "describe": "v0.1.0-1-g1b57ff7" } dunamai-1.23.0/tests/archival/git-tagged/000077500000000000000000000000001471650161100201645ustar00rootroot00000000000000dunamai-1.23.0/tests/archival/git-tagged/.git_archival.json000066400000000000000000000003061471650161100235700ustar00rootroot00000000000000{ "hash-full": "8fe614dbf9e767e70442ab8f56e99bd08d7e782d", "hash-short": "8fe614d", "timestamp": "2022-11-07T07:07:50+08:00", "refs": "HEAD -> master, feature/foo", "describe": "v0.1.0" } dunamai-1.23.0/tests/archival/git-untagged/000077500000000000000000000000001471650161100205275ustar00rootroot00000000000000dunamai-1.23.0/tests/archival/git-untagged/.git_archival.json000066400000000000000000000003001471650161100241250ustar00rootroot00000000000000{ "hash-full": "8fe614dbf9e767e70442ab8f56e99bd08d7e782d", "hash-short": "8fe614d", "timestamp": "2022-11-07T07:07:50+08:00", "refs": "HEAD -> master, feature/foo", "describe": "" } dunamai-1.23.0/tests/archival/hg-tagged/000077500000000000000000000000001471650161100177775ustar00rootroot00000000000000dunamai-1.23.0/tests/archival/hg-tagged/.hg_archival.txt000066400000000000000000000002571471650161100230710ustar00rootroot00000000000000repo: 25e474af1332ed4fff9351c70ef8f36352c013f2 node: cf36273384e558411364a3a973aaa0cc08e48aea branch: default latesttag: foo bar latesttagdistance: 1 changessincelatesttag: 1 dunamai-1.23.0/tests/archival/hg-tagged/.hgtags000066400000000000000000000002211471650161100212500ustar00rootroot00000000000000d80ce21ea5d88b577ee32661ffd3a5b3834f14e0 v0.1.0 5255faf9dfb276bbe189562e9a1194f43506c21c v0.1.1 1662f75d4df8f05506c2bc9d9801f80d879bf3f0 foo bar dunamai-1.23.0/tests/archival/hg-untagged/000077500000000000000000000000001471650161100203425ustar00rootroot00000000000000dunamai-1.23.0/tests/archival/hg-untagged/.hg_archival.txt000066400000000000000000000002541471650161100234310ustar00rootroot00000000000000repo: 25e474af1332ed4fff9351c70ef8f36352c013f2 node: 25e474af1332ed4fff9351c70ef8f36352c013f2 branch: default latesttag: null latesttagdistance: 1 changessincelatesttag: 1 dunamai-1.23.0/tests/integration/000077500000000000000000000000001471650161100167025ustar00rootroot00000000000000dunamai-1.23.0/tests/integration/__init__.py000066400000000000000000000000001471650161100210010ustar00rootroot00000000000000dunamai-1.23.0/tests/integration/test_dunamai.py000066400000000000000000001066041471650161100217400ustar00rootroot00000000000000import datetime as dt import os import shutil import sys import time from contextlib import contextmanager from pathlib import Path from typing import Callable, Iterator, List, Optional import pytest from dunamai import Version, Vcs, Concern, _get_git_version, _run_cmd def avoid_identical_ref_timestamps() -> None: time.sleep(1.2) def lacks_git_version(version: List[int]) -> bool: if shutil.which("git") is None: return True return _get_git_version() < version REPO = Path(__file__).parent.parent.parent @contextmanager def chdir(where: Path) -> Iterator[None]: start = Path.cwd() os.chdir(str(where)) try: yield finally: os.chdir(str(start)) def is_git_legacy() -> bool: version = _get_git_version() return version < [2, 7] def set_missing_env(key: str, value: str, alts: Optional[List[str]] = None) -> None: if alts is None: alts = [] for k in [key, *alts]: if os.environ.get(k) is not None: return os.environ[key] = value def make_run_callback(where: Path) -> Callable: def inner(command, expected_code: int = 0, env: Optional[dict] = None): _, out = _run_cmd(command, where=where, codes=[expected_code], env=env) return out return inner def make_from_callback(function: Callable, clear: bool = True, chronological: bool = True) -> Callable: def inner(*args, fresh: bool = False, **kwargs): version = function(*args, **kwargs) if fresh: assert version.commit is None assert version.timestamp is None else: assert isinstance(version.commit, str) assert len(version.commit) > 0 if chronological: assert isinstance(version.timestamp, dt.datetime) now = dt.datetime.utcnow().replace(tzinfo=dt.timezone.utc) delta = dt.timedelta(minutes=1) assert now - delta <= version.timestamp <= now + delta if clear: version.commit = None version.timestamp = None return version return inner from_any_vcs = make_from_callback(Version.from_any_vcs) from_any_vcs_unmocked = make_from_callback(Version.from_any_vcs, clear=False) from_explicit_vcs = make_from_callback(Version.from_vcs) @pytest.mark.skipif(shutil.which("git") is None, reason="Requires Git") def test__version__from_git__with_annotated_tags(tmp_path) -> None: vcs = tmp_path / "dunamai-git-annotated" vcs.mkdir() run = make_run_callback(vcs) from_vcs = make_from_callback(Version.from_git) b = "master" legacy = is_git_legacy() with chdir(vcs): run("git init") try: # Compatibility for newer Git versions: run("git branch -m master") except Exception: pass assert from_vcs(fresh=True) == Version("0.0.0", distance=0, dirty=True, branch=b) assert from_vcs(fresh=True).vcs == Vcs.Git # Additional one-off check not in other VCS integration tests: # strict mode requires there to be a tag with pytest.raises(RuntimeError): from_vcs(strict=True) (vcs / "foo.txt").write_text("hi") assert from_vcs(fresh=True) == Version("0.0.0", distance=0, dirty=True, branch=b) assert from_vcs(fresh=True).concerns == set() run("git add .") run('git commit --no-gpg-sign -m "Initial commit"') assert from_vcs() == Version("0.0.0", distance=1, dirty=False, branch=b) # Detect dirty if untracked files (vcs / "bar.txt").write_text("bye") assert from_vcs() == Version("0.0.0", distance=1, dirty=True, branch=b) assert from_vcs(ignore_untracked=True) == Version("0.0.0", distance=1, dirty=False, branch=b) # Once the untracked file is removed we are no longer dirty (vcs / "bar.txt").unlink() assert from_vcs() == Version("0.0.0", distance=1, dirty=False, branch=b) # Additional one-off check not in other VCS integration tests: # when the only tag in the repository does not match the pattern. run("git tag other -m Annotated") assert from_vcs() == Version("0.0.0", distance=1, dirty=False, branch=b) with pytest.raises(ValueError): from_vcs(strict=True) avoid_identical_ref_timestamps() run("git tag v0.1.0 -m Annotated") assert from_vcs() == Version("0.1.0", dirty=False, branch=b) assert from_vcs(latest_tag=True) == Version("0.1.0", dirty=False, branch=b) assert run("dunamai from git") == "0.1.0" assert run("dunamai from any") == "0.1.0" # Additional one-off checks not in other VCS integration tests: assert run(r'dunamai from any --pattern "(?P\d\.\d\.\d)"') == "0.1.0" run(r'dunamai from any --pattern "(\d\.\d\.\d)"', 1) assert run('dunamai from any --format "v{base}"') == "v0.1.0" assert run('dunamai from any --style "semver"') == "0.1.0" assert ( run('dunamai from any --format "v{base}" --style "semver"', 1) == "Version 'v0.1.0' does not conform to the Semantic Versioning style" ) assert run("dunamai from any --latest-tag") == "0.1.0" assert from_explicit_vcs(Vcs.Any) == Version("0.1.0", dirty=False, branch=b) assert from_explicit_vcs(Vcs.Git) == Version("0.1.0", dirty=False, branch=b) assert run("dunamai from any --bump") == "0.1.0" assert run('dunamai from git --format "{commit}"') != run('dunamai from git --format "{commit}" --full-commit') assert run('dunamai from any --format "{commit}"') != run('dunamai from any --format "{commit}" --full-commit') if not legacy: # Verify tags with '/' work run("git tag test/v0.1.0") assert run(r'dunamai from any --pattern "^test/v(?P\d\.\d\.\d)"') == "0.1.0" assert run('dunamai from any --pattern-prefix "test/"') == "0.1.0" (vcs / "foo.txt").write_text("bye") assert from_vcs() == Version("0.1.0", dirty=True, branch=b) run("git add .") run('git commit --no-gpg-sign -m "Second"') assert from_vcs() == Version("0.1.0", distance=1, dirty=False, branch=b) assert from_any_vcs() == Version("0.1.0", distance=1, dirty=False, branch=b) run("git tag unmatched -m Annotated") assert from_vcs() == Version("0.1.0", distance=1, dirty=False, branch=b) with pytest.raises(ValueError): from_vcs(latest_tag=True) # check that we find the expected tag that has the most recent tag creation time if legacy: run("git tag -d unmatched") run("git tag v0.2.0 -m Annotated") if not legacy: avoid_identical_ref_timestamps() run("git tag v0.2.0b1 -m Annotated") avoid_identical_ref_timestamps() run("git tag v0.2.0 -m Annotated") avoid_identical_ref_timestamps() run("git tag v0.1.1 HEAD~1 -m Annotated") assert from_vcs() == Version("0.2.0", dirty=False, branch=b) assert from_vcs(latest_tag=True) == Version("0.2.0", dirty=False, branch=b) # Check handling with identical tag and branch names: run("git checkout -b v0.2.0") assert from_vcs() == Version("0.2.0", dirty=False, branch="heads/v0.2.0") assert from_vcs(latest_tag=True) == Version("0.2.0", dirty=False, branch="heads/v0.2.0") if not legacy: run("git checkout v0.1.0") assert from_vcs() == Version("0.1.1", dirty=False) assert from_vcs(latest_tag=True) == Version("0.1.1", dirty=False) # Additional one-off check not in other VCS integration tests: run("git checkout master") (vcs / "foo.txt").write_text("third") run("git add .") run('git commit --no-gpg-sign -m "Third"') # bumping: commit = run('dunamai from any --format "{commit}"') assert run("dunamai from any --bump") == "0.2.1.dev1+{}".format(commit) if not legacy: # tag with pre-release segment. run("git tag v0.2.1b3 -m Annotated") assert from_vcs() == Version("0.2.1", stage=("b", 3), dirty=False, branch=b) if not legacy: # Additional one-off check: tag containing comma. (vcs / "foo.txt").write_text("fourth") run("git add .") run('git commit --no-gpg-sign -m "Fourth"') run("git tag v0.3.0+a,b -m Annotated") assert from_vcs() == Version("0.3.0", dirty=False, tagged_metadata="a,b", branch=b) if not legacy: assert from_vcs(path=vcs) == Version("0.3.0", dirty=False, tagged_metadata="a,b", branch=b) @pytest.mark.skipif(shutil.which("git") is None, reason="Requires Git") def test__version__from_git__with_lightweight_tags(tmp_path) -> None: vcs = tmp_path / "dunamai-git-lightweight" vcs.mkdir() run = make_run_callback(vcs) from_vcs = make_from_callback(Version.from_git) b = "master" legacy = is_git_legacy() with chdir(vcs): run("git init") (vcs / "foo.txt").write_text("hi") run("git add .") run('git commit --no-gpg-sign -m "Initial commit"') run("git tag v0.1.0") assert from_vcs() == Version("0.1.0", dirty=False, branch=b) assert from_vcs(latest_tag=True) == Version("0.1.0", dirty=False, branch=b) assert run("dunamai from git") == "0.1.0" assert run("dunamai from any") == "0.1.0" (vcs / "foo.txt").write_text("bye") run("git add .") avoid_identical_ref_timestamps() run('git commit --no-gpg-sign -m "Second"') assert from_vcs() == Version("0.1.0", distance=1, dirty=False, branch=b) assert from_any_vcs() == Version("0.1.0", distance=1, dirty=False, branch=b) (vcs / "foo.txt").write_text("again") run("git add .") avoid_identical_ref_timestamps() run('git commit --no-gpg-sign -m "Third"') assert from_vcs() == Version("0.1.0", distance=2, dirty=False, branch=b) assert from_any_vcs() == Version("0.1.0", distance=2, dirty=False, branch=b) run("git tag v0.2.0") run("git tag v0.1.1 HEAD~1") assert from_vcs() == Version("0.2.0", dirty=False, branch=b) assert from_vcs(latest_tag=True) == Version("0.2.0", dirty=False, branch=b) if not legacy: run("git checkout v0.1.1") assert from_vcs() == Version("0.1.1", dirty=False) assert from_vcs(latest_tag=True) == Version("0.1.1", dirty=False) @pytest.mark.skipif(shutil.which("git") is None, reason="Requires Git") def test__version__from_git__with_mixed_tags(tmp_path) -> None: vcs = tmp_path / "dunamai-git-mixed" vcs.mkdir() run = make_run_callback(vcs) from_vcs = make_from_callback(Version.from_git) b = "master" with chdir(vcs): run("git init") (vcs / "foo.txt").write_text("hi") run("git add .") run('git commit --no-gpg-sign -m "Initial commit"') run("git tag v0.1.0") (vcs / "foo.txt").write_text("hi 2") run("git add .") avoid_identical_ref_timestamps() run('git commit --no-gpg-sign -m "Second"') run('git tag v0.2.0 -m "Annotated"') assert from_vcs() == Version("0.2.0", dirty=False, branch=b) assert from_vcs(latest_tag=True) == Version("0.2.0", dirty=False, branch=b) (vcs / "foo.txt").write_text("hi 3") run("git add .") avoid_identical_ref_timestamps() run('git commit --no-gpg-sign -m "Third"') run("git tag v0.3.0") assert from_vcs() == Version("0.3.0", dirty=False, branch=b) assert from_vcs(latest_tag=True) == Version("0.3.0", dirty=False, branch=b) @pytest.mark.skipif(shutil.which("git") is None, reason="Requires Git") def test__version__from_git__with_nonchronological_commits(tmp_path) -> None: vcs = tmp_path / "dunamai-git-nonchronological" vcs.mkdir() run = make_run_callback(vcs) from_vcs = make_from_callback(Version.from_git, chronological=False) b = "master" legacy = is_git_legacy() with chdir(vcs): run("git init") (vcs / "foo.txt").write_text("hi") run("git add .") run( 'git commit --no-gpg-sign -m "Initial commit"', env={ "GIT_COMMITTER_DATE": "2000-01-02T01:00:00", "GIT_AUTHOR_DATE": "2000-01-02T01:00:00", **os.environ, }, ) run("git tag v0.1.0") (vcs / "foo.txt").write_text("hi 2") run("git add .") avoid_identical_ref_timestamps() run( 'git commit --no-gpg-sign -m "Second"', env={ "GIT_COMMITTER_DATE": "2000-01-01T01:00:00", "GIT_AUTHOR_DATE": "2000-01-01T01:00:00", **os.environ, }, ) run("git tag v0.2.0") if legacy: assert from_vcs() == Version("0.1.0", distance=1, dirty=False, branch=b) else: assert from_vcs() == Version("0.2.0", dirty=False, branch=b) @pytest.mark.skipif(shutil.which("git") is None, reason="Requires Git") @pytest.mark.skipif(is_git_legacy(), reason="Requires non-legacy Git") def test__version__from_git__gitflow(tmp_path) -> None: vcs = tmp_path / "dunamai-git-gitflow" vcs.mkdir() run = make_run_callback(vcs) from_vcs = make_from_callback(Version.from_git) b = "master" b2 = "develop" with chdir(vcs): run("git init") (vcs / "foo.txt").write_text("hi") run("git add .") run("git commit --no-gpg-sign -m Initial") run("git tag v0.1.0 -m Release") run("git checkout -b develop") (vcs / "foo.txt").write_text("second") run("git add .") run("git commit --no-gpg-sign -m Second") run("git checkout -b release/v0.2.0") (vcs / "foo.txt").write_text("bugfix") run("git add .") run("git commit --no-gpg-sign -m Bugfix") run("git checkout develop") run("git merge --no-gpg-sign --no-ff release/v0.2.0") run("git checkout master") run("git merge --no-gpg-sign --no-ff release/v0.2.0") run("git tag v0.2.0 -m Release") assert from_vcs() == Version("0.2.0", dirty=False, branch=b) run("git checkout develop") assert from_vcs() == Version("0.1.0", distance=3, dirty=False, branch=b2) assert run('dunamai from any --format "{base}+{distance}"') == "0.1.0+3" assert from_vcs(tag_branch="master") == Version("0.2.0", distance=1, dirty=False, branch=b2) assert run('dunamai from any --tag-branch master --format "{base}+{distance}"') == "0.2.0+1" assert run('dunamai from git --tag-branch master --format "{base}+{distance}"') == "0.2.0+1" (vcs / "foo.txt").write_text("feature") run("git add .") run("git commit --no-gpg-sign -m Feature") assert from_vcs() == Version("0.1.0", distance=4, dirty=False, branch=b2) assert from_vcs(tag_branch="master") == Version("0.2.0", distance=2, dirty=False, branch=b2) run("git checkout master") assert from_vcs() == Version("0.2.0", dirty=False, branch=b) assert from_vcs(tag_branch="master") == Version("0.2.0", dirty=False, branch=b) assert from_vcs(tag_branch="develop") == Version("0.1.0", distance=3, dirty=False, branch=b) def test__version__from_git__archival_untagged() -> None: with chdir(REPO / "tests" / "archival" / "git-untagged"): detected = Version.from_git() assert detected == Version( "0.0.0", branch="master", commit="8fe614d", timestamp=dt.datetime(2022, 11, 6, 23, 7, 50, tzinfo=dt.timezone.utc), ) assert detected._matched_tag is None assert detected._newer_unmatched_tags is None assert Version.from_any_vcs() == detected assert Version.from_git(full_commit=True).commit == "8fe614dbf9e767e70442ab8f56e99bd08d7e782d" with pytest.raises(RuntimeError): Version.from_git(strict=True) def test__version__from_git__archival_tagged() -> None: with chdir(REPO / "tests" / "archival" / "git-tagged"): detected = Version.from_git() assert detected == Version( "0.1.0", branch="master", dirty=False, distance=0, commit="8fe614d", timestamp=dt.datetime(2022, 11, 6, 23, 7, 50, tzinfo=dt.timezone.utc), ) assert detected._matched_tag == "v0.1.0" assert detected._newer_unmatched_tags == [] def test__version__from_git__archival_tagged_post() -> None: with chdir(REPO / "tests" / "archival" / "git-tagged-post"): detected = Version.from_git() assert detected == Version( "0.1.0", branch="master", dirty=False, distance=1, commit="1b57ff7", timestamp=dt.datetime(2022, 11, 6, 23, 16, 59, tzinfo=dt.timezone.utc), ) assert detected._matched_tag == "v0.1.0" assert detected._newer_unmatched_tags == [] @pytest.mark.skipif(shutil.which("git") is None, reason="Requires Git") def test__version__from_git__shallow(tmp_path) -> None: vcs = tmp_path / "dunamai-git-shallow" vcs.mkdir() run = make_run_callback(vcs) with chdir(vcs): run("git clone --depth 1 https://github.com/mtkennerly/dunamai.git .") assert Version.from_git().concerns == {Concern.ShallowRepository} with pytest.raises(RuntimeError): Version.from_git(strict=True) @pytest.mark.skipif(lacks_git_version([2, 27]), reason="Requires Git 2.27+") def test__version__from_git__exclude_decoration(tmp_path) -> None: vcs = tmp_path / "dunamai-git-exclude-decoration" vcs.mkdir() run = make_run_callback(vcs) from_vcs = make_from_callback(Version.from_git) b = "master" with chdir(vcs): run("git init") (vcs / "foo.txt").write_text("hi") run("git add .") run("git commit --no-gpg-sign -m Initial") run("git tag v0.1.0 -m Release") run("git config log.excludeDecoration refs/tags/") assert from_vcs() == Version("0.1.0", dirty=False, branch=b) # Older versions of Git fail with code 128: # "fatal: missing object 0000000000000000000000000000000000000000 for refs/tags/bad.txt" @pytest.mark.skipif(lacks_git_version([2, 7]), reason="Requires Git 2.7+") def test__version__from_git__broken_ref(tmp_path) -> None: vcs = tmp_path / "dunamai-git-broken-ref" vcs.mkdir() run = make_run_callback(vcs) from_vcs = make_from_callback(Version.from_git) b = "master" with chdir(vcs): run("git init") (vcs / "foo.txt").write_text("hi") run("git add .") run("git commit --no-gpg-sign -m Initial") run("git tag v0.1.0 -m Release") (vcs / ".git/refs/tags/bad.txt").touch() assert from_vcs() == Version("0.1.0", dirty=False, branch=b) @pytest.mark.skipif(shutil.which("git") is None, reason="Requires Git") def test__version__not_a_repository(tmp_path) -> None: vcs = tmp_path / "dunamai-not-a-repo" vcs.mkdir() run = make_run_callback(vcs) with chdir(vcs): assert run("dunamai from git", 1) == "This does not appear to be a Git project" @pytest.mark.skipif(shutil.which("hg") is None, reason="Requires Mercurial") def test__version__from_mercurial(tmp_path) -> None: vcs = tmp_path / "dunamai-hg" vcs.mkdir() run = make_run_callback(vcs) from_vcs = make_from_callback(Version.from_mercurial) b = "default" with chdir(vcs): run("hg init") assert from_vcs(fresh=True) == Version("0.0.0", distance=0, dirty=False, branch=b) assert from_vcs(fresh=True).vcs == Vcs.Mercurial (vcs / "foo.txt").write_text("hi") assert from_vcs(fresh=True) == Version("0.0.0", distance=0, dirty=True, branch=b) run("hg add .") run('hg commit -m "Initial commit"') assert from_vcs() == Version("0.0.0", distance=1, dirty=False, branch=b) assert run('dunamai from mercurial --format "{commit}"') != run( 'dunamai from mercurial --format "{commit}" --full-commit' ) assert run('dunamai from any --format "{commit}"') != run('dunamai from any --format "{commit}" --full-commit') run("hg tag v0.1.0") assert from_vcs() == Version("0.1.0", dirty=False, branch=b) assert from_vcs(latest_tag=True) == Version("0.1.0", dirty=False, branch=b) assert run("dunamai from mercurial") == "0.1.0" assert run("dunamai from any") == "0.1.0" (vcs / "foo.txt").write_text("bye") assert from_vcs() == Version("0.1.0", dirty=True, branch=b) run("hg add .") run('hg commit -m "Second"') assert from_vcs() == Version("0.1.0", distance=1, dirty=False, branch=b) assert from_any_vcs() == Version("0.1.0", distance=1, dirty=False, branch=b) run("hg tag unmatched") assert from_vcs() == Version("0.1.0", distance=2, dirty=False, branch=b) with pytest.raises(ValueError): from_vcs(latest_tag=True) run("hg tag v0.2.0") assert from_vcs() == Version("0.2.0", dirty=False, branch=b) assert from_vcs(latest_tag=True) == Version("0.2.0", dirty=False, branch=b) run('hg tag v0.1.1 -r "tag(v0.1.0)"') assert from_vcs() == Version("0.2.0", distance=1, dirty=False, branch=b) assert from_vcs(latest_tag=True) == Version("0.2.0", distance=1, dirty=False, branch=b) run("hg checkout v0.1.0") assert from_vcs() == Version("0.1.0", dirty=False, branch=b) assert from_vcs(latest_tag=True) == Version("0.1.0", dirty=False, branch=b) assert from_vcs(path=vcs) == Version("0.1.0", dirty=False, branch=b) def test__version__from_mercurial__archival_untagged() -> None: with chdir(REPO / "tests" / "archival" / "hg-untagged"): detected = Version.from_mercurial() assert detected == Version( "0.0.0", branch="default", commit="25e474af1332ed4fff9351c70ef8f36352c013f2", ) assert detected._matched_tag is None assert detected._newer_unmatched_tags is None assert Version.from_any_vcs() == detected with pytest.raises(RuntimeError): Version.from_mercurial(strict=True) def test__version__from_mercurial__archival_tagged() -> None: with chdir(REPO / "tests" / "archival" / "hg-tagged"): detected = Version.from_mercurial() assert detected == Version( "0.1.1", branch="default", commit="cf36273384e558411364a3a973aaa0cc08e48aea", ) assert detected._matched_tag == "v0.1.1" assert detected._newer_unmatched_tags == ["foo bar"] @pytest.mark.skipif(shutil.which("darcs") is None, reason="Requires Darcs") def test__version__from_darcs(tmp_path) -> None: vcs = tmp_path / "dunamai-darcs" vcs.mkdir() run = make_run_callback(vcs) from_vcs = make_from_callback(Version.from_darcs) with chdir(vcs): run("darcs init") assert from_vcs(fresh=True) == Version("0.0.0", distance=0, dirty=False) assert from_vcs(fresh=True).vcs == Vcs.Darcs (vcs / "foo.txt").write_text("hi") assert from_vcs(fresh=True) == Version("0.0.0", distance=0, dirty=True) run("darcs add foo.txt") run('darcs record -am "Initial commit"') assert from_vcs() == Version("0.0.0", distance=1, dirty=False) run("darcs tag v0.1.0") assert from_vcs() == Version("0.1.0", dirty=False) assert from_vcs(latest_tag=True) == Version("0.1.0", dirty=False) assert run("dunamai from darcs") == "0.1.0" assert run("dunamai from any") == "0.1.0" (vcs / "foo.txt").write_text("bye") assert from_vcs() == Version("0.1.0", dirty=True) run('darcs record -am "Second"') assert from_vcs() == Version("0.1.0", distance=1, dirty=False) assert from_any_vcs() == Version("0.1.0", distance=1, dirty=False) run("darcs tag unmatched") assert from_vcs() == Version("0.1.0", distance=2, dirty=False) with pytest.raises(ValueError): from_vcs(latest_tag=True) run("darcs tag v0.2.0") assert from_vcs() == Version("0.2.0", dirty=False) run("darcs obliterate --all --last 3") assert from_vcs() == Version("0.1.0", dirty=False) assert from_vcs(path=vcs) == Version("0.1.0", dirty=False) @pytest.mark.skipif(None in [shutil.which("svn"), shutil.which("svnadmin")], reason="Requires Subversion") def test__version__from_subversion(tmp_path) -> None: vcs = tmp_path / "dunamai-svn" vcs.mkdir() run = make_run_callback(vcs) from_vcs = make_from_callback(Version.from_subversion, clear=False) vcs_srv = tmp_path / "dunamai-svn-srv" vcs_srv.mkdir() run_srv = make_run_callback(vcs_srv) vcs_srv_uri = vcs_srv.as_uri() with chdir(vcs_srv): run_srv("svnadmin create .") with chdir(vcs): run('svn checkout "{}" .'.format(vcs_srv_uri)) assert from_vcs(fresh=True) == Version("0.0.0", distance=0, dirty=False) assert from_vcs(fresh=True).vcs == Vcs.Subversion run("svn mkdir trunk tags") # No tags yet, so version should be 0.0.0. assert from_vcs(fresh=True) == Version("0.0.0", distance=0, dirty=True) run("svn add --force .") run('svn commit -m "Initial commit"') # commit 1 run("svn update") # A single commit, but still no tags. Version should be 0.0.0. assert from_vcs() == Version("0.0.0", distance=1, commit="1", dirty=False) with chdir(vcs / "trunk"): # ^-- Make sure things work when we're in trunk, too. Path("foo.txt").write_text("hi") run("svn add --force .") run('svn commit -m "Initial foo.txt commit"') # commit 2 run("svn update") # Two commits, but still no tag. Version should still be 0.0.0. assert from_vcs() == Version("0.0.0", distance=2, commit="2", dirty=False) run('svn copy {0}/trunk {0}/tags/v0.1.0 -m "Tag 1"'.format(vcs_srv_uri)) # commit 3 and first tag! run("svn update") # 3 commits, one tag (v.0.1.0), version should be 0.1.0. assert from_vcs() == Version("0.1.0", commit="3", dirty=False) assert run("dunamai from subversion") == "0.1.0" assert run("dunamai from any") == "0.1.0" # Dirty the working directory. Make sure we identify it as such. (vcs / "trunk" / "foo.txt").write_text("bye") assert from_vcs() == Version("0.1.0", commit="3", dirty=True) # Fourth commit, still just one tag. Version should be 0.1.0, and dirty flag # should be reset. run('svn commit -m "Fourth"') # commit 4 run("svn update") assert from_vcs() == Version("0.1.0", distance=1, commit="4", dirty=False) assert from_any_vcs_unmocked() == Version("0.1.0", distance=1, commit="4", dirty=False) # Ensure we get the tag based on the highest commit, not necessarily # just the newest tag. run('svn copy {0}/trunk {0}/tags/v0.2.0 -m "Tag 2"'.format(vcs_srv_uri)) # commit 5 run('svn copy {0}/trunk {0}/tags/v0.1.1 -r 1 -m "Tag 3"'.format(vcs_srv_uri)) # commit 6 run("svn update") assert from_vcs() == Version("0.2.0", distance=1, commit="6", dirty=False) assert from_vcs(latest_tag=True) == Version("0.2.0", distance=1, commit="6", dirty=False) run('svn copy {0}/trunk {0}/tags/unmatched -m "Tag 4"'.format(vcs_srv_uri)) # commit 7 run("svn update") assert from_vcs() == Version("0.2.0", distance=2, commit="7", dirty=False) with pytest.raises(ValueError): from_vcs(latest_tag=True) # Checkout an earlier commit. Commit 2 occurred before the first tag # (v0.1.0, commit #3), so version should be 0.0.0. run("svn update -r 2") assert from_vcs() == Version("0.0.0", distance=2, commit="2", dirty=False) assert from_vcs(latest_tag=True) == Version("0.0.0", distance=2, commit="2", dirty=False) with chdir(vcs / "trunk"): # Do this in trunk, to make sure things still work there. # Checkout an earlier commit. Commit 3 was first tag (v0.1.0, commit # #3), so version should be 0.1.0. run("svn update -r 3") assert from_vcs() == Version("0.1.0", distance=0, commit="3", dirty=False) assert from_vcs(latest_tag=True) == Version("0.1.0", distance=0, commit="3", dirty=False) assert from_vcs(path=vcs) == Version("0.1.0", distance=0, commit="3", dirty=False) @pytest.mark.skipif(shutil.which("bzr") is None, reason="Requires Bazaar") def test__version__from_bazaar(tmp_path) -> None: vcs = tmp_path / "dunamai-bzr" vcs.mkdir() run = make_run_callback(vcs) from_vcs = make_from_callback(Version.from_bazaar, clear=False) b = "dunamai-bzr" with chdir(vcs): run("bzr init") assert from_vcs(fresh=True) == Version("0.0.0", distance=0, dirty=False) assert from_vcs(fresh=True).vcs == Vcs.Bazaar (vcs / "foo.txt").write_text("hi") assert from_vcs(fresh=True) == Version("0.0.0", distance=0, dirty=True) run("bzr add .") run('bzr commit -m "Initial commit"') assert from_vcs() == Version("0.0.0", distance=1, commit="1", dirty=False, branch=b) run("bzr tag v0.1.0") assert from_vcs() == Version("0.1.0", commit="1", dirty=False, branch=b) assert from_vcs(latest_tag=True) == Version("0.1.0", commit="1", dirty=False, branch=b) assert run("dunamai from bazaar") == "0.1.0" assert run("dunamai from any") == "0.1.0" (vcs / "foo.txt").write_text("bye") assert from_vcs() == Version("0.1.0", commit="1", dirty=True, branch=b) run("bzr add .") run('bzr commit -m "Second"') assert from_vcs() == Version("0.1.0", distance=1, commit="2", dirty=False, branch=b) assert from_any_vcs_unmocked() == Version("0.1.0", distance=1, commit="2", dirty=False, branch=b) run("bzr tag unmatched") assert from_vcs() == Version("0.1.0", distance=1, commit="2", dirty=False, branch=b) with pytest.raises(ValueError): from_vcs(latest_tag=True) run("bzr tag v0.2.0") run("bzr tag v0.1.1 -r v0.1.0") assert from_vcs() == Version("0.2.0", commit="2", dirty=False, branch=b) assert from_vcs(latest_tag=True) == Version("0.2.0", commit="2", dirty=False, branch=b) run("bzr checkout . old -r v0.1.0") with chdir(vcs / "old"): assert from_vcs() == Version("0.1.1", commit="1", dirty=False, branch=b) assert from_vcs(latest_tag=True) == Version("0.1.1", commit="1", dirty=False, branch=b) with chdir(vcs): shutil.rmtree("old") run("bzr nick renamed") assert from_vcs() == Version("0.2.0", commit="2", dirty=False, branch=b) (vcs / "foo.txt").write_text("branched") run("bzr add .") run('bzr commit -m "branched"') assert from_vcs() == Version("0.2.0", distance=1, commit="3", dirty=False, branch="renamed") run("bzr tag v0.2.1") assert from_vcs() == Version("0.2.1", commit="3", dirty=False, branch="renamed") assert from_vcs(path=vcs) == Version("0.2.1", commit="3", dirty=False, branch="renamed") @pytest.mark.skipif(shutil.which("fossil") is None, reason="Requires Fossil") def test__version__from_fossil(tmp_path) -> None: vcs = tmp_path / "dunamai-fossil" vcs.mkdir() run = make_run_callback(vcs) from_vcs = make_from_callback(Version.from_fossil) b = "trunk" if sys.platform != "win32": set_missing_env("FOSSIL_HOME", str(REPO / "tests"), ["HOME", "XDG_CONFIG_HOME"]) set_missing_env("USER", "dunamai") with chdir(vcs): run("fossil init repo") run("fossil open repo --force") assert from_vcs() == Version("0.0.0", distance=0, dirty=False, branch=b) assert from_vcs().vcs == Vcs.Fossil (vcs / "foo.txt").write_text("hi") assert from_vcs() == Version("0.0.0", distance=0, dirty=True, branch=b) run("fossil add .") run('fossil commit -m "Initial commit"') assert from_vcs() == Version("0.0.0", distance=1, dirty=False, branch=b) run("fossil tag add v0.1.0 trunk") assert from_vcs() == Version("0.1.0", dirty=False, branch=b) assert from_vcs(latest_tag=True) == Version("0.1.0", dirty=False, branch=b) assert run("dunamai from fossil") == "0.1.0" assert run("dunamai from any") == "0.1.0" (vcs / "foo.txt").write_text("bye") assert from_vcs() == Version("0.1.0", dirty=True, branch=b) run("fossil add .") run('fossil commit -m "Second"') assert from_vcs() == Version("0.1.0", distance=1, dirty=False, branch=b) assert from_any_vcs() == Version("0.1.0", distance=1, dirty=False, branch=b) run("fossil tag add unmatched trunk") assert from_vcs() == Version("0.1.0", distance=1, dirty=False, branch=b) with pytest.raises(ValueError): from_vcs(latest_tag=True) (vcs / "foo.txt").write_text("third") run("fossil add .") run("fossil commit --tag v0.2.0 -m 'Third'") assert from_vcs() == Version("0.2.0", dirty=False, branch=b) assert from_vcs(latest_tag=True) == Version("0.2.0", dirty=False, branch=b) run("fossil tag add v0.1.1 v0.1.0") assert from_vcs() == Version("0.2.0", dirty=False, branch=b) assert from_vcs(latest_tag=True) == Version("0.2.0", dirty=False, branch=b) run("fossil checkout v0.1.0") assert from_vcs() == Version("0.1.1", dirty=False, branch=b) assert from_vcs(latest_tag=True) == Version("0.1.1", dirty=False, branch=b) assert from_vcs(path=vcs) == Version("0.1.1", dirty=False, branch=b) @pytest.mark.skipif(shutil.which("pijul") is None, reason="Requires Pijul") def test__version__from_pijul(tmp_path) -> None: vcs = tmp_path / "dunamai-pijul" vcs.mkdir() run = make_run_callback(vcs) from_vcs = make_from_callback(Version.from_pijul) b = "main" with chdir(vcs): run("pijul init") assert from_vcs(fresh=True) == Version("0.0.0", distance=0, dirty=False, branch=b) assert from_vcs(fresh=True).vcs == Vcs.Pijul (vcs / "foo.txt").write_text("hi") assert from_vcs(fresh=True) == Version("0.0.0", distance=0, dirty=False, branch=b) run("pijul add foo.txt") assert from_vcs(fresh=True) == Version("0.0.0", distance=0, dirty=True, branch=b) run('pijul record . -am "Initial commit"') assert from_vcs() == Version("0.0.0", distance=1, dirty=False, branch=b) run("pijul tag create -m v0.1.0") assert from_vcs() == Version("0.1.0", dirty=False, branch=b) assert from_vcs(latest_tag=True) == Version("0.1.0", dirty=False, branch=b) assert run("dunamai from pijul") == "0.1.0" assert run("dunamai from any") == "0.1.0" (vcs / "foo.txt").write_text("bye") assert from_vcs() == Version("0.1.0", dirty=True, branch=b) run('pijul record . -am "Second"') assert from_vcs() == Version("0.1.0", distance=1, dirty=False, branch=b) assert from_any_vcs() == Version("0.1.0", distance=1, dirty=False, branch=b) run("pijul tag create -m unmatched") assert from_vcs() == Version("0.1.0", distance=1, dirty=False, branch=b) with pytest.raises(ValueError): from_vcs(latest_tag=True) assert from_vcs(path=vcs) == Version("0.1.0", distance=1, dirty=False, branch=b) dunamai-1.23.0/tests/unit/000077500000000000000000000000001471650161100153365ustar00rootroot00000000000000dunamai-1.23.0/tests/unit/__init__.py000066400000000000000000000000001471650161100174350ustar00rootroot00000000000000dunamai-1.23.0/tests/unit/test_dunamai.py000066400000000000000000000733341471650161100203770ustar00rootroot00000000000000import datetime as dt import os import re from contextlib import contextmanager from pathlib import Path from typing import Callable, Iterator, Optional try: import importlib.metadata as ilm except ImportError: import importlib_metadata as ilm # type: ignore import pytest from dunamai import ( bump_version, check_version, get_version, Version, serialize_pep440, serialize_pvp, serialize_semver, Pattern, Style, Vcs, _run_cmd, VERSION_SOURCE_PATTERN, ) @contextmanager def chdir(where: Path) -> Iterator[None]: start = Path.cwd() os.chdir(str(where)) try: yield finally: os.chdir(str(start)) def make_run_callback(where: Path) -> Callable: def inner(command, expected_code: int = 0): _, out = _run_cmd(command, where=where, codes=[expected_code]) return out return inner def make_from_callback(function: Callable, mock_commit: Optional[str] = "abc") -> Callable: def inner(*args, **kwargs): version = function(*args, **kwargs) if version.commit and mock_commit: version.commit = mock_commit return version return inner from_any_vcs = make_from_callback(Version.from_any_vcs) from_any_vcs_unmocked = make_from_callback(Version.from_any_vcs, mock_commit=None) from_explicit_vcs = make_from_callback(Version.from_vcs) def test__pattern__regex() -> None: assert Pattern.Default.regex() == VERSION_SOURCE_PATTERN assert Pattern.DefaultUnprefixed.regex() == VERSION_SOURCE_PATTERN.replace("^v", "^v?", 1) assert Pattern.Default.regex("foo-") == VERSION_SOURCE_PATTERN.replace("^", "^foo-", 1) def test__pattern__parse() -> None: assert Pattern.parse(r"(?P\d+)") == r"(?P\d+)" assert Pattern.parse(r"(?P\d+)", "foo-") == r"(?P\d+)" assert Pattern.parse(r"^(?P\d+)", "foo-") == r"^foo-(?P\d+)" assert Pattern.parse("default") == Pattern.Default.regex() assert Pattern.parse("default-unprefixed") == Pattern.DefaultUnprefixed.regex() assert Pattern.parse("default", "foo-") == Pattern.Default.regex("foo-") with pytest.raises(ValueError): Pattern.parse(r"foo") def test__version__init() -> None: v = Version("1", stage=("a", 2), distance=3, commit="abc", dirty=True, tagged_metadata="def", epoch=4) assert v.base == "1" assert v.stage == "a" assert v.revision == 2 assert v.distance == 3 assert v.commit == "abc" assert v.dirty assert v.tagged_metadata == "def" assert v.epoch == 4 def test__version__str() -> None: v = Version("1", stage=("a", 2), distance=3, commit="abc", dirty=True) assert str(v) == v.serialize() def test__version__repr() -> None: v = Version( "1", stage=("a", 2), distance=3, commit="abc", dirty=True, tagged_metadata="tagged", epoch=4, branch="master", timestamp=dt.datetime(2000, 1, 2, 3, 4, 5).replace(tzinfo=dt.timezone.utc), ) assert repr(v) == ( "Version(base='1', stage='a', revision=2, distance=3, commit='abc'," " dirty=True, tagged_metadata='tagged', epoch=4, branch='master'," " timestamp=datetime.datetime(2000, 1, 2, 3, 4, 5, tzinfo=datetime.timezone.utc))" ) def test__version__ordering() -> None: assert Version("0.1.0", distance=2) == Version("0.1.0", distance=2) assert Version("0.2.0") > Version("0.1.0") assert Version("0.1.0", distance=2) > Version("0.1.0", distance=1) with pytest.raises(TypeError): Version("0.1.0") == "0.1.0" with pytest.raises(TypeError): Version("0.1.0") < "0.2.0" assert Version("0.1.0", commit="a") != Version("0.1.0", commit="b") assert Version("0.1.0", dirty=True) == Version("0.1.0", dirty=True) assert Version("0.1.0", dirty=False) != Version("0.1.0", dirty=True) assert Version("0.1.0") != Version("0.1.0", dirty=True) assert Version("0.1.0") != Version("0.1.0", dirty=False) def test__version__serialize__pep440() -> None: assert Version("0.1.0").serialize() == "0.1.0" assert Version("1", stage=("a", 2), distance=3, commit="abc", dirty=True).serialize() == "1a2.post3.dev0+abc" assert ( Version("1", stage=("a", 2), distance=3, commit="abc", dirty=True).serialize(dirty=True) == "1a2.post3.dev0+abc.dirty" ) assert ( Version("1", stage=("a", 2), distance=3, commit="abc", dirty=True).serialize(metadata=False) == "1a2.post3.dev0" ) assert ( Version("1", stage=("a", 2), distance=3, commit="abc", dirty=True).serialize(metadata=False, dirty=True) == "1a2.post3.dev0" ) assert Version("1", stage=("a", 0), distance=3, commit="abc", dirty=False).serialize() == "1a0.post3.dev0+abc" assert Version("1", stage=("a", 2), distance=0, commit="abc", dirty=False).serialize() == "1a2" assert Version("1", stage=("a", 2), distance=3, commit="000", dirty=False).serialize() == "1a2.post3.dev0+000" assert Version("1", stage=("a", None)).serialize() == "1a0" assert Version("1", stage=("b", 2)).serialize() == "1b2" assert Version("1", stage=("rc", 2)).serialize() == "1rc2" assert Version("0.1.0").serialize(bump=True) == "0.1.0" assert Version("0.1.0", distance=3).serialize(bump=True) == "0.1.1.dev3" assert Version("1", distance=3).serialize(bump=True) == "2.dev3" assert Version("0.1.0", stage=("a", None), distance=3).serialize(bump=True) == "0.1.0a2.dev3" assert Version("0.1.0", stage=("b", 2), distance=3).serialize(bump=True) == "0.1.0b3.dev3" assert Version("0.1.0", epoch=2).serialize() == "2!0.1.0" assert Version("0.1.0", stage=("post", 1)).serialize() == "0.1.0.post1" assert Version("0.1.0", stage=("post", 1), distance=3).serialize() == "0.1.0.post1.dev3" assert Version("0.1.0", stage=("post", 1), distance=3).serialize(bump=True) == "0.1.0.post2.dev3" assert Version("0.1.0", stage=("dev", 1)).serialize() == "0.1.0.dev1" assert Version("0.1.0", stage=("dev", 1), distance=3).serialize() == "0.1.0.dev4" assert Version("0.1.0", stage=("dev", 1), distance=3).serialize(bump=True) == "0.1.0.dev5" def test__version__serialize__semver() -> None: style = Style.SemVer assert Version("0.1.0").serialize(style=style) == "0.1.0" assert ( Version("0.1.0", stage=("alpha", 2), distance=3, commit="abc", dirty=True).serialize(style=style) == "0.1.0-alpha.2.post.3+abc" ) assert ( Version("0.1.0", stage=("alpha", 2), distance=3, commit="abc", dirty=True).serialize(dirty=True, style=style) == "0.1.0-alpha.2.post.3+abc.dirty" ) assert ( Version("0.1.0", stage=("alpha", 2), distance=3, commit="abc", dirty=True).serialize( metadata=False, style=style ) == "0.1.0-alpha.2.post.3" ) assert ( Version("0.1.0", stage=("alpha", 2), distance=3, commit="abc", dirty=True).serialize( metadata=False, dirty=True, style=style ) == "0.1.0-alpha.2.post.3" ) assert ( Version("0.1.0", stage=("alpha", 0), distance=3, commit="abc", dirty=False).serialize(style=style) == "0.1.0-alpha.0.post.3+abc" ) assert ( Version("0.1.0", stage=("alpha", 2), distance=0, commit="abc", dirty=False).serialize(style=style) == "0.1.0-alpha.2" ) assert ( Version("0.1.0", stage=("alpha", 2), distance=3, commit="000", dirty=False).serialize(style=style) == "0.1.0-alpha.2.post.3+000" ) assert Version("0.1.0", stage=("alpha", None)).serialize(style=style) == "0.1.0-alpha" assert Version("0.1.0", stage=("beta", 2)).serialize(style=style) == "0.1.0-beta.2" assert Version("0.1.0", stage=("rc", 2)).serialize(style=style) == "0.1.0-rc.2" assert Version("0.1.0").serialize(style=style, bump=True) == "0.1.0" assert Version("0.1.0", distance=3).serialize(style=style, bump=True) == "0.1.1-pre.3" assert ( Version("0.1.0", stage=("alpha", None), distance=3).serialize(style=style, bump=True) == "0.1.0-alpha.2.pre.3" ) assert Version("0.1.0", stage=("beta", 2), distance=4).serialize(style=style, bump=True) == "0.1.0-beta.3.pre.4" assert Version("0.1.0", epoch=2).serialize(style=style) == "0.1.0" def test__version__serialize__pvp() -> None: style = Style.Pvp assert Version("0.1.0").serialize(style=style) == "0.1.0" assert ( Version("0.1.0", stage=("alpha", 2), distance=3, commit="abc", dirty=True).serialize(style=style) == "0.1.0-alpha-2-post-3-abc" ) assert ( Version("0.1.0", stage=("alpha", 2), distance=3, commit="abc", dirty=True).serialize(dirty=True, style=style) == "0.1.0-alpha-2-post-3-abc-dirty" ) assert ( Version("0.1.0", stage=("alpha", 2), distance=3, commit="abc", dirty=True).serialize( metadata=False, style=style ) == "0.1.0-alpha-2-post-3" ) assert ( Version("0.1.0", stage=("alpha", 2), distance=3, commit="abc", dirty=True).serialize( metadata=False, dirty=True, style=style ) == "0.1.0-alpha-2-post-3" ) assert ( Version("0.1.0", stage=("alpha", 0), distance=3, commit="abc", dirty=False).serialize(style=style) == "0.1.0-alpha-0-post-3-abc" ) assert ( Version("0.1.0", stage=("alpha", 2), distance=0, commit="abc", dirty=False).serialize(style=style) == "0.1.0-alpha-2" ) assert ( Version("0.1.0", stage=("alpha", 2), distance=3, commit="000", dirty=False).serialize(style=style) == "0.1.0-alpha-2-post-3-000" ) assert Version("0.1.0", stage=("alpha", None)).serialize(style=style) == "0.1.0-alpha" assert Version("0.1.0", stage=("beta", 2)).serialize(style=style) == "0.1.0-beta-2" assert Version("0.1.0", stage=("rc", 2)).serialize(style=style) == "0.1.0-rc-2" assert Version("0.1.0").serialize(style=style, bump=True) == "0.1.0" assert Version("0.1.0", distance=3).serialize(style=style, bump=True) == "0.1.1-pre-3" assert ( Version("0.1.0", stage=("alpha", None), distance=3).serialize(style=style, bump=True) == "0.1.0-alpha-2-pre-3" ) assert Version("0.1.0", stage=("beta", 2), distance=4).serialize(style=style, bump=True) == "0.1.0-beta-3-pre-4" assert Version("0.1.0", epoch=2).serialize(style=style) == "0.1.0" def test__version__serialize__pep440_metadata() -> None: assert Version("0.1.0").serialize() == "0.1.0" assert Version("0.1.0").serialize(metadata=True) == "0.1.0" assert Version("0.1.0").serialize(metadata=False) == "0.1.0" assert Version("0.1.0", stage=("a", 1), commit="abc").serialize() == "0.1.0a1" assert Version("0.1.0", stage=("a", 1), commit="abc").serialize(metadata=True) == "0.1.0a1+abc" assert Version("0.1.0", stage=("a", 1), commit="abc").serialize(metadata=False) == "0.1.0a1" assert Version("0.1.0", distance=1, commit="abc").serialize() == "0.1.0.post1.dev0+abc" assert Version("0.1.0", distance=1, commit="abc").serialize(metadata=True) == "0.1.0.post1.dev0+abc" assert Version("0.1.0", distance=1, commit="abc").serialize(metadata=False) == "0.1.0.post1.dev0" assert ( Version("0.1.0", distance=1, commit="abc", tagged_metadata="def").serialize(tagged_metadata=True) == "0.1.0.post1.dev0+def.abc" ) def test__version__serialize__semver_with_metadata() -> None: style = Style.SemVer assert Version("0.1.0").serialize(style=style) == "0.1.0" assert Version("0.1.0").serialize(metadata=True, style=style) == "0.1.0" assert Version("0.1.0").serialize(metadata=False, style=style) == "0.1.0" assert Version("0.1.0", stage=("a", 1), commit="abc").serialize(style=style) == "0.1.0-a.1" assert Version("0.1.0", stage=("a", 1), commit="abc").serialize(metadata=True, style=style) == "0.1.0-a.1+abc" assert Version("0.1.0", stage=("a", 1), commit="abc").serialize(metadata=False, style=style) == "0.1.0-a.1" assert Version("0.1.0", distance=1, commit="abc").serialize(style=style) == "0.1.0-post.1+abc" assert Version("0.1.0", distance=1, commit="abc").serialize(metadata=True, style=style) == "0.1.0-post.1+abc" assert Version("0.1.0", distance=1, commit="abc").serialize(metadata=False, style=style) == "0.1.0-post.1" assert ( Version("0.1.0", distance=1, commit="abc", tagged_metadata="def").serialize(style=style, tagged_metadata=True) == "0.1.0-post.1+def.abc" ) def test__version__serialize__pvp_with_metadata() -> None: style = Style.Pvp assert Version("0.1.0").serialize(style=style) == "0.1.0" assert Version("0.1.0").serialize(metadata=True, style=style) == "0.1.0" assert Version("0.1.0").serialize(metadata=False, style=style) == "0.1.0" assert Version("0.1.0", stage=("a", 1), commit="abc").serialize(style=style) == "0.1.0-a-1" assert Version("0.1.0", stage=("a", 1), commit="abc").serialize(metadata=True, style=style) == "0.1.0-a-1-abc" assert Version("0.1.0", stage=("a", 1), commit="abc").serialize(metadata=False, style=style) == "0.1.0-a-1" assert Version("0.1.0", distance=1, commit="abc").serialize(style=style) == "0.1.0-post-1-abc" assert Version("0.1.0", distance=1, commit="abc").serialize(metadata=True, style=style) == "0.1.0-post-1-abc" assert Version("0.1.0", distance=1, commit="abc").serialize(metadata=False, style=style) == "0.1.0-post-1" assert ( Version("0.1.0", distance=1, commit="abc", tagged_metadata="def").serialize(style=style, tagged_metadata=True) == "0.1.0-post-1-def-abc" ) def test__version__serialize__pep440_with_dirty() -> None: assert Version("0.1.0", dirty=True).serialize() == "0.1.0" assert Version("0.1.0", dirty=True).serialize(dirty=True) == "0.1.0+dirty" assert Version("0.1.0", dirty=False).serialize() == "0.1.0" assert Version("0.1.0", dirty=False).serialize(dirty=True) == "0.1.0" assert Version("0.1.0", dirty=True).serialize(metadata=True) == "0.1.0" assert Version("0.1.0", dirty=True).serialize(metadata=True, dirty=True) == "0.1.0+dirty" assert Version("0.1.0", dirty=True).serialize(metadata=False) == "0.1.0" assert Version("0.1.0", dirty=True).serialize(metadata=False, dirty=True) == "0.1.0" def test__version__serialize__semver_with_dirty() -> None: style = Style.SemVer assert Version("0.1.0", dirty=True).serialize(style=style) == "0.1.0" assert Version("0.1.0", dirty=True).serialize(dirty=True, style=style) == "0.1.0+dirty" assert Version("0.1.0", dirty=False).serialize(style=style) == "0.1.0" assert Version("0.1.0", dirty=False).serialize(dirty=True, style=style) == "0.1.0" assert Version("0.1.0", dirty=True).serialize(metadata=True, style=style) == "0.1.0" assert Version("0.1.0", dirty=True).serialize(metadata=True, dirty=True, style=style) == "0.1.0+dirty" assert Version("0.1.0", dirty=True).serialize(metadata=False, style=style) == "0.1.0" assert Version("0.1.0", dirty=True).serialize(metadata=False, dirty=True, style=style) == "0.1.0" def test__version__serialize__pvp_with_dirty() -> None: style = Style.Pvp assert Version("0.1.0", dirty=True).serialize(style=style) == "0.1.0" assert Version("0.1.0", dirty=True).serialize(dirty=True, style=style) == "0.1.0-dirty" assert Version("0.1.0", dirty=False).serialize(style=style) == "0.1.0" assert Version("0.1.0", dirty=False).serialize(dirty=True, style=style) == "0.1.0" assert Version("0.1.0", dirty=True).serialize(metadata=True, style=style) == "0.1.0" assert Version("0.1.0", dirty=True).serialize(metadata=True, dirty=True, style=style) == "0.1.0-dirty" assert Version("0.1.0", dirty=True).serialize(metadata=False, style=style) == "0.1.0" assert Version("0.1.0", dirty=True).serialize(metadata=False, dirty=True, style=style) == "0.1.0" def test__version__serialize__format_as_str() -> None: format = "{base},{stage},{revision},{distance},{commit},{dirty},{branch},{branch_escaped},{timestamp},{major},{minor},{patch}" assert Version("0.1.0").serialize(format=format) == "0.1.0,,,0,,clean,,,,0,1,0" assert ( Version( "1", stage=("a", 2), distance=3, commit="abc", dirty=True, branch="a/b", timestamp=dt.datetime(2001, 2, 3, 4, 5, 6, tzinfo=dt.timezone.utc), ).serialize(format=format) == "1,a,2,3,abc,dirty,a/b,ab,20010203040506,1,0,0" ) with pytest.raises(ValueError): Version("0.1.0").serialize(format="v{base}", style=Style.Pep440) def test__version__serialize__format_as_callable() -> None: def format(v: Version) -> str: return "{},{},{}".format(v.base, v.stage, v.revision) assert Version("0.1.0").serialize(format=format) == "0.1.0,None,None" assert Version("1", stage=("a", 2)).serialize(format=format) == "1,a,2" with pytest.raises(ValueError): Version("0.1.0").serialize(format=lambda v: "v{}".format(v.base), style=Style.Pep440) def immutable(v: Version) -> str: v.distance += 100 return v.serialize() version = Version("0.1.0") version.serialize(format=immutable) assert version.distance == 0 def test__version__bump() -> None: assert Version("1.2.3").bump().serialize() == "1.2.4" assert Version("1.2.3").bump(-2).serialize() == "1.3.0" assert Version("1.2.3").bump(0).serialize() == "2.0.0" assert Version("1.2.3", stage=("a", None)).bump().serialize() == "1.2.3a2" assert Version("1.2.3", stage=("a", 4)).bump().serialize() == "1.2.3a5" assert Version("1.2.3", distance=0).bump(smart=False).serialize() == "1.2.4" assert Version("1.2.3", distance=0).bump(smart=True).serialize() == "1.2.3" assert Version("1.2.3", distance=0).serialize(bump=True) == "1.2.3" assert Version("1.2.3", distance=5).bump(smart=False).serialize() == "1.2.4.post5.dev0" assert Version("1.2.3", distance=5).bump(smart=True).serialize() == "1.2.4.dev5" assert Version("1.2.3", distance=5).serialize(bump=True) == "1.2.4.dev5" def test__version__parse(): assert Version.parse("1.2.3") == Version("1.2.3") assert Version.parse("1.2.3a") == Version("1.2.3", stage=("a", None)) assert Version.parse("1.2.3a3") == Version("1.2.3", stage=("a", 3)) assert Version.parse("1.2.3+7") == Version("1.2.3", distance=7) assert Version.parse("1.2.3+d7") == Version("1.2.3", distance=7) assert Version.parse("1.2.3+b6a9020") == Version("1.2.3", commit="b6a9020") assert Version.parse("1.2.3+gb6a9020") == Version("1.2.3", commit="b6a9020") assert Version.parse("1.2.3+dirty") == Version("1.2.3", dirty=True) assert Version.parse("1.2.3+clean") == Version("1.2.3", dirty=False) assert Version.parse("1.2.3a3+7.b6a9020.dirty") == Version( "1.2.3", stage=("a", 3), distance=7, commit="b6a9020", dirty=True ) assert Version.parse("1.2.3a3+7.b6a9020.dirty.linux") == Version( "1.2.3", stage=("a", 3), distance=7, commit="b6a9020", dirty=True, tagged_metadata="linux" ) assert Version.parse("2!1.2.3") == Version("1.2.3", epoch=2) assert Version.parse("2!1.2.3a3+d7.gb6a9020.dirty.linux") == Version( "1.2.3", stage=("a", 3), distance=7, commit="b6a9020", dirty=True, tagged_metadata="linux", epoch=2, ) assert Version.parse("foo") == Version("foo") assert Version.parse("1.2.3.dev5") == Version("1.2.3", distance=5) assert Version.parse("1.2.3.post4") == Version("1.2.3", stage=("post", 4)) assert Version.parse("1.2.3.post4+d6") == Version("1.2.3", stage=("post", 4), distance=6) assert Version.parse("1.2.3.post4.dev5") == Version("1.2.3", distance=4) assert Version.parse("1.2.3.post4.dev5+d6") == Version("1.2.3", distance=10) assert Version.parse("1.2.3.post4.dev5.blah6") == Version("1.2.3.post4.dev5.blah6") def test__get_version__from_name() -> None: assert get_version("dunamai") == Version(ilm.version("dunamai")) def test__get_version__first_choice() -> None: assert get_version("dunamai", first_choice=lambda: Version("1")) == Version("1") def test__get_version__third_choice() -> None: assert get_version("dunamai_nonexistent_test", third_choice=lambda: Version("3")) == Version("3") def test__get_version__fallback() -> None: assert get_version("dunamai_nonexistent_test") == Version("0.0.0") def test__get_version__from_name__ignore() -> None: assert get_version( "dunamai", ignore=[Version(ilm.version("dunamai"))], fallback=Version("2"), ) == Version("2") def test__get_version__first_choice__ignore() -> None: assert get_version( "dunamai_nonexistent_test", first_choice=lambda: Version("1"), ignore=[Version("1")], fallback=Version("2"), ) == Version("2") def test__get_version__first_choice__ignore_with_distance() -> None: assert get_version( "dunamai_nonexistent_test", first_choice=lambda: Version("1", distance=2), ignore=[Version("1")], fallback=Version("2"), ) == Version("2") assert get_version( "dunamai_nonexistent_test", first_choice=lambda: Version("1"), ignore=[Version("1", distance=2)], fallback=Version("2"), ) != Version("2") def test__get_version__first_choice__ignore__with_commit() -> None: assert get_version( "dunamai_nonexistent_test", first_choice=lambda: Version("1", commit="aaaa"), ignore=[Version("1")], fallback=Version("2"), ) == Version("2") def test__get_version__first_choice__ignore__without_commit() -> None: assert get_version( "dunamai_nonexistent_test", first_choice=lambda: Version("1"), ignore=[Version("1", commit="aaaa")], fallback=Version("2"), ) == Version("1") def test__get_version__third_choice__ignore() -> None: assert get_version( "dunamai_nonexistent_test", third_choice=lambda: Version("3"), ignore=[Version("3")], fallback=Version("2"), ) == Version("2") def test__version__from_any_vcs(tmp_path) -> None: with chdir(tmp_path): with pytest.raises(RuntimeError): Version.from_any_vcs() with pytest.raises(RuntimeError): Version.from_vcs(Vcs.Any) def test__check_version__pep440() -> None: check_version("0.1.0") check_version("0.01.0") check_version("2!0.1.0") check_version("23!0.1.0") check_version("0.1.0a1") check_version("0.1.0b1") check_version("0.1.0rc1") with pytest.raises(ValueError): check_version("0.1.0x1") check_version("0.1.0.post0") check_version("0.1.0.dev0") check_version("0.1.0.post0.dev0") with pytest.raises(ValueError): check_version("0.1.0.other0") check_version("0.1.0+abc.dirty") check_version("0.1.0+abc..dirty") with pytest.raises(ValueError): check_version("0.1.0+abc_dirty") with pytest.raises(ValueError): check_version("0.1.0+.abc") with pytest.raises(ValueError): check_version("0.1.0+abc.") check_version("2!0.1.0a1.post0.dev0+abc.dirty") def test__check_version__semver() -> None: style = Style.SemVer check_version("0.1.0", style=style) check_version("0.1.0-alpha.1", style=style) check_version("0.1.0+abc", style=style) check_version("0.1.0+a.b.c", style=style) check_version("0.1.0-alpha.1.beta.2+abc.dirty", style=style) with pytest.raises(ValueError): check_version("1", style=style) with pytest.raises(ValueError): check_version("0.1", style=style) with pytest.raises(ValueError): check_version("0.0.0.1", style=style) # "-" is a valid identifier. Version("0.1.0--").serialize(style=style) Version("0.1.0--.-").serialize(style=style) with pytest.raises(ValueError): check_version("0.1.0+abc_dirty", style=style) # No leading zeroes in numeric segments: with pytest.raises(ValueError): Version("00.0.0").serialize(style=style) with pytest.raises(ValueError): Version("0.01.0").serialize(style=style) with pytest.raises(ValueError): Version("0.1.0-alpha.02").serialize(style=style) # But leading zeroes are fine for non-numeric parts: Version("0.1.0-alpha.02a").serialize(style=style) # Identifiers can't be empty: with pytest.raises(ValueError): Version("0.1.0-.").serialize(style=style) with pytest.raises(ValueError): Version("0.1.0-a.").serialize(style=style) with pytest.raises(ValueError): Version("0.1.0-.a").serialize(style=style) def test__check_version__pvp() -> None: style = Style.Pvp check_version("1", style=style) check_version("0.1", style=style) check_version("0.0.1", style=style) check_version("0.0.0.1", style=style) check_version("0.1.0-alpha-1", style=style) with pytest.raises(ValueError): check_version("0.1.0-a.1", style=style) with pytest.raises(ValueError): check_version("0.1.0-abc_dirty", style=style) def test__default_version_pattern() -> None: def check_re( tag: str, base: Optional[str] = None, stage: Optional[str] = None, revision: Optional[str] = None, tagged_metadata: Optional[str] = None, epoch: Optional[str] = None, ) -> None: result = re.search(VERSION_SOURCE_PATTERN, tag) if result is None: if any(x is not None for x in [base, stage, revision]): raise ValueError("Pattern did not match, {tag}".format(tag=tag)) else: assert result.group("base") == base assert result.group("stage") == stage assert result.group("revision") == revision assert result.group("tagged_metadata") == tagged_metadata assert result.group("epoch") == epoch check_re("v0.1.0", "0.1.0") check_re("av0.1.0") check_re("v0.1.0a", "0.1.0", "a") check_re("v0.1.0a1", "0.1.0", "a", "1") check_re("v0.1.0a1b", None) check_re("v0.1.0-1.a", None) check_re("v0.1.0-alpha.123", "0.1.0", "alpha", "123") check_re("v0.1.0-1.alpha", None) check_re("v0.1.0-alpha.1.post.4", None) check_re("v0.1.0rc.4", "0.1.0", "rc", "4") check_re("v0.1.0-beta", "0.1.0", "beta") check_re("v0.1.0a2", "0.1.0", "a", "2") check_re("v0.1.0-a-2", "0.1.0", "a", "2") check_re("v0.1.0.a.2", "0.1.0", "a", "2") check_re("v0.1.0_a_2", "0.1.0", "a", "2") check_re("v0.1.0rc.4+specifier", "0.1.0", "rc", "4", tagged_metadata="specifier") check_re("v1", "1") check_re("v1b2", "1", "b", "2") check_re("v1!2", "2", epoch="1") def test__serialize_pep440(): assert serialize_pep440("1.2.3") == "1.2.3" assert serialize_pep440("1.2.3", epoch=0) == "0!1.2.3" assert serialize_pep440("1.2.3", stage="a") == "1.2.3a0" assert serialize_pep440("1.2.3", stage="a", revision=4) == "1.2.3a4" assert serialize_pep440("1.2.3", post=4) == "1.2.3.post4" assert serialize_pep440("1.2.3", dev=4) == "1.2.3.dev4" assert serialize_pep440("1.2.3", metadata=[]) == "1.2.3" assert serialize_pep440("1.2.3", metadata=["foo"]) == "1.2.3+foo" assert serialize_pep440("1.2.3", metadata=["foo", "bar"]) == "1.2.3+foo.bar" assert serialize_pep440("1.2.3", metadata=[4]) == "1.2.3+4" assert ( serialize_pep440("1.2.3", epoch=0, stage="a", revision=4, post=5, dev=6, metadata=["foo", "bar"]) == "0!1.2.3a4.post5.dev6+foo.bar" ) assert serialize_pep440("1.2.3", stage="alpha", revision=4) == "1.2.3a4" assert serialize_pep440("1.2.3", stage="ALphA", revision=4) == "1.2.3a4" assert serialize_pep440("1.2.3", stage="beta", revision=4) == "1.2.3b4" assert serialize_pep440("1.2.3", stage="c", revision=4) == "1.2.3rc4" assert serialize_pep440("1.2.3", stage="pre", revision=4) == "1.2.3rc4" assert serialize_pep440("1.2.3", stage="preview", revision=4) == "1.2.3rc4" with pytest.raises(ValueError): serialize_pep440("foo") def test__serialize_semver(): assert serialize_semver("1.2.3") == "1.2.3" assert serialize_semver("1.2.3", pre=["alpha"]) == "1.2.3-alpha" assert serialize_semver("1.2.3", pre=["alpha", 4]) == "1.2.3-alpha.4" assert serialize_semver("1.2.3", metadata=["foo"]) == "1.2.3+foo" assert serialize_semver("1.2.3", metadata=["foo", "bar"]) == "1.2.3+foo.bar" assert serialize_semver("1.2.3", metadata=[4]) == "1.2.3+4" assert serialize_semver("1.2.3", pre=["alpha", 4], metadata=["foo", "bar"]) == "1.2.3-alpha.4+foo.bar" with pytest.raises(ValueError): serialize_semver("foo") def test__serialize_pvp(): assert serialize_pvp("1") == "1" assert serialize_pvp("1.2") == "1.2" assert serialize_pvp("1.2.3") == "1.2.3" assert serialize_pvp("1.2.3.4") == "1.2.3.4" assert serialize_pvp("1.2.3", metadata=["foo"]) == "1.2.3-foo" assert serialize_pvp("1.2.3", metadata=["foo", "bar"]) == "1.2.3-foo-bar" assert serialize_pvp("1.2.3", metadata=[4]) == "1.2.3-4" with pytest.raises(ValueError): serialize_pvp("foo") def test__bump_version(): # default bump=1 assert bump_version("1.2.3") == "1.2.4" assert bump_version("1.2.3", 0) == "2.0.0" assert bump_version("1.2.3", 1) == "1.3.0" assert bump_version("1.2.3", 2) == "1.2.4" assert bump_version("1.2.3", -1) == "1.2.4" assert bump_version("1.2.3", -2) == "1.3.0" assert bump_version("1.2.3", -3) == "2.0.0" # expicit bump increment assert bump_version("1.2.3", increment=3) == "1.2.6" assert bump_version("1.2.3", 0, increment=3) == "4.0.0" assert bump_version("1.2.3", 1, increment=3) == "1.5.0" assert bump_version("1.2.3", 2, increment=3) == "1.2.6" assert bump_version("1.2.3", -1, increment=3) == "1.2.6" assert bump_version("1.2.3", -2, increment=3) == "1.5.0" assert bump_version("1.2.3", -3, increment=3) == "4.0.0" # check if incorrect index raises issues with pytest.raises(IndexError): bump_version("1.2.3", 3) with pytest.raises(IndexError): bump_version("1.2.3", -4) with pytest.raises(ValueError): bump_version("foo", 0) dunamai-1.23.0/tests/unit/test_main.py000066400000000000000000000071371471650161100177030ustar00rootroot00000000000000from argparse import Namespace import pytest from dunamai import _run_cmd from dunamai.__main__ import parse_args, VERSION_SOURCE_PATTERN def test__parse_args__from(): assert parse_args(["from", "any"]) == Namespace( command="from", vcs="any", pattern=VERSION_SOURCE_PATTERN, dirty=False, metadata=None, format=None, style=None, latest_tag=False, tag_dir="tags", debug=False, bump=False, tagged_metadata=False, tag_branch=None, full_commit=False, strict=False, path=None, pattern_prefix=None, ignore_untracked=False, commit_length=None, ) assert parse_args(["from", "git"]).vcs == "git" assert parse_args(["from", "git", "--tag-branch", "foo"]).tag_branch == "foo" assert parse_args(["from", "git", "--full-commit"]).full_commit is True assert parse_args(["from", "mercurial"]).vcs == "mercurial" assert parse_args(["from", "mercurial", "--full-commit"]).full_commit is True assert parse_args(["from", "darcs"]).vcs == "darcs" assert parse_args(["from", "subversion"]).vcs == "subversion" assert parse_args(["from", "subversion"]).tag_dir == "tags" assert parse_args(["from", "bazaar"]).vcs == "bazaar" assert parse_args(["from", "fossil"]).vcs == "fossil" assert parse_args(["from", "pijul"]).vcs == "pijul" assert parse_args(["from", "any", "--pattern", r"\d+"]).pattern == r"\d+" assert parse_args(["from", "any", "--metadata"]).metadata is True assert parse_args(["from", "any", "--no-metadata"]).metadata is False assert parse_args(["from", "any", "--dirty"]).dirty is True assert parse_args(["from", "any", "--format", "v{base}"]).format == "v{base}" assert parse_args(["from", "any", "--style", "pep440"]).style == "pep440" assert parse_args(["from", "any", "--style", "semver"]).style == "semver" assert parse_args(["from", "any", "--latest-tag"]).latest_tag is True assert parse_args(["from", "any", "--tag-dir", "foo"]).tag_dir == "foo" assert parse_args(["from", "any", "--tag-branch", "foo"]).tag_branch == "foo" assert parse_args(["from", "any", "--full-commit"]).full_commit is True assert parse_args(["from", "any", "--debug"]).debug is True assert parse_args(["from", "any", "--tagged-metadata"]).tagged_metadata is True assert parse_args(["from", "any", "--strict"]).strict is True assert parse_args(["from", "any", "--path", "/tmp"]).path == "/tmp" assert parse_args(["from", "any", "--pattern-prefix", "foo-"]).pattern_prefix == "foo-" assert parse_args(["from", "any", "--ignore-untracked"]).ignore_untracked is True assert parse_args(["from", "any", "--commit-length", "10"]).commit_length == 10 assert parse_args(["from", "subversion", "--tag-dir", "foo"]).tag_dir == "foo" with pytest.raises(SystemExit): parse_args(["from", "unknown"]) def test__parse_args__check(): assert parse_args(["check", "0.1.0"]) == Namespace(command="check", version="0.1.0", style="pep440") assert parse_args(["check", "0.1.0", "--style", "semver"]).style == "semver" assert parse_args(["check", "0.1.0", "--style", "pvp"]).style == "pvp" with pytest.raises(SystemExit): parse_args(["check", "0.1.0", "--style", "unknown"]) def test__cli_check(): _run_cmd("dunamai check 0.01.0", where=None) _run_cmd("dunamai check v0.1.0", where=None, codes=[1]) _run_cmd("dunamai check 0.01.0 --style semver", where=None, codes=[1]) _run_cmd("dunamai check", where=None, codes=[1]) _run_cmd("echo 0.01.0 | dunamai check", where=None, shell=True)