diff --git a/.github/workflows/commit-test.yml b/.github/workflows/commit-test.yml index ecd7e44..9098677 100644 --- a/.github/workflows/commit-test.yml +++ b/.github/workflows/commit-test.yml @@ -1,24 +1,24 @@ -name: Run Unit Test via Pytest - -on: [push] - -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.10", "3.11", "3.12", "3.13"] - - steps: - - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - +name: Run Unit Test via Pytest + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.10", "3.11", "3.12", "3.13"] + + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Bootstrap poetry run: | - curl -sL https://install.python-poetry.org | python - -y + curl -sL https://install.python-poetry.org | python - -y - name: Update PATH run: echo "$HOME/.local/bin" >> $GITHUB_PATH @@ -47,10 +47,10 @@ jobs: - name: Pre-commit hooks run: poetry run pre-commit run --all-files - - name: Test with pytest - run: | - coverage run -m pytest -v -s - - - name: Generate Coverage Report - run: | - coverage report -m + - name: Run pytest + run: poetry run pytest --cov --cov-branch --cov-report=xml + + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..90c0e2b --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.* +!.xet +!.pre-commit-config.yaml +**/__pycache__ +poetry.lock diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..8ab9933 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,37 @@ +repos: +- repo: https://github.com/asottile/pyupgrade + rev: v3.9.0 + hooks: + - id: pyupgrade + args: + - --keep-runtime-typing + - --py39-plus + +- repo: https://github.com/python-poetry/poetry + rev: 1.5.1 + hooks: + - id: poetry-check + +- repo: https://github.com/pycqa/isort + rev: 5.12.0 + hooks: + - id: isort + name: isort (python) + args: ["--profile", "black"] + +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + +- repo: https://github.com/psf/black + rev: 23.7.0 + hooks: + - id: black + +- repo: https://github.com/charliermarsh/ruff-pre-commit + rev: "v0.0.280" + hooks: + - id: ruff diff --git a/.xet b/.xet index 7b7e219..7284281 100644 --- a/.xet +++ b/.xet @@ -1,23 +1,23 @@ { - "pyversion": { + "ppversion": { "type": "tag", - "filepath": "xet/xet.py", - "flags": "ver", + "filepath": "pyproject.toml", + "flags": null, "wrapper": "\"", "presets": null, "ssh": null, - "tag": "VERSION = ", + "tag": "version = ", "occurences": ":", "end": "" }, - "ppversion": { + "pyversion": { "type": "tag", - "filepath": "pyproject.toml", - "flags": "ver", + "filepath": "xet/xet.py", + "flags": null, "wrapper": "\"", "presets": null, "ssh": null, - "tag": "version = ", + "tag": "VERSION = ", "occurences": ":", "end": "" } diff --git a/README.md b/README.md index e61a5ad..449d57f 100644 --- a/README.md +++ b/README.md @@ -6,32 +6,37 @@ ## Overview xet is a command-line tool for managing and modifying values in multiple files using a configuration file (`.xet`). It supports various methods of identifying and modifying values, including tags, line/column positions, and regular expressions. +You can create presets and change between them on the fly. ## Installation ```sh -pip install xet +pipx install xet ``` or ```sh -pipx install xet +pip install xet ``` ## Commands -### Initialize Configuration +### Configuration + +#### Initialize Configuration ```sh xet init ``` - Options: - - `-g, --global`: Global Mode. Creates a `.xet` file in the XDG_CONFIG_HOME folder instead of locally. + - `-g, --global`: Global Mode. Creates a `.xet` file in the XDG_CONFIG_HOME folder instead of locally. Creates an empty `.xet` if it does not already exist. -Any xet command will use the `.xet` file in the immediate directory, unless the `-g, --global` flag is set, then the global `.xet` file will be used instead. +Any xet command will use the `.xet` file in the immediate directory, if there is no local `.xet` the global `.xet` file will be used instead. You can force the usage of the global `.xet` using the `-g, --global` flag. + +#### Open .xet in editor ```sh xet edit @@ -41,10 +46,33 @@ xet edit Opens `.xet` in your standard editor or nano. +#### Clarify .xet directory + +```sh +xet which +``` -### Add Entries to Configuration +Shows the absolute path to the default `.xet` that will be used if any xet command is run. -#### Add a Tag-Based Entry +#### Print .xet entries + +```sh +xet show [options] +``` + +Options: + - `-e, --except `: Exclude entries with specified flags. + - `-o, --only `: Include only entries with specified flags. + - `-n, --names `: Include only entries with specified names. + - `-p, --path `: Include only entries with specified path. + - `-g, --global`: Use the global `.xet`. + +Shows the `.xet` entries with the given optional filters applied. + + +#### Add Entries to Configuration + +##### Add a Tag-Based Entry ```sh xet add tag [options] @@ -58,12 +86,12 @@ xet add tag [options] - `-w, --wrapper `: Wrap the value with a character (e.g., quotes), also gets stripped in get mode. - `-o, --occurences `: Specify which occurrences to modify (string formatted like a list index in python, can be slices). - `-e, --end `: Will get appended in the line after value and wrappers, also gets stripped in get mode. - - `-p, --preset `: Name and value of preset, option can be repeated to add multiple presets. + - `-p, --preset `: Name and value of preset, option can be repeated to add multiple presets. - `-s, --ssh `: Hostname of ssh-host the file is found at, as found in openSSH config file. - `-g, --global`: Add the entry to the global `.xet`. -#### Add a Line/Column-Based Entry +##### Add a Line/Column-Based Entry ```sh xet add lc [options] @@ -78,7 +106,7 @@ xet add lc [options] - `-s, --ssh `: Hostname of ssh-host the file is found at, as found in openSSH config file. - `-g, --global`: Add the entry to the global `.xet`. -#### Add a Regex-Based Entry +##### Add a Regex-Based Entry ```sh xet add regex [options] @@ -94,22 +122,75 @@ xet add regex [options] - `-s, --ssh `: Hostname of ssh-host the file is found at, as found in openSSH config file. - `-g, --global`: Add the entry to the global `.xet`. -### Get Values from Configured Files +#### Update Entries + +```sh +xet update [options] +``` +- ``: The key of the property to update ('name' will update the key of the entry). +- ``: The new value for the property. +Options: + - `-e, --except `: Exclude entries with specified flags. + - `-o, --only `: Include only entries with specified flags. + - `-n, --names `: Include only entries with specified names. + - `-p, --path `: Include only entries with specified path. + - `-g, --global`: Use the global `.xet`. + +Updates the value of a property across filtered entries in the `.xet`. + +#### Remove Entries + +```sh +xet remove [options] +``` + +Options: + - `-e, --except `: Exclude entries with specified flags. + - `-o, --only `: Include only entries with specified flags. + - `-n, --names `: Include only entries with specified names. + - `-p, --path `: Include only entries with specified path. + - `-g, --global`: Use the global `.xet`. + +Removes the specified entries from the `.xet`. + +#### Snapshot ```sh -xet get [-e ] [-o ] [-n ] +xet snapshot [options] +``` +- ``: The name of the new preset. +Options: + - `--first`: When there is divergent values in seperate occurences in an entry, use the first value for the preset. + - `--split`: When there is divergent values in seperate occurences in an entry, split the entry such that no divergences remain. + - `-e, --except `: Exclude entries with specified flags. + - `-o, --only `: Include only entries with specified flags. + - `-n, --names `: Include only entries with specified names. + - `-p, --path `: Include only entries with specified path. + - `-g, --global`: Use the global `.xet`. + + +Adds a preset to the filtered entries using their current values. + +### Modifying Values + +#### Get Values from Configured Files + +```sh +xet get [options] ``` Options: - `-e, --except `: Exclude entries with specified flags. - `-o, --only `: Include only entries with specified flags. - `-n, --names `: Include only entries with specified names. + - `-p, --path `: Include only entries with specified path. - `-g, --global`: Use the global `.xet`. -### Set Values in Configured Files + +#### Set Values in Configured Files ```sh -xet set [-e ] [-o ] [-n ] +xet set [options] ``` - ``: The new value to be set. @@ -117,9 +198,10 @@ Options: - `-e, --except `: Exclude entries with specified flags. - `-o, --only `: Include only entries with specified flags. - `-n, --names `: Include only entries with specified names. + - `-p, --path `: Include only entries with specified path. - `-g, --global`: Use the global `.xet`. -### Set Values to Preset +#### Set Values to Preset ```sh xet preset @@ -129,16 +211,31 @@ xet preset - Options: - `-g, --global`: Use the global `.xet`. -### Remove an Entry +### History + +#### Undo ```sh -xet remove +xet undo ``` -- ``: Name of the entry to be removed. -- Options: - - `-g, --global`: Remove the specified entry from the global `.xet`. -Removes the specified entry from `.xet` file. +Undoes the changes of the last command. + +#### Redo + +```sh +xet redo +``` + +Redoes the last undone changes. + +#### Forget + +```sh +xet forget +``` + +Reset history file. ## Example Usage @@ -171,4 +268,3 @@ Removes the specified entry from `.xet` file. ```sh xet remove version ``` - diff --git a/poetry.lock b/poetry.lock deleted file mode 100644 index acf1218..0000000 --- a/poetry.lock +++ /dev/null @@ -1,436 +0,0 @@ -# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. - -[[package]] -name = "argparse" -version = "1.4.0" -description = "Python command-line parsing library" -category = "main" -optional = false -python-versions = "*" -files = [ - {file = "argparse-1.4.0-py2.py3-none-any.whl", hash = "sha256:c31647edb69fd3d465a847ea3157d37bed1f95f19760b11a47aa91c04b666314"}, - {file = "argparse-1.4.0.tar.gz", hash = "sha256:62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4"}, -] - -[[package]] -name = "bcrypt" -version = "4.3.0" -description = "Modern password hashing for your software and your servers" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "bcrypt-4.3.0-cp313-cp313t-macosx_10_12_universal2.whl", hash = "sha256:f01e060f14b6b57bbb72fc5b4a83ac21c443c9a2ee708e04a10e9192f90a6281"}, - {file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5eeac541cefd0bb887a371ef73c62c3cd78535e4887b310626036a7c0a817bb"}, - {file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59e1aa0e2cd871b08ca146ed08445038f42ff75968c7ae50d2fdd7860ade2180"}, - {file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:0042b2e342e9ae3d2ed22727c1262f76cc4f345683b5c1715f0250cf4277294f"}, - {file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74a8d21a09f5e025a9a23e7c0fd2c7fe8e7503e4d356c0a2c1486ba010619f09"}, - {file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:0142b2cb84a009f8452c8c5a33ace5e3dfec4159e7735f5afe9a4d50a8ea722d"}, - {file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_34_aarch64.whl", hash = "sha256:12fa6ce40cde3f0b899729dbd7d5e8811cb892d31b6f7d0334a1f37748b789fd"}, - {file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_34_x86_64.whl", hash = "sha256:5bd3cca1f2aa5dbcf39e2aa13dd094ea181f48959e1071265de49cc2b82525af"}, - {file = "bcrypt-4.3.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:335a420cfd63fc5bc27308e929bee231c15c85cc4c496610ffb17923abf7f231"}, - {file = "bcrypt-4.3.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:0e30e5e67aed0187a1764911af023043b4542e70a7461ad20e837e94d23e1d6c"}, - {file = "bcrypt-4.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b8d62290ebefd49ee0b3ce7500f5dbdcf13b81402c05f6dafab9a1e1b27212f"}, - {file = "bcrypt-4.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2ef6630e0ec01376f59a006dc72918b1bf436c3b571b80fa1968d775fa02fe7d"}, - {file = "bcrypt-4.3.0-cp313-cp313t-win32.whl", hash = "sha256:7a4be4cbf241afee43f1c3969b9103a41b40bcb3a3f467ab19f891d9bc4642e4"}, - {file = "bcrypt-4.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c1949bf259a388863ced887c7861da1df681cb2388645766c89fdfd9004c669"}, - {file = "bcrypt-4.3.0-cp38-abi3-macosx_10_12_universal2.whl", hash = "sha256:f81b0ed2639568bf14749112298f9e4e2b28853dab50a8b357e31798686a036d"}, - {file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:864f8f19adbe13b7de11ba15d85d4a428c7e2f344bac110f667676a0ff84924b"}, - {file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e36506d001e93bffe59754397572f21bb5dc7c83f54454c990c74a468cd589e"}, - {file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:842d08d75d9fe9fb94b18b071090220697f9f184d4547179b60734846461ed59"}, - {file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7c03296b85cb87db865d91da79bf63d5609284fc0cab9472fdd8367bbd830753"}, - {file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:62f26585e8b219cdc909b6a0069efc5e4267e25d4a3770a364ac58024f62a761"}, - {file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:beeefe437218a65322fbd0069eb437e7c98137e08f22c4660ac2dc795c31f8bb"}, - {file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:97eea7408db3a5bcce4a55d13245ab3fa566e23b4c67cd227062bb49e26c585d"}, - {file = "bcrypt-4.3.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:191354ebfe305e84f344c5964c7cd5f924a3bfc5d405c75ad07f232b6dffb49f"}, - {file = "bcrypt-4.3.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:41261d64150858eeb5ff43c753c4b216991e0ae16614a308a15d909503617732"}, - {file = "bcrypt-4.3.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:33752b1ba962ee793fa2b6321404bf20011fe45b9afd2a842139de3011898fef"}, - {file = "bcrypt-4.3.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:50e6e80a4bfd23a25f5c05b90167c19030cf9f87930f7cb2eacb99f45d1c3304"}, - {file = "bcrypt-4.3.0-cp38-abi3-win32.whl", hash = "sha256:67a561c4d9fb9465ec866177e7aebcad08fe23aaf6fbd692a6fab69088abfc51"}, - {file = "bcrypt-4.3.0-cp38-abi3-win_amd64.whl", hash = "sha256:584027857bc2843772114717a7490a37f68da563b3620f78a849bcb54dc11e62"}, - {file = "bcrypt-4.3.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0d3efb1157edebfd9128e4e46e2ac1a64e0c1fe46fb023158a407c7892b0f8c3"}, - {file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08bacc884fd302b611226c01014eca277d48f0a05187666bca23aac0dad6fe24"}, - {file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6746e6fec103fcd509b96bacdfdaa2fbde9a553245dbada284435173a6f1aef"}, - {file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:afe327968aaf13fc143a56a3360cb27d4ad0345e34da12c7290f1b00b8fe9a8b"}, - {file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d9af79d322e735b1fc33404b5765108ae0ff232d4b54666d46730f8ac1a43676"}, - {file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f1e3ffa1365e8702dc48c8b360fef8d7afeca482809c5e45e653af82ccd088c1"}, - {file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:3004df1b323d10021fda07a813fd33e0fd57bef0e9a480bb143877f6cba996fe"}, - {file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:531457e5c839d8caea9b589a1bcfe3756b0547d7814e9ce3d437f17da75c32b0"}, - {file = "bcrypt-4.3.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:17a854d9a7a476a89dcef6c8bd119ad23e0f82557afbd2c442777a16408e614f"}, - {file = "bcrypt-4.3.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6fb1fd3ab08c0cbc6826a2e0447610c6f09e983a281b919ed721ad32236b8b23"}, - {file = "bcrypt-4.3.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e965a9c1e9a393b8005031ff52583cedc15b7884fce7deb8b0346388837d6cfe"}, - {file = "bcrypt-4.3.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:79e70b8342a33b52b55d93b3a59223a844962bef479f6a0ea318ebbcadf71505"}, - {file = "bcrypt-4.3.0-cp39-abi3-win32.whl", hash = "sha256:b4d4e57f0a63fd0b358eb765063ff661328f69a04494427265950c71b992a39a"}, - {file = "bcrypt-4.3.0-cp39-abi3-win_amd64.whl", hash = "sha256:e53e074b120f2877a35cc6c736b8eb161377caae8925c17688bd46ba56daaa5b"}, - {file = "bcrypt-4.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c950d682f0952bafcceaf709761da0a32a942272fad381081b51096ffa46cea1"}, - {file = "bcrypt-4.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:107d53b5c67e0bbc3f03ebf5b030e0403d24dda980f8e244795335ba7b4a027d"}, - {file = "bcrypt-4.3.0-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:b693dbb82b3c27a1604a3dff5bfc5418a7e6a781bb795288141e5f80cf3a3492"}, - {file = "bcrypt-4.3.0-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:b6354d3760fcd31994a14c89659dee887f1351a06e5dac3c1142307172a79f90"}, - {file = "bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a839320bf27d474e52ef8cb16449bb2ce0ba03ca9f44daba6d93fa1d8828e48a"}, - {file = "bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:bdc6a24e754a555d7316fa4774e64c6c3997d27ed2d1964d55920c7c227bc4ce"}, - {file = "bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:55a935b8e9a1d2def0626c4269db3fcd26728cbff1e84f0341465c31c4ee56d8"}, - {file = "bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:57967b7a28d855313a963aaea51bf6df89f833db4320da458e5b3c5ab6d4c938"}, - {file = "bcrypt-4.3.0.tar.gz", hash = "sha256:3a3fd2204178b6d2adcf09cb4f6426ffef54762577a7c9b54c159008cb288c18"}, -] - -[package.extras] -tests = ["pytest (>=3.2.1,!=3.3.0)"] -typecheck = ["mypy"] - -[[package]] -name = "cffi" -version = "1.17.1" -description = "Foreign Function Interface for Python calling C code." -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, - {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, - {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, - {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, - {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, - {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, - {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, - {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, - {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, - {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, - {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, - {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, - {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, - {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, - {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, - {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, - {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, - {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, - {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, - {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, - {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, - {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, - {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, - {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, - {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, - {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, - {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, - {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, -] - -[package.dependencies] -pycparser = "*" - -[[package]] -name = "cryptography" -version = "45.0.4" -description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -category = "main" -optional = false -python-versions = "!=3.9.0,!=3.9.1,>=3.7" -files = [ - {file = "cryptography-45.0.4-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:425a9a6ac2823ee6e46a76a21a4e8342d8fa5c01e08b823c1f19a8b74f096069"}, - {file = "cryptography-45.0.4-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:680806cf63baa0039b920f4976f5f31b10e772de42f16310a6839d9f21a26b0d"}, - {file = "cryptography-45.0.4-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4ca0f52170e821bc8da6fc0cc565b7bb8ff8d90d36b5e9fdd68e8a86bdf72036"}, - {file = "cryptography-45.0.4-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f3fe7a5ae34d5a414957cc7f457e2b92076e72938423ac64d215722f6cf49a9e"}, - {file = "cryptography-45.0.4-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:25eb4d4d3e54595dc8adebc6bbd5623588991d86591a78c2548ffb64797341e2"}, - {file = "cryptography-45.0.4-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ce1678a2ccbe696cf3af15a75bb72ee008d7ff183c9228592ede9db467e64f1b"}, - {file = "cryptography-45.0.4-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:49fe9155ab32721b9122975e168a6760d8ce4cffe423bcd7ca269ba41b5dfac1"}, - {file = "cryptography-45.0.4-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2882338b2a6e0bd337052e8b9007ced85c637da19ef9ecaf437744495c8c2999"}, - {file = "cryptography-45.0.4-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:23b9c3ea30c3ed4db59e7b9619272e94891f8a3a5591d0b656a7582631ccf750"}, - {file = "cryptography-45.0.4-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b0a97c927497e3bc36b33987abb99bf17a9a175a19af38a892dc4bbb844d7ee2"}, - {file = "cryptography-45.0.4-cp311-abi3-win32.whl", hash = "sha256:e00a6c10a5c53979d6242f123c0a97cff9f3abed7f064fc412c36dc521b5f257"}, - {file = "cryptography-45.0.4-cp311-abi3-win_amd64.whl", hash = "sha256:817ee05c6c9f7a69a16200f0c90ab26d23a87701e2a284bd15156783e46dbcc8"}, - {file = "cryptography-45.0.4-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:964bcc28d867e0f5491a564b7debb3ffdd8717928d315d12e0d7defa9e43b723"}, - {file = "cryptography-45.0.4-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6a5bf57554e80f75a7db3d4b1dacaa2764611ae166ab42ea9a72bcdb5d577637"}, - {file = "cryptography-45.0.4-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:46cf7088bf91bdc9b26f9c55636492c1cce3e7aaf8041bbf0243f5e5325cfb2d"}, - {file = "cryptography-45.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7bedbe4cc930fa4b100fc845ea1ea5788fcd7ae9562e669989c11618ae8d76ee"}, - {file = "cryptography-45.0.4-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:eaa3e28ea2235b33220b949c5a0d6cf79baa80eab2eb5607ca8ab7525331b9ff"}, - {file = "cryptography-45.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7ef2dde4fa9408475038fc9aadfc1fb2676b174e68356359632e980c661ec8f6"}, - {file = "cryptography-45.0.4-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:6a3511ae33f09094185d111160fd192c67aa0a2a8d19b54d36e4c78f651dc5ad"}, - {file = "cryptography-45.0.4-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:06509dc70dd71fa56eaa138336244e2fbaf2ac164fc9b5e66828fccfd2b680d6"}, - {file = "cryptography-45.0.4-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:5f31e6b0a5a253f6aa49be67279be4a7e5a4ef259a9f33c69f7d1b1191939872"}, - {file = "cryptography-45.0.4-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:944e9ccf67a9594137f942d5b52c8d238b1b4e46c7a0c2891b7ae6e01e7c80a4"}, - {file = "cryptography-45.0.4-cp37-abi3-win32.whl", hash = "sha256:c22fe01e53dc65edd1945a2e6f0015e887f84ced233acecb64b4daadb32f5c97"}, - {file = "cryptography-45.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:627ba1bc94f6adf0b0a2e35d87020285ead22d9f648c7e75bb64f367375f3b22"}, - {file = "cryptography-45.0.4-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a77c6fb8d76e9c9f99f2f3437c1a4ac287b34eaf40997cfab1e9bd2be175ac39"}, - {file = "cryptography-45.0.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7aad98a25ed8ac917fdd8a9c1e706e5a0956e06c498be1f713b61734333a4507"}, - {file = "cryptography-45.0.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3530382a43a0e524bc931f187fc69ef4c42828cf7d7f592f7f249f602b5a4ab0"}, - {file = "cryptography-45.0.4-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:6b613164cb8425e2f8db5849ffb84892e523bf6d26deb8f9bb76ae86181fa12b"}, - {file = "cryptography-45.0.4-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:96d4819e25bf3b685199b304a0029ce4a3caf98947ce8a066c9137cc78ad2c58"}, - {file = "cryptography-45.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b97737a3ffbea79eebb062eb0d67d72307195035332501722a9ca86bab9e3ab2"}, - {file = "cryptography-45.0.4-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4828190fb6c4bcb6ebc6331f01fe66ae838bb3bd58e753b59d4b22eb444b996c"}, - {file = "cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:03dbff8411206713185b8cebe31bc5c0eb544799a50c09035733716b386e61a4"}, - {file = "cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:51dfbd4d26172d31150d84c19bbe06c68ea4b7f11bbc7b3a5e146b367c311349"}, - {file = "cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:0339a692de47084969500ee455e42c58e449461e0ec845a34a6a9b9bf7df7fb8"}, - {file = "cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:0cf13c77d710131d33e63626bd55ae7c0efb701ebdc2b3a7952b9b23a0412862"}, - {file = "cryptography-45.0.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bbc505d1dc469ac12a0a064214879eac6294038d6b24ae9f71faae1448a9608d"}, - {file = "cryptography-45.0.4.tar.gz", hash = "sha256:7405ade85c83c37682c8fe65554759800a4a8c54b2d96e0f8ad114d31b808d57"}, -] - -[package.dependencies] -cffi = {version = ">=1.14", markers = "platform_python_implementation != \"PyPy\""} - -[package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-inline-tabs", "sphinx-rtd-theme (>=3.0.0)"] -docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] -nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"] -pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] -sdist = ["build (>=1.0.0)"] -ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi (>=2024)", "cryptography-vectors (==45.0.4)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] -test-randomorder = ["pytest-randomly"] - -[[package]] -name = "decorator" -version = "5.2.1" -description = "Decorators for Humans" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a"}, - {file = "decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360"}, -] - -[[package]] -name = "deprecated" -version = "1.2.18" -description = "Python @deprecated decorator to deprecate old python classes, functions or methods." -category = "main" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -files = [ - {file = "Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec"}, - {file = "deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d"}, -] - -[package.dependencies] -wrapt = ">=1.10,<2" - -[package.extras] -dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "setuptools", "tox"] - -[[package]] -name = "fabric" -version = "3.2.2" -description = "High level SSH command execution" -category = "main" -optional = false -python-versions = "*" -files = [ - {file = "fabric-3.2.2-py3-none-any.whl", hash = "sha256:91c47c0be68b14936c88b34da8a1f55e5710fd28397dac5d4ff2e21558113a6f"}, - {file = "fabric-3.2.2.tar.gz", hash = "sha256:8783ca42e3b0076f08b26901aac6b9d9b1f19c410074e7accfab902c184ff4a3"}, -] - -[package.dependencies] -decorator = ">=5" -deprecated = ">=1.2" -invoke = ">=2.0" -paramiko = ">=2.4" - -[package.extras] -pytest = ["pytest (>=7)"] - -[[package]] -name = "invoke" -version = "2.2.0" -description = "Pythonic task execution" -category = "main" -optional = false -python-versions = ">=3.6" -files = [ - {file = "invoke-2.2.0-py3-none-any.whl", hash = "sha256:6ea924cc53d4f78e3d98bc436b08069a03077e6f85ad1ddaa8a116d7dad15820"}, - {file = "invoke-2.2.0.tar.gz", hash = "sha256:ee6cbb101af1a859c7fe84f2a264c059020b0cb7fe3535f9424300ab568f6bd5"}, -] - -[[package]] -name = "paramiko" -version = "3.5.1" -description = "SSH2 protocol library" -category = "main" -optional = false -python-versions = ">=3.6" -files = [ - {file = "paramiko-3.5.1-py3-none-any.whl", hash = "sha256:43b9a0501fc2b5e70680388d9346cf252cfb7d00b0667c39e80eb43a408b8f61"}, - {file = "paramiko-3.5.1.tar.gz", hash = "sha256:b2c665bc45b2b215bd7d7f039901b14b067da00f3a11e6640995fd58f2664822"}, -] - -[package.dependencies] -bcrypt = ">=3.2" -cryptography = ">=3.3" -pynacl = ">=1.5" - -[package.extras] -all = ["gssapi (>=1.4.1)", "invoke (>=2.0)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"] -gssapi = ["gssapi (>=1.4.1)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"] -invoke = ["invoke (>=2.0)"] - -[[package]] -name = "pycparser" -version = "2.22" -description = "C parser in Python" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, - {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, -] - -[[package]] -name = "pynacl" -version = "1.5.0" -description = "Python binding to the Networking and Cryptography (NaCl) library" -category = "main" -optional = false -python-versions = ">=3.6" -files = [ - {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"}, - {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"}, - {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"}, - {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"}, - {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"}, - {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"}, - {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"}, - {file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"}, - {file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"}, - {file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"}, -] - -[package.dependencies] -cffi = ">=1.4.1" - -[package.extras] -docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] -tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] - -[[package]] -name = "wrapt" -version = "1.17.2" -description = "Module for decorators, wrappers and monkey patching." -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984"}, - {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22"}, - {file = "wrapt-1.17.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80dd7db6a7cb57ffbc279c4394246414ec99537ae81ffd702443335a61dbf3a7"}, - {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a6e821770cf99cc586d33833b2ff32faebdbe886bd6322395606cf55153246c"}, - {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b60fb58b90c6d63779cb0c0c54eeb38941bae3ecf7a73c764c52c88c2dcb9d72"}, - {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b870b5df5b71d8c3359d21be8f0d6c485fa0ebdb6477dda51a1ea54a9b558061"}, - {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4011d137b9955791f9084749cba9a367c68d50ab8d11d64c50ba1688c9b457f2"}, - {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1473400e5b2733e58b396a04eb7f35f541e1fb976d0c0724d0223dd607e0f74c"}, - {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3cedbfa9c940fdad3e6e941db7138e26ce8aad38ab5fe9dcfadfed9db7a54e62"}, - {file = "wrapt-1.17.2-cp310-cp310-win32.whl", hash = "sha256:582530701bff1dec6779efa00c516496968edd851fba224fbd86e46cc6b73563"}, - {file = "wrapt-1.17.2-cp310-cp310-win_amd64.whl", hash = "sha256:58705da316756681ad3c9c73fd15499aa4d8c69f9fd38dc8a35e06c12468582f"}, - {file = "wrapt-1.17.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ff04ef6eec3eee8a5efef2401495967a916feaa353643defcc03fc74fe213b58"}, - {file = "wrapt-1.17.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4db983e7bca53819efdbd64590ee96c9213894272c776966ca6306b73e4affda"}, - {file = "wrapt-1.17.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9abc77a4ce4c6f2a3168ff34b1da9b0f311a8f1cfd694ec96b0603dff1c79438"}, - {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b929ac182f5ace000d459c59c2c9c33047e20e935f8e39371fa6e3b85d56f4a"}, - {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f09b286faeff3c750a879d336fb6d8713206fc97af3adc14def0cdd349df6000"}, - {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7ed2d9d039bd41e889f6fb9364554052ca21ce823580f6a07c4ec245c1f5d6"}, - {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:129a150f5c445165ff941fc02ee27df65940fcb8a22a61828b1853c98763a64b"}, - {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1fb5699e4464afe5c7e65fa51d4f99e0b2eadcc176e4aa33600a3df7801d6662"}, - {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9a2bce789a5ea90e51a02dfcc39e31b7f1e662bc3317979aa7e5538e3a034f72"}, - {file = "wrapt-1.17.2-cp311-cp311-win32.whl", hash = "sha256:4afd5814270fdf6380616b321fd31435a462019d834f83c8611a0ce7484c7317"}, - {file = "wrapt-1.17.2-cp311-cp311-win_amd64.whl", hash = "sha256:acc130bc0375999da18e3d19e5a86403667ac0c4042a094fefb7eec8ebac7cf3"}, - {file = "wrapt-1.17.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d5e2439eecc762cd85e7bd37161d4714aa03a33c5ba884e26c81559817ca0925"}, - {file = "wrapt-1.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fc7cb4c1c744f8c05cd5f9438a3caa6ab94ce8344e952d7c45a8ed59dd88392"}, - {file = "wrapt-1.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fdbdb757d5390f7c675e558fd3186d590973244fab0c5fe63d373ade3e99d40"}, - {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb1d0dbf99411f3d871deb6faa9aabb9d4e744d67dcaaa05399af89d847a91d"}, - {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d18a4865f46b8579d44e4fe1e2bcbc6472ad83d98e22a26c963d46e4c125ef0b"}, - {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc570b5f14a79734437cb7b0500376b6b791153314986074486e0b0fa8d71d98"}, - {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6d9187b01bebc3875bac9b087948a2bccefe464a7d8f627cf6e48b1bbae30f82"}, - {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9e8659775f1adf02eb1e6f109751268e493c73716ca5761f8acb695e52a756ae"}, - {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8b2816ebef96d83657b56306152a93909a83f23994f4b30ad4573b00bd11bb9"}, - {file = "wrapt-1.17.2-cp312-cp312-win32.whl", hash = "sha256:468090021f391fe0056ad3e807e3d9034e0fd01adcd3bdfba977b6fdf4213ea9"}, - {file = "wrapt-1.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:ec89ed91f2fa8e3f52ae53cd3cf640d6feff92ba90d62236a81e4e563ac0e991"}, - {file = "wrapt-1.17.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6ed6ffac43aecfe6d86ec5b74b06a5be33d5bb9243d055141e8cabb12aa08125"}, - {file = "wrapt-1.17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35621ae4c00e056adb0009f8e86e28eb4a41a4bfa8f9bfa9fca7d343fe94f998"}, - {file = "wrapt-1.17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a604bf7a053f8362d27eb9fefd2097f82600b856d5abe996d623babd067b1ab5"}, - {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cbabee4f083b6b4cd282f5b817a867cf0b1028c54d445b7ec7cfe6505057cf8"}, - {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49703ce2ddc220df165bd2962f8e03b84c89fee2d65e1c24a7defff6f988f4d6"}, - {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8112e52c5822fc4253f3901b676c55ddf288614dc7011634e2719718eaa187dc"}, - {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fee687dce376205d9a494e9c121e27183b2a3df18037f89d69bd7b35bcf59e2"}, - {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:18983c537e04d11cf027fbb60a1e8dfd5190e2b60cc27bc0808e653e7b218d1b"}, - {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:703919b1633412ab54bcf920ab388735832fdcb9f9a00ae49387f0fe67dad504"}, - {file = "wrapt-1.17.2-cp313-cp313-win32.whl", hash = "sha256:abbb9e76177c35d4e8568e58650aa6926040d6a9f6f03435b7a522bf1c487f9a"}, - {file = "wrapt-1.17.2-cp313-cp313-win_amd64.whl", hash = "sha256:69606d7bb691b50a4240ce6b22ebb319c1cfb164e5f6569835058196e0f3a845"}, - {file = "wrapt-1.17.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4a721d3c943dae44f8e243b380cb645a709ba5bd35d3ad27bc2ed947e9c68192"}, - {file = "wrapt-1.17.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:766d8bbefcb9e00c3ac3b000d9acc51f1b399513f44d77dfe0eb026ad7c9a19b"}, - {file = "wrapt-1.17.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e496a8ce2c256da1eb98bd15803a79bee00fc351f5dfb9ea82594a3f058309e0"}, - {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d615e4fe22f4ad3528448c193b218e077656ca9ccb22ce2cb20db730f8d306"}, - {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5aaeff38654462bc4b09023918b7f21790efb807f54c000a39d41d69cf552cb"}, - {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7d15bbd2bc99e92e39f49a04653062ee6085c0e18b3b7512a4f2fe91f2d681"}, - {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e3890b508a23299083e065f435a492b5435eba6e304a7114d2f919d400888cc6"}, - {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8c8b293cd65ad716d13d8dd3624e42e5a19cc2a2f1acc74b30c2c13f15cb61a6"}, - {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c82b8785d98cdd9fed4cac84d765d234ed3251bd6afe34cb7ac523cb93e8b4f"}, - {file = "wrapt-1.17.2-cp313-cp313t-win32.whl", hash = "sha256:13e6afb7fe71fe7485a4550a8844cc9ffbe263c0f1a1eea569bc7091d4898555"}, - {file = "wrapt-1.17.2-cp313-cp313t-win_amd64.whl", hash = "sha256:eaf675418ed6b3b31c7a989fd007fa7c3be66ce14e5c3b27336383604c9da85c"}, - {file = "wrapt-1.17.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5c803c401ea1c1c18de70a06a6f79fcc9c5acfc79133e9869e730ad7f8ad8ef9"}, - {file = "wrapt-1.17.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f917c1180fdb8623c2b75a99192f4025e412597c50b2ac870f156de8fb101119"}, - {file = "wrapt-1.17.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ecc840861360ba9d176d413a5489b9a0aff6d6303d7e733e2c4623cfa26904a6"}, - {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb87745b2e6dc56361bfde481d5a378dc314b252a98d7dd19a651a3fa58f24a9"}, - {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58455b79ec2661c3600e65c0a716955adc2410f7383755d537584b0de41b1d8a"}, - {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4e42a40a5e164cbfdb7b386c966a588b1047558a990981ace551ed7e12ca9c2"}, - {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:91bd7d1773e64019f9288b7a5101f3ae50d3d8e6b1de7edee9c2ccc1d32f0c0a"}, - {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:bb90fb8bda722a1b9d48ac1e6c38f923ea757b3baf8ebd0c82e09c5c1a0e7a04"}, - {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:08e7ce672e35efa54c5024936e559469436f8b8096253404faeb54d2a878416f"}, - {file = "wrapt-1.17.2-cp38-cp38-win32.whl", hash = "sha256:410a92fefd2e0e10d26210e1dfb4a876ddaf8439ef60d6434f21ef8d87efc5b7"}, - {file = "wrapt-1.17.2-cp38-cp38-win_amd64.whl", hash = "sha256:95c658736ec15602da0ed73f312d410117723914a5c91a14ee4cdd72f1d790b3"}, - {file = "wrapt-1.17.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99039fa9e6306880572915728d7f6c24a86ec57b0a83f6b2491e1d8ab0235b9a"}, - {file = "wrapt-1.17.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2696993ee1eebd20b8e4ee4356483c4cb696066ddc24bd70bcbb80fa56ff9061"}, - {file = "wrapt-1.17.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:612dff5db80beef9e649c6d803a8d50c409082f1fedc9dbcdfde2983b2025b82"}, - {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62c2caa1585c82b3f7a7ab56afef7b3602021d6da34fbc1cf234ff139fed3cd9"}, - {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c958bcfd59bacc2d0249dcfe575e71da54f9dcf4a8bdf89c4cb9a68a1170d73f"}, - {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc78a84e2dfbc27afe4b2bd7c80c8db9bca75cc5b85df52bfe634596a1da846b"}, - {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ba0f0eb61ef00ea10e00eb53a9129501f52385c44853dbd6c4ad3f403603083f"}, - {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1e1fe0e6ab7775fd842bc39e86f6dcfc4507ab0ffe206093e76d61cde37225c8"}, - {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c86563182421896d73858e08e1db93afdd2b947a70064b813d515d66549e15f9"}, - {file = "wrapt-1.17.2-cp39-cp39-win32.whl", hash = "sha256:f393cda562f79828f38a819f4788641ac7c4085f30f1ce1a68672baa686482bb"}, - {file = "wrapt-1.17.2-cp39-cp39-win_amd64.whl", hash = "sha256:36ccae62f64235cf8ddb682073a60519426fdd4725524ae38874adf72b5f2aeb"}, - {file = "wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8"}, - {file = "wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3"}, -] - -[metadata] -lock-version = "2.0" -python-versions = "^3.10" -content-hash = "ecdfcbebbc69839a4c89fe5b437c6430f8fd08517a5b788b53ab5c09bb95e4ec" diff --git a/pyproject.toml b/pyproject.toml index 81d887a..cd865ae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,10 @@ [project] name = "xet" -version = "1.1.0" +version = "1.2.0" [tool.poetry] name = "xet" -version = "1.1.0" +version = "1.2.0" description = "CLI Tool for quickly updating values across your project/machine." readme = "README.md" authors = ["Art"] @@ -13,10 +13,23 @@ authors = ["Art"] python = "^3.10" argparse = "^1.4.0" fabric = "^3.2.2" +colorama = "^0.4.6" +diff-match-patch = "20241021" + +[tool.poetry.group.dev.dependencies] +pre-commit = "4.2.0" +pytest = "^8.4.1" +pytest-cov = "^6.2.1" [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" +[tool.pytest.ini_options] +markers = [ + "noinit: does not auto-init .xet in tests", + "serial", +] + [project.scripts] xet = "xet.cli:main" diff --git a/tests/data/test.txt b/tests/data/test.txt new file mode 100644 index 0000000..813bfb5 --- /dev/null +++ b/tests/data/test.txt @@ -0,0 +1,12 @@ +TEST1 = ABC +TEST2 = "DEF" +TEST3: 'ghi' +TEST4, __jkl__ +TEST5: mno +TEST5: pqr +TEST6....7:16: sTu +vwx 8:1 TEST7 +TEST8 rege_xyz_ +TEST9 regexyz + +TEST_EQUALITYTEST_EQUALITY diff --git a/tests/data/test_numbers.txt b/tests/data/test_numbers.txt new file mode 100644 index 0000000..75c4b6f --- /dev/null +++ b/tests/data/test_numbers.txt @@ -0,0 +1,10 @@ +TEST1 = 123 +TEST2 = "456" +TEST3: '789' +TEST4, __101112__ +TEST5: 131415 +TEST5: 161718 +TEST6....7:16: 192021 +222324 8:1 TEST7 +TEST8 rege_252627_ +TEST9 rege282930 diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_xet.py b/tests/test_xet.py new file mode 100644 index 0000000..c62a333 --- /dev/null +++ b/tests/test_xet.py @@ -0,0 +1,950 @@ +import os +import shutil +from pathlib import Path +from unittest.mock import patch + +import pytest + +import xet + +patcher = patch( + "xet.xet.get_history_path", + lambda: os.path.join(os.path.abspath(os.getcwd()), "tmp", xet.HISTORY_FILE), +) +patcher.start() + + +@pytest.fixture(autouse=True) +def change_test_dir(monkeypatch): + monkeypatch.chdir(os.path.abspath(__file__.rstrip(".py").rstrip(__name__))) + + +@pytest.fixture(autouse=True) +def delete_dot_xet(): + if os.path.exists(os.path.join(os.path.abspath(os.getcwd()), xet.CONFIG_FILE)): + os.remove(os.path.join(os.path.abspath(os.getcwd()), xet.CONFIG_FILE)) + yield + if os.path.exists(os.path.join(os.path.abspath(os.getcwd()), xet.CONFIG_FILE)): + os.remove(os.path.join(os.path.abspath(os.getcwd()), xet.CONFIG_FILE)) + + +@pytest.fixture(autouse=True) +def init_dot_xet(request): + if "noinit" in request.keywords: + return + xet.main(["init"]) + + +@pytest.fixture(autouse=True) +def data_path(): + test_path = os.path.abspath("tmp") + if os.path.exists(test_path): + shutil.rmtree(test_path) + backup_path = os.path.abspath("data") + shutil.copytree(backup_path, test_path) + yield Path(test_path) + shutil.rmtree(test_path) + + +def test_tests(): + assert True + + +def test_file_restore(data_path): + os.remove(os.path.abspath(data_path / "test.txt")) + os.remove(data_path / "test_numbers.txt") + assert not os.path.exists(os.path.abspath(data_path / "test.txt")) + assert not os.path.exists(data_path / "test_numbers.txt") + + +@pytest.mark.noinit +def test_which(capsys): + xet.main(["which"]) + output = capsys.readouterr().out.rstrip() + + expected_output = os.path.join(os.environ.get("XDG_CONFIG_HOME"), xet.CONFIG_FILE) + assert output == expected_output + assert xet.get_abs_config_path() == expected_output + + xet.main(["init"]) + + xet.main(["which"]) + output = capsys.readouterr().out.rstrip() + expected_output = os.path.join(os.path.abspath(os.getcwd()), xet.CONFIG_FILE) + assert output == expected_output + assert xet.get_abs_config_path() == expected_output + + +@pytest.mark.noinit +def test_init(): + assert not os.path.exists( + os.path.join(os.path.abspath(os.getcwd()), xet.CONFIG_FILE) + ) + xet.main(["init"]) + assert os.path.exists(os.path.join(os.path.abspath(os.getcwd()), xet.CONFIG_FILE)) + + +def test_add(data_path): + global_config_options = [ + "type", + "filepath", + "flags", + "presets", + "ssh", + "wrapper", + ] + + tag_config_options = ["tag", "occurences", "end"] + + lc_config_options = ["line", "column", "end"] + + regex_config_options = ["regex", "occurences", "group"] + + xet.main( + ["add", "tag", "test_tag", os.path.abspath(data_path / "test.txt"), "TEST1 = "] + ) + xet.main( + ["add", "lc", "test_lc", os.path.abspath(data_path / "test.txt"), "420", "69"] + ) + xet.main( + [ + "add", + "regex", + "test_regex", + os.path.abspath(data_path / "test.txt"), + "^regex:", + ] + ) + + config = xet.parse_config() + + assert "test_tag" in config + assert "test_lc" in config + assert "test_regex" in config + + assert all( + [ + config_option in config["test_tag"] + for config_option in global_config_options + tag_config_options + ] + ) + + assert all( + [ + config_option in config["test_lc"] + for config_option in global_config_options + lc_config_options + ] + ) + + assert all( + [ + config_option in config["test_regex"] + for config_option in global_config_options + regex_config_options + ] + ) + + +def test_get_tag(capsys, data_path): + xet.main( + ["add", "tag", "test_1", os.path.abspath(data_path / "test.txt"), "TEST1 = "] + ) + xet.main( + [ + "add", + "tag", + "test_2", + os.path.abspath(data_path / "test.txt"), + "TEST2 = ", + "-w", + '"', + ] + ) + xet.main( + [ + "add", + "tag", + "test_3", + os.path.abspath(data_path / "test.txt"), + "TEST3: ", + "-w", + "'", + ] + ) + xet.main( + [ + "add", + "tag", + "test_4", + os.path.abspath(data_path / "test.txt"), + "TEST4, ", + "-w", + "__", + ] + ) + xet.main( + ["add", "tag", "test_5", os.path.abspath(data_path / "test.txt"), "TEST5: "] + ) + + xet.main(["get"]) + + output = capsys.readouterr().out.rstrip() + + assert output == "ABC\nDEF\nghi\njkl\nmno\npqr" + + xet.main(["get", "-v"]) + + output = capsys.readouterr().out.rstrip() + + assert output == ( + "\x1b[34mTEST1 = \x1b[0m\x1b[31mABC\x1b[0m\n" + '\x1b[34mTEST2 = \x1b[0m"\x1b[31mDEF\x1b[0m"\n' + "\x1b[34mTEST3: \x1b[0m'\x1b[31mghi\x1b[0m'\n" + "\x1b[34mTEST4, \x1b[0m__\x1b[31mjkl\x1b[0m__\n" + "\x1b[34mTEST5: \x1b[0m\x1b[31mmno\x1b[0m\n" + "\x1b[34mTEST5: \x1b[0m\x1b[31mpqr\x1b[0m" + ) + + xet.main(["get", "-vv"]) + + output = capsys.readouterr().out.rstrip() + filepath = os.path.abspath(data_path / "test.txt") + assert output == ( + f"\x1b[32mtest_1\x1b[36m:\x1b[35m" + f"{filepath}" + f"\x1b[36m:\x1b[34mTEST1 = \x1b[36m:\x1b[0m\n" + f"\x1b[34mTEST1 = \x1b[0m\x1b[31mABC\x1b[0m\n" + f"\x1b[32mtest_2\x1b[36m:\x1b[35m" + f"{filepath}" + f"\x1b[36m:\x1b[34mTEST2 = \x1b[36m:\x1b[0m\n" + f'\x1b[34mTEST2 = \x1b[0m"\x1b[31mDEF\x1b[0m"\n' + f"\x1b[32mtest_3\x1b[36m:\x1b[35m" + f"{filepath}" + f"\x1b[36m:\x1b[34mTEST3: \x1b[36m:\x1b[0m\n" + f"\x1b[34mTEST3: \x1b[0m'\x1b[31mghi\x1b[0m'\n" + f"\x1b[32mtest_4\x1b[36m:\x1b[35m" + f"{filepath}" + f"\x1b[36m:\x1b[34mTEST4, \x1b[36m:\x1b[0m\n" + f"\x1b[34mTEST4, \x1b[0m__\x1b[31mjkl\x1b[0m__\n" + f"\x1b[32mtest_5\x1b[36m:\x1b[35m" + f"{filepath}" + f"\x1b[36m:\x1b[34mTEST5: \x1b[36m:\x1b[0m\n" + f"\x1b[34mTEST5: \x1b[0m\x1b[31mmno\x1b[0m\n" + f"\x1b[34mTEST5: \x1b[0m\x1b[31mpqr\x1b[0m" + ) + + +def test_get_lc(capsys, data_path): + xet.main( + ["add", "lc", "test_6", os.path.abspath(data_path / "test.txt"), "7", "16"] + ) + xet.main( + [ + "add", + "lc", + "test_7", + os.path.abspath(data_path / "test.txt"), + "8", + "1", + "-e", + " 8:1 TEST7", + ] + ) + + xet.main(["get"]) + + output = capsys.readouterr().out.rstrip() + + assert output == "sTu\nvwx" + + xet.main(["get", "-v"]) + + output = capsys.readouterr().out.rstrip() + + assert output == ( + "TEST6....7:16: \x1b[31msTu\x1b[0m" "\n\x1b[31mvwx\x1b[0m 8:1 TEST7" + ) + + +def test_get_regex(capsys, data_path): + xet.main( + [ + "add", + "regex", + "test_8", + os.path.abspath(data_path / "test.txt"), + r"^\w* rege", + "--wrapper", + "_", + "--occurences", + "0", + ] + ) + xet.main( + [ + "add", + "regex", + "test_9", + os.path.abspath(data_path / "test.txt"), + r"^\w* rege", + "--occurences", + "1", + ] + ) + + xet.main( + [ + "add", + "regex", + "test_10", + os.path.abspath(data_path / "test.txt"), + r"(^\w* rege)(_)(\w*)(_)", + "-c", + "3", + "-f", + "group", + ] + ) + + xet.main(["get", "-e", "group"]) + + output = capsys.readouterr().out.rstrip() + + assert output == "xyz\nxyz" + + xet.main(["get", "-o", "group"]) + + output = capsys.readouterr().out.rstrip() + + assert output == "xyz" + + xet.main(["get", "-e", "group", "-v"]) + + output = capsys.readouterr().out.rstrip() + + assert output == ("TEST8 rege_\x1b[31mxyz\x1b[0m_" "\nTEST9 rege\x1b[31mxyz\x1b[0m") + + +def test_get_occurences(capsys, data_path): + xet.main( + [ + "add", + "regex", + "test_occ", + os.path.abspath(data_path / "test.txt"), + r"^TEST\d", + "--occurences", + "1:3", + ] + ) + + xet.main(["get"]) + + output = capsys.readouterr().out.rstrip() + + assert output == " = \"DEF\"\n: 'ghi'" + + xet.main( + [ + "add", + "regex", + "test_occ", + os.path.abspath(data_path / "test.txt"), + r"^TEST\d", + "--occurences", + "1", + "2", + ] + ) + + xet.main(["get"]) + + output = capsys.readouterr().out.rstrip() + + assert output == " = \"DEF\"\n: 'ghi'" + + +def test_filtering(capsys, data_path): + xet.main( + [ + "add", + "tag", + "test_1", + os.path.abspath(data_path / "test.txt"), + "TEST1 = ", + "-f", + "f1", + ] + ) + xet.main( + [ + "add", + "tag", + "test_2", + os.path.abspath(data_path / "test.txt"), + "TEST2 = ", + "-w", + '"', + "-f", + "f1", + "f2", + ] + ) + xet.main( + [ + "add", + "tag", + "test_3", + os.path.abspath(data_path / "test.txt"), + "TEST3: ", + "-w", + "'", + "-f", + "f2", + ] + ) + xet.main( + [ + "add", + "tag", + "test_4", + os.path.abspath(data_path / "test.txt"), + "TEST4, ", + "-w", + "__", + "-f", + "f3", + ] + ) + xet.main( + [ + "add", + "tag", + "test_5", + os.path.abspath(data_path / "test.txt"), + "TEST5: ", + "--flags", + "f3", + "f2", + ] + ) + + test_scenarios = [ + (["get", "-o", "f1"], "ABC\nDEF"), + (["get", "-o", "f1", "f2"], "ABC\nDEF\nghi\nmno\npqr"), + (["get", "--only", "f3"], "jkl\nmno\npqr"), + (["get", "-e", "f2", "f3"], "ABC"), + (["get", "--except", "f1"], "ghi\njkl\nmno\npqr"), + (["get", "-n", "test_2"], "DEF"), + (["get", "--names", "test_2", "test_3"], "DEF\nghi"), + ] + + for input, expected in test_scenarios: + xet.main(input) + + output = capsys.readouterr().out.rstrip() + + assert output == expected + + +def test_set_tag(capsys, data_path): + xet.main( + ["add", "tag", "test_1", os.path.abspath(data_path / "test.txt"), "TEST1 = "] + ) + xet.main( + [ + "add", + "tag", + "test_2", + os.path.abspath(data_path / "test.txt"), + "TEST2 = ", + "-w", + '"', + ] + ) + xet.main( + [ + "add", + "tag", + "test_3", + os.path.abspath(data_path / "test.txt"), + "TEST3: ", + "-w", + "'", + ] + ) + xet.main( + [ + "add", + "tag", + "test_4", + os.path.abspath(data_path / "test.txt"), + "TEST4, ", + "-w", + "__", + ] + ) + + xet.main(["set", "TEST"]) + + xet.main(["get"]) + + output = capsys.readouterr().out.rstrip() + + assert output == "TEST\nTEST\nTEST\nTEST" + + +def test_tag_equality(capsys, data_path): + xet.main( + [ + "add", + "tag", + "eq_test", + os.path.abspath(data_path / "test.txt"), + "TEST_EQUALITY", + ] + ) + + xet.main(["get"]) + + output = capsys.readouterr().out.rstrip() + + assert output == "TEST_EQUALITY" + + +def test_set_lc(capsys, data_path): + xet.main( + ["add", "lc", "test_6", os.path.abspath(data_path / "test.txt"), "7", "16"] + ) + xet.main( + [ + "add", + "lc", + "test_7", + os.path.abspath(data_path / "test.txt"), + "8", + "1", + "-e", + " 8:1 TEST7", + ] + ) + + xet.main(["set", "TEST"]) + + xet.main(["get"]) + + output = capsys.readouterr().out.rstrip() + + assert output == "TEST\nTEST" + + +def test_set_regex(capsys, data_path): + xet.main( + [ + "add", + "regex", + "test_8", + os.path.abspath(data_path / "test.txt"), + r"^\w* rege", + "--wrapper", + "_", + "--occurences", + "0", + ] + ) + xet.main( + [ + "add", + "regex", + "test_9", + os.path.abspath(data_path / "test.txt"), + r"^\w* rege", + "--occurences", + "1", + ] + ) + xet.main( + [ + "add", + "regex", + "test_10", + os.path.abspath(data_path / "test.txt"), + r"(^\w* rege)(_)(\w*)(_)", + "-c", + "3", + "-f", + "group", + ] + ) + + xet.main(["set", "TEST", "-e", "group"]) + + xet.main(["get", "-e", "group"]) + + output = capsys.readouterr().out.rstrip() + + assert output == "TEST\nTEST" + + xet.main(["set", "xyz", "-e", "group"]) + + xet.main(["get"]) + + output = capsys.readouterr().out.rstrip() + + assert output == "xyz\nxyz\nxyz" + + xet.main(["set", "TEST", "-o", "group"]) + + xet.main(["get", "-o", "group"]) + + output = capsys.readouterr().out.rstrip() + + assert output == "TEST" + + +def test_preset_snapshot(capsys, data_path): + xet.main( + [ + "add", + "tag", + "test_1", + os.path.abspath(data_path / "test.txt"), + "TEST1 = ", + "-p", + "pre1", + "test", + ] + ) + xet.main( + [ + "add", + "tag", + "test_2", + os.path.abspath(data_path / "test.txt"), + "TEST2 = ", + "-w", + '"', + "-p", + "pre1", + "test", + ] + ) + xet.main( + [ + "add", + "tag", + "test_3", + os.path.abspath(data_path / "test.txt"), + "TEST3: ", + "-w", + "'", + "-p", + "pre1", + "test", + ] + ) + xet.main( + [ + "add", + "tag", + "test_4", + os.path.abspath(data_path / "test.txt"), + "TEST4, ", + "-w", + "__", + "-p", + "pre1", + "test", + ] + ) + xet.main( + [ + "add", + "tag", + "test_5", + os.path.abspath(data_path / "test.txt"), + "TEST5: ", + "-o", + "0", + "-p", + "pre1", + "test", + ] + ) + + xet.main(["snapshot", "pre2"]) + + xet.main(["undo"]) + + config = xet.parse_config() + + for entry in config.values(): + assert "pre2" not in entry["presets"] + + xet.main(["redo"]) + + xet.main(["preset", "pre1"]) + + xet.main(["get"]) + + output = capsys.readouterr().out.rstrip() + + assert output == "test\ntest\ntest\ntest\ntest" + + xet.main(["preset", "pre2"]) + + xet.main(["get"]) + + output = capsys.readouterr().out.rstrip() + + assert output == "ABC\nDEF\nghi\njkl\nmno" + + xet.main(["undo"]) + + xet.main(["get"]) + + output = capsys.readouterr().out.rstrip() + + assert output == "test\ntest\ntest\ntest\ntest" + + +def test_history(capsys, data_path): + xet.main( + [ + "add", + "tag", + "test_1", + os.path.abspath(data_path / "test.txt"), + "TEST1 = ", + ] + ) + + xet.main(["undo"]) + + xet.main(["get"]) + + output = capsys.readouterr().out.rstrip() + + assert output == "" + + xet.main(["redo"]) + + xet.main(["get"]) + + output = capsys.readouterr().out.rstrip() + + assert output == "ABC" + + xet.main(["remove", "-n", "test_1"]) + + xet.main(["get"]) + + output = capsys.readouterr().out.rstrip() + + assert output == "" + + xet.main(["undo"]) + + xet.main(["get"]) + + output = capsys.readouterr().out.rstrip() + + assert output == "ABC" + + xet.main(["forget"]) + + xet.main(["undo"]) + + output = capsys.readouterr().out.rstrip() + + assert output == "Nothing to undo" + + xet.main(["redo"]) + + output = capsys.readouterr().out.rstrip() + + assert output == "Nothing to redo" + + xet.main(["set", "TEST"]) + xet.main(["undo"]) + xet.main(["get"]) + + output = capsys.readouterr().out.rstrip() + + assert output == "ABC" + + xet.main(["redo"]) + xet.main(["get"]) + + output = capsys.readouterr().out.rstrip() + + assert output == "TEST" + + +def test_update(capsys, data_path): + xet.main( + [ + "add", + "tag", + "test_1", + os.path.abspath(data_path / "test.txt"), + "TEST1 = ", + ] + ) + + xet.main(["update", "tag", "TEST2 = "]) + + xet.main(["update", "wrapper", '"']) + + xet.main(["get"]) + + output = capsys.readouterr().out.rstrip() + + assert output == "DEF" + + xet.main(["update", "name", "renamed_test"]) + + xet.main(["get", "-n", "renamed_test"]) + + output = capsys.readouterr().out.rstrip() + + assert output == "DEF" + + +def test_enumerate_slice(): + length = 10 + sl = xet.parse_index_or_slice("1:5") + + assert [1, 2, 3, 4] == list(range(length))[sl] + + +def test_snapshot_split(capsys, data_path): + xet.main( + ["add", "tag", "test_1", os.path.abspath(data_path / "test.txt"), "TEST1 = "] + ) + xet.main( + [ + "add", + "tag", + "test_2", + os.path.abspath(data_path / "test.txt"), + "TEST2 = ", + "-w", + '"', + ] + ) + xet.main( + [ + "add", + "tag", + "test_3", + os.path.abspath(data_path / "test.txt"), + "TEST3: ", + "-w", + "'", + ] + ) + xet.main( + [ + "add", + "tag", + "test_4", + os.path.abspath(data_path / "test.txt"), + "TEST4, ", + "-w", + "__", + ] + ) + xet.main( + ["add", "tag", "test_5", os.path.abspath(data_path / "test.txt"), "TEST5: "] + ) + + xet.main(["snapshot", "preset1"]) + + output = capsys.readouterr().out.rstrip() + + assert output == ( + "Cannot snapshot entry test_5," + "divergent occurence values detected." + "Use --split or --first." + ) + + xet.main(["snapshot", "preset2", "--split"]) + + output = capsys.readouterr().out.rstrip() + + assert output == "" + + config = xet.parse_config() + assert len(config) == 6 + + xet.main(["set", "TEST"]) + + xet.main(["preset", "preset1"]) + + xet.main(["get"]) + + output = capsys.readouterr().out.rstrip() + + assert output == "ABC\nDEF\nghi\njkl\nTEST\nTEST" + + xet.main(["preset", "preset2"]) + + xet.main(["get"]) + + output = capsys.readouterr().out.rstrip() + + assert output == "ABC\nDEF\nghi\njkl\nmno\npqr" + + +def test_snapshot_first(capsys, data_path): + xet.main( + ["add", "tag", "test_1", os.path.abspath(data_path / "test.txt"), "TEST1 = "] + ) + xet.main( + [ + "add", + "tag", + "test_2", + os.path.abspath(data_path / "test.txt"), + "TEST2 = ", + "-w", + '"', + ] + ) + xet.main( + [ + "add", + "tag", + "test_3", + os.path.abspath(data_path / "test.txt"), + "TEST3: ", + "-w", + "'", + ] + ) + xet.main( + [ + "add", + "tag", + "test_4", + os.path.abspath(data_path / "test.txt"), + "TEST4, ", + "-w", + "__", + ] + ) + xet.main( + ["add", "tag", "test_5", os.path.abspath(data_path / "test.txt"), "TEST5: "] + ) + + xet.main(["snapshot", "preset1", "--first"]) + + xet.main(["set", "TEST"]) + + xet.main(["preset", "preset1"]) + + xet.main(["get"]) + + output = capsys.readouterr().out.rstrip() + + assert output == "ABC\nDEF\nghi\njkl\nmno\nmno" diff --git a/xet/__init__.py b/xet/__init__.py index 2c04e88..eb88f75 100644 --- a/xet/__init__.py +++ b/xet/__init__.py @@ -1 +1 @@ -from .xet import * +from .xet import * # noqa diff --git a/xet/__main__.py b/xet/__main__.py index 087bf79..58417ba 100644 --- a/xet/__main__.py +++ b/xet/__main__.py @@ -1,4 +1,4 @@ -if __name__ == "__main__": - from xet.cli import main +from xet.cli import main +if __name__ == "__main__": main() diff --git a/xet/xet.py b/xet/xet.py index 9e6acd2..ec4d739 100644 --- a/xet/xet.py +++ b/xet/xet.py @@ -3,14 +3,18 @@ import os import re import subprocess -import sys -from colorama import Fore, Style +from copy import deepcopy from typing import Union -from fabric import Connection +import diff_match_patch as dmp +from colorama import Fore, Style +from fabric import Connection +VERSION = "1.2.0" CONFIG_FILE = ".xet" -VERSION = "1.1.0" +HISTORY_FILE = ".xet_history" + +DMP = dmp.diff_match_patch() NL = "\n" @@ -21,9 +25,10 @@ SEP_COLOR = Fore.CYAN -def get_config_path(g=False): +def _get_config_path(g=False, init=False): """Return the config file path, supporting XDG_CONFIG_HOME for global config""" - if g: + + if g or (not os.path.exists(CONFIG_FILE) and not init): xdg_config = os.environ.get("XDG_CONFIG_HOME") if xdg_config: return os.path.join(xdg_config, CONFIG_FILE) @@ -33,30 +38,49 @@ def get_config_path(g=False): return CONFIG_FILE +def get_history_path(): + xdg_config = os.environ.get("XDG_CONFIG_HOME") + if xdg_config: + return os.path.join(xdg_config, HISTORY_FILE) + else: + return os.path.join(os.path.expanduser("~"), HISTORY_FILE) + + +def get_abs_config_path(g=False, init=False): + return os.path.abspath(_get_config_path(g=g, init=init)) + + def init_config(args): """Initialize a .xet file""" - if os.path.exists(get_config_path(args.g)): + if os.path.exists(get_abs_config_path(args.g, init=True)): print("Configuration already exists") return - with open(get_config_path(args.g), "w") as f: + with open(get_abs_config_path(args.g, init=True), "w") as f: json.dump({}, f) -def parse_config(except_flags=None, only_flags=None, names=None, preset=None, g=False): - """Parse .xet, handling entries and applying -e/-o/-n filters""" - - except_flags = set(except_flags) if except_flags else set() - only_flags = set(only_flags) if only_flags else set() +def filter_config( + except_flags=None, + only_flags=None, + names=None, + preset=None, + path=None, + g=False, +): + """Parse .xet, handling entries and applying -e/-o/-n/-p filters""" - config_path = get_config_path(g=g) + config_path = get_abs_config_path(g=g) if not os.path.exists(config_path): print(f"Error: Config file '{config_path}' not found. Run 'xet init' first") - sys.exit(1) - with open(config_path, mode="r") as f: + return + with open(config_path) as f: config: dict = json.load(f) + except_flags = set(except_flags) if except_flags else set() + only_flags = set(only_flags) if only_flags else set() + if preset: preset_entries = [ name for name in config.keys() if preset in config[name]["presets"] @@ -70,26 +94,65 @@ def parse_config(except_flags=None, only_flags=None, names=None, preset=None, g= names = [name for name in names if name in config] config = {k: v for k, v in zip(names, [config[name] for name in names])} + if path: + path_entries = [ + name for name in config.keys() if config[name]["filepath"] in path + ] + config = { + k: v for k, v in zip(path_entries, [config[name] for name in path_entries]) + } + filtered_config = {} for key, entry in config.items(): flags = entry["flags"] if (entry and "flags" in entry) else None if flags: - if except_flags and not any([flag in except_flags for flag in flags]): + if except_flags and any([flag in flags for flag in except_flags]): continue - if only_flags and any([flag in only_flags for flag in flags]): + if only_flags and not any([flag in flags for flag in only_flags]): continue elif only_flags: continue - if entry["type"] == "lc": - entry["column"] = entry["column"] - entry["line"] = entry["line"] + filtered_config[key] = entry - return filtered_config + return filtered_config.keys() -def _parse_index_or_slice(s): +def parse_config( + except_flags=None, only_flags=None, names=None, preset=None, path=None, g=False +): + config_path = get_abs_config_path(g=g) + + if not os.path.exists(config_path): + print(f"Error: Config file '{config_path}' not found. Run 'xet init' first") + return + with open(config_path) as f: + config: dict = json.load(f) + + filtered_keys = filter_config( + except_flags=except_flags, + only_flags=only_flags, + names=names, + preset=preset, + path=path, + g=g, + ) + + return {k: v for k, v in config.items() if k in filtered_keys} + + +def load_config(g=False): + config_path = get_abs_config_path(g=g) + + if not os.path.exists(config_path): + print(f"Error: Config file '{config_path}' not found. Run 'xet init' first") + return + with open(config_path) as f: + return f.readlines() + + +def parse_index_or_slice(s): if ":" in s: parts = s.split(":") parts = [int(p) if p else None for p in parts] @@ -103,7 +166,7 @@ def _sanitize_value( wrapper: str = None, end: str = None, ): - value = value if not end else value.rstrip(end) + value = value if not end else value[: value.rfind(end)] return value if not wrapper else value.lstrip(wrapper).split(wrapper)[0] @@ -115,16 +178,25 @@ def _color_value( def _color_tag(line: str = "", tag: str = ""): - return IDENTIFIER_COLOR + tag + Style.RESET_ALL + line.lstrip(tag) + return IDENTIFIER_COLOR + tag + Style.RESET_ALL + re.sub(tag, "", line, count=1) def _filter_occurences(occurences: list, filter: str = ":"): filter = filter if filter else ":" if isinstance(filter, str): - filtered_occurences = occurences[_parse_index_or_slice(filter)] + filtered_occurences = occurences[parse_index_or_slice(filter)] elif isinstance(filter, list): - filtered_occurences = [o for i, o in enumerate(occurences) if i in occurences] - return filtered_occurences + if len(filter) == 1: + filtered_occurences = occurences[parse_index_or_slice(filter[0])] + else: + filtered_occurences = [ + o for i, o in enumerate(occurences) if str(i) in filter + ] + return ( + filtered_occurences + if isinstance(filtered_occurences, list) + else [filtered_occurences] + ) def _get_file_lines(filepath: str = "", ssh: str = None): @@ -138,7 +210,7 @@ def _get_file_lines(filepath: str = "", ssh: str = None): finally: remote_file.close() else: - with open(filepath, "r") as f: + with open(filepath) as f: return f.read().splitlines() @@ -157,7 +229,7 @@ def _set_file_lines(filepath: str = "", ssh: str = None, lines: list = []): f.writelines(lines) -def _set_tag_value( +def _set_tag_values( filepath: str = "", tag: str = "", occurences_slice: Union[str, list[int]] = ":", @@ -170,6 +242,8 @@ def _set_tag_value( lines = _get_file_lines(filepath=filepath, ssh=ssh) + old_lines = deepcopy(lines) + for i, line in enumerate(lines): if line.startswith(tag): found_occurences.append(i) @@ -180,25 +254,29 @@ def _set_tag_value( for occurence_index in filtered_occurences: if wrapper: - after_wrapper = lines[occurence_index].lstrip(tag).split(wrapper)[2] + after_wrapper = re.sub(tag, "", lines[occurence_index], count=1).split( + wrapper + )[2] + re.sub(tag, "", lines[occurence_index], count=1) end = after_wrapper + end lines[occurence_index] = ( - f"{tag}{wrapper if wrapper is not None else ''}{value}{wrapper if wrapper is not None else ''}{end}" + f"{tag}{wrapper if wrapper is not None else ''}{value}" + f"{wrapper if wrapper is not None else ''}{end}" ) _set_file_lines(filepath=filepath, ssh=ssh, lines=lines) + return old_lines, lines -def _get_tag_value( + +def _get_tag_values( filepath: str = "", tag: str = "", occurences_slice: Union[str, list[int]] = ":", wrapper: str = None, end: str = "", - verbosity: int = 0, ssh: str = None, ): - found_occurences = [] lines = _get_file_lines(filepath=filepath, ssh=ssh) @@ -211,27 +289,36 @@ def _get_tag_value( occurences=found_occurences, filter=occurences_slice ) - for occurence_index in filtered_occurences: - sanitized_value = _sanitize_value( - value=lines[occurence_index].lstrip(tag), - wrapper=wrapper, - end=end, + return [ + ( + lines[occurence_index], + _sanitize_value( + value=re.sub(tag, "", lines[occurence_index], count=1), + wrapper=wrapper, + end=end, + ), ) + for occurence_index in filtered_occurences + ] + + +def _print_tag_values(sanitized_values, tag, verbosity): + for line, value in sanitized_values: if verbosity >= 1: print( _color_tag( line=_color_value( - line=lines[occurence_index], - value=sanitized_value, + line=line, + value=value, ), tag=tag, ) ) else: - print(sanitized_value) + print(value) -def _set_lc_value( +def _set_lc_values( filepath: str = "", line: str = "", column: int = 0, @@ -240,9 +327,10 @@ def _set_lc_value( value: str = "", ssh: str = None, ): - lines = _get_file_lines(filepath=filepath, ssh=ssh) + old_lines = deepcopy(lines) + line -= 1 column -= 1 @@ -257,45 +345,54 @@ def _set_lc_value( end = after_wrapper + end lines[line] = ( - f"{lines[line][:column]}{wrapper if wrapper is not None else ''}{value}{wrapper if wrapper is not None else ''}{end}" + f"{lines[line][:column]}{wrapper if wrapper is not None else ''}{value}" + f"{wrapper if wrapper is not None else ''}{end}" ) _set_file_lines(filepath=filepath, ssh=ssh, lines=lines) + return old_lines, lines + -def _get_lc_value( +def _get_lc_values( filepath: str = "", line: str = "", column: int = 0, wrapper: str = "", end: str = "", - verbosity: int = 0, ssh: str = None, ): - line -= 1 column -= 1 lines = _get_file_lines(filepath=filepath, ssh=ssh) - sanitized_value = _sanitize_value( - value=lines[line][column:], - wrapper=wrapper, - end=end, - ) + return [ + ( + lines[line], + _sanitize_value( + value=lines[line][column:], + wrapper=wrapper, + end=end, + ), + ) + ] + +def _print_lc_values(sanitized_value, verbosity): + line, value = sanitized_value[0] if verbosity >= 1: print( _color_value( - line=lines[line], - value=sanitized_value, + line=line, + value=value, ) ) else: - print(sanitized_value) + print(value) -def _set_regex_value( +def _set_regex_values( filepath: str = "", regex: str = "", group: int = 0, @@ -306,6 +403,8 @@ def _set_regex_value( ): lines = _get_file_lines(filepath=filepath, ssh=ssh) + old_lines = deepcopy(lines) + found_occurences = [] for i, line in enumerate(lines): @@ -320,25 +419,29 @@ def _set_regex_value( for occurence_index, occurence_match in filtered_occurences: if not group: lines[occurence_index] = ( - f"{occurence_match.string}{wrapper if wrapper is not None else ''}{value}{wrapper if wrapper is not None else ''}" + f"{occurence_match.string[ : occurence_match.regs[0][1]]}" + f"{wrapper if wrapper is not None else ''}" + f"{value}{wrapper if wrapper is not None else ''}" ) else: start = lines[occurence_index][0 : occurence_match.start(group)] end = lines[occurence_index][occurence_match.end(group) :] lines[occurence_index] = ( - f"{start}{wrapper if wrapper is not None else ''}{value}{wrapper if wrapper is not None else ''}{end}" + f"{start}{wrapper if wrapper is not None else ''}" + f"{value}{wrapper if wrapper is not None else ''}{end}" ) _set_file_lines(filepath=filepath, ssh=ssh, lines=lines) + return old_lines, lines + -def _get_regex_value( +def _get_regex_values( filepath: str = "", regex: str = "", group: int = 0, occurences_slice: Union[str, list[int]] = ":", wrapper: str = "", - verbosity: int = 0, ssh: str = None, ): lines = _get_file_lines(filepath=filepath, ssh=ssh) @@ -354,31 +457,39 @@ def _get_regex_value( occurences=found_occurences, filter=occurences_slice ) - for occurence_index, occurence_match in filtered_occurences: - - sanitized_value = ( - _sanitize_value( - value=occurence_match.group(group), wrapper=wrapper, end=None - ) - if group - else occurence_match.string + return [ + ( + lines[occurence_index], + ( + _sanitize_value(value=occurence_match.group(group), wrapper=wrapper) + if group + else _sanitize_value( + value=occurence_match.string[occurence_match.regs[0][1] :], + wrapper=wrapper, + ) + ), ) + for occurence_index, occurence_match in filtered_occurences + ] + +def _print_regex_values(sanitized_value, verbosity): + for line, value in sanitized_value: if verbosity >= 1: print( _color_value( - line=lines[occurence_index], - value=sanitized_value, + line=line, + value=value, ) ) else: - print(sanitized_value) + print(value) -def _set_value(entry, value): +def _set_values(entry, value): type, filepath, wrapper, ssh = ( entry["type"], - entry["filepath"], + os.path.abspath(entry["filepath"]), entry["wrapper"], entry["ssh"], ) @@ -391,7 +502,7 @@ def _set_value(entry, value): tag = entry["tag"] occurences = entry["occurences"] end = entry["end"] - _set_tag_value( + return _set_tag_values( filepath=filepath, tag=tag, occurences_slice=occurences, @@ -404,7 +515,7 @@ def _set_value(entry, value): line = entry["line"] column = entry["column"] end = entry["end"] - _set_lc_value( + return _set_lc_values( filepath=filepath, line=line, column=column, @@ -417,7 +528,7 @@ def _set_value(entry, value): regex = entry["regex"] group = entry["group"] occurences = entry["occurences"] - _set_regex_value( + return _set_regex_values( filepath=filepath, regex=regex, group=group, @@ -431,30 +542,114 @@ def _set_value(entry, value): def set_presets(args): config = parse_config(preset=args.preset, g=args.g) - for entry in config.values(): - _set_value(entry=entry, value=entry["presets"][args.preset]) + patch = [ + ( + os.path.abspath(entry["filepath"]), + DMP.patch_toText( + DMP.patch_make( + a=NL.join(new_lines), + b=DMP.diff_main(NL.join(new_lines), NL.join(old_lines)), + ) + ), + ) + for entry, old_lines, new_lines in [ + (entry, *_set_values(entry=entry, value=entry["presets"][args.preset])) + for entry in config.values() + ] + ] + + _add_to_history(patch=patch) def set_value(args): """Set the value associated with a tag in files listed in .xet""" config = parse_config( - except_flags=args.e, only_flags=args.o, names=args.n, g=args.g + except_flags=args.e, + only_flags=args.o, + names=args.n, + path=args.p, + g=args.g, + ) + + patch = [ + ( + os.path.abspath(entry["filepath"]), + DMP.patch_toText( + DMP.patch_make( + a=NL.join(new_lines), + b=DMP.diff_main(NL.join(new_lines), NL.join(old_lines)), + ) + ), + ) + for entry, old_lines, new_lines in [ + (entry, *_set_values(entry=entry, value=args.value)) + for entry in config.values() + ] + ] + + _add_to_history(patch=patch) + + +def _get_values(entry): + type, filepath, wrapper, ssh = ( + entry["type"], + os.path.abspath(entry["filepath"]), + entry["wrapper"], + entry["ssh"], ) - for entry in config.values(): - _set_value(entry=entry, value=args.value) + if not os.path.exists(filepath): + print(f"File not found: {filepath}") + return + + if type == "tag": + tag = entry["tag"] + occurences = entry["occurences"] + end = entry["end"] + return _get_tag_values( + filepath=filepath, + tag=tag, + occurences_slice=occurences, + wrapper=wrapper, + end=end, + ) + + elif type == "lc": + line = entry["line"] + column = entry["column"] + end = entry["end"] + return _get_lc_values( + filepath=filepath, + line=line, + column=column, + wrapper=wrapper, + end=end, + ssh=ssh, + ) + + elif type == "regex": + regex = entry["regex"] + group = entry["group"] + occurences = entry["occurences"] + + return _get_regex_values( + filepath=filepath, + regex=regex, + group=group, + occurences_slice=occurences, + wrapper=wrapper, + ssh=ssh, + ) def get_value(args): """Get the value associated with a tag in files listed in .xet""" config = parse_config( - except_flags=args.e, only_flags=args.o, names=args.n, g=args.g + except_flags=args.e, only_flags=args.o, names=args.n, path=args.p, g=args.g ) for name, entry in config.items(): - type, filepath, wrapper, ssh, verbosity = ( + type, filepath, verbosity = ( entry["type"], - entry["filepath"], - entry["wrapper"], - entry["ssh"], + os.path.abspath(entry["filepath"]), args.verbosity, ) if not os.path.exists(filepath): @@ -463,54 +658,40 @@ def get_value(args): if verbosity >= 2: print( - f"{NAME_COLOR + name}{SEP_COLOR + ':'}{PATH_COLOR + filepath}{SEP_COLOR + ':'}", + ( + f"{NAME_COLOR + name}{SEP_COLOR + ':'}" + f"{PATH_COLOR + filepath}{SEP_COLOR + ':'}" + ), end="", ) if type == "tag": tag = entry["tag"] - occurences = entry["occurences"] - end = entry["end"] if verbosity >= 2: print(f"{IDENTIFIER_COLOR + tag}{SEP_COLOR + ':' + Style.RESET_ALL}") - _get_tag_value( - filepath=filepath, + _print_tag_values( + _get_values(entry=entry), tag=tag, - occurences_slice=occurences, - wrapper=wrapper, - end=end, verbosity=verbosity, ) elif type == "lc": line = entry["line"] column = entry["column"] - end = entry["end"] if verbosity >= 2: print( - f"{IDENTIFIER_COLOR + line}{SEP_COLOR + ':'}{IDENTIFIER_COLOR + column}{SEP_COLOR + ':' + Style.RESET_ALL}" + f"{IDENTIFIER_COLOR + line}{SEP_COLOR + ':'}\ + {IDENTIFIER_COLOR + column}{SEP_COLOR + ':' + Style.RESET_ALL}" ) - _get_lc_value( - filepath=filepath, - line=line, - column=column, - wrapper=wrapper, - end=end, + _print_lc_values( + _get_values(entry=entry), verbosity=verbosity, - ssh=ssh, ) elif type == "regex": regex = entry["regex"] - group = entry["group"] - occurences = entry["occurences"] if verbosity >= 2: print(f"{IDENTIFIER_COLOR + regex}{SEP_COLOR + ':' + Style.RESET_ALL}") - _get_regex_value( - filepath=filepath, - regex=regex, - group=group, - occurences_slice=occurences, - wrapper=wrapper, + _print_regex_values( + _get_values(entry=entry), verbosity=verbosity, - ssh=ssh, ) @@ -519,6 +700,8 @@ def add_entry(args): config = parse_config(g=args.g) + old_config = deepcopy(load_config(g=args.g)) + config[args.name] = { "type": args.subcommand, "filepath": args.filepath, @@ -546,29 +729,348 @@ def add_entry(args): "group": int(args.group[0]) if args.group else None, "occurences": args.occurences if args.occurences else ":", } + with open(get_abs_config_path(g=args.g), mode="w") as f: + json.dump(config, f, indent=4) + + new_config = load_config(g=args.g) + + patch = [ + ( + get_abs_config_path(), + DMP.patch_toText( + DMP.patch_make( + a=NL.join(new_config), + b=DMP.diff_main(NL.join(new_config), NL.join(old_config)), + ) + ), + ) + ] + + _add_to_history(patch=patch) + + +def _update_name(args): + config = parse_config( + except_flags=args.e, only_flags=args.o, names=args.n, path=args.p, g=args.g + ) + + if len(config) != 1: + print("Filter parameters returned more/less than one entry for name change") + + if args.updateValue in config: + print( + f"Key {IDENTIFIER_COLOR + args.updateValue + Style.RESET_ALL}" + "already present in .xet" + ) + return config + + oldKey = next(iter(config.keys())) + + config[args.updateValue] = config[oldKey] + config.pop(oldKey) + + return config + - with open(get_config_path(g=args.g), mode="w") as f: +def _update_property(args, property: str = None, updatedValue: str = ""): + """Update the given property of entries""" + + config = parse_config( + except_flags=args.e, only_flags=args.o, names=args.n, path=args.p, g=args.g + ) + + for key, entry in config.items(): + if property not in entry: + print(f"Entry: {key} does not have property {property}") + continue + entry[property] = updatedValue + + return config + + +def update_entry(args): + """Update entries in the .xet""" + + config = parse_config(args.g) + old_config = deepcopy(load_config(g=args.g)) + + if args.updateKey == "type": + print("Type cannot be updated, create a new entry") + elif args.updateKey == "name": + config = _update_name(args=args) + else: + config = _update_property( + args=args, property=args.updateKey, updatedValue=args.updateValue + ) + + with open(get_abs_config_path(g=args.g), mode="w") as f: json.dump(config, f, indent=4) + new_config = load_config(g=args.g) + + patch = [ + ( + get_abs_config_path(), + DMP.patch_toText( + DMP.patch_make( + a=NL.join(new_config), + b=DMP.diff_main(NL.join(new_config), NL.join(old_config)), + ) + ), + ) + ] + + _add_to_history(patch=patch) + def remove_entry(args): """Remove an entry from .xet based on the tag""" config = parse_config(g=args.g) - config.pop(args.name) + delete_keys = filter_config( + except_flags=args.e, + only_flags=args.o, + names=args.n, + path=args.p, + g=args.g, + ) + + old_config = deepcopy(load_config(g=args.g)) - with open(get_config_path(g=args.g), mode="w") as f: + for key in delete_keys: + config.pop(key) + + with open(get_abs_config_path(g=args.g), mode="w") as f: json.dump(config, f, indent=4) + new_config = load_config(g=args.g) + + patch = [ + ( + get_abs_config_path(), + DMP.patch_toText( + DMP.patch_make( + a=NL.join(new_config), + b=DMP.diff_main(NL.join(new_config), NL.join(old_config)), + ) + ), + ) + ] + + _add_to_history(patch=patch) + def edit_config(args): - editor = os.environ.get("EDITOR", "nano") - subprocess.run([editor, get_config_path(args.g)]) + """Edit .xet with default editor""" + editor = os.environ.get("EDITOR") + if editor: + subprocess.run([editor, get_abs_config_path(args.g)]) + else: + print("No default editor found.") + + +def which_config(args): + """Outputs the .xet that gets defaulted to in the current directory""" + print(get_abs_config_path()) + + +def show_config(args): + """Outputs the .xet entries with all given filters applied""" + print( + json.dumps( + parse_config( + except_flags=args.e, + only_flags=args.o, + names=args.n, + path=args.p, + g=args.g, + ), + indent=4, + ) + ) + + +def _init_history(): + history = {"past": [], "future": []} + + with open(get_history_path(), mode="w") as f: + json.dump(history, f, indent=4) + + +def _load_history(): + if not os.path.exists(get_history_path()): + _init_history() + + with open(get_history_path()) as f: + history: dict = json.load(f) + + return history + + +def _add_to_history(patch: list): + history = _load_history() + + history["past"].insert(0, patch) + + history["future"] = [] + + with open(get_history_path(), mode="w") as f: + json.dump(history, f, indent=4) + + +def forget(args): + _init_history() + + +def undo(args): + history = _load_history() + + if len(history["past"]) == 0: + print("Nothing to undo") + return + + to_undo = history["past"].pop(0) + + to_future = [] + for filepath, patch in to_undo: + with open(filepath) as f: + text = f.read() + + patched_text, _ = DMP.patch_apply(patches=DMP.patch_fromText(patch), text=text) + + with open(filepath, mode="w") as f: + f.write(patched_text) + + to_future.append( + ( + filepath, + DMP.patch_toText( + DMP.patch_make( + a=patched_text, + b=DMP.diff_main(patched_text, text), + ) + ), + ) + ) + + history["future"].append(to_future) + + with open(get_history_path(), mode="w") as f: + json.dump(history, f, indent=4) + + +def redo(args): + history = _load_history() + + if len(history["future"]) == 0: + print("Nothing to redo") + return + + to_redo = history["future"].pop(0) + + to_past = [] + + for filepath, patch in to_redo: + with open(filepath) as f: + text = f.read() + + patched_text, _ = DMP.patch_apply(patches=DMP.patch_fromText(patch), text=text) + + with open(filepath, mode="w") as f: + f.write(patched_text) + + to_past.append( + ( + filepath, + DMP.patch_toText( + DMP.patch_make( + a=patched_text, + b=DMP.diff_main(patched_text, text), + ) + ), + ) + ) + + history["past"].insert(0, to_past) + + with open(get_history_path(), mode="w") as f: + json.dump(history, f, indent=4) + +def enumerate_slice(slice: slice, length: int): + return list(range(length))[slice] -def main(): + +def snapshot(args): + old_config = deepcopy(load_config(g=args.g)) + + config = parse_config( + except_flags=args.e, + only_flags=args.o, + names=args.n, + path=args.p, + g=args.g, + ) + + if args.split: + split_config = {} + for name, entry in config.items(): + values = [value for _, value in _get_values(entry=entry)] + if len(set(values)) != 1: + for value in sorted(set(values), key=values.index): + new_entry = deepcopy(entry) + new_entry["occurences"] = [ + str(i) for i, x in enumerate(values) if x == value + ] + split_config[name + "_" + value] = new_entry + else: + split_config[name] = entry + config = split_config + + for name, entry in config.items(): + values = [value for _, value in _get_values(entry=entry)] + + if len(set(values)) != 1: + if args.first: + values = [values[0]] + else: + print( + f"Cannot snapshot entry {name}," + "divergent occurence values detected." + "Use --split or --first." + ) + continue + + if not entry["presets"]: + entry["presets"] = {} + + entry["presets"][args.preset] = values[0] + + with open(get_abs_config_path(g=args.g), mode="w") as f: + json.dump(config, f, indent=4) + + new_config = load_config(g=args.g) + + patch = [ + ( + get_abs_config_path(), + DMP.patch_toText( + DMP.patch_make( + a=NL.join(new_config), + b=DMP.diff_main(NL.join(new_config), NL.join(old_config)), + ) + ), + ) + ] + + _add_to_history(patch=patch) + + +def main(args=None): parser = argparse.ArgumentParser( - prog="xet", description="A CLI tool to manage values across multiple files" + prog="xet", + description="A CLI tool to manage values across multiple files, projects\ + and even machines", ) subparsers = parser.add_subparsers( @@ -581,109 +1083,53 @@ def main(): init_parser.set_defaults(func=init_config) - init_parser.add_argument( - "--global", - "-g", - dest="g", - action="store_true", - help="Use the global config", - ) - edit_parser = subparsers.add_parser( "edit", help="Opens the .xet in the standard editor" ) edit_parser.set_defaults(func=edit_config) - edit_parser.add_argument( - "--global", - "-g", - dest="g", - action="store_true", - help="Edit global config", + """WHICH PARSER""" + path_parser = subparsers.add_parser("which", help="Prints path of .xet") + path_parser.set_defaults(func=which_config) + + """SHOW PARSER""" + show_parser = subparsers.add_parser( + "show", + help="Show entries listed in the .xet", ) + show_parser.set_defaults(func=show_config) + """GET PARSER""" get_parser = subparsers.add_parser( "get", - help=f"Get {VALUE_COLOR + 'values' + Style.RESET_ALL} from entries listed in the .xet", + help=f"Get {VALUE_COLOR + 'values' + Style.RESET_ALL} from entries\ + listed in the .xet", ) get_parser.set_defaults(func=get_value) - get_parser.add_argument( - "--global", - "-g", - dest="g", - action="store_true", - help="Use the global config", - ) - get_parser.add_argument( - "--except", "-e", dest="e", nargs="+", help="Exclude entries with these flags" - ) - get_parser.add_argument( - "--only", - "-o", - dest="o", - nargs="+", - help="Include only entries with these flags", - ) - get_parser.add_argument( - "--names", - "-n", - dest="n", - nargs="*", - help=f"Include only entries with the given {NAME_COLOR + 'names' + Style.RESET_ALL}", - ) get_parser.add_argument( "-v", "--verbose", dest="verbosity", - help=f"Enable verbose output. -v outputs the entire line, -vv also outputs the entry {NAME_COLOR + 'name'} {PATH_COLOR + 'filepath' + Style.RESET_ALL} and {IDENTIFIER_COLOR + 'identifier/s' + Style.RESET_ALL}", + help=f"Enable verbose output. -v outputs the entire line, -vv also outputs the\ + entry {NAME_COLOR + 'name'} {PATH_COLOR + 'filepath' + Style.RESET_ALL}\ + and {IDENTIFIER_COLOR + 'identifier/s' + Style.RESET_ALL}", action="count", default=0, ) + """SET PARSER""" + set_parser = subparsers.add_parser( "set", - help=f"Set a {VALUE_COLOR + 'value' + Style.RESET_ALL} in files listed in the .xet", + help=f"Set a {VALUE_COLOR + 'value' + Style.RESET_ALL}\ + in files listed in the .xet", ) set_parser.set_defaults(func=set_value) set_parser.add_argument( "value", help=f"{VALUE_COLOR + 'Value' + Style.RESET_ALL} to set" ) - set_parser.add_argument( - "--global", - "-g", - dest="g", - action="store_true", - help="Use the global config", - ) - set_parser.add_argument( - "--except", "-e", dest="e", nargs="*", help="Exclude entries with these flags" - ) - set_parser.add_argument( - "--only", - "-o", - dest="o", - nargs="*", - help="Include only entries with these flags", - ) - set_parser.add_argument( - "--names", - "-n", - dest="n", - nargs="*", - help=f"Include only entries with the given {NAME_COLOR + 'names' + Style.RESET_ALL}", - ) - - set_parser.add_argument( - "-v", - "--verbose", - dest="verbosity", - help="Enable verbose output", - action="count", - default=0, - ) - """ ADD PARSER AND SUB-PARSERS """ @@ -694,17 +1140,20 @@ def main(): add_tag_parser = add_sub_parser.add_parser( "tag", - help=f"Add a {IDENTIFIER_COLOR + 'tag' + Style.RESET_ALL} identifier entry to the .xet", + help=f"Add a {IDENTIFIER_COLOR + 'tag' + Style.RESET_ALL}\ + identifier entry to the .xet", ) add_lc_parser = add_sub_parser.add_parser( "lc", - help=f"Add a {IDENTIFIER_COLOR + 'line/column' + Style.RESET_ALL} identifier entry to the .xet", + help=f"Add a {IDENTIFIER_COLOR + 'line/column' + Style.RESET_ALL}\ + identifier entry to the .xet", ) add_regex_parser = add_sub_parser.add_parser( "regex", - help=f"Add a {IDENTIFIER_COLOR + 'regex' + Style.RESET_ALL} identifier entry to the .xet", + help=f"Add a {IDENTIFIER_COLOR + 'regex' + Style.RESET_ALL}\ + identifier entry to the .xet", ) add_sub_parsers = [add_tag_parser, add_lc_parser, add_regex_parser] @@ -717,7 +1166,8 @@ def main(): map( # Add name argument to all add sub parsers lambda sub: sub.add_argument( "name", - help=f"The {NAME_COLOR + 'name' + Style.RESET_ALL} of the entry in the config", + help=f"The {NAME_COLOR + 'name' + Style.RESET_ALL}\ + of the entry in the config", ), add_sub_parsers, ) @@ -726,7 +1176,9 @@ def main(): list( map( # Add Filepath argument to all add sub parsers lambda sub: sub.add_argument( - "filepath", help=f"{PATH_COLOR + 'Path' + Style.RESET_ALL} of the file" + "filepath", + help=f"{PATH_COLOR + 'Path' + Style.RESET_ALL}\ + of the file", ), add_sub_parsers, ) @@ -737,23 +1189,28 @@ def main(): # tag parser add_tag_parser.add_argument( "tag", - help=f"{IDENTIFIER_COLOR + 'Tag' + Style.RESET_ALL} identifying the line in the file", + help=f"{IDENTIFIER_COLOR + 'Tag' + Style.RESET_ALL}\ + identifying the line in the file", ) # lc parser add_lc_parser.add_argument( "line", - help=f"The {IDENTIFIER_COLOR + 'line' + Style.RESET_ALL} at which the value is located", + help=f"The {IDENTIFIER_COLOR + 'line' + Style.RESET_ALL}\ + at which the value is located", ) add_lc_parser.add_argument( "column", - help=f"The {IDENTIFIER_COLOR + 'column' + Style.RESET_ALL} at which the value is located", + help=f"The {IDENTIFIER_COLOR + 'column' + Style.RESET_ALL}\ + at which the value is located", ) # regex parser add_regex_parser.add_argument( "regex", - help=f"The {IDENTIFIER_COLOR + 'regular expression' + Style.RESET_ALL}, if no group is specified values are updated after any given match (like tags)", + help=f"The {IDENTIFIER_COLOR + 'regular expression' + Style.RESET_ALL}\ + , if no group is specified values are updated after any given\ + match (like tags)", ) # non-unique optional arguments @@ -778,7 +1235,7 @@ def main(): "--end", dest="end", default="", - help=f"Will be written at the very end of the line", + help="Will be written at the very end of the line", ), [add_tag_parser, add_lc_parser], ) @@ -791,7 +1248,10 @@ def main(): "-o", nargs="*", dest="occurences", - help=f"Which occurence of the {IDENTIFIER_COLOR + 'tag/match' + Style.RESET_ALL} should be included, can be an integer, list of integers or the string 'all'", + help=f"Which occurence of the \ + {IDENTIFIER_COLOR + 'tag/match' + Style.RESET_ALL}\ + should be included, can be an integer,\ + list of integers or the string 'all'", ), [add_tag_parser, add_regex_parser], ) @@ -838,7 +1298,8 @@ def main(): "--wrapper", "-w", dest="wrapper", - help=f"{VALUE_COLOR + 'Value' + Style.RESET_ALL} will be wrapped in this character (useful for updating values in brackets or commas)", + help=f"{VALUE_COLOR + 'Value' + Style.RESET_ALL}\ + will be wrapped in this character", ), add_sub_parsers, ) @@ -852,7 +1313,8 @@ def main(): dest="presets", action="append", nargs=2, - help=f" presets can be set with xet preset ", + help=f" \ + presets can be set with xet preset ", ), add_sub_parsers, ) @@ -864,37 +1326,43 @@ def main(): add_regex_parser.add_argument( "--capture-group", "-c", + dest="group", nargs=1, - help=f"The group number which should be interpreted as the {VALUE_COLOR + 'value' + Style.RESET_ALL}. 0 means the entire match is interpreted as the {VALUE_COLOR + 'value'}. Everything but the {VALUE_COLOR + 'value' + Style.RESET_ALL} itself is preserved", + help=f"The group number of the\ + {VALUE_COLOR + 'value' + Style.RESET_ALL}.\ + 0 means the entire match is interpreted as the\ + {VALUE_COLOR + 'value' + Style.RESET_ALL}.", ) """ - REMOVE PARSER + UPDATE PARSER """ - remove_parser = subparsers.add_parser("remove", help="Remove an entry from .xet") - remove_parser.set_defaults(func=remove_entry) - remove_parser.add_argument( - "name", help=f"{NAME_COLOR + 'Name' + Style.RESET_ALL} of the entry to remove" - ) + update_parser = subparsers.add_parser("update", help="Update entries in the .xet") - remove_parser.add_argument( - "--global", - "-g", - dest="g", - action="store_true", - help="Use the global config", + update_parser.add_argument( + "updateKey", + help=( + "The key to be updated in the chosen entries" + f"('{NAME_COLOR + 'name' + Style.RESET_ALL}' changes the key of the entry)" + ), ) - remove_parser.add_argument( - "-v", - "--verbose", - dest="verbosity", - help="Enable verbose output", - action="count", - default=0, + update_parser.add_argument( + "updateValue", help="The new value of the given key for the chosen entries" ) + update_parser.set_defaults(func=update_entry) + + # unique positional arguments + + """ + REMOVE PARSER + """ + + remove_parser = subparsers.add_parser("remove", help="Remove an entry from .xet") + remove_parser.set_defaults(func=remove_entry) + """ PRESET PARSER """ @@ -905,16 +1373,150 @@ def main(): preset_parser.set_defaults(func=set_presets) preset_parser.add_argument("preset", help="Name of the preset") - preset_parser.add_argument( - "--global", - "-g", - dest="g", - action="store_true", - help="Use the global config", + """ + SNAPSHOT PARSER + """ + + snapshot_parser = subparsers.add_parser( + "snapshot", + help=f"Creates a snapshot of the {VALUE_COLOR + 'values' + Style.RESET_ALL}\ + of the filtered entries and adds a preset", + ) + + snapshot_parser.set_defaults(func=snapshot) + snapshot_parser.add_argument("preset", help="Name of the snapshot preset") + + snapshot_parser.add_argument("--split", action="store_true", dest="split") + snapshot_parser.add_argument("--first", action="store_true", dest="first") + + """UNDO/REDO PARSERS""" + + undo_parser = subparsers.add_parser( + "undo", + help="Undo the last xet command", + ) + undo_parser.set_defaults(func=undo) + + redo_parser = subparsers.add_parser( + "redo", + help="Redo the last undone xet command", + ) + redo_parser.set_defaults(func=redo) + + forget_parser = subparsers.add_parser( + "forget", + help="Reset the xet history", + ) + forget_parser.set_defaults(func=forget) + + # NON-UNIQUE ARGUMENTS OVERALL + + list( # Add only argument to update path sub parsers + map( + lambda sub: sub.add_argument( + "--only", + "-o", + dest="o", + nargs="*", + help="Include only entries with these flags", + ), + [ + remove_parser, + update_parser, + get_parser, + set_parser, + show_parser, + snapshot_parser, + ], + ) + ) + + list( # Add except argument to update path sub parsers + map( + lambda sub: sub.add_argument( + "--except", + "-e", + dest="e", + nargs="+", + help="Exclude entries with these flags", + ), + [ + remove_parser, + update_parser, + get_parser, + set_parser, + show_parser, + snapshot_parser, + ], + ) + ) + + list( # Add name argument to update path sub parsers + map( + lambda sub: sub.add_argument( + "--names", + "-n", + dest="n", + nargs="*", + help=f"Include only entries with the given\ + {NAME_COLOR + 'names' + Style.RESET_ALL}", + ), + [ + remove_parser, + update_parser, + get_parser, + set_parser, + show_parser, + snapshot_parser, + ], + ) + ) + list( # Add path argument to update path sub parsers + map( + lambda sub: sub.add_argument( + "-p", + "--path", + dest="p", + nargs="+", + help=f"Include only entries with these\ + {PATH_COLOR + 'paths' + Style.RESET_ALL}", + ), + [ + remove_parser, + update_parser, + get_parser, + set_parser, + show_parser, + snapshot_parser, + ], + ) + ) + + list( # Add global argument to parsers + map( + lambda sub: sub.add_argument( + "--global", + "-g", + action="store_true", + dest="g", + help="Use the global .xet", + ), + [ + update_parser, + get_parser, + set_parser, + show_parser, + edit_parser, + init_parser, + remove_parser, + preset_parser, + snapshot_parser, + ], + ) ) - args = parser.parse_args() - args.func(args) + exec = parser.parse_args(args=args) + exec.func(exec) if __name__ == "__main__":