diff --git a/toolsv2/.pdm-python b/toolsv2/.pdm-python new file mode 100644 index 0000000000..7f367161fd --- /dev/null +++ b/toolsv2/.pdm-python @@ -0,0 +1 @@ +/home/felix/tmp/yunohost/repo_apps/toolsv2/.venv/bin/python \ No newline at end of file diff --git a/toolsv2/pdm.lock b/toolsv2/pdm.lock new file mode 100644 index 0000000000..399a14033e --- /dev/null +++ b/toolsv2/pdm.lock @@ -0,0 +1,269 @@ +# This file is @generated by PDM. +# It is not intended for manual editing. + +[metadata] +groups = ["default", "dev"] +strategy = ["cross_platform", "inherit_metadata"] +lock_version = "4.4.1" +content_hash = "sha256:4bf91edcb3dce5d08880e052da5d784fcfa603c2c014ef9776475cc078c05c48" + +[[package]] +name = "black" +version = "24.2.0" +requires_python = ">=3.8" +summary = "The uncompromising code formatter." +groups = ["dev"] +dependencies = [ + "click>=8.0.0", + "mypy-extensions>=0.4.3", + "packaging>=22.0", + "pathspec>=0.9.0", + "platformdirs>=2", + "tomli>=1.1.0; python_version < \"3.11\"", + "typing-extensions>=4.0.1; python_version < \"3.11\"", +] +files = [ + {file = "black-24.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6981eae48b3b33399c8757036c7f5d48a535b962a7c2310d19361edeef64ce29"}, + {file = "black-24.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d533d5e3259720fdbc1b37444491b024003e012c5173f7d06825a77508085430"}, + {file = "black-24.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61a0391772490ddfb8a693c067df1ef5227257e72b0e4108482b8d41b5aee13f"}, + {file = "black-24.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:992e451b04667116680cb88f63449267c13e1ad134f30087dec8527242e9862a"}, + {file = "black-24.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:163baf4ef40e6897a2a9b83890e59141cc8c2a98f2dda5080dc15c00ee1e62cd"}, + {file = "black-24.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e37c99f89929af50ffaf912454b3e3b47fd64109659026b678c091a4cd450fb2"}, + {file = "black-24.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9de21bafcba9683853f6c96c2d515e364aee631b178eaa5145fc1c61a3cc92"}, + {file = "black-24.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:9db528bccb9e8e20c08e716b3b09c6bdd64da0dd129b11e160bf082d4642ac23"}, + {file = "black-24.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d84f29eb3ee44859052073b7636533ec995bd0f64e2fb43aeceefc70090e752b"}, + {file = "black-24.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e08fb9a15c914b81dd734ddd7fb10513016e5ce7e6704bdd5e1251ceee51ac9"}, + {file = "black-24.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:810d445ae6069ce64030c78ff6127cd9cd178a9ac3361435708b907d8a04c693"}, + {file = "black-24.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:ba15742a13de85e9b8f3239c8f807723991fbfae24bad92d34a2b12e81904982"}, + {file = "black-24.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7e53a8c630f71db01b28cd9602a1ada68c937cbf2c333e6ed041390d6968faf4"}, + {file = "black-24.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:93601c2deb321b4bad8f95df408e3fb3943d85012dddb6121336b8e24a0d1218"}, + {file = "black-24.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0057f800de6acc4407fe75bb147b0c2b5cbb7c3ed110d3e5999cd01184d53b0"}, + {file = "black-24.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:faf2ee02e6612577ba0181f4347bcbcf591eb122f7841ae5ba233d12c39dcb4d"}, + {file = "black-24.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:057c3dc602eaa6fdc451069bd027a1b2635028b575a6c3acfd63193ced20d9c8"}, + {file = "black-24.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:08654d0797e65f2423f850fc8e16a0ce50925f9337fb4a4a176a7aa4026e63f8"}, + {file = "black-24.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca610d29415ee1a30a3f30fab7a8f4144e9d34c89a235d81292a1edb2b55f540"}, + {file = "black-24.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:4dd76e9468d5536abd40ffbc7a247f83b2324f0c050556d9c371c2b9a9a95e31"}, + {file = "black-24.2.0-py3-none-any.whl", hash = "sha256:e8a6ae970537e67830776488bca52000eaa37fa63b9988e8c487458d9cd5ace6"}, + {file = "black-24.2.0.tar.gz", hash = "sha256:bce4f25c27c3435e4dace4815bcb2008b87e167e3bf4ee47ccdc5ce906eb4894"}, +] + +[[package]] +name = "click" +version = "8.1.7" +requires_python = ">=3.7" +summary = "Composable command line interface toolkit" +groups = ["dev"] +dependencies = [ + "colorama; platform_system == \"Windows\"", +] +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[[package]] +name = "colorama" +version = "0.4.6" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +summary = "Cross-platform colored terminal text." +groups = ["dev"] +marker = "platform_system == \"Windows\"" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "gitdb" +version = "4.0.11" +requires_python = ">=3.7" +summary = "Git Object Database" +groups = ["default"] +dependencies = [ + "smmap<6,>=3.0.1", +] +files = [ + {file = "gitdb-4.0.11-py3-none-any.whl", hash = "sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4"}, + {file = "gitdb-4.0.11.tar.gz", hash = "sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b"}, +] + +[[package]] +name = "gitpython" +version = "3.1.42" +requires_python = ">=3.7" +summary = "GitPython is a Python library used to interact with Git repositories" +groups = ["default"] +dependencies = [ + "gitdb<5,>=4.0.1", +] +files = [ + {file = "GitPython-3.1.42-py3-none-any.whl", hash = "sha256:1bf9cd7c9e7255f77778ea54359e54ac22a72a5b51288c457c881057b7bb9ecd"}, + {file = "GitPython-3.1.42.tar.gz", hash = "sha256:2d99869e0fef71a73cbd242528105af1d6c1b108c60dfabd994bf292f76c3ceb"}, +] + +[[package]] +name = "mypy" +version = "1.9.0" +requires_python = ">=3.8" +summary = "Optional static typing for Python" +groups = ["dev"] +dependencies = [ + "mypy-extensions>=1.0.0", + "tomli>=1.1.0; python_version < \"3.11\"", + "typing-extensions>=4.1.0", +] +files = [ + {file = "mypy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8a67616990062232ee4c3952f41c779afac41405806042a8126fe96e098419f"}, + {file = "mypy-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d357423fa57a489e8c47b7c85dfb96698caba13d66e086b412298a1a0ea3b0ed"}, + {file = "mypy-1.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49c87c15aed320de9b438ae7b00c1ac91cd393c1b854c2ce538e2a72d55df150"}, + {file = "mypy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:48533cdd345c3c2e5ef48ba3b0d3880b257b423e7995dada04248725c6f77374"}, + {file = "mypy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:4d3dbd346cfec7cb98e6cbb6e0f3c23618af826316188d587d1c1bc34f0ede03"}, + {file = "mypy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:653265f9a2784db65bfca694d1edd23093ce49740b2244cde583aeb134c008f3"}, + {file = "mypy-1.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a3c007ff3ee90f69cf0a15cbcdf0995749569b86b6d2f327af01fd1b8aee9dc"}, + {file = "mypy-1.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2418488264eb41f69cc64a69a745fad4a8f86649af4b1041a4c64ee61fc61129"}, + {file = "mypy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:68edad3dc7d70f2f17ae4c6c1b9471a56138ca22722487eebacfd1eb5321d612"}, + {file = "mypy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:85ca5fcc24f0b4aeedc1d02f93707bccc04733f21d41c88334c5482219b1ccb3"}, + {file = "mypy-1.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aceb1db093b04db5cd390821464504111b8ec3e351eb85afd1433490163d60cd"}, + {file = "mypy-1.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0235391f1c6f6ce487b23b9dbd1327b4ec33bb93934aa986efe8a9563d9349e6"}, + {file = "mypy-1.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4d5ddc13421ba3e2e082a6c2d74c2ddb3979c39b582dacd53dd5d9431237185"}, + {file = "mypy-1.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:190da1ee69b427d7efa8aa0d5e5ccd67a4fb04038c380237a0d96829cb157913"}, + {file = "mypy-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:fe28657de3bfec596bbeef01cb219833ad9d38dd5393fc649f4b366840baefe6"}, + {file = "mypy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e54396d70be04b34f31d2edf3362c1edd023246c82f1730bbf8768c28db5361b"}, + {file = "mypy-1.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5e6061f44f2313b94f920e91b204ec600982961e07a17e0f6cd83371cb23f5c2"}, + {file = "mypy-1.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a10926e5473c5fc3da8abb04119a1f5811a236dc3a38d92015cb1e6ba4cb9e"}, + {file = "mypy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b685154e22e4e9199fc95f298661deea28aaede5ae16ccc8cbb1045e716b3e04"}, + {file = "mypy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:5d741d3fc7c4da608764073089e5f58ef6352bedc223ff58f2f038c2c4698a89"}, + {file = "mypy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:587ce887f75dd9700252a3abbc9c97bbe165a4a630597845c61279cf32dfbf02"}, + {file = "mypy-1.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f88566144752999351725ac623471661c9d1cd8caa0134ff98cceeea181789f4"}, + {file = "mypy-1.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61758fabd58ce4b0720ae1e2fea5cfd4431591d6d590b197775329264f86311d"}, + {file = "mypy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e49499be624dead83927e70c756970a0bc8240e9f769389cdf5714b0784ca6bf"}, + {file = "mypy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:571741dc4194b4f82d344b15e8837e8c5fcc462d66d076748142327626a1b6e9"}, + {file = "mypy-1.9.0-py3-none-any.whl", hash = "sha256:a260627a570559181a9ea5de61ac6297aa5af202f06fd7ab093ce74e7181e43e"}, + {file = "mypy-1.9.0.tar.gz", hash = "sha256:3cc5da0127e6a478cddd906068496a97a7618a21ce9b54bde5bf7e539c7af974"}, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +requires_python = ">=3.5" +summary = "Type system extensions for programs checked with the mypy type checker." +groups = ["dev"] +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "packaging" +version = "24.0" +requires_python = ">=3.7" +summary = "Core utilities for Python packages" +groups = ["dev"] +files = [ + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +requires_python = ">=3.8" +summary = "Utility library for gitignore style pattern matching of file paths." +groups = ["dev"] +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "platformdirs" +version = "4.2.0" +requires_python = ">=3.8" +summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +groups = ["dev"] +files = [ + {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, + {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, +] + +[[package]] +name = "ruff" +version = "0.3.2" +requires_python = ">=3.7" +summary = "An extremely fast Python linter and code formatter, written in Rust." +groups = ["dev"] +files = [ + {file = "ruff-0.3.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:77f2612752e25f730da7421ca5e3147b213dca4f9a0f7e0b534e9562c5441f01"}, + {file = "ruff-0.3.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9966b964b2dd1107797be9ca7195002b874424d1d5472097701ae8f43eadef5d"}, + {file = "ruff-0.3.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b83d17ff166aa0659d1e1deaf9f2f14cbe387293a906de09bc4860717eb2e2da"}, + {file = "ruff-0.3.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb875c6cc87b3703aeda85f01c9aebdce3d217aeaca3c2e52e38077383f7268a"}, + {file = "ruff-0.3.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be75e468a6a86426430373d81c041b7605137a28f7014a72d2fc749e47f572aa"}, + {file = "ruff-0.3.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:967978ac2d4506255e2f52afe70dda023fc602b283e97685c8447d036863a302"}, + {file = "ruff-0.3.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1231eacd4510f73222940727ac927bc5d07667a86b0cbe822024dd00343e77e9"}, + {file = "ruff-0.3.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c6d613b19e9a8021be2ee1d0e27710208d1603b56f47203d0abbde906929a9b"}, + {file = "ruff-0.3.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8439338a6303585d27b66b4626cbde89bb3e50fa3cae86ce52c1db7449330a7"}, + {file = "ruff-0.3.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:de8b480d8379620cbb5ea466a9e53bb467d2fb07c7eca54a4aa8576483c35d36"}, + {file = "ruff-0.3.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b74c3de9103bd35df2bb05d8b2899bf2dbe4efda6474ea9681280648ec4d237d"}, + {file = "ruff-0.3.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f380be9fc15a99765c9cf316b40b9da1f6ad2ab9639e551703e581a5e6da6745"}, + {file = "ruff-0.3.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:0ac06a3759c3ab9ef86bbeca665d31ad3aa9a4b1c17684aadb7e61c10baa0df4"}, + {file = "ruff-0.3.2-py3-none-win32.whl", hash = "sha256:9bd640a8f7dd07a0b6901fcebccedadeb1a705a50350fb86b4003b805c81385a"}, + {file = "ruff-0.3.2-py3-none-win_amd64.whl", hash = "sha256:0c1bdd9920cab5707c26c8b3bf33a064a4ca7842d91a99ec0634fec68f9f4037"}, + {file = "ruff-0.3.2-py3-none-win_arm64.whl", hash = "sha256:5f65103b1d76e0d600cabd577b04179ff592064eaa451a70a81085930e907d0b"}, + {file = "ruff-0.3.2.tar.gz", hash = "sha256:fa78ec9418eb1ca3db392811df3376b46471ae93792a81af2d1cbb0e5dcb5142"}, +] + +[[package]] +name = "smmap" +version = "5.0.1" +requires_python = ">=3.7" +summary = "A pure Python implementation of a sliding window memory map manager" +groups = ["default"] +files = [ + {file = "smmap-5.0.1-py3-none-any.whl", hash = "sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da"}, + {file = "smmap-5.0.1.tar.gz", hash = "sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62"}, +] + +[[package]] +name = "tomli" +version = "2.0.1" +requires_python = ">=3.7" +summary = "A lil' TOML parser" +groups = ["dev"] +marker = "python_version < \"3.11\"" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "tomlkit" +version = "0.12.4" +requires_python = ">=3.7" +summary = "Style preserving TOML library" +groups = ["default"] +files = [ + {file = "tomlkit-0.12.4-py3-none-any.whl", hash = "sha256:5cd82d48a3dd89dee1f9d64420aa20ae65cfbd00668d6f094d7578a78efbb77b"}, + {file = "tomlkit-0.12.4.tar.gz", hash = "sha256:7ca1cfc12232806517a8515047ba66a19369e71edf2439d0f5824f91032b6cc3"}, +] + +[[package]] +name = "types-toml" +version = "0.10.8.20240310" +requires_python = ">=3.8" +summary = "Typing stubs for toml" +groups = ["dev"] +files = [ + {file = "types-toml-0.10.8.20240310.tar.gz", hash = "sha256:3d41501302972436a6b8b239c850b26689657e25281b48ff0ec06345b8830331"}, + {file = "types_toml-0.10.8.20240310-py3-none-any.whl", hash = "sha256:627b47775d25fa29977d9c70dc0cbab3f314f32c8d8d0c012f2ef5de7aaec05d"}, +] + +[[package]] +name = "typing-extensions" +version = "4.10.0" +requires_python = ">=3.8" +summary = "Backported and Experimental Type Hints for Python 3.8+" +groups = ["dev"] +files = [ + {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, + {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, +] diff --git a/toolsv2/pyproject.toml b/toolsv2/pyproject.toml new file mode 100644 index 0000000000..7be81f007d --- /dev/null +++ b/toolsv2/pyproject.toml @@ -0,0 +1,48 @@ +[project] +name = "tools" +version = "0.1.0" +description = "Default template for PDM package" +authors = [ + {name = "Salamandar", email = "felix@piedallu.me"}, +] +dependencies = [ + "gitpython>=3.1.42", + "tomlkit>=0.12.4", +] +requires-python = ">=3.8" +readme = "README.md" +license = {text = "GPLv3"} + + +[tool.pdm] +distribution = false + +[tool.pdm.dev-dependencies] +dev = [ + "black>=24", + "ruff>=0.3", + "mypy>=1.9", + "types-toml>=0.10", +] + +[tool.black] +line-length = 120 + +[tool.ruff] +line-length = 120 + +[tool.ruff.lint] +select = [ + "F", # pyflakes + "E", # pycodestyle + "W", # pycodestyle + "I", # isort + "N", # pep8-naming + "B", # flake8-ubgbear + "ANN", # flake8-annotations + "Q", # flake8-quotes + "PTH", # flake8-use-pathlib + "UP", # pyupgrade, +] + +ignore = ["ANN101", "ANN102", "ANN401"] diff --git a/toolsv2/tests/__init__.py b/toolsv2/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/toolsv2/tools/__init__.py b/toolsv2/tools/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/toolsv2/tools/forge/__init__.py b/toolsv2/tools/forge/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/toolsv2/tools/forge/github.py b/toolsv2/tools/forge/github.py new file mode 100644 index 0000000000..f0cbe701f1 --- /dev/null +++ b/toolsv2/tools/forge/github.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 + +import logging +from functools import cache + +import github + +from ..utils.paths import APPS_REPO_ROOT + + +@cache +def github_login() -> str | None: + if (file := APPS_REPO_ROOT / ".github_login").exists(): + return file.open(encoding="utf-8").read().strip() + return None + + +@cache +def github_email() -> str | None: + if (file := APPS_REPO_ROOT / ".github_email").exists(): + return file.open(encoding="utf-8").read().strip() + return None + + +@cache +def github_token() -> str | None: + if (file := APPS_REPO_ROOT / ".github_token").exists(): + return file.open(encoding="utf-8").read().strip() + return None + + +@cache +def github_auth() -> github.Auth.Auth | None: + token = github_token() + if token is None: + logging.warning("Could not get Github token authentication.") + return None + return github.Auth.Token(token) + + +@cache +def github_api() -> github.Github: + auth = github_auth() + if auth is None: + logging.warning("Returning unauthenticated Github API.") + return github.Github() + return github.Github(auth=auth) diff --git a/toolsv2/tools/forge/rest_api.py b/toolsv2/tools/forge/rest_api.py new file mode 100644 index 0000000000..f44428d7ad --- /dev/null +++ b/toolsv2/tools/forge/rest_api.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python3 + +import re +from enum import Enum +from typing import Any, Optional + +import requests + + +class RefType(Enum): + tags = 1 + commits = 2 + + +class GithubAPI: + def __init__(self, upstream: str, auth: Optional[tuple[str, str]] = None) -> None: + self.upstream = upstream + self.upstream_repo = upstream.replace("https://github.com/", "").strip("/") + assert ( + len(self.upstream_repo.split("/")) == 2 + ), f"'{upstream}' doesn't seem to be a github repository ?" + self.auth = auth + + def internal_api(self, uri: str) -> Any: + url = f"https://api.github.com/{uri}" + r = requests.get(url, auth=self.auth) + r.raise_for_status() + return r.json() + + def tags(self) -> list[dict[str, str]]: + """Get a list of tags for project.""" + return self.internal_api(f"repos/{self.upstream_repo}/tags") + + def commits(self) -> list[dict[str, Any]]: + """Get a list of commits for project.""" + return self.internal_api(f"repos/{self.upstream_repo}/commits") + + def releases(self) -> list[dict[str, Any]]: + """Get a list of releases for project.""" + return self.internal_api(f"repos/{self.upstream_repo}/releases") + + def url_for_ref(self, ref: str, ref_type: RefType) -> str: + """Get a URL for a ref.""" + if ref_type == RefType.tags: + return f"{self.upstream}/archive/refs/tags/{ref}.tar.gz" + elif ref_type == RefType.commits: + return f"{self.upstream}/archive/{ref}.tar.gz" + else: + raise NotImplementedError + + +class GitlabAPI: + def __init__(self, upstream: str) -> None: + # Find gitlab api root... + self.forge_root = self.get_forge_root(upstream).rstrip("/") + self.project_path = upstream.replace(self.forge_root, "").lstrip("/") + self.project_id = self.find_project_id(self.project_path) + + def get_forge_root(self, project_url: str) -> str: + """A small heuristic based on the content of the html page...""" + r = requests.get(project_url) + r.raise_for_status() + match = re.search(r"const url = `(.*)/api/graphql`", r.text) + assert match is not None + return match.group(1) + + def find_project_id(self, project: str) -> int: + try: + project = self.internal_api(f"projects/{project.replace('/', '%2F')}") + except requests.exceptions.HTTPError as err: + if err.response.status_code != 404: + raise + # Second chance for some buggy gitlab instances... + name = self.project_path.split("/")[-1] + projects = self.internal_api(f"projects?search={name}") + project = next( + filter( + lambda x: x.get("path_with_namespace") == self.project_path, + projects, + ) + ) + + assert isinstance(project, dict) + project_id = project.get("id", None) + return project_id + + def internal_api(self, uri: str) -> Any: + url = f"{self.forge_root}/api/v4/{uri}" + r = requests.get(url) + r.raise_for_status() + return r.json() + + def tags(self) -> list[dict[str, str]]: + """Get a list of tags for project.""" + return self.internal_api(f"projects/{self.project_id}/repository/tags") + + def commits(self) -> list[dict[str, Any]]: + """Get a list of commits for project.""" + return [ + { + "sha": commit["id"], + "commit": {"author": {"date": commit["committed_date"]}}, + } + for commit in self.internal_api( + f"projects/{self.project_id}/repository/commits" + ) + ] + + def releases(self) -> list[dict[str, Any]]: + """Get a list of releases for project.""" + releases = self.internal_api(f"projects/{self.project_id}/releases") + retval = [] + for release in releases: + r = { + "tag_name": release["tag_name"], + "prerelease": False, + "draft": False, + "html_url": release["_links"]["self"], + "assets": [ + { + "name": asset["name"], + "browser_download_url": asset["direct_asset_url"], + } + for asset in release["assets"]["links"] + ], + } + for source in release["assets"]["sources"]: + r["assets"].append( + { + "name": f"source.{source['format']}", + "browser_download_url": source["url"], + } + ) + retval.append(r) + + return retval + + def url_for_ref(self, ref: str, ref_type: RefType) -> str: + name = self.project_path.split("/")[-1] + clean_ref = ref.replace("/", "-") + return f"{self.forge_root}/{self.project_path}/-/archive/{ref}/{name}-{clean_ref}.tar.bz2" + + +class GiteaForgejoAPI: + def __init__(self, upstream: str): + # Find gitea/forgejo api root... + self.forge_root = self.get_forge_root(upstream).rstrip("/") + self.project_path = upstream.replace(self.forge_root, "").lstrip("/") + + def get_forge_root(self, project_url: str) -> str: + """A small heuristic based on the content of the html page...""" + r = requests.get(project_url) + r.raise_for_status() + match = re.search(r"appUrl: '([^']*)',", r.text) + assert match is not None + return match.group(1).replace("\\", "") + + def internal_api(self, uri: str): + url = f"{self.forge_root}/api/v1/{uri}" + r = requests.get(url) + r.raise_for_status() + return r.json() + + def tags(self) -> list[dict[str, Any]]: + """Get a list of tags for project.""" + return self.internal_api(f"repos/{self.project_path}/tags") + + def commits(self) -> list[dict[str, Any]]: + """Get a list of commits for project.""" + return self.internal_api(f"repos/{self.project_path}/commits") + + def releases(self) -> list[dict[str, Any]]: + """Get a list of releases for project.""" + return self.internal_api(f"repos/{self.project_path}/releases") + + def url_for_ref(self, ref: str, ref_type: RefType) -> str: + """Get a URL for a ref.""" + return f"{self.forge_root}/{self.project_path}/archive/{ref}.tar.gz" diff --git a/toolsv2/tools/utils/logging_sender.py b/toolsv2/tools/utils/logging_sender.py new file mode 100644 index 0000000000..4cadf2e331 --- /dev/null +++ b/toolsv2/tools/utils/logging_sender.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 + +import subprocess +from shutil import which +import logging +import logging.handlers + + +def send_to_matrix(message: str) -> None: + if which("sendxmpppy") is None: + logging.warning("Could not send error via xmpp.") + return + subprocess.call(["sendxmpppy", message], stdout=subprocess.DEVNULL) + + +class LogSenderHandler(logging.Handler): + def __init__(self): + logging.Handler.__init__(self) + self.is_logging = False + + def emit(self, record): + msg = f"[Apps tools error] {record.msg}" + send_to_matrix(msg) + + @classmethod + def add(cls, level=logging.ERROR): + if not logging.getLogger().handlers: + logging.basicConfig() + + # create handler + handler = cls() + handler.setLevel(level) + # add the handler + logging.getLogger().handlers.append(handler) + + +def enable(): + """Enables the LogSenderHandler""" + LogSenderHandler.add(logging.ERROR) diff --git a/toolsv2/tools/utils/paths.py b/toolsv2/tools/utils/paths.py new file mode 100644 index 0000000000..71aa3af036 --- /dev/null +++ b/toolsv2/tools/utils/paths.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 + +from pathlib import Path + +from git import Repo + +APPS_REPO_ROOT = Path(Repo(__file__, search_parent_directories=True).working_dir) + + +def apps_repo_root() -> Path: + return APPS_REPO_ROOT diff --git a/toolsv2/tools/utils/utils.py b/toolsv2/tools/utils/utils.py new file mode 100644 index 0000000000..9bb7d35833 --- /dev/null +++ b/toolsv2/tools/utils/utils.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 + +import time +from functools import cache +from pathlib import Path +from typing import Any, Union + +import toml + +from .paths import APPS_REPO_ROOT + + +def git_repo_age(path: Path) -> Union[bool, int]: + for file in [path / ".git" / "FETCH_HEAD", path / ".git" / "HEAD"]: + if file.exists(): + return int(time.time() - file.stat().st_mtime) + return False + + +@cache +def get_catalog(working_only: bool = False) -> dict[str, dict[str, Any]]: + """Load the app catalog and filter out the non-working ones""" + catalog = toml.load((APPS_REPO_ROOT / "apps.toml").open("r", encoding="utf-8")) + if working_only: + catalog = { + app: infos + for app, infos in catalog.items() + if infos.get("state") != "notworking" + } + return catalog + + +@cache +def get_categories() -> dict[str, Any]: + categories_path = APPS_REPO_ROOT / "categories.toml" + return toml.load(categories_path) + + +@cache +def get_antifeatures() -> dict[str, Any]: + antifeatures_path = APPS_REPO_ROOT / "antifeatures.toml" + return toml.load(antifeatures_path) + + +@cache +def get_wishlist() -> dict[str, dict[str, str]]: + wishlist_path = APPS_REPO_ROOT / "wishlist.toml" + return toml.load(wishlist_path) + + +@cache +def get_graveyard() -> dict[str, dict[str, str]]: + wishlist_path = APPS_REPO_ROOT / "graveyard.toml" + return toml.load(wishlist_path)