16 Commits

Author SHA1 Message Date
d5c2873d39 chore(changelog): update changelog for v0.2.0
All checks were successful
Build and Publish nightly package / build-and-publish (release) Successful in 18s
2025-05-11 12:57:51 +00:00
c005d1d38c chore(version): bump version to 0.2.0
All checks were successful
Auto Changelog & Release / detect-version-change (push) Successful in 5s
Auto Changelog & Release / changelog-only (push) Has been skipped
Build and Publish nightly package / build-and-publish (push) Successful in 17s
Auto Changelog & Release / release (push) Successful in 9s
2025-05-11 14:57:32 +02:00
5c0db1c656 chore(version): bump to 0.2.0
- Updates the project version to 0.2.0 in preparation for a new release
2025-05-11 14:57:24 +02:00
15671ac85b chore(deps): update dependencies for charset-normalizer and cryptography
All checks were successful
Auto Changelog & Release / detect-version-change (push) Successful in 5s
Auto Changelog & Release / release (push) Has been skipped
Auto Changelog & Release / changelog-only (push) Successful in 11s
Build and Publish nightly package / build-and-publish (push) Successful in 21s
- Bump charset-normalizer from 3.4.1 to 3.4.2
- Bump cryptography from 44.0.2 to 44.0.3
- Adjust Click dependency constraint to "<8.2.0"

These updates enhance compatibility and address potential issues with
dependency versions.
2025-05-11 14:54:43 +02:00
18889a3352 chore(changelog): update unreleased changelog 2025-05-11 12:51:01 +00:00
ef6ac68f4b feat(deps): add click dependency and update poetry lockfile
All checks were successful
Auto Changelog & Release / detect-version-change (push) Successful in 4s
Auto Changelog & Release / release (push) Has been skipped
Auto Changelog & Release / changelog-only (push) Successful in 8s
Build and Publish nightly package / build-and-publish (push) Successful in 21s
2025-05-11 14:50:47 +02:00
307d18fa4d chore(changelog): update unreleased changelog 2025-05-11 12:41:07 +00:00
719f257739 feat(cli): add verbose option to status command
All checks were successful
Auto Changelog & Release / detect-version-change (push) Successful in 4s
Auto Changelog & Release / release (push) Has been skipped
Auto Changelog & Release / changelog-only (push) Successful in 7s
Build and Publish nightly package / build-and-publish (push) Successful in 50s
- Introduces a `--verbose` flag to the `status` command for detailed output
- Displays repository details, including age, size, and Git status
- Enhances usability by providing more granular information on request
2025-05-11 14:40:50 +02:00
55e79d224f refactor(cli): streamline repository status logic
- Replace `RepoCategory` with `RepoCatalogState` for improved abstraction
- Simplify repository data processing and remove redundant functions
- Enhance code readability and maintainability by consolidating logic
2025-05-11 14:38:54 +02:00
68b3233fcb refactor(review): streamline repo review logic and data handling
- Replaces manual path handling with `RepoCatalogState` abstraction
- Simplifies repo status checks and removes redundant imports
- Improves clarity and maintainability of interactive review process
2025-05-11 14:38:44 +02:00
9bff626f30 refactor(cli): use RepoCatalogState for repository operations
- Replaces direct config usage with RepoCatalogState to manage categories
  and repositories, improving modularity and encapsulation.
- Updates repository movement logic to use `RepoCatalogState` methods
  for better abstraction and error handling.
2025-05-11 14:38:18 +02:00
458d965062 refactor(cli): simplify repository cleaning logic
- Replaced direct filesystem and Git operations with catalog-based API
- Streamlined handling of repository properties and deletion logic
- Improved readability and maintainability by reducing redundancy
2025-05-11 14:38:12 +02:00
d3ee7ee63d refactor(archive): streamline repository archiving logic
- Simplify archive directory resolution using catalog state
- Replace redundant directory checks with repository model methods
- Remove unused imports and legacy functions for cleaner code
- Improve error handling for missing archive categories
2025-05-11 14:38:06 +02:00
eb5bf52f2f feat(models): add catalog and filesystem utilities
- Introduce models for managing repository catalog and categories
- Add utilities for directory size and Git status checks
- Enable repository metadata management, validation, and operations
2025-05-11 14:37:53 +02:00
51e5c10a5d chore(changelog): update unreleased changelog 2025-05-11 12:21:19 +00:00
c596f46389 fix(scripts): update nightly version suffix format
All checks were successful
Auto Changelog & Release / detect-version-change (push) Successful in 5s
Auto Changelog & Release / release (push) Has been skipped
Auto Changelog & Release / changelog-only (push) Successful in 7s
Build and Publish nightly package / build-and-publish (push) Successful in 16s
- Changes nightly version suffix to use `.dev` format for consistency
- Replaces `+nightly` with `.dev` followed by timestamp
2025-05-11 14:21:04 +02:00
12 changed files with 482 additions and 386 deletions

View File

@@ -5,7 +5,7 @@ NIGHTLY_SUFFIX=""
if [[ "$1" == "nightly" ]]; then
# Beispiel: 20240511.1358 → 11. Mai, 13:58 Uhr
NIGHTLY_SUFFIX="+nightly.$(date +%Y%m%d.%H%M)"
NIGHTLY_SUFFIX=".dev$(date +%Y%m%d%H%M)"
fi
FULL_VERSION="${BASE_VERSION}${NIGHTLY_SUFFIX}"

View File

@@ -2,6 +2,26 @@
All notable changes to this project will be documented in this file.
## [0.2.0](https://git.0xmax42.io/maxp/repoCat/compare/v0.1.0..v0.2.0) - 2025-05-11
### 🚀 Features
- *(deps)* Add click dependency and update poetry lockfile - ([ef6ac68](https://git.0xmax42.io/maxp/repoCat/commit/ef6ac68f4b8a7423c4015d893647d704c0544f2c))
- *(cli)* Add verbose option to status command - ([719f257](https://git.0xmax42.io/maxp/repoCat/commit/719f2577390ed5b374d240fe6eecd527b31c587a))
- *(models)* Add catalog and filesystem utilities - ([eb5bf52](https://git.0xmax42.io/maxp/repoCat/commit/eb5bf52f2fd00d07a1642c67ff65671a4479108f))
### 🐛 Bug Fixes
- *(scripts)* Update nightly version suffix format - ([c596f46](https://git.0xmax42.io/maxp/repoCat/commit/c596f46389c2ea88dbf5699fc41a5d7107827d55))
### 🚜 Refactor
- *(cli)* Streamline repository status logic - ([55e79d2](https://git.0xmax42.io/maxp/repoCat/commit/55e79d224fb8eed83a0d5e4e1256f4f78d4a5390))
- *(review)* Streamline repo review logic and data handling - ([68b3233](https://git.0xmax42.io/maxp/repoCat/commit/68b3233fcb97b424efa565b938451640dd31b1d2))
- *(cli)* Use RepoCatalogState for repository operations - ([9bff626](https://git.0xmax42.io/maxp/repoCat/commit/9bff626f3062eb0413735e170c0a346f0946a84d))
- *(cli)* Simplify repository cleaning logic - ([458d965](https://git.0xmax42.io/maxp/repoCat/commit/458d965062498bbf4b027243f2ab0ffe7636c456))
- *(archive)* Streamline repository archiving logic - ([d3ee7ee](https://git.0xmax42.io/maxp/repoCat/commit/d3ee7ee63d2a0319243f826eea3b98fd95a6b9a6))
## [0.1.0] - 2025-05-11
### 🚀 Features

View File

@@ -1 +1 @@
0.1.0
0.2.0

266
poetry.lock generated
View File

@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand.
# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand.
[[package]]
name = "annotated-types"
@@ -107,104 +107,104 @@ pycparser = "*"
[[package]]
name = "charset-normalizer"
version = "3.4.1"
version = "3.4.2"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
optional = false
python-versions = ">=3.7"
groups = ["dev"]
files = [
{file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"},
{file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"},
{file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"},
{file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"},
{file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"},
{file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"},
{file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"},
{file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"},
{file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"},
{file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"},
{file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"},
{file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"},
{file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"},
{file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"},
{file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"},
{file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"},
{file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"},
{file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"},
{file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"},
{file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"},
{file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"},
{file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"},
{file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"},
{file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"},
{file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"},
{file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"},
{file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"},
{file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"},
{file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"},
{file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"},
{file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"},
{file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"},
{file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"},
{file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"},
{file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"},
{file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"},
{file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"},
{file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"},
{file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"},
{file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"},
{file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"},
{file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"},
{file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"},
{file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"},
{file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"},
{file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"},
{file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"},
{file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"},
{file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"},
{file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"},
{file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"},
{file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"},
{file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"},
{file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"},
{file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"},
{file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"},
{file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"},
{file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"},
{file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"},
{file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"},
{file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"},
{file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"},
{file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"},
{file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"},
{file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"},
{file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"},
{file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"},
{file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"},
{file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"},
{file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"},
{file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"},
{file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"},
{file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"},
{file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"},
{file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"},
{file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"},
{file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"},
{file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"},
{file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"},
{file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"},
{file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"},
{file = "charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941"},
{file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd"},
{file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6"},
{file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d"},
{file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86"},
{file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c"},
{file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0"},
{file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef"},
{file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6"},
{file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366"},
{file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db"},
{file = "charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a"},
{file = "charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509"},
{file = "charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2"},
{file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645"},
{file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd"},
{file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8"},
{file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f"},
{file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7"},
{file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9"},
{file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544"},
{file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82"},
{file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0"},
{file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5"},
{file = "charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a"},
{file = "charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28"},
{file = "charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7"},
{file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3"},
{file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a"},
{file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214"},
{file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a"},
{file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd"},
{file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981"},
{file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c"},
{file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b"},
{file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d"},
{file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f"},
{file = "charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c"},
{file = "charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e"},
{file = "charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0"},
{file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf"},
{file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e"},
{file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1"},
{file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c"},
{file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691"},
{file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0"},
{file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b"},
{file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff"},
{file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b"},
{file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148"},
{file = "charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7"},
{file = "charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980"},
{file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184"},
{file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa"},
{file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344"},
{file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da"},
{file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02"},
{file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d"},
{file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4"},
{file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f"},
{file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64"},
{file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f"},
{file = "charset_normalizer-3.4.2-cp37-cp37m-win32.whl", hash = "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58"},
{file = "charset_normalizer-3.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2"},
{file = "charset_normalizer-3.4.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb"},
{file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a"},
{file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45"},
{file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5"},
{file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1"},
{file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027"},
{file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b"},
{file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455"},
{file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01"},
{file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58"},
{file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681"},
{file = "charset_normalizer-3.4.2-cp38-cp38-win32.whl", hash = "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7"},
{file = "charset_normalizer-3.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a"},
{file = "charset_normalizer-3.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4"},
{file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7"},
{file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836"},
{file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597"},
{file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7"},
{file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f"},
{file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba"},
{file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12"},
{file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518"},
{file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5"},
{file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3"},
{file = "charset_normalizer-3.4.2-cp39-cp39-win32.whl", hash = "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471"},
{file = "charset_normalizer-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e"},
{file = "charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0"},
{file = "charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63"},
]
[[package]]
@@ -237,48 +237,50 @@ files = [
[[package]]
name = "cryptography"
version = "44.0.2"
version = "44.0.3"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
optional = false
python-versions = "!=3.9.0,!=3.9.1,>=3.7"
groups = ["dev"]
markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\" and sys_platform == \"linux\""
files = [
{file = "cryptography-44.0.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:efcfe97d1b3c79e486554efddeb8f6f53a4cdd4cf6086642784fa31fc384e1d7"},
{file = "cryptography-44.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29ecec49f3ba3f3849362854b7253a9f59799e3763b0c9d0826259a88efa02f1"},
{file = "cryptography-44.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc821e161ae88bfe8088d11bb39caf2916562e0a2dc7b6d56714a48b784ef0bb"},
{file = "cryptography-44.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3c00b6b757b32ce0f62c574b78b939afab9eecaf597c4d624caca4f9e71e7843"},
{file = "cryptography-44.0.2-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7bdcd82189759aba3816d1f729ce42ffded1ac304c151d0a8e89b9996ab863d5"},
{file = "cryptography-44.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:4973da6ca3db4405c54cd0b26d328be54c7747e89e284fcff166132eb7bccc9c"},
{file = "cryptography-44.0.2-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4e389622b6927d8133f314949a9812972711a111d577a5d1f4bee5e58736b80a"},
{file = "cryptography-44.0.2-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f514ef4cd14bb6fb484b4a60203e912cfcb64f2ab139e88c2274511514bf7308"},
{file = "cryptography-44.0.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1bc312dfb7a6e5d66082c87c34c8a62176e684b6fe3d90fcfe1568de675e6688"},
{file = "cryptography-44.0.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b721b8b4d948b218c88cb8c45a01793483821e709afe5f622861fc6182b20a7"},
{file = "cryptography-44.0.2-cp37-abi3-win32.whl", hash = "sha256:51e4de3af4ec3899d6d178a8c005226491c27c4ba84101bfb59c901e10ca9f79"},
{file = "cryptography-44.0.2-cp37-abi3-win_amd64.whl", hash = "sha256:c505d61b6176aaf982c5717ce04e87da5abc9a36a5b39ac03905c4aafe8de7aa"},
{file = "cryptography-44.0.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8e0ddd63e6bf1161800592c71ac794d3fb8001f2caebe0966e77c5234fa9efc3"},
{file = "cryptography-44.0.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81276f0ea79a208d961c433a947029e1a15948966658cf6710bbabb60fcc2639"},
{file = "cryptography-44.0.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a1e657c0f4ea2a23304ee3f964db058c9e9e635cc7019c4aa21c330755ef6fd"},
{file = "cryptography-44.0.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6210c05941994290f3f7f175a4a57dbbb2afd9273657614c506d5976db061181"},
{file = "cryptography-44.0.2-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1c3572526997b36f245a96a2b1713bf79ce99b271bbcf084beb6b9b075f29ea"},
{file = "cryptography-44.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b042d2a275c8cee83a4b7ae30c45a15e6a4baa65a179a0ec2d78ebb90e4f6699"},
{file = "cryptography-44.0.2-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d03806036b4f89e3b13b6218fefea8d5312e450935b1a2d55f0524e2ed7c59d9"},
{file = "cryptography-44.0.2-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c7362add18b416b69d58c910caa217f980c5ef39b23a38a0880dfd87bdf8cd23"},
{file = "cryptography-44.0.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:8cadc6e3b5a1f144a039ea08a0bdb03a2a92e19c46be3285123d32029f40a922"},
{file = "cryptography-44.0.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6f101b1f780f7fc613d040ca4bdf835c6ef3b00e9bd7125a4255ec574c7916e4"},
{file = "cryptography-44.0.2-cp39-abi3-win32.whl", hash = "sha256:3dc62975e31617badc19a906481deacdeb80b4bb454394b4098e3f2525a488c5"},
{file = "cryptography-44.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:5f6f90b72d8ccadb9c6e311c775c8305381db88374c65fa1a68250aa8a9cb3a6"},
{file = "cryptography-44.0.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:af4ff3e388f2fa7bff9f7f2b31b87d5651c45731d3e8cfa0944be43dff5cfbdb"},
{file = "cryptography-44.0.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:0529b1d5a0105dd3731fa65680b45ce49da4d8115ea76e9da77a875396727b41"},
{file = "cryptography-44.0.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7ca25849404be2f8e4b3c59483d9d3c51298a22c1c61a0e84415104dacaf5562"},
{file = "cryptography-44.0.2-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:268e4e9b177c76d569e8a145a6939eca9a5fec658c932348598818acf31ae9a5"},
{file = "cryptography-44.0.2-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:9eb9d22b0a5d8fd9925a7764a054dca914000607dff201a24c791ff5c799e1fa"},
{file = "cryptography-44.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2bf7bf75f7df9715f810d1b038870309342bff3069c5bd8c6b96128cb158668d"},
{file = "cryptography-44.0.2-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:909c97ab43a9c0c0b0ada7a1281430e4e5ec0458e6d9244c0e821bbf152f061d"},
{file = "cryptography-44.0.2-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:96e7a5e9d6e71f9f4fca8eebfd603f8e86c5225bb18eb621b2c1e50b290a9471"},
{file = "cryptography-44.0.2-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:d1b3031093a366ac767b3feb8bcddb596671b3aaff82d4050f984da0c248b615"},
{file = "cryptography-44.0.2-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:04abd71114848aa25edb28e225ab5f268096f44cf0127f3d36975bdf1bdf3390"},
{file = "cryptography-44.0.2.tar.gz", hash = "sha256:c63454aa261a0cf0c5b4718349629793e9e634993538db841165b3df74f37ec0"},
{file = "cryptography-44.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:962bc30480a08d133e631e8dfd4783ab71cc9e33d5d7c1e192f0b7c06397bb88"},
{file = "cryptography-44.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffc61e8f3bf5b60346d89cd3d37231019c17a081208dfbbd6e1605ba03fa137"},
{file = "cryptography-44.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58968d331425a6f9eedcee087f77fd3c927c88f55368f43ff7e0a19891f2642c"},
{file = "cryptography-44.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e28d62e59a4dbd1d22e747f57d4f00c459af22181f0b2f787ea83f5a876d7c76"},
{file = "cryptography-44.0.3-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af653022a0c25ef2e3ffb2c673a50e5a0d02fecc41608f4954176f1933b12359"},
{file = "cryptography-44.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:157f1f3b8d941c2bd8f3ffee0af9b049c9665c39d3da9db2dc338feca5e98a43"},
{file = "cryptography-44.0.3-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:c6cd67722619e4d55fdb42ead64ed8843d64638e9c07f4011163e46bc512cf01"},
{file = "cryptography-44.0.3-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:b424563394c369a804ecbee9b06dfb34997f19d00b3518e39f83a5642618397d"},
{file = "cryptography-44.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c91fc8e8fd78af553f98bc7f2a1d8db977334e4eea302a4bfd75b9461c2d8904"},
{file = "cryptography-44.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:25cd194c39fa5a0aa4169125ee27d1172097857b27109a45fadc59653ec06f44"},
{file = "cryptography-44.0.3-cp37-abi3-win32.whl", hash = "sha256:3be3f649d91cb182c3a6bd336de8b61a0a71965bd13d1a04a0e15b39c3d5809d"},
{file = "cryptography-44.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:3883076d5c4cc56dbef0b898a74eb6992fdac29a7b9013870b34efe4ddb39a0d"},
{file = "cryptography-44.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:5639c2b16764c6f76eedf722dbad9a0914960d3489c0cc38694ddf9464f1bb2f"},
{file = "cryptography-44.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3ffef566ac88f75967d7abd852ed5f182da252d23fac11b4766da3957766759"},
{file = "cryptography-44.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:192ed30fac1728f7587c6f4613c29c584abdc565d7417c13904708db10206645"},
{file = "cryptography-44.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7d5fe7195c27c32a64955740b949070f21cba664604291c298518d2e255931d2"},
{file = "cryptography-44.0.3-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3f07943aa4d7dad689e3bb1638ddc4944cc5e0921e3c227486daae0e31a05e54"},
{file = "cryptography-44.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cb90f60e03d563ca2445099edf605c16ed1d5b15182d21831f58460c48bffb93"},
{file = "cryptography-44.0.3-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ab0b005721cc0039e885ac3503825661bd9810b15d4f374e473f8c89b7d5460c"},
{file = "cryptography-44.0.3-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:3bb0847e6363c037df8f6ede57d88eaf3410ca2267fb12275370a76f85786a6f"},
{file = "cryptography-44.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b0cc66c74c797e1db750aaa842ad5b8b78e14805a9b5d1348dc603612d3e3ff5"},
{file = "cryptography-44.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6866df152b581f9429020320e5eb9794c8780e90f7ccb021940d7f50ee00ae0b"},
{file = "cryptography-44.0.3-cp39-abi3-win32.whl", hash = "sha256:c138abae3a12a94c75c10499f1cbae81294a6f983b3af066390adee73f433028"},
{file = "cryptography-44.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:5d186f32e52e66994dce4f766884bcb9c68b8da62d61d9d215bfe5fb56d21334"},
{file = "cryptography-44.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:cad399780053fb383dc067475135e41c9fe7d901a97dd5d9c5dfb5611afc0d7d"},
{file = "cryptography-44.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:21a83f6f35b9cc656d71b5de8d519f566df01e660ac2578805ab245ffd8523f8"},
{file = "cryptography-44.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fc3c9babc1e1faefd62704bb46a69f359a9819eb0292e40df3fb6e3574715cd4"},
{file = "cryptography-44.0.3-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:e909df4053064a97f1e6565153ff8bb389af12c5c8d29c343308760890560aff"},
{file = "cryptography-44.0.3-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:dad80b45c22e05b259e33ddd458e9e2ba099c86ccf4e88db7bbab4b747b18d06"},
{file = "cryptography-44.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:479d92908277bed6e1a1c69b277734a7771c2b78633c224445b5c60a9f4bc1d9"},
{file = "cryptography-44.0.3-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:896530bc9107b226f265effa7ef3f21270f18a2026bc09fed1ebd7b66ddf6375"},
{file = "cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:9b4d4a5dbee05a2c390bf212e78b99434efec37b17a4bff42f50285c5c8c9647"},
{file = "cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02f55fb4f8b79c1221b0961488eaae21015b69b210e18c386b69de182ebb1259"},
{file = "cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:dd3db61b8fe5be220eee484a17233287d0be6932d056cf5738225b9c05ef4fff"},
{file = "cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:978631ec51a6bbc0b7e58f23b68a8ce9e5f09721940933e9c217068388789fe5"},
{file = "cryptography-44.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:5d20cc348cca3a8aa7312f42ab953a56e15323800ca3ab0706b8cd452a3a056c"},
{file = "cryptography-44.0.3.tar.gz", hash = "sha256:fe19d8bc5536a91a24a8133328880a41831b6c5df54599a8417b62fe015d3053"},
]
[package.dependencies]
@@ -291,7 +293,7 @@ nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2) ; python_version >= \"3.8\""]
pep8test = ["check-sdist ; python_version >= \"3.8\"", "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 (==44.0.2)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"]
test = ["certifi (>=2024)", "cryptography-vectors (==44.0.3)", "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]]
@@ -1108,4 +1110,4 @@ cffi = ["cffi (>=1.11)"]
[metadata]
lock-version = "2.1"
python-versions = ">=3.12"
content-hash = "413f1d8377278d9377ecc6f9018f23d3b0bda29587689a3fc5946d89ecd99631"
content-hash = "101746662448c2be59e530e0f6437825338181f70e8466275317f1973cf9209b"

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "repocat"
version = "0.1.0"
version = "0.2.0"
description = ""
authors = ["Max P. <Mail@MPassarello.de>"]
readme = "README.md"
@@ -10,6 +10,7 @@ packages = [{ include = "repocat", from = "src" }]
python = ">=3.12"
rich = "^14.0.0"
typer = { extras = ["all"], version = "^0.15.3" }
click = "<8.2.0"
pyyaml = "^6.0.2"
pydantic = "^2.11.4"
zstandard = "^0.23.0"

View File

@@ -1,29 +1,21 @@
import shutil
import tarfile
from datetime import datetime, timedelta
from datetime import datetime
from pathlib import Path
from typer import Typer, Option
from rich.console import Console
from rich.table import Table
import subprocess
import zstandard
import uuid
from repocat.config import load_config
from repocat.cli.clean import is_git_dirty, is_git_unpushed, get_dir_size
from repocat.models.catalog import RepoCatalogState
app = Typer()
console = Console()
def get_archive_target_dir():
config = load_config()
for cat in config.categories:
if cat.is_archive:
return config.base_dir / cat.subdir
raise ValueError("Keine Archiv-Kategorie (is_archive: true) in der Konfiguration gefunden.")
def archive_repo(source: Path, target_dir: Path, dry_run: bool):
def archive_repo(source: Path, target_dir: Path, dry_run: bool) -> Path:
today = datetime.today().strftime("%Y.%m.%d")
unique = uuid.uuid4().hex[:8]
archive_name = f"{today} - {source.name} - {unique}.tar.zst"
@@ -33,7 +25,7 @@ def archive_repo(source: Path, target_dir: Path, dry_run: bool):
raise FileExistsError(f"Archiv existiert bereits: {archive_path}")
if dry_run:
return archive_path # Nur anzeigen
return archive_path
with open(archive_path, "wb") as f:
cctx = zstandard.ZstdCompressor(level=20)
@@ -44,6 +36,7 @@ def archive_repo(source: Path, target_dir: Path, dry_run: bool):
shutil.rmtree(source)
return archive_path
@app.command("run")
def run(
older_than: int = Option(30, "--older-than", help="Nur Repositories älter als X Tage archivieren."),
@@ -51,11 +44,16 @@ def run(
category: list[str] = Option(None, "--category", "-c", help="Nur bestimmte Kategorien prüfen."),
):
config = load_config()
now = datetime.now()
archive_path = get_archive_target_dir()
catalog = RepoCatalogState.from_config(config)
archive_cat = next((c for c in catalog.categories if c.config.is_archive), None)
if not archive_cat:
console.print("[red]Keine Archiv-Kategorie in der Konfiguration gefunden.[/]")
raise SystemExit(1)
archive_path = archive_cat.path
archive_path.mkdir(parents=True, exist_ok=True)
base = config.base_dir
table = Table(title="Archivierung")
table.add_column("Kategorie")
table.add_column("Repository")
@@ -66,44 +64,33 @@ def run(
archived = 0
skipped = 0
for cat in config.categories:
if cat.is_archive or cat.name == "review" or cat.volatile:
for cat in catalog.categories:
if cat.config.is_archive or cat.config.name == "review" or cat.config.volatile:
continue
if category and cat.name not in category:
if category and cat.config.name not in category:
continue
source_dir = base / cat.subdir
if not source_dir.exists():
continue
limit_days = cat.config.days if cat.config.days is not None else older_than
for repo in sorted(source_dir.iterdir()):
if not repo.is_dir():
continue
mtime = datetime.fromtimestamp(repo.stat().st_mtime)
age_days = (now - mtime).days
size_mb = get_dir_size(repo) / 1024 / 1024
limit_days = cat.days if cat.days is not None else older_than
if age_days < limit_days:
table.add_row(cat.name, repo.name, str(age_days), f"{size_mb:.1f}", "[blue]Zu jung[/]")
for repo in cat.repos:
if not repo.is_expired(limit_days):
table.add_row(cat.config.name, repo.name, str(repo.age_days), f"{repo.size_mb:.1f}", "[blue]Zu jung[/]")
skipped += 1
continue
if (repo / ".git").exists():
if is_git_dirty(repo) or is_git_unpushed(repo):
table.add_row(cat.name, repo.name, str(age_days), f"{size_mb:.1f}", "[blue]Geschützt (git)[/]")
skipped += 1
continue
if repo.git and repo.git.is_protected:
table.add_row(cat.config.name, repo.name, str(repo.age_days), f"{repo.size_mb:.1f}", "[blue]Geschützt (git)[/]")
skipped += 1
continue
try:
archive_file = archive_repo(repo, archive_path, dry_run)
archive_file = archive_repo(repo.path, archive_path, dry_run)
action = "[yellow]Würde archivieren[/]" if dry_run else f"[green]Archiviert →[/] {archive_file.name}"
archived += 1
except Exception as e:
action = f"[red]Fehler: {e}[/]"
table.add_row(cat.name, repo.name, str(age_days), f"{size_mb:.1f}", action)
table.add_row(cat.config.name, repo.name, str(repo.age_days), f"{repo.size_mb:.1f}", action)
console.print(table)
console.print(f"\n[green]✓ Archivierung abgeschlossen[/] – {archived} archiviert, {skipped} übersprungen")
console.print(f"\n[green]✓ Archivierung abgeschlossen[/] – {archived} archiviert, {skipped} übersprungen")

View File

@@ -1,65 +1,29 @@
import subprocess
from typer import Typer, Option, Argument
from pathlib import Path
import shutil
from datetime import datetime, timedelta
from typer import Typer, Option
from rich.console import Console
from rich.table import Table
import os
import shutil
from repocat.config import load_config
from repocat.models.catalog import RepoCatalogState
app = Typer()
console = Console()
def is_git_dirty(path: Path) -> bool:
try:
result = subprocess.run(
["git", "status", "--porcelain"],
cwd=path,
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
text=True,
)
return bool(result.stdout.strip())
except Exception:
return False
def is_git_unpushed(path: Path) -> bool:
try:
# Check if upstream exists
subprocess.run(["git", "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"],
cwd=path, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True)
result = subprocess.run(
["git", "log", "@{u}..HEAD", "--oneline"],
cwd=path,
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
text=True,
)
return bool(result.stdout.strip())
except Exception:
return False
def get_dir_size(path: Path) -> int:
"""Returns size in bytes."""
return sum(f.stat().st_size for f in path.rglob("*") if f.is_file())
def complete_categories(incomplete: str):
config = load_config()
return [c.name for c in config.categories if c.name.startswith(incomplete)]
@app.command("volatile")
def clean_volatile(
dry_run: bool = Option(False, "--dry-run", help="Nur anzeigen, was gelöscht würde"),
category: list[str] = Option(None, "--category", "-c", help="Nur bestimmte Kategorien reinigen", autocompletion=complete_categories),
):
"""
Löscht **bedingungslos** alle Repositories aus `volatile`-Kategorien (z.B. `scratches`).
Löscht **bedingungslos** alle Repositories aus `volatile`-Kategorien (z.\u200bB. `scratches`).
"""
config = load_config()
base = config.base_dir
catalog = RepoCatalogState.from_config(load_config())
table = Table(title="Clean – Volatile (Zerstörung)")
table.add_column("Kategorie")
@@ -70,37 +34,31 @@ def clean_volatile(
deleted = 0
skipped = 0
for cat in config.categories:
if not cat.volatile:
for cat in catalog.categories:
if not cat.config.volatile:
continue
if category and cat.name not in category:
if category and cat.config.name not in category:
continue
path = base / cat.subdir
if not path.exists():
continue
for repo in sorted(path.iterdir()):
if not repo.is_dir():
continue
size_mb = get_dir_size(repo) / 1024 / 1024
for repo in cat.repos:
size_mb = repo.size_mb
if dry_run:
action = "[yellow]Würde löschen[/]"
else:
try:
shutil.rmtree(repo)
shutil.rmtree(repo.path)
action = "[red]Gelöscht[/]"
deleted += 1
except Exception as e:
action = f"[red]Fehler: {e}[/]"
table.add_row(cat.name, repo.name, f"{size_mb:.1f}", action)
table.add_row(cat.config.name, repo.name, f"{size_mb:.1f}", action)
console.print(table)
console.print(f"\n[green]✓ Volatile-Clean abgeschlossen[/] – {deleted} gelöscht, {skipped} übersprungen")
@app.command()
def run(
dry_run: bool = Option(False, "--dry-run", help="Nur anzeigen, was gelöscht würde"),
@@ -109,11 +67,7 @@ def run(
"""
Löscht alte oder volatile Repositories aus den konfigurierten Kategorien.
"""
config = load_config()
now = datetime.now()
base = config.base_dir
console.print(f"[bold yellow]Base-Verzeichnis:[/] {base}")
catalog = RepoCatalogState.from_config(load_config())
table = Table(title="Clean-Ergebnis")
table.add_column("Kategorie")
@@ -125,43 +79,27 @@ def run(
deleted = 0
skipped = 0
for cat in config.categories:
if category and cat.name not in category:
for cat in catalog.categories:
if category and cat.config.name not in category:
continue
if cat.config.is_archive:
continue
if cat.is_archive: continue
path = base / cat.subdir
if not path.exists():
continue
cutoff = now - timedelta(days=cat.days or 0)
for repo in sorted(path.iterdir()):
if not repo.is_dir():
continue
mtime = datetime.fromtimestamp(repo.stat().st_mtime)
age_days = (now - mtime).days
size_mb = get_dir_size(repo) / 1024 / 1024
limit_days = cat.config.days or 0
for repo in cat.repos:
should_delete = (
(cat.volatile)
or (cat.days is not None and mtime < cutoff)
cat.config.volatile
or (cat.config.days is not None and repo.is_expired(limit_days))
)
# Git-Schutz prüfen
is_git = (repo / ".git").exists()
dirty = is_git and is_git_dirty(repo)
unpushed = is_git and is_git_unpushed(repo)
if is_git and (dirty or unpushed):
reason = []
if dirty:
reason.append("uncommitted")
if unpushed:
reason.append("unpushed")
status = "/".join(reason)
if repo.git and repo.git.is_protected:
reasons = []
if repo.git.dirty:
reasons.append("uncommitted")
if repo.git.unpushed:
reasons.append("unpushed")
status = "/".join(reasons)
action = f"[blue]Geschützt ({status})[/]"
skipped += 1
else:
@@ -170,7 +108,7 @@ def run(
action = "[yellow]Würde löschen[/]"
else:
try:
shutil.rmtree(repo)
shutil.rmtree(repo.path)
action = "[red]Gelöscht[/]"
deleted += 1
except Exception as e:
@@ -179,7 +117,7 @@ def run(
action = "[cyan]Keine Aktion[/]"
skipped += 1
table.add_row(cat.name, repo.name, str(age_days), f"{size_mb:.1f}", action)
table.add_row(cat.config.name, repo.name, str(repo.age_days), f"{repo.size_mb:.1f}", action)
console.print(table)
console.print(f"\n[green]✓ Clean abgeschlossen[/] – {deleted} gelöscht, {skipped} übersprungen")

View File

@@ -1,10 +1,9 @@
from typer import Typer, Argument
from rich.console import Console
from pathlib import Path
import shutil
import typer
from repocat.config import load_config
from repocat.models.catalog import RepoCatalogState
app = Typer()
console = Console()
@@ -12,20 +11,16 @@ console = Console()
def complete_source_repo(incomplete: str):
config = load_config()
base = config.base_dir
catalog = RepoCatalogState.from_config(config)
results = []
for cat in config.categories:
if cat.is_archive:
for cat in catalog.categories:
if cat.config.is_archive:
continue
cat_path = base / cat.subdir
if not cat_path.exists():
continue
for item in cat_path.iterdir():
if item.is_dir():
full_name = f"{cat.name}/{item.name}"
if full_name.startswith(incomplete):
results.append(full_name)
for repo in cat.repos:
full_name = f"{cat.config.name}/{repo.name}"
if full_name.startswith(incomplete):
results.append(full_name)
return sorted(results)
@@ -43,39 +38,35 @@ def move_command(
Verschiebt ein Repository in eine andere Kategorie.
"""
config = load_config()
base = config.base_dir
catalog = RepoCatalogState.from_config(config)
# Quelle aufspalten in Kategorie und Repo
try:
source_cat_name, repo_name = source.split("/", 1)
except ValueError:
console.print("[red]Ungültiges Format für Quelle. Bitte <kategorie>/<reponame> angeben.[/]")
raise typer.Exit(1)
source_cat = next((c for c in config.categories if c.name == source_cat_name), None)
source_cat = catalog.get_category(source_cat_name)
if not source_cat:
console.print(f"[red]Unbekannte Quellkategorie:[/] {source_cat_name}")
raise typer.Exit(1)
target_cat = next((c for c in config.categories if c.name == target), None)
target_cat = catalog.get_category(target)
if not target_cat:
console.print(f"[red]Ungültige Zielkategorie:[/] {target}")
raise typer.Exit(1)
source_path = base / source_cat.subdir / repo_name
destination_path = base / target_cat.subdir / repo_name
if not source_path.exists():
console.print(f"[red]Quell-Repository existiert nicht:[/] {source_path}")
raise typer.Exit(1)
if destination_path.exists():
console.print(f"[red]Zielordner existiert bereits:[/] {destination_path}")
repo = source_cat.find_repo(repo_name)
if not repo:
console.print(f"[red]Repository '{repo_name}' nicht gefunden in Kategorie '{source_cat_name}'.[/]")
raise typer.Exit(1)
try:
shutil.move(str(source_path), str(destination_path))
console.print(f"[green]✓ Erfolgreich verschoben:[/] {source_cat.name}/{repo_name}{target}")
repo.move_to(target_cat.path)
console.print(f"[green]✓ Erfolgreich verschoben:[/] {source_cat_name}/{repo_name}{target}")
except FileExistsError as e:
console.print(f"[red]{e}[/]")
raise typer.Exit(1)
except Exception as e:
console.print(f"[red]Fehler beim Verschieben:[/] {e}")
raise typer.Exit(1)
raise typer.Exit(1)

View File

@@ -1,19 +1,15 @@
from typer import Typer, confirm, prompt
from typer import Typer
from rich.console import Console
from rich.table import Table
from pathlib import Path
import shutil
from datetime import datetime
import typer
from rich.prompt import Prompt, Confirm
from datetime import datetime
import shutil
import typer
from repocat.cli.move import move_command
from repocat.config import load_config
from repocat.models.config import RepoCategory
from repocat.models.catalog import RepoCatalogState
from repocat.cli.move import move_command
from repocat.cli.archive import archive_repo
from repocat.cli.clean import get_dir_size, is_git_dirty, is_git_unpushed
app = Typer()
console = Console()
@@ -23,48 +19,39 @@ def review():
"""
Führt interaktiv durch alle Repositories im Review-Ordner.
"""
config = load_config()
base = config.base_dir
catalog = RepoCatalogState.from_config(load_config())
archive_cat = next((c for c in config.categories if c.is_archive), None)
archive_cat = next((c for c in catalog.categories if c.config.is_archive), None)
if not archive_cat:
console.print("[red]Keine Archiv-Kategorie in der Konfiguration gefunden.[/]")
raise typer.Exit(1)
archive_path = base / archive_cat.subdir
archive_path.mkdir(parents=True, exist_ok=True)
review_cat = next((c for c in config.categories if c.name == "review"), None)
review_cat = catalog.get_category("review")
if not review_cat:
console.print("[red]Kein 'review'-Eintrag in der Konfiguration gefunden.[/]")
raise typer.Exit(1)
review_path = base / review_cat.subdir
if not review_path.exists():
console.print(f"[yellow]Hinweis:[/] Kein review-Ordner vorhanden unter {review_path}")
return
repos = [p for p in review_path.iterdir() if p.is_dir()]
if not repos:
if not review_cat.repos:
console.print("[green]✓ Keine Repositories im Review-Ordner.[/]")
return
for repo in sorted(repos):
for repo in sorted(review_cat.repos, key=lambda r: r.name):
console.rule(f"[bold cyan]{repo.name}")
age_days = (datetime.now() - datetime.fromtimestamp(repo.stat().st_mtime)).days
size_mb = get_dir_size(repo) / 1024 / 1024
console.print(f"[bold]Alter:[/] {repo.age_days} Tage")
console.print(f"[bold]Größe:[/] {repo.size_mb:.1f} MB")
status = []
if is_git_dirty(repo):
status.append("uncommitted")
if is_git_unpushed(repo):
status.append("unpushed")
if not status:
status.append("clean")
if repo.git:
status = []
if repo.git.dirty:
status.append("uncommitted")
if repo.git.unpushed:
status.append("unpushed")
if not status:
status.append("clean")
else:
status = ["kein Git"]
console.print(f"[bold]Alter:[/] {age_days} Tage")
console.print(f"[bold]Größe:[/] {size_mb:.1f} MB")
console.print(f"[bold]Git:[/] {', '.join(status)}")
while True:
@@ -81,17 +68,17 @@ def review():
raise typer.Exit()
elif action in ("d", "delete"):
if Confirm.ask(f"Bist du sicher, dass du [red]{repo.name}[/] löschen willst?"):
shutil.rmtree(repo)
shutil.rmtree(repo.path)
console.print(f"[red]✓ Gelöscht:[/] {repo.name}")
break
elif action in ("m", "move"):
target_categories = [c.name for c in config.categories if c.name != "review" and not c.is_archive]
target_categories = [c.config.name for c in catalog.categories if c.config.name != "review" and not c.config.is_archive]
target = Prompt.ask("Zielkategorie", choices=target_categories)
move_command(source=f"review/{repo.name}", target=target)
break
elif action in ("a", "archive"):
try:
archive_file = archive_repo(repo, archive_path, dry_run=False)
archive_file = archive_repo(repo.path, archive_cat.path, dry_run=False)
console.print(f"[green]✓ Archiviert:[/] {archive_file.name}")
break
except Exception as e:
@@ -99,4 +86,4 @@ def review():
else:
console.print("[yellow]Ungültige Eingabe. Gültig sind: m, d, s, q[/]")
console.print("[green]✓ Review abgeschlossen.[/]")
console.print("[green]✓ Review abgeschlossen.[/]")

View File

@@ -1,71 +1,45 @@
from typer import Typer
from typer import Typer, Option
from rich.console import Console
from rich.table import Table
from pathlib import Path
from datetime import datetime
from repocat.config import load_config
from repocat.models.config import RepoCategory
from repocat.models.catalog import RepoCatalogState
app = Typer()
console = Console()
def get_dir_size(path: Path) -> int:
"""Returns size in bytes."""
return sum(f.stat().st_size for f in path.rglob("*") if f.is_file())
def summarize_category(category: RepoCategory, base_path: Path):
cat_path = base_path / category.subdir
if not cat_path.exists():
return 0, 0.0, None, None
now = datetime.now()
repo_infos = []
for repo in cat_path.iterdir():
if not repo.is_dir():
continue
size_bytes = get_dir_size(repo)
mtime = datetime.fromtimestamp(repo.stat().st_mtime)
age_days = (now - mtime).days
repo_infos.append((repo.name, size_bytes, age_days, mtime))
if not repo_infos:
return 0, 0.0, None, None
total_size = sum(info[1] for info in repo_infos) / 1024 / 1024
avg_age = sum(info[2] for info in repo_infos) // len(repo_infos)
oldest = max(repo_infos, key=lambda x: x[2]) # größtes Alter
return len(repo_infos), total_size, avg_age, (oldest[0], oldest[2])
@app.command("status")
def status():
def status(verbose: bool = Option(False, "--verbose", "-v", help="Zeige Details zu einzelnen Repositories")):
"""
Zeigt eine Übersicht über alle Repository-Kategorien.
"""
config = load_config()
base = config.base_dir
catalog = RepoCatalogState.from_config(load_config())
table = Table(title="Repository-Status")
table.add_column("Kategorie")
table.add_column("Repos", justify="right")
table.add_column("Gesamtgröße", justify="right")
table.add_column("Ø Alter", justify="right")
table.add_column("Ältestes Repo", justify="left")
table.add_column("\u00d8 Alter", justify="right")
table.add_column("\u00c4ltestes Repo", justify="left")
for cat in config.categories:
if cat.is_archive:
for category in catalog.categories:
if category.config.is_archive:
continue
count, total_size, avg_age, oldest = summarize_category(cat, base)
repos = category.repos
count = len(repos)
total_size = category.total_size_mb()
if not repos:
avg_age = None
oldest = None
else:
ages = [repo.age_days for repo in repos]
avg_age = sum(ages) // len(ages)
oldest_repo = max(repos, key=lambda r: r.age_days)
oldest = (oldest_repo.name, oldest_repo.age_days)
table.add_row(
cat.name,
category.config.name,
str(count),
f"{total_size:.1f} MB",
f"{avg_age or ''} d",
@@ -73,3 +47,38 @@ def status():
)
console.print(table)
if verbose:
for category in catalog.categories:
if category.config.is_archive:
continue
if not category.repos:
continue
repo_table = Table(title=f"Details: {category.config.name}")
repo_table.add_column("Repository")
repo_table.add_column("Alter (Tage)", justify="right")
repo_table.add_column("Größe (MB)", justify="right")
repo_table.add_column("Git", justify="left")
for repo in sorted(category.repos, key=lambda r: r.age_days, reverse=True):
git_status = ""
if repo.git:
if repo.git.dirty:
git_status += "uncommitted "
if repo.git.unpushed:
git_status += "unpushed"
if not git_status:
git_status = "clean"
else:
git_status = ""
repo_table.add_row(
repo.name,
str(repo.age_days),
f"{repo.size_mb:.1f}",
git_status.strip()
)
console.print(repo_table)

View File

@@ -0,0 +1,118 @@
from __future__ import annotations
from pathlib import Path
from datetime import datetime
from typing import Optional, List
from pydantic import BaseModel, Field
import shutil
from repocat.models.config import RepoCategory, RepoCatConfig
from repocat.utils.fsutils import get_dir_size, is_git_dirty, is_git_unpushed
class GitStatus(BaseModel):
dirty: bool
unpushed: bool
branch: Optional[str] = None
remote_url: Optional[str] = None
@property
def is_protected(self) -> bool:
return self.dirty or self.unpushed
class RepoModel(BaseModel):
name: str
path: Path
size_bytes: int
last_modified: datetime
git: Optional[GitStatus] = None
@property
def size_mb(self) -> float:
return self.size_bytes / 1024 / 1024
@property
def age_days(self) -> int:
return (datetime.now() - self.last_modified).days
@property
def is_git_repo(self) -> bool:
return self.git is not None
def is_expired(self, days: int) -> bool:
return self.age_days >= days
def move_to(self, target_dir: Path) -> None:
destination = target_dir / self.name
if destination.exists():
raise FileExistsError(f"Zielordner existiert bereits: {destination}")
shutil.move(str(self.path), str(destination))
self.path = destination
class RepoCategoryState(BaseModel):
config: RepoCategory
path: Path
repos: List[RepoModel] = Field(default_factory=list)
def total_size_mb(self) -> float:
return sum(repo.size_mb for repo in self.repos)
def find_repo(self, name: str) -> Optional[RepoModel]:
return next((r for r in self.repos if r.name == name), None)
class RepoCatalogState(BaseModel):
config: RepoCatConfig
categories: List[RepoCategoryState] = Field(default_factory=list)
@classmethod
def from_config(cls, config: RepoCatConfig) -> RepoCatalogState:
categories: List[RepoCategoryState] = []
for cat_cfg in config.categories:
cat_path = config.base_dir / cat_cfg.subdir
repos: List[RepoModel] = []
if cat_path.exists():
for item in sorted(cat_path.iterdir()):
if not item.is_dir():
continue
last_mod = datetime.fromtimestamp(item.stat().st_mtime)
size = get_dir_size(item)
git = None
if (item / ".git").exists():
git = GitStatus(
dirty=is_git_dirty(item),
unpushed=is_git_unpushed(item),
)
repos.append(RepoModel(
name=item.name,
path=item,
size_bytes=size,
last_modified=last_mod,
git=git,
))
categories.append(RepoCategoryState(
config=cat_cfg,
path=cat_path,
repos=repos,
))
return cls(config=config, categories=categories)
def get_category(self, name: str) -> Optional[RepoCategoryState]:
return next((c for c in self.categories if c.config.name == name), None)
def find_repo(self, name: str) -> Optional[RepoModel]:
for cat in self.categories:
if (repo := cat.find_repo(name)):
return repo
return None
def missing_directories(self) -> List[Path]:
return [cat.path for cat in self.categories if not cat.path.exists()]

View File

@@ -0,0 +1,43 @@
import subprocess
from pathlib import Path
def get_dir_size(path: Path) -> int:
"""Returns directory size in bytes."""
return sum(f.stat().st_size for f in path.rglob("*") if f.is_file())
def is_git_dirty(path: Path) -> bool:
"""Returns True if there are uncommitted changes."""
try:
result = subprocess.run(
["git", "status", "--porcelain"],
cwd=path,
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
text=True,
)
return bool(result.stdout.strip())
except Exception:
return False
def is_git_unpushed(path: Path) -> bool:
"""Returns True if there are commits that haven't been pushed to upstream."""
try:
# Check if upstream exists
subprocess.run(
["git", "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"],
cwd=path,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
check=True,
)
result = subprocess.run(
["git", "log", "@{u}..HEAD", "--oneline"],
cwd=path,
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
text=True,
)
return bool(result.stdout.strip())
except Exception:
return False