From e5f0d1df58010292ba4ea192014c7ea735b3aa78 Mon Sep 17 00:00:00 2001 From: "Rodney, Tiara" Date: Mon, 5 May 2025 01:20:13 +0200 Subject: [PATCH 01/92] feat(string): init ChunkedRollingHash --- NOTES | 88 +++++++++++ src/byteb4rb1e_utils/string.py | 139 +++++++++++++++++- .../string/test_chunked_hash.py | 56 +++++++ 3 files changed, 282 insertions(+), 1 deletion(-) create mode 100644 NOTES create mode 100644 tests/unit/byteb4rb1e_utils/string/test_chunked_hash.py diff --git a/NOTES b/NOTES new file mode 100644 index 0000000..1684c52 --- /dev/null +++ b/NOTES @@ -0,0 +1,88 @@ +These are just a couple of brain farts that came up and I'd rather note down. +There's no clear structure. + +RFC 1341 Boundary Matching in a Circular Buffer +1. Algorithm Considerations + +Knuth-Morris-Pratt (KMP) Limitations: + + Useful when patterns have prefix-suffix overlaps for efficient skipping. + + If the failure table consists only of zeros, KMP provides no speed advantage + over naive searching. + + Boundary pattern is arbitrary, meaning KMP’s preprocessing may not be + beneficial. + +Alternatives to KMP: + + Rabin-Karp rolling hash → Uses fast hash comparisons instead of + character-by-character matching. + + Boyer-Moore-Horspool → Precomputes skip distances to avoid redundant + comparisons, works well for longer patterns. + + Crochemore-Perrin two-way search → used by str.find(), flexible + but assumes a linear memory layout so not really applicable for my circular + buffer approach + +2. Boundary Characteristics + +Max length: 70 bytes. Character set: ASCII only. No structure guarantees: The +boundary is client-defined, so I must be able to handle arbitrary sequences. + +3. Algorithm Selection + +Rolling Hash → Best for arbitrary short-to-medium patterns in a circular buffer. +Boyer-Moore → Ideal if the boundary has distinct character distributions to +optimize skipping. + + + + +# Optimized Chunk-Based Rolling Hash Matching + +We need to efficiently detect an RFC 1341 multipart boundary inside a circular +buffer, ensuring minimal overhead while avoiding unnecessary comparisons. + +Traditional approaches like Knuth-Morris-Pratt (KMP) don’t provide an advantage +when the boundary lacks repeated subpatterns. Meanwhile, full rolling hash +matching scans every byte, which can be wasteful. + +Thus, we introduce a chunk-wise hash-based skipping strategy, allowing us to +skip large sections of the buffer when an early non-match is detected. + +## Core Idea + +Precompute hashes for evenly sized chunks of the boundary. -> First, match only +the hash of the first chunk → immediately skip unnecessary buffer sections if no +match. -> If the first chunk matches, progressively verify subsequent chunks +until the full boundary is confirmed. Benefits Over Full Matching + +## Benefits Over Full Matching + +- Reduces comparisons significantly → eliminates large sections early when + non-matches occur. +- Balances preprocessing cost vs runtime → faster + elimination means fewer wasted cycles. + Integrates seamlessly into circular buffers → allows skipping intelligently. + + +### Precompute Chunk Hashes + +- Divide the pattern into `N` equal-sized chunks (e.g., 7 chunks of 10 bytes + for a 70-byte boundary). +- Compute a rolling hash for each chunk in addition to the full pattern, storing + them for quick lookup. + +### Sliding Window Search in the Buffer + +- Compute the rolling hash for each window of size chunk_size. +- Compare the first chunk’s hash with the buffer window. +- If no match, skip boundary_length - chunk_size bytes. + +### Progressive Chunk Verification + +- If the first chunk matches, verify the next chunk sequentially. +- Continue matching chunks until the full boundary is confirmed. +- Perform final character-by-character validation to rule out hash collisions. diff --git a/src/byteb4rb1e_utils/string.py b/src/byteb4rb1e_utils/string.py index 800a5bc..88e117d 100644 --- a/src/byteb4rb1e_utils/string.py +++ b/src/byteb4rb1e_utils/string.py @@ -1,4 +1,6 @@ -from typing import Optional +from dataclasses import dataclass +import math +from typing import List, Optional, Tuple class RollingHash: @@ -89,3 +91,138 @@ class RollingHash: self._hash = (self._hash * self.base + new_byte) % self.mod return self._hash + + +@dataclass +class ChunkedRollingHashOptions: + """ + """ + max_chunk_size: int = 10 + base: int = RollingHash.base + mod: int = RollingHash.mod + + +class ChunkedRollingHash: + """Chunked Rolling hash for linear and circular buffers + + This implementation was inspired by the Rabin-Karp rolling hash + algorithm. + + A search pattern is chunked and for each chunk its hash is calculated. + + I came up with this approach as the requirement for efficient RFC1341 HTTP + multipart entity boundary matching for stream data in a circular/ring + buffer. I've tested a couple of algorithms, but none gave me any real + performance improvements over a naive/bruteforce search. + + That's how this algorithm came to be. Big O? I don't know (yet)... + + Why this is more performant for my specific use-cases? + ------------------------------------------------------ + + #. Precompute hashes for evenly sized chunks of a search pattern, in + addition of a hash of the full search-pattern. + #. First, match only the hash of the first chunk → immediately skip + unnecessary buffer sections if no match. + #. If the first chunk matches, progressively verify subsequent chunks, + until the full search pattern is confirmed. + + Benefits Over Full Matching + --------------------------- + + - Reduces comparisons significantly → eliminates large sections early when + non-matches occur. + - Balances preprocessing cost vs runtime → faster elimination means fewer + wasted cycles. + - Integrates seamlessly into circular buffers → allows skipping + intelligently. + """ + _chunk_count: int + #: hashes of chunks of search string + _chunks_hash: List[int] + #: hash of the full search string + _hash: int + #: length of search string + _length: int + #: remainder for calculating the actual size of the last chunk + _remainder: int + + _base: int + + _mod: int + + def __init__( + self, + data: bytes, + options: ChunkedRollingHashOptions = ChunkedRollingHashOptions() + ): + """ + """ + self._base = options.base + self._mod = options.mod + self._max_chunk_size = options.max_chunk_size + self._chunks_hash = [] + self._hash = RollingHash.compute_initial_hash( + data, + base = self._base, + mod = self._mod + ) + self._length = len(data) + + # only the last chunk differs in size; store its remainder separately + # for optimized handling + self._remainder = self._length % self._max_chunk_size + + self._chunk_count = math.ceil(self._length / self._max_chunk_size) + # tracks chunk progression during matching + self._current = 0 + + # precompute hashes for all chunks to enable rapid comparison + for i in range(0, self._chunk_count): + chunk = data[i*self._max_chunk_size:(i+1)*self._max_chunk_size] + + self._chunks_hash.append( + RollingHash.compute_initial_hash(chunk, base=self._base, mod=self._mod) + ) + + def match( + self, + data: bytes + ): + """match a buffer against a search string through chunked hashing + """ + # progressively match each chunk + for i in range(self._current, self._chunk_count - 1): + chunk = data[i*self._max_chunk_size:(i+1)*self._max_chunk_size] + + # no more data left to process + if chunk == b'': break + + chunk_hash = RollingHash.compute_initial_hash( + chunk, + base = self._base, + mod = self._mod + ) + + if chunk_hash != self._chunks_hash[i]: + self._current = 0 + return False + + self._current += 1 + + # processing hasn't completed for last chunk to be processed yet + if self._current != self._chunk_count - 1: + return + + last_chunk = data[-self._remainder:] + last_chunk_hash = RollingHash.compute_initial_hash( + last_chunk, + base = self._base, + mod = self._mod + ) + + if self._chunks_hash[self._current] == last_chunk_hash: + return True + + self._current = 0 + return False diff --git a/tests/unit/byteb4rb1e_utils/string/test_chunked_hash.py b/tests/unit/byteb4rb1e_utils/string/test_chunked_hash.py new file mode 100644 index 0000000..2d6a51b --- /dev/null +++ b/tests/unit/byteb4rb1e_utils/string/test_chunked_hash.py @@ -0,0 +1,56 @@ +import unittest + +from byteb4rb1e_utils.string import ( + ChunkedRollingHash, + ChunkedRollingHashOptions, + RollingHash, +) + +class test___init__(unittest.TestCase): + """ChunkedRollingHash.__init__()""" + def test_default(self): + """default options""" + result = ChunkedRollingHash(b'abcdefgh') + + self.assertEqual(result._mod, ChunkedRollingHashOptions.mod) + self.assertEqual(result._base, ChunkedRollingHashOptions.base) + self.assertEqual(result._max_chunk_size, ChunkedRollingHashOptions.max_chunk_size) + + control_hash = RollingHash.compute_initial_hash( + b'abcdefgh', + base = result._base, + mod = result._mod + ) + + self.assertEqual(result._length, 8) + self.assertEqual(result._chunk_count, 1) + self.assertEqual(len(result._chunks_hash), result._chunk_count) + self.assertEqual(result._hash, control_hash) + self.assertEqual(result._chunks_hash[0], control_hash) + + def test_override(self): + """override of options""" + options = ChunkedRollingHashOptions( + mod = 4, + base = 10, + max_chunk_size = 5, + ) + result = ChunkedRollingHash(b'abcdefgh', options) + + self.assertEqual(result._mod, options.mod) + self.assertEqual(result._base, options.base) + self.assertEqual(result._max_chunk_size, options.max_chunk_size) + + control_hash1 = RollingHash.compute_initial_hash( + b'abcde', + base = result._base, + mod = result._mod + ) + control_hash2 = RollingHash.compute_initial_hash( + b'fgh', + base = result._base, + mod = result._mod + ) + + self.assertEqual(result._chunks_hash[0], control_hash1) + self.assertEqual(result._chunks_hash[1], control_hash2) From a384efbe058435b528413426e9d817d28ba38346 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Fri, 20 Jun 2025 19:43:43 +0200 Subject: [PATCH 02/92] todo(6): open --- TODO | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/TODO b/TODO index e4fe7e9..22bafe4 100644 --- a/TODO +++ b/TODO @@ -109,3 +109,14 @@ Description: Implement my custom algorithm for doing rolling hash string search against a fixed length ring buffer --- + +ID: 6 +Type: feature +Title: implement importlib.resources handler for urllib +Status: open +Priority: high +Created: 2025-06-20 +Description: A handler that can be registered with an urllib.request + OpenerDirector to open importlib.resources package files. + +--- From 32d6a7a0dfd408c6de6eba2abb060175030ac9b6 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Fri, 20 Jun 2025 19:44:25 +0200 Subject: [PATCH 03/92] todo(6): in-progress --- TODO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO b/TODO index 22bafe4..75efac6 100644 --- a/TODO +++ b/TODO @@ -113,7 +113,7 @@ Description: Implement my custom algorithm for doing rolling hash string search ID: 6 Type: feature Title: implement importlib.resources handler for urllib -Status: open +Status: in-progress Priority: high Created: 2025-06-20 Description: A handler that can be registered with an urllib.request From d4068f464ba8f021b165bb30b76c494596a809a0 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Fri, 20 Jun 2025 19:48:05 +0200 Subject: [PATCH 04/92] todo(7): open --- TODO | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/TODO b/TODO index 75efac6..bcbbcbe 100644 --- a/TODO +++ b/TODO @@ -120,3 +120,14 @@ Description: A handler that can be registered with an urllib.request OpenerDirector to open importlib.resources package files. --- + +ID: 7 +Type: feature +Title: setup advanced testing environment +Status: open +Priority: high +Created: 2025-06-20 +Description: copy the testing environment setup from + byteb4rb1e.sphinxcontrib.ext + +--- From c6c6d806acc7f53038544dff09c6c0855038ed32 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Fri, 20 Jun 2025 19:48:22 +0200 Subject: [PATCH 05/92] todo(7): in-progress --- TODO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO b/TODO index bcbbcbe..0d227ca 100644 --- a/TODO +++ b/TODO @@ -124,7 +124,7 @@ Description: A handler that can be registered with an urllib.request ID: 7 Type: feature Title: setup advanced testing environment -Status: open +Status: in-progress Priority: high Created: 2025-06-20 Description: copy the testing environment setup from From c0adb4cdfbfd13720838b7cdcfdd1c08e1645331 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Fri, 20 Jun 2025 20:19:05 +0200 Subject: [PATCH 06/92] todo(8): open --- TODO | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/TODO b/TODO index 0d227ca..c4d3782 100644 --- a/TODO +++ b/TODO @@ -131,3 +131,13 @@ Description: copy the testing environment setup from byteb4rb1e.sphinxcontrib.ext --- + +ID: 8 +Type: bugfix +Title: rename package +Status: open +Priority: high +Created: 2025-06-20 +Description: use dot namespaces to make the package a little more elegant + +--- From b1a469a351f47b4be2e1c9048794c51b09c2b778 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Fri, 20 Jun 2025 19:52:58 +0200 Subject: [PATCH 07/92] feat(test): init tox config --- tox.ini | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 tox.ini diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..efb3353 --- /dev/null +++ b/tox.ini @@ -0,0 +1,44 @@ +[tox] +requires = + tox>=4.19 +env_list = + py3{8-12}-{unit} + lint + format + +[testenv] +deps = + . + +[testenv:lint] +description = run type check on code base +labels = static +deps = + mypy +commands = + mypy src tests --junit-xml test-reports/{env_name}.xml + +[testenv:audit] +description = run type check on code base +labels = audit +deps = + pip-audit +commands = + pip-audit . + +[testenv:format] +description = run type check on code base +labels = static +deps = + black +commands = + black --check src tests + +[testenv:py3{9-13}-unit] +description = run type check on code base +labels = unit +deps = + {[testenv]deps} + pytest +commands = + pytest tests/unit --junitxml=test-reports/{env_name}.xml From 6955b5e33049aeeb4b7bb13cb2a028e50f0de640 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Fri, 20 Jun 2025 19:54:22 +0200 Subject: [PATCH 08/92] feat(test): add entrypoints and runtime dependency --- Pipfile | 4 +- Pipfile.lock | 101 ++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 74 insertions(+), 31 deletions(-) diff --git a/Pipfile b/Pipfile index 89a5cd3..eb452da 100644 --- a/Pipfile +++ b/Pipfile @@ -11,10 +11,12 @@ pylint = "~=3.3.6" build = "*" pipenv = "*" byteb4rb1e-utils = { editable = true, path = '.'} +tox = "*" [requires] python_version = "3.11" [scripts] "build" = "python3 -m build" - +"test-static" = "tox run -m static" +"test-unit" = "tox run -m unit" diff --git a/Pipfile.lock b/Pipfile.lock index 7f78522..75f1b92 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "4847baa5a13a96f2c3de2a246a0c088806c308426c79d8105387dff1fe1f1e58" + "sha256": "9b051b97c212860afb34d1f834104a9bcaefc303b400f6b6828fe6e675b0f170" }, "pipfile-spec": 6, "requires": { @@ -19,11 +19,11 @@ "develop": { "astroid": { "hashes": [ - "sha256:622cc8e3048684aa42c820d9d218978021c3c3d174fb03a9f0d615921744f550", - "sha256:d05bfd0acba96a7bd43e222828b7d9bc1e138aaeb0649707908d3702a9831248" + "sha256:104fb9cb9b27ea95e847a94c003be03a9e039334a8ebca5ee27dafaf5c5711eb", + "sha256:c332157953060c6deb9caa57303ae0d20b0fbdb2e59b4a4f2a6ba49d0a7961ce" ], "markers": "python_full_version >= '3.9.0'", - "version": "==3.3.9" + "version": "==3.3.10" }, "autopep8": { "hashes": [ @@ -47,13 +47,29 @@ "editable": true, "path": "." }, + "cachetools": { + "hashes": [ + "sha256:1c7bb3cf9193deaf3508b7c5f2a79986c13ea38965c5adcff1f84519cf39163e", + "sha256:b4c4f404392848db3ce7aac34950d17be4d864da4b8b66911008e430bc544587" + ], + "markers": "python_version >= '3.9'", + "version": "==6.1.0" + }, "certifi": { "hashes": [ - "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", - "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3" + "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057", + "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b" ], - "markers": "python_version >= '3.6'", - "version": "==2025.4.26" + "markers": "python_version >= '3.7'", + "version": "==2025.6.15" + }, + "chardet": { + "hashes": [ + "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", + "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970" + ], + "markers": "python_version >= '3.7'", + "version": "==5.2.0" }, "colorama": { "hashes": [ @@ -159,20 +175,28 @@ }, "pipenv": { "hashes": [ - "sha256:85d42e13da78f27f0213c998dba9a59f3ba6a6fe9e420b75b561acc344f021ad", - "sha256:f26dc0352f3fb167c3897a66a5d8c9ab81dd52a836a48630712e1e5a06840ebf" + "sha256:87370bedcf0ff66d226af07ca341ae94afcc08fed90d57ad9fea9ffd44ced4d3", + "sha256:f0a67aa928824e61003d52acea72a94b180800019f03d38a311966f6330bc8d1" ], "index": "pypi", "markers": "python_version >= '3.9'", - "version": "==2025.0.2" + "version": "==2025.0.3" }, "platformdirs": { "hashes": [ - "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94", - "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351" + "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", + "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4" ], "markers": "python_version >= '3.9'", - "version": "==4.3.7" + "version": "==4.3.8" + }, + "pluggy": { + "hashes": [ + "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", + "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746" + ], + "markers": "python_version >= '3.9'", + "version": "==1.6.0" }, "pycodestyle": { "hashes": [ @@ -184,12 +208,20 @@ }, "pylint": { "hashes": [ - "sha256:8b7c2d3e86ae3f94fb27703d521dd0b9b6b378775991f504d7c3a6275aa0a6a6", - "sha256:b634a041aac33706d56a0d217e6587228c66427e20ec21a019bc4cdee48c040a" + "sha256:2b11de8bde49f9c5059452e0c310c079c746a0a8eeaa789e5aa966ecc23e4559", + "sha256:43860aafefce92fca4cf6b61fe199cdc5ae54ea28f9bf4cd49de267b5195803d" ], "index": "pypi", "markers": "python_full_version >= '3.9.0'", - "version": "==3.3.6" + "version": "==3.3.7" + }, + "pyproject-api": { + "hashes": [ + "sha256:43c9918f49daab37e302038fc1aed54a8c7a91a9fa935d00b9a485f37e0f5335", + "sha256:7d6238d92f8962773dd75b5f0c4a6a27cce092a14b623b811dba656f3b628948" + ], + "markers": "python_version >= '3.9'", + "version": "==1.9.1" }, "pyproject-hooks": { "hashes": [ @@ -201,11 +233,11 @@ }, "setuptools": { "hashes": [ - "sha256:a65cffc4fb86167e3020b3ef58e08226baad8b29a3b34ce2c9d07e901bac481d", - "sha256:ec8308eb180b2312062b1c5523204acf872cd8b0a9e6c2ae76431b22bc4065d7" + "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", + "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c" ], "markers": "python_version >= '3.9'", - "version": "==80.3.0" + "version": "==80.9.0" }, "setuptools-scm": { "hashes": [ @@ -218,27 +250,36 @@ }, "tomlkit": { "hashes": [ - "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde", - "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79" + "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", + "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0" ], "markers": "python_version >= '3.8'", - "version": "==0.13.2" + "version": "==0.13.3" + }, + "tox": { + "hashes": [ + "sha256:2b8a7fb986b82aa2c830c0615082a490d134e0626dbc9189986da46a313c4f20", + "sha256:b97d5ecc0c0d5755bcc5348387fef793e1bfa68eb33746412f4c60881d7f5f57" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==4.27.0" }, "typing-extensions": { "hashes": [ - "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", - "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef" + "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", + "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af" ], - "markers": "python_version >= '3.8'", - "version": "==4.13.2" + "markers": "python_version >= '3.9'", + "version": "==4.14.0" }, "virtualenv": { "hashes": [ - "sha256:800863162bcaa5450a6e4d721049730e7f2dae07720e0902b0e4040bd6f9ada8", - "sha256:e34302959180fca3af42d1800df014b35019490b119eba981af27f2fa486e5d6" + "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11", + "sha256:e10c0a9d02835e592521be48b332b6caee6887f332c111aa79a09b9e79efc2af" ], "markers": "python_version >= '3.8'", - "version": "==20.30.0" + "version": "==20.31.2" } } } From ab626d5c8eb9f60472d1185a7e44012dabd641c1 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Fri, 20 Jun 2025 19:57:57 +0200 Subject: [PATCH 09/92] chore(test): remove redundant dependencies dependencies for test environments are handled by tox and defined in tox.ini --- Pipfile | 3 -- Pipfile.lock | 123 +-------------------------------------------------- 2 files changed, 1 insertion(+), 125 deletions(-) diff --git a/Pipfile b/Pipfile index eb452da..d693e3e 100644 --- a/Pipfile +++ b/Pipfile @@ -4,10 +4,7 @@ verify_ssl = true name = "pypi" [dev-packages] -mypy = "~=1.15.0" -autopep8 = "~=2.3.2" setuptools-scm = "~=8.2.0" -pylint = "~=3.3.6" build = "*" pipenv = "*" byteb4rb1e-utils = { editable = true, path = '.'} diff --git a/Pipfile.lock b/Pipfile.lock index 75f1b92..aa123f0 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "9b051b97c212860afb34d1f834104a9bcaefc303b400f6b6828fe6e675b0f170" + "sha256": "cb7c8c0a12f574d2bc30ffe38e79ba18ee29424cb1fb1cdce8373f89d56f3e1c" }, "pipfile-spec": 6, "requires": { @@ -17,23 +17,6 @@ }, "default": {}, "develop": { - "astroid": { - "hashes": [ - "sha256:104fb9cb9b27ea95e847a94c003be03a9e039334a8ebca5ee27dafaf5c5711eb", - "sha256:c332157953060c6deb9caa57303ae0d20b0fbdb2e59b4a4f2a6ba49d0a7961ce" - ], - "markers": "python_full_version >= '3.9.0'", - "version": "==3.3.10" - }, - "autopep8": { - "hashes": [ - "sha256:89440a4f969197b69a995e4ce0661b031f455a9f776d2c5ba3dbd83466931758", - "sha256:ce8ad498672c845a0c3de2629c15b635ec2b05ef8177a6e7c91c74f3e9b51128" - ], - "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==2.3.2" - }, "build": { "hashes": [ "sha256:1d61c0887fa860c01971625baae8bdd338e517b836a2f70dd1f7aa3a6b2fc5b5", @@ -79,14 +62,6 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'", "version": "==0.4.6" }, - "dill": { - "hashes": [ - "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0", - "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049" - ], - "markers": "python_version >= '3.8'", - "version": "==0.4.0" - }, "distlib": { "hashes": [ "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", @@ -102,69 +77,6 @@ "markers": "python_version >= '3.9'", "version": "==3.18.0" }, - "isort": { - "hashes": [ - "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450", - "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615" - ], - "markers": "python_full_version >= '3.9.0'", - "version": "==6.0.1" - }, - "mccabe": { - "hashes": [ - "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", - "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e" - ], - "markers": "python_version >= '3.6'", - "version": "==0.7.0" - }, - "mypy": { - "hashes": [ - "sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e", - "sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22", - "sha256:1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f", - "sha256:1fbb8da62dc352133d7d7ca90ed2fb0e9d42bb1a32724c287d3c76c58cbaa9c2", - "sha256:2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f", - "sha256:2e2c2e6d3593f6451b18588848e66260ff62ccca522dd231cd4dd59b0160668b", - "sha256:2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5", - "sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f", - "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43", - "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e", - "sha256:5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c", - "sha256:6983aae8b2f653e098edb77f893f7b6aca69f6cffb19b2cc7443f23cce5f4828", - "sha256:712e962a6357634fef20412699a3655c610110e01cdaa6180acec7fc9f8513ba", - "sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee", - "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d", - "sha256:8f8722560a14cde92fdb1e31597760dc35f9f5524cce17836c0d22841830fd5b", - "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445", - "sha256:973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e", - "sha256:979e4e1a006511dacf628e36fadfecbcc0160a8af6ca7dad2f5025529e082c13", - "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5", - "sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd", - "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf", - "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357", - "sha256:be68172e9fd9ad8fb876c6389f16d1c1b5f100ffa779f77b1fb2176fcc9ab95b", - "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036", - "sha256:c4bb0e1bd29f7d34efcccd71cf733580191e9a264a2202b0239da95984c5b559", - "sha256:c7be1e46525adfa0d97681432ee9fcd61a3964c2446795714699a998d193f1a3", - "sha256:c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f", - "sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464", - "sha256:d10d994b41fb3497719bbf866f227b3489048ea4bbbb5015357db306249f7980", - "sha256:e601a7fa172c2131bff456bb3ee08a88360760d0d2f8cbd7a75a65497e2df078", - "sha256:f95579473af29ab73a10bada2f9722856792a36ec5af5399b653aa28360290a5" - ], - "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==1.15.0" - }, - "mypy-extensions": { - "hashes": [ - "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", - "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558" - ], - "markers": "python_version >= '3.8'", - "version": "==1.1.0" - }, "packaging": { "hashes": [ "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", @@ -198,23 +110,6 @@ "markers": "python_version >= '3.9'", "version": "==1.6.0" }, - "pycodestyle": { - "hashes": [ - "sha256:35863c5974a271c7a726ed228a14a4f6daf49df369d8c50cd9a6f58a5e143ba9", - "sha256:c8415bf09abe81d9c7f872502a6eee881fbe85d8763dd5b9924bb0a01d67efae" - ], - "markers": "python_version >= '3.9'", - "version": "==2.13.0" - }, - "pylint": { - "hashes": [ - "sha256:2b11de8bde49f9c5059452e0c310c079c746a0a8eeaa789e5aa966ecc23e4559", - "sha256:43860aafefce92fca4cf6b61fe199cdc5ae54ea28f9bf4cd49de267b5195803d" - ], - "index": "pypi", - "markers": "python_full_version >= '3.9.0'", - "version": "==3.3.7" - }, "pyproject-api": { "hashes": [ "sha256:43c9918f49daab37e302038fc1aed54a8c7a91a9fa935d00b9a485f37e0f5335", @@ -248,14 +143,6 @@ "markers": "python_version >= '3.8'", "version": "==8.2.0" }, - "tomlkit": { - "hashes": [ - "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", - "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0" - ], - "markers": "python_version >= '3.8'", - "version": "==0.13.3" - }, "tox": { "hashes": [ "sha256:2b8a7fb986b82aa2c830c0615082a490d134e0626dbc9189986da46a313c4f20", @@ -265,14 +152,6 @@ "markers": "python_version >= '3.9'", "version": "==4.27.0" }, - "typing-extensions": { - "hashes": [ - "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", - "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af" - ], - "markers": "python_version >= '3.9'", - "version": "==4.14.0" - }, "virtualenv": { "hashes": [ "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11", From dc69eea88af659047451e9d3462fd34a92902066 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Fri, 20 Jun 2025 20:04:15 +0200 Subject: [PATCH 10/92] chore(test): remove redundant module entrypoints pytest compared to built-in unittest does not discover test suites based on a directory being marked as a module, instead matching against the basename of a file to determine whether it is a test suite or not. --- tests/unit/__init__.py | 0 tests/unit/byteb4rb1e_utils/collections/__init__.py | 0 tests/unit/byteb4rb1e_utils/io/__init__.py | 0 tests/unit/byteb4rb1e_utils/string/__init__.py | 0 4 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 tests/unit/__init__.py delete mode 100644 tests/unit/byteb4rb1e_utils/collections/__init__.py delete mode 100644 tests/unit/byteb4rb1e_utils/io/__init__.py delete mode 100644 tests/unit/byteb4rb1e_utils/string/__init__.py diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/unit/byteb4rb1e_utils/collections/__init__.py b/tests/unit/byteb4rb1e_utils/collections/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/unit/byteb4rb1e_utils/io/__init__.py b/tests/unit/byteb4rb1e_utils/io/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/unit/byteb4rb1e_utils/string/__init__.py b/tests/unit/byteb4rb1e_utils/string/__init__.py deleted file mode 100644 index e69de29..0000000 From b6a99d4b2d294a4a85879c4f8f40f5a018508091 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Fri, 20 Jun 2025 20:05:32 +0200 Subject: [PATCH 11/92] chore(test): ignore test-reports --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5c8bfee..dbfbc60 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ /configure~ *.swo *.swp +/test-reports/ From 6bf67f4a881593648092fd863df4816089cfbef5 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Fri, 20 Jun 2025 20:17:56 +0200 Subject: [PATCH 12/92] chore(test): update Makefile --- Makefile | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 37439f1..f1b82b3 100644 --- a/Makefile +++ b/Makefile @@ -16,8 +16,13 @@ configure: configure.ac .venv/bin/python3 -m pip install --upgrade pip .venv/bin/pip install -r requirements-dev.txt -test-reports: - .venv/bin/python3 -m unittest discover -v +test-reports: test-reports/unit test-reports/static + +test-reports/unit: + python3 -m pipenv run -v test-unit + +test-reports/static: + python3 -m pipenv run -v test-static build: .venv/bin/pipenv .venv/bin/pipenv run build From f0f36542f457727c5bae1d43802ae778c3b32204 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Fri, 20 Jun 2025 20:21:06 +0200 Subject: [PATCH 13/92] todo(7): done --- TODO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO b/TODO index c4d3782..e748d19 100644 --- a/TODO +++ b/TODO @@ -124,7 +124,7 @@ Description: A handler that can be registered with an urllib.request ID: 7 Type: feature Title: setup advanced testing environment -Status: in-progress +Status: done Priority: high Created: 2025-06-20 Description: copy the testing environment setup from From dd57ecabb937baf268e9f2af35962123146f66b2 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Fri, 20 Jun 2025 20:25:51 +0200 Subject: [PATCH 14/92] todo(8): in-progress --- TODO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO b/TODO index e748d19..ae2d7f6 100644 --- a/TODO +++ b/TODO @@ -135,7 +135,7 @@ Description: copy the testing environment setup from ID: 8 Type: bugfix Title: rename package -Status: open +Status: in-progress Priority: high Created: 2025-06-20 Description: use dot namespaces to make the package a little more elegant From 1fb1e0d0bf50f61c10746b0c14e6c6922c5de914 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Fri, 20 Jun 2025 20:33:37 +0200 Subject: [PATCH 15/92] chore: rename package --- pyproject.toml | 2 +- src/{byteb4rb1e_utils => byteb4rb1e/utils}/collections.py | 0 src/{byteb4rb1e_utils => byteb4rb1e/utils}/http/__init__.py | 0 .../utils}/http/server/__init__.py | 2 +- .../utils}/http/server/__main__.py | 4 ++-- src/{byteb4rb1e_utils => byteb4rb1e/utils}/io/__init__.py | 0 src/{byteb4rb1e_utils => byteb4rb1e/utils}/string.py | 0 tests/unit/{byteb4rb1e_utils => byteb4rb1e/utils}/__init__.py | 0 .../utils}/collections/test_circular_buffer.py | 2 +- .../utils}/io/test_chunksio.py | 2 +- .../utils}/string/test_rolling_hash.py | 2 +- 11 files changed, 7 insertions(+), 7 deletions(-) rename src/{byteb4rb1e_utils => byteb4rb1e/utils}/collections.py (100%) rename src/{byteb4rb1e_utils => byteb4rb1e/utils}/http/__init__.py (100%) rename src/{byteb4rb1e_utils => byteb4rb1e/utils}/http/server/__init__.py (99%) rename src/{byteb4rb1e_utils => byteb4rb1e/utils}/http/server/__main__.py (96%) rename src/{byteb4rb1e_utils => byteb4rb1e/utils}/io/__init__.py (100%) rename src/{byteb4rb1e_utils => byteb4rb1e/utils}/string.py (100%) rename tests/unit/{byteb4rb1e_utils => byteb4rb1e/utils}/__init__.py (100%) rename tests/unit/{byteb4rb1e_utils => byteb4rb1e/utils}/collections/test_circular_buffer.py (98%) rename tests/unit/{byteb4rb1e_utils => byteb4rb1e/utils}/io/test_chunksio.py (99%) rename tests/unit/{byteb4rb1e_utils => byteb4rb1e/utils}/string/test_rolling_hash.py (97%) diff --git a/pyproject.toml b/pyproject.toml index b5fcdb7..fd23512 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ requires = [ build-backend = "setuptools.build_meta" [project] -name = "byteb4rb1e-utils" +name = "byteb4rb1e.utils" description = "personal utilities and helpers" authors = [ { name = "Tiara Rodney", email = "tiara.rodney@administratrix.de" } diff --git a/src/byteb4rb1e_utils/collections.py b/src/byteb4rb1e/utils/collections.py similarity index 100% rename from src/byteb4rb1e_utils/collections.py rename to src/byteb4rb1e/utils/collections.py diff --git a/src/byteb4rb1e_utils/http/__init__.py b/src/byteb4rb1e/utils/http/__init__.py similarity index 100% rename from src/byteb4rb1e_utils/http/__init__.py rename to src/byteb4rb1e/utils/http/__init__.py diff --git a/src/byteb4rb1e_utils/http/server/__init__.py b/src/byteb4rb1e/utils/http/server/__init__.py similarity index 99% rename from src/byteb4rb1e_utils/http/server/__init__.py rename to src/byteb4rb1e/utils/http/server/__init__.py index c1f6186..2486612 100644 --- a/src/byteb4rb1e_utils/http/server/__init__.py +++ b/src/byteb4rb1e/utils/http/server/__init__.py @@ -1,7 +1,7 @@ from dataclasses import dataclass from http.server import SimpleHTTPRequestHandler -from byteb4rb1e_utils.io import ChunksIO +from byteb4rb1e.utils.io import ChunksIO @dataclass diff --git a/src/byteb4rb1e_utils/http/server/__main__.py b/src/byteb4rb1e/utils/http/server/__main__.py similarity index 96% rename from src/byteb4rb1e_utils/http/server/__main__.py rename to src/byteb4rb1e/utils/http/server/__main__.py index 7bca378..b66bc52 100644 --- a/src/byteb4rb1e_utils/http/server/__main__.py +++ b/src/byteb4rb1e/utils/http/server/__main__.py @@ -8,12 +8,12 @@ from http.server import HTTPServer from io import BytesIO, IOBase from typing import Optional, Tuple, List -from byteb4rb1e_utils.http.server import ( +from byteb4rb1e.utils.http.server import ( HandlerOptions, MultipartUploadHandler, ServerOptions, ) -from byteb4rb1e_utils.io import ChunksIO +from byteb4rb1e.utils.io import ChunksIO __doc__ = """tsmuds - Tiara's Simple Multipart Upload Debugging Server diff --git a/src/byteb4rb1e_utils/io/__init__.py b/src/byteb4rb1e/utils/io/__init__.py similarity index 100% rename from src/byteb4rb1e_utils/io/__init__.py rename to src/byteb4rb1e/utils/io/__init__.py diff --git a/src/byteb4rb1e_utils/string.py b/src/byteb4rb1e/utils/string.py similarity index 100% rename from src/byteb4rb1e_utils/string.py rename to src/byteb4rb1e/utils/string.py diff --git a/tests/unit/byteb4rb1e_utils/__init__.py b/tests/unit/byteb4rb1e/utils/__init__.py similarity index 100% rename from tests/unit/byteb4rb1e_utils/__init__.py rename to tests/unit/byteb4rb1e/utils/__init__.py diff --git a/tests/unit/byteb4rb1e_utils/collections/test_circular_buffer.py b/tests/unit/byteb4rb1e/utils/collections/test_circular_buffer.py similarity index 98% rename from tests/unit/byteb4rb1e_utils/collections/test_circular_buffer.py rename to tests/unit/byteb4rb1e/utils/collections/test_circular_buffer.py index aeb0934..1baf601 100644 --- a/tests/unit/byteb4rb1e_utils/collections/test_circular_buffer.py +++ b/tests/unit/byteb4rb1e/utils/collections/test_circular_buffer.py @@ -1,6 +1,6 @@ import unittest -from byteb4rb1e_utils.collections import CircularBuffer +from byteb4rb1e.utils.collections import CircularBuffer class test_init(unittest.TestCase): """CircularBuffer.__init__()""" diff --git a/tests/unit/byteb4rb1e_utils/io/test_chunksio.py b/tests/unit/byteb4rb1e/utils/io/test_chunksio.py similarity index 99% rename from tests/unit/byteb4rb1e_utils/io/test_chunksio.py rename to tests/unit/byteb4rb1e/utils/io/test_chunksio.py index 4218d69..a27f20f 100644 --- a/tests/unit/byteb4rb1e_utils/io/test_chunksio.py +++ b/tests/unit/byteb4rb1e/utils/io/test_chunksio.py @@ -1,7 +1,7 @@ from io import BytesIO, IOBase import unittest -from byteb4rb1e_utils.io import ChunksIO +from byteb4rb1e.utils.io import ChunksIO class TestGetChunkSize(unittest.TestCase): diff --git a/tests/unit/byteb4rb1e_utils/string/test_rolling_hash.py b/tests/unit/byteb4rb1e/utils/string/test_rolling_hash.py similarity index 97% rename from tests/unit/byteb4rb1e_utils/string/test_rolling_hash.py rename to tests/unit/byteb4rb1e/utils/string/test_rolling_hash.py index 52c146a..590f527 100644 --- a/tests/unit/byteb4rb1e_utils/string/test_rolling_hash.py +++ b/tests/unit/byteb4rb1e/utils/string/test_rolling_hash.py @@ -1,6 +1,6 @@ import unittest -from byteb4rb1e_utils.string import RollingHash +from byteb4rb1e.utils.string import RollingHash class test_compute_initial_hash(unittest.TestCase): """RollingHash.compute_initial_hash() From fb0c65c6af2dc29c5f823214b1d36c1e7e5437b6 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Fri, 20 Jun 2025 20:35:11 +0200 Subject: [PATCH 16/92] todo(8): done --- TODO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO b/TODO index ae2d7f6..d762bd5 100644 --- a/TODO +++ b/TODO @@ -135,7 +135,7 @@ Description: copy the testing environment setup from ID: 8 Type: bugfix Title: rename package -Status: in-progress +Status: done Priority: high Created: 2025-06-20 Description: use dot namespaces to make the package a little more elegant From 576aad9d4c48d636ae286672c2932a76750768bd Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Fri, 20 Jun 2025 20:37:26 +0200 Subject: [PATCH 17/92] todo(9): open --- TODO | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/TODO b/TODO index d762bd5..def7ca6 100644 --- a/TODO +++ b/TODO @@ -141,3 +141,14 @@ Created: 2025-06-20 Description: use dot namespaces to make the package a little more elegant --- + +ID: 9 +Type: bugfix +Title: fix LICENSE reference +Status: open +Priority: high +Created: 2025-06-20 +Description: license specification is no longer a trove classifier in + pyproject.toml, hence the reference to LICENSE must be changed + +--- From e0e99480e36251dcaa6d98439209c8377249ce38 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Fri, 20 Jun 2025 20:38:14 +0200 Subject: [PATCH 18/92] todo(9): in-progress --- TODO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO b/TODO index def7ca6..e7f9b56 100644 --- a/TODO +++ b/TODO @@ -145,7 +145,7 @@ Description: use dot namespaces to make the package a little more elegant ID: 9 Type: bugfix Title: fix LICENSE reference -Status: open +Status: in-progress Priority: high Created: 2025-06-20 Description: license specification is no longer a trove classifier in From 7e8082bae2d2f7ac98db5e6a3f757b97292ea10f Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Fri, 20 Jun 2025 20:39:01 +0200 Subject: [PATCH 19/92] feat(license): add unlicense license Don't know what to license this under yet --- LICENSE | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e69de29 From d799c62c78feabf922f750b36b8023244842e0c9 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Fri, 20 Jun 2025 20:39:34 +0200 Subject: [PATCH 20/92] chore: change license reference --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index fd23512..5bc3eb4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ description = "personal utilities and helpers" authors = [ { name = "Tiara Rodney", email = "tiara.rodney@administratrix.de" } ] -license = { file = "LICENSE" } +license-files = ["LICENSE"] readme = "README.md" classifiers = [ "Development Status :: 1 - Planning", From 324df0e6d2d125086fb854696e64abf92af7cefd Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Fri, 20 Jun 2025 20:39:53 +0200 Subject: [PATCH 21/92] todo(9): done --- TODO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO b/TODO index e7f9b56..be8e36a 100644 --- a/TODO +++ b/TODO @@ -145,7 +145,7 @@ Description: use dot namespaces to make the package a little more elegant ID: 9 Type: bugfix Title: fix LICENSE reference -Status: in-progress +Status: done Priority: high Created: 2025-06-20 Description: license specification is no longer a trove classifier in From 44e35846a50c673221e9b880faaab189415d10ce Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Fri, 20 Jun 2025 21:00:31 +0200 Subject: [PATCH 22/92] todo(10): open --- TODO | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/TODO b/TODO index be8e36a..3b2a0c8 100644 --- a/TODO +++ b/TODO @@ -152,3 +152,14 @@ Description: license specification is no longer a trove classifier in pyproject.toml, hence the reference to LICENSE must be changed --- + +ID: 10 +Type: feature +Title: pytest current test context fixtures +Status: open +Priority: high +Created: 2025-06-20 +Description: add fixtures for doing things in relation to the active testing + context + +--- From c579ddd0226261f830cbe21101583d8e47a60686 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Fri, 20 Jun 2025 21:00:53 +0200 Subject: [PATCH 23/92] todo(10): in-progress --- TODO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO b/TODO index 3b2a0c8..0b3b40c 100644 --- a/TODO +++ b/TODO @@ -156,7 +156,7 @@ Description: license specification is no longer a trove classifier in ID: 10 Type: feature Title: pytest current test context fixtures -Status: open +Status: in-progress Priority: high Created: 2025-06-20 Description: add fixtures for doing things in relation to the active testing From 644beb86964fcb29c3f3b664d44a07b73c062b2c Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Fri, 20 Jun 2025 21:47:17 +0200 Subject: [PATCH 24/92] feat(testing): init pytest fixtures current_test fixture allows to retrieve the current test context, that is exposed through the shell environment --- Makefile | 3 +++ Pipfile | 1 + src/byteb4rb1e/utils/testing/pytest/fixtures.py | 14 ++++++++++++++ .../utils/testing/pytest/test_fixtures.py | 16 ++++++++++++++++ tests/integration/conftest.py | 5 +++++ tox.ini | 14 ++++++++++++-- 6 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 src/byteb4rb1e/utils/testing/pytest/fixtures.py create mode 100644 tests/integration/byteb4rb1e/utils/testing/pytest/test_fixtures.py create mode 100644 tests/integration/conftest.py diff --git a/Makefile b/Makefile index f1b82b3..0a1ec21 100644 --- a/Makefile +++ b/Makefile @@ -21,6 +21,9 @@ test-reports: test-reports/unit test-reports/static test-reports/unit: python3 -m pipenv run -v test-unit +test-reports/integration: + python3 -m pipenv run -v test-integration + test-reports/static: python3 -m pipenv run -v test-static diff --git a/Pipfile b/Pipfile index d693e3e..fa10d4e 100644 --- a/Pipfile +++ b/Pipfile @@ -17,3 +17,4 @@ python_version = "3.11" "build" = "python3 -m build" "test-static" = "tox run -m static" "test-unit" = "tox run -m unit" +"test-integration" = "tox run -m integration" diff --git a/src/byteb4rb1e/utils/testing/pytest/fixtures.py b/src/byteb4rb1e/utils/testing/pytest/fixtures.py new file mode 100644 index 0000000..7415a22 --- /dev/null +++ b/src/byteb4rb1e/utils/testing/pytest/fixtures.py @@ -0,0 +1,14 @@ +import os +from pathlib import Path +from typing import Tuple + +import pytest + + +@pytest.fixture +def current_test() -> Tuple[Path, str]: + """ + """ + suite_path, case_name = os.getenv('PYTEST_CURRENT_TEST').split('::', 1) + case_name = case_name.split(' ', 1)[0] + return Path(suite_path).resolve(), case_name diff --git a/tests/integration/byteb4rb1e/utils/testing/pytest/test_fixtures.py b/tests/integration/byteb4rb1e/utils/testing/pytest/test_fixtures.py new file mode 100644 index 0000000..56302e5 --- /dev/null +++ b/tests/integration/byteb4rb1e/utils/testing/pytest/test_fixtures.py @@ -0,0 +1,16 @@ +from pathlib import Path + +import pytest + +pytestmark = pytest.mark.pytest + +from byteb4rb1e.utils.testing.pytest.fixtures import current_test + + +def test_current_test(current_test): + """ + """ + suite_path, case_name = current_test + + assert str(Path(__file__)) == str(suite_path) + assert case_name == "test_current_test" diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py new file mode 100644 index 0000000..7c7aa51 --- /dev/null +++ b/tests/integration/conftest.py @@ -0,0 +1,5 @@ +def pytest_configure(config): + # register an additional marker + config.addinivalue_line( + "markers", "pytest: test pytest integration" + ) diff --git a/tox.ini b/tox.ini index efb3353..0f03dd8 100644 --- a/tox.ini +++ b/tox.ini @@ -2,7 +2,8 @@ requires = tox>=4.19 env_list = - py3{8-12}-{unit} + unit-py3{9-13} + integration-py3{9-13}-pytest8 lint format @@ -34,7 +35,7 @@ deps = commands = black --check src tests -[testenv:py3{9-13}-unit] +[testenv:unit-py3{9-13}] description = run type check on code base labels = unit deps = @@ -42,3 +43,12 @@ deps = pytest commands = pytest tests/unit --junitxml=test-reports/{env_name}.xml + +[testenv:integration-py3{9-13}-pytest8] +description = run pytest integration tests +labels = integration +deps = + {[testenv]deps} + pytest8: pytest>=8.0,<=9.0 +commands = + pytest tests/integration -m pytest --junitxml=test-reports/{env_name}.xml From 03561be791a2ac8a8b5ac3053d6f933506647f8b Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Fri, 20 Jun 2025 21:48:21 +0200 Subject: [PATCH 25/92] todo(10): done --- TODO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO b/TODO index 0b3b40c..81b72b8 100644 --- a/TODO +++ b/TODO @@ -156,7 +156,7 @@ Description: license specification is no longer a trove classifier in ID: 10 Type: feature Title: pytest current test context fixtures -Status: in-progress +Status: done Priority: high Created: 2025-06-20 Description: add fixtures for doing things in relation to the active testing From 1ea3b3a24dc757350a07123215169f14fcf8003b Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Fri, 20 Jun 2025 20:54:32 +0200 Subject: [PATCH 26/92] feat(urllib): init PkgHandler --- src/byteb4rb1e/utils/urllib/__init__.py | 0 src/byteb4rb1e/utils/urllib/request.py | 36 +++++++++++++++++++ .../byteb4rb1e/utils/urllib/test_request.py | 9 +++++ 3 files changed, 45 insertions(+) create mode 100644 src/byteb4rb1e/utils/urllib/__init__.py create mode 100644 src/byteb4rb1e/utils/urllib/request.py create mode 100644 tests/unit/byteb4rb1e/utils/urllib/test_request.py diff --git a/src/byteb4rb1e/utils/urllib/__init__.py b/src/byteb4rb1e/utils/urllib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/byteb4rb1e/utils/urllib/request.py b/src/byteb4rb1e/utils/urllib/request.py new file mode 100644 index 0000000..28e8e8e --- /dev/null +++ b/src/byteb4rb1e/utils/urllib/request.py @@ -0,0 +1,36 @@ +import importlib.resources +from urllib.request import URLError +import urllib.request + + +class PkgHandler(urllib.request.BaseHandler): + """ + """ + def pkg_open(self, req): + pkg_files = importlib.resources.files(req.host) + + raise Exception(sorted(pkg_files.glob('**/*'))) + + try: + fh = list( + pkg_files.glob(req.selector.lstrip('//')) + )[0].open('rb') + except Exception as e: + raise URLError(f'{e.__class__.__name__}: {e}') from e + + fh.seek(0, 2); + size = fh.tell(); + fh.seek(0); + + mtype, _ = mimetypes.guess_type(url) + + headers = email.message_from_string( + 'Content-Type: %s\nContent-Length: %d\n' % + (mtype or 'text/plain', size) + ) + + if not mtype or mtype.starts_with('text/'): + fh.close() + fh = importlib.resources.files(req.host).glob(req.selector)[0].open('r') + + return urllib.request.addinfourl(fh, header) diff --git a/tests/unit/byteb4rb1e/utils/urllib/test_request.py b/tests/unit/byteb4rb1e/utils/urllib/test_request.py new file mode 100644 index 0000000..28548ef --- /dev/null +++ b/tests/unit/byteb4rb1e/utils/urllib/test_request.py @@ -0,0 +1,9 @@ +from byteb4rb1e.utils.urllib.request import PkgHandler + +class TestPkgHandler: + """ + """ + def test_default(self): + """ + """ + pass From 43cdf21d4bb2fa7ff7e54abe78c74bc126753a43 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Fri, 20 Jun 2025 23:16:16 +0200 Subject: [PATCH 27/92] feat(testing.pytest): add subprocess decorator --- .../utils/testing/pytest/__init__.py | 14 ++++++ .../utils/testing/pytest/decorators.py | 47 +++++++++++++++++++ .../utils/testing/pytest/fixtures.py | 6 +-- .../byteb4rb1e/utils/testing/pytest/test_.py | 33 +++++++++++++ .../utils/testing/pytest/test_decorators.py | 21 +++++++++ .../byteb4rb1e/utils/urllib/test_request.py | 4 ++ 6 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 src/byteb4rb1e/utils/testing/pytest/__init__.py create mode 100644 src/byteb4rb1e/utils/testing/pytest/decorators.py create mode 100644 tests/integration/byteb4rb1e/utils/testing/pytest/test_.py create mode 100644 tests/integration/byteb4rb1e/utils/testing/pytest/test_decorators.py diff --git a/src/byteb4rb1e/utils/testing/pytest/__init__.py b/src/byteb4rb1e/utils/testing/pytest/__init__.py new file mode 100644 index 0000000..87e7c10 --- /dev/null +++ b/src/byteb4rb1e/utils/testing/pytest/__init__.py @@ -0,0 +1,14 @@ +import os +from pathlib import Path +from typing import Tuple + + +def get_current_test() -> Tuple[Path, str]: + current_test_env = os.getenv("PYTEST_CURRENT_TEST") + if current_test_env is None: + raise RuntimeError("PYTEST_CURRENT_TEST not set. Must be run under pytest.") + + suite_path, case_name = current_test_env.split('::', 1) + case_name = case_name.split(' ', 1)[0] + return Path(suite_path).resolve(), case_name + diff --git a/src/byteb4rb1e/utils/testing/pytest/decorators.py b/src/byteb4rb1e/utils/testing/pytest/decorators.py new file mode 100644 index 0000000..bf1066b --- /dev/null +++ b/src/byteb4rb1e/utils/testing/pytest/decorators.py @@ -0,0 +1,47 @@ +from functools import wraps +from pathlib import Path +import os +import subprocess +import sys + +from byteb4rb1e.utils.testing.pytest import get_current_test + + +def run_in_subprocess_once(): + """ + A decorator that reruns th test in a subprocess if not already inside one. + Requires pytest to be installed and test to be run by pytest. + + For what? Anything that can't be done in a thread-safe manner, e.g. modifying PYTHON_PATH + """ + def decorator(test_func): + @wraps(test_func) + def wrapper(*args, **kwargs): + if os.environ.get("XPYTEST_INSIDE_SUBPROCESS") == "1": + return test_func(*args, **kwargs) + + suite_path, case_name = get_current_test() + + cmd = [ + sys.executable, + "-m", "pytest", + f"{suite_path}::{case_name}", + ] + + result = subprocess.run( + cmd, + env={**os.environ, "XPYTEST_INSIDE_SUBPROCESS": "1"}, + capture_output=True, + text=True, + ) + + if result.returncode != 0: + print(' '.join(cmd)) + print("==== Subprocess stdout ====") + print(result.stdout) + print("==== Subprocess stderr ====") + print(result.stderr) + raise AssertionError(f"Subprocess test failed with exit code {result.returncode}") + return wrapper + return decorator + diff --git a/src/byteb4rb1e/utils/testing/pytest/fixtures.py b/src/byteb4rb1e/utils/testing/pytest/fixtures.py index 7415a22..dc87091 100644 --- a/src/byteb4rb1e/utils/testing/pytest/fixtures.py +++ b/src/byteb4rb1e/utils/testing/pytest/fixtures.py @@ -4,11 +4,11 @@ from typing import Tuple import pytest +from byteb4rb1e.utils.testing.pytest import get_current_test + @pytest.fixture def current_test() -> Tuple[Path, str]: """ """ - suite_path, case_name = os.getenv('PYTEST_CURRENT_TEST').split('::', 1) - case_name = case_name.split(' ', 1)[0] - return Path(suite_path).resolve(), case_name + return get_current_test() diff --git a/tests/integration/byteb4rb1e/utils/testing/pytest/test_.py b/tests/integration/byteb4rb1e/utils/testing/pytest/test_.py new file mode 100644 index 0000000..57a0410 --- /dev/null +++ b/tests/integration/byteb4rb1e/utils/testing/pytest/test_.py @@ -0,0 +1,33 @@ +import os +from pathlib import Path + +import pytest + +pytestmark = pytest.mark.pytest + +from byteb4rb1e.utils.testing.pytest import get_current_test +from byteb4rb1e.utils.testing.pytest.decorators import run_in_subprocess_once + + +class Test_get_current_test: + """ + """ + + def test_default(self): + """ + """ + os.environ['PYTEST_CURRENT_TEST'] = 'foo::bar (something)' + + result = get_current_test() + + assert isinstance(result[0], Path) + assert str(result[0].name) == 'foo' + + assert result[1] == 'bar' + + def test_invalid(self): + """ + """ + del os.environ['PYTEST_CURRENT_TEST'] + with pytest.raises(RuntimeError): + get_current_test() diff --git a/tests/integration/byteb4rb1e/utils/testing/pytest/test_decorators.py b/tests/integration/byteb4rb1e/utils/testing/pytest/test_decorators.py new file mode 100644 index 0000000..7dd20fe --- /dev/null +++ b/tests/integration/byteb4rb1e/utils/testing/pytest/test_decorators.py @@ -0,0 +1,21 @@ +from pathlib import Path + +import pytest + +pytestmark = pytest.mark.pytest + +from byteb4rb1e.utils.testing.pytest.decorators import run_in_subprocess_once + + +@run_in_subprocess_once() +def test_run_in_subprocess_once(tmp_path): + marker = tmp_path / "executed_in_subprocess.txt" + + if marker.exists(): + raise AssertionError("Marker file exists before test logic ran (shouldn't happen in parent process)") + + # Create proof of execution + marker.write_text("Subprocess was here.") + + # Now assert it + assert marker.exists() diff --git a/tests/unit/byteb4rb1e/utils/urllib/test_request.py b/tests/unit/byteb4rb1e/utils/urllib/test_request.py index 28548ef..74cf40d 100644 --- a/tests/unit/byteb4rb1e/utils/urllib/test_request.py +++ b/tests/unit/byteb4rb1e/utils/urllib/test_request.py @@ -1,5 +1,8 @@ +import pytest + from byteb4rb1e.utils.urllib.request import PkgHandler + class TestPkgHandler: """ """ @@ -7,3 +10,4 @@ class TestPkgHandler: """ """ pass + From 24806959bbb4cca59240ffa4391def21bced7bf0 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 21 Jun 2025 00:32:07 +0200 Subject: [PATCH 28/92] feat(testing.pytest): add pkg mock fixture --- .../utils/testing/pytest/fixtures.py | 28 ++++++++++++++++++- .../utils/testing/pytest/test_fixtures.py | 19 ++++++++++++- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/byteb4rb1e/utils/testing/pytest/fixtures.py b/src/byteb4rb1e/utils/testing/pytest/fixtures.py index dc87091..3362010 100644 --- a/src/byteb4rb1e/utils/testing/pytest/fixtures.py +++ b/src/byteb4rb1e/utils/testing/pytest/fixtures.py @@ -1,6 +1,7 @@ import os from pathlib import Path -from typing import Tuple +import sys +from typing import Dict, Tuple, Union import pytest @@ -12,3 +13,28 @@ def current_test() -> Tuple[Path, str]: """ """ return get_current_test() + + +@pytest.fixture +def mock_pkg(tmp_path): + def _create(name: str, files: Dict[str, Union[str, bytes]]): + pkg_path = tmp_path / name.replace('.', os.path.sep) + pkg_path.mkdir(parents=True) + (pkg_path / "__init__.py").touch() + + for fname, content in files.items(): + fpath = (pkg_path / fname) + fpath.parent.mkdir(parents=True, exist_ok=True) + if isinstance(content, str): + fpath.write_text(content) + else: + fpath.write_bytes(content) + + sys.path.insert(0, str(tmp_path)) + return name, pkg_path + + yield _create + + # cleanup sys.path after test + if str(tmp_path) in sys.path: + sys.path.remove(str(tmp_path)) diff --git a/tests/integration/byteb4rb1e/utils/testing/pytest/test_fixtures.py b/tests/integration/byteb4rb1e/utils/testing/pytest/test_fixtures.py index 56302e5..c6b8e1f 100644 --- a/tests/integration/byteb4rb1e/utils/testing/pytest/test_fixtures.py +++ b/tests/integration/byteb4rb1e/utils/testing/pytest/test_fixtures.py @@ -1,10 +1,12 @@ from pathlib import Path +import importlib.resources import pytest pytestmark = pytest.mark.pytest -from byteb4rb1e.utils.testing.pytest.fixtures import current_test +from byteb4rb1e.utils.testing.pytest.decorators import run_in_subprocess_once +from byteb4rb1e.utils.testing.pytest.fixtures import current_test, mock_pkg def test_current_test(current_test): @@ -14,3 +16,18 @@ def test_current_test(current_test): assert str(Path(__file__)) == str(suite_path) assert case_name == "test_current_test" + + +@run_in_subprocess_once() +def test_mock_pkg(mock_pkg): + """ + """ + dummy_data = 'Hello' + + mock_pkg('foobarpkg', { + 'data.txt': dummy_data + }) + + result = next(importlib.resources.files('foobarpkg').glob('data.txt')).read_text() + + assert result == dummy_data From 59713aefb894fe24e81deba795b27d520889a37b Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 21 Jun 2025 00:32:43 +0200 Subject: [PATCH 29/92] feat(urllib.request): add importlib resource handler --- src/byteb4rb1e/utils/urllib/request.py | 20 ++--- .../byteb4rb1e/utils/urllib/test_request.py | 81 ++++++++++++++++++- 2 files changed, 90 insertions(+), 11 deletions(-) diff --git a/src/byteb4rb1e/utils/urllib/request.py b/src/byteb4rb1e/utils/urllib/request.py index 28e8e8e..3f9ee20 100644 --- a/src/byteb4rb1e/utils/urllib/request.py +++ b/src/byteb4rb1e/utils/urllib/request.py @@ -1,4 +1,6 @@ +import email import importlib.resources +import mimetypes from urllib.request import URLError import urllib.request @@ -6,15 +8,13 @@ import urllib.request class PkgHandler(urllib.request.BaseHandler): """ """ - def pkg_open(self, req): + def pkg_open(self, req) -> urllib.request.addinfourl: pkg_files = importlib.resources.files(req.host) - raise Exception(sorted(pkg_files.glob('**/*'))) - try: - fh = list( + fh = next( pkg_files.glob(req.selector.lstrip('//')) - )[0].open('rb') + ).open('rb') except Exception as e: raise URLError(f'{e.__class__.__name__}: {e}') from e @@ -22,15 +22,17 @@ class PkgHandler(urllib.request.BaseHandler): size = fh.tell(); fh.seek(0); - mtype, _ = mimetypes.guess_type(url) + mtype, _ = mimetypes.guess_type(req.selector) headers = email.message_from_string( 'Content-Type: %s\nContent-Length: %d\n' % (mtype or 'text/plain', size) ) - if not mtype or mtype.starts_with('text/'): + if not mtype or mtype.startswith('text/'): fh.close() - fh = importlib.resources.files(req.host).glob(req.selector)[0].open('r') + fh = next( + pkg_files.glob(req.selector.lstrip('//')) + ).open('r') - return urllib.request.addinfourl(fh, header) + return urllib.request.addinfourl(fh, headers, None) diff --git a/tests/unit/byteb4rb1e/utils/urllib/test_request.py b/tests/unit/byteb4rb1e/utils/urllib/test_request.py index 74cf40d..5fdf7c0 100644 --- a/tests/unit/byteb4rb1e/utils/urllib/test_request.py +++ b/tests/unit/byteb4rb1e/utils/urllib/test_request.py @@ -1,13 +1,90 @@ +import os.path +import sys +import urllib.request + import pytest +from byteb4rb1e.utils.testing.pytest.decorators import run_in_subprocess_once +from byteb4rb1e.utils.testing.pytest.fixtures import mock_pkg from byteb4rb1e.utils.urllib.request import PkgHandler class TestPkgHandler: """ """ - def test_default(self): + @run_in_subprocess_once() + def test_text(self, mock_pkg): """ """ - pass + _opener: urllib.request.OpenerDirector = urllib.request.build_opener( + PkgHandler() + ) + dummy_data = 'Hello' + + mock_pkg('foobarpkg', { + 'data.txt': dummy_data + }) + + result = _opener.open('pkg://foobarpkg/data.txt').readline() + + assert isinstance(result, str) + assert result == dummy_data + + + @run_in_subprocess_once() + def test_bytes(self, mock_pkg): + """ + """ + _opener: urllib.request.OpenerDirector = urllib.request.build_opener( + PkgHandler() + ) + + dummy_data = b'foobar123' + + mock_pkg('foobarpkg', { + 'data.bin': dummy_data + }) + + result = _opener.open('pkg://foobarpkg/data.bin').readline() + + assert isinstance(result, bytes) + assert result == dummy_data + + + @run_in_subprocess_once() + def test_subdir(self, mock_pkg): + """ + """ + _opener: urllib.request.OpenerDirector = urllib.request.build_opener( + PkgHandler() + ) + + dummy_data = 'foobar123' + + mock_pkg('foobarpkg', { + 'foo/bar/data.txt': dummy_data + }) + + result = _opener.open('pkg://foobarpkg/foo/bar/data.txt').readline() + + assert result == dummy_data + + + @run_in_subprocess_once() + def test_nested_module(self, mock_pkg): + """ + """ + _opener: urllib.request.OpenerDirector = urllib.request.build_opener( + PkgHandler() + ) + + dummy_data = 'foobar123' + + mock_pkg('foo.bar.pkg', { + 'dummy/data.txt': dummy_data + }) + + result = _opener.open('pkg://foo.bar.pkg/dummy/data.txt').readline() + + assert result == dummy_data From 3795ff3e38099e2367828e0e5232b98dd6ade48c Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 21 Jun 2025 00:38:20 +0200 Subject: [PATCH 30/92] chore: add integration test to default tests --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 0a1ec21..5cf7aae 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ configure: configure.ac .venv/bin/python3 -m pip install --upgrade pip .venv/bin/pip install -r requirements-dev.txt -test-reports: test-reports/unit test-reports/static +test-reports: test-reports/unit test-reports/static test-reports/integration test-reports/unit: python3 -m pipenv run -v test-unit From a4fa0839809357573284f2a5e169e909729a4e84 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 21 Jun 2025 00:39:13 +0200 Subject: [PATCH 31/92] todo(6): done --- TODO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO b/TODO index 81b72b8..2da03d6 100644 --- a/TODO +++ b/TODO @@ -113,7 +113,7 @@ Description: Implement my custom algorithm for doing rolling hash string search ID: 6 Type: feature Title: implement importlib.resources handler for urllib -Status: in-progress +Status: done Priority: high Created: 2025-06-20 Description: A handler that can be registered with an urllib.request From 0eee6c5771495d7e84946f0830c8496464c5c5ed Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 21 Jun 2025 01:33:26 +0200 Subject: [PATCH 32/92] todo(11): open --- TODO | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/TODO b/TODO index 2da03d6..8f9d762 100644 --- a/TODO +++ b/TODO @@ -163,3 +163,14 @@ Description: add fixtures for doing things in relation to the active testing context --- + +ID: 11 +Type: bugfix +Title: move testing utils out of utils +Status: open +Priority: high +Created: 2025-06-20 +Description: to shorten the namespace and also indicate that testing utilities + are different from regular utilities + +--- From 55ec6323bbf33ac0b20c67e5eb713cfed08262d5 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 21 Jun 2025 01:34:30 +0200 Subject: [PATCH 33/92] todo(11): in-progress --- TODO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO b/TODO index 8f9d762..30d6506 100644 --- a/TODO +++ b/TODO @@ -167,7 +167,7 @@ Description: add fixtures for doing things in relation to the active testing ID: 11 Type: bugfix Title: move testing utils out of utils -Status: open +Status: in-progres Priority: high Created: 2025-06-20 Description: to shorten the namespace and also indicate that testing utilities From 9abfabde004a7271164c53c9f09336d1d7246fd3 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 21 Jun 2025 01:41:47 +0200 Subject: [PATCH 34/92] chore: move testing utils out of utils --- src/byteb4rb1e/{utils => }/testing/pytest/__init__.py | 0 src/byteb4rb1e/{utils => }/testing/pytest/decorators.py | 2 +- src/byteb4rb1e/{utils => }/testing/pytest/fixtures.py | 2 +- .../byteb4rb1e/{utils => }/testing/pytest/test_.py | 4 ++-- .../byteb4rb1e/{utils => }/testing/pytest/test_decorators.py | 2 +- .../byteb4rb1e/{utils => }/testing/pytest/test_fixtures.py | 4 ++-- tests/unit/byteb4rb1e/utils/urllib/test_request.py | 4 ++-- 7 files changed, 9 insertions(+), 9 deletions(-) rename src/byteb4rb1e/{utils => }/testing/pytest/__init__.py (100%) rename src/byteb4rb1e/{utils => }/testing/pytest/decorators.py (95%) rename src/byteb4rb1e/{utils => }/testing/pytest/fixtures.py (93%) rename tests/integration/byteb4rb1e/{utils => }/testing/pytest/test_.py (80%) rename tests/integration/byteb4rb1e/{utils => }/testing/pytest/test_decorators.py (85%) rename tests/integration/byteb4rb1e/{utils => }/testing/pytest/test_fixtures.py (78%) diff --git a/src/byteb4rb1e/utils/testing/pytest/__init__.py b/src/byteb4rb1e/testing/pytest/__init__.py similarity index 100% rename from src/byteb4rb1e/utils/testing/pytest/__init__.py rename to src/byteb4rb1e/testing/pytest/__init__.py diff --git a/src/byteb4rb1e/utils/testing/pytest/decorators.py b/src/byteb4rb1e/testing/pytest/decorators.py similarity index 95% rename from src/byteb4rb1e/utils/testing/pytest/decorators.py rename to src/byteb4rb1e/testing/pytest/decorators.py index bf1066b..a7d8161 100644 --- a/src/byteb4rb1e/utils/testing/pytest/decorators.py +++ b/src/byteb4rb1e/testing/pytest/decorators.py @@ -4,7 +4,7 @@ import os import subprocess import sys -from byteb4rb1e.utils.testing.pytest import get_current_test +from byteb4rb1e.testing.pytest import get_current_test def run_in_subprocess_once(): diff --git a/src/byteb4rb1e/utils/testing/pytest/fixtures.py b/src/byteb4rb1e/testing/pytest/fixtures.py similarity index 93% rename from src/byteb4rb1e/utils/testing/pytest/fixtures.py rename to src/byteb4rb1e/testing/pytest/fixtures.py index 3362010..b9ca0cb 100644 --- a/src/byteb4rb1e/utils/testing/pytest/fixtures.py +++ b/src/byteb4rb1e/testing/pytest/fixtures.py @@ -5,7 +5,7 @@ from typing import Dict, Tuple, Union import pytest -from byteb4rb1e.utils.testing.pytest import get_current_test +from byteb4rb1e.testing.pytest import get_current_test @pytest.fixture diff --git a/tests/integration/byteb4rb1e/utils/testing/pytest/test_.py b/tests/integration/byteb4rb1e/testing/pytest/test_.py similarity index 80% rename from tests/integration/byteb4rb1e/utils/testing/pytest/test_.py rename to tests/integration/byteb4rb1e/testing/pytest/test_.py index 57a0410..8141015 100644 --- a/tests/integration/byteb4rb1e/utils/testing/pytest/test_.py +++ b/tests/integration/byteb4rb1e/testing/pytest/test_.py @@ -5,8 +5,8 @@ import pytest pytestmark = pytest.mark.pytest -from byteb4rb1e.utils.testing.pytest import get_current_test -from byteb4rb1e.utils.testing.pytest.decorators import run_in_subprocess_once +from byteb4rb1e.testing.pytest import get_current_test +from byteb4rb1e.testing.pytest.decorators import run_in_subprocess_once class Test_get_current_test: diff --git a/tests/integration/byteb4rb1e/utils/testing/pytest/test_decorators.py b/tests/integration/byteb4rb1e/testing/pytest/test_decorators.py similarity index 85% rename from tests/integration/byteb4rb1e/utils/testing/pytest/test_decorators.py rename to tests/integration/byteb4rb1e/testing/pytest/test_decorators.py index 7dd20fe..c906bef 100644 --- a/tests/integration/byteb4rb1e/utils/testing/pytest/test_decorators.py +++ b/tests/integration/byteb4rb1e/testing/pytest/test_decorators.py @@ -4,7 +4,7 @@ import pytest pytestmark = pytest.mark.pytest -from byteb4rb1e.utils.testing.pytest.decorators import run_in_subprocess_once +from byteb4rb1e.testing.pytest.decorators import run_in_subprocess_once @run_in_subprocess_once() diff --git a/tests/integration/byteb4rb1e/utils/testing/pytest/test_fixtures.py b/tests/integration/byteb4rb1e/testing/pytest/test_fixtures.py similarity index 78% rename from tests/integration/byteb4rb1e/utils/testing/pytest/test_fixtures.py rename to tests/integration/byteb4rb1e/testing/pytest/test_fixtures.py index c6b8e1f..0e0c5d9 100644 --- a/tests/integration/byteb4rb1e/utils/testing/pytest/test_fixtures.py +++ b/tests/integration/byteb4rb1e/testing/pytest/test_fixtures.py @@ -5,8 +5,8 @@ import pytest pytestmark = pytest.mark.pytest -from byteb4rb1e.utils.testing.pytest.decorators import run_in_subprocess_once -from byteb4rb1e.utils.testing.pytest.fixtures import current_test, mock_pkg +from byteb4rb1e.testing.pytest.decorators import run_in_subprocess_once +from byteb4rb1e.testing.pytest.fixtures import current_test, mock_pkg def test_current_test(current_test): diff --git a/tests/unit/byteb4rb1e/utils/urllib/test_request.py b/tests/unit/byteb4rb1e/utils/urllib/test_request.py index 5fdf7c0..787fb29 100644 --- a/tests/unit/byteb4rb1e/utils/urllib/test_request.py +++ b/tests/unit/byteb4rb1e/utils/urllib/test_request.py @@ -4,8 +4,8 @@ import urllib.request import pytest -from byteb4rb1e.utils.testing.pytest.decorators import run_in_subprocess_once -from byteb4rb1e.utils.testing.pytest.fixtures import mock_pkg +from byteb4rb1e.testing.pytest.decorators import run_in_subprocess_once +from byteb4rb1e.testing.pytest.fixtures import mock_pkg from byteb4rb1e.utils.urllib.request import PkgHandler From 53dc780a04d5f1dee47e5a040e69a36f63beff30 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 21 Jun 2025 01:42:16 +0200 Subject: [PATCH 35/92] todo(11): done --- TODO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO b/TODO index 30d6506..adbeb8c 100644 --- a/TODO +++ b/TODO @@ -167,7 +167,7 @@ Description: add fixtures for doing things in relation to the active testing ID: 11 Type: bugfix Title: move testing utils out of utils -Status: in-progres +Status: done Priority: high Created: 2025-06-20 Description: to shorten the namespace and also indicate that testing utilities From 6b6fb02f41c03dad1d39e604b8f2d70fc8e2101c Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 21 Jun 2025 18:13:59 +0200 Subject: [PATCH 36/92] todo(12): open --- TODO | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/TODO b/TODO index adbeb8c..94ae30c 100644 --- a/TODO +++ b/TODO @@ -174,3 +174,14 @@ Description: to shorten the namespace and also indicate that testing utilities are different from regular utilities --- + +ID: 12 +Type: feature +Title: simplify testing.fixtures.mock_pkg +Status: open +Priority: high +Created: 2025-06-21 +Description: Only bootstrap a package mock with the minimum requirements for a + Python module and let the consumer handle the directory layout. + +--- From e6bf6579194753f0823f1892cd92efa2f945a49b Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 21 Jun 2025 18:14:22 +0200 Subject: [PATCH 37/92] todo(12): in-progress --- TODO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO b/TODO index 94ae30c..440cd59 100644 --- a/TODO +++ b/TODO @@ -178,7 +178,7 @@ Description: to shorten the namespace and also indicate that testing utilities ID: 12 Type: feature Title: simplify testing.fixtures.mock_pkg -Status: open +Status: in-progress Priority: high Created: 2025-06-21 Description: Only bootstrap a package mock with the minimum requirements for a From aa2540cf3f9a141e045e2e1c3b56ff7aa4a39673 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 21 Jun 2025 18:14:52 +0200 Subject: [PATCH 38/92] refactor(testing.pytest): change mock_pkg interface being more explicit about what the fixture provides as an output, instead of solely describing the site effects. Also the consumer is now responsible for the module directory layout. --- src/byteb4rb1e/testing/pytest/fixtures.py | 32 +++++++++++-------- .../testing/pytest/test_fixtures.py | 15 ++++++--- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/src/byteb4rb1e/testing/pytest/fixtures.py b/src/byteb4rb1e/testing/pytest/fixtures.py index b9ca0cb..7c93041 100644 --- a/src/byteb4rb1e/testing/pytest/fixtures.py +++ b/src/byteb4rb1e/testing/pytest/fixtures.py @@ -7,6 +7,8 @@ import pytest from byteb4rb1e.testing.pytest import get_current_test +_SITE_PACKAGE_COUNTER: Dict[str, int] = {} + @pytest.fixture def current_test() -> Tuple[Path, str]: @@ -16,25 +18,27 @@ def current_test() -> Tuple[Path, str]: @pytest.fixture -def mock_pkg(tmp_path): - def _create(name: str, files: Dict[str, Union[str, bytes]]): - pkg_path = tmp_path / name.replace('.', os.path.sep) +def mock_system_site_package_dir(tmp_path): + global _SITE_PACKAGE_COUNTER + + package_id = _SITE_PACKAGE_COUNTER.setdefault(tmp_path, 0) + _SITE_PACKAGE_COUNTER[tmp_path] += 1 + + sys_path = tmp_path / str(package_id) + + def _create(name: str) -> Path: + pkg_path = sys_path / name.replace('.', os.path.sep) + pkg_path.mkdir(parents=True) + (pkg_path / "__init__.py").touch() - for fname, content in files.items(): - fpath = (pkg_path / fname) - fpath.parent.mkdir(parents=True, exist_ok=True) - if isinstance(content, str): - fpath.write_text(content) - else: - fpath.write_bytes(content) + sys.path.insert(0, str(sys_path)) - sys.path.insert(0, str(tmp_path)) - return name, pkg_path + return pkg_path yield _create # cleanup sys.path after test - if str(tmp_path) in sys.path: - sys.path.remove(str(tmp_path)) + if str(sys_path) in sys.path: + sys.path.remove(str(sys_path)) diff --git a/tests/integration/byteb4rb1e/testing/pytest/test_fixtures.py b/tests/integration/byteb4rb1e/testing/pytest/test_fixtures.py index 0e0c5d9..a08851d 100644 --- a/tests/integration/byteb4rb1e/testing/pytest/test_fixtures.py +++ b/tests/integration/byteb4rb1e/testing/pytest/test_fixtures.py @@ -6,7 +6,10 @@ import pytest pytestmark = pytest.mark.pytest from byteb4rb1e.testing.pytest.decorators import run_in_subprocess_once -from byteb4rb1e.testing.pytest.fixtures import current_test, mock_pkg +from byteb4rb1e.testing.pytest.fixtures import ( + current_test, + mock_system_site_package_dir +) def test_current_test(current_test): @@ -19,14 +22,16 @@ def test_current_test(current_test): @run_in_subprocess_once() -def test_mock_pkg(mock_pkg): +def test_mock_system_site_package_dir(mock_system_site_package_dir): """ """ dummy_data = 'Hello' - mock_pkg('foobarpkg', { - 'data.txt': dummy_data - }) + pkgdir = mock_system_site_package_dir('foobarpkg') + + (pkgdir / 'data.txt').write_text(dummy_data) + + assert (pkgdir / '__init__.py').exists() result = next(importlib.resources.files('foobarpkg').glob('data.txt')).read_text() From 16c12b55769ffc0ac66fbf016b63406c20e0b4ea Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 21 Jun 2025 18:16:18 +0200 Subject: [PATCH 39/92] todo(12): done --- TODO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO b/TODO index 440cd59..145c66f 100644 --- a/TODO +++ b/TODO @@ -178,7 +178,7 @@ Description: to shorten the namespace and also indicate that testing utilities ID: 12 Type: feature Title: simplify testing.fixtures.mock_pkg -Status: in-progress +Status: done Priority: high Created: 2025-06-21 Description: Only bootstrap a package mock with the minimum requirements for a From 81dc18c506d5890cbc34552bb6337d4cf08c1c71 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 21 Jun 2025 20:22:19 +0200 Subject: [PATCH 40/92] todo(13): open --- TODO | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/TODO b/TODO index 145c66f..794f881 100644 --- a/TODO +++ b/TODO @@ -185,3 +185,14 @@ Description: Only bootstrap a package mock with the minimum requirements for a Python module and let the consumer handle the directory layout. --- + +ID: 13 +Type: bugfix +Title: fix unit tests for urllib PkgHandler +Status: open +Priority: high +Created: 2025-06-21 +Description: change of issue 12 wasn't properly reflected in urllib PkgHandler + unit tests + +--- From 3b5097a99c65b7bb0b080045e90ae12c60af996a Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 21 Jun 2025 20:23:41 +0200 Subject: [PATCH 41/92] todo(14): open --- TODO | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/TODO b/TODO index 794f881..fe7ff1b 100644 --- a/TODO +++ b/TODO @@ -196,3 +196,14 @@ Description: change of issue 12 wasn't properly reflected in urllib PkgHandler unit tests --- + +ID: 14 +Type: feature +Title: add compression support for urllib PkgHandler +Status: open +Priority: high +Created: 2025-06-21 +Description: with a proper content-type of the PkgHandler addinfourl object, a + consumer can determine whether the file is compressed or not. + +--- From 69d498a20326b597ae8de50f06d1c3a4e3d00e75 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 21 Jun 2025 20:24:51 +0200 Subject: [PATCH 42/92] todo(13): in-progress --- TODO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO b/TODO index fe7ff1b..25a59e4 100644 --- a/TODO +++ b/TODO @@ -189,7 +189,7 @@ Description: Only bootstrap a package mock with the minimum requirements for a ID: 13 Type: bugfix Title: fix unit tests for urllib PkgHandler -Status: open +Status: in-progress Priority: high Created: 2025-06-21 Description: change of issue 12 wasn't properly reflected in urllib PkgHandler From f9897a9aa54083c8416de7b959338c836f80b5ad Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 21 Jun 2025 20:23:59 +0200 Subject: [PATCH 43/92] fix(tests): adapt name of new package mock fixture --- .../byteb4rb1e/utils/urllib/test_request.py | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/tests/unit/byteb4rb1e/utils/urllib/test_request.py b/tests/unit/byteb4rb1e/utils/urllib/test_request.py index 787fb29..468751d 100644 --- a/tests/unit/byteb4rb1e/utils/urllib/test_request.py +++ b/tests/unit/byteb4rb1e/utils/urllib/test_request.py @@ -5,7 +5,7 @@ import urllib.request import pytest from byteb4rb1e.testing.pytest.decorators import run_in_subprocess_once -from byteb4rb1e.testing.pytest.fixtures import mock_pkg +from byteb4rb1e.testing.pytest.fixtures import mock_system_site_package_dir from byteb4rb1e.utils.urllib.request import PkgHandler @@ -13,7 +13,7 @@ class TestPkgHandler: """ """ @run_in_subprocess_once() - def test_text(self, mock_pkg): + def test_text(self, mock_system_site_package_dir): """ """ _opener: urllib.request.OpenerDirector = urllib.request.build_opener( @@ -22,9 +22,8 @@ class TestPkgHandler: dummy_data = 'Hello' - mock_pkg('foobarpkg', { - 'data.txt': dummy_data - }) + pkg_dir = mock_system_site_package_dir('foobarpkg') + (pkg_dir / 'data.txt').write_text(dummy_data) result = _opener.open('pkg://foobarpkg/data.txt').readline() @@ -33,7 +32,7 @@ class TestPkgHandler: @run_in_subprocess_once() - def test_bytes(self, mock_pkg): + def test_bytes(self, mock_system_site_package_dir): """ """ _opener: urllib.request.OpenerDirector = urllib.request.build_opener( @@ -42,9 +41,8 @@ class TestPkgHandler: dummy_data = b'foobar123' - mock_pkg('foobarpkg', { - 'data.bin': dummy_data - }) + pkg_dir = mock_system_site_package_dir('foobarpkg') + (pkg_dir / 'data.bin').write_bytes(dummy_data) result = _opener.open('pkg://foobarpkg/data.bin').readline() @@ -53,7 +51,7 @@ class TestPkgHandler: @run_in_subprocess_once() - def test_subdir(self, mock_pkg): + def test_subdir(self, mock_system_site_package_dir): """ """ _opener: urllib.request.OpenerDirector = urllib.request.build_opener( @@ -62,9 +60,12 @@ class TestPkgHandler: dummy_data = 'foobar123' - mock_pkg('foobarpkg', { - 'foo/bar/data.txt': dummy_data - }) + pkg_dir = mock_system_site_package_dir('foobarpkg') + + dummy_file = (pkg_dir / 'foo' / 'bar' / 'data.txt') + + dummy_file.parent.mkdir(parents=True) + dummy_file.write_text(dummy_data) result = _opener.open('pkg://foobarpkg/foo/bar/data.txt').readline() @@ -72,7 +73,7 @@ class TestPkgHandler: @run_in_subprocess_once() - def test_nested_module(self, mock_pkg): + def test_nested_module(self, mock_system_site_package_dir): """ """ _opener: urllib.request.OpenerDirector = urllib.request.build_opener( @@ -81,9 +82,11 @@ class TestPkgHandler: dummy_data = 'foobar123' - mock_pkg('foo.bar.pkg', { - 'dummy/data.txt': dummy_data - }) + pkg_dir = mock_system_site_package_dir('foo.bar.pkg') + dummy_file = (pkg_dir / 'dummy' / 'data.txt') + + dummy_file.parent.mkdir(parents=True) + dummy_file.write_text(dummy_data) result = _opener.open('pkg://foo.bar.pkg/dummy/data.txt').readline() From 5beb699ddaaffaa2bc5e63cd8fca417e2a2e2f06 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 21 Jun 2025 20:25:28 +0200 Subject: [PATCH 44/92] todo(13): done --- TODO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO b/TODO index 25a59e4..a987c01 100644 --- a/TODO +++ b/TODO @@ -189,7 +189,7 @@ Description: Only bootstrap a package mock with the minimum requirements for a ID: 13 Type: bugfix Title: fix unit tests for urllib PkgHandler -Status: in-progress +Status: done Priority: high Created: 2025-06-21 Description: change of issue 12 wasn't properly reflected in urllib PkgHandler From 981985e51add0dba01dcc44083cbd7f53e9f7821 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 21 Jun 2025 20:26:15 +0200 Subject: [PATCH 45/92] todo(14): in-progress --- TODO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO b/TODO index a987c01..20427a8 100644 --- a/TODO +++ b/TODO @@ -200,7 +200,7 @@ Description: change of issue 12 wasn't properly reflected in urllib PkgHandler ID: 14 Type: feature Title: add compression support for urllib PkgHandler -Status: open +Status: in-progress Priority: high Created: 2025-06-21 Description: with a proper content-type of the PkgHandler addinfourl object, a From 2d9fe8b625cda630a302e264c585159bbe1805d9 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 21 Jun 2025 20:26:36 +0200 Subject: [PATCH 46/92] feat(urllib.request): add compression support for PkgHandler --- src/byteb4rb1e/utils/urllib/request.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/byteb4rb1e/utils/urllib/request.py b/src/byteb4rb1e/utils/urllib/request.py index 3f9ee20..2408d94 100644 --- a/src/byteb4rb1e/utils/urllib/request.py +++ b/src/byteb4rb1e/utils/urllib/request.py @@ -22,7 +22,10 @@ class PkgHandler(urllib.request.BaseHandler): size = fh.tell(); fh.seek(0); - mtype, _ = mimetypes.guess_type(req.selector) + mtype, compression = mimetypes.guess_type(req.selector) + + if compression and mtype: + mtype = f"{mtype}+{compression}" headers = email.message_from_string( 'Content-Type: %s\nContent-Length: %d\n' % From 7e18e4795df73db2c50f2e2cd7457d9c9ec25305 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 21 Jun 2025 20:27:09 +0200 Subject: [PATCH 47/92] todo(14): done --- TODO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO b/TODO index 20427a8..9b0c012 100644 --- a/TODO +++ b/TODO @@ -200,7 +200,7 @@ Description: change of issue 12 wasn't properly reflected in urllib PkgHandler ID: 14 Type: feature Title: add compression support for urllib PkgHandler -Status: in-progress +Status: done Priority: high Created: 2025-06-21 Description: with a proper content-type of the PkgHandler addinfourl object, a From b9e9e13630e6b7729a7ffe6b5eac38ba7d1e39a6 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 28 Jun 2025 01:39:37 +0200 Subject: [PATCH 48/92] todo(15): open --- TODO | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/TODO b/TODO index 9b0c012..6cf48ae 100644 --- a/TODO +++ b/TODO @@ -207,3 +207,15 @@ Description: with a proper content-type of the PkgHandler addinfourl object, a consumer can determine whether the file is compressed or not. --- + +ID: 15 +Type: bugfix +Title: modularize module containers +Status: open +Priority: high +Created: 2025-06-28 +Description: Even though importlib can find submodules through traversing paths + instead of relying on __init__.py for every ancestor module, this + is not supported by some modules like sphinx.ext.autosummary + +--- From 9d09a4abaa3a0ff9e3ba01ba4cd5f3c625b26f75 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 28 Jun 2025 01:40:17 +0200 Subject: [PATCH 49/92] todo(15): in-progress --- TODO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO b/TODO index 6cf48ae..f38eda0 100644 --- a/TODO +++ b/TODO @@ -211,7 +211,7 @@ Description: with a proper content-type of the PkgHandler addinfourl object, a ID: 15 Type: bugfix Title: modularize module containers -Status: open +Status: in-progress Priority: high Created: 2025-06-28 Description: Even though importlib can find submodules through traversing paths From 89f7420fae81caadd5cf80d82ab638a74e279092 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 28 Jun 2025 01:41:32 +0200 Subject: [PATCH 50/92] refactor: modularize submodules --- src/byteb4rb1e/testing/__init__.py | 0 src/byteb4rb1e/utils/__init__.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/byteb4rb1e/testing/__init__.py create mode 100644 src/byteb4rb1e/utils/__init__.py diff --git a/src/byteb4rb1e/testing/__init__.py b/src/byteb4rb1e/testing/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/byteb4rb1e/utils/__init__.py b/src/byteb4rb1e/utils/__init__.py new file mode 100644 index 0000000..e69de29 From 74dac5249bf1a006bf8cdc057c891a2acc87ca05 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 28 Jun 2025 01:42:13 +0200 Subject: [PATCH 51/92] todo(15): done --- TODO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO b/TODO index f38eda0..6cf48ae 100644 --- a/TODO +++ b/TODO @@ -211,7 +211,7 @@ Description: with a proper content-type of the PkgHandler addinfourl object, a ID: 15 Type: bugfix Title: modularize module containers -Status: in-progress +Status: open Priority: high Created: 2025-06-28 Description: Even though importlib can find submodules through traversing paths From 6b29a8d525e156c77e1a370849d550c13905a48d Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Wed, 31 Dec 2025 14:22:38 +0100 Subject: [PATCH 52/92] todo(16): open --- TODO | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/TODO b/TODO index 6cf48ae..63dbc1d 100644 --- a/TODO +++ b/TODO @@ -219,3 +219,14 @@ Description: Even though importlib can find submodules through traversing paths is not supported by some modules like sphinx.ext.autosummary --- + +ID: 16 +Type: feature +Title: SQL-aware dataclass +Status: open +Priority: high +Created: 2025-12-31 +Description: A dataclass that transparently maps onto an SQL datastore, with + command generation for syncing data between data class and store + +--- From b257fd3d880c3e3fb7dea7bdde935d71b0f7a73d Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Wed, 31 Dec 2025 14:24:09 +0100 Subject: [PATCH 53/92] todo(16): in-progress --- TODO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO b/TODO index 63dbc1d..f5726f4 100644 --- a/TODO +++ b/TODO @@ -223,7 +223,7 @@ Description: Even though importlib can find submodules through traversing paths ID: 16 Type: feature Title: SQL-aware dataclass -Status: open +Status: in-progress Priority: high Created: 2025-12-31 Description: A dataclass that transparently maps onto an SQL datastore, with From 0a23243b767bac8d4cdd965ad9e24ac58fe19def Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Wed, 31 Dec 2025 14:24:38 +0100 Subject: [PATCH 54/92] todo(16): change priority project that uses it is currently on hold, hence priority is low --- TODO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO b/TODO index f5726f4..56cf823 100644 --- a/TODO +++ b/TODO @@ -224,7 +224,7 @@ ID: 16 Type: feature Title: SQL-aware dataclass Status: in-progress -Priority: high +Priority: low Created: 2025-12-31 Description: A dataclass that transparently maps onto an SQL datastore, with command generation for syncing data between data class and store From 90567899e0fb276473ec48af0ef027662a9acff9 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Wed, 31 Dec 2025 14:27:59 +0100 Subject: [PATCH 55/92] todo(17): open --- TODO | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/TODO b/TODO index 56cf823..47e17df 100644 --- a/TODO +++ b/TODO @@ -230,3 +230,14 @@ Description: A dataclass that transparently maps onto an SQL datastore, with command generation for syncing data between data class and store --- + +ID: 17 +Type: feature +Title: recursive-descent HTML (DOM) parser +Status: open +Priority: high +Created: 2025-12-31 +Description: Extend the built-in event-driven parser to be modeled after DOM + recursive-descent HTML parser + +--- From 22aeecd6302d440462c66136c52dbd922426bee4 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Wed, 31 Dec 2025 14:28:27 +0100 Subject: [PATCH 56/92] todo(17): in-progress --- TODO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO b/TODO index 47e17df..e747197 100644 --- a/TODO +++ b/TODO @@ -234,7 +234,7 @@ Description: A dataclass that transparently maps onto an SQL datastore, with ID: 17 Type: feature Title: recursive-descent HTML (DOM) parser -Status: open +Status: in-progress Priority: high Created: 2025-12-31 Description: Extend the built-in event-driven parser to be modeled after DOM From cc4b567181068135899af26c239d5d973afd6c02 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Wed, 31 Dec 2025 14:40:39 +0100 Subject: [PATCH 57/92] chore: ignore tox working directory --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index dbfbc60..2351bda 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ *.swo *.swp /test-reports/ +/.tox/ From 5bf4a7eee4386822872423b09539d1517fd4967c Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Wed, 4 Mar 2026 13:11:07 +0100 Subject: [PATCH 58/92] migrate sphinxcontrib.h5p.utils --- src/byteb4rb1e/utils/argparse/__init__.py | 6 + src/byteb4rb1e/utils/argparse/command.py | 54 ++++ src/byteb4rb1e/utils/argparse/dispatcher.py | 122 ++++++++ src/byteb4rb1e/utils/http/client.py | 109 +++++++ src/byteb4rb1e/utils/saas/bitbucket.py | 78 +++++ src/byteb4rb1e/utils/saas/github.py | 65 +++++ src/byteb4rb1e/utils/vcs/__init__.py | 0 src/byteb4rb1e/utils/vcs/git.py | 308 ++++++++++++++++++++ 8 files changed, 742 insertions(+) create mode 100644 src/byteb4rb1e/utils/argparse/__init__.py create mode 100644 src/byteb4rb1e/utils/argparse/command.py create mode 100644 src/byteb4rb1e/utils/argparse/dispatcher.py create mode 100644 src/byteb4rb1e/utils/http/client.py create mode 100644 src/byteb4rb1e/utils/saas/bitbucket.py create mode 100644 src/byteb4rb1e/utils/saas/github.py create mode 100644 src/byteb4rb1e/utils/vcs/__init__.py create mode 100644 src/byteb4rb1e/utils/vcs/git.py diff --git a/src/byteb4rb1e/utils/argparse/__init__.py b/src/byteb4rb1e/utils/argparse/__init__.py new file mode 100644 index 0000000..84ae3ed --- /dev/null +++ b/src/byteb4rb1e/utils/argparse/__init__.py @@ -0,0 +1,6 @@ +"""Utilities for building composable CLIs from command dataclasses.""" + +from byteb4rb1e.utils.argparse.command import CLICommand +from byteb4rb1e.utils.argparse.dispatcher import CLI + +__all__ = ["CLI", "CLICommand"] diff --git a/src/byteb4rb1e/utils/argparse/command.py b/src/byteb4rb1e/utils/argparse/command.py new file mode 100644 index 0000000..199e4f1 --- /dev/null +++ b/src/byteb4rb1e/utils/argparse/command.py @@ -0,0 +1,54 @@ +"""Base command dataclass for composable CLI trees.""" + +from __future__ import annotations + +from argparse import ArgumentParser +from dataclasses import dataclass, fields +from typing import Any, ClassVar, Dict, List, Optional, Type + + +@dataclass +class CLICommand: + """Base class for CLI commands. + + Subclasses define their identity (name, help, description) as + dataclass fields. These are passed as kwargs to + ``subparsers.add_parser()``. + + Override ``add_arguments`` to register flags and positionals. + Override ``execute`` to implement the command's logic. + + Nest subcommands by setting ``_subcommands`` as a class variable. + """ + + name: str = "" + help: str = "" + description: str = "" + + _subcommands: ClassVar[List[Type[Command]]] = [] + + def add_arguments(self, parser: ArgumentParser) -> None: + """Add arguments to the parser. Override in subclasses.""" + + def execute(self, args: Any) -> int: + """Run the command. Override in subclasses. + + Returns an exit code (0 = success). + """ + return 0 + + def parser_kwargs(self) -> Dict[str, Any]: + """Return the dataclass fields as kwargs for add_parser. + + Excludes ``name`` (used as the positional parser name) and + any empty-string fields so argparse defaults apply. + """ + skip = {"name"} + kwargs = {} + for f in fields(self): + if f.name in skip or f.name.startswith("_"): + continue + val = getattr(self, f.name) + if val != "": + kwargs[f.name] = val + return kwargs diff --git a/src/byteb4rb1e/utils/argparse/dispatcher.py b/src/byteb4rb1e/utils/argparse/dispatcher.py new file mode 100644 index 0000000..0c8768b --- /dev/null +++ b/src/byteb4rb1e/utils/argparse/dispatcher.py @@ -0,0 +1,122 @@ +"""CLI dispatcher — builds parser trees from command dataclasses.""" + +from __future__ import annotations + +import logging +from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser +from typing import Any, Dict, List, Optional, Type + +from byteb4rb1e.utils.argparse.command import CLICommand + + +class CLI: + """Composable CLI built from a tree of Command dataclasses. + + Recursively bootstraps an argparse parser hierarchy and tracks + dest names so ``run()`` can dispatch to the correct leaf command + without dest chaining in the caller. + + Usage:: + + cli = CLI(prog="repository", description="...") + cli.bootstrap([MirrorCommand, IndexCommand]) + cli.run() + """ + + def __init__( + self, + prog: Optional[str] = None, + description: str = "", + ) -> None: + kwargs = {} # type: Dict[str, Any] + if prog: + kwargs["prog"] = prog + if description: + kwargs["description"] = description + kwargs.setdefault( + "formatter_class", ArgumentDefaultsHelpFormatter, + ) + self.parser = ArgumentParser(**kwargs) + self._dests = [] # type: List[str] + self._commands = {} # type: Dict[str, Command] + + def add_arguments(self, parser: ArgumentParser) -> None: + """Add global arguments to the root parser.""" + parser.add_argument( + "-v", "--verbose", action="count", default=0, + help="Increase verbosity (-v for INFO, -vv for DEBUG)", + ) + + def bootstrap( + self, + commands: List[Type[Command]], + ) -> None: + """Build the parser tree from a list of top-level commands.""" + self.add_arguments(self.parser) + dest = "command" + self._dests.append(dest) + sub = self.parser.add_subparsers(dest=dest) + for cmd_cls in commands: + self._add(sub, cmd_cls, prefix="") + + def _add( + self, + subparsers: Any, + cmd_cls: Type[Command], + prefix: str, + ) -> None: + """Recursively add a command and its subcommands.""" + cmd = cmd_cls() + parser = subparsers.add_parser( + cmd.name, + formatter_class=ArgumentDefaultsHelpFormatter, + **cmd.parser_kwargs(), + ) + cmd.add_arguments(parser) + + key = "%s.%s" % (prefix, cmd.name) if prefix else cmd.name + self._commands[key] = cmd + + if cmd._subcommands: + dest = "%s_command" % cmd.name + self._dests.append(dest) + child_sub = parser.add_subparsers(dest=dest) + for sc_cls in cmd._subcommands: + self._add(child_sub, sc_cls, prefix=key) + + def _resolve(self, args: Any) -> Optional[Command]: + """Walk dest chain to find the leaf command.""" + parts = [] # type: List[str] + for dest in self._dests: + val = getattr(args, dest, None) + if val is None: + break + parts.append(val) + if not parts: + return None + key = ".".join(parts) + return self._commands.get(key) + + @staticmethod + def _setup_logging(verbosity: int) -> None: + if verbosity >= 2: + level = logging.DEBUG + elif verbosity >= 1: + level = logging.INFO + else: + level = logging.WARNING + logging.basicConfig( + level=level, + format="%(asctime)s [%(levelname)s] %(message)s", + handlers=[logging.StreamHandler()], + ) + + def run(self) -> None: + """Parse args and dispatch to the leaf command.""" + args = self.parser.parse_args() + self._setup_logging(getattr(args, "verbose", 0)) + cmd = self._resolve(args) + if cmd is None: + self.parser.print_help() + raise SystemExit(1) + raise SystemExit(cmd.execute(args)) diff --git a/src/byteb4rb1e/utils/http/client.py b/src/byteb4rb1e/utils/http/client.py new file mode 100644 index 0000000..0962445 --- /dev/null +++ b/src/byteb4rb1e/utils/http/client.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +"""Generic HTTP client. + +Thin urllib wrapper with retry-on-rate-limit. No domain knowledge — +GitHub, Bitbucket, etc. are handled by higher-level modules. +""" + +import json +import time +from typing import Any, Dict, Optional +import urllib.request +import urllib.parse +from warnings import warn + + +class HttpResponse: + def __init__(self, status: int, headers: dict, data: bytes, reason: str): + self.status_code = status + self.headers = headers + self.data = data + self.reason = reason + self.text = data.decode("utf-8", errors="replace") + + def json(self): + return json.loads(self.data.decode("utf-8")) + + +def _request( + url: str, + method: str = "GET", + params: Optional[Dict[str, Any]] = None, + headers: Optional[Dict[str, str]] = None, + data: Optional[bytes] = None, +) -> HttpResponse: + # TODO: do proper exponential backoff + backoff = [1, 2, 4] + + if params: + query = urllib.parse.urlencode(params) + url = f"{url}?{query}" + + req = urllib.request.Request( + url, + headers=headers or {}, + method=method, + data=data, + ) + + for delay in backoff: + try: + with urllib.request.urlopen(req, timeout=30) as resp: + status = resp.getcode() + resp_data = resp.read() + resp_headers = dict(resp.getheaders()) + + if status == 429: + warn(f"Rate-limited on {url} (HTTP {status})." + f" Backing off {delay}s...") + time.sleep(delay) + continue + + return HttpResponse( + status, resp_headers, resp_data, resp.reason, + ) + + except urllib.error.HTTPError as e: + status = e.code + err_data = e.read() + err_headers = dict(e.headers.items()) + if status == 429: + warn(f"Rate-limited on {url} (HTTP {status})." + f" Backing off {delay}s...") + time.sleep(delay) + continue + return HttpResponse( + status, err_headers, err_data, e.reason, + ) + + except urllib.error.URLError as e: + raise Exception( + "Network error on %s: %s", url, e, + ) from e + + # If all retries exhausted, return last error-like response + return HttpResponse(503, {}, b"", "Service unavailable") + + +def get( + url: str, + params: Optional[Dict[str, Any]] = None, + headers: Optional[Dict[str, str]] = None, +) -> HttpResponse: + return _request(url, method="GET", params=params, headers=headers) + + +def post( + url: str, + data: Optional[bytes] = None, + headers: Optional[Dict[str, str]] = None, +) -> HttpResponse: + return _request(url, method="POST", headers=headers, data=data) + + +def put( + url: str, + data: Optional[bytes] = None, + headers: Optional[Dict[str, str]] = None, +) -> HttpResponse: + return _request(url, method="PUT", headers=headers, data=data) diff --git a/src/byteb4rb1e/utils/saas/bitbucket.py b/src/byteb4rb1e/utils/saas/bitbucket.py new file mode 100644 index 0000000..d9b5bad --- /dev/null +++ b/src/byteb4rb1e/utils/saas/bitbucket.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +"""Bitbucket Cloud REST API v2.0 wrapper. + +Thin layer over http.py for Bitbucket-specific operations: + +- Bearer token authentication +- Repository existence checks +- Repository creation within a workspace/project +""" + +import json +from typing import Any, Dict, Optional + +from byteb4rb1e.utils.http import client as http_client + + +BITBUCKET_API = "https://api.bitbucket.org/2.0" + + +def http_headers(token: str) -> Dict[str, str]: + """Construct Bitbucket API headers with Bearer token auth.""" + return { + "Authorization": f"Bearer {token}", + "Accept": "application/json", + "Content-Type": "application/json", + } + + +def repository_exists( + workspace: str, + repo_slug: str, + token: str, +) -> bool: + """Check whether a repository exists in the workspace.""" + url = f"{BITBUCKET_API}/repositories/{workspace}/{repo_slug}" + resp = http_client.get(url, headers=http_headers(token)) + return resp.status_code == 200 + + +def create_repository( + workspace: str, + repo_slug: str, + token: str, + project: Optional[str] = None, + description: str = "", + is_private: bool = True, +) -> http_client.HttpResponse: + """Create a new repository in the workspace. + + When *project* is given the repository is assigned to that + Bitbucket project (by key). This is required for workspaces + that scope access keys at the project level. + + Returns the API response. Caller should check status_code == 200 + for success. + """ + url = f"{BITBUCKET_API}/repositories/{workspace}/{repo_slug}" + body: Dict[str, Any] = { + "scm": "git", + "is_private": is_private, + "description": description, + "fork_policy": "no_forks", + } + if project: + body["project"] = {"key": project} + return http_client.put( + url, + data=json.dumps(body).encode("utf-8"), + headers=http_headers(token), + ) + + +def clone_url( + workspace: str, + repo_slug: str, +) -> str: + """Return the SSH clone URL for a Bitbucket repository.""" + return f"git@bitbucket.org:{workspace}/{repo_slug}.git" diff --git a/src/byteb4rb1e/utils/saas/github.py b/src/byteb4rb1e/utils/saas/github.py new file mode 100644 index 0000000..d174827 --- /dev/null +++ b/src/byteb4rb1e/utils/saas/github.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +import hashlib +from pathlib import Path +from typing import Any, Dict, List, Optional + +from byteb4rb1e.utils.http import client as http_client + + +GITHUB_API = "https://api.github.com" + + +def http_headers(token: Optional[str]) -> Dict[str, str]: + headers = { + "Accept": "application/vnd.github+json", + "User-Agent": "sphinx-h5p-worker1" + } + if token: + # Use standard PAT header; token not logged anywhere. + headers["Authorization"] = f"Bearer {token}" + return headers + + +def blob_sha(path: Path) -> str: + """Calculate Git blob SHA-1 for a file, matching GitHub API 'sha'.""" + data = path.read_bytes() + header = f"blob {len(data)}\0".encode("utf-8") + store = header + data + return hashlib.sha1(store).hexdigest() + + +def list_org_repos(org: str, token: Optional[str]) -> List[Dict[str, Any]]: + repos: List[Dict[str, Any]] = [] + page = 1 + per_page = 100 + while True: + url = f"{GITHUB_API}/orgs/{org}/repos" + resp = http_client.get( + url, + params={"page": page, "per_page": per_page, "type": "public"}, + headers=http_headers(token), + ) + if resp.status_code != 200: + raise RuntimeError(f"Failed to list repos for org {org}: {resp.status_code} {resp.text}") + batch = resp.json() + if not batch: + break + repos.extend(batch) + page += 1 + return repos + + +def fetch_file( + org: str, + repo: str, + path: str, + token: str +) -> http_client.HttpResponse: + """ + """ + url = f"{GITHUB_API}/repos/{org}/{repo}/{path}" + + return http_client.get( + url, + headers=http_headers(token), + ) diff --git a/src/byteb4rb1e/utils/vcs/__init__.py b/src/byteb4rb1e/utils/vcs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/byteb4rb1e/utils/vcs/git.py b/src/byteb4rb1e/utils/vcs/git.py new file mode 100644 index 0000000..ab7e87f --- /dev/null +++ b/src/byteb4rb1e/utils/vcs/git.py @@ -0,0 +1,308 @@ +#!/usr/bin/env python3 +"""Git subprocess wrapper for repository operations. + +Provides primitives for mirror cloning, syncing, remote management, +file extraction from bare repos, and submodule management. +No pygit2 or gitpython, uses subprocess only. +""" +import logging +import subprocess +from pathlib import Path +from typing import List, Optional + +logger = logging.getLogger(__name__) + + +class GitError(Exception): + """A git subprocess returned a non-zero exit code.""" + + def __init__(self, args: List[str], returncode: int, stderr: str): + self.args_list = args + self.returncode = returncode + self.stderr = stderr + super().__init__( + f"git exited {returncode}: {' '.join(args)}\n{stderr}" + ) + + +def parse_base_url(base_url: str) -> str: + """Extract workspace from an SCP-style Bitbucket base URL. + + The host part must be exactly ``bitbucket.org`` — bootstrapping + requires the Bitbucket API, so other hosts are rejected. + + >>> _parse_base_url("git@bitbucket.org:byteb4rb1e") + 'byteb4rb1e' + """ + # SCP-style: git@bitbucket.org:workspace + if ":" not in base_url or "//" in base_url: + raise ValueError( + f"Expected SCP-style URL (git@bitbucket.org:workspace), " + f"got: {base_url}" + ) + host_part, workspace = base_url.split(":", 1) + # host_part is e.g. "git@bitbucket.org" + host = host_part.split("@", 1)[-1] + if host != "bitbucket.org": + raise ValueError( + f"Mirror base URL must target bitbucket.org, " + f"got host: {host}" + ) + return Path(workspace).parent + + +def parse_repo_name(base_url: str) -> str: + """Extract workspace from an SCP-style Bitbucket base URL. + + The host part must be exactly ``bitbucket.org`` — bootstrapping + requires the Bitbucket API, so other hosts are rejected. + + >>> _parse_base_url("git@bitbucket.org:byteb4rb1e") + 'byteb4rb1e' + """ + # SCP-style: git@bitbucket.org:workspace + if ":" not in base_url or "//" in base_url: + raise ValueError( + f"Expected SCP-style URL (git@bitbucket.org:workspace), " + f"got: {base_url}" + ) + host_part, workspace = base_url.split(":", 1) + # host_part is e.g. "git@bitbucket.org" + host = host_part.split("@", 1)[-1] + if host != "bitbucket.org": + raise ValueError( + f"Mirror base URL must target bitbucket.org, " + f"got host: {host}" + ) + return Path(workspace).name.split('.')[0] + + + +def _run( + args: List[str], + cwd: Optional[Path] = None, + capture_stdout: bool = False, +) -> subprocess.CompletedProcess: # type: ignore[type-arg] + """Run a git command, raising GitError on failure.""" + cmd = ["git"] + args + logger.debug("$ %s", " ".join(cmd)) + result = subprocess.run( + cmd, + cwd=cwd, + capture_output=True, + text=True, + ) + if result.returncode != 0: + raise GitError(cmd, result.returncode, result.stderr.strip()) + return result + + +def mirror_clone(source_url: str, dest: Path) -> None: + """Clone a repository as a bare mirror. + + Equivalent to ``git clone --mirror ``. + The destination directory must not already exist. + """ + _run(["clone", "--mirror", source_url, str(dest)]) + logger.info("Cloned mirror %s → %s", source_url, dest) + + +def add_remote(repo: Path, name: str, url: str) -> None: + """Add a named remote to a bare repository.""" + _run(["remote", "add", name, url], cwd=repo) + logger.debug("Added remote %s → %s in %s", name, url, repo) + + +def has_remote(repo: Path, name: str) -> bool: + """Check whether a named remote exists.""" + result = _run(["remote"], cwd=repo) + return name in result.stdout.splitlines() + + +def mirror_update(repo: Path) -> None: + """Fetch all remotes in a bare mirror repository. + + Equivalent to ``git remote update`` inside the bare repo. + """ + _run(["remote", "update"], cwd=repo) + logger.debug("Updated remotes in %s", repo) + + +def fetch(repo: Path, remote: str = "origin") -> None: + """Fetch from a single remote.""" + _run(["fetch", remote], cwd=repo) + logger.debug("fetched %s in %s", remote, repo) + + +def show_ref(repo: Path) -> str: + """Return the raw output of ``git show-ref`` (all refs + SHAs). + + Returns an empty string if the repo has no refs. + """ + try: + result = _run(["show-ref"], cwd=repo) + return result.stdout + except GitError: + return "" + + +def mirror_push(repo: Path, remote: str) -> None: + """Push the full mirror to a remote. + + Equivalent to ``git push --mirror ``. + """ + _run(["push", "--mirror", remote], cwd=repo) + logger.info("Pushed mirror to %s from %s", remote, repo) + + +def read_file( + repo: Path, + filepath: str, + ref: str = "HEAD", +) -> Optional[str]: + """Extract a file's contents from a bare repo without checkout. + + Returns the file content as a string, or None if the file does + not exist at the given ref. + """ + try: + result = _run( + ["show", f"{ref}:{filepath}"], + cwd=repo, + capture_stdout=True, + ) + return result.stdout + except GitError: + return None + + +# ------------------------------------------------------------------- +# Ref / tag primitives +# ------------------------------------------------------------------- + +def list_tags(repo: Path) -> List[str]: + """List all tags in a repository.""" + result = _run(["tag", "-l"], cwd=repo) + return [t for t in result.stdout.splitlines() if t] + + +def resolve_ref(repo: Path, ref: str) -> str: + """Resolve a ref to a full SHA. + + Raises GitError if the ref cannot be resolved. + """ + result = _run( + ["rev-parse", ref], cwd=repo, capture_stdout=True, + ) + return result.stdout.strip() + + +def head_ref(repo: Path) -> str: + """Return the full SHA of HEAD.""" + return resolve_ref(repo, "HEAD") + + +# ------------------------------------------------------------------- +# Pull-through bare clone cache +# ------------------------------------------------------------------- + +def bare_path_for_url(url: str, cache_dir: Path) -> Path: + """Derive a cache path from a clone URL. + + Strips scheme/host, keeps the path component, appends ``.git``. + + Examples:: + + https://github.com/h5p/h5p-multi-choice + → cache_dir / h5p / h5p-multi-choice.git + git@github.com:h5p/h5p-multi-choice.git + → cache_dir / h5p / h5p-multi-choice.git + """ + # Handle SCP-style URLs (git@host:path) + if ":" in url and "//" not in url: + path_part = url.split(":", 1)[1] + else: + # Strip scheme + host + from urllib.parse import urlparse + parsed = urlparse(url) + path_part = parsed.path.lstrip("/") + + # Strip trailing .git if present, then re-add it + if path_part.endswith(".git"): + path_part = path_part[:-4] + + return cache_dir / (path_part + ".git") + + +def ensure_bare_clone(url: str, cache_dir: Path) -> Path: + """Ensure a bare mirror clone exists in *cache_dir*. + + If the bare repo already exists, fetches updates via + ``mirror_update``. Otherwise, creates a new mirror clone. + Returns the path to the bare repo. + """ + bare_path = bare_path_for_url(url, cache_dir) + if bare_path.exists(): + mirror_update(bare_path) + logger.debug("Updated existing cache %s", bare_path) + else: + bare_path.parent.mkdir(parents=True, exist_ok=True) + mirror_clone(url, bare_path) + logger.info("Cached new bare clone %s", bare_path) + return bare_path + + +# ------------------------------------------------------------------- +# Submodule operations +# ------------------------------------------------------------------- + +def has_submodule(repo: Path, path: str) -> bool: + """Check whether a submodule is registered at *path*. + + Reads ``.gitmodules`` to determine whether the submodule exists. + Returns False if ``.gitmodules`` does not exist. + """ + gitmodules = repo / ".gitmodules" + if not gitmodules.is_file(): + return False + try: + result = _run( + ["config", "--file", ".gitmodules", + "--get-regexp", r"submodule\..*\.path"], + cwd=repo, + ) + except GitError: + return False + for line in result.stdout.splitlines(): + parts = line.split(None, 1) + if len(parts) == 2 and parts[1] == path: + return True + return False + + +def submodule_add(repo: Path, url: str, path: str) -> None: + """Add a git submodule at *path* pointing to *url*. + + Equivalent to ``git submodule add `` inside *repo*. + """ + _run(["submodule", "add", url, path], cwd=repo) + logger.info("Added submodule %s → %s", url, path) + + +def submodule_update(repo: Path, path: str) -> None: + """Fetch and update a submodule to the latest remote HEAD. + + Enters the submodule directory, fetches origin, and checks out + the latest commit on the remote default branch. + """ + sub_path = repo / path + _run(["fetch", "origin"], cwd=sub_path) + # Determine default branch from remote HEAD + result = _run( + ["symbolic-ref", "refs/remotes/origin/HEAD", + "--short"], + cwd=sub_path, + ) + default_branch = result.stdout.strip() + _run(["checkout", default_branch], cwd=sub_path) + logger.info("Updated submodule %s to %s", path, default_branch) From c4fb29f6945a4030be7b61f52dbb3e8f96ed7c81 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Wed, 4 Mar 2026 18:10:18 +0100 Subject: [PATCH 59/92] feat(git): submodule and remote handling --- src/byteb4rb1e/utils/vcs/git.py | 45 ++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/src/byteb4rb1e/utils/vcs/git.py b/src/byteb4rb1e/utils/vcs/git.py index ab7e87f..cd4cd87 100644 --- a/src/byteb4rb1e/utils/vcs/git.py +++ b/src/byteb4rb1e/utils/vcs/git.py @@ -146,6 +146,18 @@ def show_ref(repo: Path) -> str: return "" +def ls_remote(repo: Path, remote: str) -> str: + """Return the raw output of ``git ls-remote ``. + + Returns an empty string if the remote has no refs or on error. + """ + try: + result = _run(["ls-remote", remote], cwd=repo) + return result.stdout + except GitError: + return "" + + def mirror_push(repo: Path, remote: str) -> None: """Push the full mirror to a remote. @@ -260,22 +272,39 @@ def has_submodule(repo: Path, path: str) -> bool: """Check whether a submodule is registered at *path*. Reads ``.gitmodules`` to determine whether the submodule exists. + *path* is resolved relative to *repo*, then compared against + the repository root so the check works when *repo* is a + subdirectory of the actual git working tree. Returns False if ``.gitmodules`` does not exist. """ - gitmodules = repo / ".gitmodules" + try: + toplevel = Path( + _run( + ["rev-parse", "--show-toplevel"], cwd=repo, + ).stdout.strip() + ) + except GitError: + return False + gitmodules = toplevel / ".gitmodules" if not gitmodules.is_file(): return False + # Resolve the full path relative to the repo root + full_path = (repo / path).resolve() + try: + rel_path = str(full_path.relative_to(toplevel.resolve())) + except ValueError: + return False try: result = _run( - ["config", "--file", ".gitmodules", + ["config", "--file", str(gitmodules), "--get-regexp", r"submodule\..*\.path"], - cwd=repo, + cwd=toplevel, ) except GitError: return False for line in result.stdout.splitlines(): parts = line.split(None, 1) - if len(parts) == 2 and parts[1] == path: + if len(parts) == 2 and parts[1] == rel_path: return True return False @@ -306,3 +335,11 @@ def submodule_update(repo: Path, path: str) -> None: default_branch = result.stdout.strip() _run(["checkout", default_branch], cwd=sub_path) logger.info("Updated submodule %s to %s", path, default_branch) + + +def submodule_checkout(repo: Path, path: str, ref: str) -> None: + """Fetch and checkout a specific ref in a submodule.""" + sub_path = repo / path + _run(["fetch", "origin"], cwd=sub_path) + _run(["checkout", ref], cwd=sub_path) + logger.info("Checked out submodule %s at %s", path, ref) From 4cdf357022209b57e9507f996aa21578f57a5723 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Mon, 16 Mar 2026 00:09:57 +0100 Subject: [PATCH 60/92] dirty --- src/byteb4rb1e/utils/argparse/dispatcher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/byteb4rb1e/utils/argparse/dispatcher.py b/src/byteb4rb1e/utils/argparse/dispatcher.py index 0c8768b..b579351 100644 --- a/src/byteb4rb1e/utils/argparse/dispatcher.py +++ b/src/byteb4rb1e/utils/argparse/dispatcher.py @@ -90,7 +90,7 @@ class CLI: for dest in self._dests: val = getattr(args, dest, None) if val is None: - break + continue parts.append(val) if not parts: return None From 4cd79cc6a4252ecd66ec5111ac7a9398ad6ae319 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Mon, 16 Mar 2026 00:16:04 +0100 Subject: [PATCH 61/92] chore: reapply editable --- Pipfile | 7 +- Pipfile.lock | 597 ++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 549 insertions(+), 55 deletions(-) diff --git a/Pipfile b/Pipfile index fa10d4e..673ee77 100644 --- a/Pipfile +++ b/Pipfile @@ -7,14 +7,17 @@ name = "pypi" setuptools-scm = "~=8.2.0" build = "*" pipenv = "*" -byteb4rb1e-utils = { editable = true, path = '.'} tox = "*" +twine = "*" [requires] -python_version = "3.11" +python_version = "3" [scripts] "build" = "python3 -m build" "test-static" = "tox run -m static" "test-unit" = "tox run -m unit" "test-integration" = "tox run -m integration" + +[packages] +"byteb4rb1e.utils" = {file = ".", editable = true} diff --git a/Pipfile.lock b/Pipfile.lock index aa123f0..84ab77b 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,11 +1,11 @@ { "_meta": { "hash": { - "sha256": "cb7c8c0a12f574d2bc30ffe38e79ba18ee29424cb1fb1cdce8373f89d56f3e1c" + "sha256": "b9a6a40fe538c374ded3e0fc84bd0cbe34d236468cb184d613e59e3ccf14f0f1" }, "pipfile-spec": 6, "requires": { - "python_version": "3.11" + "python_version": "3" }, "sources": [ { @@ -15,44 +15,262 @@ } ] }, - "default": {}, + "default": { + "byteb4rb1e.utils": { + "editable": true, + "file": "." + } + }, "develop": { "build": { "hashes": [ - "sha256:1d61c0887fa860c01971625baae8bdd338e517b836a2f70dd1f7aa3a6b2fc5b5", - "sha256:b36993e92ca9375a219c99e606a122ff365a760a2d4bba0caa09bd5278b608b7" + "sha256:6a07c1b8eb6f2b311b96fcbdbce5dab5fe637ffda0fd83c9cac622e927501596", + "sha256:f1b91b925aa322be454f8330c6fb48b465da993d1e7e7e6fa35027ec49f3c936" ], "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==1.2.2.post1" - }, - "byteb4rb1e-utils": { - "editable": true, - "path": "." + "markers": "python_version >= '3.9'", + "version": "==1.4.0" }, "cachetools": { "hashes": [ - "sha256:1c7bb3cf9193deaf3508b7c5f2a79986c13ea38965c5adcff1f84519cf39163e", - "sha256:b4c4f404392848db3ce7aac34950d17be4d864da4b8b66911008e430bc544587" + "sha256:0cd042c24377200c1dcd225f8b7b12b0ca53cc2c961b43757e774ebe190fd990", + "sha256:46bc8ebefbe485407621d0a4264b23c080cedd913921bad7ac3ed2f26c183114" ], - "markers": "python_version >= '3.9'", - "version": "==6.1.0" + "markers": "python_version >= '3.10'", + "version": "==7.0.5" }, "certifi": { "hashes": [ - "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057", - "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b" + "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", + "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7" ], "markers": "python_version >= '3.7'", - "version": "==2025.6.15" + "version": "==2026.2.25" }, - "chardet": { + "cffi": { "hashes": [ - "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", - "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970" + "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", + "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", + "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", + "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", + "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", + "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2", + "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", + "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", + "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65", + "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", + "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", + "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", + "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", + "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a", + "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", + "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", + "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", + "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", + "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", + "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", + "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", + "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", + "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", + "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", + "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165", + "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", + "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", + "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c", + "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", + "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", + "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", + "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", + "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63", + "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", + "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", + "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", + "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", + "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", + "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", + "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", + "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", + "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", + "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", + "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", + "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", + "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", + "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322", + "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", + "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", + "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", + "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", + "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", + "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", + "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", + "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", + "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", + "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", + "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", + "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", + "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", + "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9", + "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", + "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", + "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", + "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", + "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", + "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f", + "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", + "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", + "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", + "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", + "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", + "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", + "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", + "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", + "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", + "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7", + "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", + "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534", + "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", + "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", + "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", + "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", + "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf" + ], + "markers": "python_version >= '3.9'", + "version": "==2.0.0" + }, + "charset-normalizer": { + "hashes": [ + "sha256:06a7e86163334edfc5d20fe104db92fcd666e5a5df0977cb5680a506fe26cc8e", + "sha256:0c173ce3a681f309f31b87125fecec7a5d1347261ea11ebbb856fa6006b23c8c", + "sha256:0e28d62a8fc7a1fa411c43bd65e346f3bce9716dc51b897fbe930c5987b402d5", + "sha256:0e901eb1049fdb80f5bd11ed5ea1e498ec423102f7a9b9e4645d5b8204ff2815", + "sha256:11afb56037cbc4b1555a34dd69151e8e069bee82e613a73bef6e714ce733585f", + "sha256:150b8ce8e830eb7ccb029ec9ca36022f756986aaaa7956aad6d9ec90089338c0", + "sha256:172985e4ff804a7ad08eebec0a1640ece87ba5041d565fff23c8f99c1f389484", + "sha256:197c1a244a274bb016dd8b79204850144ef77fe81c5b797dc389327adb552407", + "sha256:1ae6b62897110aa7c79ea2f5dd38d1abca6db663687c0b1ad9aed6f6bae3d9d6", + "sha256:1cf0a70018692f85172348fe06d3a4b63f94ecb055e13a00c644d368eb82e5b8", + "sha256:1ed80ff870ca6de33f4d953fda4d55654b9a2b340ff39ab32fa3adbcd718f264", + "sha256:22c6f0c2fbc31e76c3b8a86fba1a56eda6166e238c29cdd3d14befdb4a4e4815", + "sha256:231d4da14bcd9301310faf492051bee27df11f2bc7549bc0bb41fef11b82daa2", + "sha256:259695e2ccc253feb2a016303543d691825e920917e31f894ca1a687982b1de4", + "sha256:2a24157fa36980478dd1770b585c0f30d19e18f4fb0c47c13aa568f871718579", + "sha256:2b1a63e8224e401cafe7739f77efd3f9e7f5f2026bda4aead8e59afab537784f", + "sha256:2bd9d128ef93637a5d7a6af25363cf5dec3fa21cf80e68055aad627f280e8afa", + "sha256:2e1d8ca8611099001949d1cdfaefc510cf0f212484fe7c565f735b68c78c3c95", + "sha256:2ef7fedc7a6ecbe99969cd09632516738a97eeb8bd7258bf8a0f23114c057dab", + "sha256:2f7fdd9b6e6c529d6a2501a2d36b240109e78a8ceaef5687cfcfa2bbe671d297", + "sha256:30f445ae60aad5e1f8bdbb3108e39f6fbc09f4ea16c815c66578878325f8f15a", + "sha256:31215157227939b4fb3d740cd23fe27be0439afef67b785a1eb78a3ae69cba9e", + "sha256:34315ff4fc374b285ad7f4a0bf7dcbfe769e1b104230d40f49f700d4ab6bbd84", + "sha256:3516bbb8d42169de9e61b8520cbeeeb716f12f4ecfe3fd30a9919aa16c806ca8", + "sha256:3778fd7d7cd04ae8f54651f4a7a0bd6e39a0cf20f801720a4c21d80e9b7ad6b0", + "sha256:39f5068d35621da2881271e5c3205125cc456f54e9030d3f723288c873a71bf9", + "sha256:404a1e552cf5b675a87f0651f8b79f5f1e6fd100ee88dc612f89aa16abd4486f", + "sha256:419a9d91bd238052642a51938af8ac05da5b3343becde08d5cdeab9046df9ee1", + "sha256:423fb7e748a08f854a08a222b983f4df1912b1daedce51a72bd24fe8f26a1843", + "sha256:4482481cb0572180b6fd976a4d5c72a30263e98564da68b86ec91f0fe35e8565", + "sha256:461598cd852bfa5a61b09cae2b1c02e2efcd166ee5516e243d540ac24bfa68a7", + "sha256:47955475ac79cc504ef2704b192364e51d0d473ad452caedd0002605f780101c", + "sha256:48696db7f18afb80a068821504296eb0787d9ce239b91ca15059d1d3eaacf13b", + "sha256:4be9f4830ba8741527693848403e2c457c16e499100963ec711b1c6f2049b7c7", + "sha256:4d1d02209e06550bdaef34af58e041ad71b88e624f5d825519da3a3308e22687", + "sha256:4f41da960b196ea355357285ad1316a00099f22d0929fe168343b99b254729c9", + "sha256:517ad0e93394ac532745129ceabdf2696b609ec9f87863d337140317ebce1c14", + "sha256:51fb3c322c81d20567019778cb5a4a6f2dc1c200b886bc0d636238e364848c89", + "sha256:5273b9f0b5835ff0350c0828faea623c68bfa65b792720c453e22b25cc72930f", + "sha256:530d548084c4a9f7a16ed4a294d459b4f229db50df689bfe92027452452943a0", + "sha256:530e8cebeea0d76bdcf93357aa5e41336f48c3dc709ac52da2bb167c5b8271d9", + "sha256:54fae94be3d75f3e573c9a1b5402dc593de19377013c9a0e4285e3d402dd3a2a", + "sha256:572d7c822caf521f0525ba1bce1a622a0b85cf47ffbdae6c9c19e3b5ac3c4389", + "sha256:58c948d0d086229efc484fe2f30c2d382c86720f55cd9bc33591774348ad44e0", + "sha256:5d11595abf8dd942a77883a39d81433739b287b6aa71620f15164f8096221b30", + "sha256:5f8ddd609f9e1af8c7bd6e2aca279c931aefecd148a14402d4e368f3171769fd", + "sha256:5feb91325bbceade6afab43eb3b508c63ee53579fe896c77137ded51c6b6958e", + "sha256:60c74963d8350241a79cb8feea80e54d518f72c26db618862a8f53e5023deaf9", + "sha256:613f19aa6e082cf96e17e3ffd89383343d0d589abda756b7764cf78361fd41dc", + "sha256:659a1e1b500fac8f2779dd9e1570464e012f43e580371470b45277a27baa7532", + "sha256:695f5c2823691a25f17bc5d5ffe79fa90972cc34b002ac6c843bb8a1720e950d", + "sha256:69dd852c2f0ad631b8b60cfbe25a28c0058a894de5abb566619c205ce0550eae", + "sha256:6cceb5473417d28edd20c6c984ab6fee6c6267d38d906823ebfe20b03d607dc2", + "sha256:71be7e0e01753a89cf024abf7ecb6bca2c81738ead80d43004d9b5e3f1244e64", + "sha256:74119174722c4349af9708993118581686f343adc1c8c9c007d59be90d077f3f", + "sha256:74a2e659c7ecbc73562e2a15e05039f1e22c75b7c7618b4b574a3ea9118d1557", + "sha256:7504e9b7dc05f99a9bbb4525c67a2c155073b44d720470a148b34166a69c054e", + "sha256:79090741d842f564b1b2827c0b82d846405b744d31e84f18d7a7b41c20e473ff", + "sha256:7a6967aaf043bceabab5412ed6bd6bd26603dae84d5cb75bf8d9a74a4959d398", + "sha256:7bda6eebafd42133efdca535b04ccb338ab29467b3f7bf79569883676fc628db", + "sha256:7edbed096e4a4798710ed6bc75dcaa2a21b68b6c356553ac4823c3658d53743a", + "sha256:7f9019c9cb613f084481bd6a100b12e1547cf2efe362d873c2e31e4035a6fa43", + "sha256:802168e03fba8bbc5ce0d866d589e4b1ca751d06edee69f7f3a19c5a9fe6b597", + "sha256:80d0a5615143c0b3225e5e3ef22c8d5d51f3f72ce0ea6fb84c943546c7b25b6c", + "sha256:82060f995ab5003a2d6e0f4ad29065b7672b6593c8c63559beefe5b443242c3e", + "sha256:836ab36280f21fc1a03c99cd05c6b7af70d2697e374c7af0b61ed271401a72a2", + "sha256:8761ac29b6c81574724322a554605608a9960769ea83d2c73e396f3df896ad54", + "sha256:87725cfb1a4f1f8c2fc9890ae2f42094120f4b44db9360be5d99a4c6b0e03a9e", + "sha256:899d28f422116b08be5118ef350c292b36fc15ec2daeb9ea987c89281c7bb5c4", + "sha256:8bc5f0687d796c05b1e28ab0d38a50e6309906ee09375dd3aff6a9c09dd6e8f4", + "sha256:8bea55c4eef25b0b19a0337dc4e3f9a15b00d569c77211fa8cde38684f234fb7", + "sha256:8e5a94886bedca0f9b78fecd6afb6629142fd2605aa70a125d49f4edc6037ee6", + "sha256:90ca27cd8da8118b18a52d5f547859cc1f8354a00cd1e8e5120df3e30d6279e5", + "sha256:92734d4d8d187a354a556626c221cd1a892a4e0802ccb2af432a1d85ec012194", + "sha256:947cf925bc916d90adba35a64c82aace04fa39b46b52d4630ece166655905a69", + "sha256:95b52c68d64c1878818687a473a10547b3292e82b6f6fe483808fb1468e2f52f", + "sha256:97d0235baafca5f2b09cf332cc275f021e694e8362c6bb9c96fc9a0eb74fc316", + "sha256:9ca4c0b502ab399ef89248a2c84c54954f77a070f28e546a85e91da627d1301e", + "sha256:9cc4fc6c196d6a8b76629a70ddfcd4635a6898756e2d9cac5565cf0654605d73", + "sha256:9cc6e6d9e571d2f863fa77700701dae73ed5f78881efc8b3f9a4398772ff53e8", + "sha256:a056d1ad2633548ca18ffa2f85c202cfb48b68615129143915b8dc72a806a923", + "sha256:a26611d9987b230566f24a0a125f17fe0de6a6aff9f25c9f564aaa2721a5fb88", + "sha256:a4474d924a47185a06411e0064b803c68be044be2d60e50e8bddcc2649957c1f", + "sha256:a4ea868bc28109052790eb2b52a9ab33f3aa7adc02f96673526ff47419490e21", + "sha256:a9e68c9d88823b274cf1e72f28cb5dc89c990edf430b0bfd3e2fb0785bfeabf4", + "sha256:aa9cccf4a44b9b62d8ba8b4dd06c649ba683e4bf04eea606d2e94cfc2d6ff4d6", + "sha256:ab30e5e3e706e3063bc6de96b118688cb10396b70bb9864a430f67df98c61ecc", + "sha256:ac2393c73378fea4e52aa56285a3d64be50f1a12395afef9cce47772f60334c2", + "sha256:ad8faf8df23f0378c6d527d8b0b15ea4a2e23c89376877c598c4870d1b2c7866", + "sha256:b35b200d6a71b9839a46b9b7fff66b6638bb52fc9658aa58796b0326595d3021", + "sha256:b3694e3f87f8ac7ce279d4355645b3c878d24d1424581b46282f24b92f5a4ae2", + "sha256:b4ff1d35e8c5bd078be89349b6f3a845128e685e751b6ea1169cf2160b344c4d", + "sha256:bbc8c8650c6e51041ad1be191742b8b421d05bbd3410f43fa2a00c8db87678e8", + "sha256:bc72863f4d9aba2e8fd9085e63548a324ba706d2ea2c83b260da08a59b9482de", + "sha256:bf625105bb9eef28a56a943fec8c8a98aeb80e7d7db99bd3c388137e6eb2d237", + "sha256:c2274ca724536f173122f36c98ce188fd24ce3dad886ec2b7af859518ce008a4", + "sha256:c45a03a4c69820a399f1dda9e1d8fbf3562eda46e7720458180302021b08f778", + "sha256:c8ae56368f8cc97c7e40a7ee18e1cedaf8e780cd8bc5ed5ac8b81f238614facb", + "sha256:c907cdc8109f6c619e6254212e794d6548373cc40e1ec75e6e3823d9135d29cc", + "sha256:ca0276464d148c72defa8bb4390cce01b4a0e425f3b50d1435aa6d7a18107602", + "sha256:cd5e2801c89992ed8c0a3f0293ae83c159a60d9a5d685005383ef4caca77f2c4", + "sha256:d08ec48f0a1c48d75d0356cea971921848fb620fdeba805b28f937e90691209f", + "sha256:d1a2ee9c1499fc8f86f4521f27a973c914b211ffa87322f4ee33bb35392da2c5", + "sha256:d5f5d1e9def3405f60e3ca8232d56f35c98fb7bf581efcc60051ebf53cb8b611", + "sha256:d60377dce4511655582e300dc1e5a5f24ba0cb229005a1d5c8d0cb72bb758ab8", + "sha256:d73beaac5e90173ac3deb9928a74763a6d230f494e4bfb422c217a0ad8e629bf", + "sha256:d7de2637729c67d67cf87614b566626057e95c303bc0a55ffe391f5205e7003d", + "sha256:dad6e0f2e481fffdcf776d10ebee25e0ef89f16d691f1e5dee4b586375fdc64b", + "sha256:dda86aba335c902b6149a02a55b38e96287157e609200811837678214ba2b1db", + "sha256:df01808ee470038c3f8dc4f48620df7225c49c2d6639e38f96e6d6ac6e6f7b0e", + "sha256:e1f6e2f00a6b8edb562826e4632e26d063ac10307e80f7461f7de3ad8ef3f077", + "sha256:e25369dc110d58ddf29b949377a93e0716d72a24f62bad72b2b39f155949c1fd", + "sha256:e3c701e954abf6fc03a49f7c579cc80c2c6cc52525340ca3186c41d3f33482ef", + "sha256:e5bcc1a1ae744e0bb59641171ae53743760130600da8db48cbb6e4918e186e4e", + "sha256:e68c14b04827dd76dcbd1aeea9e604e3e4b78322d8faf2f8132c7138efa340a8", + "sha256:e8aeb10fcbe92767f0fa69ad5a72deca50d0dca07fbde97848997d778a50c9fe", + "sha256:e985a16ff513596f217cee86c21371b8cd011c0f6f056d0920aa2d926c544058", + "sha256:ecbbd45615a6885fe3240eb9db73b9e62518b611850fdf8ab08bd56de7ad2b17", + "sha256:ee4ec14bc1680d6b0afab9aea2ef27e26d2024f18b24a2d7155a52b60da7e833", + "sha256:ef5960d965e67165d75b7c7ffc60a83ec5abfc5c11b764ec13ea54fbef8b4421", + "sha256:f0cdaecd4c953bfae0b6bb64910aaaca5a424ad9c72d85cb88417bb9814f7550", + "sha256:f1ce721c8a7dfec21fcbdfe04e8f68174183cf4e8188e0645e92aa23985c57ff", + "sha256:f50498891691e0864dc3da965f340fada0771f6142a378083dc4608f4ea513e2", + "sha256:f5ea69428fa1b49573eef0cc44a1d43bebd45ad0c611eb7d7eac760c7ae771bc", + "sha256:f61aa92e4aad0be58eb6eb4e0c21acf32cf8065f4b2cae5665da756c4ceef982", + "sha256:f6e4333fb15c83f7d1482a76d45a0818897b3d33f00efd215528ff7c51b8e35d", + "sha256:f820f24b09e3e779fe84c3c456cb4108a7aa639b0d1f02c28046e11bfcd088ed", + "sha256:f98059e4fcd3e3e4e2d632b7cf81c2faae96c43c60b569e9c621468082f1d104", + "sha256:fcce033e4021347d80ed9c66dcf1e7b1546319834b74445f561d2e2221de5659" ], "markers": "python_version >= '3.7'", - "version": "==5.2.0" + "version": "==3.4.6" }, "colorama": { "hashes": [ @@ -62,45 +280,221 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'", "version": "==0.4.6" }, + "cryptography": { + "hashes": [ + "sha256:02f547fce831f5096c9a567fd41bc12ca8f11df260959ecc7c3202555cc47a72", + "sha256:039917b0dc418bb9f6edce8a906572d69e74bd330b0b3fea4f79dab7f8ddd235", + "sha256:1abfdb89b41c3be0365328a410baa9df3ff8a9110fb75e7b52e66803ddabc9a9", + "sha256:2ae6971afd6246710480e3f15824ed3029a60fc16991db250034efd0b9fb4356", + "sha256:2b7a67c9cd56372f3249b39699f2ad479f6991e62ea15800973b956f4b73e257", + "sha256:351695ada9ea9618b3500b490ad54c739860883df6c1f555e088eaf25b1bbaad", + "sha256:38946c54b16c885c72c4f59846be9743d699eee2b69b6988e0a00a01f46a61a4", + "sha256:3b4995dc971c9fb83c25aa44cf45f02ba86f71ee600d81091c2f0cbae116b06c", + "sha256:3ce58ba46e1bc2aac4f7d9290223cead56743fa6ab94a5d53292ffaac6a91614", + "sha256:3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed", + "sha256:4108d4c09fbbf2789d0c926eb4152ae1760d5a2d97612b92d508d96c861e4d31", + "sha256:420d0e909050490d04359e7fdb5ed7e667ca5c3c402b809ae2563d7e66a92229", + "sha256:47fb8a66058b80e509c47118ef8a75d14c455e81ac369050f20ba0d23e77fee0", + "sha256:4c3341037c136030cb46e4b1e17b7418ea4cbd9dd207e4a6f3b2b24e0d4ac731", + "sha256:4d7e3d356b8cd4ea5aff04f129d5f66ebdc7b6f8eae802b93739ed520c47c79b", + "sha256:4d8ae8659ab18c65ced284993c2265910f6c9e650189d4e3f68445ef82a810e4", + "sha256:4e817a8920bfbcff8940ecfd60f23d01836408242b30f1a708d93198393a80b4", + "sha256:50bfb6925eff619c9c023b967d5b77a54e04256c4281b0e21336a130cd7fc263", + "sha256:556e106ee01aa13484ce9b0239bca667be5004efb0aabbed28d353df86445595", + "sha256:582f5fcd2afa31622f317f80426a027f30dc792e9c80ffee87b993200ea115f1", + "sha256:5be7bf2fb40769e05739dd0046e7b26f9d4670badc7b032d6ce4db64dddc0678", + "sha256:60ee7e19e95104d4c03871d7d7dfb3d22ef8a9b9c6778c94e1c8fcc8365afd48", + "sha256:61aa400dce22cb001a98014f647dc21cda08f7915ceb95df0c9eaf84b4b6af76", + "sha256:68f68d13f2e1cb95163fa3b4db4bf9a159a418f5f6e7242564fc75fcae667fd0", + "sha256:7d1f30a86d2757199cb2d56e48cce14deddf1f9c95f1ef1b64ee91ea43fe2e18", + "sha256:7d731d4b107030987fd61a7f8ab512b25b53cef8f233a97379ede116f30eb67d", + "sha256:803812e111e75d1aa73690d2facc295eaefd4439be1023fefc4995eaea2af90d", + "sha256:80a8d7bfdf38f87ca30a5391c0c9ce4ed2926918e017c29ddf643d0ed2778ea1", + "sha256:8293f3dea7fc929ef7240796ba231413afa7b68ce38fd21da2995549f5961981", + "sha256:8456928655f856c6e1533ff59d5be76578a7157224dbd9ce6872f25055ab9ab7", + "sha256:890bcb4abd5a2d3f852196437129eb3667d62630333aacc13dfd470fad3aaa82", + "sha256:94a76daa32eb78d61339aff7952ea819b1734b46f73646a07decb40e5b3448e2", + "sha256:9f16fbdf4da055efb21c22d81b89f155f02ba420558db21288b3d0035bafd5f4", + "sha256:a3d1fae9863299076f05cb8a778c467578262fae09f9dc0ee9b12eb4268ce663", + "sha256:a3d507bb6a513ca96ba84443226af944b0f7f47dcc9a399d110cd6146481d24c", + "sha256:abace499247268e3757271b2f1e244b36b06f8515cf27c4d49468fc9eb16e93d", + "sha256:ba2a27ff02f48193fc4daeadf8ad2590516fa3d0adeeb34336b96f7fa64c1e3a", + "sha256:bc84e875994c3b445871ea7181d424588171efec3e185dced958dad9e001950a", + "sha256:bfd56bb4b37ed4f330b82402f6f435845a5f5648edf1ad497da51a8452d5d62d", + "sha256:c18ff11e86df2e28854939acde2d003f7984f721eba450b56a200ad90eeb0e6b", + "sha256:c3bcce8521d785d510b2aad26ae2c966092b7daa8f45dd8f44734a104dc0bc1a", + "sha256:c4143987a42a2397f2fc3b4d7e3a7d313fbe684f67ff443999e803dd75a76826", + "sha256:c69fd885df7d089548a42d5ec05be26050ebcd2283d89b3d30676eb32ff87dee", + "sha256:ced80795227d70549a411a4ab66e8ce307899fad2220ce5ab2f296e687eacde9", + "sha256:d66e421495fdb797610a08f43b05269e0a5ea7f5e652a89bfd5a7d3c1dee3648", + "sha256:d861ee9e76ace6cf36a6a89b959ec08e7bc2493ee39d07ffe5acb23ef46d27da", + "sha256:e9251e3be159d1020c4030bd2e5f84d6a43fe54b6c19c12f51cde9542a2817b2", + "sha256:f145bba11b878005c496e93e257c1e88f154d278d2638e6450d17e0f31e558d2", + "sha256:fe346b143ff9685e40192a4960938545c699054ba11d4f9029f94751e3f71d87" + ], + "markers": "python_version >= '3.8' and python_full_version not in '3.9.0, 3.9.1'", + "version": "==46.0.5" + }, "distlib": { "hashes": [ - "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", - "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403" + "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", + "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d" ], - "version": "==0.3.9" + "version": "==0.4.0" + }, + "docutils": { + "hashes": [ + "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", + "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de" + ], + "markers": "python_version >= '3.9'", + "version": "==0.22.4" }, "filelock": { "hashes": [ - "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", - "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de" + "sha256:b64ece2b38f4ca29dd3e810287aa8c48182bbecd1ae6e9ae126c9b35f1382694", + "sha256:ca8afb0da15f229774c9ad1b455ed96e85a81373065fb10446672f64444ddf70" + ], + "markers": "python_version >= '3.10'", + "version": "==3.25.2" + }, + "id": { + "hashes": [ + "sha256:d0732d624fb46fd4e7bc4e5152f00214450953b9e772c182c1c22964def1a069", + "sha256:f5ec41ed2629a508f5d0988eda142e190c9c6da971100612c4de9ad9f9b237ca" ], "markers": "python_version >= '3.9'", - "version": "==3.18.0" + "version": "==1.6.1" + }, + "idna": { + "hashes": [ + "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", + "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" + ], + "markers": "python_version >= '3.8'", + "version": "==3.11" + }, + "jaraco.classes": { + "hashes": [ + "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", + "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790" + ], + "markers": "python_version >= '3.8'", + "version": "==3.4.0" + }, + "jaraco.context": { + "hashes": [ + "sha256:0df6a0287258f3e364072c3e40d5411b20cafa30cb28c4839d24319cecf9f808", + "sha256:bc046b2dc94f1e5532bd02402684414575cc11f565d929b6563125deb0a6e581" + ], + "markers": "python_version >= '3.9'", + "version": "==6.1.1" + }, + "jaraco.functools": { + "hashes": [ + "sha256:9eec1e36f45c818d9bf307c8948eb03b2b56cd44087b3cdc989abca1f20b9176", + "sha256:da21933b0417b89515562656547a77b4931f98176eb173644c0d35032a33d6bb" + ], + "markers": "python_version >= '3.9'", + "version": "==4.4.0" + }, + "jeepney": { + "hashes": [ + "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", + "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732" + ], + "markers": "python_version >= '3.7'", + "version": "==0.9.0" + }, + "keyring": { + "hashes": [ + "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", + "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b" + ], + "markers": "python_version >= '3.9'", + "version": "==25.7.0" + }, + "markdown-it-py": { + "hashes": [ + "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", + "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3" + ], + "markers": "python_version >= '3.10'", + "version": "==4.0.0" + }, + "mdurl": { + "hashes": [ + "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", + "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba" + ], + "markers": "python_version >= '3.7'", + "version": "==0.1.2" + }, + "more-itertools": { + "hashes": [ + "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", + "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd" + ], + "markers": "python_version >= '3.9'", + "version": "==10.8.0" + }, + "nh3": { + "hashes": [ + "sha256:0d5eb734a78ac364af1797fef718340a373f626a9ff6b4fb0b4badf7927e7b81", + "sha256:185ed41b88c910b9ca8edc89ca3b4be688a12cb9de129d84befa2f74a0039fee", + "sha256:1ef87f8e916321a88b45f2d597f29bd56e560ed4568a50f0f1305afab86b7189", + "sha256:21a63ccb18ddad3f784bb775955839b8b80e347e597726f01e43ca1abcc5c808", + "sha256:21b058cd20d9f0919421a820a2843fdb5e1749c0bf57a6247ab8f4ba6723c9fc", + "sha256:24769a428e9e971e4ccfb24628f83aaa7dc3c8b41b130c8ddc1835fa1c924489", + "sha256:2efd17c0355d04d39e6d79122b42662277ac10a17ea48831d90b46e5ef7e4fc0", + "sha256:3a62b8ae7c235481715055222e54c682422d0495a5c73326807d4e44c5d14691", + "sha256:45fe0d6a607264910daec30360c8a3b5b1500fd832d21b2da608256287bcb92d", + "sha256:4c730617bdc15d7092dcc0469dc2826b914c8f874996d105b4bc3842a41c1cd9", + "sha256:52e973cb742e95b9ae1b35822ce23992428750f4b46b619fe86eba4205255b30", + "sha256:5a4b2c1f3e6f3cbe7048e17f4fefad3f8d3e14cc0fd08fb8599e0d5653f6b181", + "sha256:5bc1d4b30ba1ba896669d944b6003630592665974bd11a3dc2f661bde92798a7", + "sha256:90126a834c18af03bfd6ff9a027bfa6bbf0e238527bc780a24de6bd7cc1041e2", + "sha256:92a958e6f6d0100e025a5686aafd67e3c98eac67495728f8bb64fbeb3e474493", + "sha256:9ed40cf8449a59a03aa465114fedce1ff7ac52561688811d047917cc878b19ca", + "sha256:a446eae598987f49ee97ac2f18eafcce4e62e7574bd1eb23782e4702e54e217d", + "sha256:b50c3770299fb2a7c1113751501e8878d525d15160a4c05194d7fe62b758aad8", + "sha256:b7a18ee057761e455d58b9d31445c3e4b2594cff4ddb84d2e331c011ef46f462", + "sha256:b838e619f483531483d26d889438e53a880510e832d2aafe73f93b7b1ac2bce2", + "sha256:e8ee96156f7dfc6e30ecda650e480c5ae0a7d38f0c6fafc3c1c655e2500421d9", + "sha256:e974850b131fdffa75e7ad8e0d9c7a855b96227b093417fdf1bd61656e530f37", + "sha256:e98fa3dbfd54e25487e36ba500bc29bca3a4cab4ffba18cfb1a35a2d02624297", + "sha256:f433a2dd66545aad4a720ad1b2150edcdca75bfff6f4e6f378ade1ec138d5e77", + "sha256:f4400a73c2a62859e769f9d36d1b5a7a5c65c4179d1dddd2f6f3095b2db0cbfc", + "sha256:f508ddd4e2433fdcb78c790fc2d24e3a349ba775e5fa904af89891321d4844a3", + "sha256:fc305a2264868ec8fa16548296f803d8fd9c1fa66cd28b88b605b1bd06667c0b" + ], + "markers": "python_version >= '3.8'", + "version": "==0.3.3" }, "packaging": { "hashes": [ - "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", - "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" + "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", + "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529" ], "markers": "python_version >= '3.8'", - "version": "==25.0" + "version": "==26.0" }, "pipenv": { "hashes": [ - "sha256:87370bedcf0ff66d226af07ca341ae94afcc08fed90d57ad9fea9ffd44ced4d3", - "sha256:f0a67aa928824e61003d52acea72a94b180800019f03d38a311966f6330bc8d1" + "sha256:06fba6b4fa542acf8f551cfedf604a08940bbe3068bd575e5163f7c2e8e51eac", + "sha256:56ca111e9b236f551030eb7422f6aa71edd9b2ccc018e100ad80ebc57eb2b270" ], "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==2025.0.3" + "markers": "python_version >= '3.10'", + "version": "==2026.1.0" }, "platformdirs": { "hashes": [ - "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", - "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4" + "sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934", + "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868" ], - "markers": "python_version >= '3.9'", - "version": "==4.3.8" + "markers": "python_version >= '3.10'", + "version": "==4.9.4" }, "pluggy": { "hashes": [ @@ -110,13 +504,29 @@ "markers": "python_version >= '3.9'", "version": "==1.6.0" }, + "pycparser": { + "hashes": [ + "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", + "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992" + ], + "markers": "python_version >= '3.10'", + "version": "==3.0" + }, + "pygments": { + "hashes": [ + "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", + "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" + ], + "markers": "python_version >= '3.8'", + "version": "==2.19.2" + }, "pyproject-api": { "hashes": [ - "sha256:43c9918f49daab37e302038fc1aed54a8c7a91a9fa935d00b9a485f37e0f5335", - "sha256:7d6238d92f8962773dd75b5f0c4a6a27cce092a14b623b811dba656f3b628948" + "sha256:40c6f2d82eebdc4afee61c773ed208c04c19db4c4a60d97f8d7be3ebc0bbb330", + "sha256:8757c41a79c0f4ab71b99abed52b97ecf66bd20b04fa59da43b5840bac105a09" ], - "markers": "python_version >= '3.9'", - "version": "==1.9.1" + "markers": "python_version >= '3.10'", + "version": "==1.10.0" }, "pyproject-hooks": { "hashes": [ @@ -126,13 +536,69 @@ "markers": "python_version >= '3.7'", "version": "==1.2.0" }, - "setuptools": { + "python-discovery": { "hashes": [ - "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", - "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c" + "sha256:7acca36e818cd88e9b2ba03e045ad7e93e1713e29c6bbfba5d90202310b7baa5", + "sha256:90e795f0121bc84572e737c9aa9966311b9fde44ffb88a5953b3ec9b31c6945e" + ], + "markers": "python_version >= '3.8'", + "version": "==1.1.3" + }, + "readme-renderer": { + "hashes": [ + "sha256:2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151", + "sha256:8712034eabbfa6805cacf1402b4eeb2a73028f72d1166d6f5cb7f9c047c5d1e1" ], "markers": "python_version >= '3.9'", - "version": "==80.9.0" + "version": "==44.0" + }, + "requests": { + "hashes": [ + "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", + "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf" + ], + "markers": "python_version >= '3.9'", + "version": "==2.32.5" + }, + "requests-toolbelt": { + "hashes": [ + "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", + "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.0.0" + }, + "rfc3986": { + "hashes": [ + "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", + "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c" + ], + "markers": "python_version >= '3.7'", + "version": "==2.0.0" + }, + "rich": { + "hashes": [ + "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d", + "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b" + ], + "markers": "python_full_version >= '3.8.0'", + "version": "==14.3.3" + }, + "secretstorage": { + "hashes": [ + "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", + "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be" + ], + "markers": "python_version >= '3.10'", + "version": "==3.5.0" + }, + "setuptools": { + "hashes": [ + "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", + "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb" + ], + "markers": "python_version >= '3.9'", + "version": "==82.0.1" }, "setuptools-scm": { "hashes": [ @@ -143,22 +609,47 @@ "markers": "python_version >= '3.8'", "version": "==8.2.0" }, + "tomli-w": { + "hashes": [ + "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90", + "sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021" + ], + "markers": "python_version >= '3.9'", + "version": "==1.2.0" + }, "tox": { "hashes": [ - "sha256:2b8a7fb986b82aa2c830c0615082a490d134e0626dbc9189986da46a313c4f20", - "sha256:b97d5ecc0c0d5755bcc5348387fef793e1bfa68eb33746412f4c60881d7f5f57" + "sha256:4130d02e1d53648d7107d121ed79f69a27b717817c5e9da521d50319dd261212", + "sha256:6dd2d7d4e4fd5895ce4ea20e258fce0d4b81e914b697d116a5ab0365f8303bad" + ], + "index": "pypi", + "markers": "python_version >= '3.10'", + "version": "==4.49.1" + }, + "twine": { + "hashes": [ + "sha256:418ebf08ccda9a8caaebe414433b0ba5e25eb5e4a927667122fbe8f829f985d8", + "sha256:e5ed0d2fd70c9959770dce51c8f39c8945c574e18173a7b81802dab51b4b75cf" ], "index": "pypi", "markers": "python_version >= '3.9'", - "version": "==4.27.0" + "version": "==6.2.0" + }, + "urllib3": { + "hashes": [ + "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", + "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4" + ], + "markers": "python_version >= '3.9'", + "version": "==2.6.3" }, "virtualenv": { "hashes": [ - "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11", - "sha256:e10c0a9d02835e592521be48b332b6caee6887f332c111aa79a09b9e79efc2af" + "sha256:1720dc3a62ef5b443092e3f499228599045d7fea4c79199770499df8becf9098", + "sha256:1bd755b504931164a5a496d217c014d098426cddc79363ad66ac78125f9d908f" ], "markers": "python_version >= '3.8'", - "version": "==20.31.2" + "version": "==21.2.0" } } } From bd3d0814c96e717c23807ba12448959a7b15a07a Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Mon, 16 Mar 2026 00:16:04 +0100 Subject: [PATCH 62/92] chore: reapply editable --- Pipfile.lock | 416 +-------------------------------------------------- 1 file changed, 4 insertions(+), 412 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index 84ab77b..6702742 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "b9a6a40fe538c374ded3e0fc84bd0cbe34d236468cb184d613e59e3ccf14f0f1" + "sha256": "5341a513f59952743e55c6f51253eecbcec1a2856a69e159f64193f59c081d4c" }, "pipfile-spec": 6, "requires": { @@ -31,6 +31,9 @@ "markers": "python_version >= '3.9'", "version": "==1.4.0" }, + "byteb4rb1e.utils": { + "version": "==0.1.dev92+g4cdf35702.d20260315" + }, "cachetools": { "hashes": [ "sha256:0cd042c24377200c1dcd225f8b7b12b0ca53cc2c961b43757e774ebe190fd990", @@ -47,231 +50,6 @@ "markers": "python_version >= '3.7'", "version": "==2026.2.25" }, - "cffi": { - "hashes": [ - "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", - "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", - "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", - "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", - "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", - "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2", - "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", - "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", - "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65", - "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", - "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", - "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", - "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", - "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a", - "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", - "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", - "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", - "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", - "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", - "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", - "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", - "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", - "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", - "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", - "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165", - "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", - "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", - "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c", - "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", - "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", - "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", - "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", - "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63", - "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", - "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", - "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", - "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", - "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", - "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", - "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", - "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", - "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", - "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", - "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", - "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", - "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", - "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322", - "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", - "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", - "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", - "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", - "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", - "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", - "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", - "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", - "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", - "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", - "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", - "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", - "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", - "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9", - "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", - "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", - "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", - "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", - "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", - "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f", - "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", - "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", - "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", - "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", - "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", - "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", - "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", - "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", - "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", - "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7", - "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", - "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534", - "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", - "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", - "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", - "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", - "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf" - ], - "markers": "python_version >= '3.9'", - "version": "==2.0.0" - }, - "charset-normalizer": { - "hashes": [ - "sha256:06a7e86163334edfc5d20fe104db92fcd666e5a5df0977cb5680a506fe26cc8e", - "sha256:0c173ce3a681f309f31b87125fecec7a5d1347261ea11ebbb856fa6006b23c8c", - "sha256:0e28d62a8fc7a1fa411c43bd65e346f3bce9716dc51b897fbe930c5987b402d5", - "sha256:0e901eb1049fdb80f5bd11ed5ea1e498ec423102f7a9b9e4645d5b8204ff2815", - "sha256:11afb56037cbc4b1555a34dd69151e8e069bee82e613a73bef6e714ce733585f", - "sha256:150b8ce8e830eb7ccb029ec9ca36022f756986aaaa7956aad6d9ec90089338c0", - "sha256:172985e4ff804a7ad08eebec0a1640ece87ba5041d565fff23c8f99c1f389484", - "sha256:197c1a244a274bb016dd8b79204850144ef77fe81c5b797dc389327adb552407", - "sha256:1ae6b62897110aa7c79ea2f5dd38d1abca6db663687c0b1ad9aed6f6bae3d9d6", - "sha256:1cf0a70018692f85172348fe06d3a4b63f94ecb055e13a00c644d368eb82e5b8", - "sha256:1ed80ff870ca6de33f4d953fda4d55654b9a2b340ff39ab32fa3adbcd718f264", - "sha256:22c6f0c2fbc31e76c3b8a86fba1a56eda6166e238c29cdd3d14befdb4a4e4815", - "sha256:231d4da14bcd9301310faf492051bee27df11f2bc7549bc0bb41fef11b82daa2", - "sha256:259695e2ccc253feb2a016303543d691825e920917e31f894ca1a687982b1de4", - "sha256:2a24157fa36980478dd1770b585c0f30d19e18f4fb0c47c13aa568f871718579", - "sha256:2b1a63e8224e401cafe7739f77efd3f9e7f5f2026bda4aead8e59afab537784f", - "sha256:2bd9d128ef93637a5d7a6af25363cf5dec3fa21cf80e68055aad627f280e8afa", - "sha256:2e1d8ca8611099001949d1cdfaefc510cf0f212484fe7c565f735b68c78c3c95", - "sha256:2ef7fedc7a6ecbe99969cd09632516738a97eeb8bd7258bf8a0f23114c057dab", - "sha256:2f7fdd9b6e6c529d6a2501a2d36b240109e78a8ceaef5687cfcfa2bbe671d297", - "sha256:30f445ae60aad5e1f8bdbb3108e39f6fbc09f4ea16c815c66578878325f8f15a", - "sha256:31215157227939b4fb3d740cd23fe27be0439afef67b785a1eb78a3ae69cba9e", - "sha256:34315ff4fc374b285ad7f4a0bf7dcbfe769e1b104230d40f49f700d4ab6bbd84", - "sha256:3516bbb8d42169de9e61b8520cbeeeb716f12f4ecfe3fd30a9919aa16c806ca8", - "sha256:3778fd7d7cd04ae8f54651f4a7a0bd6e39a0cf20f801720a4c21d80e9b7ad6b0", - "sha256:39f5068d35621da2881271e5c3205125cc456f54e9030d3f723288c873a71bf9", - "sha256:404a1e552cf5b675a87f0651f8b79f5f1e6fd100ee88dc612f89aa16abd4486f", - "sha256:419a9d91bd238052642a51938af8ac05da5b3343becde08d5cdeab9046df9ee1", - "sha256:423fb7e748a08f854a08a222b983f4df1912b1daedce51a72bd24fe8f26a1843", - "sha256:4482481cb0572180b6fd976a4d5c72a30263e98564da68b86ec91f0fe35e8565", - "sha256:461598cd852bfa5a61b09cae2b1c02e2efcd166ee5516e243d540ac24bfa68a7", - "sha256:47955475ac79cc504ef2704b192364e51d0d473ad452caedd0002605f780101c", - "sha256:48696db7f18afb80a068821504296eb0787d9ce239b91ca15059d1d3eaacf13b", - "sha256:4be9f4830ba8741527693848403e2c457c16e499100963ec711b1c6f2049b7c7", - "sha256:4d1d02209e06550bdaef34af58e041ad71b88e624f5d825519da3a3308e22687", - "sha256:4f41da960b196ea355357285ad1316a00099f22d0929fe168343b99b254729c9", - "sha256:517ad0e93394ac532745129ceabdf2696b609ec9f87863d337140317ebce1c14", - "sha256:51fb3c322c81d20567019778cb5a4a6f2dc1c200b886bc0d636238e364848c89", - "sha256:5273b9f0b5835ff0350c0828faea623c68bfa65b792720c453e22b25cc72930f", - "sha256:530d548084c4a9f7a16ed4a294d459b4f229db50df689bfe92027452452943a0", - "sha256:530e8cebeea0d76bdcf93357aa5e41336f48c3dc709ac52da2bb167c5b8271d9", - "sha256:54fae94be3d75f3e573c9a1b5402dc593de19377013c9a0e4285e3d402dd3a2a", - "sha256:572d7c822caf521f0525ba1bce1a622a0b85cf47ffbdae6c9c19e3b5ac3c4389", - "sha256:58c948d0d086229efc484fe2f30c2d382c86720f55cd9bc33591774348ad44e0", - "sha256:5d11595abf8dd942a77883a39d81433739b287b6aa71620f15164f8096221b30", - "sha256:5f8ddd609f9e1af8c7bd6e2aca279c931aefecd148a14402d4e368f3171769fd", - "sha256:5feb91325bbceade6afab43eb3b508c63ee53579fe896c77137ded51c6b6958e", - "sha256:60c74963d8350241a79cb8feea80e54d518f72c26db618862a8f53e5023deaf9", - "sha256:613f19aa6e082cf96e17e3ffd89383343d0d589abda756b7764cf78361fd41dc", - "sha256:659a1e1b500fac8f2779dd9e1570464e012f43e580371470b45277a27baa7532", - "sha256:695f5c2823691a25f17bc5d5ffe79fa90972cc34b002ac6c843bb8a1720e950d", - "sha256:69dd852c2f0ad631b8b60cfbe25a28c0058a894de5abb566619c205ce0550eae", - "sha256:6cceb5473417d28edd20c6c984ab6fee6c6267d38d906823ebfe20b03d607dc2", - "sha256:71be7e0e01753a89cf024abf7ecb6bca2c81738ead80d43004d9b5e3f1244e64", - "sha256:74119174722c4349af9708993118581686f343adc1c8c9c007d59be90d077f3f", - "sha256:74a2e659c7ecbc73562e2a15e05039f1e22c75b7c7618b4b574a3ea9118d1557", - "sha256:7504e9b7dc05f99a9bbb4525c67a2c155073b44d720470a148b34166a69c054e", - "sha256:79090741d842f564b1b2827c0b82d846405b744d31e84f18d7a7b41c20e473ff", - "sha256:7a6967aaf043bceabab5412ed6bd6bd26603dae84d5cb75bf8d9a74a4959d398", - "sha256:7bda6eebafd42133efdca535b04ccb338ab29467b3f7bf79569883676fc628db", - "sha256:7edbed096e4a4798710ed6bc75dcaa2a21b68b6c356553ac4823c3658d53743a", - "sha256:7f9019c9cb613f084481bd6a100b12e1547cf2efe362d873c2e31e4035a6fa43", - "sha256:802168e03fba8bbc5ce0d866d589e4b1ca751d06edee69f7f3a19c5a9fe6b597", - "sha256:80d0a5615143c0b3225e5e3ef22c8d5d51f3f72ce0ea6fb84c943546c7b25b6c", - "sha256:82060f995ab5003a2d6e0f4ad29065b7672b6593c8c63559beefe5b443242c3e", - "sha256:836ab36280f21fc1a03c99cd05c6b7af70d2697e374c7af0b61ed271401a72a2", - "sha256:8761ac29b6c81574724322a554605608a9960769ea83d2c73e396f3df896ad54", - "sha256:87725cfb1a4f1f8c2fc9890ae2f42094120f4b44db9360be5d99a4c6b0e03a9e", - "sha256:899d28f422116b08be5118ef350c292b36fc15ec2daeb9ea987c89281c7bb5c4", - "sha256:8bc5f0687d796c05b1e28ab0d38a50e6309906ee09375dd3aff6a9c09dd6e8f4", - "sha256:8bea55c4eef25b0b19a0337dc4e3f9a15b00d569c77211fa8cde38684f234fb7", - "sha256:8e5a94886bedca0f9b78fecd6afb6629142fd2605aa70a125d49f4edc6037ee6", - "sha256:90ca27cd8da8118b18a52d5f547859cc1f8354a00cd1e8e5120df3e30d6279e5", - "sha256:92734d4d8d187a354a556626c221cd1a892a4e0802ccb2af432a1d85ec012194", - "sha256:947cf925bc916d90adba35a64c82aace04fa39b46b52d4630ece166655905a69", - "sha256:95b52c68d64c1878818687a473a10547b3292e82b6f6fe483808fb1468e2f52f", - "sha256:97d0235baafca5f2b09cf332cc275f021e694e8362c6bb9c96fc9a0eb74fc316", - "sha256:9ca4c0b502ab399ef89248a2c84c54954f77a070f28e546a85e91da627d1301e", - "sha256:9cc4fc6c196d6a8b76629a70ddfcd4635a6898756e2d9cac5565cf0654605d73", - "sha256:9cc6e6d9e571d2f863fa77700701dae73ed5f78881efc8b3f9a4398772ff53e8", - "sha256:a056d1ad2633548ca18ffa2f85c202cfb48b68615129143915b8dc72a806a923", - "sha256:a26611d9987b230566f24a0a125f17fe0de6a6aff9f25c9f564aaa2721a5fb88", - "sha256:a4474d924a47185a06411e0064b803c68be044be2d60e50e8bddcc2649957c1f", - "sha256:a4ea868bc28109052790eb2b52a9ab33f3aa7adc02f96673526ff47419490e21", - "sha256:a9e68c9d88823b274cf1e72f28cb5dc89c990edf430b0bfd3e2fb0785bfeabf4", - "sha256:aa9cccf4a44b9b62d8ba8b4dd06c649ba683e4bf04eea606d2e94cfc2d6ff4d6", - "sha256:ab30e5e3e706e3063bc6de96b118688cb10396b70bb9864a430f67df98c61ecc", - "sha256:ac2393c73378fea4e52aa56285a3d64be50f1a12395afef9cce47772f60334c2", - "sha256:ad8faf8df23f0378c6d527d8b0b15ea4a2e23c89376877c598c4870d1b2c7866", - "sha256:b35b200d6a71b9839a46b9b7fff66b6638bb52fc9658aa58796b0326595d3021", - "sha256:b3694e3f87f8ac7ce279d4355645b3c878d24d1424581b46282f24b92f5a4ae2", - "sha256:b4ff1d35e8c5bd078be89349b6f3a845128e685e751b6ea1169cf2160b344c4d", - "sha256:bbc8c8650c6e51041ad1be191742b8b421d05bbd3410f43fa2a00c8db87678e8", - "sha256:bc72863f4d9aba2e8fd9085e63548a324ba706d2ea2c83b260da08a59b9482de", - "sha256:bf625105bb9eef28a56a943fec8c8a98aeb80e7d7db99bd3c388137e6eb2d237", - "sha256:c2274ca724536f173122f36c98ce188fd24ce3dad886ec2b7af859518ce008a4", - "sha256:c45a03a4c69820a399f1dda9e1d8fbf3562eda46e7720458180302021b08f778", - "sha256:c8ae56368f8cc97c7e40a7ee18e1cedaf8e780cd8bc5ed5ac8b81f238614facb", - "sha256:c907cdc8109f6c619e6254212e794d6548373cc40e1ec75e6e3823d9135d29cc", - "sha256:ca0276464d148c72defa8bb4390cce01b4a0e425f3b50d1435aa6d7a18107602", - "sha256:cd5e2801c89992ed8c0a3f0293ae83c159a60d9a5d685005383ef4caca77f2c4", - "sha256:d08ec48f0a1c48d75d0356cea971921848fb620fdeba805b28f937e90691209f", - "sha256:d1a2ee9c1499fc8f86f4521f27a973c914b211ffa87322f4ee33bb35392da2c5", - "sha256:d5f5d1e9def3405f60e3ca8232d56f35c98fb7bf581efcc60051ebf53cb8b611", - "sha256:d60377dce4511655582e300dc1e5a5f24ba0cb229005a1d5c8d0cb72bb758ab8", - "sha256:d73beaac5e90173ac3deb9928a74763a6d230f494e4bfb422c217a0ad8e629bf", - "sha256:d7de2637729c67d67cf87614b566626057e95c303bc0a55ffe391f5205e7003d", - "sha256:dad6e0f2e481fffdcf776d10ebee25e0ef89f16d691f1e5dee4b586375fdc64b", - "sha256:dda86aba335c902b6149a02a55b38e96287157e609200811837678214ba2b1db", - "sha256:df01808ee470038c3f8dc4f48620df7225c49c2d6639e38f96e6d6ac6e6f7b0e", - "sha256:e1f6e2f00a6b8edb562826e4632e26d063ac10307e80f7461f7de3ad8ef3f077", - "sha256:e25369dc110d58ddf29b949377a93e0716d72a24f62bad72b2b39f155949c1fd", - "sha256:e3c701e954abf6fc03a49f7c579cc80c2c6cc52525340ca3186c41d3f33482ef", - "sha256:e5bcc1a1ae744e0bb59641171ae53743760130600da8db48cbb6e4918e186e4e", - "sha256:e68c14b04827dd76dcbd1aeea9e604e3e4b78322d8faf2f8132c7138efa340a8", - "sha256:e8aeb10fcbe92767f0fa69ad5a72deca50d0dca07fbde97848997d778a50c9fe", - "sha256:e985a16ff513596f217cee86c21371b8cd011c0f6f056d0920aa2d926c544058", - "sha256:ecbbd45615a6885fe3240eb9db73b9e62518b611850fdf8ab08bd56de7ad2b17", - "sha256:ee4ec14bc1680d6b0afab9aea2ef27e26d2024f18b24a2d7155a52b60da7e833", - "sha256:ef5960d965e67165d75b7c7ffc60a83ec5abfc5c11b764ec13ea54fbef8b4421", - "sha256:f0cdaecd4c953bfae0b6bb64910aaaca5a424ad9c72d85cb88417bb9814f7550", - "sha256:f1ce721c8a7dfec21fcbdfe04e8f68174183cf4e8188e0645e92aa23985c57ff", - "sha256:f50498891691e0864dc3da965f340fada0771f6142a378083dc4608f4ea513e2", - "sha256:f5ea69428fa1b49573eef0cc44a1d43bebd45ad0c611eb7d7eac760c7ae771bc", - "sha256:f61aa92e4aad0be58eb6eb4e0c21acf32cf8065f4b2cae5665da756c4ceef982", - "sha256:f6e4333fb15c83f7d1482a76d45a0818897b3d33f00efd215528ff7c51b8e35d", - "sha256:f820f24b09e3e779fe84c3c456cb4108a7aa639b0d1f02c28046e11bfcd088ed", - "sha256:f98059e4fcd3e3e4e2d632b7cf81c2faae96c43c60b569e9c621468082f1d104", - "sha256:fcce033e4021347d80ed9c66dcf1e7b1546319834b74445f561d2e2221de5659" - ], - "markers": "python_version >= '3.7'", - "version": "==3.4.6" - }, "colorama": { "hashes": [ "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", @@ -342,14 +120,6 @@ ], "version": "==0.4.0" }, - "docutils": { - "hashes": [ - "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", - "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de" - ], - "markers": "python_version >= '3.9'", - "version": "==0.22.4" - }, "filelock": { "hashes": [ "sha256:b64ece2b38f4ca29dd3e810287aa8c48182bbecd1ae6e9ae126c9b35f1382694", @@ -358,119 +128,6 @@ "markers": "python_version >= '3.10'", "version": "==3.25.2" }, - "id": { - "hashes": [ - "sha256:d0732d624fb46fd4e7bc4e5152f00214450953b9e772c182c1c22964def1a069", - "sha256:f5ec41ed2629a508f5d0988eda142e190c9c6da971100612c4de9ad9f9b237ca" - ], - "markers": "python_version >= '3.9'", - "version": "==1.6.1" - }, - "idna": { - "hashes": [ - "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", - "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" - ], - "markers": "python_version >= '3.8'", - "version": "==3.11" - }, - "jaraco.classes": { - "hashes": [ - "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", - "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790" - ], - "markers": "python_version >= '3.8'", - "version": "==3.4.0" - }, - "jaraco.context": { - "hashes": [ - "sha256:0df6a0287258f3e364072c3e40d5411b20cafa30cb28c4839d24319cecf9f808", - "sha256:bc046b2dc94f1e5532bd02402684414575cc11f565d929b6563125deb0a6e581" - ], - "markers": "python_version >= '3.9'", - "version": "==6.1.1" - }, - "jaraco.functools": { - "hashes": [ - "sha256:9eec1e36f45c818d9bf307c8948eb03b2b56cd44087b3cdc989abca1f20b9176", - "sha256:da21933b0417b89515562656547a77b4931f98176eb173644c0d35032a33d6bb" - ], - "markers": "python_version >= '3.9'", - "version": "==4.4.0" - }, - "jeepney": { - "hashes": [ - "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", - "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732" - ], - "markers": "python_version >= '3.7'", - "version": "==0.9.0" - }, - "keyring": { - "hashes": [ - "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", - "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b" - ], - "markers": "python_version >= '3.9'", - "version": "==25.7.0" - }, - "markdown-it-py": { - "hashes": [ - "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", - "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3" - ], - "markers": "python_version >= '3.10'", - "version": "==4.0.0" - }, - "mdurl": { - "hashes": [ - "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", - "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba" - ], - "markers": "python_version >= '3.7'", - "version": "==0.1.2" - }, - "more-itertools": { - "hashes": [ - "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", - "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd" - ], - "markers": "python_version >= '3.9'", - "version": "==10.8.0" - }, - "nh3": { - "hashes": [ - "sha256:0d5eb734a78ac364af1797fef718340a373f626a9ff6b4fb0b4badf7927e7b81", - "sha256:185ed41b88c910b9ca8edc89ca3b4be688a12cb9de129d84befa2f74a0039fee", - "sha256:1ef87f8e916321a88b45f2d597f29bd56e560ed4568a50f0f1305afab86b7189", - "sha256:21a63ccb18ddad3f784bb775955839b8b80e347e597726f01e43ca1abcc5c808", - "sha256:21b058cd20d9f0919421a820a2843fdb5e1749c0bf57a6247ab8f4ba6723c9fc", - "sha256:24769a428e9e971e4ccfb24628f83aaa7dc3c8b41b130c8ddc1835fa1c924489", - "sha256:2efd17c0355d04d39e6d79122b42662277ac10a17ea48831d90b46e5ef7e4fc0", - "sha256:3a62b8ae7c235481715055222e54c682422d0495a5c73326807d4e44c5d14691", - "sha256:45fe0d6a607264910daec30360c8a3b5b1500fd832d21b2da608256287bcb92d", - "sha256:4c730617bdc15d7092dcc0469dc2826b914c8f874996d105b4bc3842a41c1cd9", - "sha256:52e973cb742e95b9ae1b35822ce23992428750f4b46b619fe86eba4205255b30", - "sha256:5a4b2c1f3e6f3cbe7048e17f4fefad3f8d3e14cc0fd08fb8599e0d5653f6b181", - "sha256:5bc1d4b30ba1ba896669d944b6003630592665974bd11a3dc2f661bde92798a7", - "sha256:90126a834c18af03bfd6ff9a027bfa6bbf0e238527bc780a24de6bd7cc1041e2", - "sha256:92a958e6f6d0100e025a5686aafd67e3c98eac67495728f8bb64fbeb3e474493", - "sha256:9ed40cf8449a59a03aa465114fedce1ff7ac52561688811d047917cc878b19ca", - "sha256:a446eae598987f49ee97ac2f18eafcce4e62e7574bd1eb23782e4702e54e217d", - "sha256:b50c3770299fb2a7c1113751501e8878d525d15160a4c05194d7fe62b758aad8", - "sha256:b7a18ee057761e455d58b9d31445c3e4b2594cff4ddb84d2e331c011ef46f462", - "sha256:b838e619f483531483d26d889438e53a880510e832d2aafe73f93b7b1ac2bce2", - "sha256:e8ee96156f7dfc6e30ecda650e480c5ae0a7d38f0c6fafc3c1c655e2500421d9", - "sha256:e974850b131fdffa75e7ad8e0d9c7a855b96227b093417fdf1bd61656e530f37", - "sha256:e98fa3dbfd54e25487e36ba500bc29bca3a4cab4ffba18cfb1a35a2d02624297", - "sha256:f433a2dd66545aad4a720ad1b2150edcdca75bfff6f4e6f378ade1ec138d5e77", - "sha256:f4400a73c2a62859e769f9d36d1b5a7a5c65c4179d1dddd2f6f3095b2db0cbfc", - "sha256:f508ddd4e2433fdcb78c790fc2d24e3a349ba775e5fa904af89891321d4844a3", - "sha256:fc305a2264868ec8fa16548296f803d8fd9c1fa66cd28b88b605b1bd06667c0b" - ], - "markers": "python_version >= '3.8'", - "version": "==0.3.3" - }, "packaging": { "hashes": [ "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", @@ -544,54 +201,6 @@ "markers": "python_version >= '3.8'", "version": "==1.1.3" }, - "readme-renderer": { - "hashes": [ - "sha256:2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151", - "sha256:8712034eabbfa6805cacf1402b4eeb2a73028f72d1166d6f5cb7f9c047c5d1e1" - ], - "markers": "python_version >= '3.9'", - "version": "==44.0" - }, - "requests": { - "hashes": [ - "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", - "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf" - ], - "markers": "python_version >= '3.9'", - "version": "==2.32.5" - }, - "requests-toolbelt": { - "hashes": [ - "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", - "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.0.0" - }, - "rfc3986": { - "hashes": [ - "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", - "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c" - ], - "markers": "python_version >= '3.7'", - "version": "==2.0.0" - }, - "rich": { - "hashes": [ - "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d", - "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b" - ], - "markers": "python_full_version >= '3.8.0'", - "version": "==14.3.3" - }, - "secretstorage": { - "hashes": [ - "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", - "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be" - ], - "markers": "python_version >= '3.10'", - "version": "==3.5.0" - }, "setuptools": { "hashes": [ "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", @@ -626,23 +235,6 @@ "markers": "python_version >= '3.10'", "version": "==4.49.1" }, - "twine": { - "hashes": [ - "sha256:418ebf08ccda9a8caaebe414433b0ba5e25eb5e4a927667122fbe8f829f985d8", - "sha256:e5ed0d2fd70c9959770dce51c8f39c8945c574e18173a7b81802dab51b4b75cf" - ], - "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==6.2.0" - }, - "urllib3": { - "hashes": [ - "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", - "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4" - ], - "markers": "python_version >= '3.9'", - "version": "==2.6.3" - }, "virtualenv": { "hashes": [ "sha256:1720dc3a62ef5b443092e3f499228599045d7fea4c79199770499df8becf9098", From 3ee3f1532610fdea091e6c01c0c288c8789f1a32 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 21 Mar 2026 17:13:02 +0100 Subject: [PATCH 63/92] chore: update build --- Pipfile | 11 +- Pipfile.lock | 712 ++++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 2 +- 3 files changed, 707 insertions(+), 18 deletions(-) diff --git a/Pipfile b/Pipfile index 673ee77..f6b9a8b 100644 --- a/Pipfile +++ b/Pipfile @@ -9,15 +9,18 @@ build = "*" pipenv = "*" tox = "*" twine = "*" +pypi-attestations = "*" [requires] python_version = "3" [scripts] -"build" = "python3 -m build" -"test-static" = "tox run -m static" -"test-unit" = "tox run -m unit" -"test-integration" = "tox run -m integration" +"dist" = "python3 -m build" +"dist:attestations" = "python3 -m pypi_attestations sign dist/*" +"dist:publish" = "python3 -m twine upload --sign --repository tiararodney dist/*" +"test:static" = "tox run -m static" +"test:unit" = "tox run -m unit" +"test:integration" = "tox run -m integration" [packages] "byteb4rb1e.utils" = {file = ".", editable = true} diff --git a/Pipfile.lock b/Pipfile.lock index 6702742..ae31cf6 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "5341a513f59952743e55c6f51253eecbcec1a2856a69e159f64193f59c081d4c" + "sha256": "edaf04f6c9d148cb35296f633912e3b45bfba2ccd30848a824495b4f5ba085ee" }, "pipfile-spec": 6, "requires": { @@ -22,6 +22,14 @@ } }, "develop": { + "annotated-types": { + "hashes": [ + "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", + "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89" + ], + "markers": "python_version >= '3.8'", + "version": "==0.7.0" + }, "build": { "hashes": [ "sha256:6a07c1b8eb6f2b311b96fcbdbce5dab5fe637ffda0fd83c9cac622e927501596", @@ -31,9 +39,6 @@ "markers": "python_version >= '3.9'", "version": "==1.4.0" }, - "byteb4rb1e.utils": { - "version": "==0.1.dev92+g4cdf35702.d20260315" - }, "cachetools": { "hashes": [ "sha256:0cd042c24377200c1dcd225f8b7b12b0ca53cc2c961b43757e774ebe190fd990", @@ -50,6 +55,231 @@ "markers": "python_version >= '3.7'", "version": "==2026.2.25" }, + "cffi": { + "hashes": [ + "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", + "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", + "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", + "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", + "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", + "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2", + "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", + "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", + "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65", + "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", + "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", + "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", + "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", + "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a", + "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", + "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", + "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", + "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", + "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", + "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", + "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", + "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", + "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", + "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", + "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165", + "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", + "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", + "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c", + "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", + "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", + "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", + "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", + "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63", + "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", + "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", + "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", + "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", + "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", + "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", + "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", + "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", + "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", + "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", + "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", + "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", + "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", + "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322", + "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", + "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", + "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", + "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", + "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", + "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", + "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", + "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", + "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", + "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", + "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", + "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", + "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", + "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9", + "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", + "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", + "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", + "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", + "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", + "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f", + "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", + "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", + "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", + "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", + "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", + "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", + "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", + "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", + "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", + "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7", + "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", + "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534", + "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", + "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", + "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", + "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", + "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf" + ], + "markers": "python_version >= '3.9'", + "version": "==2.0.0" + }, + "charset-normalizer": { + "hashes": [ + "sha256:06a7e86163334edfc5d20fe104db92fcd666e5a5df0977cb5680a506fe26cc8e", + "sha256:0c173ce3a681f309f31b87125fecec7a5d1347261ea11ebbb856fa6006b23c8c", + "sha256:0e28d62a8fc7a1fa411c43bd65e346f3bce9716dc51b897fbe930c5987b402d5", + "sha256:0e901eb1049fdb80f5bd11ed5ea1e498ec423102f7a9b9e4645d5b8204ff2815", + "sha256:11afb56037cbc4b1555a34dd69151e8e069bee82e613a73bef6e714ce733585f", + "sha256:150b8ce8e830eb7ccb029ec9ca36022f756986aaaa7956aad6d9ec90089338c0", + "sha256:172985e4ff804a7ad08eebec0a1640ece87ba5041d565fff23c8f99c1f389484", + "sha256:197c1a244a274bb016dd8b79204850144ef77fe81c5b797dc389327adb552407", + "sha256:1ae6b62897110aa7c79ea2f5dd38d1abca6db663687c0b1ad9aed6f6bae3d9d6", + "sha256:1cf0a70018692f85172348fe06d3a4b63f94ecb055e13a00c644d368eb82e5b8", + "sha256:1ed80ff870ca6de33f4d953fda4d55654b9a2b340ff39ab32fa3adbcd718f264", + "sha256:22c6f0c2fbc31e76c3b8a86fba1a56eda6166e238c29cdd3d14befdb4a4e4815", + "sha256:231d4da14bcd9301310faf492051bee27df11f2bc7549bc0bb41fef11b82daa2", + "sha256:259695e2ccc253feb2a016303543d691825e920917e31f894ca1a687982b1de4", + "sha256:2a24157fa36980478dd1770b585c0f30d19e18f4fb0c47c13aa568f871718579", + "sha256:2b1a63e8224e401cafe7739f77efd3f9e7f5f2026bda4aead8e59afab537784f", + "sha256:2bd9d128ef93637a5d7a6af25363cf5dec3fa21cf80e68055aad627f280e8afa", + "sha256:2e1d8ca8611099001949d1cdfaefc510cf0f212484fe7c565f735b68c78c3c95", + "sha256:2ef7fedc7a6ecbe99969cd09632516738a97eeb8bd7258bf8a0f23114c057dab", + "sha256:2f7fdd9b6e6c529d6a2501a2d36b240109e78a8ceaef5687cfcfa2bbe671d297", + "sha256:30f445ae60aad5e1f8bdbb3108e39f6fbc09f4ea16c815c66578878325f8f15a", + "sha256:31215157227939b4fb3d740cd23fe27be0439afef67b785a1eb78a3ae69cba9e", + "sha256:34315ff4fc374b285ad7f4a0bf7dcbfe769e1b104230d40f49f700d4ab6bbd84", + "sha256:3516bbb8d42169de9e61b8520cbeeeb716f12f4ecfe3fd30a9919aa16c806ca8", + "sha256:3778fd7d7cd04ae8f54651f4a7a0bd6e39a0cf20f801720a4c21d80e9b7ad6b0", + "sha256:39f5068d35621da2881271e5c3205125cc456f54e9030d3f723288c873a71bf9", + "sha256:404a1e552cf5b675a87f0651f8b79f5f1e6fd100ee88dc612f89aa16abd4486f", + "sha256:419a9d91bd238052642a51938af8ac05da5b3343becde08d5cdeab9046df9ee1", + "sha256:423fb7e748a08f854a08a222b983f4df1912b1daedce51a72bd24fe8f26a1843", + "sha256:4482481cb0572180b6fd976a4d5c72a30263e98564da68b86ec91f0fe35e8565", + "sha256:461598cd852bfa5a61b09cae2b1c02e2efcd166ee5516e243d540ac24bfa68a7", + "sha256:47955475ac79cc504ef2704b192364e51d0d473ad452caedd0002605f780101c", + "sha256:48696db7f18afb80a068821504296eb0787d9ce239b91ca15059d1d3eaacf13b", + "sha256:4be9f4830ba8741527693848403e2c457c16e499100963ec711b1c6f2049b7c7", + "sha256:4d1d02209e06550bdaef34af58e041ad71b88e624f5d825519da3a3308e22687", + "sha256:4f41da960b196ea355357285ad1316a00099f22d0929fe168343b99b254729c9", + "sha256:517ad0e93394ac532745129ceabdf2696b609ec9f87863d337140317ebce1c14", + "sha256:51fb3c322c81d20567019778cb5a4a6f2dc1c200b886bc0d636238e364848c89", + "sha256:5273b9f0b5835ff0350c0828faea623c68bfa65b792720c453e22b25cc72930f", + "sha256:530d548084c4a9f7a16ed4a294d459b4f229db50df689bfe92027452452943a0", + "sha256:530e8cebeea0d76bdcf93357aa5e41336f48c3dc709ac52da2bb167c5b8271d9", + "sha256:54fae94be3d75f3e573c9a1b5402dc593de19377013c9a0e4285e3d402dd3a2a", + "sha256:572d7c822caf521f0525ba1bce1a622a0b85cf47ffbdae6c9c19e3b5ac3c4389", + "sha256:58c948d0d086229efc484fe2f30c2d382c86720f55cd9bc33591774348ad44e0", + "sha256:5d11595abf8dd942a77883a39d81433739b287b6aa71620f15164f8096221b30", + "sha256:5f8ddd609f9e1af8c7bd6e2aca279c931aefecd148a14402d4e368f3171769fd", + "sha256:5feb91325bbceade6afab43eb3b508c63ee53579fe896c77137ded51c6b6958e", + "sha256:60c74963d8350241a79cb8feea80e54d518f72c26db618862a8f53e5023deaf9", + "sha256:613f19aa6e082cf96e17e3ffd89383343d0d589abda756b7764cf78361fd41dc", + "sha256:659a1e1b500fac8f2779dd9e1570464e012f43e580371470b45277a27baa7532", + "sha256:695f5c2823691a25f17bc5d5ffe79fa90972cc34b002ac6c843bb8a1720e950d", + "sha256:69dd852c2f0ad631b8b60cfbe25a28c0058a894de5abb566619c205ce0550eae", + "sha256:6cceb5473417d28edd20c6c984ab6fee6c6267d38d906823ebfe20b03d607dc2", + "sha256:71be7e0e01753a89cf024abf7ecb6bca2c81738ead80d43004d9b5e3f1244e64", + "sha256:74119174722c4349af9708993118581686f343adc1c8c9c007d59be90d077f3f", + "sha256:74a2e659c7ecbc73562e2a15e05039f1e22c75b7c7618b4b574a3ea9118d1557", + "sha256:7504e9b7dc05f99a9bbb4525c67a2c155073b44d720470a148b34166a69c054e", + "sha256:79090741d842f564b1b2827c0b82d846405b744d31e84f18d7a7b41c20e473ff", + "sha256:7a6967aaf043bceabab5412ed6bd6bd26603dae84d5cb75bf8d9a74a4959d398", + "sha256:7bda6eebafd42133efdca535b04ccb338ab29467b3f7bf79569883676fc628db", + "sha256:7edbed096e4a4798710ed6bc75dcaa2a21b68b6c356553ac4823c3658d53743a", + "sha256:7f9019c9cb613f084481bd6a100b12e1547cf2efe362d873c2e31e4035a6fa43", + "sha256:802168e03fba8bbc5ce0d866d589e4b1ca751d06edee69f7f3a19c5a9fe6b597", + "sha256:80d0a5615143c0b3225e5e3ef22c8d5d51f3f72ce0ea6fb84c943546c7b25b6c", + "sha256:82060f995ab5003a2d6e0f4ad29065b7672b6593c8c63559beefe5b443242c3e", + "sha256:836ab36280f21fc1a03c99cd05c6b7af70d2697e374c7af0b61ed271401a72a2", + "sha256:8761ac29b6c81574724322a554605608a9960769ea83d2c73e396f3df896ad54", + "sha256:87725cfb1a4f1f8c2fc9890ae2f42094120f4b44db9360be5d99a4c6b0e03a9e", + "sha256:899d28f422116b08be5118ef350c292b36fc15ec2daeb9ea987c89281c7bb5c4", + "sha256:8bc5f0687d796c05b1e28ab0d38a50e6309906ee09375dd3aff6a9c09dd6e8f4", + "sha256:8bea55c4eef25b0b19a0337dc4e3f9a15b00d569c77211fa8cde38684f234fb7", + "sha256:8e5a94886bedca0f9b78fecd6afb6629142fd2605aa70a125d49f4edc6037ee6", + "sha256:90ca27cd8da8118b18a52d5f547859cc1f8354a00cd1e8e5120df3e30d6279e5", + "sha256:92734d4d8d187a354a556626c221cd1a892a4e0802ccb2af432a1d85ec012194", + "sha256:947cf925bc916d90adba35a64c82aace04fa39b46b52d4630ece166655905a69", + "sha256:95b52c68d64c1878818687a473a10547b3292e82b6f6fe483808fb1468e2f52f", + "sha256:97d0235baafca5f2b09cf332cc275f021e694e8362c6bb9c96fc9a0eb74fc316", + "sha256:9ca4c0b502ab399ef89248a2c84c54954f77a070f28e546a85e91da627d1301e", + "sha256:9cc4fc6c196d6a8b76629a70ddfcd4635a6898756e2d9cac5565cf0654605d73", + "sha256:9cc6e6d9e571d2f863fa77700701dae73ed5f78881efc8b3f9a4398772ff53e8", + "sha256:a056d1ad2633548ca18ffa2f85c202cfb48b68615129143915b8dc72a806a923", + "sha256:a26611d9987b230566f24a0a125f17fe0de6a6aff9f25c9f564aaa2721a5fb88", + "sha256:a4474d924a47185a06411e0064b803c68be044be2d60e50e8bddcc2649957c1f", + "sha256:a4ea868bc28109052790eb2b52a9ab33f3aa7adc02f96673526ff47419490e21", + "sha256:a9e68c9d88823b274cf1e72f28cb5dc89c990edf430b0bfd3e2fb0785bfeabf4", + "sha256:aa9cccf4a44b9b62d8ba8b4dd06c649ba683e4bf04eea606d2e94cfc2d6ff4d6", + "sha256:ab30e5e3e706e3063bc6de96b118688cb10396b70bb9864a430f67df98c61ecc", + "sha256:ac2393c73378fea4e52aa56285a3d64be50f1a12395afef9cce47772f60334c2", + "sha256:ad8faf8df23f0378c6d527d8b0b15ea4a2e23c89376877c598c4870d1b2c7866", + "sha256:b35b200d6a71b9839a46b9b7fff66b6638bb52fc9658aa58796b0326595d3021", + "sha256:b3694e3f87f8ac7ce279d4355645b3c878d24d1424581b46282f24b92f5a4ae2", + "sha256:b4ff1d35e8c5bd078be89349b6f3a845128e685e751b6ea1169cf2160b344c4d", + "sha256:bbc8c8650c6e51041ad1be191742b8b421d05bbd3410f43fa2a00c8db87678e8", + "sha256:bc72863f4d9aba2e8fd9085e63548a324ba706d2ea2c83b260da08a59b9482de", + "sha256:bf625105bb9eef28a56a943fec8c8a98aeb80e7d7db99bd3c388137e6eb2d237", + "sha256:c2274ca724536f173122f36c98ce188fd24ce3dad886ec2b7af859518ce008a4", + "sha256:c45a03a4c69820a399f1dda9e1d8fbf3562eda46e7720458180302021b08f778", + "sha256:c8ae56368f8cc97c7e40a7ee18e1cedaf8e780cd8bc5ed5ac8b81f238614facb", + "sha256:c907cdc8109f6c619e6254212e794d6548373cc40e1ec75e6e3823d9135d29cc", + "sha256:ca0276464d148c72defa8bb4390cce01b4a0e425f3b50d1435aa6d7a18107602", + "sha256:cd5e2801c89992ed8c0a3f0293ae83c159a60d9a5d685005383ef4caca77f2c4", + "sha256:d08ec48f0a1c48d75d0356cea971921848fb620fdeba805b28f937e90691209f", + "sha256:d1a2ee9c1499fc8f86f4521f27a973c914b211ffa87322f4ee33bb35392da2c5", + "sha256:d5f5d1e9def3405f60e3ca8232d56f35c98fb7bf581efcc60051ebf53cb8b611", + "sha256:d60377dce4511655582e300dc1e5a5f24ba0cb229005a1d5c8d0cb72bb758ab8", + "sha256:d73beaac5e90173ac3deb9928a74763a6d230f494e4bfb422c217a0ad8e629bf", + "sha256:d7de2637729c67d67cf87614b566626057e95c303bc0a55ffe391f5205e7003d", + "sha256:dad6e0f2e481fffdcf776d10ebee25e0ef89f16d691f1e5dee4b586375fdc64b", + "sha256:dda86aba335c902b6149a02a55b38e96287157e609200811837678214ba2b1db", + "sha256:df01808ee470038c3f8dc4f48620df7225c49c2d6639e38f96e6d6ac6e6f7b0e", + "sha256:e1f6e2f00a6b8edb562826e4632e26d063ac10307e80f7461f7de3ad8ef3f077", + "sha256:e25369dc110d58ddf29b949377a93e0716d72a24f62bad72b2b39f155949c1fd", + "sha256:e3c701e954abf6fc03a49f7c579cc80c2c6cc52525340ca3186c41d3f33482ef", + "sha256:e5bcc1a1ae744e0bb59641171ae53743760130600da8db48cbb6e4918e186e4e", + "sha256:e68c14b04827dd76dcbd1aeea9e604e3e4b78322d8faf2f8132c7138efa340a8", + "sha256:e8aeb10fcbe92767f0fa69ad5a72deca50d0dca07fbde97848997d778a50c9fe", + "sha256:e985a16ff513596f217cee86c21371b8cd011c0f6f056d0920aa2d926c544058", + "sha256:ecbbd45615a6885fe3240eb9db73b9e62518b611850fdf8ab08bd56de7ad2b17", + "sha256:ee4ec14bc1680d6b0afab9aea2ef27e26d2024f18b24a2d7155a52b60da7e833", + "sha256:ef5960d965e67165d75b7c7ffc60a83ec5abfc5c11b764ec13ea54fbef8b4421", + "sha256:f0cdaecd4c953bfae0b6bb64910aaaca5a424ad9c72d85cb88417bb9814f7550", + "sha256:f1ce721c8a7dfec21fcbdfe04e8f68174183cf4e8188e0645e92aa23985c57ff", + "sha256:f50498891691e0864dc3da965f340fada0771f6142a378083dc4608f4ea513e2", + "sha256:f5ea69428fa1b49573eef0cc44a1d43bebd45ad0c611eb7d7eac760c7ae771bc", + "sha256:f61aa92e4aad0be58eb6eb4e0c21acf32cf8065f4b2cae5665da756c4ceef982", + "sha256:f6e4333fb15c83f7d1482a76d45a0818897b3d33f00efd215528ff7c51b8e35d", + "sha256:f820f24b09e3e779fe84c3c456cb4108a7aa639b0d1f02c28046e11bfcd088ed", + "sha256:f98059e4fcd3e3e4e2d632b7cf81c2faae96c43c60b569e9c621468082f1d104", + "sha256:fcce033e4021347d80ed9c66dcf1e7b1546319834b74445f561d2e2221de5659" + ], + "markers": "python_version >= '3.7'", + "version": "==3.4.6" + }, "colorama": { "hashes": [ "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", @@ -120,6 +350,30 @@ ], "version": "==0.4.0" }, + "dnspython": { + "hashes": [ + "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", + "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f" + ], + "markers": "python_version >= '3.10'", + "version": "==2.8.0" + }, + "docutils": { + "hashes": [ + "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", + "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de" + ], + "markers": "python_version >= '3.9'", + "version": "==0.22.4" + }, + "email-validator": { + "hashes": [ + "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4", + "sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426" + ], + "markers": "python_version >= '3.8'", + "version": "==2.3.0" + }, "filelock": { "hashes": [ "sha256:b64ece2b38f4ca29dd3e810287aa8c48182bbecd1ae6e9ae126c9b35f1382694", @@ -128,6 +382,119 @@ "markers": "python_version >= '3.10'", "version": "==3.25.2" }, + "id": { + "hashes": [ + "sha256:d0732d624fb46fd4e7bc4e5152f00214450953b9e772c182c1c22964def1a069", + "sha256:f5ec41ed2629a508f5d0988eda142e190c9c6da971100612c4de9ad9f9b237ca" + ], + "markers": "python_version >= '3.9'", + "version": "==1.6.1" + }, + "idna": { + "hashes": [ + "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", + "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" + ], + "markers": "python_version >= '3.8'", + "version": "==3.11" + }, + "jaraco.classes": { + "hashes": [ + "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", + "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790" + ], + "markers": "python_version >= '3.8'", + "version": "==3.4.0" + }, + "jaraco.context": { + "hashes": [ + "sha256:bf8150b79a2d5d91ae48629d8b427a8f7ba0e1097dd6202a9059f29a36379535", + "sha256:f1a6c9d391e661cc5b8d39861ff077a7dc24dc23833ccee564b234b81c82dfe3" + ], + "markers": "python_version >= '3.10'", + "version": "==6.1.2" + }, + "jaraco.functools": { + "hashes": [ + "sha256:9eec1e36f45c818d9bf307c8948eb03b2b56cd44087b3cdc989abca1f20b9176", + "sha256:da21933b0417b89515562656547a77b4931f98176eb173644c0d35032a33d6bb" + ], + "markers": "python_version >= '3.9'", + "version": "==4.4.0" + }, + "jeepney": { + "hashes": [ + "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", + "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732" + ], + "markers": "python_version >= '3.7'", + "version": "==0.9.0" + }, + "keyring": { + "hashes": [ + "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", + "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b" + ], + "markers": "python_version >= '3.9'", + "version": "==25.7.0" + }, + "markdown-it-py": { + "hashes": [ + "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", + "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3" + ], + "markers": "python_version >= '3.10'", + "version": "==4.0.0" + }, + "mdurl": { + "hashes": [ + "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", + "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba" + ], + "markers": "python_version >= '3.7'", + "version": "==0.1.2" + }, + "more-itertools": { + "hashes": [ + "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", + "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd" + ], + "markers": "python_version >= '3.9'", + "version": "==10.8.0" + }, + "nh3": { + "hashes": [ + "sha256:0d5eb734a78ac364af1797fef718340a373f626a9ff6b4fb0b4badf7927e7b81", + "sha256:185ed41b88c910b9ca8edc89ca3b4be688a12cb9de129d84befa2f74a0039fee", + "sha256:1ef87f8e916321a88b45f2d597f29bd56e560ed4568a50f0f1305afab86b7189", + "sha256:21a63ccb18ddad3f784bb775955839b8b80e347e597726f01e43ca1abcc5c808", + "sha256:21b058cd20d9f0919421a820a2843fdb5e1749c0bf57a6247ab8f4ba6723c9fc", + "sha256:24769a428e9e971e4ccfb24628f83aaa7dc3c8b41b130c8ddc1835fa1c924489", + "sha256:2efd17c0355d04d39e6d79122b42662277ac10a17ea48831d90b46e5ef7e4fc0", + "sha256:3a62b8ae7c235481715055222e54c682422d0495a5c73326807d4e44c5d14691", + "sha256:45fe0d6a607264910daec30360c8a3b5b1500fd832d21b2da608256287bcb92d", + "sha256:4c730617bdc15d7092dcc0469dc2826b914c8f874996d105b4bc3842a41c1cd9", + "sha256:52e973cb742e95b9ae1b35822ce23992428750f4b46b619fe86eba4205255b30", + "sha256:5a4b2c1f3e6f3cbe7048e17f4fefad3f8d3e14cc0fd08fb8599e0d5653f6b181", + "sha256:5bc1d4b30ba1ba896669d944b6003630592665974bd11a3dc2f661bde92798a7", + "sha256:90126a834c18af03bfd6ff9a027bfa6bbf0e238527bc780a24de6bd7cc1041e2", + "sha256:92a958e6f6d0100e025a5686aafd67e3c98eac67495728f8bb64fbeb3e474493", + "sha256:9ed40cf8449a59a03aa465114fedce1ff7ac52561688811d047917cc878b19ca", + "sha256:a446eae598987f49ee97ac2f18eafcce4e62e7574bd1eb23782e4702e54e217d", + "sha256:b50c3770299fb2a7c1113751501e8878d525d15160a4c05194d7fe62b758aad8", + "sha256:b7a18ee057761e455d58b9d31445c3e4b2594cff4ddb84d2e331c011ef46f462", + "sha256:b838e619f483531483d26d889438e53a880510e832d2aafe73f93b7b1ac2bce2", + "sha256:e8ee96156f7dfc6e30ecda650e480c5ae0a7d38f0c6fafc3c1c655e2500421d9", + "sha256:e974850b131fdffa75e7ad8e0d9c7a855b96227b093417fdf1bd61656e530f37", + "sha256:e98fa3dbfd54e25487e36ba500bc29bca3a4cab4ffba18cfb1a35a2d02624297", + "sha256:f433a2dd66545aad4a720ad1b2150edcdca75bfff6f4e6f378ade1ec138d5e77", + "sha256:f4400a73c2a62859e769f9d36d1b5a7a5c65c4179d1dddd2f6f3095b2db0cbfc", + "sha256:f508ddd4e2433fdcb78c790fc2d24e3a349ba775e5fa904af89891321d4844a3", + "sha256:fc305a2264868ec8fa16548296f803d8fd9c1fa66cd28b88b605b1bd06667c0b" + ], + "markers": "python_version >= '3.8'", + "version": "==0.3.3" + }, "packaging": { "hashes": [ "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", @@ -138,12 +505,12 @@ }, "pipenv": { "hashes": [ - "sha256:06fba6b4fa542acf8f551cfedf604a08940bbe3068bd575e5163f7c2e8e51eac", - "sha256:56ca111e9b236f551030eb7422f6aa71edd9b2ccc018e100ad80ebc57eb2b270" + "sha256:cd2858095181578ec17451f3ff02b8f74eb9038013ddbbc54228c5f0611fa3da", + "sha256:ddba48a3f9a27e6330b391180ba078354d4d8de480bbe49e7432d6c8ead5bbd7" ], "index": "pypi", "markers": "python_version >= '3.10'", - "version": "==2026.1.0" + "version": "==2026.2.1" }, "platformdirs": { "hashes": [ @@ -161,6 +528,14 @@ "markers": "python_version >= '3.9'", "version": "==1.6.0" }, + "pyasn1": { + "hashes": [ + "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", + "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde" + ], + "markers": "python_version >= '3.8'", + "version": "==0.6.3" + }, "pycparser": { "hashes": [ "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", @@ -169,6 +544,144 @@ "markers": "python_version >= '3.10'", "version": "==3.0" }, + "pydantic": { + "extras": [ + "email" + ], + "hashes": [ + "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", + "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d" + ], + "markers": "python_version >= '3.9'", + "version": "==2.12.5" + }, + "pydantic-core": { + "hashes": [ + "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", + "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", + "sha256:0384e2e1021894b1ff5a786dbf94771e2986ebe2869533874d7e43bc79c6f504", + "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", + "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", + "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c", + "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", + "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", + "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", + "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a", + "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", + "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2", + "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", + "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", + "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", + "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba", + "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", + "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", + "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963", + "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", + "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", + "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", + "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", + "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2", + "sha256:299e0a22e7ae2b85c1a57f104538b2656e8ab1873511fd718a1c1c6f149b77b5", + "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", + "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", + "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", + "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", + "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", + "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093", + "sha256:346285d28e4c8017da95144c7f3acd42740d637ff41946af5ce6e5e420502dd5", + "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", + "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", + "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", + "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", + "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", + "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", + "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", + "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", + "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", + "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", + "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", + "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", + "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", + "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97", + "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a", + "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", + "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9", + "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", + "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", + "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", + "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", + "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", + "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", + "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941", + "sha256:707625ef0983fcfb461acfaf14de2067c5942c6bb0f3b4c99158bed6fedd3cf3", + "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", + "sha256:753e230374206729bf0a807954bcc6c150d3743928a73faffee51ac6557a03c3", + "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", + "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", + "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146", + "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", + "sha256:7b93a4d08587e2b7e7882de461e82b6ed76d9026ce91ca7915e740ecc7855f60", + "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", + "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", + "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", + "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", + "sha256:873e0d5b4fb9b89ef7c2d2a963ea7d02879d9da0da8d9d4933dee8ee86a8b460", + "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", + "sha256:8bfeaf8735be79f225f3fefab7f941c712aaca36f1128c9d7e2352ee1aa87bdf", + "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", + "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", + "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", + "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", + "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", + "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", + "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", + "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", + "sha256:a75dafbf87d6276ddc5b2bf6fae5254e3d0876b626eb24969a574fff9149ee5d", + "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", + "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", + "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", + "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", + "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8", + "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", + "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", + "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", + "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", + "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", + "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", + "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", + "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", + "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", + "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a", + "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", + "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", + "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", + "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a", + "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556", + "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", + "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49", + "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2", + "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9", + "sha256:e4f4a984405e91527a0d62649ee21138f8e3d0ef103be488c1dc11a80d7f184b", + "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", + "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", + "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", + "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", + "sha256:e8465ab91a4bd96d36dde3263f06caa6a8a6019e4113f24dc753d79a8b3a3f82", + "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", + "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b", + "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", + "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", + "sha256:f0cd744688278965817fd0839c4a4116add48d23890d468bc436f78beb28abf5", + "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", + "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", + "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", + "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", + "sha256:f41eb9797986d6ebac5e8edff36d5cef9de40def462311b3eb3eeded1431e425", + "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52" + ], + "markers": "python_version >= '3.9'", + "version": "==2.41.5" + }, "pygments": { "hashes": [ "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", @@ -177,6 +690,31 @@ "markers": "python_version >= '3.8'", "version": "==2.19.2" }, + "pyjwt": { + "hashes": [ + "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c", + "sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b" + ], + "markers": "python_version >= '3.9'", + "version": "==2.12.1" + }, + "pyopenssl": { + "hashes": [ + "sha256:df94d28498848b98cc1c0ffb8ef1e71e40210d3b0a8064c9d29571ed2904bf81", + "sha256:f293934e52936f2e3413b89c6ce36df66a0b34ae1ea3a053b8c5020ff2f513fc" + ], + "markers": "python_version >= '3.8'", + "version": "==26.0.0" + }, + "pypi-attestations": { + "hashes": [ + "sha256:278a28d741b57d62973c00d453ec9b9bb30456464d69296c6780474cd0bf098e", + "sha256:2daf3ec46ff4c7123184ec892852b4d4599b78128f01f742a44406a73200c5df" + ], + "index": "pypi", + "markers": "python_version >= '3.10'", + "version": "==0.0.29" + }, "pyproject-api": { "hashes": [ "sha256:40c6f2d82eebdc4afee61c773ed208c04c19db4c4a60d97f8d7be3ebc0bbb330", @@ -195,11 +733,94 @@ }, "python-discovery": { "hashes": [ - "sha256:7acca36e818cd88e9b2ba03e045ad7e93e1713e29c6bbfba5d90202310b7baa5", - "sha256:90e795f0121bc84572e737c9aa9966311b9fde44ffb88a5953b3ec9b31c6945e" + "sha256:1e108f1bbe2ed0ef089823d28805d5ad32be8e734b86a5f212bf89b71c266e4a", + "sha256:7d33e350704818b09e3da2bd419d37e21e7c30db6e0977bb438916e06b41b5b1" ], "markers": "python_version >= '3.8'", - "version": "==1.1.3" + "version": "==1.2.0" + }, + "readme-renderer": { + "hashes": [ + "sha256:2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151", + "sha256:8712034eabbfa6805cacf1402b4eeb2a73028f72d1166d6f5cb7f9c047c5d1e1" + ], + "markers": "python_version >= '3.9'", + "version": "==44.0" + }, + "requests": { + "hashes": [ + "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", + "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf" + ], + "markers": "python_version >= '3.9'", + "version": "==2.32.5" + }, + "requests-toolbelt": { + "hashes": [ + "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", + "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.0.0" + }, + "rfc3161-client": { + "hashes": [ + "sha256:078e4bbf0770ddc472e2ca96cf1e23efd0c313e6682b4c2c9765e1fdf09f55a3", + "sha256:09c47582ecea2ca4a3debf8a1eda775cc3d5ae1379da40272cc065d32e639a7a", + "sha256:3106f3361a5a36789f43d2700e5678c847a9d3460a23f455f4c20cd39314c557", + "sha256:31b6ee79f15b93d90952efd0395bb3f5ebf07941469c5c6eb32f9b64312cda6e", + "sha256:61c04b4953453e5c26a1949c20adac415b65cd062dab0960574d6c36240222d2", + "sha256:8a54fdb2f9e64481272b89137a7b71403cf1d30f5505c2e0c15a47a1cc100264", + "sha256:8fb34470e867a29cc15dc4987ea14f19d3bd25c863e132b6f75dca583e2cc67e", + "sha256:9c53a6711bab0c3f77dc9cf1e2fd750da475ff7abbc40ffe0333d8c518a8a9c8", + "sha256:ae440461a310ae097417afe536d9d22fd71c95fbc9d21db3561b2707bed0aff0", + "sha256:d31d30e354d2349ae8483ce811ef61498a3780daf8622c0b79d8cd44d271b46b", + "sha256:d9ed8e597d0ee7387da1945e1583c4516b26f133770b3956e079606e2d90b69c", + "sha256:e904430e27e75a5a379fc4aac09bd60ba5f4b48054f0481b2fb417297e404047", + "sha256:f1a2e32e2a053455cee1ff9b325b88dbc7c66c8882dde60962add92f572df5c5" + ], + "markers": "python_version >= '3.9'", + "version": "==1.0.5" + }, + "rfc3986": { + "hashes": [ + "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", + "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c" + ], + "markers": "python_version >= '3.7'", + "version": "==2.0.0" + }, + "rfc8785": { + "hashes": [ + "sha256:520d690b448ecf0703691c76e1a34a24ddcd4fc5bc41d589cb7c58ec651bcd48", + "sha256:e545841329fe0eee4f6a3b44e7034343100c12b4ec566dc06ca9735681deb4da" + ], + "markers": "python_version >= '3.8'", + "version": "==0.1.4" + }, + "rich": { + "hashes": [ + "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d", + "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b" + ], + "markers": "python_full_version >= '3.8.0'", + "version": "==14.3.3" + }, + "secretstorage": { + "hashes": [ + "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", + "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be" + ], + "markers": "python_version >= '3.10'", + "version": "==3.5.0" + }, + "securesystemslib": { + "hashes": [ + "sha256:2e5414bbdde33155a91805b295cbedc4ae3f12b48dccc63e1089093537f43c81", + "sha256:ca915f4b88209bb5450ac05426b859d74b7cd1421cafcf73b8dd3418a0b17486" + ], + "markers": "python_version ~= '3.8'", + "version": "==1.3.1" }, "setuptools": { "hashes": [ @@ -218,6 +839,30 @@ "markers": "python_version >= '3.8'", "version": "==8.2.0" }, + "sigstore": { + "hashes": [ + "sha256:bdbb49a42fd5f0ea6765919adb42ccee7254c482330764d0842eec4e11ad78d7", + "sha256:ed2e0f50aae85148a8aa4fc0f57c298927fce430ad1f988f38611ce90c85829f" + ], + "markers": "python_version >= '3.10'", + "version": "==4.2.0" + }, + "sigstore-models": { + "hashes": [ + "sha256:5201a68f4d7d0f8bec1e2f4378eb646b084c52609a4e31db8c385095fff68b2e", + "sha256:c766c09470c2a7e8a4a333c893f07e2001c56a3ff1757b1a246119f53169a849" + ], + "markers": "python_version >= '3.10'", + "version": "==0.0.6" + }, + "sigstore-rekor-types": { + "hashes": [ + "sha256:19aef25433218ebf9975a1e8b523cc84aaf3cd395ad39a30523b083ea7917ec5", + "sha256:b62bf38c5b1a62bc0d7fe0ee51a0709e49311d137c7880c329882a8f4b2d1d78" + ], + "markers": "python_version >= '3.8'", + "version": "==0.0.18" + }, "tomli-w": { "hashes": [ "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90", @@ -228,12 +873,53 @@ }, "tox": { "hashes": [ - "sha256:4130d02e1d53648d7107d121ed79f69a27b717817c5e9da521d50319dd261212", - "sha256:6dd2d7d4e4fd5895ce4ea20e258fce0d4b81e914b697d116a5ab0365f8303bad" + "sha256:5e788a512bfe6f7447e0c8d7c1b666eb2e56e5e676c65717490423bec37d1a07", + "sha256:c745641de6cc4f19d066bd9f98c1c25f7affb005b381b7f3694a1f142ea0946b" ], "index": "pypi", "markers": "python_version >= '3.10'", - "version": "==4.49.1" + "version": "==4.50.3" + }, + "tuf": { + "hashes": [ + "sha256:458f663a233d95cc76dde0e1a3d01796516a05ce2781fefafebe037f7729601a", + "sha256:9eed0f7888c5fff45dc62164ff243a05d47fb8a3208035eb268974287e0aee8d" + ], + "markers": "python_version >= '3.8'", + "version": "==6.0.0" + }, + "twine": { + "hashes": [ + "sha256:418ebf08ccda9a8caaebe414433b0ba5e25eb5e4a927667122fbe8f829f985d8", + "sha256:e5ed0d2fd70c9959770dce51c8f39c8945c574e18173a7b81802dab51b4b75cf" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==6.2.0" + }, + "typing-extensions": { + "hashes": [ + "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", + "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548" + ], + "markers": "python_version >= '3.9'", + "version": "==4.15.0" + }, + "typing-inspection": { + "hashes": [ + "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", + "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464" + ], + "markers": "python_version >= '3.9'", + "version": "==0.4.2" + }, + "urllib3": { + "hashes": [ + "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", + "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4" + ], + "markers": "python_version >= '3.9'", + "version": "==2.6.3" }, "virtualenv": { "hashes": [ diff --git a/pyproject.toml b/pyproject.toml index 5bc3eb4..423e4e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ build-backend = "setuptools.build_meta" name = "byteb4rb1e.utils" description = "personal utilities and helpers" authors = [ - { name = "Tiara Rodney", email = "tiara.rodney@administratrix.de" } + { name = "Tiara Rodney", email = "tiara.rodney@byteb4rb1e.me" } ] license-files = ["LICENSE"] readme = "README.md" From d8d32e1662a63924a116bb65d27fcbcaf77e64f4 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 21 Mar 2026 18:35:12 +0100 Subject: [PATCH 64/92] docs: add development guidelines --- DEVELOPMENT.md | 122 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 DEVELOPMENT.md diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 0000000..93093de --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,122 @@ +# Development + +> All changes MUST follow the vendor/tiara-gitflow-spec.git and no work MUST be +> started without a TODO issue. + +## Prerequisites + +- Python 3.9+ +- [Pipenv](https://pipenv.pypa.io/) +- [tox](https://tox.wiki/) (installed via Pipenv dev dependencies) +- Node.js (for the `@byteb4rb1e/mime-todo` issue tracker CLI) + +## Setup + +Iniitialize Git submodules: + +```bash +git submodule update --init --remote --recursive +``` + +Install dependencies (includes the package in editable mode): + +```bash +pipenv install --dev +``` + + +## Tooling + +### Package + +The project is packaged as `byteb4rb1e.utils` under a namespace package +layout (`src/byteb4rb1e/utils/`). It is installed in editable mode via +Pipenv. + +Build a distribution: + +```bash +pipenv run dist +``` + +### Testing + +Tests are managed by tox. Test environments are defined in `tox.ini`: + +```bash +# run all test suites +tox + +# run specific environments +tox -e unit-py313 +tox -e lint +tox -e format +``` + +| Environment | Purpose | +|---|---| +| `unit-py3{9-13}` | Unit tests | +| `smoke-py3{9-13}` | Smoke tests | +| `integration-py3{9-13}` | Integration tests | +| `lint` | Type checking (mypy) | +| `format` | Code style (autopep8) | +| `audit` | Dependency audit (pip-audit) | + +### Issue tracker + +Issues are tracked in the `TODO` file using the +[MIME TODO](https://specs.code.tiararodney.com/mime-todo/) format. Use the +`@byteb4rb1e/mime-todo` CLI to interact with it: + +```bash +# list issues +npx @byteb4rb1e/mime-todo list + +# show a specific issue +npx @byteb4rb1e/mime-todo show 3 + +# create an issue +npx @byteb4rb1e/mime-todo create --type feature --title "Title" --plan "Description" --module homeostat +``` + +See [CONTRIBUTING.md](CONTRIBUTING.md) for the full issue lifecycle. + +### Publishing + +Build wheel and source distributions: + +```sh +pipenv run sdist +``` + +Configure publishing options: + +`~/.pypirc` +``` +[distutils] +index-servers = + tiararodney + +[tiararodney] +repository: https://pypi.code.tiararodney.com/root/byteb4rb1e/ +username: +password: +``` + +Publish to pypi.code.tiararodney.com: + +```sh +pipenv run sdist:publish:tiarardoney +``` + + +## Project layout + +``` +src/byteb4rb1e/utils/ # package source +tests/ # test suites (unit/, smoke/, integration/) +vendor/ # vendored specs +dist/ # sdist and wheel build output +DEVELOPMENT.md # this file +TODO # issue tracker (MIME TODO format) +``` From 9221fdcfe2a925572491314abddd3912d1cb4efe Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Fri, 27 Mar 2026 19:00:09 +0100 Subject: [PATCH 65/92] chore: cleanup --- Makefile | 32 - Pipfile | 4 +- Pipfile.lock | 19 +- configure | 2663 ------------------------------------------ configure.ac | 27 - pyproject.toml | 1 - requirements-dev.txt | 25 - tox.ini | 4 +- 8 files changed, 23 insertions(+), 2752 deletions(-) delete mode 100644 Makefile delete mode 100644 configure delete mode 100644 configure.ac delete mode 100644 requirements-dev.txt diff --git a/Makefile b/Makefile deleted file mode 100644 index 5cf7aae..0000000 --- a/Makefile +++ /dev/null @@ -1,32 +0,0 @@ -.PHONY: chore configure - -chore: configure Pipfile.lock requirements-dev.txt - -Pipfile.lock: .venv Pipfile - .venv/bin/pipenv lock - -requirements-dev.txt: .venv Pipfile.lock - .venv/bin/pipenv requirements --dev-only > requirements-dev.txt - -configure: configure.ac - autoconf - -.venv: requirements-dev.txt - python3 -m venv .venv - .venv/bin/python3 -m pip install --upgrade pip - .venv/bin/pip install -r requirements-dev.txt - -test-reports: test-reports/unit test-reports/static test-reports/integration - -test-reports/unit: - python3 -m pipenv run -v test-unit - -test-reports/integration: - python3 -m pipenv run -v test-integration - -test-reports/static: - python3 -m pipenv run -v test-static - -build: .venv/bin/pipenv - .venv/bin/pipenv run build - diff --git a/Pipfile b/Pipfile index f6b9a8b..8671f4d 100644 --- a/Pipfile +++ b/Pipfile @@ -10,6 +10,7 @@ pipenv = "*" tox = "*" twine = "*" pypi-attestations = "*" +autopep8 = "*" [requires] python_version = "3" @@ -17,7 +18,8 @@ python_version = "3" [scripts] "dist" = "python3 -m build" "dist:attestations" = "python3 -m pypi_attestations sign dist/*" -"dist:publish" = "python3 -m twine upload --sign --repository tiararodney dist/*" +"dist:publish:tiararodney" = "python3 -m twine upload --sign --repository tiararodney dist/*" +"test" = "tox" "test:static" = "tox run -m static" "test:unit" = "tox run -m unit" "test:integration" = "tox run -m integration" diff --git a/Pipfile.lock b/Pipfile.lock index ae31cf6..d715cea 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "edaf04f6c9d148cb35296f633912e3b45bfba2ccd30848a824495b4f5ba085ee" + "sha256": "7bf1e5e3285cb7ead9e247720d2abc340a64c17d42127e41745bff3309521b41" }, "pipfile-spec": 6, "requires": { @@ -30,6 +30,15 @@ "markers": "python_version >= '3.8'", "version": "==0.7.0" }, + "autopep8": { + "hashes": [ + "sha256:89440a4f969197b69a995e4ce0661b031f455a9f776d2c5ba3dbd83466931758", + "sha256:ce8ad498672c845a0c3de2629c15b635ec2b05ef8177a6e7c91c74f3e9b51128" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==2.3.2" + }, "build": { "hashes": [ "sha256:6a07c1b8eb6f2b311b96fcbdbce5dab5fe637ffda0fd83c9cac622e927501596", @@ -536,6 +545,14 @@ "markers": "python_version >= '3.8'", "version": "==0.6.3" }, + "pycodestyle": { + "hashes": [ + "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783", + "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d" + ], + "markers": "python_version >= '3.9'", + "version": "==2.14.0" + }, "pycparser": { "hashes": [ "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", diff --git a/configure b/configure deleted file mode 100644 index ade147c..0000000 --- a/configure +++ /dev/null @@ -1,2663 +0,0 @@ -#! /bin/sh -# Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.72. -# -# -# Copyright (C) 1992-1996, 1998-2017, 2020-2023 Free Software Foundation, -# Inc. -# -# -# This configure script is free software; the Free Software Foundation -# gives unlimited permission to copy, distribute and modify it. -## -------------------- ## -## M4sh Initialization. ## -## -------------------- ## - -# Be more Bourne compatible -DUALCASE=1; export DUALCASE # for MKS sh -if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 -then : - emulate sh - NULLCMD=: - # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which - # is contrary to our usage. Disable this feature. - alias -g '${1+"$@"}'='"$@"' - setopt NO_GLOB_SUBST -else case e in #( - e) case `(set -o) 2>/dev/null` in #( - *posix*) : - set -o posix ;; #( - *) : - ;; -esac ;; -esac -fi - - - -# Reset variables that may have inherited troublesome values from -# the environment. - -# IFS needs to be set, to space, tab, and newline, in precisely that order. -# (If _AS_PATH_WALK were called with IFS unset, it would have the -# side effect of setting IFS to empty, thus disabling word splitting.) -# Quoting is to prevent editors from complaining about space-tab. -as_nl=' -' -export as_nl -IFS=" "" $as_nl" - -PS1='$ ' -PS2='> ' -PS4='+ ' - -# Ensure predictable behavior from utilities with locale-dependent output. -LC_ALL=C -export LC_ALL -LANGUAGE=C -export LANGUAGE - -# We cannot yet rely on "unset" to work, but we need these variables -# to be unset--not just set to an empty or harmless value--now, to -# avoid bugs in old shells (e.g. pre-3.0 UWIN ksh). This construct -# also avoids known problems related to "unset" and subshell syntax -# in other old shells (e.g. bash 2.01 and pdksh 5.2.14). -for as_var in BASH_ENV ENV MAIL MAILPATH CDPATH -do eval test \${$as_var+y} \ - && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : -done - -# Ensure that fds 0, 1, and 2 are open. -if (exec 3>&0) 2>/dev/null; then :; else exec 0&1) 2>/dev/null; then :; else exec 1>/dev/null; fi -if (exec 3>&2) ; then :; else exec 2>/dev/null; fi - -# The user is always right. -if ${PATH_SEPARATOR+false} :; then - PATH_SEPARATOR=: - (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { - (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || - PATH_SEPARATOR=';' - } -fi - - -# Find who we are. Look in the path if we contain no directory separator. -as_myself= -case $0 in #(( - *[\\/]* ) as_myself=$0 ;; - *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH -do - IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac - test -r "$as_dir$0" && as_myself=$as_dir$0 && break - done -IFS=$as_save_IFS - - ;; -esac -# We did not find ourselves, most probably we were run as 'sh COMMAND' -# in which case we are not to be found in the path. -if test "x$as_myself" = x; then - as_myself=$0 -fi -if test ! -f "$as_myself"; then - printf "%s\n" "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 - exit 1 -fi - - -# Use a proper internal environment variable to ensure we don't fall - # into an infinite loop, continuously re-executing ourselves. - if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then - _as_can_reexec=no; export _as_can_reexec; - # We cannot yet assume a decent shell, so we have to provide a -# neutralization value for shells without unset; and this also -# works around shells that cannot unset nonexistent variables. -# Preserve -v and -x to the replacement shell. -BASH_ENV=/dev/null -ENV=/dev/null -(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV -case $- in # (((( - *v*x* | *x*v* ) as_opts=-vx ;; - *v* ) as_opts=-v ;; - *x* ) as_opts=-x ;; - * ) as_opts= ;; -esac -exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} -# Admittedly, this is quite paranoid, since all the known shells bail -# out after a failed 'exec'. -printf "%s\n" "$0: could not re-execute with $CONFIG_SHELL" >&2 -exit 255 - fi - # We don't want this to propagate to other subprocesses. - { _as_can_reexec=; unset _as_can_reexec;} -if test "x$CONFIG_SHELL" = x; then - as_bourne_compatible="if test \${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 -then : - emulate sh - NULLCMD=: - # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which - # is contrary to our usage. Disable this feature. - alias -g '\${1+\"\$@\"}'='\"\$@\"' - setopt NO_GLOB_SUBST -else case e in #( - e) case \`(set -o) 2>/dev/null\` in #( - *posix*) : - set -o posix ;; #( - *) : - ;; -esac ;; -esac -fi -" - as_required="as_fn_return () { (exit \$1); } -as_fn_success () { as_fn_return 0; } -as_fn_failure () { as_fn_return 1; } -as_fn_ret_success () { return 0; } -as_fn_ret_failure () { return 1; } - -exitcode=0 -as_fn_success || { exitcode=1; echo as_fn_success failed.; } -as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } -as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } -as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } -if ( set x; as_fn_ret_success y && test x = \"\$1\" ) -then : - -else case e in #( - e) exitcode=1; echo positional parameters were not saved. ;; -esac -fi -test x\$exitcode = x0 || exit 1 -blah=\$(echo \$(echo blah)) -test x\"\$blah\" = xblah || exit 1 -test -x / || exit 1" - as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO - as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO - eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && - test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1" - if (eval "$as_required") 2>/dev/null -then : - as_have_required=yes -else case e in #( - e) as_have_required=no ;; -esac -fi - if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null -then : - -else case e in #( - e) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -as_found=false -for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH -do - IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac - as_found=: - case $as_dir in #( - /*) - for as_base in sh bash ksh sh5; do - # Try only shells that exist, to save several forks. - as_shell=$as_dir$as_base - if { test -f "$as_shell" || test -f "$as_shell.exe"; } && - as_run=a "$as_shell" -c "$as_bourne_compatible""$as_required" 2>/dev/null -then : - CONFIG_SHELL=$as_shell as_have_required=yes - if as_run=a "$as_shell" -c "$as_bourne_compatible""$as_suggested" 2>/dev/null -then : - break 2 -fi -fi - done;; - esac - as_found=false -done -IFS=$as_save_IFS -if $as_found -then : - -else case e in #( - e) if { test -f "$SHELL" || test -f "$SHELL.exe"; } && - as_run=a "$SHELL" -c "$as_bourne_compatible""$as_required" 2>/dev/null -then : - CONFIG_SHELL=$SHELL as_have_required=yes -fi ;; -esac -fi - - - if test "x$CONFIG_SHELL" != x -then : - export CONFIG_SHELL - # We cannot yet assume a decent shell, so we have to provide a -# neutralization value for shells without unset; and this also -# works around shells that cannot unset nonexistent variables. -# Preserve -v and -x to the replacement shell. -BASH_ENV=/dev/null -ENV=/dev/null -(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV -case $- in # (((( - *v*x* | *x*v* ) as_opts=-vx ;; - *v* ) as_opts=-v ;; - *x* ) as_opts=-x ;; - * ) as_opts= ;; -esac -exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} -# Admittedly, this is quite paranoid, since all the known shells bail -# out after a failed 'exec'. -printf "%s\n" "$0: could not re-execute with $CONFIG_SHELL" >&2 -exit 255 -fi - - if test x$as_have_required = xno -then : - printf "%s\n" "$0: This script requires a shell more modern than all" - printf "%s\n" "$0: the shells that I found on your system." - if test ${ZSH_VERSION+y} ; then - printf "%s\n" "$0: In particular, zsh $ZSH_VERSION has bugs and should" - printf "%s\n" "$0: be upgraded to zsh 4.3.4 or later." - else - printf "%s\n" "$0: Please tell bug-autoconf@gnu.org about your system, -$0: including any error possibly output before this -$0: message. Then install a modern shell, or manually run -$0: the script under such a shell if you do have one." - fi - exit 1 -fi ;; -esac -fi -fi -SHELL=${CONFIG_SHELL-/bin/sh} -export SHELL -# Unset more variables known to interfere with behavior of common tools. -CLICOLOR_FORCE= GREP_OPTIONS= -unset CLICOLOR_FORCE GREP_OPTIONS - -## --------------------- ## -## M4sh Shell Functions. ## -## --------------------- ## -# as_fn_unset VAR -# --------------- -# Portably unset VAR. -as_fn_unset () -{ - { eval $1=; unset $1;} -} -as_unset=as_fn_unset - - -# as_fn_set_status STATUS -# ----------------------- -# Set $? to STATUS, without forking. -as_fn_set_status () -{ - return $1 -} # as_fn_set_status - -# as_fn_exit STATUS -# ----------------- -# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. -as_fn_exit () -{ - set +e - as_fn_set_status $1 - exit $1 -} # as_fn_exit - -# as_fn_mkdir_p -# ------------- -# Create "$as_dir" as a directory, including parents if necessary. -as_fn_mkdir_p () -{ - - case $as_dir in #( - -*) as_dir=./$as_dir;; - esac - test -d "$as_dir" || eval $as_mkdir_p || { - as_dirs= - while :; do - case $as_dir in #( - *\'*) as_qdir=`printf "%s\n" "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( - *) as_qdir=$as_dir;; - esac - as_dirs="'$as_qdir' $as_dirs" - as_dir=`$as_dirname -- "$as_dir" || -$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ - X"$as_dir" : 'X\(//\)[^/]' \| \ - X"$as_dir" : 'X\(//\)$' \| \ - X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || -printf "%s\n" X"$as_dir" | - sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ - s//\1/ - q - } - /^X\(\/\/\)[^/].*/{ - s//\1/ - q - } - /^X\(\/\/\)$/{ - s//\1/ - q - } - /^X\(\/\).*/{ - s//\1/ - q - } - s/.*/./; q'` - test -d "$as_dir" && break - done - test -z "$as_dirs" || eval "mkdir $as_dirs" - } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" - - -} # as_fn_mkdir_p - -# as_fn_executable_p FILE -# ----------------------- -# Test if FILE is an executable regular file. -as_fn_executable_p () -{ - test -f "$1" && test -x "$1" -} # as_fn_executable_p -# as_fn_append VAR VALUE -# ---------------------- -# Append the text in VALUE to the end of the definition contained in VAR. Take -# advantage of any shell optimizations that allow amortized linear growth over -# repeated appends, instead of the typical quadratic growth present in naive -# implementations. -if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null -then : - eval 'as_fn_append () - { - eval $1+=\$2 - }' -else case e in #( - e) as_fn_append () - { - eval $1=\$$1\$2 - } ;; -esac -fi # as_fn_append - -# as_fn_arith ARG... -# ------------------ -# Perform arithmetic evaluation on the ARGs, and store the result in the -# global $as_val. Take advantage of shells that can avoid forks. The arguments -# must be portable across $(()) and expr. -if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null -then : - eval 'as_fn_arith () - { - as_val=$(( $* )) - }' -else case e in #( - e) as_fn_arith () - { - as_val=`expr "$@" || test $? -eq 1` - } ;; -esac -fi # as_fn_arith - - -# as_fn_error STATUS ERROR [LINENO LOG_FD] -# ---------------------------------------- -# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are -# provided, also output the error to LOG_FD, referencing LINENO. Then exit the -# script with STATUS, using 1 if that was 0. -as_fn_error () -{ - as_status=$1; test $as_status -eq 0 && as_status=1 - if test "$4"; then - as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 - fi - printf "%s\n" "$as_me: error: $2" >&2 - as_fn_exit $as_status -} # as_fn_error - -if expr a : '\(a\)' >/dev/null 2>&1 && - test "X`expr 00001 : '.*\(...\)'`" = X001; then - as_expr=expr -else - as_expr=false -fi - -if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then - as_basename=basename -else - as_basename=false -fi - -if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then - as_dirname=dirname -else - as_dirname=false -fi - -as_me=`$as_basename -- "$0" || -$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ - X"$0" : 'X\(//\)$' \| \ - X"$0" : 'X\(/\)' \| . 2>/dev/null || -printf "%s\n" X/"$0" | - sed '/^.*\/\([^/][^/]*\)\/*$/{ - s//\1/ - q - } - /^X\/\(\/\/\)$/{ - s//\1/ - q - } - /^X\/\(\/\).*/{ - s//\1/ - q - } - s/.*/./; q'` - -# Avoid depending upon Character Ranges. -as_cr_letters='abcdefghijklmnopqrstuvwxyz' -as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' -as_cr_Letters=$as_cr_letters$as_cr_LETTERS -as_cr_digits='0123456789' -as_cr_alnum=$as_cr_Letters$as_cr_digits - - - as_lineno_1=$LINENO as_lineno_1a=$LINENO - as_lineno_2=$LINENO as_lineno_2a=$LINENO - eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" && - test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || { - # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-) - sed -n ' - p - /[$]LINENO/= - ' <$as_myself | - sed ' - t clear - :clear - s/[$]LINENO.*/&-/ - t lineno - b - :lineno - N - :loop - s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ - t loop - s/-\n.*// - ' >$as_me.lineno && - chmod +x "$as_me.lineno" || - { printf "%s\n" "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } - - # If we had to re-execute with $CONFIG_SHELL, we're ensured to have - # already done that, so ensure we don't try to do so again and fall - # in an infinite loop. This has already happened in practice. - _as_can_reexec=no; export _as_can_reexec - # Don't try to exec as it changes $[0], causing all sort of problems - # (the dirname of $[0] is not the place where we might find the - # original and so on. Autoconf is especially sensitive to this). - . "./$as_me.lineno" - # Exit status is that of the last command. - exit -} - - -# Determine whether it's possible to make 'echo' print without a newline. -# These variables are no longer used directly by Autoconf, but are AC_SUBSTed -# for compatibility with existing Makefiles. -ECHO_C= ECHO_N= ECHO_T= -case `echo -n x` in #((((( --n*) - case `echo 'xy\c'` in - *c*) ECHO_T=' ';; # ECHO_T is single tab character. - xy) ECHO_C='\c';; - *) echo `echo ksh88 bug on AIX 6.1` > /dev/null - ECHO_T=' ';; - esac;; -*) - ECHO_N='-n';; -esac - -# For backward compatibility with old third-party macros, we provide -# the shell variables $as_echo and $as_echo_n. New code should use -# AS_ECHO(["message"]) and AS_ECHO_N(["message"]), respectively. -as_echo='printf %s\n' -as_echo_n='printf %s' - -rm -f conf$$ conf$$.exe conf$$.file -if test -d conf$$.dir; then - rm -f conf$$.dir/conf$$.file -else - rm -f conf$$.dir - mkdir conf$$.dir 2>/dev/null -fi -if (echo >conf$$.file) 2>/dev/null; then - if ln -s conf$$.file conf$$ 2>/dev/null; then - as_ln_s='ln -s' - # ... but there are two gotchas: - # 1) On MSYS, both 'ln -s file dir' and 'ln file dir' fail. - # 2) DJGPP < 2.04 has no symlinks; 'ln -s' creates a wrapper executable. - # In both cases, we have to default to 'cp -pR'. - ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || - as_ln_s='cp -pR' - elif ln conf$$.file conf$$ 2>/dev/null; then - as_ln_s=ln - else - as_ln_s='cp -pR' - fi -else - as_ln_s='cp -pR' -fi -rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file -rmdir conf$$.dir 2>/dev/null - -if mkdir -p . 2>/dev/null; then - as_mkdir_p='mkdir -p "$as_dir"' -else - test -d ./-p && rmdir ./-p - as_mkdir_p=false -fi - -as_test_x='test -x' -as_executable_p=as_fn_executable_p - -# Sed expression to map a string onto a valid CPP name. -as_sed_cpp="y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g" -as_tr_cpp="eval sed '$as_sed_cpp'" # deprecated - -# Sed expression to map a string onto a valid variable name. -as_sed_sh="y%*+%pp%;s%[^_$as_cr_alnum]%_%g" -as_tr_sh="eval sed '$as_sed_sh'" # deprecated - - -test -n "$DJDIR" || exec 7<&0 &1 - -# Name of the host. -# hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status, -# so uname gets run too. -ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` - -# -# Initializations. -# -ac_default_prefix=/usr/local -ac_clean_files= -ac_config_libobj_dir=. -LIBOBJS= -cross_compiling=no -subdirs= -MFLAGS= -MAKEFLAGS= - -# Identity of this package. -PACKAGE_NAME='' -PACKAGE_TARNAME='' -PACKAGE_VERSION='' -PACKAGE_STRING='' -PACKAGE_BUGREPORT='' -PACKAGE_URL='' - -ac_subst_vars='LTLIBOBJS -LIBOBJS -REALPATH -PYTHON3 -GIT -MAKE -target_alias -host_alias -build_alias -LIBS -ECHO_T -ECHO_N -ECHO_C -DEFS -mandir -localedir -libdir -psdir -pdfdir -dvidir -htmldir -infodir -docdir -oldincludedir -includedir -runstatedir -localstatedir -sharedstatedir -sysconfdir -datadir -datarootdir -libexecdir -sbindir -bindir -program_transform_name -prefix -exec_prefix -PACKAGE_URL -PACKAGE_BUGREPORT -PACKAGE_STRING -PACKAGE_VERSION -PACKAGE_TARNAME -PACKAGE_NAME -PATH_SEPARATOR -SHELL' -ac_subst_files='' -ac_user_opts=' -enable_option_checking -' - ac_precious_vars='build_alias -host_alias -target_alias' - - -# Initialize some variables set by options. -ac_init_help= -ac_init_version=false -ac_unrecognized_opts= -ac_unrecognized_sep= -# The variables have the same names as the options, with -# dashes changed to underlines. -cache_file=/dev/null -exec_prefix=NONE -no_create= -no_recursion= -prefix=NONE -program_prefix=NONE -program_suffix=NONE -program_transform_name=s,x,x, -silent= -site= -srcdir= -verbose= -x_includes=NONE -x_libraries=NONE - -# Installation directory options. -# These are left unexpanded so users can "make install exec_prefix=/foo" -# and all the variables that are supposed to be based on exec_prefix -# by default will actually change. -# Use braces instead of parens because sh, perl, etc. also accept them. -# (The list follows the same order as the GNU Coding Standards.) -bindir='${exec_prefix}/bin' -sbindir='${exec_prefix}/sbin' -libexecdir='${exec_prefix}/libexec' -datarootdir='${prefix}/share' -datadir='${datarootdir}' -sysconfdir='${prefix}/etc' -sharedstatedir='${prefix}/com' -localstatedir='${prefix}/var' -runstatedir='${localstatedir}/run' -includedir='${prefix}/include' -oldincludedir='/usr/include' -docdir='${datarootdir}/doc/${PACKAGE}' -infodir='${datarootdir}/info' -htmldir='${docdir}' -dvidir='${docdir}' -pdfdir='${docdir}' -psdir='${docdir}' -libdir='${exec_prefix}/lib' -localedir='${datarootdir}/locale' -mandir='${datarootdir}/man' - -ac_prev= -ac_dashdash= -for ac_option -do - # If the previous option needs an argument, assign it. - if test -n "$ac_prev"; then - eval $ac_prev=\$ac_option - ac_prev= - continue - fi - - case $ac_option in - *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; - *=) ac_optarg= ;; - *) ac_optarg=yes ;; - esac - - case $ac_dashdash$ac_option in - --) - ac_dashdash=yes ;; - - -bindir | --bindir | --bindi | --bind | --bin | --bi) - ac_prev=bindir ;; - -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) - bindir=$ac_optarg ;; - - -build | --build | --buil | --bui | --bu) - ac_prev=build_alias ;; - -build=* | --build=* | --buil=* | --bui=* | --bu=*) - build_alias=$ac_optarg ;; - - -cache-file | --cache-file | --cache-fil | --cache-fi \ - | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) - ac_prev=cache_file ;; - -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ - | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) - cache_file=$ac_optarg ;; - - --config-cache | -C) - cache_file=config.cache ;; - - -datadir | --datadir | --datadi | --datad) - ac_prev=datadir ;; - -datadir=* | --datadir=* | --datadi=* | --datad=*) - datadir=$ac_optarg ;; - - -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \ - | --dataroo | --dataro | --datar) - ac_prev=datarootdir ;; - -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \ - | --dataroot=* | --dataroo=* | --dataro=* | --datar=*) - datarootdir=$ac_optarg ;; - - -disable-* | --disable-*) - ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` - # Reject names that are not valid shell variable names. - expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && - as_fn_error $? "invalid feature name: '$ac_useropt'" - ac_useropt_orig=$ac_useropt - ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` - case $ac_user_opts in - *" -"enable_$ac_useropt" -"*) ;; - *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig" - ac_unrecognized_sep=', ';; - esac - eval enable_$ac_useropt=no ;; - - -docdir | --docdir | --docdi | --doc | --do) - ac_prev=docdir ;; - -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*) - docdir=$ac_optarg ;; - - -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv) - ac_prev=dvidir ;; - -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*) - dvidir=$ac_optarg ;; - - -enable-* | --enable-*) - ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` - # Reject names that are not valid shell variable names. - expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && - as_fn_error $? "invalid feature name: '$ac_useropt'" - ac_useropt_orig=$ac_useropt - ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` - case $ac_user_opts in - *" -"enable_$ac_useropt" -"*) ;; - *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig" - ac_unrecognized_sep=', ';; - esac - eval enable_$ac_useropt=\$ac_optarg ;; - - -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ - | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ - | --exec | --exe | --ex) - ac_prev=exec_prefix ;; - -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ - | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ - | --exec=* | --exe=* | --ex=*) - exec_prefix=$ac_optarg ;; - - -gas | --gas | --ga | --g) - # Obsolete; use --with-gas. - with_gas=yes ;; - - -help | --help | --hel | --he | -h) - ac_init_help=long ;; - -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) - ac_init_help=recursive ;; - -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) - ac_init_help=short ;; - - -host | --host | --hos | --ho) - ac_prev=host_alias ;; - -host=* | --host=* | --hos=* | --ho=*) - host_alias=$ac_optarg ;; - - -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht) - ac_prev=htmldir ;; - -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \ - | --ht=*) - htmldir=$ac_optarg ;; - - -includedir | --includedir | --includedi | --included | --include \ - | --includ | --inclu | --incl | --inc) - ac_prev=includedir ;; - -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ - | --includ=* | --inclu=* | --incl=* | --inc=*) - includedir=$ac_optarg ;; - - -infodir | --infodir | --infodi | --infod | --info | --inf) - ac_prev=infodir ;; - -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) - infodir=$ac_optarg ;; - - -libdir | --libdir | --libdi | --libd) - ac_prev=libdir ;; - -libdir=* | --libdir=* | --libdi=* | --libd=*) - libdir=$ac_optarg ;; - - -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ - | --libexe | --libex | --libe) - ac_prev=libexecdir ;; - -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ - | --libexe=* | --libex=* | --libe=*) - libexecdir=$ac_optarg ;; - - -localedir | --localedir | --localedi | --localed | --locale) - ac_prev=localedir ;; - -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*) - localedir=$ac_optarg ;; - - -localstatedir | --localstatedir | --localstatedi | --localstated \ - | --localstate | --localstat | --localsta | --localst | --locals) - ac_prev=localstatedir ;; - -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ - | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*) - localstatedir=$ac_optarg ;; - - -mandir | --mandir | --mandi | --mand | --man | --ma | --m) - ac_prev=mandir ;; - -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) - mandir=$ac_optarg ;; - - -nfp | --nfp | --nf) - # Obsolete; use --without-fp. - with_fp=no ;; - - -no-create | --no-create | --no-creat | --no-crea | --no-cre \ - | --no-cr | --no-c | -n) - no_create=yes ;; - - -no-recursion | --no-recursion | --no-recursio | --no-recursi \ - | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) - no_recursion=yes ;; - - -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ - | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ - | --oldin | --oldi | --old | --ol | --o) - ac_prev=oldincludedir ;; - -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ - | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ - | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) - oldincludedir=$ac_optarg ;; - - -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) - ac_prev=prefix ;; - -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) - prefix=$ac_optarg ;; - - -program-prefix | --program-prefix | --program-prefi | --program-pref \ - | --program-pre | --program-pr | --program-p) - ac_prev=program_prefix ;; - -program-prefix=* | --program-prefix=* | --program-prefi=* \ - | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) - program_prefix=$ac_optarg ;; - - -program-suffix | --program-suffix | --program-suffi | --program-suff \ - | --program-suf | --program-su | --program-s) - ac_prev=program_suffix ;; - -program-suffix=* | --program-suffix=* | --program-suffi=* \ - | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) - program_suffix=$ac_optarg ;; - - -program-transform-name | --program-transform-name \ - | --program-transform-nam | --program-transform-na \ - | --program-transform-n | --program-transform- \ - | --program-transform | --program-transfor \ - | --program-transfo | --program-transf \ - | --program-trans | --program-tran \ - | --progr-tra | --program-tr | --program-t) - ac_prev=program_transform_name ;; - -program-transform-name=* | --program-transform-name=* \ - | --program-transform-nam=* | --program-transform-na=* \ - | --program-transform-n=* | --program-transform-=* \ - | --program-transform=* | --program-transfor=* \ - | --program-transfo=* | --program-transf=* \ - | --program-trans=* | --program-tran=* \ - | --progr-tra=* | --program-tr=* | --program-t=*) - program_transform_name=$ac_optarg ;; - - -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd) - ac_prev=pdfdir ;; - -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*) - pdfdir=$ac_optarg ;; - - -psdir | --psdir | --psdi | --psd | --ps) - ac_prev=psdir ;; - -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*) - psdir=$ac_optarg ;; - - -q | -quiet | --quiet | --quie | --qui | --qu | --q \ - | -silent | --silent | --silen | --sile | --sil) - silent=yes ;; - - -runstatedir | --runstatedir | --runstatedi | --runstated \ - | --runstate | --runstat | --runsta | --runst | --runs \ - | --run | --ru | --r) - ac_prev=runstatedir ;; - -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \ - | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \ - | --run=* | --ru=* | --r=*) - runstatedir=$ac_optarg ;; - - -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) - ac_prev=sbindir ;; - -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ - | --sbi=* | --sb=*) - sbindir=$ac_optarg ;; - - -sharedstatedir | --sharedstatedir | --sharedstatedi \ - | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ - | --sharedst | --shareds | --shared | --share | --shar \ - | --sha | --sh) - ac_prev=sharedstatedir ;; - -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ - | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ - | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ - | --sha=* | --sh=*) - sharedstatedir=$ac_optarg ;; - - -site | --site | --sit) - ac_prev=site ;; - -site=* | --site=* | --sit=*) - site=$ac_optarg ;; - - -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) - ac_prev=srcdir ;; - -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) - srcdir=$ac_optarg ;; - - -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ - | --syscon | --sysco | --sysc | --sys | --sy) - ac_prev=sysconfdir ;; - -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ - | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) - sysconfdir=$ac_optarg ;; - - -target | --target | --targe | --targ | --tar | --ta | --t) - ac_prev=target_alias ;; - -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) - target_alias=$ac_optarg ;; - - -v | -verbose | --verbose | --verbos | --verbo | --verb) - verbose=yes ;; - - -version | --version | --versio | --versi | --vers | -V) - ac_init_version=: ;; - - -with-* | --with-*) - ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` - # Reject names that are not valid shell variable names. - expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && - as_fn_error $? "invalid package name: '$ac_useropt'" - ac_useropt_orig=$ac_useropt - ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` - case $ac_user_opts in - *" -"with_$ac_useropt" -"*) ;; - *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig" - ac_unrecognized_sep=', ';; - esac - eval with_$ac_useropt=\$ac_optarg ;; - - -without-* | --without-*) - ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` - # Reject names that are not valid shell variable names. - expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && - as_fn_error $? "invalid package name: '$ac_useropt'" - ac_useropt_orig=$ac_useropt - ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` - case $ac_user_opts in - *" -"with_$ac_useropt" -"*) ;; - *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig" - ac_unrecognized_sep=', ';; - esac - eval with_$ac_useropt=no ;; - - --x) - # Obsolete; use --with-x. - with_x=yes ;; - - -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ - | --x-incl | --x-inc | --x-in | --x-i) - ac_prev=x_includes ;; - -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ - | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) - x_includes=$ac_optarg ;; - - -x-libraries | --x-libraries | --x-librarie | --x-librari \ - | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) - ac_prev=x_libraries ;; - -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ - | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) - x_libraries=$ac_optarg ;; - - -*) as_fn_error $? "unrecognized option: '$ac_option' -Try '$0 --help' for more information" - ;; - - *=*) - ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` - # Reject names that are not valid shell variable names. - case $ac_envvar in #( - '' | [0-9]* | *[!_$as_cr_alnum]* ) - as_fn_error $? "invalid variable name: '$ac_envvar'" ;; - esac - eval $ac_envvar=\$ac_optarg - export $ac_envvar ;; - - *) - # FIXME: should be removed in autoconf 3.0. - printf "%s\n" "$as_me: WARNING: you should use --build, --host, --target" >&2 - expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && - printf "%s\n" "$as_me: WARNING: invalid host type: $ac_option" >&2 - : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" - ;; - - esac -done - -if test -n "$ac_prev"; then - ac_option=--`echo $ac_prev | sed 's/_/-/g'` - as_fn_error $? "missing argument to $ac_option" -fi - -if test -n "$ac_unrecognized_opts"; then - case $enable_option_checking in - no) ;; - fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; - *) printf "%s\n" "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; - esac -fi - -# Check all directory arguments for consistency. -for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ - datadir sysconfdir sharedstatedir localstatedir includedir \ - oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ - libdir localedir mandir runstatedir -do - eval ac_val=\$$ac_var - # Remove trailing slashes. - case $ac_val in - */ ) - ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'` - eval $ac_var=\$ac_val;; - esac - # Be sure to have absolute directory names. - case $ac_val in - [\\/$]* | ?:[\\/]* ) continue;; - NONE | '' ) case $ac_var in *prefix ) continue;; esac;; - esac - as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" -done - -# There might be people who depend on the old broken behavior: '$host' -# used to hold the argument of --host etc. -# FIXME: To remove some day. -build=$build_alias -host=$host_alias -target=$target_alias - -# FIXME: To remove some day. -if test "x$host_alias" != x; then - if test "x$build_alias" = x; then - cross_compiling=maybe - elif test "x$build_alias" != "x$host_alias"; then - cross_compiling=yes - fi -fi - -ac_tool_prefix= -test -n "$host_alias" && ac_tool_prefix=$host_alias- - -test "$silent" = yes && exec 6>/dev/null - - -ac_pwd=`pwd` && test -n "$ac_pwd" && -ac_ls_di=`ls -di .` && -ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || - as_fn_error $? "working directory cannot be determined" -test "X$ac_ls_di" = "X$ac_pwd_ls_di" || - as_fn_error $? "pwd does not report name of working directory" - - -# Find the source files, if location was not specified. -if test -z "$srcdir"; then - ac_srcdir_defaulted=yes - # Try the directory containing this script, then the parent directory. - ac_confdir=`$as_dirname -- "$as_myself" || -$as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ - X"$as_myself" : 'X\(//\)[^/]' \| \ - X"$as_myself" : 'X\(//\)$' \| \ - X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || -printf "%s\n" X"$as_myself" | - sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ - s//\1/ - q - } - /^X\(\/\/\)[^/].*/{ - s//\1/ - q - } - /^X\(\/\/\)$/{ - s//\1/ - q - } - /^X\(\/\).*/{ - s//\1/ - q - } - s/.*/./; q'` - srcdir=$ac_confdir - if test ! -r "$srcdir/$ac_unique_file"; then - srcdir=.. - fi -else - ac_srcdir_defaulted=no -fi -if test ! -r "$srcdir/$ac_unique_file"; then - test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." - as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" -fi -ac_msg="sources are in $srcdir, but 'cd $srcdir' does not work" -ac_abs_confdir=`( - cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" - pwd)` -# When building in place, set srcdir=. -if test "$ac_abs_confdir" = "$ac_pwd"; then - srcdir=. -fi -# Remove unnecessary trailing slashes from srcdir. -# Double slashes in file names in object file debugging info -# mess up M-x gdb in Emacs. -case $srcdir in -*/) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;; -esac -for ac_var in $ac_precious_vars; do - eval ac_env_${ac_var}_set=\${${ac_var}+set} - eval ac_env_${ac_var}_value=\$${ac_var} - eval ac_cv_env_${ac_var}_set=\${${ac_var}+set} - eval ac_cv_env_${ac_var}_value=\$${ac_var} -done - -# -# Report the --help message. -# -if test "$ac_init_help" = "long"; then - # Omit some internal or obsolete options to make the list less imposing. - # This message is too long to be a string in the A/UX 3.1 sh. - cat <<_ACEOF -'configure' configures this package to adapt to many kinds of systems. - -Usage: $0 [OPTION]... [VAR=VALUE]... - -To assign environment variables (e.g., CC, CFLAGS...), specify them as -VAR=VALUE. See below for descriptions of some of the useful variables. - -Defaults for the options are specified in brackets. - -Configuration: - -h, --help display this help and exit - --help=short display options specific to this package - --help=recursive display the short help of all the included packages - -V, --version display version information and exit - -q, --quiet, --silent do not print 'checking ...' messages - --cache-file=FILE cache test results in FILE [disabled] - -C, --config-cache alias for '--cache-file=config.cache' - -n, --no-create do not create output files - --srcdir=DIR find the sources in DIR [configure dir or '..'] - -Installation directories: - --prefix=PREFIX install architecture-independent files in PREFIX - [$ac_default_prefix] - --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX - [PREFIX] - -By default, 'make install' will install all the files in -'$ac_default_prefix/bin', '$ac_default_prefix/lib' etc. You can specify -an installation prefix other than '$ac_default_prefix' using '--prefix', -for instance '--prefix=\$HOME'. - -For better control, use the options below. - -Fine tuning of the installation directories: - --bindir=DIR user executables [EPREFIX/bin] - --sbindir=DIR system admin executables [EPREFIX/sbin] - --libexecdir=DIR program executables [EPREFIX/libexec] - --sysconfdir=DIR read-only single-machine data [PREFIX/etc] - --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] - --localstatedir=DIR modifiable single-machine data [PREFIX/var] - --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run] - --libdir=DIR object code libraries [EPREFIX/lib] - --includedir=DIR C header files [PREFIX/include] - --oldincludedir=DIR C header files for non-gcc [/usr/include] - --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] - --datadir=DIR read-only architecture-independent data [DATAROOTDIR] - --infodir=DIR info documentation [DATAROOTDIR/info] - --localedir=DIR locale-dependent data [DATAROOTDIR/locale] - --mandir=DIR man documentation [DATAROOTDIR/man] - --docdir=DIR documentation root [DATAROOTDIR/doc/PACKAGE] - --htmldir=DIR html documentation [DOCDIR] - --dvidir=DIR dvi documentation [DOCDIR] - --pdfdir=DIR pdf documentation [DOCDIR] - --psdir=DIR ps documentation [DOCDIR] -_ACEOF - - cat <<\_ACEOF -_ACEOF -fi - -if test -n "$ac_init_help"; then - - cat <<\_ACEOF - -Report bugs to the package provider. -_ACEOF -ac_status=$? -fi - -if test "$ac_init_help" = "recursive"; then - # If there are subdirs, report their specific --help. - for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue - test -d "$ac_dir" || - { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } || - continue - ac_builddir=. - -case "$ac_dir" in -.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; -*) - ac_dir_suffix=/`printf "%s\n" "$ac_dir" | sed 's|^\.[\\/]||'` - # A ".." for each directory in $ac_dir_suffix. - ac_top_builddir_sub=`printf "%s\n" "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` - case $ac_top_builddir_sub in - "") ac_top_builddir_sub=. ac_top_build_prefix= ;; - *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; - esac ;; -esac -ac_abs_top_builddir=$ac_pwd -ac_abs_builddir=$ac_pwd$ac_dir_suffix -# for backward compatibility: -ac_top_builddir=$ac_top_build_prefix - -case $srcdir in - .) # We are building in place. - ac_srcdir=. - ac_top_srcdir=$ac_top_builddir_sub - ac_abs_top_srcdir=$ac_pwd ;; - [\\/]* | ?:[\\/]* ) # Absolute name. - ac_srcdir=$srcdir$ac_dir_suffix; - ac_top_srcdir=$srcdir - ac_abs_top_srcdir=$srcdir ;; - *) # Relative name. - ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix - ac_top_srcdir=$ac_top_build_prefix$srcdir - ac_abs_top_srcdir=$ac_pwd/$srcdir ;; -esac -ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix - - cd "$ac_dir" || { ac_status=$?; continue; } - # Check for configure.gnu first; this name is used for a wrapper for - # Metaconfig's "Configure" on case-insensitive file systems. - if test -f "$ac_srcdir/configure.gnu"; then - echo && - $SHELL "$ac_srcdir/configure.gnu" --help=recursive - elif test -f "$ac_srcdir/configure"; then - echo && - $SHELL "$ac_srcdir/configure" --help=recursive - else - printf "%s\n" "$as_me: WARNING: no configuration information is in $ac_dir" >&2 - fi || ac_status=$? - cd "$ac_pwd" || { ac_status=$?; break; } - done -fi - -test -n "$ac_init_help" && exit $ac_status -if $ac_init_version; then - cat <<\_ACEOF -configure -generated by GNU Autoconf 2.72 - -Copyright (C) 2023 Free Software Foundation, Inc. -This configure script is free software; the Free Software Foundation -gives unlimited permission to copy, distribute and modify it. -_ACEOF - exit -fi - -## ------------------------ ## -## Autoconf initialization. ## -## ------------------------ ## -ac_configure_args_raw= -for ac_arg -do - case $ac_arg in - *\'*) - ac_arg=`printf "%s\n" "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; - esac - as_fn_append ac_configure_args_raw " '$ac_arg'" -done - -case $ac_configure_args_raw in - *$as_nl*) - ac_safe_unquote= ;; - *) - ac_unsafe_z='|&;<>()$`\\"*?[ '' ' # This string ends in space, tab. - ac_unsafe_a="$ac_unsafe_z#~" - ac_safe_unquote="s/ '\\([^$ac_unsafe_a][^$ac_unsafe_z]*\\)'/ \\1/g" - ac_configure_args_raw=` printf "%s\n" "$ac_configure_args_raw" | sed "$ac_safe_unquote"`;; -esac - -cat >config.log <<_ACEOF -This file contains any messages produced by compilers while -running configure, to aid debugging if configure makes a mistake. - -It was created by $as_me, which was -generated by GNU Autoconf 2.72. Invocation command line was - - $ $0$ac_configure_args_raw - -_ACEOF -exec 5>>config.log -{ -cat <<_ASUNAME -## --------- ## -## Platform. ## -## --------- ## - -hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` -uname -m = `(uname -m) 2>/dev/null || echo unknown` -uname -r = `(uname -r) 2>/dev/null || echo unknown` -uname -s = `(uname -s) 2>/dev/null || echo unknown` -uname -v = `(uname -v) 2>/dev/null || echo unknown` - -/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` -/bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` - -/bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` -/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` -/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` -/usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown` -/bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` -/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` -/bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` - -_ASUNAME - -as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH -do - IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac - printf "%s\n" "PATH: $as_dir" - done -IFS=$as_save_IFS - -} >&5 - -cat >&5 <<_ACEOF - - -## ----------- ## -## Core tests. ## -## ----------- ## - -_ACEOF - - -# Keep a trace of the command line. -# Strip out --no-create and --no-recursion so they do not pile up. -# Strip out --silent because we don't want to record it for future runs. -# Also quote any args containing shell meta-characters. -# Make two passes to allow for proper duplicate-argument suppression. -ac_configure_args= -ac_configure_args0= -ac_configure_args1= -ac_must_keep_next=false -for ac_pass in 1 2 -do - for ac_arg - do - case $ac_arg in - -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; - -q | -quiet | --quiet | --quie | --qui | --qu | --q \ - | -silent | --silent | --silen | --sile | --sil) - continue ;; - *\'*) - ac_arg=`printf "%s\n" "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; - esac - case $ac_pass in - 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; - 2) - as_fn_append ac_configure_args1 " '$ac_arg'" - if test $ac_must_keep_next = true; then - ac_must_keep_next=false # Got value, back to normal. - else - case $ac_arg in - *=* | --config-cache | -C | -disable-* | --disable-* \ - | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ - | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ - | -with-* | --with-* | -without-* | --without-* | --x) - case "$ac_configure_args0 " in - "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; - esac - ;; - -* ) ac_must_keep_next=true ;; - esac - fi - as_fn_append ac_configure_args " '$ac_arg'" - ;; - esac - done -done -{ ac_configure_args0=; unset ac_configure_args0;} -{ ac_configure_args1=; unset ac_configure_args1;} - -# When interrupted or exit'd, cleanup temporary files, and complete -# config.log. We remove comments because anyway the quotes in there -# would cause problems or look ugly. -# WARNING: Use '\'' to represent an apostrophe within the trap. -# WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. -trap 'exit_status=$? - # Sanitize IFS. - IFS=" "" $as_nl" - # Save into config.log some information that might help in debugging. - { - echo - - printf "%s\n" "## ---------------- ## -## Cache variables. ## -## ---------------- ##" - echo - # The following way of writing the cache mishandles newlines in values, -( - for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do - eval ac_val=\$$ac_var - case $ac_val in #( - *${as_nl}*) - case $ac_var in #( - *_cv_*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 -printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; - esac - case $ac_var in #( - _ | IFS | as_nl) ;; #( - BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( - *) { eval $ac_var=; unset $ac_var;} ;; - esac ;; - esac - done - (set) 2>&1 | - case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #( - *${as_nl}ac_space=\ *) - sed -n \ - "s/'\''/'\''\\\\'\'''\''/g; - s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p" - ;; #( - *) - sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" - ;; - esac | - sort -) - echo - - printf "%s\n" "## ----------------- ## -## Output variables. ## -## ----------------- ##" - echo - for ac_var in $ac_subst_vars - do - eval ac_val=\$$ac_var - case $ac_val in - *\'\''*) ac_val=`printf "%s\n" "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; - esac - printf "%s\n" "$ac_var='\''$ac_val'\''" - done | sort - echo - - if test -n "$ac_subst_files"; then - printf "%s\n" "## ------------------- ## -## File substitutions. ## -## ------------------- ##" - echo - for ac_var in $ac_subst_files - do - eval ac_val=\$$ac_var - case $ac_val in - *\'\''*) ac_val=`printf "%s\n" "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; - esac - printf "%s\n" "$ac_var='\''$ac_val'\''" - done | sort - echo - fi - - if test -s confdefs.h; then - printf "%s\n" "## ----------- ## -## confdefs.h. ## -## ----------- ##" - echo - cat confdefs.h - echo - fi - test "$ac_signal" != 0 && - printf "%s\n" "$as_me: caught signal $ac_signal" - printf "%s\n" "$as_me: exit $exit_status" - } >&5 - rm -f core *.core core.conftest.* && - rm -f -r conftest* confdefs* conf$$* $ac_clean_files && - exit $exit_status -' 0 -for ac_signal in 1 2 13 15; do - trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal -done -ac_signal=0 - -# confdefs.h avoids OS command line length limits that DEFS can exceed. -rm -f -r conftest* confdefs.h - -printf "%s\n" "/* confdefs.h */" > confdefs.h - -# Predefined preprocessor variables. - -printf "%s\n" "#define PACKAGE_NAME \"$PACKAGE_NAME\"" >>confdefs.h - -printf "%s\n" "#define PACKAGE_TARNAME \"$PACKAGE_TARNAME\"" >>confdefs.h - -printf "%s\n" "#define PACKAGE_VERSION \"$PACKAGE_VERSION\"" >>confdefs.h - -printf "%s\n" "#define PACKAGE_STRING \"$PACKAGE_STRING\"" >>confdefs.h - -printf "%s\n" "#define PACKAGE_BUGREPORT \"$PACKAGE_BUGREPORT\"" >>confdefs.h - -printf "%s\n" "#define PACKAGE_URL \"$PACKAGE_URL\"" >>confdefs.h - - -# Let the site file select an alternate cache file if it wants to. -# Prefer an explicitly selected file to automatically selected ones. -if test -n "$CONFIG_SITE"; then - ac_site_files="$CONFIG_SITE" -elif test "x$prefix" != xNONE; then - ac_site_files="$prefix/share/config.site $prefix/etc/config.site" -else - ac_site_files="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site" -fi - -for ac_site_file in $ac_site_files -do - case $ac_site_file in #( - */*) : - ;; #( - *) : - ac_site_file=./$ac_site_file ;; -esac - if test -f "$ac_site_file" && test -r "$ac_site_file"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 -printf "%s\n" "$as_me: loading site script $ac_site_file" >&6;} - sed 's/^/| /' "$ac_site_file" >&5 - . "$ac_site_file" \ - || { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} -as_fn_error $? "failed to load site script $ac_site_file -See 'config.log' for more details" "$LINENO" 5; } - fi -done - -if test -r "$cache_file"; then - # Some versions of bash will fail to source /dev/null (special files - # actually), so we avoid doing that. DJGPP emulates it as a regular file. - if test /dev/null != "$cache_file" && test -f "$cache_file"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 -printf "%s\n" "$as_me: loading cache $cache_file" >&6;} - case $cache_file in - [\\/]* | ?:[\\/]* ) . "$cache_file";; - *) . "./$cache_file";; - esac - fi -else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 -printf "%s\n" "$as_me: creating cache $cache_file" >&6;} - >$cache_file -fi - -# Check that the precious variables saved in the cache have kept the same -# value. -ac_cache_corrupted=false -for ac_var in $ac_precious_vars; do - eval ac_old_set=\$ac_cv_env_${ac_var}_set - eval ac_new_set=\$ac_env_${ac_var}_set - eval ac_old_val=\$ac_cv_env_${ac_var}_value - eval ac_new_val=\$ac_env_${ac_var}_value - case $ac_old_set,$ac_new_set in - set,) - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: '$ac_var' was set to '$ac_old_val' in the previous run" >&5 -printf "%s\n" "$as_me: error: '$ac_var' was set to '$ac_old_val' in the previous run" >&2;} - ac_cache_corrupted=: ;; - ,set) - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: '$ac_var' was not set in the previous run" >&5 -printf "%s\n" "$as_me: error: '$ac_var' was not set in the previous run" >&2;} - ac_cache_corrupted=: ;; - ,);; - *) - if test "x$ac_old_val" != "x$ac_new_val"; then - # differences in whitespace do not lead to failure. - ac_old_val_w=`echo x $ac_old_val` - ac_new_val_w=`echo x $ac_new_val` - if test "$ac_old_val_w" != "$ac_new_val_w"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: '$ac_var' has changed since the previous run:" >&5 -printf "%s\n" "$as_me: error: '$ac_var' has changed since the previous run:" >&2;} - ac_cache_corrupted=: - else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in '$ac_var' since the previous run:" >&5 -printf "%s\n" "$as_me: warning: ignoring whitespace changes in '$ac_var' since the previous run:" >&2;} - eval $ac_var=\$ac_old_val - fi - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: former value: '$ac_old_val'" >&5 -printf "%s\n" "$as_me: former value: '$ac_old_val'" >&2;} - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: current value: '$ac_new_val'" >&5 -printf "%s\n" "$as_me: current value: '$ac_new_val'" >&2;} - fi;; - esac - # Pass precious variables to config.status. - if test "$ac_new_set" = set; then - case $ac_new_val in - *\'*) ac_arg=$ac_var=`printf "%s\n" "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; - *) ac_arg=$ac_var=$ac_new_val ;; - esac - case " $ac_configure_args " in - *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. - *) as_fn_append ac_configure_args " '$ac_arg'" ;; - esac - fi -done -if $ac_cache_corrupted; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 -printf "%s\n" "$as_me: error: changes in the environment can compromise the build" >&2;} - as_fn_error $? "run '${MAKE-make} distclean' and/or 'rm $cache_file' - and start over" "$LINENO" 5 -fi -## -------------------- ## -## Main body of script. ## -## -------------------- ## - -ac_ext=c -ac_cpp='$CPP $CPPFLAGS' -ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' -ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' -ac_compiler_gnu=$ac_cv_c_compiler_gnu - - - -for ac_prog in make -do - # Extract the first word of "$ac_prog", so it can be a program name with args. -set dummy $ac_prog; ac_word=$2 -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -printf %s "checking for $ac_word... " >&6; } -if test ${ac_cv_prog_MAKE+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) if test -n "$MAKE"; then - ac_cv_prog_MAKE="$MAKE" # Let the user override the test. -else -as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH -do - IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac - for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then - ac_cv_prog_MAKE="$ac_prog" - printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 - break 2 - fi -done - done -IFS=$as_save_IFS - -fi ;; -esac -fi -MAKE=$ac_cv_prog_MAKE -if test -n "$MAKE"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $MAKE" >&5 -printf "%s\n" "$MAKE" >&6; } -else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } -fi - - - test -n "$MAKE" && break -done -test -n "$MAKE" || MAKE="no" - -if test "$MAKE" == "no" -then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: without GNU Make, you have to inspect 'Makefile' and deduce build targets yourself." >&5 -printf "%s\n" "$as_me: without GNU Make, you have to inspect 'Makefile' and deduce build targets yourself." >&6;} -fi - -for ac_prog in git -do - # Extract the first word of "$ac_prog", so it can be a program name with args. -set dummy $ac_prog; ac_word=$2 -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -printf %s "checking for $ac_word... " >&6; } -if test ${ac_cv_prog_GIT+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) if test -n "$GIT"; then - ac_cv_prog_GIT="$GIT" # Let the user override the test. -else -as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH -do - IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac - for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then - ac_cv_prog_GIT="$ac_prog" - printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 - break 2 - fi -done - done -IFS=$as_save_IFS - -fi ;; -esac -fi -GIT=$ac_cv_prog_GIT -if test -n "$GIT"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $GIT" >&5 -printf "%s\n" "$GIT" >&6; } -else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } -fi - - - test -n "$GIT" && break -done -test -n "$GIT" || GIT="no" - -if test "$GIT" == "no" -then : - as_fn_error $? "install Git, before continuing." "$LINENO" 5 -fi - -for ac_prog in python3 -do - # Extract the first word of "$ac_prog", so it can be a program name with args. -set dummy $ac_prog; ac_word=$2 -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -printf %s "checking for $ac_word... " >&6; } -if test ${ac_cv_prog_PYTHON3+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) if test -n "$PYTHON3"; then - ac_cv_prog_PYTHON3="$PYTHON3" # Let the user override the test. -else -as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH -do - IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac - for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then - ac_cv_prog_PYTHON3="$ac_prog" - printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 - break 2 - fi -done - done -IFS=$as_save_IFS - -fi ;; -esac -fi -PYTHON3=$ac_cv_prog_PYTHON3 -if test -n "$PYTHON3"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $PYTHON3" >&5 -printf "%s\n" "$PYTHON3" >&6; } -else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } -fi - - - test -n "$PYTHON3" && break -done -test -n "$PYTHON3" || PYTHON3="no" - -if test "$PYTHON3" == "no" -then : - as_fn_error $? "install Python 3, before continuing." "$LINENO" 5 -fi - -# required in Makefile to ensure proper path resolution during preprocessing -# realpath is not available on macOS -for ac_prog in realpath -do - # Extract the first word of "$ac_prog", so it can be a program name with args. -set dummy $ac_prog; ac_word=$2 -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -printf %s "checking for $ac_word... " >&6; } -if test ${ac_cv_prog_REALPATH+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) if test -n "$REALPATH"; then - ac_cv_prog_REALPATH="$REALPATH" # Let the user override the test. -else -as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH -do - IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac - for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then - ac_cv_prog_REALPATH="$ac_prog" - printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 - break 2 - fi -done - done -IFS=$as_save_IFS - -fi ;; -esac -fi -REALPATH=$ac_cv_prog_REALPATH -if test -n "$REALPATH"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $REALPATH" >&5 -printf "%s\n" "$REALPATH" >&6; } -else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 -printf "%s\n" "no" >&6; } -fi - - - test -n "$REALPATH" && break -done -test -n "$REALPATH" || REALPATH="no" - -if test "$REALPATH" == "no" -then : - as_fn_error $? "set a persistent alias for 'realpath', before continuing, e.g. - -alias='python3 -c \"import pathlib,sys;print(pathlib.Path(sys.argv[1]).resolve())\"'\" -" "$LINENO" 5 -fi - -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: initializing python3 venv..." >&5 -printf "%s\n" "$as_me: initializing python3 venv..." >&6;} -make .venv - -cat >confcache <<\_ACEOF -# This file is a shell script that caches the results of configure -# tests run on this system so they can be shared between configure -# scripts and configure runs, see configure's option --config-cache. -# It is not useful on other systems. If it contains results you don't -# want to keep, you may remove or edit it. -# -# config.status only pays attention to the cache file if you give it -# the --recheck option to rerun configure. -# -# 'ac_cv_env_foo' variables (set or unset) will be overridden when -# loading this file, other *unset* 'ac_cv_foo' will be assigned the -# following values. - -_ACEOF - -# The following way of writing the cache mishandles newlines in values, -# but we know of no workaround that is simple, portable, and efficient. -# So, we kill variables containing newlines. -# Ultrix sh set writes to stderr and can't be redirected directly, -# and sets the high bit in the cache file unless we assign to the vars. -( - for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do - eval ac_val=\$$ac_var - case $ac_val in #( - *${as_nl}*) - case $ac_var in #( - *_cv_*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 -printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; - esac - case $ac_var in #( - _ | IFS | as_nl) ;; #( - BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( - *) { eval $ac_var=; unset $ac_var;} ;; - esac ;; - esac - done - - (set) 2>&1 | - case $as_nl`(ac_space=' '; set) 2>&1` in #( - *${as_nl}ac_space=\ *) - # 'set' does not quote correctly, so add quotes: double-quote - # substitution turns \\\\ into \\, and sed turns \\ into \. - sed -n \ - "s/'/'\\\\''/g; - s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" - ;; #( - *) - # 'set' quotes correctly as required by POSIX, so do not add quotes. - sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" - ;; - esac | - sort -) | - sed ' - /^ac_cv_env_/b end - t clear - :clear - s/^\([^=]*\)=\(.*[{}].*\)$/test ${\1+y} || &/ - t end - s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ - :end' >>confcache -if diff "$cache_file" confcache >/dev/null 2>&1; then :; else - if test -w "$cache_file"; then - if test "x$cache_file" != "x/dev/null"; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 -printf "%s\n" "$as_me: updating cache $cache_file" >&6;} - if test ! -f "$cache_file" || test -h "$cache_file"; then - cat confcache >"$cache_file" - else - case $cache_file in #( - */* | ?:*) - mv -f confcache "$cache_file"$$ && - mv -f "$cache_file"$$ "$cache_file" ;; #( - *) - mv -f confcache "$cache_file" ;; - esac - fi - fi - else - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 -printf "%s\n" "$as_me: not updating unwritable cache $cache_file" >&6;} - fi -fi -rm -f confcache - -test "x$prefix" = xNONE && prefix=$ac_default_prefix -# Let make expand exec_prefix. -test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' - -# Transform confdefs.h into DEFS. -# Protect against shell expansion while executing Makefile rules. -# Protect against Makefile macro expansion. -# -# If the first sed substitution is executed (which looks for macros that -# take arguments), then branch to the quote section. Otherwise, -# look for a macro that doesn't take arguments. -ac_script=' -:mline -/\\$/{ - N - s,\\\n,, - b mline -} -t clear -:clear -s/^[ ]*#[ ]*define[ ][ ]*\([^ (][^ (]*([^)]*)\)[ ]*\(.*\)/-D\1=\2/g -t quote -s/^[ ]*#[ ]*define[ ][ ]*\([^ ][^ ]*\)[ ]*\(.*\)/-D\1=\2/g -t quote -b any -:quote -s/[][ `~#$^&*(){}\\|;'\''"<>?]/\\&/g -s/\$/$$/g -H -:any -${ - g - s/^\n// - s/\n/ /g - p -} -' -DEFS=`sed -n "$ac_script" confdefs.h` - - -ac_libobjs= -ac_ltlibobjs= -U= -for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue - # 1. Remove the extension, and $U if already installed. - ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' - ac_i=`printf "%s\n" "$ac_i" | sed "$ac_script"` - # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR - # will be set to the directory where LIBOBJS objects are built. - as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" - as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo' -done -LIBOBJS=$ac_libobjs - -LTLIBOBJS=$ac_ltlibobjs - - - -: "${CONFIG_STATUS=./config.status}" -ac_write_fail=0 -ac_clean_files_save=$ac_clean_files -ac_clean_files="$ac_clean_files $CONFIG_STATUS" -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 -printf "%s\n" "$as_me: creating $CONFIG_STATUS" >&6;} -as_write_fail=0 -cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 -#! $SHELL -# Generated by $as_me. -# Run this file to recreate the current configuration. -# Compiler output produced by configure, useful for debugging -# configure, is in config.log if it exists. - -debug=false -ac_cs_recheck=false -ac_cs_silent=false - -SHELL=\${CONFIG_SHELL-$SHELL} -export SHELL -_ASEOF -cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 -## -------------------- ## -## M4sh Initialization. ## -## -------------------- ## - -# Be more Bourne compatible -DUALCASE=1; export DUALCASE # for MKS sh -if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 -then : - emulate sh - NULLCMD=: - # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which - # is contrary to our usage. Disable this feature. - alias -g '${1+"$@"}'='"$@"' - setopt NO_GLOB_SUBST -else case e in #( - e) case `(set -o) 2>/dev/null` in #( - *posix*) : - set -o posix ;; #( - *) : - ;; -esac ;; -esac -fi - - - -# Reset variables that may have inherited troublesome values from -# the environment. - -# IFS needs to be set, to space, tab, and newline, in precisely that order. -# (If _AS_PATH_WALK were called with IFS unset, it would have the -# side effect of setting IFS to empty, thus disabling word splitting.) -# Quoting is to prevent editors from complaining about space-tab. -as_nl=' -' -export as_nl -IFS=" "" $as_nl" - -PS1='$ ' -PS2='> ' -PS4='+ ' - -# Ensure predictable behavior from utilities with locale-dependent output. -LC_ALL=C -export LC_ALL -LANGUAGE=C -export LANGUAGE - -# We cannot yet rely on "unset" to work, but we need these variables -# to be unset--not just set to an empty or harmless value--now, to -# avoid bugs in old shells (e.g. pre-3.0 UWIN ksh). This construct -# also avoids known problems related to "unset" and subshell syntax -# in other old shells (e.g. bash 2.01 and pdksh 5.2.14). -for as_var in BASH_ENV ENV MAIL MAILPATH CDPATH -do eval test \${$as_var+y} \ - && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : -done - -# Ensure that fds 0, 1, and 2 are open. -if (exec 3>&0) 2>/dev/null; then :; else exec 0&1) 2>/dev/null; then :; else exec 1>/dev/null; fi -if (exec 3>&2) ; then :; else exec 2>/dev/null; fi - -# The user is always right. -if ${PATH_SEPARATOR+false} :; then - PATH_SEPARATOR=: - (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { - (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || - PATH_SEPARATOR=';' - } -fi - - -# Find who we are. Look in the path if we contain no directory separator. -as_myself= -case $0 in #(( - *[\\/]* ) as_myself=$0 ;; - *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH -do - IFS=$as_save_IFS - case $as_dir in #((( - '') as_dir=./ ;; - */) ;; - *) as_dir=$as_dir/ ;; - esac - test -r "$as_dir$0" && as_myself=$as_dir$0 && break - done -IFS=$as_save_IFS - - ;; -esac -# We did not find ourselves, most probably we were run as 'sh COMMAND' -# in which case we are not to be found in the path. -if test "x$as_myself" = x; then - as_myself=$0 -fi -if test ! -f "$as_myself"; then - printf "%s\n" "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 - exit 1 -fi - - - -# as_fn_error STATUS ERROR [LINENO LOG_FD] -# ---------------------------------------- -# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are -# provided, also output the error to LOG_FD, referencing LINENO. Then exit the -# script with STATUS, using 1 if that was 0. -as_fn_error () -{ - as_status=$1; test $as_status -eq 0 && as_status=1 - if test "$4"; then - as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 - fi - printf "%s\n" "$as_me: error: $2" >&2 - as_fn_exit $as_status -} # as_fn_error - - -# as_fn_set_status STATUS -# ----------------------- -# Set $? to STATUS, without forking. -as_fn_set_status () -{ - return $1 -} # as_fn_set_status - -# as_fn_exit STATUS -# ----------------- -# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. -as_fn_exit () -{ - set +e - as_fn_set_status $1 - exit $1 -} # as_fn_exit - -# as_fn_unset VAR -# --------------- -# Portably unset VAR. -as_fn_unset () -{ - { eval $1=; unset $1;} -} -as_unset=as_fn_unset - -# as_fn_append VAR VALUE -# ---------------------- -# Append the text in VALUE to the end of the definition contained in VAR. Take -# advantage of any shell optimizations that allow amortized linear growth over -# repeated appends, instead of the typical quadratic growth present in naive -# implementations. -if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null -then : - eval 'as_fn_append () - { - eval $1+=\$2 - }' -else case e in #( - e) as_fn_append () - { - eval $1=\$$1\$2 - } ;; -esac -fi # as_fn_append - -# as_fn_arith ARG... -# ------------------ -# Perform arithmetic evaluation on the ARGs, and store the result in the -# global $as_val. Take advantage of shells that can avoid forks. The arguments -# must be portable across $(()) and expr. -if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null -then : - eval 'as_fn_arith () - { - as_val=$(( $* )) - }' -else case e in #( - e) as_fn_arith () - { - as_val=`expr "$@" || test $? -eq 1` - } ;; -esac -fi # as_fn_arith - - -if expr a : '\(a\)' >/dev/null 2>&1 && - test "X`expr 00001 : '.*\(...\)'`" = X001; then - as_expr=expr -else - as_expr=false -fi - -if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then - as_basename=basename -else - as_basename=false -fi - -if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then - as_dirname=dirname -else - as_dirname=false -fi - -as_me=`$as_basename -- "$0" || -$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ - X"$0" : 'X\(//\)$' \| \ - X"$0" : 'X\(/\)' \| . 2>/dev/null || -printf "%s\n" X/"$0" | - sed '/^.*\/\([^/][^/]*\)\/*$/{ - s//\1/ - q - } - /^X\/\(\/\/\)$/{ - s//\1/ - q - } - /^X\/\(\/\).*/{ - s//\1/ - q - } - s/.*/./; q'` - -# Avoid depending upon Character Ranges. -as_cr_letters='abcdefghijklmnopqrstuvwxyz' -as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' -as_cr_Letters=$as_cr_letters$as_cr_LETTERS -as_cr_digits='0123456789' -as_cr_alnum=$as_cr_Letters$as_cr_digits - - -# Determine whether it's possible to make 'echo' print without a newline. -# These variables are no longer used directly by Autoconf, but are AC_SUBSTed -# for compatibility with existing Makefiles. -ECHO_C= ECHO_N= ECHO_T= -case `echo -n x` in #((((( --n*) - case `echo 'xy\c'` in - *c*) ECHO_T=' ';; # ECHO_T is single tab character. - xy) ECHO_C='\c';; - *) echo `echo ksh88 bug on AIX 6.1` > /dev/null - ECHO_T=' ';; - esac;; -*) - ECHO_N='-n';; -esac - -# For backward compatibility with old third-party macros, we provide -# the shell variables $as_echo and $as_echo_n. New code should use -# AS_ECHO(["message"]) and AS_ECHO_N(["message"]), respectively. -as_echo='printf %s\n' -as_echo_n='printf %s' - -rm -f conf$$ conf$$.exe conf$$.file -if test -d conf$$.dir; then - rm -f conf$$.dir/conf$$.file -else - rm -f conf$$.dir - mkdir conf$$.dir 2>/dev/null -fi -if (echo >conf$$.file) 2>/dev/null; then - if ln -s conf$$.file conf$$ 2>/dev/null; then - as_ln_s='ln -s' - # ... but there are two gotchas: - # 1) On MSYS, both 'ln -s file dir' and 'ln file dir' fail. - # 2) DJGPP < 2.04 has no symlinks; 'ln -s' creates a wrapper executable. - # In both cases, we have to default to 'cp -pR'. - ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || - as_ln_s='cp -pR' - elif ln conf$$.file conf$$ 2>/dev/null; then - as_ln_s=ln - else - as_ln_s='cp -pR' - fi -else - as_ln_s='cp -pR' -fi -rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file -rmdir conf$$.dir 2>/dev/null - - -# as_fn_mkdir_p -# ------------- -# Create "$as_dir" as a directory, including parents if necessary. -as_fn_mkdir_p () -{ - - case $as_dir in #( - -*) as_dir=./$as_dir;; - esac - test -d "$as_dir" || eval $as_mkdir_p || { - as_dirs= - while :; do - case $as_dir in #( - *\'*) as_qdir=`printf "%s\n" "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( - *) as_qdir=$as_dir;; - esac - as_dirs="'$as_qdir' $as_dirs" - as_dir=`$as_dirname -- "$as_dir" || -$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ - X"$as_dir" : 'X\(//\)[^/]' \| \ - X"$as_dir" : 'X\(//\)$' \| \ - X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || -printf "%s\n" X"$as_dir" | - sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ - s//\1/ - q - } - /^X\(\/\/\)[^/].*/{ - s//\1/ - q - } - /^X\(\/\/\)$/{ - s//\1/ - q - } - /^X\(\/\).*/{ - s//\1/ - q - } - s/.*/./; q'` - test -d "$as_dir" && break - done - test -z "$as_dirs" || eval "mkdir $as_dirs" - } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" - - -} # as_fn_mkdir_p -if mkdir -p . 2>/dev/null; then - as_mkdir_p='mkdir -p "$as_dir"' -else - test -d ./-p && rmdir ./-p - as_mkdir_p=false -fi - - -# as_fn_executable_p FILE -# ----------------------- -# Test if FILE is an executable regular file. -as_fn_executable_p () -{ - test -f "$1" && test -x "$1" -} # as_fn_executable_p -as_test_x='test -x' -as_executable_p=as_fn_executable_p - -# Sed expression to map a string onto a valid CPP name. -as_sed_cpp="y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g" -as_tr_cpp="eval sed '$as_sed_cpp'" # deprecated - -# Sed expression to map a string onto a valid variable name. -as_sed_sh="y%*+%pp%;s%[^_$as_cr_alnum]%_%g" -as_tr_sh="eval sed '$as_sed_sh'" # deprecated - - -exec 6>&1 -## ----------------------------------- ## -## Main body of $CONFIG_STATUS script. ## -## ----------------------------------- ## -_ASEOF -test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1 - -cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 -# Save the log message, to keep $0 and so on meaningful, and to -# report actual input values of CONFIG_FILES etc. instead of their -# values after options handling. -ac_log=" -This file was extended by $as_me, which was -generated by GNU Autoconf 2.72. Invocation command line was - - CONFIG_FILES = $CONFIG_FILES - CONFIG_HEADERS = $CONFIG_HEADERS - CONFIG_LINKS = $CONFIG_LINKS - CONFIG_COMMANDS = $CONFIG_COMMANDS - $ $0 $@ - -on `(hostname || uname -n) 2>/dev/null | sed 1q` -" - -_ACEOF - - - - -cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 -# Files that config.status was made for. - -_ACEOF - -cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 -ac_cs_usage="\ -'$as_me' instantiates files and other configuration actions -from templates according to the current configuration. Unless the files -and actions are specified as TAGs, all are instantiated by default. - -Usage: $0 [OPTION]... [TAG]... - - -h, --help print this help, then exit - -V, --version print version number and configuration settings, then exit - --config print configuration, then exit - -q, --quiet, --silent - do not print progress messages - -d, --debug don't remove temporary files - --recheck update $as_me by reconfiguring in the same conditions - -Report bugs to the package provider." - -_ACEOF -ac_cs_config=`printf "%s\n" "$ac_configure_args" | sed "$ac_safe_unquote"` -ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\''/g"` -cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 -ac_cs_config='$ac_cs_config_escaped' -ac_cs_version="\\ -config.status -configured by $0, generated by GNU Autoconf 2.72, - with options \\"\$ac_cs_config\\" - -Copyright (C) 2023 Free Software Foundation, Inc. -This config.status script is free software; the Free Software Foundation -gives unlimited permission to copy, distribute and modify it." - -ac_pwd='$ac_pwd' -srcdir='$srcdir' -test -n "\$AWK" || AWK=awk -_ACEOF - -cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 -# The default lists apply if the user does not specify any file. -ac_need_defaults=: -while test $# != 0 -do - case $1 in - --*=?*) - ac_option=`expr "X$1" : 'X\([^=]*\)='` - ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` - ac_shift=: - ;; - --*=) - ac_option=`expr "X$1" : 'X\([^=]*\)='` - ac_optarg= - ac_shift=: - ;; - *) - ac_option=$1 - ac_optarg=$2 - ac_shift=shift - ;; - esac - - case $ac_option in - # Handling of the options. - -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) - ac_cs_recheck=: ;; - --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) - printf "%s\n" "$ac_cs_version"; exit ;; - --config | --confi | --conf | --con | --co | --c ) - printf "%s\n" "$ac_cs_config"; exit ;; - --debug | --debu | --deb | --de | --d | -d ) - debug=: ;; - --he | --h | --help | --hel | -h ) - printf "%s\n" "$ac_cs_usage"; exit ;; - -q | -quiet | --quiet | --quie | --qui | --qu | --q \ - | -silent | --silent | --silen | --sile | --sil | --si | --s) - ac_cs_silent=: ;; - - # This is an error. - -*) as_fn_error $? "unrecognized option: '$1' -Try '$0 --help' for more information." ;; - - *) as_fn_append ac_config_targets " $1" - ac_need_defaults=false ;; - - esac - shift -done - -ac_configure_extra_args= - -if $ac_cs_silent; then - exec 6>/dev/null - ac_configure_extra_args="$ac_configure_extra_args --silent" -fi - -_ACEOF -cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 -if \$ac_cs_recheck; then - set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion - shift - \printf "%s\n" "running CONFIG_SHELL=$SHELL \$*" >&6 - CONFIG_SHELL='$SHELL' - export CONFIG_SHELL - exec "\$@" -fi - -_ACEOF -cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 -exec 5>>config.log -{ - echo - sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX -## Running $as_me. ## -_ASBOX - printf "%s\n" "$ac_log" -} >&5 - -_ACEOF -cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 -_ACEOF - -cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 - -# Handling of arguments. -for ac_config_target in $ac_config_targets -do - case $ac_config_target in - - *) as_fn_error $? "invalid argument: '$ac_config_target'" "$LINENO" 5;; - esac -done - - -as_fn_exit 0 -_ACEOF -ac_clean_files=$ac_clean_files_save - -test $ac_write_fail = 0 || - as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5 - - -# configure is writing to config.log, and then calls config.status. -# config.status does its own redirection, appending to config.log. -# Unfortunately, on DOS this fails, as config.log is still kept open -# by configure, so config.status won't be able to write to it; its -# output is simply discarded. So we exec the FD to /dev/null, -# effectively closing config.log, so it can be properly (re)opened and -# appended to by config.status. When coming back to configure, we -# need to make the FD available again. -if test "$no_create" != yes; then - ac_cs_success=: - ac_config_status_args= - test "$silent" = yes && - ac_config_status_args="$ac_config_status_args --quiet" - exec 5>/dev/null - $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false - exec 5>>config.log - # Use ||, not &&, to avoid exiting from the if with $? = 1, which - # would make configure fail if this is the last instruction. - $ac_cs_success || as_fn_exit 1 -fi -if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 -printf "%s\n" "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} -fi - - diff --git a/configure.ac b/configure.ac deleted file mode 100644 index e8c53d1..0000000 --- a/configure.ac +++ /dev/null @@ -1,27 +0,0 @@ -AC_INIT - -AC_CHECK_PROGS([MAKE], [make], [no]) -AS_IF([test "$MAKE" == "no"], - [AC_MSG_NOTICE([without GNU Make, you have to inspect 'Makefile' and deduce build targets yourself.])]) - -AC_CHECK_PROGS([GIT], [git], [no]) -AS_IF([test "$GIT" == "no"], - [AC_MSG_ERROR([install Git, before continuing.])]) - -AC_CHECK_PROGS([PYTHON3], [python3], [no]) -AS_IF([test "$PYTHON3" == "no"], - [AC_MSG_ERROR([install Python 3, before continuing.])]) - -# required in Makefile to ensure proper path resolution during preprocessing -# realpath is not available on macOS -AC_CHECK_PROGS([REALPATH], [realpath], [no]) -AS_IF([test "$REALPATH" == "no"], - [AC_MSG_ERROR([set a persistent alias for 'realpath', before continuing, e.g. - -alias='python3 -c "import pathlib,sys;print(pathlib.Path(sys.argv[[1]]).resolve())"'" -])]) - -AC_MSG_NOTICE([initializing python3 venv...]) -make .venv - -AC_OUTPUT diff --git a/pyproject.toml b/pyproject.toml index 423e4e5..b3c3d30 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,6 @@ strict = true max_line_length = 80 aggressive = 3 recursive = true -in-place = true [tool.setuptools_scm] diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index 15e7cea..0000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,25 +0,0 @@ --i https://pypi.org/simple -astroid==3.3.9; python_full_version >= '3.9.0' -autopep8==2.3.2; python_version >= '3.9' -build==1.2.2.post1; python_version >= '3.8' --e . -certifi==2025.4.26; python_version >= '3.6' -colorama==0.4.6; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6' -dill==0.4.0; python_version >= '3.8' -distlib==0.3.9 -filelock==3.18.0; python_version >= '3.9' -isort==6.0.1; python_full_version >= '3.9.0' -mccabe==0.7.0; python_version >= '3.6' -mypy==1.15.0; python_version >= '3.9' -mypy-extensions==1.1.0; python_version >= '3.8' -packaging==25.0; python_version >= '3.8' -pipenv==2025.0.2; python_version >= '3.9' -platformdirs==4.3.7; python_version >= '3.9' -pycodestyle==2.13.0; python_version >= '3.9' -pylint==3.3.6; python_full_version >= '3.9.0' -pyproject-hooks==1.2.0; python_version >= '3.7' -setuptools==80.3.0; python_version >= '3.9' -setuptools-scm==8.2.0; python_version >= '3.8' -tomlkit==0.13.2; python_version >= '3.8' -typing-extensions==4.13.2; python_version >= '3.8' -virtualenv==20.30.0; python_version >= '3.8' diff --git a/tox.ini b/tox.ini index 0f03dd8..d4a94e1 100644 --- a/tox.ini +++ b/tox.ini @@ -31,9 +31,9 @@ commands = description = run type check on code base labels = static deps = - black + autopep8 commands = - black --check src tests + autopep8 --diff --exit-code src tests [testenv:unit-py3{9-13}] description = run type check on code base From d296f135d1e50c487e3239265bd83b4ade0d1a07 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 6 Jun 2026 14:14:02 +0200 Subject: [PATCH 66/92] chore: convert TODO to MIME TODO format --- TODO | 136 ++++++++++++++++++++++++----------------------------------- 1 file changed, 54 insertions(+), 82 deletions(-) diff --git a/TODO b/TODO index e747197..920b16c 100644 --- a/TODO +++ b/TODO @@ -1,91 +1,52 @@ -# TODO List for esm-logging - -This is a poor-man's issue tracker. I am not primarily a GitHub user so don't -want to commit to their issue tracking feature, but my primary SVC service -provider (Bitbucket) only offers paid integration into their issue tracker -(Jira). I don't have the time (and patience) at the moment to analyze the best -approach, so this file will have to suffice. - -It's a very simple concept: Track any issues (features, bugfixes, hotfixes) in -here, assign a sequential number to it and use that number when branching. - -I will try to develop a format so that I can parse the file later on, should I -decide to migrate to a real issue tracker. It's probably going to be Bugzilla, -but for that my html-theme-ref project needs to stabilize first. - -## Format Specification - -The file uses Markdown conventions for formatting headers and other text block -entitities, but SHOULD NOT be considered a Markdown file. That's why it has no -definitive file extension. - -Each issue entry follows a structured format for easier parsing and future -migration. Issues MUST be **appended** to this file and never moved, to -preserve Git diffing. - -### Issue Format - -``` - -ID: [ISSUE-NUMBER] -Type: [feature/bugfix/hotfix] -Title: [Short title] -Status: [open/in-progress/done/hold/cancelled] -Priority: [low/medium/high] -Created: [YYYY-MM-DD] -Description: [Detailed explanation] - ---- -``` - -- ISSUE-NUMBERs must be sequential -- truncation of description must be indentended so that every line starts at the - same column -- issues must be started with two LF -- issues must be terminated with two LF, then `---` -- issues may have a free-text field (epilog), which must be started with two LF. - -## Issues +--ISSUE +Content-Type: application/sprints +Sprints: +--ISSUE +Content-Type: application/issue ID: 1 Type: feature Title: implement KMP algorithm for string searching Status: hold Priority: high Created: 2025-05-03 +Relationships: Description: Implement the Knuth-Morris-Pratt algorithm for string searching. I require this for matching RFC 9112 boundaries of entities against a circular buffer. ---- - +--ISSUE +Content-Type: application/issue ID: 2 Type: feature Title: implement circular buffer Status: done Priority: high Created: 2025-05-04 +Relationships: Description: implement a simple circular buffer ---- - +--ISSUE +Content-Type: application/issue ID: 3 Type: bugfix Title: move unit tests to subdirectory Status: done Priority: high Created: 2025-05-04 +Relationships: Description: move the unit test suites to a unit/ subdirectory so that integration tests and benchmarks can be cleanly separated ---- - +--ISSUE +Content-Type: application/issue ID: 4 Type: feature Title: implement Rabin-Karp rolling hash algorithm Status: done Priority: high Created: 2025-05-05 +Relationships: Description: After testing a couple of string search algorithms, I've ditched the idea of using KMP as my use-case gives no advantage compared to naive searching. In addition I've came upon the challenge that many @@ -97,147 +58,158 @@ Description: After testing a couple of string search algorithms, I've ditched need an implementation of the original Rabin-Karp rolling hash algorithm ---- - +--ISSUE +Content-Type: application/issue ID: 5 Type: feature Title: implement chunked rolling hash algorithm Status: in-progress Priority: high Created: 2025-05-05 +Relationships: Description: Implement my custom algorithm for doing rolling hash string search against a fixed length ring buffer ---- - +--ISSUE +Content-Type: application/issue ID: 6 Type: feature Title: implement importlib.resources handler for urllib Status: done Priority: high Created: 2025-06-20 +Relationships: Description: A handler that can be registered with an urllib.request OpenerDirector to open importlib.resources package files. ---- - +--ISSUE +Content-Type: application/issue ID: 7 Type: feature Title: setup advanced testing environment Status: done Priority: high Created: 2025-06-20 +Relationships: Description: copy the testing environment setup from byteb4rb1e.sphinxcontrib.ext ---- - +--ISSUE +Content-Type: application/issue ID: 8 Type: bugfix Title: rename package Status: done Priority: high Created: 2025-06-20 +Relationships: Description: use dot namespaces to make the package a little more elegant ---- - +--ISSUE +Content-Type: application/issue ID: 9 Type: bugfix Title: fix LICENSE reference Status: done Priority: high Created: 2025-06-20 +Relationships: Description: license specification is no longer a trove classifier in pyproject.toml, hence the reference to LICENSE must be changed ---- - +--ISSUE +Content-Type: application/issue ID: 10 Type: feature Title: pytest current test context fixtures Status: done Priority: high Created: 2025-06-20 +Relationships: Description: add fixtures for doing things in relation to the active testing context ---- - +--ISSUE +Content-Type: application/issue ID: 11 Type: bugfix Title: move testing utils out of utils Status: done Priority: high Created: 2025-06-20 +Relationships: Description: to shorten the namespace and also indicate that testing utilities are different from regular utilities ---- - +--ISSUE +Content-Type: application/issue ID: 12 Type: feature Title: simplify testing.fixtures.mock_pkg Status: done Priority: high Created: 2025-06-21 +Relationships: Description: Only bootstrap a package mock with the minimum requirements for a Python module and let the consumer handle the directory layout. ---- - +--ISSUE +Content-Type: application/issue ID: 13 Type: bugfix Title: fix unit tests for urllib PkgHandler Status: done Priority: high Created: 2025-06-21 +Relationships: Description: change of issue 12 wasn't properly reflected in urllib PkgHandler unit tests ---- - +--ISSUE +Content-Type: application/issue ID: 14 Type: feature Title: add compression support for urllib PkgHandler Status: done Priority: high Created: 2025-06-21 +Relationships: Description: with a proper content-type of the PkgHandler addinfourl object, a consumer can determine whether the file is compressed or not. ---- - +--ISSUE +Content-Type: application/issue ID: 15 Type: bugfix Title: modularize module containers Status: open Priority: high Created: 2025-06-28 +Relationships: Description: Even though importlib can find submodules through traversing paths instead of relying on __init__.py for every ancestor module, this is not supported by some modules like sphinx.ext.autosummary ---- - +--ISSUE +Content-Type: application/issue ID: 16 Type: feature Title: SQL-aware dataclass Status: in-progress Priority: low Created: 2025-12-31 +Relationships: Description: A dataclass that transparently maps onto an SQL datastore, with command generation for syncing data between data class and store ---- - +--ISSUE +Content-Type: application/issue ID: 17 Type: feature Title: recursive-descent HTML (DOM) parser Status: in-progress Priority: high Created: 2025-12-31 +Relationships: Description: Extend the built-in event-driven parser to be modeled after DOM recursive-descent HTML parser - ---- From 307f7c322c5540fc61bf1f8451292717516052ac Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 6 Jun 2026 14:14:13 +0200 Subject: [PATCH 67/92] todo(18): open --- TODO | 63 ++++++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 46 insertions(+), 17 deletions(-) diff --git a/TODO b/TODO index 920b16c..f0ae4ab 100644 --- a/TODO +++ b/TODO @@ -10,7 +10,7 @@ Title: implement KMP algorithm for string searching Status: hold Priority: high Created: 2025-05-03 -Relationships: +Relationships: Description: Implement the Knuth-Morris-Pratt algorithm for string searching. I require this for matching RFC 9112 boundaries of entities against a circular buffer. @@ -23,7 +23,7 @@ Title: implement circular buffer Status: done Priority: high Created: 2025-05-04 -Relationships: +Relationships: Description: implement a simple circular buffer --ISSUE @@ -34,7 +34,7 @@ Title: move unit tests to subdirectory Status: done Priority: high Created: 2025-05-04 -Relationships: +Relationships: Description: move the unit test suites to a unit/ subdirectory so that integration tests and benchmarks can be cleanly separated @@ -46,7 +46,7 @@ Title: implement Rabin-Karp rolling hash algorithm Status: done Priority: high Created: 2025-05-05 -Relationships: +Relationships: Description: After testing a couple of string search algorithms, I've ditched the idea of using KMP as my use-case gives no advantage compared to naive searching. In addition I've came upon the challenge that many @@ -66,7 +66,7 @@ Title: implement chunked rolling hash algorithm Status: in-progress Priority: high Created: 2025-05-05 -Relationships: +Relationships: Description: Implement my custom algorithm for doing rolling hash string search against a fixed length ring buffer @@ -78,7 +78,7 @@ Title: implement importlib.resources handler for urllib Status: done Priority: high Created: 2025-06-20 -Relationships: +Relationships: Description: A handler that can be registered with an urllib.request OpenerDirector to open importlib.resources package files. @@ -90,7 +90,7 @@ Title: setup advanced testing environment Status: done Priority: high Created: 2025-06-20 -Relationships: +Relationships: Description: copy the testing environment setup from byteb4rb1e.sphinxcontrib.ext @@ -102,7 +102,7 @@ Title: rename package Status: done Priority: high Created: 2025-06-20 -Relationships: +Relationships: Description: use dot namespaces to make the package a little more elegant --ISSUE @@ -113,7 +113,7 @@ Title: fix LICENSE reference Status: done Priority: high Created: 2025-06-20 -Relationships: +Relationships: Description: license specification is no longer a trove classifier in pyproject.toml, hence the reference to LICENSE must be changed @@ -125,7 +125,7 @@ Title: pytest current test context fixtures Status: done Priority: high Created: 2025-06-20 -Relationships: +Relationships: Description: add fixtures for doing things in relation to the active testing context @@ -137,7 +137,7 @@ Title: move testing utils out of utils Status: done Priority: high Created: 2025-06-20 -Relationships: +Relationships: Description: to shorten the namespace and also indicate that testing utilities are different from regular utilities @@ -149,7 +149,7 @@ Title: simplify testing.fixtures.mock_pkg Status: done Priority: high Created: 2025-06-21 -Relationships: +Relationships: Description: Only bootstrap a package mock with the minimum requirements for a Python module and let the consumer handle the directory layout. @@ -161,7 +161,7 @@ Title: fix unit tests for urllib PkgHandler Status: done Priority: high Created: 2025-06-21 -Relationships: +Relationships: Description: change of issue 12 wasn't properly reflected in urllib PkgHandler unit tests @@ -173,7 +173,7 @@ Title: add compression support for urllib PkgHandler Status: done Priority: high Created: 2025-06-21 -Relationships: +Relationships: Description: with a proper content-type of the PkgHandler addinfourl object, a consumer can determine whether the file is compressed or not. @@ -185,7 +185,7 @@ Title: modularize module containers Status: open Priority: high Created: 2025-06-28 -Relationships: +Relationships: Description: Even though importlib can find submodules through traversing paths instead of relying on __init__.py for every ancestor module, this is not supported by some modules like sphinx.ext.autosummary @@ -198,7 +198,7 @@ Title: SQL-aware dataclass Status: in-progress Priority: low Created: 2025-12-31 -Relationships: +Relationships: Description: A dataclass that transparently maps onto an SQL datastore, with command generation for syncing data between data class and store @@ -210,6 +210,35 @@ Title: recursive-descent HTML (DOM) parser Status: in-progress Priority: high Created: 2025-12-31 -Relationships: +Relationships: Description: Extend the built-in event-driven parser to be modeled after DOM recursive-descent HTML parser + +--ISSUE +Content-Type: application/issue +ID: 18 +Type: feature +Title: implement saas wrapper for Forgejo +Status: open +Priority: medium +Created: 2026-06-06 +Relationships: +Description: Add a new sub-package byteb4rb1e.utils.saas.forgejo, supporting the + same/similar operations as the Bitbucket wrapper + (byteb4rb1e.utils.saas.bitbucket) against the Forgejo REST API: + token-based authentication headers, repository existence checks, + repository creation within an owner/organization, and clone URL + construction. Implement as a thin layer over + byteb4rb1e.utils.http.client, consistent with the existing + Bitbucket and GitHub modules. + Unlike Bitbucket (one global SaaS instance, hence the hardcoded + api.bitbucket.org), Forgejo is self-hosted (e.g. + git.code.tiararodney.com). The wrapper MUST take a host/instance + URL parameter (or read one from config) rather than baking any + specific instance in. This is the biggest API-surface difference + from the bitbucket module. + Bitbucket's clone_url constructs SSH only. Forgejo's repository + API returns both clone_url (HTTPS) and ssh_url, and HTTPS is + needed in CI (no SSH host keys on the Woodpecker runner). The + wrapper SHOULD expose both, either as ssh_clone_url and + https_clone_url, or a single clone_url(..., scheme="ssh"|"https"). From 1879157fabc85df702f0323832281f34972845ef Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 6 Jun 2026 14:34:12 +0200 Subject: [PATCH 68/92] todo(19): open --- TODO | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/TODO b/TODO index f0ae4ab..00a6d22 100644 --- a/TODO +++ b/TODO @@ -242,3 +242,22 @@ Description: Add a new sub-package byteb4rb1e.utils.saas.forgejo, supporting the needed in CI (no SSH host keys on the Woodpecker runner). The wrapper SHOULD expose both, either as ssh_clone_url and https_clone_url, or a single clone_url(..., scheme="ssh"|"https"). + +--ISSUE +Content-Type: application/issue +ID: 19 +Type: feature +Title: config framework with CLI integration +Status: open +Priority: medium +Created: 2026-06-06 +Relationships: +Description: Add byteb4rb1e.utils.config: INI-backed config dataclasses where a + dataclass is the single source of truth for settings, with three + layers (field defaults, INI file sections, CLI overrides). Includes + INI loading/writing (load_ini, ensure_ini, ensure_ini_multi, + format_section), per-flag CLI integration (add_config_arguments, + apply_cli_overrides), dotted-path overrides via a unified --config + KEY=VALUE flag (apply_overrides, format_help), and the companion + argparse KeyValueAction (byteb4rb1e.utils.argparse.actions) that + accumulates KEY=VALUE pairs into a dict. From a0fd3e5d48ee773c365c436714cb5e647a4b795b Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 6 Jun 2026 14:34:27 +0200 Subject: [PATCH 69/92] todo(20): open --- TODO | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/TODO b/TODO index 00a6d22..1071f90 100644 --- a/TODO +++ b/TODO @@ -261,3 +261,20 @@ Description: Add byteb4rb1e.utils.config: INI-backed config dataclasses where a KEY=VALUE flag (apply_overrides, format_help), and the companion argparse KeyValueAction (byteb4rb1e.utils.argparse.actions) that accumulates KEY=VALUE pairs into a dict. + +--ISSUE +Content-Type: application/issue +ID: 20 +Type: feature +Title: cookie-persisting HTTP session client +Status: open +Priority: medium +Created: 2026-06-06 +Relationships: +Description: Extend byteb4rb1e.utils.http.client with an HttpSession class that + persists cookies across requests via http.cookiejar (suitable for + login followed by cookie-authenticated page fetches), supporting + GET with query params, form-encoded POST, default/per-request + header merging, and HTTPError-to-response conversion. Also refactor + HttpResponse into a frozen dataclass with text as a derived + property. From 358ce939a98980bd693a0e806dc1b2928735e808 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 6 Jun 2026 14:34:43 +0200 Subject: [PATCH 70/92] todo(19): in-progress byteb4rb1e.utils.config and byteb4rb1e.utils.argparse.actions.KeyValueAction (with package export) live on branch feature/19 with spec-compliant commit granularity (production code before tests, module boundaries separate); unit test suites for both pass via tox; develop working tree is clean of these files. --- TODO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO b/TODO index 1071f90..d3f8b17 100644 --- a/TODO +++ b/TODO @@ -248,7 +248,7 @@ Content-Type: application/issue ID: 19 Type: feature Title: config framework with CLI integration -Status: open +Status: in-progress Priority: medium Created: 2026-06-06 Relationships: From 18aca33e42f5a4f7e6f2ba7b65856e3eca993d2a Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 6 Jun 2026 14:34:45 +0200 Subject: [PATCH 71/92] todo(20): in-progress HttpSession and the HttpResponse frozen-dataclass refactor live on branch feature/20 as separate refactor and feat commits; unit tests covering HttpResponse and HttpSession (cookie persistence, header merging, params, form POST, HTTPError handling) added and passing via tox; develop working tree is clean of http/client.py changes. --- TODO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO b/TODO index d3f8b17..6fb9efd 100644 --- a/TODO +++ b/TODO @@ -267,7 +267,7 @@ Content-Type: application/issue ID: 20 Type: feature Title: cookie-persisting HTTP session client -Status: open +Status: in-progress Priority: medium Created: 2026-06-06 Relationships: From 8077a64f7b9af18026b4300ec8b25cc1da946f23 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 6 Jun 2026 14:35:01 +0200 Subject: [PATCH 72/92] feat: add KeyValueAction argparse action --- src/byteb4rb1e/utils/argparse/actions.py | 33 ++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/byteb4rb1e/utils/argparse/actions.py diff --git a/src/byteb4rb1e/utils/argparse/actions.py b/src/byteb4rb1e/utils/argparse/actions.py new file mode 100644 index 0000000..79a5e8f --- /dev/null +++ b/src/byteb4rb1e/utils/argparse/actions.py @@ -0,0 +1,33 @@ +"""Custom argparse actions.""" + +from __future__ import annotations + +import argparse +from typing import Any + + +class KeyValueAction(argparse.Action): + """Argparse action that accumulates ``KEY=VALUE`` pairs into a dict. + + Usage:: + + parser.add_argument("--config", action=KeyValueAction, + default={}, metavar="KEY=VALUE", + help="Set a config option (can be repeated)") + + Then ``args.config`` is a ``dict[str, str]``. + """ + + def __call__( + self, + parser: argparse.ArgumentParser, + namespace: argparse.Namespace, + values: Any, + option_string: str | None = None, + ) -> None: + d = getattr(namespace, self.dest, None) or {} + if "=" not in values: + parser.error(f"Invalid format: {values!r} (expected KEY=VALUE)") + key, _, value = values.partition("=") + d[key.strip()] = value.strip() + setattr(namespace, self.dest, d) From e12d65f53c076fcce228e8cf832f7ceef9e4cfed Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 6 Jun 2026 14:35:01 +0200 Subject: [PATCH 73/92] feat: export KeyValueAction from argparse package --- src/byteb4rb1e/utils/argparse/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/byteb4rb1e/utils/argparse/__init__.py b/src/byteb4rb1e/utils/argparse/__init__.py index 84ae3ed..5bf1156 100644 --- a/src/byteb4rb1e/utils/argparse/__init__.py +++ b/src/byteb4rb1e/utils/argparse/__init__.py @@ -1,6 +1,7 @@ """Utilities for building composable CLIs from command dataclasses.""" +from byteb4rb1e.utils.argparse.actions import KeyValueAction from byteb4rb1e.utils.argparse.command import CLICommand from byteb4rb1e.utils.argparse.dispatcher import CLI -__all__ = ["CLI", "CLICommand"] +__all__ = ["CLI", "CLICommand", "KeyValueAction"] From fab4176ba924ad62349180bb2856732712723dcc Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 6 Jun 2026 14:35:02 +0200 Subject: [PATCH 74/92] feat: add config framework --- src/byteb4rb1e/utils/config.py | 369 +++++++++++++++++++++++++++++++++ 1 file changed, 369 insertions(+) create mode 100644 src/byteb4rb1e/utils/config.py diff --git a/src/byteb4rb1e/utils/config.py b/src/byteb4rb1e/utils/config.py new file mode 100644 index 0000000..8bece72 --- /dev/null +++ b/src/byteb4rb1e/utils/config.py @@ -0,0 +1,369 @@ +"""Config framework — INI-backed dataclasses with CLI integration. + +A config dataclass is the single source of truth for settings. Values +come from three layers (later wins): + + 1. Dataclass field defaults + 2. INI file sections + 3. CLI overrides (via argparse flags or ``--config KEY=VALUE``) + +Two CLI integration styles: + +- ``add_config_arguments`` — generates one ``--flag`` per field. +- ``apply_overrides`` — accepts a ``dict[str, str]`` of dotted-path + overrides from a unified ``--config KEY=VALUE`` flag. +""" + +import configparser +from argparse import ArgumentParser, Namespace +from dataclasses import MISSING, fields +from pathlib import Path +from typing import Any, Type, TypeVar, get_type_hints + +T = TypeVar("T") + + +# --------------------------------------------------------------------------- +# Internal helpers +# --------------------------------------------------------------------------- + +def _parse_bool(value: str) -> bool: + """Parse a boolean from INI/CLI string.""" + return value.lower() in ("true", "yes", "1", "on") + + +_TYPE_MAP = { + int: int, + float: float, + str: str, + bool: _parse_bool, +} + + +def resolve_hints(cls: Type) -> dict[str, type]: + """Resolve type hints for a dataclass, handling both evaluated + and string annotations. + + :param cls: a dataclass class. + :returns: dict mapping field names to resolved types. + """ + try: + return get_type_hints(cls) + except Exception: + return { + f.name: f.type if isinstance(f.type, type) + else str + for f in fields(cls) + } + + +def _section_name(cls: Type, section: str | None = None) -> str: + """Derive INI section name from class name if not provided.""" + if section is not None: + return section + name = cls.__name__ + if name.endswith("Config"): + name = name[: -len("Config")] + return name.lower() + + +# --------------------------------------------------------------------------- +# INI loading +# --------------------------------------------------------------------------- + +def load_ini( + cls: Type[T], + path: Path, + section: str | None = None, +) -> T: + """Load a config dataclass from an INI file. + + If *section* is not given, the dataclass name (lowercased, + without trailing "Config") is used. + + Unknown keys in the INI file raise ValueError. Missing keys + use the dataclass default. + """ + section = _section_name(cls, section) + + parser = configparser.ConfigParser( + comment_prefixes=("#", ";"), + inline_comment_prefixes=("#", ";"), + ) + parser.read(path) + + if not parser.has_section(section): + return cls() # type: ignore[call-arg] + + hints = resolve_hints(cls) + field_names = {f.name for f in fields(cls) if f.init} + kwargs: dict[str, Any] = {} + + for key, raw_value in parser.items(section): + if key not in field_names: + raise ValueError( + f"Unknown config key '{key}' in" + f" [{section}]. Valid keys:" + f" {sorted(field_names)}" + ) + + field_type = hints.get(key, str) + coerce = _TYPE_MAP.get(field_type, field_type) + kwargs[key] = coerce(raw_value) + + return cls(**kwargs) # type: ignore[call-arg] + + +# --------------------------------------------------------------------------- +# INI writing +# --------------------------------------------------------------------------- + +def format_section(cls: Type, section: str | None = None) -> str: + """Format a config dataclass as an INI section string. + + Returns the section header and all fields with their defaults + as commented key-value pairs. + + :param cls: a dataclass class. + :param section: section name (derived from class name if None). + :returns: INI section string. + """ + section = _section_name(cls, section) + hints = resolve_hints(cls) + lines = [f"[{section}]"] + + for f in fields(cls): + if not f.init: + continue + field_type = hints.get(f.name, str) + type_name = getattr(field_type, "__name__", str(field_type)) + + if f.default is not MISSING: + default = f.default + elif f.default_factory is not MISSING: # type: ignore[arg-type] + default = f.default_factory() # type: ignore[misc] + else: + continue + + lines.append(f"# {f.name} ({type_name})") + lines.append(f"{f.name} = {default}") + lines.append("") + + return "\n".join(lines) + + +def ensure_ini( + cls: Type[T], + path: Path, + section: str | None = None, +) -> T: + """Load config from INI, creating the file with defaults if + it does not exist. + + On first run, writes a commented INI file with all fields and + their default values. On subsequent runs, reads the existing + file. Never writes back CLI overrides. + """ + section = _section_name(cls, section) + + if not path.exists(): + _write_default_ini(cls, path, section) + + return load_ini(cls, path, section) + + +def ensure_ini_multi( + configs: list[tuple[Type, str | None]], + path: Path, +) -> None: + """Create an INI file with multiple sections if it does not exist. + + Each entry is a (dataclass_cls, section_name) tuple. If + section_name is None, it is derived from the class name. + + Does not overwrite an existing file. + + :param configs: list of (cls, section) tuples. + :param path: path to the INI file. + """ + if path.exists(): + return + + sections = [format_section(cls, section) for cls, section in configs] + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text("\n".join(sections) + "\n") + + +def _write_default_ini( + cls: Type, + path: Path, + section: str, +) -> None: + """Write an INI file with all fields as commented defaults.""" + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(format_section(cls, section) + "\n") + + +# --------------------------------------------------------------------------- +# CLI: per-flag style (add_config_arguments / apply_cli_overrides) +# --------------------------------------------------------------------------- + +def add_config_arguments( + cls: Type[T], + parser: ArgumentParser, + prefix: str = "", +) -> None: + """Add CLI arguments for each field in a config dataclass. + + Field names are converted to CLI flags: ``heart_rate_resolution`` + becomes ``--heart-rate-resolution`` (or ``---heart-rate-resolution`` + if a prefix is given). + """ + hints = resolve_hints(cls) + + for f in fields(cls): + if not f.init: + continue + flag_name = f.name.replace("_", "-") + if prefix: + flag_name = f"{prefix}-{flag_name}" + + field_type = hints.get(f.name, str) + + kwargs: dict[str, Any] = { + "dest": f.name, + } + + if field_type is bool: + kwargs["action"] = ( + "store_false" + if f.default is True + else "store_true" + ) + kwargs["default"] = None + else: + kwargs["type"] = _TYPE_MAP.get( + field_type, field_type + ) + kwargs["default"] = None + kwargs["metavar"] = field_type.__name__.upper() + + parser.add_argument(f"--{flag_name}", **kwargs) + + +def apply_cli_overrides( + config: T, + args: Namespace, +) -> T: + """Apply CLI argument values to a config instance. + + Only overrides fields that were explicitly set on the command + line (not None). Returns a new instance. + """ + overrides = {} + for f in fields(config): # type: ignore[arg-type] + if not f.init: + continue + cli_value = getattr(args, f.name, None) + if cli_value is not None: + overrides[f.name] = cli_value + + if not overrides: + return config + + from dataclasses import asdict + merged = asdict(config) # type: ignore[arg-type] + merged.update(overrides) + return type(config)(**merged) # type: ignore[return-value] + + +# --------------------------------------------------------------------------- +# CLI: dotted-path style (apply_overrides) +# --------------------------------------------------------------------------- + +def apply_overrides( + config: T, + overrides: dict[str, str], + prefix: str = "", +) -> T: + """Apply dotted-path string overrides to a config dataclass. + + Used with a unified ``--config KEY=VALUE`` CLI flag. Each key + is a dotted path relative to the prefix. + + Example:: + + overrides = { + "provider.base_url": "http://localhost:4000", + "provider.model": "qwen2.5:7b", + } + config = apply_overrides(config, overrides, prefix="provider") + # config.base_url == "http://localhost:4000" + # config.model == "qwen2.5:7b" + + :param config: a dataclass instance. + :param overrides: dict of dotted keys to string values. + :param prefix: only apply keys starting with this prefix. + :returns: new config instance with overrides applied. + """ + hints = resolve_hints(type(config)) + kwargs: dict[str, Any] = {} + changed = False + + for f in fields(config): + if not f.init: + continue + full_key = f"{prefix}.{f.name}" if prefix else f.name + if full_key in overrides: + raw = overrides[full_key] + field_type = hints.get(f.name, str) + coerce = _TYPE_MAP.get(field_type, field_type) + kwargs[f.name] = coerce(raw) + changed = True + else: + kwargs[f.name] = getattr(config, f.name) + + if not changed: + return config + + return type(config)(**kwargs) # type: ignore[return-value] + + +def format_help(cls: Type, prefix: str = "") -> list[str]: + """Generate help lines for a config dataclass. + + Each line shows the dotted key path, type, and default value. + Suitable for CLI epilog text. + + :param cls: a dataclass class. + :param prefix: prepended to each key path. + :returns: list of formatted help strings. + """ + hints = resolve_hints(cls) + lines = [] + + for f in fields(cls): + if not f.init: + continue + field_type = hints.get(f.name, str) + type_name = getattr(field_type, "__name__", str(field_type)) + key = f"{prefix}.{f.name}" if prefix else f.name + + if f.default is not MISSING: + default = f.default + elif f.default_factory is not MISSING: # type: ignore[arg-type] + default = repr(f.default_factory()) # type: ignore[misc] + else: + default = "(required)" + + lines.append(f" {key} ({type_name}, default: {default})") + + return lines + + +# --------------------------------------------------------------------------- +# Backwards compat +# --------------------------------------------------------------------------- + +# keep the old private name working for existing callers +_resolve_hints = resolve_hints From d23a8287fd537cd0cd4a08ce183bd66c86360c80 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 6 Jun 2026 14:35:02 +0200 Subject: [PATCH 75/92] test: add KeyValueAction unit tests --- .../byteb4rb1e/utils/argparse/test_actions.py | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 tests/unit/byteb4rb1e/utils/argparse/test_actions.py diff --git a/tests/unit/byteb4rb1e/utils/argparse/test_actions.py b/tests/unit/byteb4rb1e/utils/argparse/test_actions.py new file mode 100644 index 0000000..3d0c150 --- /dev/null +++ b/tests/unit/byteb4rb1e/utils/argparse/test_actions.py @@ -0,0 +1,52 @@ +"""Tests for custom argparse actions.""" + +from argparse import ArgumentParser + +import pytest + +from byteb4rb1e.utils.argparse.actions import KeyValueAction + + +def _parse(*args): + parser = ArgumentParser() + parser.add_argument("--config", action=KeyValueAction, default={}, metavar="KEY=VALUE") + return parser.parse_args(list(args)) + + +class TestKeyValueAction: + + def test_single_pair(self): + args = _parse("--config", "key=value") + assert args.config == {"key": "value"} + + def test_multiple_pairs(self): + args = _parse("--config", "a=1", "--config", "b=2") + assert args.config == {"a": "1", "b": "2"} + + def test_dotted_key(self): + args = _parse("--config", "provider.base_url=http://localhost") + assert args.config == {"provider.base_url": "http://localhost"} + + def test_value_with_equals(self): + args = _parse("--config", "url=http://host?a=1&b=2") + assert args.config == {"url": "http://host?a=1&b=2"} + + def test_empty_value(self): + args = _parse("--config", "key=") + assert args.config == {"key": ""} + + def test_strips_whitespace(self): + args = _parse("--config", " key = value ") + assert args.config == {"key": "value"} + + def test_overwrites_duplicate_key(self): + args = _parse("--config", "key=first", "--config", "key=second") + assert args.config == {"key": "second"} + + def test_default_empty_dict(self): + args = _parse() + assert args.config == {} + + def test_no_equals_raises(self): + with pytest.raises(SystemExit): + _parse("--config", "no_equals_here") From 96c8e4685e6ca7f4211ac1b1db9e83e6dcc9141e Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 6 Jun 2026 14:35:02 +0200 Subject: [PATCH 76/92] test: add config framework unit tests --- .../byteb4rb1e/utils/config/test_config.py | 347 ++++++++++++++++++ 1 file changed, 347 insertions(+) create mode 100644 tests/unit/byteb4rb1e/utils/config/test_config.py diff --git a/tests/unit/byteb4rb1e/utils/config/test_config.py b/tests/unit/byteb4rb1e/utils/config/test_config.py new file mode 100644 index 0000000..6b25306 --- /dev/null +++ b/tests/unit/byteb4rb1e/utils/config/test_config.py @@ -0,0 +1,347 @@ +"""Unit tests for the config framework.""" + +from argparse import ArgumentParser, Namespace +from dataclasses import dataclass +from pathlib import Path + +import pytest + +from byteb4rb1e.utils.config import ( + add_config_arguments, + apply_cli_overrides, + apply_overrides, + ensure_ini, + ensure_ini_multi, + format_help, + format_section, + load_ini, + resolve_hints, +) + + +@dataclass +class SampleConfig: + name: str = "default" + count: int = 10 + ratio: float = 0.5 + enabled: bool = True + + +class TestLoadIni: + def test_loads_values(self, tmp_path): + ini = tmp_path / "test.ini" + ini.write_text( + "[sample]\n" + "name = custom\n" + "count = 42\n" + "ratio = 0.75\n" + ) + config = load_ini(SampleConfig, ini) + assert config.name == "custom" + assert config.count == 42 + assert config.ratio == 0.75 + assert config.enabled is True # default + + def test_missing_section_uses_defaults(self, tmp_path): + ini = tmp_path / "test.ini" + ini.write_text("[other]\nfoo = bar\n") + config = load_ini(SampleConfig, ini) + assert config.name == "default" + assert config.count == 10 + + def test_missing_file_uses_defaults(self, tmp_path): + config = load_ini( + SampleConfig, tmp_path / "missing.ini" + ) + assert config.name == "default" + + def test_unknown_key_raises(self, tmp_path): + ini = tmp_path / "test.ini" + ini.write_text("[sample]\nunknown_key = bad\n") + with pytest.raises(ValueError, match="unknown_key"): + load_ini(SampleConfig, ini) + + def test_custom_section_name(self, tmp_path): + ini = tmp_path / "test.ini" + ini.write_text("[mysection]\nname = custom\n") + config = load_ini( + SampleConfig, ini, section="mysection" + ) + assert config.name == "custom" + + def test_comments_ignored(self, tmp_path): + ini = tmp_path / "test.ini" + ini.write_text( + "[sample]\n" + "# this is a comment\n" + "name = works # inline comment\n" + ) + config = load_ini(SampleConfig, ini) + assert config.name == "works" + + +class TestAddConfigArguments: + def test_generates_flags(self): + parser = ArgumentParser() + add_config_arguments(SampleConfig, parser) + args = parser.parse_args( + ["--name", "cli", "--count", "99"] + ) + assert args.name == "cli" + assert args.count == 99 + + def test_defaults_are_none(self): + parser = ArgumentParser() + add_config_arguments(SampleConfig, parser) + args = parser.parse_args([]) + assert args.name is None + assert args.count is None + + def test_underscores_become_dashes(self): + @dataclass + class DashConfig: + my_long_name: str = "x" + + parser = ArgumentParser() + add_config_arguments(DashConfig, parser) + args = parser.parse_args( + ["--my-long-name", "val"] + ) + assert args.my_long_name == "val" + + +class TestApplyCliOverrides: + def test_overrides_set_values(self): + config = SampleConfig() + args = Namespace(name="override", count=None, + ratio=None, enabled=None) + result = apply_cli_overrides(config, args) + assert result.name == "override" + assert result.count == 10 # unchanged + + def test_no_overrides_returns_same(self): + config = SampleConfig() + args = Namespace(name=None, count=None, + ratio=None, enabled=None) + result = apply_cli_overrides(config, args) + assert result.name == "default" + assert result is config + + +class TestEnsureIni: + def test_creates_file_if_missing(self, tmp_path): + ini = tmp_path / "new.ini" + assert not ini.exists() + config = ensure_ini(SampleConfig, ini) + assert ini.exists() + assert config.name == "default" + assert config.count == 10 + + def test_created_file_has_all_fields(self, tmp_path): + ini = tmp_path / "new.ini" + ensure_ini(SampleConfig, ini) + content = ini.read_text() + assert "name" in content + assert "count" in content + assert "ratio" in content + assert "enabled" in content + + def test_created_file_has_comments(self, tmp_path): + ini = tmp_path / "new.ini" + ensure_ini(SampleConfig, ini) + content = ini.read_text() + assert "# name (str)" in content + assert "# count (int)" in content + + def test_reads_existing_file(self, tmp_path): + ini = tmp_path / "existing.ini" + ini.write_text("[sample]\ncount = 42\n") + config = ensure_ini(SampleConfig, ini) + assert config.count == 42 + + def test_does_not_overwrite_existing(self, tmp_path): + ini = tmp_path / "existing.ini" + ini.write_text("[sample]\ncount = 42\n") + ensure_ini(SampleConfig, ini) + content = ini.read_text() + assert content == "[sample]\ncount = 42\n" + + def test_created_file_is_loadable(self, tmp_path): + ini = tmp_path / "new.ini" + ensure_ini(SampleConfig, ini) + config = load_ini(SampleConfig, ini) + assert config.name == "default" + assert config.count == 10 + assert config.ratio == 0.5 + + +class TestIntegration: + def test_ini_then_cli_override(self, tmp_path): + ini = tmp_path / "test.ini" + ini.write_text("[sample]\ncount = 42\n") + config = load_ini(SampleConfig, ini) + assert config.count == 42 + + args = Namespace(name=None, count=99, + ratio=None, enabled=None) + config = apply_cli_overrides(config, args) + assert config.count == 99 + assert config.name == "default" + + def test_ensure_then_cli_override(self, tmp_path): + ini = tmp_path / "new.ini" + config = ensure_ini(SampleConfig, ini) + assert config.count == 10 + + args = Namespace(name=None, count=99, + ratio=None, enabled=None) + config = apply_cli_overrides(config, args) + assert config.count == 99 + assert config.name == "default" + + # Config file unchanged + reloaded = load_ini(SampleConfig, ini) + assert reloaded.count == 10 + + +class TestResolveHints: + def test_returns_type_dict(self): + hints = resolve_hints(SampleConfig) + assert hints["name"] is str + assert hints["count"] is int + assert hints["ratio"] is float + assert hints["enabled"] is bool + + +class TestFormatSection: + def test_includes_section_header(self): + text = format_section(SampleConfig) + assert "[sample]" in text + + def test_custom_section_name(self): + text = format_section(SampleConfig, "custom") + assert "[custom]" in text + + def test_includes_all_fields(self): + text = format_section(SampleConfig) + assert "name = default" in text + assert "count = 10" in text + assert "ratio = 0.5" in text + assert "enabled = True" in text + + def test_includes_type_comments(self): + text = format_section(SampleConfig) + assert "# name (str)" in text + assert "# count (int)" in text + + def test_is_loadable(self, tmp_path): + ini = tmp_path / "test.ini" + ini.write_text(format_section(SampleConfig) + "\n") + config = load_ini(SampleConfig, ini) + assert config.name == "default" + assert config.count == 10 + + +class TestEnsureIniMulti: + def test_creates_file_with_multiple_sections(self, tmp_path): + @dataclass + class OtherConfig: + host: str = "localhost" + port: int = 8080 + + ini = tmp_path / "multi.ini" + ensure_ini_multi([ + (SampleConfig, None), + (OtherConfig, "server"), + ], ini) + + content = ini.read_text() + assert "[sample]" in content + assert "[server]" in content + assert "name = default" in content + assert "host = localhost" in content + + def test_does_not_overwrite_existing(self, tmp_path): + ini = tmp_path / "multi.ini" + ini.write_text("[existing]\nfoo = bar\n") + ensure_ini_multi([(SampleConfig, None)], ini) + content = ini.read_text() + assert content == "[existing]\nfoo = bar\n" + + def test_sections_are_loadable(self, tmp_path): + @dataclass + class DbConfig: + url: str = "sqlite:///test.db" + + ini = tmp_path / "multi.ini" + ensure_ini_multi([ + (SampleConfig, None), + (DbConfig, "database"), + ], ini) + + sample = load_ini(SampleConfig, ini) + db = load_ini(DbConfig, ini, section="database") + assert sample.name == "default" + assert db.url == "sqlite:///test.db" + + +class TestApplyOverrides: + def test_applies_dotted_path(self): + config = SampleConfig() + result = apply_overrides(config, { + "provider.name": "custom", + "provider.count": "99", + }, prefix="provider") + assert result.name == "custom" + assert result.count == 99 + + def test_without_prefix(self): + config = SampleConfig() + result = apply_overrides(config, { + "name": "direct", + "count": "42", + }) + assert result.name == "direct" + assert result.count == 42 + + def test_no_matching_keys_returns_same(self): + config = SampleConfig() + result = apply_overrides(config, {"other.key": "val"}, prefix="provider") + assert result is config + + def test_bool_coercion(self): + config = SampleConfig() + result = apply_overrides(config, {"enabled": "false"}) + assert result.enabled is False + + def test_preserves_unset_fields(self): + config = SampleConfig() + result = apply_overrides(config, {"name": "changed"}) + assert result.name == "changed" + assert result.count == 10 # unchanged + assert result.ratio == 0.5 # unchanged + + +class TestFormatHelp: + def test_lists_all_fields(self): + lines = format_help(SampleConfig) + assert len(lines) == 4 + assert any("name" in l for l in lines) + assert any("count" in l for l in lines) + + def test_includes_types(self): + lines = format_help(SampleConfig) + text = "\n".join(lines) + assert "str" in text + assert "int" in text + + def test_includes_defaults(self): + lines = format_help(SampleConfig) + text = "\n".join(lines) + assert "default" in text + assert "10" in text + + def test_with_prefix(self): + lines = format_help(SampleConfig, prefix="provider") + assert any("provider.name" in l for l in lines) + assert any("provider.count" in l for l in lines) From 9a4d2041f92ed59608e91e49f91e57027d0aaf3e Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 6 Jun 2026 14:36:17 +0200 Subject: [PATCH 77/92] refactor: convert HttpResponse to frozen dataclass text becomes a derived property; reason is now optional. --- src/byteb4rb1e/utils/http/client.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/byteb4rb1e/utils/http/client.py b/src/byteb4rb1e/utils/http/client.py index 0962445..02b8cb6 100644 --- a/src/byteb4rb1e/utils/http/client.py +++ b/src/byteb4rb1e/utils/http/client.py @@ -5,6 +5,7 @@ Thin urllib wrapper with retry-on-rate-limit. No domain knowledge — GitHub, Bitbucket, etc. are handled by higher-level modules. """ +from dataclasses import dataclass import json import time from typing import Any, Dict, Optional @@ -13,17 +14,20 @@ import urllib.parse from warnings import warn +@dataclass(frozen=True) class HttpResponse: - def __init__(self, status: int, headers: dict, data: bytes, reason: str): - self.status_code = status - self.headers = headers - self.data = data - self.reason = reason - self.text = data.decode("utf-8", errors="replace") + status_code: int + headers: dict[str, str] + data: bytes + reason: Optional[str] = None def json(self): return json.loads(self.data.decode("utf-8")) + @property + def text(self) -> str: + return self.data.decode("utf-8", errors="replace") + def _request( url: str, From bdd3892c5c97806780eccb33bee6c857337ee303 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 6 Jun 2026 14:36:18 +0200 Subject: [PATCH 78/92] feat: add cookie-persisting HttpSession HTTP client that persists cookies across requests via http.cookiejar, for sites requiring login followed by cookie-authenticated fetches. Supports GET with query params, form-encoded POST, default/per-request header merging, and HTTPError-to-response conversion. --- src/byteb4rb1e/utils/http/client.py | 88 +++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/src/byteb4rb1e/utils/http/client.py b/src/byteb4rb1e/utils/http/client.py index 02b8cb6..e011221 100644 --- a/src/byteb4rb1e/utils/http/client.py +++ b/src/byteb4rb1e/utils/http/client.py @@ -6,6 +6,7 @@ GitHub, Bitbucket, etc. are handled by higher-level modules. """ from dataclasses import dataclass +import http.cookiejar import json import time from typing import Any, Dict, Optional @@ -29,6 +30,93 @@ class HttpResponse: return self.data.decode("utf-8", errors="replace") +class HttpSession: + """HTTP client that persists cookies across requests. + + Suitable for sites that require login followed by + cookie-authenticated page fetches. + """ + + def __init__( + self, + default_headers: dict[str, str] | None = None, + timeout: int = 30, + ) -> None: + self._timeout = timeout + self._default_headers = default_headers or {} + self._jar = http.cookiejar.CookieJar() + self._opener = urllib.request.build_opener( + urllib.request.HTTPCookieProcessor(self._jar), + ) + + def get( + self, + url: str, + params: dict[str, str] | None = None, + headers: dict[str, str] | None = None, + ) -> HttpResponse: + if params: + query = urllib.parse.urlencode(params) + url = f"{url}?{query}" + + req = urllib.request.Request( + url, + headers=self._merged_headers(headers), + method="GET", + ) + return self._send(req) + + def post( + self, + url: str, + data: dict[str, str] | None = None, + headers: dict[str, str] | None = None, + ) -> HttpResponse: + body = ( + urllib.parse.urlencode(data).encode() + if data else None + ) + + merged = self._merged_headers(headers) + if data and "Content-Type" not in merged: + merged["Content-Type"] = ( + "application/x-www-form-urlencoded" + ) + + req = urllib.request.Request( + url, + data=body, + headers=merged, + method="POST", + ) + return self._send(req) + + def _send(self, req: urllib.request.Request) -> HttpResponse: + try: + with self._opener.open( + req, timeout=self._timeout + ) as resp: + return HttpResponse( + status_code=resp.getcode(), + headers=dict(resp.getheaders()), + data=resp.read(), + ) + except urllib.error.HTTPError as e: + return HttpResponse( + status_code=e.code, + headers=dict(e.headers.items()), + data=e.read(), + ) + + def _merged_headers( + self, extra: dict[str, str] | None + ) -> dict[str, str]: + merged = dict(self._default_headers) + if extra: + merged.update(extra) + return merged + + def _request( url: str, method: str = "GET", From c3c17e1e8e419b055b7ec10e22d4cdd0b4bfe521 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 6 Jun 2026 14:40:43 +0200 Subject: [PATCH 79/92] test: add http client unit tests --- .../unit/byteb4rb1e/utils/http/test_client.py | 217 ++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 tests/unit/byteb4rb1e/utils/http/test_client.py diff --git a/tests/unit/byteb4rb1e/utils/http/test_client.py b/tests/unit/byteb4rb1e/utils/http/test_client.py new file mode 100644 index 0000000..8eed589 --- /dev/null +++ b/tests/unit/byteb4rb1e/utils/http/test_client.py @@ -0,0 +1,217 @@ +"""Tests for the generic HTTP client.""" + +import email.message +import io +import urllib.error +import urllib.parse +import urllib.request +from types import TracebackType +from typing import Dict, List, Optional, Tuple, Type, Union + +import pytest + +from byteb4rb1e.utils.http.client import HttpResponse, HttpSession + + +class _FakeRawResponse: + """Stands in for the object returned by OpenerDirector.open().""" + + def __init__( + self, + status: int = 200, + headers: Optional[Dict[str, str]] = None, + data: bytes = b"", + ) -> None: + self._status = status + self._headers = headers or {} + self._data = data + + def getcode(self) -> int: + return self._status + + def getheaders(self) -> List[Tuple[str, str]]: + return list(self._headers.items()) + + def read(self) -> bytes: + return self._data + + def __enter__(self) -> "_FakeRawResponse": + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc: Optional[BaseException], + tb: Optional[TracebackType], + ) -> None: + return None + + +class _FakeOpener: + """Records requests and replays canned responses.""" + + def __init__( + self, + responses: Optional[ + List[Union[_FakeRawResponse, Exception]] + ] = None, + ) -> None: + self.requests: List[urllib.request.Request] = [] + self._responses = list(responses or [_FakeRawResponse()]) + + def open( + self, + req: urllib.request.Request, + timeout: Optional[int] = None, + ) -> _FakeRawResponse: + self.requests.append(req) + response = self._responses.pop(0) + if isinstance(response, Exception): + raise response + return response + + +def _http_error( + code: int = 404, + data: bytes = b"", + headers: Optional[Dict[str, str]] = None, +) -> urllib.error.HTTPError: + hdrs = email.message.Message() + for key, value in (headers or {}).items(): + hdrs[key] = value + return urllib.error.HTTPError( + "http://testserver/", code, "error", hdrs, io.BytesIO(data), + ) + + +class TestHttpResponse: + + def test_json(self) -> None: + resp = HttpResponse(200, {}, b'{"a": 1}') + assert resp.json() == {"a": 1} + + def test_text(self) -> None: + resp = HttpResponse(200, {}, b"hello") + assert resp.text == "hello" + + def test_text_replaces_invalid_utf8(self) -> None: + resp = HttpResponse(200, {}, b"\xff\xfe") + assert "�" in resp.text + + def test_reason_defaults_to_none(self) -> None: + resp = HttpResponse(200, {}, b"") + assert resp.reason is None + + def test_frozen(self) -> None: + resp = HttpResponse(200, {}, b"") + with pytest.raises(Exception): + resp.status_code = 500 + + +class TestHttpSession: + + def test_opener_has_cookie_processor(self) -> None: + session = HttpSession() + processors = [ + h for h in session._opener.handlers + if isinstance(h, urllib.request.HTTPCookieProcessor) + ] + assert len(processors) == 1 + assert processors[0].cookiejar is session._jar + + def test_get(self) -> None: + opener = _FakeOpener([ + _FakeRawResponse(200, {"X-Foo": "bar"}, b"body"), + ]) + session = HttpSession() + session._opener = opener + + resp = session.get("http://testserver/page") + + assert resp.status_code == 200 + assert resp.data == b"body" + assert resp.headers == {"X-Foo": "bar"} + assert opener.requests[0].get_method() == "GET" + assert opener.requests[0].full_url == "http://testserver/page" + + def test_get_with_params(self) -> None: + opener = _FakeOpener() + session = HttpSession() + session._opener = opener + + session.get("http://testserver/page", params={"a": "1", "b": "x y"}) + + assert opener.requests[0].full_url == ( + "http://testserver/page?a=1&b=x+y" + ) + + def test_default_headers_sent(self) -> None: + opener = _FakeOpener() + session = HttpSession(default_headers={"User-Agent": "test"}) + session._opener = opener + + session.get("http://testserver/") + + assert opener.requests[0].get_header("User-agent") == "test" + + def test_request_headers_override_defaults(self) -> None: + opener = _FakeOpener() + session = HttpSession(default_headers={"X-Token": "default"}) + session._opener = opener + + session.get("http://testserver/", headers={"X-Token": "override"}) + + assert opener.requests[0].get_header("X-token") == "override" + + def test_post_form_encodes_data(self) -> None: + opener = _FakeOpener() + session = HttpSession() + session._opener = opener + + session.post("http://testserver/login", data={"user": "u", "pass": "p"}) + + req = opener.requests[0] + assert req.get_method() == "POST" + assert isinstance(req.data, bytes) + assert dict(urllib.parse.parse_qsl(req.data.decode())) == { + "user": "u", + "pass": "p", + } + assert req.get_header("Content-type") == ( + "application/x-www-form-urlencoded" + ) + + def test_post_keeps_explicit_content_type(self) -> None: + opener = _FakeOpener() + session = HttpSession() + session._opener = opener + + session.post( + "http://testserver/", + data={"a": "1"}, + headers={"Content-Type": "text/plain"}, + ) + + assert opener.requests[0].get_header("Content-type") == "text/plain" + + def test_post_without_data(self) -> None: + opener = _FakeOpener() + session = HttpSession() + session._opener = opener + + session.post("http://testserver/") + + assert opener.requests[0].data is None + + def test_http_error_returned_as_response(self) -> None: + opener = _FakeOpener([ + _http_error(404, b"missing", {"X-Err": "yes"}), + ]) + session = HttpSession() + session._opener = opener + + resp = session.get("http://testserver/nope") + + assert resp.status_code == 404 + assert resp.data == b"missing" + assert resp.headers["X-Err"] == "yes" From fef5ee1bf2163209680a4bcafb87a083ecbdeade Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 6 Jun 2026 14:54:41 +0200 Subject: [PATCH 80/92] todo(19): done byteb4rb1e.utils.config (INI loading/writing, per-flag CLI integration, dotted-path --config KEY=VALUE overrides, help formatting) and byteb4rb1e.utils.argparse.actions.KeyValueAction with package export live on feature/19 in five granular commits (production code before tests, module boundaries separate). 46 unit tests pass via tox -e unit-py313. develop working tree clean of these files. --- TODO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO b/TODO index 6fb9efd..1039c22 100644 --- a/TODO +++ b/TODO @@ -248,7 +248,7 @@ Content-Type: application/issue ID: 19 Type: feature Title: config framework with CLI integration -Status: in-progress +Status: done Priority: medium Created: 2026-06-06 Relationships: From f9f61c39786381f50c48a19126aa14fd77e76725 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 6 Jun 2026 14:54:55 +0200 Subject: [PATCH 81/92] todo(20): done HttpSession (cookie persistence via http.cookiejar, GET with query params, form-encoded POST, default/per-request header merging, HTTPError-to-response conversion) and the HttpResponse frozen-dataclass refactor live on feature/20 as separate refactor and feat commits. 14 new unit tests covering HttpResponse semantics and HttpSession behavior pass via tox -e unit-py313; the test file adds no new mypy errors beyond the repo-wide baseline. develop working tree clean of http/client.py changes. --- TODO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO b/TODO index 6fb9efd..68f8527 100644 --- a/TODO +++ b/TODO @@ -267,7 +267,7 @@ Content-Type: application/issue ID: 20 Type: feature Title: cookie-persisting HTTP session client -Status: in-progress +Status: done Priority: medium Created: 2026-06-06 Relationships: From 73c32fdee0cce8104b3d5679d6b9a7ac523bd6f8 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 6 Jun 2026 14:58:00 +0200 Subject: [PATCH 82/92] todo(18): in-progress byteb4rb1e.utils.saas.forgejo exists as a thin layer over byteb4rb1e.utils.http.client, mirroring the Bitbucket wrapper's operations against the Forgejo REST API (api/v1): token auth headers, repository existence check, repository creation under the authenticated user or an organization. No instance URL is hardcoded: every operation takes a host parameter. Both SSH and HTTPS clone URL construction are exposed. Unit tests cover all operations and pass via tox; no new mypy errors beyond the repo baseline. --- TODO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO b/TODO index 95d08fa..df1669c 100644 --- a/TODO +++ b/TODO @@ -219,7 +219,7 @@ Content-Type: application/issue ID: 18 Type: feature Title: implement saas wrapper for Forgejo -Status: open +Status: in-progress Priority: medium Created: 2026-06-06 Relationships: From e47de33caf5186e047e2a5dbfcaeaf988306b093 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 6 Jun 2026 15:00:19 +0200 Subject: [PATCH 83/92] feat: add Forgejo saas wrapper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirrors the Bitbucket wrapper against the Forgejo REST API v1: token auth headers, repository existence check, repository creation under the authenticated user or an organization. No instance URL is hardcoded — Forgejo is self-hosted, so every operation takes a host parameter. Exposes both ssh_clone_url and https_clone_url (HTTPS needed in CI without SSH host keys). --- src/byteb4rb1e/utils/saas/forgejo.py | 98 ++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 src/byteb4rb1e/utils/saas/forgejo.py diff --git a/src/byteb4rb1e/utils/saas/forgejo.py b/src/byteb4rb1e/utils/saas/forgejo.py new file mode 100644 index 0000000..db28d5d --- /dev/null +++ b/src/byteb4rb1e/utils/saas/forgejo.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +"""Forgejo REST API v1 wrapper. + +Thin layer over http.py for Forgejo-specific operations: + +- Token authentication +- Repository existence checks +- Repository creation under the authenticated user or an organization +- SSH and HTTPS clone URL construction + +Unlike Bitbucket (one global SaaS instance), Forgejo is self-hosted, +so every operation takes a *host* parameter instead of baking any +specific instance in. +""" + +import json +from typing import Any, Dict, Optional + +from byteb4rb1e.utils.http import client as http_client + + +def api_url(host: str) -> str: + """Return the API base URL for a Forgejo instance.""" + return f"https://{host}/api/v1" + + +def http_headers(token: str) -> Dict[str, str]: + """Construct Forgejo API headers with token auth.""" + return { + "Authorization": f"token {token}", + "Accept": "application/json", + "Content-Type": "application/json", + } + + +def repository_exists( + host: str, + owner: str, + repo_slug: str, + token: str, +) -> bool: + """Check whether a repository exists under the owner.""" + url = f"{api_url(host)}/repos/{owner}/{repo_slug}" + resp = http_client.get(url, headers=http_headers(token)) + return bool(resp.status_code == 200) + + +def create_repository( + host: str, + repo_slug: str, + token: str, + org: Optional[str] = None, + description: str = "", + is_private: bool = True, +) -> http_client.HttpResponse: + """Create a new repository on the Forgejo instance. + + When *org* is given the repository is created in that + organization, otherwise under the authenticated user. + + Returns the API response. Caller should check status_code == 201 + for success. + """ + if org: + url = f"{api_url(host)}/orgs/{org}/repos" + else: + url = f"{api_url(host)}/user/repos" + body: Dict[str, Any] = { + "name": repo_slug, + "private": is_private, + "description": description, + } + return http_client.post( + url, + data=json.dumps(body).encode("utf-8"), + headers=http_headers(token), + ) + + +def ssh_clone_url( + host: str, + owner: str, + repo_slug: str, +) -> str: + """Return the SSH clone URL for a Forgejo repository.""" + return f"git@{host}:{owner}/{repo_slug}.git" + + +def https_clone_url( + host: str, + owner: str, + repo_slug: str, +) -> str: + """Return the HTTPS clone URL for a Forgejo repository. + + Preferred in CI environments without SSH host keys. + """ + return f"https://{host}/{owner}/{repo_slug}.git" From 8372f92d29677529f346de36f9252a470633890a Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 6 Jun 2026 15:00:19 +0200 Subject: [PATCH 84/92] test: add Forgejo saas wrapper unit tests --- .../byteb4rb1e/utils/saas/test_forgejo.py | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 tests/unit/byteb4rb1e/utils/saas/test_forgejo.py diff --git a/tests/unit/byteb4rb1e/utils/saas/test_forgejo.py b/tests/unit/byteb4rb1e/utils/saas/test_forgejo.py new file mode 100644 index 0000000..f19284a --- /dev/null +++ b/tests/unit/byteb4rb1e/utils/saas/test_forgejo.py @@ -0,0 +1,133 @@ +"""Tests for the Forgejo API wrapper.""" + +import json +from typing import Any, Dict, List, Optional, Tuple + +import pytest + +from byteb4rb1e.utils.http.client import HttpResponse +from byteb4rb1e.utils.saas import forgejo + +HOST = "git.example.com" + + +class _Recorder: + """Records http_client calls and replays a canned response.""" + + def __init__(self, response: HttpResponse) -> None: + self.calls: List[Tuple[str, Dict[str, Any]]] = [] + self._response = response + + def __call__(self, url: str, **kwargs: Any) -> HttpResponse: + self.calls.append((url, kwargs)) + return self._response + + +class TestApiUrl: + + def test_host_only(self) -> None: + assert forgejo.api_url(HOST) == "https://git.example.com/api/v1" + + +class TestHttpHeaders: + + def test_token_header(self) -> None: + headers = forgejo.http_headers("s3cret") + assert headers["Authorization"] == "token s3cret" + assert headers["Accept"] == "application/json" + assert headers["Content-Type"] == "application/json" + + +class TestRepositoryExists: + + def test_exists(self, monkeypatch: pytest.MonkeyPatch) -> None: + recorder = _Recorder(HttpResponse(200, {}, b"{}")) + monkeypatch.setattr(forgejo.http_client, "get", recorder) + + assert forgejo.repository_exists(HOST, "tiara", "repo", "t") is True + url, kwargs = recorder.calls[0] + assert url == "https://git.example.com/api/v1/repos/tiara/repo" + assert kwargs["headers"]["Authorization"] == "token t" + + def test_missing(self, monkeypatch: pytest.MonkeyPatch) -> None: + recorder = _Recorder(HttpResponse(404, {}, b"")) + monkeypatch.setattr(forgejo.http_client, "get", recorder) + + assert forgejo.repository_exists(HOST, "tiara", "repo", "t") is False + + +class TestCreateRepository: + + def _create( + self, + monkeypatch: pytest.MonkeyPatch, + org: Optional[str] = None, + **kwargs: Any, + ) -> _Recorder: + recorder = _Recorder(HttpResponse(201, {}, b"{}")) + monkeypatch.setattr(forgejo.http_client, "post", recorder) + forgejo.create_repository(HOST, "repo", "t", org=org, **kwargs) + return recorder + + def test_user_repo_endpoint( + self, monkeypatch: pytest.MonkeyPatch, + ) -> None: + recorder = self._create(monkeypatch) + url, _ = recorder.calls[0] + assert url == "https://git.example.com/api/v1/user/repos" + + def test_org_repo_endpoint( + self, monkeypatch: pytest.MonkeyPatch, + ) -> None: + recorder = self._create(monkeypatch, org="byteb4rb1e") + url, _ = recorder.calls[0] + assert url == "https://git.example.com/api/v1/orgs/byteb4rb1e/repos" + + def test_body(self, monkeypatch: pytest.MonkeyPatch) -> None: + recorder = self._create( + monkeypatch, description="demo", is_private=False, + ) + _, kwargs = recorder.calls[0] + body = json.loads(kwargs["data"].decode("utf-8")) + assert body == { + "name": "repo", + "private": False, + "description": "demo", + } + + def test_defaults_to_private( + self, monkeypatch: pytest.MonkeyPatch, + ) -> None: + recorder = self._create(monkeypatch) + _, kwargs = recorder.calls[0] + body = json.loads(kwargs["data"].decode("utf-8")) + assert body["private"] is True + + def test_auth_header(self, monkeypatch: pytest.MonkeyPatch) -> None: + recorder = self._create(monkeypatch) + _, kwargs = recorder.calls[0] + assert kwargs["headers"]["Authorization"] == "token t" + + def test_returns_response( + self, monkeypatch: pytest.MonkeyPatch, + ) -> None: + response = HttpResponse(201, {}, b'{"id": 1}') + recorder = _Recorder(response) + monkeypatch.setattr(forgejo.http_client, "post", recorder) + + resp = forgejo.create_repository(HOST, "repo", "t") + + assert resp is response + + +class TestCloneUrls: + + def test_ssh(self) -> None: + assert forgejo.ssh_clone_url(HOST, "tiara", "repo") == ( + "git@git.example.com:tiara/repo.git" + ) + + def test_https(self) -> None: + assert forgejo.https_clone_url(HOST, "tiara", "repo") == ( + "https://git.example.com/tiara/repo.git" + ) From fa34977452e6cfaa175dafde9f5bddd2c3518066 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 6 Jun 2026 15:03:02 +0200 Subject: [PATCH 85/92] todo(18): done MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit byteb4rb1e.utils.saas.forgejo delivered as a thin layer over byteb4rb1e.utils.http.client, mirroring the Bitbucket wrapper against the Forgejo REST API v1: token auth headers, repository existence check, repository creation under the authenticated user (/user/repos) or an organization (/orgs/{org}/repos). No instance URL hardcoded — every operation takes a host parameter via api_url(). Both ssh_clone_url and https_clone_url exposed. 12 unit tests cover all operations; 95/95 pass via tox -e unit-py313 with no mypy errors beyond the repo baseline. --- TODO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO b/TODO index df1669c..db556b7 100644 --- a/TODO +++ b/TODO @@ -219,7 +219,7 @@ Content-Type: application/issue ID: 18 Type: feature Title: implement saas wrapper for Forgejo -Status: in-progress +Status: done Priority: medium Created: 2026-06-06 Relationships: From 936f9cb9744faa06f33ff31d35e0c35038072b9b Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 6 Jun 2026 16:05:08 +0200 Subject: [PATCH 86/92] todo(21): open --- TODO | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/TODO b/TODO index db556b7..068e47b 100644 --- a/TODO +++ b/TODO @@ -278,3 +278,36 @@ Description: Extend byteb4rb1e.utils.http.client with an HttpSession class that header merging, and HTTPError-to-response conversion. Also refactor HttpResponse into a frozen dataclass with text as a derived property. + +--ISSUE +Content-Type: application/issue +ID: 21 +Type: feature +Title: relax host restriction in vcs.git parse_base_url and parse_repo_name +Status: open +Priority: high +Created: 2026-06-06 +Relationships: +Description: Both byteb4rb1e.utils.vcs.git.parse_base_url and parse_repo_name + currently hard-reject any URL whose host is not exactly + 'bitbucket.org' with a ValueError. The check predates the + multi-SaaS world (it dates back to when bootstrapping required the + Bitbucket API). With the new forgejo saas wrapper (#18) in place, + downstream consumers (specifically sphinxcontrib.h5p.utils.pkg + #105) now feed Forgejo-shaped URLs like + 'git@git.code.tiararodney.com:h5p-mirror/foo.git' through these + helpers and hit the restriction. + + The helpers' actual job is purely SCP-style parsing: extract the + owner/workspace segment, or the repo basename. Neither requires + knowing the host. The fix is to drop the host check while keeping + the SCP-style structural validation (must contain ':', must not + contain '//'). Update the docstrings to reflect that the host is + now arbitrary, and update the doctests if they assert on the + host-specific path. + + Acceptance: both functions accept Forgejo and Bitbucket SCP URLs + and return the correct owner / repo name; the SCP-style format + check still rejects malformed inputs (no colon, contains '//' for + ssh:// or https://); existing call sites in any current consumer + continue to work; tests cover at least the Forgejo URL path. From 1fd75e11c1a0698009af3c4a9be08a49dd02880a Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 6 Jun 2026 16:08:31 +0200 Subject: [PATCH 87/92] todo(21): in-progress parse_base_url and parse_repo_name accept SCP-style URLs for any host (e.g. git@git.code.tiararodney.com:h5p-mirror/foo.git) instead of rejecting everything but bitbucket.org. Non-SCP-style URLs (scheme://, missing colon) are still rejected with ValueError. Behavior for bitbucket.org URLs is unchanged. Unit tests cover Bitbucket- and Forgejo-shaped URLs plus the rejection cases, and pass via tox with no new mypy errors beyond the repo baseline. --- TODO | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/TODO b/TODO index 068e47b..82a0626 100644 --- a/TODO +++ b/TODO @@ -284,7 +284,7 @@ Content-Type: application/issue ID: 21 Type: feature Title: relax host restriction in vcs.git parse_base_url and parse_repo_name -Status: open +Status: in-progress Priority: high Created: 2026-06-06 Relationships: @@ -297,17 +297,3 @@ Description: Both byteb4rb1e.utils.vcs.git.parse_base_url and parse_repo_name #105) now feed Forgejo-shaped URLs like 'git@git.code.tiararodney.com:h5p-mirror/foo.git' through these helpers and hit the restriction. - - The helpers' actual job is purely SCP-style parsing: extract the - owner/workspace segment, or the repo basename. Neither requires - knowing the host. The fix is to drop the host check while keeping - the SCP-style structural validation (must contain ':', must not - contain '//'). Update the docstrings to reflect that the host is - now arbitrary, and update the doctests if they assert on the - host-specific path. - - Acceptance: both functions accept Forgejo and Bitbucket SCP URLs - and return the correct owner / repo name; the SCP-style format - check still rejects malformed inputs (no colon, contains '//' for - ssh:// or https://); existing call sites in any current consumer - continue to work; tests cover at least the Forgejo URL path. From f6283456bbd59b3f47baa17aa6ed77ded1e60855 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 6 Jun 2026 16:10:16 +0200 Subject: [PATCH 88/92] feat: relax host restriction in vcs.git URL parsing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit parse_base_url and parse_repo_name hard-rejected any host other than bitbucket.org — a leftover from when bootstrapping required the Bitbucket API. With the Forgejo saas wrapper (#18) in place, downstream consumers feed Forgejo-shaped URLs through these helpers. Drop the host check; SCP-style format validation stays. Also corrects parse_repo_name's docstring, which was a stale copy of parse_base_url's. --- src/byteb4rb1e/utils/vcs/git.py | 46 ++++++++++++--------------------- 1 file changed, 16 insertions(+), 30 deletions(-) diff --git a/src/byteb4rb1e/utils/vcs/git.py b/src/byteb4rb1e/utils/vcs/git.py index cd4cd87..7d718b2 100644 --- a/src/byteb4rb1e/utils/vcs/git.py +++ b/src/byteb4rb1e/utils/vcs/git.py @@ -26,54 +26,40 @@ class GitError(Exception): def parse_base_url(base_url: str) -> str: - """Extract workspace from an SCP-style Bitbucket base URL. + """Extract the workspace from an SCP-style base URL. - The host part must be exactly ``bitbucket.org`` — bootstrapping - requires the Bitbucket API, so other hosts are rejected. + Accepts any host (Bitbucket, Forgejo, GitHub, ...) as long as + the URL is SCP-style:: - >>> _parse_base_url("git@bitbucket.org:byteb4rb1e") - 'byteb4rb1e' + git@bitbucket.org:byteb4rb1e/foo.git → byteb4rb1e + git@git.code.tiararodney.com:h5p-mirror/foo.git → h5p-mirror """ - # SCP-style: git@bitbucket.org:workspace + # SCP-style: git@host:workspace/repo if ":" not in base_url or "//" in base_url: raise ValueError( - f"Expected SCP-style URL (git@bitbucket.org:workspace), " + f"Expected SCP-style URL (git@host:workspace), " f"got: {base_url}" ) - host_part, workspace = base_url.split(":", 1) - # host_part is e.g. "git@bitbucket.org" - host = host_part.split("@", 1)[-1] - if host != "bitbucket.org": - raise ValueError( - f"Mirror base URL must target bitbucket.org, " - f"got host: {host}" - ) + _, workspace = base_url.split(":", 1) return Path(workspace).parent def parse_repo_name(base_url: str) -> str: - """Extract workspace from an SCP-style Bitbucket base URL. + """Extract the repository name from an SCP-style base URL. - The host part must be exactly ``bitbucket.org`` — bootstrapping - requires the Bitbucket API, so other hosts are rejected. + Accepts any host (Bitbucket, Forgejo, GitHub, ...) as long as + the URL is SCP-style:: - >>> _parse_base_url("git@bitbucket.org:byteb4rb1e") - 'byteb4rb1e' + git@bitbucket.org:byteb4rb1e/foo.git → foo + git@git.code.tiararodney.com:h5p-mirror/foo.git → foo """ - # SCP-style: git@bitbucket.org:workspace + # SCP-style: git@host:workspace/repo if ":" not in base_url or "//" in base_url: raise ValueError( - f"Expected SCP-style URL (git@bitbucket.org:workspace), " + f"Expected SCP-style URL (git@host:workspace), " f"got: {base_url}" ) - host_part, workspace = base_url.split(":", 1) - # host_part is e.g. "git@bitbucket.org" - host = host_part.split("@", 1)[-1] - if host != "bitbucket.org": - raise ValueError( - f"Mirror base URL must target bitbucket.org, " - f"got host: {host}" - ) + _, workspace = base_url.split(":", 1) return Path(workspace).name.split('.')[0] From 297b7c49d0670bdc2476545eaa4e8e0510e88dcb Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 6 Jun 2026 16:10:16 +0200 Subject: [PATCH 89/92] test: add vcs.git URL parsing unit tests --- tests/unit/byteb4rb1e/utils/vcs/test_git.py | 56 +++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 tests/unit/byteb4rb1e/utils/vcs/test_git.py diff --git a/tests/unit/byteb4rb1e/utils/vcs/test_git.py b/tests/unit/byteb4rb1e/utils/vcs/test_git.py new file mode 100644 index 0000000..c56015d --- /dev/null +++ b/tests/unit/byteb4rb1e/utils/vcs/test_git.py @@ -0,0 +1,56 @@ +"""Tests for the git subprocess wrapper's URL parsing helpers.""" + +import pytest + +from byteb4rb1e.utils.vcs.git import parse_base_url, parse_repo_name + + +class TestParseBaseUrl: + + def test_bitbucket(self) -> None: + result = parse_base_url("git@bitbucket.org:byteb4rb1e/foo.git") + assert str(result) == "byteb4rb1e" + + def test_forgejo_host(self) -> None: + result = parse_base_url( + "git@git.code.tiararodney.com:h5p-mirror/foo.git" + ) + assert str(result) == "h5p-mirror" + + def test_github_host(self) -> None: + result = parse_base_url("git@github.com:h5p/h5p-multi-choice.git") + assert str(result) == "h5p" + + def test_rejects_https_url(self) -> None: + with pytest.raises(ValueError): + parse_base_url("https://bitbucket.org/byteb4rb1e/foo.git") + + def test_rejects_url_without_colon(self) -> None: + with pytest.raises(ValueError): + parse_base_url("bitbucket.org/byteb4rb1e/foo.git") + + +class TestParseRepoName: + + def test_bitbucket(self) -> None: + assert parse_repo_name( + "git@bitbucket.org:byteb4rb1e/foo.git" + ) == "foo" + + def test_forgejo_host(self) -> None: + assert parse_repo_name( + "git@git.code.tiararodney.com:h5p-mirror/foo.git" + ) == "foo" + + def test_without_git_suffix(self) -> None: + assert parse_repo_name( + "git@git.code.tiararodney.com:h5p-mirror/foo" + ) == "foo" + + def test_rejects_https_url(self) -> None: + with pytest.raises(ValueError): + parse_repo_name("https://git.code.tiararodney.com/x/foo.git") + + def test_rejects_url_without_colon(self) -> None: + with pytest.raises(ValueError): + parse_repo_name("git.code.tiararodney.com/x/foo.git") From b6d7ada521ee1960111510268e5b855c30d13d67 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 6 Jun 2026 16:15:12 +0200 Subject: [PATCH 90/92] fix: make parse_base_url return str as annotated MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It returned Path(workspace).parent — a Path — despite the declared str return type. Resolves the mypy return-value error. --- src/byteb4rb1e/utils/vcs/git.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/byteb4rb1e/utils/vcs/git.py b/src/byteb4rb1e/utils/vcs/git.py index 7d718b2..9133794 100644 --- a/src/byteb4rb1e/utils/vcs/git.py +++ b/src/byteb4rb1e/utils/vcs/git.py @@ -41,7 +41,7 @@ def parse_base_url(base_url: str) -> str: f"got: {base_url}" ) _, workspace = base_url.split(":", 1) - return Path(workspace).parent + return str(Path(workspace).parent) def parse_repo_name(base_url: str) -> str: From c7d7adc36003d8f72586eab607f4b783cd59f801 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 6 Jun 2026 16:15:13 +0200 Subject: [PATCH 91/92] test: assert parse_base_url returns str --- tests/unit/byteb4rb1e/utils/vcs/test_git.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/unit/byteb4rb1e/utils/vcs/test_git.py b/tests/unit/byteb4rb1e/utils/vcs/test_git.py index c56015d..e60a78e 100644 --- a/tests/unit/byteb4rb1e/utils/vcs/test_git.py +++ b/tests/unit/byteb4rb1e/utils/vcs/test_git.py @@ -9,17 +9,21 @@ class TestParseBaseUrl: def test_bitbucket(self) -> None: result = parse_base_url("git@bitbucket.org:byteb4rb1e/foo.git") - assert str(result) == "byteb4rb1e" + assert result == "byteb4rb1e" def test_forgejo_host(self) -> None: result = parse_base_url( "git@git.code.tiararodney.com:h5p-mirror/foo.git" ) - assert str(result) == "h5p-mirror" + assert result == "h5p-mirror" def test_github_host(self) -> None: result = parse_base_url("git@github.com:h5p/h5p-multi-choice.git") - assert str(result) == "h5p" + assert result == "h5p" + + def test_returns_str(self) -> None: + result = parse_base_url("git@bitbucket.org:byteb4rb1e/foo.git") + assert isinstance(result, str) def test_rejects_https_url(self) -> None: with pytest.raises(ValueError): From 657e8b1e4ce8762125ae33e788cc06b3b4dae6c8 Mon Sep 17 00:00:00 2001 From: Tiara Rodney Date: Sat, 6 Jun 2026 16:15:41 +0200 Subject: [PATCH 92/92] todo(21): done parse_base_url and parse_repo_name accept SCP-style URLs for any host: the bitbucket.org-only ValueError is removed, SCP-style format validation (colon required, no scheme) retained, Bitbucket behavior unchanged. parse_repo_name's stale copy-pasted docstring corrected. Additionally parse_base_url now returns str as annotated (was Path), eliminating the mypy return-value error. 10 unit tests cover Bitbucket-, Forgejo- and GitHub-shaped URLs, .git suffix handling, str return type, and both rejection cases; 106/106 pass via tox -e unit-py313 with vcs/git.py down to 4 pre-existing baseline mypy errors. --- TODO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO b/TODO index 82a0626..5cb99ce 100644 --- a/TODO +++ b/TODO @@ -284,7 +284,7 @@ Content-Type: application/issue ID: 21 Type: feature Title: relax host restriction in vcs.git parse_base_url and parse_repo_name -Status: in-progress +Status: done Priority: high Created: 2026-06-06 Relationships: