├── src ├── __init__.py ├── ihex_view.py ├── srec_view.py ├── ti_txt_view.py ├── helper.py └── base_view.py ├── test ├── __init__.py ├── test_ihex.py ├── test_srec.py └── test_ti_txt.py ├── .gitignore ├── __init__.py ├── hexfiles.py ├── requirements.txt ├── noxfile.py ├── README.md ├── LICENSE ├── pyproject.toml ├── plugin.json └── poetry.lock /src/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | venv 2 | .vscode 3 | __pycache__ 4 | .idea 5 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | from .hexfiles import register 2 | 3 | register() 4 | -------------------------------------------------------------------------------- /src/ihex_view.py: -------------------------------------------------------------------------------- 1 | from .base_view import BaseView 2 | from .helper import HexfileType 3 | 4 | 5 | class IHexView(BaseView): 6 | name = "IHex" 7 | long_name = "Intel HEX" 8 | hexfile_type = HexfileType.IHEX 9 | -------------------------------------------------------------------------------- /src/srec_view.py: -------------------------------------------------------------------------------- 1 | from .base_view import BaseView 2 | from .helper import HexfileType 3 | 4 | 5 | class SrecView(BaseView): 6 | name = "SREC" 7 | long_name = "Motorola S-record" 8 | hexfile_type = HexfileType.SREC 9 | -------------------------------------------------------------------------------- /src/ti_txt_view.py: -------------------------------------------------------------------------------- 1 | from .base_view import BaseView 2 | from .helper import HexfileType 3 | 4 | 5 | class TiTxtView(BaseView): 6 | name = "TI_TXT" 7 | long_name = "Texas Instruments TXT" 8 | hexfile_type = HexfileType.TI_TXT 9 | -------------------------------------------------------------------------------- /hexfiles.py: -------------------------------------------------------------------------------- 1 | from .src.ihex_view import IHexView 2 | from .src.srec_view import SrecView 3 | from .src.ti_txt_view import TiTxtView 4 | 5 | 6 | def register() -> None: 7 | IHexView.register() 8 | TiTxtView.register() 9 | SrecView.register() 10 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | argparse-addons==0.12.0 ; python_version >= "3.10" and python_version < "4.0" 2 | bincopy==17.14.5 ; python_version >= "3.10" and python_version < "4.0" 3 | humanfriendly==10.0 ; python_version >= "3.10" and python_version < "4.0" 4 | pyelftools==0.31 ; python_version >= "3.10" and python_version < "4.0" 5 | pyreadline3==3.4.1 ; sys_platform == "win32" and python_version >= "3.10" and python_version < "4.0" 6 | -------------------------------------------------------------------------------- /test/test_ihex.py: -------------------------------------------------------------------------------- 1 | import binaryninja 2 | from binaryninja import BinaryView 3 | from bincopy import is_ihex 4 | from ..src.ihex_view import IHexView 5 | 6 | data = ":10010000214601360121470136007EFE09D2190140" 7 | 8 | 9 | def make_view() -> BinaryView: 10 | return binaryninja.load(data.encode("utf8")) 11 | 12 | 13 | def test_valid() -> None: 14 | assert is_ihex(data) 15 | 16 | 17 | def test_invalid() -> None: 18 | bv = binaryninja.load(b"\xff\xff\xff\xff") 19 | assert IHexView.is_valid_for_data(bv) is False 20 | 21 | 22 | def test_make_ihex() -> None: 23 | view = make_view() 24 | assert view.view_type == "IHex" 25 | -------------------------------------------------------------------------------- /test/test_srec.py: -------------------------------------------------------------------------------- 1 | import binaryninja 2 | from binaryninja import BinaryView 3 | from bincopy import is_srec 4 | from ..src.srec_view import SrecView 5 | 6 | data = "S1137AF00A0A0D0000000000000000000000000061" 7 | 8 | 9 | def make_view() -> BinaryView: 10 | return binaryninja.load(data.encode("utf8")) 11 | 12 | 13 | def test_valid() -> None: 14 | assert is_srec(data) 15 | 16 | 17 | def test_invalid() -> None: 18 | bv = binaryninja.load(b"\xff\xff\xff\xff") 19 | assert SrecView.is_valid_for_data(bv) is False 20 | 21 | 22 | def test_make_view() -> None: 23 | view = make_view() 24 | assert view.view_type == "SREC" 25 | -------------------------------------------------------------------------------- /noxfile.py: -------------------------------------------------------------------------------- 1 | from nox import Session 2 | from nox_poetry import session 3 | 4 | 5 | @session(venv_backend="none") 6 | def fmt(s: Session) -> None: 7 | s.run("ruff", "check", ".", "--fix") 8 | s.run("black", ".") 9 | 10 | 11 | @session(venv_backend="none") 12 | def generate_requirements_txt(s: Session) -> None: 13 | s.run("poetry", "export", "-f", "requirements.txt", "--output", "requirements.txt") 14 | 15 | 16 | @session(venv_backend="none") 17 | def test(s: Session) -> None: 18 | s.run("python", "-m", "pytest", "test", *s.posargs) 19 | 20 | 21 | @session(venv_backend="none") 22 | def type_check(s: Session) -> None: 23 | s.run("python", "-m", "mypy", "src", "test") 24 | -------------------------------------------------------------------------------- /test/test_ti_txt.py: -------------------------------------------------------------------------------- 1 | import binaryninja 2 | from binaryninja import BinaryView 3 | from bincopy import is_ti_txt 4 | from ..src.ti_txt_view import TiTxtView 5 | 6 | data = """\ 7 | @F000 8 | 31 40 00 03 B2 40 80 5A 20 01 D2 D3 22 00 D2 E3 9 | 21 00 3F 40 E8 FD 1F 83 FE 23 F9 3F 10 | @FFFE 11 | 00 F0 12 | q 13 | """ 14 | 15 | 16 | def make_view() -> BinaryView: 17 | return binaryninja.load(data.encode("utf8")) 18 | 19 | 20 | def test_valid() -> None: 21 | assert is_ti_txt(data) 22 | 23 | 24 | def test_invalid() -> None: 25 | bv = binaryninja.load(b"\xff\xff\xff\xff") 26 | assert TiTxtView.is_valid_for_data(bv) is False 27 | 28 | 29 | def test_make_view() -> None: 30 | view = make_view() 31 | assert view.view_type == "TI_TXT" 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hexfiles (v1.3.0) 2 | Author: **toolCHAINZ** 3 | 4 | _A simple loader for Motorola SREC, Intel HEX, and TI-TXT files._ 5 | 6 | ## Description: 7 | 8 | # Hexfiles 9 | 10 | `hexfiles` provides a simple `BinaryView` for "Hex" files (Motorola SREC, Intel Hex, TI-TXT). The actual parsing of hex files is offloaded to the excellent Python library `bincopy`. For now, this `BinaryView` is read-only (patches will not be saved back into the source hex file). Will hopefully add that soon. 11 | 12 | 13 | ## Installation Instructions 14 | 15 | ### Darwin 16 | 17 | `pip install -r requirements.txt` 18 | 19 | ### Windows 20 | 21 | `py -m pip install -r requirements.txt` 22 | 23 | ### Linux 24 | 25 | `pip install -r requirements.txt` 26 | 27 | ## Minimum Version 28 | 29 | This plugin requires the following minimum version of Binary Ninja: 30 | 31 | * 2170 32 | 33 | 34 | ## License 35 | 36 | This plugin is released under a MIT license. 37 | ## Metadata Version 38 | 39 | 2 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020-2023 toolCHAINZ 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "hexfiles" 3 | version = "1.3.0" 4 | description = "A simple loader for Motorola SREC, Intel HEX, and TI-TXT files." 5 | authors = ["toolCHAINZ"] 6 | license = "MIT" 7 | readme = "README.md" 8 | 9 | [tool.poetry.dependencies] 10 | python = "^3.10" 11 | bincopy = "^17.14.5" 12 | 13 | 14 | [tool.poetry.group.dev.dependencies] 15 | nox = "^2023.4.22" 16 | ruff = "^0.0.269" 17 | black = "^23.3.0" 18 | nox-poetry = "^1.0.2" 19 | pytest = "^7.3.1" 20 | mypy = "^1.3.0" 21 | 22 | [build-system] 23 | requires = ["poetry-core"] 24 | build-backend = "poetry.core.masonry.api" 25 | 26 | [tool.ruff] 27 | line-length = 99 28 | target-version = "py38" 29 | extend-select = [ 30 | "I", # isort 31 | "N", # pep8-naming 32 | "UP", # pyupgrade 33 | "RUF", # ruff 34 | "B", # flake8-bugbear 35 | "C4", # flake8-comprehensions 36 | "PTH", # flake8-use-pathlib 37 | "SIM", # flake8-simplify 38 | "TID", # flake8-tidy-imports 39 | "TID252" # prefer relative imports 40 | ] 41 | extend-ignore = ["RUF005"] 42 | src = ["src"] 43 | 44 | [tool.mypy] 45 | warn_return_any = true 46 | warn_unused_configs = true 47 | strict = true 48 | namespace_packages = true 49 | 50 | [[tool.mypy.overrides]] 51 | module = "binaryninja" 52 | ignore_missing_imports = true 53 | 54 | 55 | [[tool.mypy.overrides]] 56 | module = "bincopy" 57 | ignore_missing_imports = true -------------------------------------------------------------------------------- /src/helper.py: -------------------------------------------------------------------------------- 1 | import enum 2 | 3 | from binaryninja import BinaryView 4 | from bincopy import BinFile, _Segment, is_ihex, is_srec, is_ti_txt 5 | 6 | 7 | class HexfileType(enum.IntEnum): 8 | TI_TXT = 1 9 | IHEX = 2 10 | SREC = 3 11 | # VMEM = 4 12 | 13 | 14 | def is_valid_for_data(bv: BinaryView, ty: HexfileType) -> bool: 15 | length = bv.length 16 | capped_length = min(length, 1000) 17 | # read up to 1000 bytes 18 | data = bv.read(0, capped_length) 19 | 20 | if any(b > 128 for b in data): 21 | return False 22 | # find last newline 23 | index = data.find(b"\n") 24 | trimmed = data 25 | if index != -1: 26 | trimmed = data[0 : index + 1] 27 | match ty: 28 | case HexfileType.TI_TXT: 29 | if bv.length < 2: 30 | return False 31 | last_two_bytes = bv.read(bv.length - 2, 2) 32 | if last_two_bytes != b"q\n": 33 | return False 34 | return is_ti_txt(bv.read(0, bv.length).decode("ascii")) # type: ignore 35 | case HexfileType.IHEX: 36 | return is_ihex(trimmed.decode("ascii")) # type: ignore 37 | case HexfileType.SREC: 38 | return is_srec(trimmed.decode("ascii")) # type: ignore 39 | # case HexfileType.VMEM: 40 | # return is_verilog_vmem(trimmed.decode("utf8")) 41 | 42 | 43 | def get_segments(bv: BinaryView) -> list[_Segment]: 44 | binfile = BinFile() 45 | binfile.add(bv.read(0, bv.length).decode("ascii")) 46 | return binfile.segments # type: ignore 47 | -------------------------------------------------------------------------------- /plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginmetadataversion": 2, 3 | "name": "hexfiles", 4 | "author": "toolCHAINZ", 5 | "type": [ 6 | "binaryview" 7 | ], 8 | "api": [ 9 | "python3" 10 | ], 11 | "description": "A simple loader for Motorola SREC, Intel HEX, and TI-TXT files.", 12 | "longdescription": "# Hexfiles\n\n `hexfiles` provides a simple `BinaryView` for \"Hex\" files (Motorola SREC, Intel Hex, TI-TXT). The actual parsing of hex files is offloaded to the excellent Python library `bincopy`. For now, this `BinaryView` is read-only (patches will not be saved back into the source hex file). Will hopefully add that soon.", 13 | "license": { 14 | "name": "MIT", 15 | "text": "Copyright 2020-2023 toolCHAINZ\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." 16 | }, 17 | "platforms": [ 18 | "Darwin", 19 | "Windows", 20 | "Linux" 21 | ], 22 | "installinstructions": { 23 | "Darwin": "`pip install -r requirements.txt`", 24 | "Windows": "`py -m pip install -r requirements.txt`", 25 | "Linux": "`pip install -r requirements.txt`" 26 | }, 27 | "version": "1.3.0", 28 | "minimumbinaryninjaversion": 2170 29 | } -------------------------------------------------------------------------------- /src/base_view.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from binaryninja import BinaryView, SegmentFlag, Settings, Platform 4 | from bincopy import _Segment 5 | import binaryninja 6 | 7 | from .helper import ( 8 | HexfileType, 9 | get_segments, 10 | is_valid_for_data, 11 | ) 12 | 13 | 14 | class BaseView(BinaryView): # type: ignore 15 | hexfile_type: HexfileType 16 | hex_segments: list[_Segment] 17 | 18 | @classmethod 19 | def get_load_settings_for_data(cls, _data: BinaryView) -> Optional[Settings]: 20 | s = Settings() 21 | return s 22 | 23 | @classmethod 24 | def is_valid_for_data(cls, data: BinaryView) -> bool: 25 | return is_valid_for_data(data, cls.hexfile_type) 26 | 27 | def __init__(self, data: BinaryView): 28 | self.hex_segments = get_segments(data) 29 | decoded = b"".join(s.data for s in self.hex_segments) 30 | parent = BinaryView.new(data=decoded) 31 | BinaryView.__init__(self, file_metadata=data.file, parent_view=parent) 32 | 33 | def init(self) -> bool: 34 | offset = 0 35 | platform_list = list(Platform) 36 | self.platform = platform_list[binaryninja.interaction.get_choice_input("Select the platform of the loaded file: ","Platform Selection", [x.name for x in platform_list])] 37 | for segment in self.hex_segments: 38 | length = len(segment.data) 39 | choice = binaryninja.interaction.get_choice_input(f"Select permission levels for the segment {hex(segment.address)}: ", f"Segment Permissions {hex(segment.address)}", ["r--", "rw-", "r-x","rwx"]) 40 | perms = None 41 | match choice: 42 | case 0: 43 | perms = SegmentFlag.SegmentReadable 44 | case 1: 45 | perms = SegmentFlag.SegmentReadable | SegmentFlag.SegmentWritable 46 | case 2: 47 | perms = SegmentFlag.SegmentReadable | SegmentFlag.SegmentExecutable 48 | case 3: 49 | perms = SegmentFlag.SegmentReadable | SegmentFlag.SegmentWritable | SegmentFlag.SegmentExecutable 50 | case _: 51 | return False 52 | 53 | 54 | self.add_auto_segment( 55 | segment.address, 56 | length, 57 | offset, 58 | length, 59 | perms 60 | ) 61 | offset += length 62 | return True 63 | 64 | def perform_get_address_size(self) -> int: 65 | return 4 66 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "argcomplete" 5 | version = "3.2.3" 6 | description = "Bash tab completion for argparse" 7 | optional = false 8 | python-versions = ">=3.8" 9 | files = [ 10 | {file = "argcomplete-3.2.3-py3-none-any.whl", hash = "sha256:c12355e0494c76a2a7b73e3a59b09024ca0ba1e279fb9ed6c1b82d5b74b6a70c"}, 11 | {file = "argcomplete-3.2.3.tar.gz", hash = "sha256:bf7900329262e481be5a15f56f19736b376df6f82ed27576fa893652c5de6c23"}, 12 | ] 13 | 14 | [package.extras] 15 | test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] 16 | 17 | [[package]] 18 | name = "argparse-addons" 19 | version = "0.12.0" 20 | description = "Additional argparse types and actions." 21 | optional = false 22 | python-versions = ">=3.6" 23 | files = [ 24 | {file = "argparse_addons-0.12.0-py3-none-any.whl", hash = "sha256:48b70ecd719054fcb0d7e6f25a1fecc13607aac61d446e83f47d211b4ead0d61"}, 25 | {file = "argparse_addons-0.12.0.tar.gz", hash = "sha256:6322a0dcd706887e76308d23136d5b86da0eab75a282dc6496701d1210b460af"}, 26 | ] 27 | 28 | [[package]] 29 | name = "bincopy" 30 | version = "17.14.5" 31 | description = "Mangling of various file formats that conveys binary information (Motorola S-Record, Intel HEX and binary files)." 32 | optional = false 33 | python-versions = ">=3.6" 34 | files = [ 35 | {file = "bincopy-17.14.5-py3-none-any.whl", hash = "sha256:54d963954047d3bd23d46358dbb2bb7e49d749376850cb702a94791e0894d917"}, 36 | {file = "bincopy-17.14.5.tar.gz", hash = "sha256:5f4de7c37a3db7adcf3edc4833a223f3356d9bf08be72ec6e431c9f0acd29f18"}, 37 | ] 38 | 39 | [package.dependencies] 40 | argparse-addons = ">=0.4.0" 41 | humanfriendly = "*" 42 | pyelftools = "*" 43 | 44 | [[package]] 45 | name = "black" 46 | version = "23.12.1" 47 | description = "The uncompromising code formatter." 48 | optional = false 49 | python-versions = ">=3.8" 50 | files = [ 51 | {file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"}, 52 | {file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"}, 53 | {file = "black-23.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920b569dc6b3472513ba6ddea21f440d4b4c699494d2e972a1753cdc25df7b0"}, 54 | {file = "black-23.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:3fa4be75ef2a6b96ea8d92b1587dd8cb3a35c7e3d51f0738ced0781c3aa3a5a3"}, 55 | {file = "black-23.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d4df77958a622f9b5a4c96edb4b8c0034f8434032ab11077ec6c56ae9f384ba"}, 56 | {file = "black-23.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:602cfb1196dc692424c70b6507593a2b29aac0547c1be9a1d1365f0d964c353b"}, 57 | {file = "black-23.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c4352800f14be5b4864016882cdba10755bd50805c95f728011bcb47a4afd59"}, 58 | {file = "black-23.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:0808494f2b2df923ffc5723ed3c7b096bd76341f6213989759287611e9837d50"}, 59 | {file = "black-23.12.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25e57fd232a6d6ff3f4478a6fd0580838e47c93c83eaf1ccc92d4faf27112c4e"}, 60 | {file = "black-23.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d9e13db441c509a3763a7a3d9a49ccc1b4e974a47be4e08ade2a228876500ec"}, 61 | {file = "black-23.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1bd9c210f8b109b1762ec9fd36592fdd528485aadb3f5849b2740ef17e674e"}, 62 | {file = "black-23.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:ae76c22bde5cbb6bfd211ec343ded2163bba7883c7bc77f6b756a1049436fbb9"}, 63 | {file = "black-23.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1fa88a0f74e50e4487477bc0bb900c6781dbddfdfa32691e780bf854c3b4a47f"}, 64 | {file = "black-23.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4d6a9668e45ad99d2f8ec70d5c8c04ef4f32f648ef39048d010b0689832ec6d"}, 65 | {file = "black-23.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b18fb2ae6c4bb63eebe5be6bd869ba2f14fd0259bda7d18a46b764d8fb86298a"}, 66 | {file = "black-23.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:c04b6d9d20e9c13f43eee8ea87d44156b8505ca8a3c878773f68b4e4812a421e"}, 67 | {file = "black-23.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e1b38b3135fd4c025c28c55ddfc236b05af657828a8a6abe5deec419a0b7055"}, 68 | {file = "black-23.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4f0031eaa7b921db76decd73636ef3a12c942ed367d8c3841a0739412b260a54"}, 69 | {file = "black-23.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97e56155c6b737854e60a9ab1c598ff2533d57e7506d97af5481141671abf3ea"}, 70 | {file = "black-23.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:dd15245c8b68fe2b6bd0f32c1556509d11bb33aec9b5d0866dd8e2ed3dba09c2"}, 71 | {file = "black-23.12.1-py3-none-any.whl", hash = "sha256:78baad24af0f033958cad29731e27363183e140962595def56423e626f4bee3e"}, 72 | {file = "black-23.12.1.tar.gz", hash = "sha256:4ce3ef14ebe8d9509188014d96af1c456a910d5b5cbf434a09fef7e024b3d0d5"}, 73 | ] 74 | 75 | [package.dependencies] 76 | click = ">=8.0.0" 77 | mypy-extensions = ">=0.4.3" 78 | packaging = ">=22.0" 79 | pathspec = ">=0.9.0" 80 | platformdirs = ">=2" 81 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 82 | typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} 83 | 84 | [package.extras] 85 | colorama = ["colorama (>=0.4.3)"] 86 | d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] 87 | jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] 88 | uvloop = ["uvloop (>=0.15.2)"] 89 | 90 | [[package]] 91 | name = "click" 92 | version = "8.1.7" 93 | description = "Composable command line interface toolkit" 94 | optional = false 95 | python-versions = ">=3.7" 96 | files = [ 97 | {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, 98 | {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, 99 | ] 100 | 101 | [package.dependencies] 102 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 103 | 104 | [[package]] 105 | name = "colorama" 106 | version = "0.4.6" 107 | description = "Cross-platform colored terminal text." 108 | optional = false 109 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 110 | files = [ 111 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 112 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 113 | ] 114 | 115 | [[package]] 116 | name = "colorlog" 117 | version = "6.8.2" 118 | description = "Add colours to the output of Python's logging module." 119 | optional = false 120 | python-versions = ">=3.6" 121 | files = [ 122 | {file = "colorlog-6.8.2-py3-none-any.whl", hash = "sha256:4dcbb62368e2800cb3c5abd348da7e53f6c362dda502ec27c560b2e58a66bd33"}, 123 | {file = "colorlog-6.8.2.tar.gz", hash = "sha256:3e3e079a41feb5a1b64f978b5ea4f46040a94f11f0e8bbb8261e3dbbeca64d44"}, 124 | ] 125 | 126 | [package.dependencies] 127 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 128 | 129 | [package.extras] 130 | development = ["black", "flake8", "mypy", "pytest", "types-colorama"] 131 | 132 | [[package]] 133 | name = "distlib" 134 | version = "0.3.8" 135 | description = "Distribution utilities" 136 | optional = false 137 | python-versions = "*" 138 | files = [ 139 | {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, 140 | {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, 141 | ] 142 | 143 | [[package]] 144 | name = "exceptiongroup" 145 | version = "1.2.0" 146 | description = "Backport of PEP 654 (exception groups)" 147 | optional = false 148 | python-versions = ">=3.7" 149 | files = [ 150 | {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, 151 | {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, 152 | ] 153 | 154 | [package.extras] 155 | test = ["pytest (>=6)"] 156 | 157 | [[package]] 158 | name = "filelock" 159 | version = "3.13.4" 160 | description = "A platform independent file lock." 161 | optional = false 162 | python-versions = ">=3.8" 163 | files = [ 164 | {file = "filelock-3.13.4-py3-none-any.whl", hash = "sha256:404e5e9253aa60ad457cae1be07c0f0ca90a63931200a47d9b6a6af84fd7b45f"}, 165 | {file = "filelock-3.13.4.tar.gz", hash = "sha256:d13f466618bfde72bd2c18255e269f72542c6e70e7bac83a0232d6b1cc5c8cf4"}, 166 | ] 167 | 168 | [package.extras] 169 | docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] 170 | testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] 171 | typing = ["typing-extensions (>=4.8)"] 172 | 173 | [[package]] 174 | name = "humanfriendly" 175 | version = "10.0" 176 | description = "Human friendly output for text interfaces using Python" 177 | optional = false 178 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 179 | files = [ 180 | {file = "humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477"}, 181 | {file = "humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc"}, 182 | ] 183 | 184 | [package.dependencies] 185 | pyreadline3 = {version = "*", markers = "sys_platform == \"win32\" and python_version >= \"3.8\""} 186 | 187 | [[package]] 188 | name = "iniconfig" 189 | version = "2.0.0" 190 | description = "brain-dead simple config-ini parsing" 191 | optional = false 192 | python-versions = ">=3.7" 193 | files = [ 194 | {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, 195 | {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, 196 | ] 197 | 198 | [[package]] 199 | name = "mypy" 200 | version = "1.9.0" 201 | description = "Optional static typing for Python" 202 | optional = false 203 | python-versions = ">=3.8" 204 | files = [ 205 | {file = "mypy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8a67616990062232ee4c3952f41c779afac41405806042a8126fe96e098419f"}, 206 | {file = "mypy-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d357423fa57a489e8c47b7c85dfb96698caba13d66e086b412298a1a0ea3b0ed"}, 207 | {file = "mypy-1.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49c87c15aed320de9b438ae7b00c1ac91cd393c1b854c2ce538e2a72d55df150"}, 208 | {file = "mypy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:48533cdd345c3c2e5ef48ba3b0d3880b257b423e7995dada04248725c6f77374"}, 209 | {file = "mypy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:4d3dbd346cfec7cb98e6cbb6e0f3c23618af826316188d587d1c1bc34f0ede03"}, 210 | {file = "mypy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:653265f9a2784db65bfca694d1edd23093ce49740b2244cde583aeb134c008f3"}, 211 | {file = "mypy-1.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a3c007ff3ee90f69cf0a15cbcdf0995749569b86b6d2f327af01fd1b8aee9dc"}, 212 | {file = "mypy-1.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2418488264eb41f69cc64a69a745fad4a8f86649af4b1041a4c64ee61fc61129"}, 213 | {file = "mypy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:68edad3dc7d70f2f17ae4c6c1b9471a56138ca22722487eebacfd1eb5321d612"}, 214 | {file = "mypy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:85ca5fcc24f0b4aeedc1d02f93707bccc04733f21d41c88334c5482219b1ccb3"}, 215 | {file = "mypy-1.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aceb1db093b04db5cd390821464504111b8ec3e351eb85afd1433490163d60cd"}, 216 | {file = "mypy-1.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0235391f1c6f6ce487b23b9dbd1327b4ec33bb93934aa986efe8a9563d9349e6"}, 217 | {file = "mypy-1.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4d5ddc13421ba3e2e082a6c2d74c2ddb3979c39b582dacd53dd5d9431237185"}, 218 | {file = "mypy-1.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:190da1ee69b427d7efa8aa0d5e5ccd67a4fb04038c380237a0d96829cb157913"}, 219 | {file = "mypy-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:fe28657de3bfec596bbeef01cb219833ad9d38dd5393fc649f4b366840baefe6"}, 220 | {file = "mypy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e54396d70be04b34f31d2edf3362c1edd023246c82f1730bbf8768c28db5361b"}, 221 | {file = "mypy-1.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5e6061f44f2313b94f920e91b204ec600982961e07a17e0f6cd83371cb23f5c2"}, 222 | {file = "mypy-1.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a10926e5473c5fc3da8abb04119a1f5811a236dc3a38d92015cb1e6ba4cb9e"}, 223 | {file = "mypy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b685154e22e4e9199fc95f298661deea28aaede5ae16ccc8cbb1045e716b3e04"}, 224 | {file = "mypy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:5d741d3fc7c4da608764073089e5f58ef6352bedc223ff58f2f038c2c4698a89"}, 225 | {file = "mypy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:587ce887f75dd9700252a3abbc9c97bbe165a4a630597845c61279cf32dfbf02"}, 226 | {file = "mypy-1.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f88566144752999351725ac623471661c9d1cd8caa0134ff98cceeea181789f4"}, 227 | {file = "mypy-1.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61758fabd58ce4b0720ae1e2fea5cfd4431591d6d590b197775329264f86311d"}, 228 | {file = "mypy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e49499be624dead83927e70c756970a0bc8240e9f769389cdf5714b0784ca6bf"}, 229 | {file = "mypy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:571741dc4194b4f82d344b15e8837e8c5fcc462d66d076748142327626a1b6e9"}, 230 | {file = "mypy-1.9.0-py3-none-any.whl", hash = "sha256:a260627a570559181a9ea5de61ac6297aa5af202f06fd7ab093ce74e7181e43e"}, 231 | {file = "mypy-1.9.0.tar.gz", hash = "sha256:3cc5da0127e6a478cddd906068496a97a7618a21ce9b54bde5bf7e539c7af974"}, 232 | ] 233 | 234 | [package.dependencies] 235 | mypy-extensions = ">=1.0.0" 236 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 237 | typing-extensions = ">=4.1.0" 238 | 239 | [package.extras] 240 | dmypy = ["psutil (>=4.0)"] 241 | install-types = ["pip"] 242 | mypyc = ["setuptools (>=50)"] 243 | reports = ["lxml"] 244 | 245 | [[package]] 246 | name = "mypy-extensions" 247 | version = "1.0.0" 248 | description = "Type system extensions for programs checked with the mypy type checker." 249 | optional = false 250 | python-versions = ">=3.5" 251 | files = [ 252 | {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, 253 | {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, 254 | ] 255 | 256 | [[package]] 257 | name = "nox" 258 | version = "2023.4.22" 259 | description = "Flexible test automation." 260 | optional = false 261 | python-versions = ">=3.7" 262 | files = [ 263 | {file = "nox-2023.4.22-py3-none-any.whl", hash = "sha256:0b1adc619c58ab4fa57d6ab2e7823fe47a32e70202f287d78474adcc7bda1891"}, 264 | {file = "nox-2023.4.22.tar.gz", hash = "sha256:46c0560b0dc609d7d967dc99e22cb463d3c4caf54a5fda735d6c11b5177e3a9f"}, 265 | ] 266 | 267 | [package.dependencies] 268 | argcomplete = ">=1.9.4,<4.0" 269 | colorlog = ">=2.6.1,<7.0.0" 270 | packaging = ">=20.9" 271 | virtualenv = ">=14" 272 | 273 | [package.extras] 274 | tox-to-nox = ["jinja2", "tox (<4)"] 275 | 276 | [[package]] 277 | name = "nox-poetry" 278 | version = "1.0.3" 279 | description = "nox-poetry" 280 | optional = false 281 | python-versions = ">=3.7,<4.0" 282 | files = [ 283 | {file = "nox_poetry-1.0.3-py3-none-any.whl", hash = "sha256:a2fffeb70ae81840479e68287afe1c772bf376f70f1e92f99832a20b3c64d064"}, 284 | {file = "nox_poetry-1.0.3.tar.gz", hash = "sha256:dc7ecbbd812a333a0c0b558f57e5b37f7c12926cddbcecaf2264957fd373824e"}, 285 | ] 286 | 287 | [package.dependencies] 288 | nox = ">=2020.8.22" 289 | packaging = ">=20.9" 290 | tomlkit = ">=0.7" 291 | 292 | [[package]] 293 | name = "packaging" 294 | version = "24.0" 295 | description = "Core utilities for Python packages" 296 | optional = false 297 | python-versions = ">=3.7" 298 | files = [ 299 | {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, 300 | {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, 301 | ] 302 | 303 | [[package]] 304 | name = "pathspec" 305 | version = "0.12.1" 306 | description = "Utility library for gitignore style pattern matching of file paths." 307 | optional = false 308 | python-versions = ">=3.8" 309 | files = [ 310 | {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, 311 | {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, 312 | ] 313 | 314 | [[package]] 315 | name = "platformdirs" 316 | version = "4.2.0" 317 | description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 318 | optional = false 319 | python-versions = ">=3.8" 320 | files = [ 321 | {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, 322 | {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, 323 | ] 324 | 325 | [package.extras] 326 | docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] 327 | test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] 328 | 329 | [[package]] 330 | name = "pluggy" 331 | version = "1.4.0" 332 | description = "plugin and hook calling mechanisms for python" 333 | optional = false 334 | python-versions = ">=3.8" 335 | files = [ 336 | {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, 337 | {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, 338 | ] 339 | 340 | [package.extras] 341 | dev = ["pre-commit", "tox"] 342 | testing = ["pytest", "pytest-benchmark"] 343 | 344 | [[package]] 345 | name = "pyelftools" 346 | version = "0.31" 347 | description = "Library for analyzing ELF files and DWARF debugging information" 348 | optional = false 349 | python-versions = "*" 350 | files = [ 351 | {file = "pyelftools-0.31-py3-none-any.whl", hash = "sha256:f52de7b3c7e8c64c8abc04a79a1cf37ac5fb0b8a49809827130b858944840607"}, 352 | {file = "pyelftools-0.31.tar.gz", hash = "sha256:c774416b10310156879443b81187d182d8d9ee499660380e645918b50bc88f99"}, 353 | ] 354 | 355 | [[package]] 356 | name = "pyreadline3" 357 | version = "3.4.1" 358 | description = "A python implementation of GNU readline." 359 | optional = false 360 | python-versions = "*" 361 | files = [ 362 | {file = "pyreadline3-3.4.1-py3-none-any.whl", hash = "sha256:b0efb6516fd4fb07b45949053826a62fa4cb353db5be2bbb4a7aa1fdd1e345fb"}, 363 | {file = "pyreadline3-3.4.1.tar.gz", hash = "sha256:6f3d1f7b8a31ba32b73917cefc1f28cc660562f39aea8646d30bd6eff21f7bae"}, 364 | ] 365 | 366 | [[package]] 367 | name = "pytest" 368 | version = "7.4.4" 369 | description = "pytest: simple powerful testing with Python" 370 | optional = false 371 | python-versions = ">=3.7" 372 | files = [ 373 | {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, 374 | {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, 375 | ] 376 | 377 | [package.dependencies] 378 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 379 | exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} 380 | iniconfig = "*" 381 | packaging = "*" 382 | pluggy = ">=0.12,<2.0" 383 | tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} 384 | 385 | [package.extras] 386 | testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] 387 | 388 | [[package]] 389 | name = "ruff" 390 | version = "0.0.269" 391 | description = "An extremely fast Python linter, written in Rust." 392 | optional = false 393 | python-versions = ">=3.7" 394 | files = [ 395 | {file = "ruff-0.0.269-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:3569bcdee679045c09c0161fabc057599759c49219a08d9a4aad2cc3982ccba3"}, 396 | {file = "ruff-0.0.269-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:56347da63757a56cbce7d4b3d6044ca4f1941cd1bbff3714f7554360c3361f83"}, 397 | {file = "ruff-0.0.269-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6da8ee25ef2f0cc6cc8e6e20942c1d44d25a36dce35070d7184655bc14f63f63"}, 398 | {file = "ruff-0.0.269-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd81b8e681b9eaa6cf15484f3985bd8bd97c3d114e95bff3e8ea283bf8865062"}, 399 | {file = "ruff-0.0.269-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f19f59ca3c28742955241fb452f3346241ddbd34e72ac5cb3d84fadebcf6bc8"}, 400 | {file = "ruff-0.0.269-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f062059b8289a4fab7f6064601b811d447c2f9d3d432a17f689efe4d68988450"}, 401 | {file = "ruff-0.0.269-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f5dc7aac52c58e82510217e3c7efd80765c134c097c2815d59e40face0d1fe6"}, 402 | {file = "ruff-0.0.269-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e131b4dbe798c391090c6407641d6ab12c0fa1bb952379dde45e5000e208dabb"}, 403 | {file = "ruff-0.0.269-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a374434e588e06550df0f8dcb74777290f285678de991fda4e1063c367ab2eb2"}, 404 | {file = "ruff-0.0.269-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:cec2f4b84a14b87f1b121488649eb5b4eaa06467a2387373f750da74bdcb5679"}, 405 | {file = "ruff-0.0.269-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:374b161753a247904aec7a32d45e165302b76b6e83d22d099bf3ff7c232c888f"}, 406 | {file = "ruff-0.0.269-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9ca0a1ddb1d835b5f742db9711c6cf59f213a1ad0088cb1e924a005fd399e7d8"}, 407 | {file = "ruff-0.0.269-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5a20658f0b97d207c7841c13d528f36d666bf445b00b01139f28a8ccb80093bb"}, 408 | {file = "ruff-0.0.269-py3-none-win32.whl", hash = "sha256:03ff42bc91ceca58e0f0f072cb3f9286a9208f609812753474e799a997cdad1a"}, 409 | {file = "ruff-0.0.269-py3-none-win_amd64.whl", hash = "sha256:f3b59ccff57b21ef0967ea8021fd187ec14c528ec65507d8bcbe035912050776"}, 410 | {file = "ruff-0.0.269-py3-none-win_arm64.whl", hash = "sha256:bbeb857b1e508a4487bdb02ca1e6d41dd8d5ac5335a5246e25de8a3dff38c1ff"}, 411 | {file = "ruff-0.0.269.tar.gz", hash = "sha256:11ddcfbab32cf5c420ea9dd5531170ace5a3e59c16d9251c7bd2581f7b16f602"}, 412 | ] 413 | 414 | [[package]] 415 | name = "tomli" 416 | version = "2.0.1" 417 | description = "A lil' TOML parser" 418 | optional = false 419 | python-versions = ">=3.7" 420 | files = [ 421 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 422 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 423 | ] 424 | 425 | [[package]] 426 | name = "tomlkit" 427 | version = "0.12.4" 428 | description = "Style preserving TOML library" 429 | optional = false 430 | python-versions = ">=3.7" 431 | files = [ 432 | {file = "tomlkit-0.12.4-py3-none-any.whl", hash = "sha256:5cd82d48a3dd89dee1f9d64420aa20ae65cfbd00668d6f094d7578a78efbb77b"}, 433 | {file = "tomlkit-0.12.4.tar.gz", hash = "sha256:7ca1cfc12232806517a8515047ba66a19369e71edf2439d0f5824f91032b6cc3"}, 434 | ] 435 | 436 | [[package]] 437 | name = "typing-extensions" 438 | version = "4.11.0" 439 | description = "Backported and Experimental Type Hints for Python 3.8+" 440 | optional = false 441 | python-versions = ">=3.8" 442 | files = [ 443 | {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, 444 | {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, 445 | ] 446 | 447 | [[package]] 448 | name = "virtualenv" 449 | version = "20.25.1" 450 | description = "Virtual Python Environment builder" 451 | optional = false 452 | python-versions = ">=3.7" 453 | files = [ 454 | {file = "virtualenv-20.25.1-py3-none-any.whl", hash = "sha256:961c026ac520bac5f69acb8ea063e8a4f071bcc9457b9c1f28f6b085c511583a"}, 455 | {file = "virtualenv-20.25.1.tar.gz", hash = "sha256:e08e13ecdca7a0bd53798f356d5831434afa5b07b93f0abdf0797b7a06ffe197"}, 456 | ] 457 | 458 | [package.dependencies] 459 | distlib = ">=0.3.7,<1" 460 | filelock = ">=3.12.2,<4" 461 | platformdirs = ">=3.9.1,<5" 462 | 463 | [package.extras] 464 | docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] 465 | test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] 466 | 467 | [metadata] 468 | lock-version = "2.0" 469 | python-versions = "^3.10" 470 | content-hash = "8f78267b852ead2825b65d6ced0c43b5a6a4c194642e012472713d2ca7f88c48" 471 | --------------------------------------------------------------------------------