From ddf9b16a29ee61e8f0f1d4430ec96c7a6acb15c0 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 24 Sep 2024 21:25:05 +0200 Subject: [PATCH 1/4] Modernize Project Setup --- .coveralls.yml | 1 - .gitignore | 53 +- .pre-commit-config.yaml | 12 + .pre-commit-hooks.yaml | 13 + .travis.yml | 36 - MANIFEST.in | 8 - README.creole | 60 -- README.md | 26 + cli.py | 119 +++ dev-cli.py | 119 +++ dragonlib/__init__.py | 9 +- dragonlib/__main__.py | 11 + dragonlib/cli_app/__init__.py | 62 ++ dragonlib/cli_app/update_readme_history.py | 26 + dragonlib/cli_dev/__init__.py | 83 ++ dragonlib/cli_dev/code_style.py | 25 + dragonlib/cli_dev/packaging.py | 81 ++ dragonlib/cli_dev/testing.py | 55 + dragonlib/constants.py | 8 + dragonlib/tests/__init__.py | 37 + dragonlib/tests/test_doctests.py | 10 + dragonlib/tests/test_project_setup.py | 40 + pyproject.toml | 180 ++++ requirements.dev.txt | 1116 ++++++++++++++++++++ requirements.txt | 86 ++ setup.cfg | 16 - setup.py | 241 ----- 27 files changed, 2132 insertions(+), 401 deletions(-) delete mode 100644 .coveralls.yml create mode 100644 .pre-commit-config.yaml create mode 100644 .pre-commit-hooks.yaml delete mode 100644 .travis.yml delete mode 100644 MANIFEST.in delete mode 100644 README.creole create mode 100644 README.md create mode 100755 cli.py create mode 100755 dev-cli.py create mode 100644 dragonlib/__main__.py create mode 100644 dragonlib/cli_app/__init__.py create mode 100644 dragonlib/cli_app/update_readme_history.py create mode 100644 dragonlib/cli_dev/__init__.py create mode 100644 dragonlib/cli_dev/code_style.py create mode 100644 dragonlib/cli_dev/packaging.py create mode 100644 dragonlib/cli_dev/testing.py create mode 100644 dragonlib/constants.py create mode 100644 dragonlib/tests/test_doctests.py create mode 100644 dragonlib/tests/test_project_setup.py create mode 100644 pyproject.toml create mode 100644 requirements.dev.txt create mode 100644 requirements.txt delete mode 100644 setup.cfg delete mode 100755 setup.py diff --git a/.coveralls.yml b/.coveralls.yml deleted file mode 100644 index 611ae6f..0000000 --- a/.coveralls.yml +++ /dev/null @@ -1 +0,0 @@ -repo_token: jtynFT5loh47f2EPQlcjtUoAq9tiRTSqk \ No newline at end of file diff --git a/.gitignore b/.gitignore index 8ea3868..ec72297 100644 --- a/.gitignore +++ b/.gitignore @@ -1,38 +1,15 @@ -*.py[cod] -*.rom -*~ - -# C extensions -*.so - -# Packages -*.egg -*.egg-info/* -/dist/* -build -eggs -parts -bin -var -sdist -develop-eggs -.installed.cfg -lib -lib64 - -# Installer logs -pip-log.txt - -# Unit test / coverage reports -.coverage -/htmlcov/* -.tox -nosetests.xml - -# Translations -*.mo - -.project -.pydevproject -.idea/* -.settings/* \ No newline at end of file +.* +*.egg-info +__pycache__ +/dist/ +/build/ +/coverage.* +*.orig + +!.github +!.editorconfig +!.flake8 +!.gitignore +!.pre-commit-config.yaml +!.pre-commit-hooks.yaml +!.gitkeep diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..61dd4fd --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,12 @@ +# pre-commit plugin configuration +# See https://pre-commit.com for more information +default_install_hook_types: + - pre-commit + - post-rewrite + - pre-push + +repos: + - repo: https://github.com/jedie/cli-base-utilities + rev: v0.11.0 + hooks: + - id: update-readme-history diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml new file mode 100644 index 0000000..d6f5771 --- /dev/null +++ b/.pre-commit-hooks.yaml @@ -0,0 +1,13 @@ +# https://pre-commit.com/#creating-new-hooks +- id: update-readme-history + name: cli-base-utilities + description: >- + Update history in README.md from git log. + entry: "python -m cli_base update-readme-history -v" + language: python + language_version: python3 + require_serial: true + pass_filenames: false + always_run: true + verbose: true + stages: [pre-commit, post-rewrite, pre-push] diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 0edf1fd..0000000 --- a/.travis.yml +++ /dev/null @@ -1,36 +0,0 @@ -# Config file for automatic testing at travis-ci.org - -language: python -sudo: false - -python: - - "2.7" - - "3.4" - - "pypy" - - "pypy3" - -install: - - pip install --upgrade pip - - pip install . - - pip install coveralls - -script: - - coverage run --source=dragonlib ./setup.py nosetests - -after_success: - - coveralls - -# blacklist -branches: - except: - - wip - - experimental - -# whitelist -#branches: -# only: -# - master -# - stable - -notifications: - irc: "irc.freenode.org#pylucid" diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index fdbbb22..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,8 +0,0 @@ -include AUTHORS LICENSE MANIFEST.in README.creole -include *.sh *.py -recursive-exclude * *.py[co] -recursive-include * *.creole -recursive-include * *.bas -recursive-include * *.sha1 -recursive-include * *.cmd -recursive-include * *.sh \ No newline at end of file diff --git a/README.creole b/README.creole deleted file mode 100644 index e21eb06..0000000 --- a/README.creole +++ /dev/null @@ -1,60 +0,0 @@ -== Dragon/CoCO Python Library - -Python Modules/Tools Open source (GPL v3 or later) for 6809 based homecomputer like: - -* {{{Dragon 32}}} : http://en.wikipedia.org/wiki/Dragon_32 -* {{{Tandy TRS-80 Color Computer}}} (CoCo) : http://en.wikipedia.org/wiki/TRS-80_Color_Computer - -Used in: - -{{{DragonPy}}} - Emulator for 6809 CPU based system like Dragon 32 / CoCo written in Python: -* https://github.com/jedie/DragonPy - -{{{DwLoadServer}}} - DWLOAD server implemented in Python -* https://github.com/DWLOAD/DwLoadServer - - -Tested with Python 3.4, 2.7 and PyPy 2 - -| {{https://travis-ci.org/6809/dragonlib.svg|Build Status on travis-ci.org}} | [[https://travis-ci.org/6809/dragonlib/|travis-ci.org/6809/dragonlib]] | -| {{https://coveralls.io/repos/6809/dragonlib/badge.svg|Coverage Status on coveralls.io}} | [[https://coveralls.io/r/6809/dragonlib|coveralls.io/r/6809/dragonlib]] | - - -=== run unittests - -{{{ -/home/FooBar$ cd DragonPy_env -/home/FooBar/DragonPy_env$ source bin/activate -(DragonPy_env)~/DragonPy_env$ cd src/dragonlib -(DragonPy_env)~/DragonPy_env/src/dragonlib$ ./setup.py nosetests -}}} - - -== History - -* [[https://github.com/6809/dragonlib/compare/v0.1.6...v0.1.7|21.08.2015 - v0.1.7]]: -** Bugfixes: Disable logging, again ;) -** remove own six and add it to setup.py "install_requires" -** cleanup code -* [[https://github.com/6809/dragonlib/compare/v0.1.5...v0.1.6|19.08.2015 - v0.1.6]]: -** Bugfixes: Disable logging -** use nose to run unittests -* 26.05.2015 - v0.1.5 - [[https://github.com/6809/dragonlib/compare/v0.1.4...v0.1.5|Bugfixes to support Py2, too.]] -* 26.05.2015 - v0.1.4 - [[https://github.com/6809/dragonlib/compare/v0.1.3...v0.1.4|Update test code, use travis-ci.org and coveralls.io]] -* 15.12.2014 - v0.1.3 - [[https://github.com/6809/dragonlib/compare/v0.1.2...v0.1.3|Add a Pygments Lexer]] -* 19.11.2014 - v0.1.2 - [[https://github.com/6809/dragonlib/compare/v0.1.1...v0.1.2|Bugfix "api.bin2bas()" and "api.bas2bin()"]] -* 15.11.2014 - v0.1.1 - Add {{{api.bin2bas()}}} and {{{api.bas2bin()}}} for Dragon DOS Binary <-> ASCII listing -* 25.08.2014 - Split from DragonPy project -* TODO: Add old history ;) - -== Links: - -| Forum | [[http://forum.pylucid.org/]] -| IRC | [[http://www.pylucid.org/permalink/304/irc-channel|#pylucid on freenode.net]] -| Jabber | pylucid@conference.jabber.org -| PyPi | [[https://pypi.python.org/pypi/DragonLib/]] -| Github | [[https://github.com/6809/dragonlib]] - -== donation - -* Send [[http://www.bitcoin.org/|Bitcoins]] to [[https://blockexplorer.com/address/1823RZ5Md1Q2X5aSXRC5LRPcYdveCiVX6F|1823RZ5Md1Q2X5aSXRC5LRPcYdveCiVX6F]] diff --git a/README.md b/README.md new file mode 100644 index 0000000..4af53ca --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# Dragon/CoCO Python Library + +[![tests](https://github.com/6809/dragonlib/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/6809/dragonlib/actions/workflows/tests.yml) +[![codecov](https://codecov.io/github/jedie/dragonlib/branch/main/graph/badge.svg)](https://app.codecov.io/github/jedie/dragonlib) +[![dragonlib @ PyPi](https://img.shields.io/pypi/v/dragonlib?label=dragonlib%20%40%20PyPi)](https://pypi.org/project/dragonlib/) +[![Python Versions](https://img.shields.io/pypi/pyversions/dragonlib)](https://github.com/6809/dragonlib/blob/main/pyproject.toml) +[![License GPL-3.0-or-later](https://img.shields.io/pypi/l/dragonlib)](https://github.com/6809/dragonlib/blob/main/LICENSE) + +Python Modules/Tools Open source (GPL v3 or later) for 6809 based homecomputer like: + +* [Dragon 32](http://en.wikipedia.org/wiki/Dragon_32) +* [Tandy TRS-80 Color Computer](http://en.wikipedia.org/wiki/TRS-80_Color_Computer) (CoCo) + +Used in: + +* [DragonPy](https://github.com/jedie/DragonPy) - Emulator for 6809 CPU based system like Dragon 32 / CoCo written in Python: +* [DwLoadServer](https://github.com/DWLOAD/DwLoadServer) - DWLOAD server implemented in Python + + +## Start hacking + +```shell +~$ git clone https://github.com/6809/dragonlib.git +~$ cd dragonlib +~/dragonlib$ ./dev-cli.py --help +``` diff --git a/cli.py b/cli.py new file mode 100755 index 0000000..ffd0018 --- /dev/null +++ b/cli.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 + +""" + bootstrap CLI + ~~~~~~~~~~~~~ + + Just call this file, and the magic happens ;) +""" + +import hashlib +import shlex +import subprocess +import sys +import venv +from pathlib import Path + + +def print_no_pip_error(): + print('Error: Pip not available!') + print('Hint: "apt-get install python3-venv"\n') + + +try: + from ensurepip import version +except ModuleNotFoundError as err: + print(err) + print('-' * 100) + print_no_pip_error() + raise +else: + if not version(): + print_no_pip_error() + sys.exit(-1) + + +assert sys.version_info >= (3, 9), f'Python version {sys.version_info} is too old!' + + +if sys.platform == 'win32': # wtf + # Files under Windows, e.g.: .../.venv/Scripts/python.exe + BIN_NAME = 'Scripts' + FILE_EXT = '.exe' +else: + # Files under Linux/Mac and all other than Windows, e.g.: .../.venv/bin/python3 + BIN_NAME = 'bin' + FILE_EXT = '' + +BASE_PATH = Path(__file__).parent +VENV_PATH = BASE_PATH / '.venv-app' +BIN_PATH = VENV_PATH / BIN_NAME +PYTHON_PATH = BIN_PATH / f'python3{FILE_EXT}' +PIP_PATH = BIN_PATH / f'pip{FILE_EXT}' +PIP_SYNC_PATH = BIN_PATH / f'pip-sync{FILE_EXT}' + +DEP_LOCK_PATH = BASE_PATH / 'requirements.txt' +DEP_HASH_PATH = VENV_PATH / '.dep_hash' + +# script file defined in pyproject.toml as [console_scripts] +# (Under Windows: ".exe" not added!) +PROJECT_SHELL_SCRIPT = BIN_PATH / 'dragonlib_app' + + +def get_dep_hash(): + """Get SHA512 hash from lock file content.""" + return hashlib.sha512(DEP_LOCK_PATH.read_bytes()).hexdigest() + + +def store_dep_hash(): + """Generate .venv/.dep_hash""" + DEP_HASH_PATH.write_text(get_dep_hash()) + + +def venv_up2date(): + """Is existing .venv is up-to-date?""" + if DEP_HASH_PATH.is_file(): + return DEP_HASH_PATH.read_text() == get_dep_hash() + return False + + +def verbose_check_call(*popen_args): + print(f'\n+ {shlex.join(str(arg) for arg in popen_args)}\n') + return subprocess.check_call(popen_args) + + +def main(argv): + assert DEP_LOCK_PATH.is_file(), f'File not found: "{DEP_LOCK_PATH}" !' + + # Create virtual env in ".venv/": + if not PYTHON_PATH.is_file(): + print(f'Create virtual env here: {VENV_PATH.absolute()}') + builder = venv.EnvBuilder(symlinks=True, upgrade=True, with_pip=True) + builder.create(env_dir=VENV_PATH) + + if not PROJECT_SHELL_SCRIPT.is_file() or not venv_up2date(): + # Update pip + verbose_check_call(PYTHON_PATH, '-m', 'pip', 'install', '-U', 'pip') + + # Install pip-tools + verbose_check_call(PYTHON_PATH, '-m', 'pip', 'install', '-U', 'pip-tools') + + # install requirements via "pip-sync" + verbose_check_call(PIP_SYNC_PATH, str(DEP_LOCK_PATH)) + + # install project + verbose_check_call(PIP_PATH, 'install', '--no-deps', '-e', '.') + store_dep_hash() + + # Call our entry point CLI: + try: + verbose_check_call(PROJECT_SHELL_SCRIPT, *argv[1:]) + except subprocess.CalledProcessError as err: + sys.exit(err.returncode) + except KeyboardInterrupt: + print('Bye!') + sys.exit(130) + + +if __name__ == '__main__': + main(sys.argv) diff --git a/dev-cli.py b/dev-cli.py new file mode 100755 index 0000000..4d0d200 --- /dev/null +++ b/dev-cli.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 + +""" + bootstrap CLI + ~~~~~~~~~~~~~ + + Just call this file, and the magic happens ;) +""" + +import hashlib +import shlex +import subprocess +import sys +import venv +from pathlib import Path + + +def print_no_pip_error(): + print('Error: Pip not available!') + print('Hint: "apt-get install python3-venv"\n') + + +try: + from ensurepip import version +except ModuleNotFoundError as err: + print(err) + print('-' * 100) + print_no_pip_error() + raise +else: + if not version(): + print_no_pip_error() + sys.exit(-1) + + +assert sys.version_info >= (3, 9), f'Python version {sys.version_info} is too old!' + + +if sys.platform == 'win32': # wtf + # Files under Windows, e.g.: .../.venv/Scripts/python.exe + BIN_NAME = 'Scripts' + FILE_EXT = '.exe' +else: + # Files under Linux/Mac and all other than Windows, e.g.: .../.venv/bin/python3 + BIN_NAME = 'bin' + FILE_EXT = '' + +BASE_PATH = Path(__file__).parent +VENV_PATH = BASE_PATH / '.venv' +BIN_PATH = VENV_PATH / BIN_NAME +PYTHON_PATH = BIN_PATH / f'python3{FILE_EXT}' +PIP_PATH = BIN_PATH / f'pip{FILE_EXT}' +PIP_SYNC_PATH = BIN_PATH / f'pip-sync{FILE_EXT}' + +DEP_LOCK_PATH = BASE_PATH / 'requirements.dev.txt' +DEP_HASH_PATH = VENV_PATH / '.dep_hash' + +# script file defined in pyproject.toml as [console_scripts] +# (Under Windows: ".exe" not added!) +PROJECT_SHELL_SCRIPT = BIN_PATH / 'dragonlib_dev' + + +def get_dep_hash(): + """Get SHA512 hash from lock file content.""" + return hashlib.sha512(DEP_LOCK_PATH.read_bytes()).hexdigest() + + +def store_dep_hash(): + """Generate .venv/.dep_hash""" + DEP_HASH_PATH.write_text(get_dep_hash()) + + +def venv_up2date(): + """Is existing .venv is up-to-date?""" + if DEP_HASH_PATH.is_file(): + return DEP_HASH_PATH.read_text() == get_dep_hash() + return False + + +def verbose_check_call(*popen_args): + print(f'\n+ {shlex.join(str(arg) for arg in popen_args)}\n') + return subprocess.check_call(popen_args) + + +def main(argv): + assert DEP_LOCK_PATH.is_file(), f'File not found: "{DEP_LOCK_PATH}" !' + + # Create virtual env in ".venv/": + if not PYTHON_PATH.is_file(): + print(f'Create virtual env here: {VENV_PATH.absolute()}') + builder = venv.EnvBuilder(symlinks=True, upgrade=True, with_pip=True) + builder.create(env_dir=VENV_PATH) + + if not PROJECT_SHELL_SCRIPT.is_file() or not venv_up2date(): + # Update pip + verbose_check_call(PYTHON_PATH, '-m', 'pip', 'install', '-U', 'pip') + + # Install pip-tools + verbose_check_call(PYTHON_PATH, '-m', 'pip', 'install', '-U', 'pip-tools') + + # install requirements via "pip-sync" + verbose_check_call(PIP_SYNC_PATH, str(DEP_LOCK_PATH)) + + # install project + verbose_check_call(PIP_PATH, 'install', '--no-deps', '-e', '.') + store_dep_hash() + + # Call our entry point CLI: + try: + verbose_check_call(PROJECT_SHELL_SCRIPT, *argv[1:]) + except subprocess.CalledProcessError as err: + sys.exit(err.returncode) + except KeyboardInterrupt: + print('Bye!') + sys.exit(130) + + +if __name__ == '__main__': + main(sys.argv) diff --git a/dragonlib/__init__.py b/dragonlib/__init__.py index 283b03a..6b83c64 100644 --- a/dragonlib/__init__.py +++ b/dragonlib/__init__.py @@ -1 +1,8 @@ -__version__ = "0.1.7" \ No newline at end of file +""" + dragonlib + Library around 6809 computers like Dragon 32/64, CoCo... +""" + +# See https://packaging.python.org/en/latest/specifications/version-specifiers/ +__version__ = '0.2.0' +__author__ = 'Jens Diemer ' diff --git a/dragonlib/__main__.py b/dragonlib/__main__.py new file mode 100644 index 0000000..0f1ce28 --- /dev/null +++ b/dragonlib/__main__.py @@ -0,0 +1,11 @@ +""" + Allow dragonlib to be executable + through `python -m dragonlib`. +""" + + +from dragonlib.cli_app import main + + +if __name__ == '__main__': + main() diff --git a/dragonlib/cli_app/__init__.py b/dragonlib/cli_app/__init__.py new file mode 100644 index 0000000..1b6d5f4 --- /dev/null +++ b/dragonlib/cli_app/__init__.py @@ -0,0 +1,62 @@ +""" + CLI for usage +""" + +import logging +import sys + +import rich_click as click +from rich import print # noqa +from rich.console import Console +from rich.traceback import install as rich_traceback_install +from rich_click import RichGroup + +from cli_base.autodiscover import import_all_files +from cli_base.cli_tools.version_info import print_version + +import dragonlib +from dragonlib import constants + + +logger = logging.getLogger(__name__) + + +class ClickGroup(RichGroup): # FIXME: How to set the "info_name" easier? + def make_context(self, info_name, *args, **kwargs): + info_name = './cli.py' + return super().make_context(info_name, *args, **kwargs) + + +@click.group( + cls=ClickGroup, + epilog=constants.CLI_EPILOG, +) +def cli(): + pass + + +# Register all click commands, just by import all files in this package: +import_all_files(package=__package__, init_file=__file__) + + +@cli.command() +def version(): + """Print version and exit""" + # Pseudo command, because the version always printed on every CLI call ;) + sys.exit(0) + + +def main(): + print_version(dragonlib) + + console = Console() + rich_traceback_install( + width=console.size.width, # full terminal width + show_locals=True, + suppress=[click], + max_frames=2, + ) + + # Execute Click CLI: + cli.name = './cli.py' + cli() diff --git a/dragonlib/cli_app/update_readme_history.py b/dragonlib/cli_app/update_readme_history.py new file mode 100644 index 0000000..f2d040f --- /dev/null +++ b/dragonlib/cli_app/update_readme_history.py @@ -0,0 +1,26 @@ +import sys + +import rich_click as click +from cli_base.cli_tools import git_history +from cli_base.cli_tools.verbosity import OPTION_KWARGS_VERBOSE, setup_logging +from rich import print # noqa +from dragonlib.cli_app import cli + + +@cli.command() +@click.option('-v', '--verbosity', **OPTION_KWARGS_VERBOSE) +def update_readme_history(verbosity: int): + """ + Update project history base on git commits/tags in README.md + + Will be exited with 1 if the README.md was updated otherwise with 0. + + Also, callable via e.g.: + python -m cli_base update-readme-history -v + """ + setup_logging(verbosity=verbosity) + updated = git_history.update_readme_history(verbosity=verbosity) + exit_code = 1 if updated else 0 + if verbosity: + print(f'{exit_code=}') + sys.exit(exit_code) diff --git a/dragonlib/cli_dev/__init__.py b/dragonlib/cli_dev/__init__.py new file mode 100644 index 0000000..605ead0 --- /dev/null +++ b/dragonlib/cli_dev/__init__.py @@ -0,0 +1,83 @@ +""" + CLI for development +""" + +import logging +import sys + +import rich_click as click +from bx_py_utils.path import assert_is_file +from rich.console import Console +from rich.traceback import install as rich_traceback_install +from rich_click import RichGroup +from typeguard import install_import_hook + +from cli_base.autodiscover import import_all_files +from cli_base.cli_tools.dev_tools import run_coverage, run_tox, run_unittest_cli +from cli_base.cli_tools.version_info import print_version + +import dragonlib +from dragonlib import constants + + +# Check type annotations via typeguard in all tests. +# Sadly we must activate this here and can't do this in ./tests/__init__.py +install_import_hook(packages=('dragonlib',)) + + +logger = logging.getLogger(__name__) + + +PACKAGE_ROOT = constants.BASE_PATH.parent +assert_is_file(PACKAGE_ROOT / 'pyproject.toml') # Exists only in cloned git repo + + +class ClickGroup(RichGroup): # FIXME: How to set the "info_name" easier? + def make_context(self, info_name, *args, **kwargs): + info_name = './dev-cli.py' + return super().make_context(info_name, *args, **kwargs) + + +@click.group( + cls=ClickGroup, + epilog=constants.CLI_EPILOG, +) +def cli(): + pass + + +# Register all click commands, just by import all files in this package: +import_all_files(package=__package__, init_file=__file__) + + +@cli.command() +def version(): + """Print version and exit""" + # Pseudo command, because the version always printed on every CLI call ;) + sys.exit(0) + + +def main(): + print_version(dragonlib) + + console = Console() + rich_traceback_install( + width=console.size.width, # full terminal width + show_locals=True, + suppress=[click], + max_frames=2, + ) + + if len(sys.argv) >= 2: + # Check if we can just pass a command call to origin CLI: + command = sys.argv[1] + command_map = { + 'test': run_unittest_cli, + 'tox': run_tox, + 'coverage': run_coverage, + } + if real_func := command_map.get(command): + real_func(argv=sys.argv, exit_after_run=True) + + # Execute Click CLI: + cli() diff --git a/dragonlib/cli_dev/code_style.py b/dragonlib/cli_dev/code_style.py new file mode 100644 index 0000000..a3e9545 --- /dev/null +++ b/dragonlib/cli_dev/code_style.py @@ -0,0 +1,25 @@ +import rich_click as click +from cli_base.cli_tools import code_style +from cli_base.cli_tools.verbosity import OPTION_KWARGS_VERBOSE +from cli_base.click_defaults import OPTION_ARGS_DEFAULT_TRUE +from dragonlib.cli_dev import PACKAGE_ROOT, cli + + +@cli.command() +@click.option('--color/--no-color', **OPTION_ARGS_DEFAULT_TRUE) +@click.option('-v', '--verbosity', **OPTION_KWARGS_VERBOSE) +def fix_code_style(color: bool, verbosity: int): + """ + Fix code style of all cli_base source code files via darker + """ + code_style.fix(package_root=PACKAGE_ROOT, darker_color=color, darker_verbose=verbosity > 0) + + +@cli.command() +@click.option('--color/--no-color', **OPTION_ARGS_DEFAULT_TRUE) +@click.option('-v', '--verbosity', **OPTION_KWARGS_VERBOSE) +def check_code_style(color: bool, verbosity: int): + """ + Check code style by calling darker + flake8 + """ + code_style.check(package_root=PACKAGE_ROOT, darker_color=color, darker_verbose=verbosity > 0) diff --git a/dragonlib/cli_dev/packaging.py b/dragonlib/cli_dev/packaging.py new file mode 100644 index 0000000..a734cf4 --- /dev/null +++ b/dragonlib/cli_dev/packaging.py @@ -0,0 +1,81 @@ +import sys +from pathlib import Path + +import click +from cli_base.cli_tools.dev_tools import run_unittest_cli +from cli_base.cli_tools.subprocess_utils import verbose_check_call +from cli_base.cli_tools.verbosity import OPTION_KWARGS_VERBOSE +from cli_base.run_pip_audit import run_pip_audit +from manageprojects.utilities.publish import publish_package + +import dragonlib +from dragonlib.cli_dev import PACKAGE_ROOT, cli + + +@cli.command() +def install(): + """ + Run pip-sync and install 'cli_base' via pip as editable. + """ + verbose_check_call('pip-sync', PACKAGE_ROOT / 'requirements.dev.txt') + verbose_check_call('pip', 'install', '--no-deps', '-e', '.') + + +@cli.command() +@click.option('-v', '--verbosity', **OPTION_KWARGS_VERBOSE) +def pip_audit(verbosity: int): + """ + Run pip-audit check against current requirements files + """ + run_pip_audit(base_path=PACKAGE_ROOT, verbosity=verbosity) + + +@cli.command() +def update(): + """ + Update "requirements*.txt" dependencies files + """ + bin_path = Path(sys.executable).parent + + verbose_check_call(bin_path / 'pip', 'install', '-U', 'pip') + verbose_check_call(bin_path / 'pip', 'install', '-U', 'pip-tools') + + extra_env = dict( + CUSTOM_COMPILE_COMMAND='./dev-cli.py update', + ) + + pip_compile_base = [bin_path / 'pip-compile', '--verbose', '--upgrade'] + + # Only "prod" dependencies: + verbose_check_call( + *pip_compile_base, + 'pyproject.toml', + '--output-file', + 'requirements.txt', + extra_env=extra_env, + ) + + # dependencies + "dev"-optional-dependencies: + verbose_check_call( + *pip_compile_base, + 'pyproject.toml', + '--extra=dev', + '--output-file', + 'requirements.dev.txt', + extra_env=extra_env, + ) + + run_pip_audit(base_path=PACKAGE_ROOT) + + # Install new dependencies in current .venv: + verbose_check_call(bin_path / 'pip-sync', 'requirements.dev.txt') + + +@cli.command() +def publish(): + """ + Build and upload this project to PyPi + """ + run_unittest_cli(verbose=False, exit_after_run=False) # Don't publish a broken state + + publish_package(module=dragonlib, package_path=PACKAGE_ROOT) diff --git a/dragonlib/cli_dev/testing.py b/dragonlib/cli_dev/testing.py new file mode 100644 index 0000000..5ed9a11 --- /dev/null +++ b/dragonlib/cli_dev/testing.py @@ -0,0 +1,55 @@ +import rich_click as click +from cli_base.cli_tools.dev_tools import run_coverage, run_tox, run_unittest_cli +from cli_base.cli_tools.subprocess_utils import verbose_check_call +from cli_base.cli_tools.test_utils.snapshot import UpdateTestSnapshotFiles +from cli_base.cli_tools.verbosity import OPTION_KWARGS_VERBOSE +from dragonlib.cli_dev import PACKAGE_ROOT, cli + + +@cli.command() +@click.option('-v', '--verbosity', **OPTION_KWARGS_VERBOSE) +def mypy(verbosity: int): + """Run Mypy (configured in pyproject.toml)""" + verbose_check_call('mypy', '.', cwd=PACKAGE_ROOT, verbose=verbosity > 0, exit_on_error=True) + + +@cli.command() +@click.option('-v', '--verbosity', **OPTION_KWARGS_VERBOSE) +def update_test_snapshot_files(verbosity: int): + """ + Update all test snapshot files (by remove and recreate all snapshot files) + """ + + with UpdateTestSnapshotFiles(root_path=PACKAGE_ROOT, verbose=verbosity > 0): + # Just recreate them by running tests: + run_unittest_cli( + extra_env=dict( + RAISE_SNAPSHOT_ERRORS='0', # Recreate snapshot files without error + ), + verbose=verbosity > 1, + exit_after_run=False, + ) + + +@cli.command() # Dummy command +def test(): + """ + Run unittests + """ + run_unittest_cli() + + +@cli.command() # Dummy command +def coverage(): + """ + Run tests and show coverage report. + """ + run_coverage() + + +@cli.command() # Dummy "tox" command +def tox(): + """ + Run tox + """ + run_tox() diff --git a/dragonlib/constants.py b/dragonlib/constants.py new file mode 100644 index 0000000..d56fb00 --- /dev/null +++ b/dragonlib/constants.py @@ -0,0 +1,8 @@ +from pathlib import Path + +import dragonlib + + +CLI_EPILOG = 'Project Homepage: https://github.com/6809/dragonlib' + +BASE_PATH = Path(dragonlib.__file__).parent diff --git a/dragonlib/tests/__init__.py b/dragonlib/tests/__init__.py index e69de29..32d9a2b 100644 --- a/dragonlib/tests/__init__.py +++ b/dragonlib/tests/__init__.py @@ -0,0 +1,37 @@ +import os +import unittest.util +from pathlib import Path + +from bx_py_utils.test_utils.deny_requests import deny_any_real_request +from cli_base.cli_tools.verbosity import MAX_LOG_LEVEL, setup_logging +from rich import print # noqa + +from typeguard import install_import_hook + + +# Check type annotations via typeguard in all tests: +install_import_hook(packages=('dragonlib',)) + + +def pre_configure_tests() -> None: + print(f'Configure unittests via "load_tests Protocol" from {Path(__file__).relative_to(Path.cwd())}') + + # Hacky way to display more "assert"-Context in failing tests: + _MIN_MAX_DIFF = unittest.util._MAX_LENGTH - unittest.util._MIN_DIFF_LEN + unittest.util._MAX_LENGTH = int(os.environ.get('UNITTEST_MAX_LENGTH', 300)) + unittest.util._MIN_DIFF_LEN = unittest.util._MAX_LENGTH - _MIN_MAX_DIFF + + # Deny any request via docket/urllib3 because tests they should mock all requests: + deny_any_real_request() + + # Display DEBUG logs in tests: + setup_logging(verbosity=MAX_LOG_LEVEL) + + +def load_tests(loader, tests, pattern): + """ + Use unittest "load_tests Protocol" as a hook to setup test environment before running tests. + https://docs.python.org/3/library/unittest.html#load-tests-protocol + """ + pre_configure_tests() + return loader.discover(start_dir=Path(__file__).parent, pattern=pattern) diff --git a/dragonlib/tests/test_doctests.py b/dragonlib/tests/test_doctests.py new file mode 100644 index 0000000..1aa78e6 --- /dev/null +++ b/dragonlib/tests/test_doctests.py @@ -0,0 +1,10 @@ +from bx_py_utils.test_utils.unittest_utils import BaseDocTests + +import dragonlib + + +class DocTests(BaseDocTests): + def test_doctests(self): + self.run_doctests( + modules=(dragonlib,), + ) diff --git a/dragonlib/tests/test_project_setup.py b/dragonlib/tests/test_project_setup.py new file mode 100644 index 0000000..fee77c6 --- /dev/null +++ b/dragonlib/tests/test_project_setup.py @@ -0,0 +1,40 @@ +import subprocess +from unittest import TestCase + +from bx_py_utils.path import assert_is_file +from manageprojects.test_utils.project_setup import check_editor_config, get_py_max_line_length +from cli_base.cli_tools.code_style import assert_code_style +from packaging.version import Version + +from dragonlib import __version__ +from dragonlib.cli_dev import PACKAGE_ROOT + + +class ProjectSetupTestCase(TestCase): + def test_version(self): + self.assertIsNotNone(__version__) + + version = Version(__version__) # Will raise InvalidVersion() if wrong formatted + self.assertEqual(str(version), __version__) + + cli_bin = PACKAGE_ROOT / 'cli.py' + assert_is_file(cli_bin) + + output = subprocess.check_output([cli_bin, 'version'], text=True) + self.assertIn(f'dragonlib v{__version__}', output) + + dev_cli_bin = PACKAGE_ROOT / 'dev-cli.py' + assert_is_file(dev_cli_bin) + + output = subprocess.check_output([dev_cli_bin, 'version'], text=True) + self.assertIn(f'dragonlib v{__version__}', output) + + def test_code_style(self): + return_code = assert_code_style(package_root=PACKAGE_ROOT) + self.assertEqual(return_code, 0, 'Code style error, see output above!') + + def test_check_editor_config(self): + check_editor_config(package_root=PACKAGE_ROOT) + + max_line_length = get_py_max_line_length(package_root=PACKAGE_ROOT) + self.assertEqual(max_line_length, 119) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..53a715e --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,180 @@ +[project] +name = "dragonlib" +dynamic = ["version"] +description = "Library around 6809 computers like Dragon 32/64, CoCo..." +license = {text = "GPL-3.0-or-later"} +readme = "README.md" +authors = [ + {name = 'Jens Diemer', email = 'dragonlib@jensdiemer.de'} +] +requires-python = ">=3.11" +dependencies = [ + "cli-base-utilities", # https://github.com/jedie/cli-base-utilities + "bx_py_utils", # https://github.com/boxine/bx_py_utils + "click", # https://github.com/pallets/click/ + "rich-click", # https://github.com/ewels/rich-click + "rich", # https://github.com/Textualize/rich +] +[project.optional-dependencies] +dev = [ + "cli-base-utilities", # https://github.com/jedie/cli-base-utilities + "manageprojects", # https://github.com/jedie/manageprojects + "urllib3", # for bx_py_utils.test_utils.deny_requests.deny_any_real_request() in tests + "pip-tools", # https://github.com/jazzband/pip-tools/ + "tox", # https://github.com/tox-dev/tox + "coverage", # https://github.com/nedbat/coveragepy + "autopep8", # https://github.com/hhatto/autopep8 + "pyupgrade", # https://github.com/asottile/pyupgrade + "flake8", # https://github.com/pycqa/flake8 + "flake8-bugbear", # https://github.com/PyCQA/flake8-bugbear + "pyflakes", # https://github.com/PyCQA/pyflakes + "codespell", # https://github.com/codespell-project/codespell + "EditorConfig", # https://github.com/editorconfig/editorconfig-core-py + "pip-audit", # https://github.com/pypa/pip-audit + "mypy", # https://github.com/python/mypy + "twine", # https://github.com/pypa/twine + "pre-commit", # https://github.com/pre-commit/pre-commit + "typeguard", # https://github.com/agronholm/typeguard/ + + # https://github.com/akaihola/darker + # https://github.com/ikamensh/flynt + # https://github.com/pycqa/isort + # https://github.com/pygments/pygments + "darker[flynt, isort, color]", + + # Work-a-round for: https://github.com/jazzband/pip-tools/issues/1866 + # see also: https://github.com/jazzband/pip-tools/issues/994#issuecomment-1321226661 + # backports.tarfile is needed for python <3.12 + 'backports.tarfile', # via jaraco-context -> keyring -> twine +] + +[tool.pip-tools.compile] +index-url="https://pypi.org/simple" +extra-index-url=["https://www.piwheels.org/simple"] # Add hashes of piwheels packages, too! +generate-hashes = true +# https://pip-tools.readthedocs.io/en/stable/#deprecations +allow-unsafe = true +resolver="backtracking" +strip-extras = true + +[project.urls] +Documentation = "https://github.com/6809/dragonlib" +Source = "https://github.com/6809/dragonlib" + +[project.scripts] +dragonlib_app = "dragonlib.__main__:main" +dragonlib_dev = "dragonlib.cli_dev:main" + +[build-system] +requires = ["setuptools>=61.0", "setuptools_scm>=7.1"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.packages.find] +where = ["."] +include = ["dragonlib*"] + +[tool.setuptools.dynamic] +version = {attr = "dragonlib.__version__"} + + +[tool.cli_base.pip_audit] +requirements=["requirements.dev.txt"] +strict=true +require_hashes=true +ignore-vuln=[] + + +[tool.cli_base] +version_module_name = "dragonlib" # Used by "update-readme-history" git hook + + +[tool.darker] +src = ['.'] +revision = "origin/main..." +line_length = 119 +color = true +skip_string_normalization = true +diff = false +check = false +stdout = false +isort = true +lint = [ + "flake8", +] +log_level = "INFO" + + +[tool.isort] +# https://pycqa.github.io/isort/docs/configuration/config_files/#pyprojecttoml-preferred-format +atomic=true +profile='black' +skip_glob=['.*', '*/htmlcov/*'] +known_first_party=['dragonlib'] +line_length=119 +lines_after_imports=2 + + +[tool.coverage.run] +branch = true +parallel = true +concurrency = ["multiprocessing"] +source = ['.'] +command_line = '-m unittest --verbose --locals --buffer' + +[tool.coverage.report] +omit = ['.*', '*/tests/*'] +skip_empty = true +fail_under = 30 +show_missing = true +exclude_lines = [ + 'if self.debug:', + 'pragma: no cover', + 'raise NotImplementedError', + 'if __name__ == .__main__.:', +] + + +[tool.tox] # https://tox.wiki/en/latest/config.html#pyproject-toml +legacy_tox_ini = """ +[tox] +isolated_build = True +envlist = py{312,311} +skip_missing_interpreters = True + +[testenv] +passenv = * +skip_install = true +commands_pre = + pip install -U pip-tools + pip-sync requirements.dev.txt +commands = + {envpython} -m coverage run --context='{envname}' +""" + + +[tool.mypy] +warn_unused_configs = true +ignore_missing_imports = true +allow_redefinition = true # https://github.com/python/mypy/issues/7165 +show_error_codes = true +plugins = [] +exclude = ['.venv', 'tests'] + + +[manageprojects] # https://github.com/jedie/manageprojects +initial_revision = "56c3caa" +initial_date = 2024-09-22T16:52:30+02:00 +cookiecutter_template = "https://github.com/jedie/cookiecutter_templates/" +cookiecutter_directory = "piptools-python" + +[manageprojects.cookiecutter_context.cookiecutter] +full_name = "Jens Diemer" +github_username = "jedie" +author_email = "dragonlib@jensdiemer.de" +package_name = "dragonlib" +package_version = "0.2.0" +package_description = "Library around 6809 computers like Dragon 32/64, CoCo..." +package_url = "https://github.com/6809/dragonlib" +issues_url = "https://github.com/6809/dragonlib/issues" +license = "GPL-3.0-or-later" +_template = "https://github.com/jedie/cookiecutter_templates/" diff --git a/requirements.dev.txt b/requirements.dev.txt new file mode 100644 index 0000000..0b07b20 --- /dev/null +++ b/requirements.dev.txt @@ -0,0 +1,1116 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# ./dev-cli.py update +# +--extra-index-url https://www.piwheels.org/simple + +arrow==1.3.0 \ + --hash=sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80 \ + --hash=sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85 + # via cookiecutter +astor==0.8.1 \ + --hash=sha256:070a54e890cefb5b3739d19f30f5a5ec840ffc9c50ffa7d23cc9fc1a38ebbfc5 \ + --hash=sha256:6a6effda93f4e1ce9f618779b2dd1d9d84f1e32812c23a29b3fff6fd7f63fa5e + # via flynt +async-timeout==4.0.3 \ + --hash=sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f \ + --hash=sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028 \ + --hash=sha256:fd893e4cb1d9dff66d0e0576cc6376a23eacbbcd43b6607e4d3ecfae1ab15367 + # via cli-base-utilities +attrs==24.2.0 \ + --hash=sha256:09df292052496e45c3edea6e1046968c7e5231b5fee8106a49f950b42dd5eeef \ + --hash=sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346 \ + --hash=sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2 + # via flake8-bugbear +autoflake==2.3.1 \ + --hash=sha256:3ae7495db9084b7b32818b4140e6dc4fc280b712fb414f5b8fe57b0a8e85a840 \ + --hash=sha256:c98b75dc5b0a86459c4f01a1d32ac7eb4338ec4317a4469515ff1e687ecd909e + # via manageprojects +autopep8==2.3.1 \ + --hash=sha256:8d6c87eba648fdcfc83e29b788910b8643171c395d9c4bcf115ece035b9c9dda \ + --hash=sha256:a203fe0fcad7939987422140ab17a930f684763bf7335bdb6709991dd7ef6c2d \ + --hash=sha256:e889a44238a4d9a64d74d6e7b81c65a9c2d2dcd5dc898a6d054568c0d2479e0e + # via + # dragonlib (pyproject.toml) + # manageprojects +backports-tarfile==1.2.0 \ + --hash=sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34 \ + --hash=sha256:7ecb5ff4d016780ea7a9c7d0230e566f658bc0e192a6c02e3cfef76975659663 \ + --hash=sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991 + # via dragonlib (pyproject.toml) +binaryornot==0.4.4 \ + --hash=sha256:359501dfc9d40632edc9fac890e19542db1a287bbcfa58175b66658392018061 \ + --hash=sha256:b8b71173c917bddcd2c16070412e369c3ed7f0528926f70cac18a6c97fd563e4 + # via cookiecutter +black==24.8.0 \ + --hash=sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6 \ + --hash=sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e \ + --hash=sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f \ + --hash=sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018 \ + --hash=sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e \ + --hash=sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd \ + --hash=sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4 \ + --hash=sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed \ + --hash=sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2 \ + --hash=sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42 \ + --hash=sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af \ + --hash=sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb \ + --hash=sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368 \ + --hash=sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb \ + --hash=sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af \ + --hash=sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed \ + --hash=sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47 \ + --hash=sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2 \ + --hash=sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a \ + --hash=sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c \ + --hash=sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920 \ + --hash=sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1 + # via darker +boolean-py==4.0 \ + --hash=sha256:17b9a181630e43dde1851d42bef546d616d5d9b4480357514597e78b203d06e4 \ + --hash=sha256:2876f2051d7d6394a531d82dc6eb407faa0b01a0a0b3083817ccd7323b8d96bd \ + --hash=sha256:7400ad07f5606b59271a9762f58ea131b77644f4d56dd6df5b7dff0fa1706515 + # via license-expression +build==1.2.2 \ + --hash=sha256:119b2fb462adef986483438377a13b2f42064a2a3a4161f24a0cca698a07ac8c \ + --hash=sha256:277ccc71619d98afdd841a0e96ac9fe1593b823af481d3b0cea748e8894e0613 \ + --hash=sha256:45b7d306fed5299c934f42ead7fc470286a3a30c957e44246ad982ab285fbb43 + # via pip-tools +bx-py-utils==104 \ + --hash=sha256:508cfc1d0fa6c22298f697c4efaa913337847d488d8a53eeccfae9ee106123f6 \ + --hash=sha256:77fddff99aaf2cb558ce2974ef66d0e7a4d48cca1cbb99e43eca0d8b77fa07fc \ + --hash=sha256:c92ebc4fb122e3e3c228d984d0a1f5c3284c3da6aab1a1c753f7eb1f71bdab3a + # via + # cli-base-utilities + # dragonlib (pyproject.toml) +cachecontrol==0.14.0 \ + --hash=sha256:7db1195b41c81f8274a7bbd97c956f44e8348265a1bc7641c37dfebc39f0c938 \ + --hash=sha256:f5bf3f0620c38db2e5122c0726bdebb0d16869de966ea6a2befe92470b740ea0 + # via + # cachecontrol + # pip-audit +cachetools==5.5.0 \ + --hash=sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292 \ + --hash=sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a \ + --hash=sha256:bb8dc9a16172c8165e14b83c01b3dcb4070993ab743bbaaee6bf6656397843f6 + # via tox +certifi==2024.8.30 \ + --hash=sha256:3dffae5ce57c3934b457066e04f14270151dd908412a601a3abb554c5acff9d4 \ + --hash=sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8 \ + --hash=sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9 + # via requests +cffi==1.17.1 \ + --hash=sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8 \ + --hash=sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2 \ + --hash=sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1 \ + --hash=sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15 \ + --hash=sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36 \ + --hash=sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824 \ + --hash=sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8 \ + --hash=sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36 \ + --hash=sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17 \ + --hash=sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf \ + --hash=sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc \ + --hash=sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3 \ + --hash=sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed \ + --hash=sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702 \ + --hash=sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1 \ + --hash=sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8 \ + --hash=sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903 \ + --hash=sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6 \ + --hash=sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d \ + --hash=sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b \ + --hash=sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e \ + --hash=sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be \ + --hash=sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c \ + --hash=sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683 \ + --hash=sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9 \ + --hash=sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c \ + --hash=sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8 \ + --hash=sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1 \ + --hash=sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4 \ + --hash=sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655 \ + --hash=sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67 \ + --hash=sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595 \ + --hash=sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0 \ + --hash=sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65 \ + --hash=sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41 \ + --hash=sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6 \ + --hash=sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401 \ + --hash=sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6 \ + --hash=sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3 \ + --hash=sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16 \ + --hash=sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93 \ + --hash=sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e \ + --hash=sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4 \ + --hash=sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964 \ + --hash=sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c \ + --hash=sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576 \ + --hash=sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0 \ + --hash=sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3 \ + --hash=sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662 \ + --hash=sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3 \ + --hash=sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff \ + --hash=sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5 \ + --hash=sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd \ + --hash=sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f \ + --hash=sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5 \ + --hash=sha256:df27fb06fb48fa1ca9b2a87f05bae8ff3edc0ac5d8c65e8f77df3f9987a1b36b \ + --hash=sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14 \ + --hash=sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d \ + --hash=sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9 \ + --hash=sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7 \ + --hash=sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382 \ + --hash=sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a \ + --hash=sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e \ + --hash=sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a \ + --hash=sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4 \ + --hash=sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99 \ + --hash=sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87 \ + --hash=sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b + # via cryptography +cfgv==3.4.0 \ + --hash=sha256:a26cde6f2dbc2fb41e75d59fe67f9a9deb9c0257659fd357194e97f3e0891297 \ + --hash=sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9 \ + --hash=sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560 + # via pre-commit +chardet==5.2.0 \ + --hash=sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7 \ + --hash=sha256:5e60dfdc063399966f1864c5cfdb9dfeb45361dc57770ddf07eea37f6bea3019 \ + --hash=sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970 + # via + # binaryornot + # tox +charset-normalizer==3.3.2 \ + --hash=sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027 \ + --hash=sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087 \ + --hash=sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786 \ + --hash=sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8 \ + --hash=sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09 \ + --hash=sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185 \ + --hash=sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574 \ + --hash=sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e \ + --hash=sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519 \ + --hash=sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898 \ + --hash=sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269 \ + --hash=sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3 \ + --hash=sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f \ + --hash=sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6 \ + --hash=sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8 \ + --hash=sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a \ + --hash=sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73 \ + --hash=sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc \ + --hash=sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714 \ + --hash=sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2 \ + --hash=sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc \ + --hash=sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce \ + --hash=sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d \ + --hash=sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e \ + --hash=sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6 \ + --hash=sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269 \ + --hash=sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96 \ + --hash=sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d \ + --hash=sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a \ + --hash=sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4 \ + --hash=sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77 \ + --hash=sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d \ + --hash=sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0 \ + --hash=sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed \ + --hash=sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068 \ + --hash=sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac \ + --hash=sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25 \ + --hash=sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8 \ + --hash=sha256:7c3627434be5f9e083deea5193461b7b75bc51b47cecf0a7cacf66f34d3394bf \ + --hash=sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab \ + --hash=sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26 \ + --hash=sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2 \ + --hash=sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db \ + --hash=sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f \ + --hash=sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5 \ + --hash=sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99 \ + --hash=sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c \ + --hash=sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d \ + --hash=sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811 \ + --hash=sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa \ + --hash=sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a \ + --hash=sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03 \ + --hash=sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b \ + --hash=sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04 \ + --hash=sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c \ + --hash=sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001 \ + --hash=sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458 \ + --hash=sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389 \ + --hash=sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99 \ + --hash=sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985 \ + --hash=sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537 \ + --hash=sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238 \ + --hash=sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f \ + --hash=sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d \ + --hash=sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796 \ + --hash=sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a \ + --hash=sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143 \ + --hash=sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8 \ + --hash=sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c \ + --hash=sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5 \ + --hash=sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5 \ + --hash=sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711 \ + --hash=sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4 \ + --hash=sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6 \ + --hash=sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c \ + --hash=sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7 \ + --hash=sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4 \ + --hash=sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b \ + --hash=sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae \ + --hash=sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12 \ + --hash=sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c \ + --hash=sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae \ + --hash=sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8 \ + --hash=sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887 \ + --hash=sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b \ + --hash=sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4 \ + --hash=sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f \ + --hash=sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5 \ + --hash=sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33 \ + --hash=sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519 \ + --hash=sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561 + # via requests +cli-base-utilities==0.11.0 \ + --hash=sha256:22c01bffece11781db0cba2110e9bf721361d50fb8a877bd251422ebaf3daee9 \ + --hash=sha256:2c674f3af4898f97d101f6687fb45e85a4b996b93fb9a155a29b4daaf302dc43 \ + --hash=sha256:fc503f8df7e19653167ebd12551718aa0c114114b959f177be2092eab1ce1b11 + # via + # dragonlib (pyproject.toml) + # manageprojects +click==8.1.7 \ + --hash=sha256:8e38806544348fdafedd47e92e90aca882377a0680918dec4c80c225a0e5ed13 \ + --hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \ + --hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de + # via + # black + # cli-base-utilities + # cookiecutter + # dragonlib (pyproject.toml) + # manageprojects + # pip-tools + # rich-click +codespell==2.3.0 \ + --hash=sha256:2aadc005c73e46f364d90ad2ec53f4f5e725eecc9947a4279eded22a4a38eeec \ + --hash=sha256:360c7d10f75e65f67bad720af7007e1060a5d395670ec11a7ed1fed9dd17471f \ + --hash=sha256:a9c7cef2501c9cfede2110fd6d4e5e62296920efe9abfb84648df866e47f58d1 + # via + # dragonlib (pyproject.toml) + # manageprojects +colorama==0.4.6 \ + --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ + --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 + # via tox +cookiecutter==2.6.0 \ + --hash=sha256:a54a8e37995e4ed963b3e82831072d1ad4b005af736bb17b99c2cbd9d41b6e2d \ + --hash=sha256:db21f8169ea4f4fdc2408d48ca44859349de2647fbe494a9d6c3edfc0542c21c \ + --hash=sha256:e24aa0a6aee3b225cb4cf395ad2e11b82f2737e57ab863cbfc120a21830fd09b + # via manageprojects +coverage==7.6.1 \ + --hash=sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca \ + --hash=sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d \ + --hash=sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6 \ + --hash=sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989 \ + --hash=sha256:0efc26b3345182f40b1e05ffe3a83052922861b84c3d1758b13a1845364a915b \ + --hash=sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c \ + --hash=sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b \ + --hash=sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223 \ + --hash=sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f \ + --hash=sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56 \ + --hash=sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3 \ + --hash=sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8 \ + --hash=sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb \ + --hash=sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388 \ + --hash=sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0 \ + --hash=sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a \ + --hash=sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8 \ + --hash=sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f \ + --hash=sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a \ + --hash=sha256:4bbacd18d4462035cb435c0e223825c7a38fec63984cf53302071f8043fa17a0 \ + --hash=sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962 \ + --hash=sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8 \ + --hash=sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391 \ + --hash=sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc \ + --hash=sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2 \ + --hash=sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155 \ + --hash=sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb \ + --hash=sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0 \ + --hash=sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c \ + --hash=sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a \ + --hash=sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004 \ + --hash=sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060 \ + --hash=sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232 \ + --hash=sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93 \ + --hash=sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129 \ + --hash=sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163 \ + --hash=sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de \ + --hash=sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6 \ + --hash=sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23 \ + --hash=sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569 \ + --hash=sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d \ + --hash=sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778 \ + --hash=sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d \ + --hash=sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36 \ + --hash=sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a \ + --hash=sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6 \ + --hash=sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34 \ + --hash=sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704 \ + --hash=sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106 \ + --hash=sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9 \ + --hash=sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862 \ + --hash=sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b \ + --hash=sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255 \ + --hash=sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16 \ + --hash=sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3 \ + --hash=sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133 \ + --hash=sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb \ + --hash=sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657 \ + --hash=sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d \ + --hash=sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca \ + --hash=sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36 \ + --hash=sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c \ + --hash=sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e \ + --hash=sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff \ + --hash=sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7 \ + --hash=sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5 \ + --hash=sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02 \ + --hash=sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c \ + --hash=sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df \ + --hash=sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3 \ + --hash=sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a \ + --hash=sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959 \ + --hash=sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234 \ + --hash=sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc + # via dragonlib (pyproject.toml) +cryptography==43.0.1 \ + --hash=sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494 \ + --hash=sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806 \ + --hash=sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d \ + --hash=sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062 \ + --hash=sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2 \ + --hash=sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4 \ + --hash=sha256:48f856c79ff4dd4f0ddbca8af99957730b34d8ead94b016e7bfc1f045e5450a4 \ + --hash=sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1 \ + --hash=sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85 \ + --hash=sha256:5b43d1ea6b378b54a1dc99dd8a2b5be47658fe9a7ce0a58ff0b55f4b43ef2b84 \ + --hash=sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042 \ + --hash=sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d \ + --hash=sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962 \ + --hash=sha256:7c05650fe8023c5ed0d46793d4b7d7e6cd9c04e68eabe5b0aeea836e37bdcec2 \ + --hash=sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa \ + --hash=sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d \ + --hash=sha256:88cce104c36870d70c49c7c8fd22885875d950d9ee6ab54df2745f83ba0dc365 \ + --hash=sha256:9d3cdb25fa98afdd3d0892d132b8d7139e2c087da1712041f6b762e4f807cc96 \ + --hash=sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47 \ + --hash=sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d \ + --hash=sha256:c1332724be35d23a854994ff0b66530119500b6053d0bd3363265f7e5e77288d \ + --hash=sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c \ + --hash=sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb \ + --hash=sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277 \ + --hash=sha256:e710bf40870f4db63c3d7d929aa9e09e4e7ee219e703f949ec4073b4294f6172 \ + --hash=sha256:ea25acb556320250756e53f9e20a4177515f012c9eaea17eb7587a8c4d8ae034 \ + --hash=sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a \ + --hash=sha256:fba1007b3ef89946dbbb515aeeb41e30203b004f0b4b00e5e16078b518563289 + # via secretstorage +cyclonedx-python-lib==7.6.1 \ + --hash=sha256:42e510e957c2ce9c71dd33020e43ce53fe6d0c854cfdc3c56e854e9461e846eb \ + --hash=sha256:6570f14ad191c4c2a87032f4fb0fc913e5c37a43a577898daeb42a3079e52126 + # via pip-audit +darker==2.1.1 \ + --hash=sha256:0ce80b96fb84f5f312a34d6a9b3235d51a7884340ff55ada41fb2f030a5bfc5b \ + --hash=sha256:a6e6a682c0604e76fe9aec7650e96a944f517563c69b28fcc076db9d957d98ea \ + --hash=sha256:ead701414c45359fc0312bc285614d3285fc135476d43f3bc08d989ee19d9020 + # via + # dragonlib (pyproject.toml) + # manageprojects +darkgraylib==1.2.1 \ + --hash=sha256:60c59de69842367ce0c78c32c451fa8e9d29500e681312d9864a7416bcdb7792 \ + --hash=sha256:a5dd6a2015a470d9047278cdd01a91ccb1d746675f8fd4562b3b5f6b8cbda930 \ + --hash=sha256:dfa96200aa470455c55cc5160bbec05f262843e2a953b3e35a147ee4ec062c63 + # via + # darker + # graylint +defusedxml==0.7.1 \ + --hash=sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69 \ + --hash=sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61 + # via py-serializable +distlib==0.3.8 \ + --hash=sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784 \ + --hash=sha256:0444bad8dcf71ec6c3281939ed79fe8a3a278dd0745b8e6d1374d038dd5d64c7 \ + --hash=sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64 + # via virtualenv +docutils==0.21.2 \ + --hash=sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f \ + --hash=sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2 \ + --hash=sha256:f6c6a2ce2fceaf3a3bb162dd3b43ee5aa6b481f363d1af4021f7f194d94f0eea + # via readme-renderer +editorconfig==0.12.4 \ + --hash=sha256:24857fa1793917dd9ccf0c7810a07e05404ce9b823521c7dce22a4fb5d125f80 \ + --hash=sha256:bc3762aae43e78db19a28f21438d90d176e8a7a78b62c4b9b01b36c796863e9f + # via + # dragonlib (pyproject.toml) + # manageprojects +filelock==3.16.1 \ + --hash=sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0 \ + --hash=sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435 + # via + # cachecontrol + # tox + # virtualenv +flake8==7.1.1 \ + --hash=sha256:049d058491e228e03e67b390f311bbf88fce2dbaa8fa673e7aea87b7198b8d38 \ + --hash=sha256:1655b1e0ef48cb379a4893ac851225d58392ccde0b16e41395b7960af51e66ce \ + --hash=sha256:597477df7860daa5aa0fdd84bf5208a043ab96b8e96ab708770ae0364dd03213 + # via + # dragonlib (pyproject.toml) + # flake8-bugbear + # manageprojects +flake8-bugbear==24.8.19 \ + --hash=sha256:25bc3867f7338ee3b3e0916bf8b8a0b743f53a9a5175782ddc4325ed4f386b89 \ + --hash=sha256:4ee3d12e790c70021a269d60f391efdda17381de3c1f83553cbe01cafc96558d \ + --hash=sha256:9b77627eceda28c51c27af94560a72b5b2c97c016651bdce45d8f56c180d2d32 + # via + # dragonlib (pyproject.toml) + # manageprojects +flynt==1.0.1 \ + --hash=sha256:65d1c546434827275123222a98408e9561bcd67db832dd58f530ff17b8329ec1 \ + --hash=sha256:7f8b46341eabefeee5a14b4284a547ee0eafb9b447f2b3c227de2fbb5c769305 \ + --hash=sha256:988aac00672a5469726cc0a17cef7d1178c284a9fe8563458db2475d0aaed965 + # via darker +graylint==1.1.1 \ + --hash=sha256:0fd8e02972ca03d0ef2bf0adea76b5343efcd492d7afb5f658f3e3a724f55a36 \ + --hash=sha256:a895ab9b4fc0666a24979b5e769df2c964189bfaad8f6a86684c980dea5634f3 \ + --hash=sha256:b7e0eab6c159684dbf5ef84e942c3340f6a6549b02a3d11b1a1763cc4f8f0593 + # via darker +html5lib==1.1 \ + --hash=sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d \ + --hash=sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f \ + --hash=sha256:e6a6ca5b1d876bce04ea36e2dd747a45b36fe3d9ac4db0b8437f46842fbebcbf + # via pip-audit +identify==2.6.1 \ + --hash=sha256:53863bcac7caf8d2ed85bd20312ea5dcfc22226800f6d6881f232d861db5a8f0 \ + --hash=sha256:91478c5fb7c3aac5ff7bf9b4344f803843dc586832d5f110d672b19aa1984c98 \ + --hash=sha256:fabaffe28c2f7e909ce6424a31ca8ee25b4c90beee6ca5900c5fdb77b2ef6aee + # via pre-commit +idna==3.10 \ + --hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \ + --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3 + # via requests +importlib-metadata==8.5.0 \ + --hash=sha256:25fdeef51551b4bf26b88884dea00a484828b76d3bf033b01fd7db602b15c78b \ + --hash=sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b \ + --hash=sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7 + # via twine +isort==5.13.2 \ + --hash=sha256:34a146c22c83acbd192a848e12a9b5e6bc14ef3d17f38fd5ad22a0b0488b87ba \ + --hash=sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109 \ + --hash=sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6 + # via darker +jaraco-classes==3.4.0 \ + --hash=sha256:3eee3025d493c64ae6f07a075647b8a31f6ab52633465398b64254d842aa694f \ + --hash=sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd \ + --hash=sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790 + # via keyring +jaraco-context==6.0.1 \ + --hash=sha256:9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3 \ + --hash=sha256:a8d40a2423da612544547753f21b6d901437836453ba240d734bccf9b7ee48d7 \ + --hash=sha256:f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4 + # via keyring +jaraco-functools==4.0.2 \ + --hash=sha256:089319bf84b20bc74cc019b25619b203f0cedec1a1942e50851709a307d4e20b \ + --hash=sha256:3460c74cd0d32bf82b9576bbb3527c4364d5b27a21f5158a62aed6c4b42e23f5 \ + --hash=sha256:c9d16a3ed4ccb5a889ad8e0b7a343401ee5b2a71cee6ed192d3f68bc351e94e3 + # via keyring +jeepney==0.8.0 \ + --hash=sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806 \ + --hash=sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755 \ + --hash=sha256:d33e9925c7f8fede3d08133df6499da70d87883dcb80a83d192d2f696cfd9467 + # via + # keyring + # secretstorage +jinja2==3.1.4 \ + --hash=sha256:2cc61acdcd68c66df952089a1f22f3a5047ecf02a4598de6d82ec2991f701077 \ + --hash=sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369 \ + --hash=sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d + # via cookiecutter +keyring==25.4.1 \ + --hash=sha256:24996478704ab666032c630a4728ba286a6b545b5dc43a9418b442716980381e \ + --hash=sha256:5426f817cf7f6f007ba5ec722b1bcad95a75b27d780343772ad76b17cb47b0bf \ + --hash=sha256:b07ebc55f3e8ed86ac81dd31ef14e81ace9dd9c3d4b5d77a6e9a2016d0d71a1b + # via twine +license-expression==30.3.1 \ + --hash=sha256:4b67d089930f4c4ceaade36c74ae3529ea81a51c9ad425748c0d75a1111bcbf7 \ + --hash=sha256:60d5bec1f3364c256a92b9a08583d7ea933c7aa272c8d36d04144a89a3858c01 \ + --hash=sha256:97904b9185c7bbb1e98799606fa7424191c375e70ba63a524b6f7100e42ddc46 + # via cyclonedx-python-lib +manageprojects==0.19.2 \ + --hash=sha256:4c5649deb9791f3016c1848b34080e1564ea3bec2a8e25a09f9f0af9012086df \ + --hash=sha256:5f9b7d692df1540b8787ae31acc9613270be01c7d4416aad77e5dee4ca97e363 + # via dragonlib (pyproject.toml) +markdown-it-py==3.0.0 \ + --hash=sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 \ + --hash=sha256:6d00561ef69282f31c923e5520f8f7025f6c2bbd1d7796f3d730840cc54130a6 \ + --hash=sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb + # via rich +markupsafe==2.1.5 \ + --hash=sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf \ + --hash=sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff \ + --hash=sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f \ + --hash=sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3 \ + --hash=sha256:192e316d05938784327f7e4970b0508ea32a03aad31988ba6623b1e4f460df02 \ + --hash=sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532 \ + --hash=sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f \ + --hash=sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617 \ + --hash=sha256:2cd05d6c529898e729ea24672f95288cf2a2cb6f5a1b08b6800938e78135cb12 \ + --hash=sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df \ + --hash=sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4 \ + --hash=sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906 \ + --hash=sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f \ + --hash=sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4 \ + --hash=sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8 \ + --hash=sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371 \ + --hash=sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2 \ + --hash=sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465 \ + --hash=sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52 \ + --hash=sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6 \ + --hash=sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169 \ + --hash=sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad \ + --hash=sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2 \ + --hash=sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0 \ + --hash=sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029 \ + --hash=sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f \ + --hash=sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a \ + --hash=sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced \ + --hash=sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5 \ + --hash=sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c \ + --hash=sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf \ + --hash=sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9 \ + --hash=sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb \ + --hash=sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad \ + --hash=sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3 \ + --hash=sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1 \ + --hash=sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46 \ + --hash=sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc \ + --hash=sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a \ + --hash=sha256:a89baf8dc3291ecfd0bee0732cf641f470ad6f05d4db9bb1be2c469c464e0b06 \ + --hash=sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee \ + --hash=sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900 \ + --hash=sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5 \ + --hash=sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea \ + --hash=sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f \ + --hash=sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5 \ + --hash=sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e \ + --hash=sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a \ + --hash=sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f \ + --hash=sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50 \ + --hash=sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a \ + --hash=sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b \ + --hash=sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4 \ + --hash=sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff \ + --hash=sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2 \ + --hash=sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46 \ + --hash=sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b \ + --hash=sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf \ + --hash=sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5 \ + --hash=sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5 \ + --hash=sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab \ + --hash=sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd \ + --hash=sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68 + # via jinja2 +mccabe==0.7.0 \ + --hash=sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325 \ + --hash=sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e \ + --hash=sha256:afd5dbbfbc7a5c66374ba2c14777063ec6fb2f0218bf1de55c65dbcf8a7f053d + # via flake8 +mdurl==0.1.2 \ + --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ + --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba + # via markdown-it-py +more-itertools==10.5.0 \ + --hash=sha256:037b0d3203ce90cca8ab1defbbdac29d5f993fc20131f3664dc8d6acfa872aef \ + --hash=sha256:10dc83c9dd717b6e23423252cef14a1f0efa9d91afed5a60905fef9d7639d0f1 \ + --hash=sha256:5482bfef7849c25dc3c6dd53a6173ae4795da2a41a80faea6700d9f5846c5da6 + # via + # jaraco-classes + # jaraco-functools +msgpack==1.1.0 \ + --hash=sha256:06f5fd2f6bb2a7914922d935d3b8bb4a7fff3a9a91cfce6d06c13bc42bec975b \ + --hash=sha256:071603e2f0771c45ad9bc65719291c568d4edf120b44eb36324dcb02a13bfddf \ + --hash=sha256:0907e1a7119b337971a689153665764adc34e89175f9a34793307d9def08e6ca \ + --hash=sha256:0f92a83b84e7c0749e3f12821949d79485971f087604178026085f60ce109330 \ + --hash=sha256:115a7af8ee9e8cddc10f87636767857e7e3717b7a2e97379dc2054712693e90f \ + --hash=sha256:13599f8829cfbe0158f6456374e9eea9f44eee08076291771d8ae93eda56607f \ + --hash=sha256:17fb65dd0bec285907f68b15734a993ad3fc94332b5bb21b0435846228de1f39 \ + --hash=sha256:2137773500afa5494a61b1208619e3871f75f27b03bcfca7b3a7023284140247 \ + --hash=sha256:3180065ec2abbe13a4ad37688b61b99d7f9e012a535b930e0e683ad6bc30155b \ + --hash=sha256:398b713459fea610861c8a7b62a6fec1882759f308ae0795b5413ff6a160cf3c \ + --hash=sha256:3d364a55082fb2a7416f6c63ae383fbd903adb5a6cf78c5b96cc6316dc1cedc7 \ + --hash=sha256:3df7e6b05571b3814361e8464f9304c42d2196808e0119f55d0d3e62cd5ea044 \ + --hash=sha256:41c991beebf175faf352fb940bf2af9ad1fb77fd25f38d9142053914947cdbf6 \ + --hash=sha256:42f754515e0f683f9c79210a5d1cad631ec3d06cea5172214d2176a42e67e19b \ + --hash=sha256:452aff037287acb1d70a804ffd022b21fa2bb7c46bee884dbc864cc9024128a0 \ + --hash=sha256:4676e5be1b472909b2ee6356ff425ebedf5142427842aa06b4dfd5117d1ca8a2 \ + --hash=sha256:46c34e99110762a76e3911fc923222472c9d681f1094096ac4102c18319e6468 \ + --hash=sha256:471e27a5787a2e3f974ba023f9e265a8c7cfd373632247deb225617e3100a3c7 \ + --hash=sha256:4a1964df7b81285d00a84da4e70cb1383f2e665e0f1f2a7027e683956d04b734 \ + --hash=sha256:4b51405e36e075193bc051315dbf29168d6141ae2500ba8cd80a522964e31434 \ + --hash=sha256:4d1b7ff2d6146e16e8bd665ac726a89c74163ef8cd39fa8c1087d4e52d3a2325 \ + --hash=sha256:53258eeb7a80fc46f62fd59c876957a2d0e15e6449a9e71842b6d24419d88ca1 \ + --hash=sha256:534480ee5690ab3cbed89d4c8971a5c631b69a8c0883ecfea96c19118510c846 \ + --hash=sha256:58638690ebd0a06427c5fe1a227bb6b8b9fdc2bd07701bec13c2335c82131a88 \ + --hash=sha256:58dfc47f8b102da61e8949708b3eafc3504509a5728f8b4ddef84bd9e16ad420 \ + --hash=sha256:59caf6a4ed0d164055ccff8fe31eddc0ebc07cf7326a2aaa0dbf7a4001cd823e \ + --hash=sha256:5dbad74103df937e1325cc4bfeaf57713be0b4f15e1c2da43ccdd836393e2ea2 \ + --hash=sha256:5e1da8f11a3dd397f0a32c76165cf0c4eb95b31013a94f6ecc0b280c05c91b59 \ + --hash=sha256:646afc8102935a388ffc3914b336d22d1c2d6209c773f3eb5dd4d6d3b6f8c1cb \ + --hash=sha256:64fc9068d701233effd61b19efb1485587560b66fe57b3e50d29c5d78e7fef68 \ + --hash=sha256:65553c9b6da8166e819a6aa90ad15288599b340f91d18f60b2061f402b9a4915 \ + --hash=sha256:685ec345eefc757a7c8af44a3032734a739f8c45d1b0ac45efc5d8977aa4720f \ + --hash=sha256:6ad622bf7756d5a497d5b6836e7fc3752e2dd6f4c648e24b1803f6048596f701 \ + --hash=sha256:73322a6cc57fcee3c0c57c4463d828e9428275fb85a27aa2aa1a92fdc42afd7b \ + --hash=sha256:74bed8f63f8f14d75eec75cf3d04ad581da6b914001b474a5d3cd3372c8cc27d \ + --hash=sha256:79ec007767b9b56860e0372085f8504db5d06bd6a327a335449508bbee9648fa \ + --hash=sha256:7a946a8992941fea80ed4beae6bff74ffd7ee129a90b4dd5cf9c476a30e9708d \ + --hash=sha256:7ad442d527a7e358a469faf43fda45aaf4ac3249c8310a82f0ccff9164e5dccd \ + --hash=sha256:7c9a35ce2c2573bada929e0b7b3576de647b0defbd25f5139dcdaba0ae35a4cc \ + --hash=sha256:7e7b853bbc44fb03fbdba34feb4bd414322180135e2cb5164f20ce1c9795ee48 \ + --hash=sha256:879a7b7b0ad82481c52d3c7eb99bf6f0645dbdec5134a4bddbd16f3506947feb \ + --hash=sha256:8a706d1e74dd3dea05cb54580d9bd8b2880e9264856ce5068027eed09680aa74 \ + --hash=sha256:8a84efb768fb968381e525eeeb3d92857e4985aacc39f3c47ffd00eb4509315b \ + --hash=sha256:8cf9e8c3a2153934a23ac160cc4cba0ec035f6867c8013cc6077a79823370346 \ + --hash=sha256:8da4bf6d54ceed70e8861f833f83ce0814a2b72102e890cbdfe4b34764cdd66e \ + --hash=sha256:8e59bca908d9ca0de3dc8684f21ebf9a690fe47b6be93236eb40b99af28b6ea6 \ + --hash=sha256:914571a2a5b4e7606997e169f64ce53a8b1e06f2cf2c3a7273aa106236d43dd5 \ + --hash=sha256:a27072dbdd43f16e7f5d555415e19de580dbbc3a81626f2d16aa04010502c1d6 \ + --hash=sha256:a51abd48c6d8ac89e0cfd4fe177c61481aca2d5e7ba42044fd218cfd8ea9899f \ + --hash=sha256:a52a1f3a5af7ba1c9ace055b659189f6c669cf3657095b50f9602af3a3ba0fe5 \ + --hash=sha256:ad33e8400e4ec17ba782f7b9cf868977d867ed784a1f5f2ab46e7ba53b6e1e1b \ + --hash=sha256:b4c01941fd2ff87c2a934ee6055bda4ed353a7846b8d4f341c428109e9fcde8c \ + --hash=sha256:bce7d9e614a04d0883af0b3d4d501171fbfca038f12c77fa838d9f198147a23f \ + --hash=sha256:c40ffa9a15d74e05ba1fe2681ea33b9caffd886675412612d93ab17b58ea2fec \ + --hash=sha256:c5a91481a3cc573ac8c0d9aace09345d989dc4a0202b7fcb312c88c26d4e71a8 \ + --hash=sha256:c921af52214dcbb75e6bdf6a661b23c3e6417f00c603dd2070bccb5c3ef499f5 \ + --hash=sha256:d46cf9e3705ea9485687aa4001a76e44748b609d260af21c4ceea7f2212a501d \ + --hash=sha256:d8ce0b22b890be5d252de90d0e0d119f363012027cf256185fc3d474c44b1b9e \ + --hash=sha256:dd432ccc2c72b914e4cb77afce64aab761c1137cc698be3984eee260bcb2896e \ + --hash=sha256:e0856a2b7e8dcb874be44fea031d22e5b3a19121be92a1e098f46068a11b0870 \ + --hash=sha256:e1f3c3d21f7cf67bcf2da8e494d30a75e4cf60041d98b3f79875afb5b96f3a3f \ + --hash=sha256:e8598af127c3ebc78ec460f96848942eca0126563c3e5b9724b33edf20106b76 \ + --hash=sha256:f1ba6136e650898082d9d5a5217d5906d1e138024f836ff48691784bbe1adf96 \ + --hash=sha256:f3e9b4936df53b970513eac1758f3882c88658a220b58dcc1e39606dccaaf01c \ + --hash=sha256:f80bc7d47f76089633763f952e67f8214cb7b3ee6bfa489b3cb6a84cfac114cd \ + --hash=sha256:fd2906780f25c8ed5d7b323379f6138524ba793428db5d0e9d226d3fa6aa1788 + # via cachecontrol +mypy==1.11.2 \ + --hash=sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36 \ + --hash=sha256:2ff93107f01968ed834f4256bc1fc4475e2fecf6c661260066a985b52741ddce \ + --hash=sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6 \ + --hash=sha256:37c7fa6121c1cdfcaac97ce3d3b5588e847aa79b580c1e922bb5d5d2902df19b \ + --hash=sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca \ + --hash=sha256:3f14cd3d386ac4d05c5a39a51b84387403dadbd936e17cb35882134d4f8f0d24 \ + --hash=sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383 \ + --hash=sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7 \ + --hash=sha256:4a8a53bc3ffbd161b5b2a4fff2f0f1e23a33b0168f1c0778ec70e1a3d66deb86 \ + --hash=sha256:539c570477a96a4e6fb718b8d5c3e0c0eba1f485df13f86d2970c91f0673148d \ + --hash=sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4 \ + --hash=sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8 \ + --hash=sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987 \ + --hash=sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385 \ + --hash=sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79 \ + --hash=sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef \ + --hash=sha256:801ca29f43d5acce85f8e999b1e431fb479cb02d0e11deb7d2abb56bdaf24fd6 \ + --hash=sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70 \ + --hash=sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca \ + --hash=sha256:af8d155170fcf87a2afb55b35dc1a0ac21df4431e7d96717621962e4b9192e70 \ + --hash=sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12 \ + --hash=sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104 \ + --hash=sha256:cecf1897ad229cbc3b84d26738f58f348e7924912073539839414a8bc1b498a5 \ + --hash=sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a \ + --hash=sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318 \ + --hash=sha256:edb91dded4df17eae4537668b23f0ff6baf3707683734b6a818d5b9d0c0c31a1 \ + --hash=sha256:ee23de8530d99b6db0573c4ef4bd8f39a2a6f9b60655bf7a1357e585a3486f2b \ + --hash=sha256:f7821776e5c4286b6a13138cc935e2e9b6fde05e081bdebf5cdb2bb97c9df81d + # via + # dragonlib (pyproject.toml) + # manageprojects + # refurb +mypy-extensions==1.0.0 \ + --hash=sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d \ + --hash=sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782 \ + --hash=sha256:7b58bcc6d3a548c146400c2e5de934bdbf2658503f897adee40f210e19b1f0ac + # via + # black + # mypy +nh3==0.2.18 \ + --hash=sha256:0411beb0589eacb6734f28d5497ca2ed379eafab8ad8c84b31bb5c34072b7164 \ + --hash=sha256:14c5a72e9fe82aea5fe3072116ad4661af5cf8e8ff8fc5ad3450f123e4925e86 \ + --hash=sha256:19aaba96e0f795bd0a6c56291495ff59364f4300d4a39b29a0abc9cb3774a84b \ + --hash=sha256:1c6e9383e082f5895d49836603af919395b99e293e0818a5e15492843d0b4d53 \ + --hash=sha256:34c03fa78e328c691f982b7c03d4423bdfd7da69cd707fe572f544cf74ac23ad \ + --hash=sha256:36c95d4b70530b320b365659bb5034341316e6a9b30f0b25fa9c9eff4c27a204 \ + --hash=sha256:3a157ab149e591bb638a55c8c6bcb8cdb559c8b12c13a8affaba6cedfe51713a \ + --hash=sha256:42c64511469005058cd17cc1537578eac40ae9f7200bedcfd1fc1a05f4f8c200 \ + --hash=sha256:5f36b271dae35c465ef5e9090e1fdaba4a60a56f0bb0ba03e0932a66f28b9189 \ + --hash=sha256:6955369e4d9f48f41e3f238a9e60f9410645db7e07435e62c6a9ea6135a4907f \ + --hash=sha256:7b7c2a3c9eb1a827d42539aa64091640bd275b81e097cd1d8d82ef91ffa2e811 \ + --hash=sha256:8ce0f819d2f1933953fca255db2471ad58184a60508f03e6285e5114b6254844 \ + --hash=sha256:94a166927e53972a9698af9542ace4e38b9de50c34352b962f4d9a7d4c927af4 \ + --hash=sha256:a7f1b5b2c15866f2db413a3649a8fe4fd7b428ae58be2c0f6bca5eefd53ca2be \ + --hash=sha256:c8b3a1cebcba9b3669ed1a84cc65bf005728d2f0bc1ed2a6594a992e817f3a50 \ + --hash=sha256:de3ceed6e661954871d6cd78b410213bdcb136f79aafe22aa7182e028b8c7307 \ + --hash=sha256:f0eca9ca8628dbb4e916ae2491d72957fdd35f7a5d326b7032a345f111ac07fe + # via readme-renderer +nodeenv==1.9.1 \ + --hash=sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f \ + --hash=sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9 \ + --hash=sha256:e3d5a1b67a0cdf73b094aca8a68a80befbde9ca317303f18bf8af61198edc37f + # via pre-commit +packageurl-python==0.15.6 \ + --hash=sha256:0d39b14186da7367ab71ca35e656cfc1e81eaf98dcd2801ded953f34232bfc92 \ + --hash=sha256:a40210652c89022772a6c8340d6066f7d5dc67132141e5284a4db7a27d0a8ab0 \ + --hash=sha256:cbc89afd15d5f4d05db4f1b61297e5b97a43f61f28799f6d282aff467ed2ee96 + # via cyclonedx-python-lib +packaging==24.1 \ + --hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \ + --hash=sha256:270c5ae91ef4184b318179b819b2e4420e8f41246d05334899bc9dfc30c80cac \ + --hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124 + # via + # black + # build + # cli-base-utilities + # pip-audit + # pip-requirements-parser + # pyproject-api + # tox +pathspec==0.12.1 \ + --hash=sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08 \ + --hash=sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712 \ + --hash=sha256:abdf2ccde2babf20f12de0f090074ebea14a544ece6a038e20fb5e000a30e869 + # via black +pip-api==0.0.34 \ + --hash=sha256:8b2d7d7c37f2447373aa2cf8b1f60a2f2b27a84e1e9e0294a3f6ef10eb3ba6bb \ + --hash=sha256:9b75e958f14c5a2614bae415f2adf7eeb54d50a2cfbe7e24fd4826471bac3625 \ + --hash=sha256:d395ae96471efb8efc3574e75406c1ae495c36e4ca4e68905cd0be57cb5d182d + # via pip-audit +pip-audit==2.7.3 \ + --hash=sha256:08891bbf179bffe478521f150818112bae998424f58bf9285c0078965aef38bc \ + --hash=sha256:46a11faee3323f76adf7987de8171daeb660e8f57d8088cc27fb1c1e5c7747b0 + # via dragonlib (pyproject.toml) +pip-requirements-parser==32.0.1 \ + --hash=sha256:4659bc2a667783e7a15d190f6fccf8b2486685b6dba4c19c3876314769c57526 \ + --hash=sha256:53464120dcec4eaa04fdc5d7577ef231a7d1194d397dd9cb066d7155c9be6f27 \ + --hash=sha256:b4fa3a7a0be38243123cf9d1f3518da10c51bdb165a2b2985566247f9155a7d3 + # via pip-audit +pip-tools==7.4.1 \ + --hash=sha256:35d594eaaa262f2d3161954aeb25617ee9e597ecf624b77089e5c7ee309e5937 \ + --hash=sha256:4c690e5fbae2f21e87843e89c26191f0d9454f362d8acdbd695716493ec8b3a9 \ + --hash=sha256:864826f5073864450e24dbeeb85ce3920cdfb09848a3d69ebf537b521f14bcc9 + # via dragonlib (pyproject.toml) +pkginfo==1.10.0 \ + --hash=sha256:5df73835398d10db79f8eecd5cd86b1f6d29317589ea70796994d49399af6297 \ + --hash=sha256:889a6da2ed7ffc58ab5b900d888ddce90bce912f2d2de1dc1c26f4cb9fe65097 \ + --hash=sha256:ac3003435f60fdd443e836719b8b3312c7f2f12ec42d137d1b2feacbac73d757 + # via twine +platformdirs==4.3.6 \ + --hash=sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907 \ + --hash=sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb + # via + # black + # tox + # virtualenv +pluggy==1.5.0 \ + --hash=sha256:0a73af206bfd0f78d40b53c4da46553729ce04cb0d1c23e3b26c1d2f4538c691 \ + --hash=sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1 \ + --hash=sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669 + # via tox +pre-commit==3.8.0 \ + --hash=sha256:03a8cc30ccb2ff639d46e6f47563a1ceffb02578ce6ab3adb5f10c38d4e47cea \ + --hash=sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af \ + --hash=sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f + # via dragonlib (pyproject.toml) +py-serializable==1.1.1 \ + --hash=sha256:008cf879c8a227851e745e307c1a1c9b58bc22ab87f3794a3750513b6c9e8713 \ + --hash=sha256:f268db3afc42c8786da6dc64a8a36e33a82cf65cdcff22d1188b0927f6d4cfa9 + # via cyclonedx-python-lib +pycodestyle==2.12.1 \ + --hash=sha256:37e5160726190311bb6ce887106c6ab5553f7a646d4294017ccccbac26a7b86d \ + --hash=sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3 \ + --hash=sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521 + # via + # autopep8 + # flake8 +pycparser==2.22 \ + --hash=sha256:1a6dcf2544745c9222d3981d9ffcd6231ffc69b827b0847fc40586388287a0ca \ + --hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \ + --hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc + # via cffi +pyflakes==3.2.0 \ + --hash=sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f \ + --hash=sha256:2f47e486427c68ec1f4a5f04dfcb1df637f456b649852051047181ec76dc8cf8 \ + --hash=sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a + # via + # autoflake + # dragonlib (pyproject.toml) + # flake8 + # manageprojects +pygments==2.18.0 \ + --hash=sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199 \ + --hash=sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a + # via + # darker + # readme-renderer + # rich +pyparsing==3.1.4 \ + --hash=sha256:7b4bab7b9f90b65322e263c942eadc57997d42553c2d6bd7c32798f6c04b823f \ + --hash=sha256:a6a7ee4235a3f944aa1fa2249307708f893fe5717dc603503c6c7969c070fb7c \ + --hash=sha256:f86ec8d1a83f11977c9a6ea7598e8c27fc5cddfa5b07ea2241edbbde1d7bc032 + # via pip-requirements-parser +pyproject-api==1.8.0 \ + --hash=sha256:3d7d347a047afe796fd5d1885b1e391ba29be7169bd2f102fcd378f04273d228 \ + --hash=sha256:77b8049f2feb5d33eefcc21b57f1e279636277a8ac8ad6b5871037b243778496 + # via tox +pyproject-hooks==1.1.0 \ + --hash=sha256:4b37730834edbd6bd37f26ece6b44802fb1c1ee2ece0e54ddff8bfc06db86965 \ + --hash=sha256:7ceeefe9aec63a1064c18d939bdc3adf2d8aa1988a510afec15151578b232aa2 + # via + # build + # pip-tools +python-dateutil==2.9.0.post0 \ + --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ + --hash=sha256:5d23f9d7e398684c7dac22b46a93fb71de568cfdcd004512f7072819e41ef54c \ + --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 + # via + # arrow + # cli-base-utilities +python-slugify==8.0.4 \ + --hash=sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8 \ + --hash=sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856 \ + --hash=sha256:cbe9e94cf0011a0079b3fe056ceab7275030bcb10bfc545d28dab6a4d3c1174d + # via cookiecutter +pyupgrade==3.17.0 \ + --hash=sha256:81a234c8e8630146b5e705caf1b86d127d799cbc5c369d3ca43c056b505783ec \ + --hash=sha256:cbc8f67a61d3f4e7ca9c2ef57b9aae67f023d3780ce30c99fccec78401723754 \ + --hash=sha256:d5dd1dcaf9a016c31508bb9d3d09fd335d736578092f91df52bb26ac30c37919 + # via + # dragonlib (pyproject.toml) + # manageprojects +pyyaml==6.0.2 \ + --hash=sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff \ + --hash=sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48 \ + --hash=sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086 \ + --hash=sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e \ + --hash=sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133 \ + --hash=sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5 \ + --hash=sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484 \ + --hash=sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee \ + --hash=sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5 \ + --hash=sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68 \ + --hash=sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a \ + --hash=sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf \ + --hash=sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99 \ + --hash=sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8 \ + --hash=sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85 \ + --hash=sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19 \ + --hash=sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc \ + --hash=sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a \ + --hash=sha256:4a116b1e66edb7eea662c281c85f5669606bdccb6173b03d442a0dd33586c8cc \ + --hash=sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1 \ + --hash=sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317 \ + --hash=sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c \ + --hash=sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631 \ + --hash=sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d \ + --hash=sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652 \ + --hash=sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5 \ + --hash=sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e \ + --hash=sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b \ + --hash=sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8 \ + --hash=sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476 \ + --hash=sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706 \ + --hash=sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563 \ + --hash=sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237 \ + --hash=sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b \ + --hash=sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083 \ + --hash=sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180 \ + --hash=sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425 \ + --hash=sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e \ + --hash=sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f \ + --hash=sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725 \ + --hash=sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183 \ + --hash=sha256:c17d542ee2e07d5085fd1a99af3c9bd7d313b0f26527f4fb2cdc5b553f3f5af2 \ + --hash=sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab \ + --hash=sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774 \ + --hash=sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725 \ + --hash=sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e \ + --hash=sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5 \ + --hash=sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d \ + --hash=sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290 \ + --hash=sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44 \ + --hash=sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed \ + --hash=sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4 \ + --hash=sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba \ + --hash=sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12 \ + --hash=sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4 + # via + # cookiecutter + # pre-commit +readme-renderer==44.0 \ + --hash=sha256:2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151 \ + --hash=sha256:8712034eabbfa6805cacf1402b4eeb2a73028f72d1166d6f5cb7f9c047c5d1e1 \ + --hash=sha256:d812ef55c1e4fba85c293a8a06646caef3a915dedf37258a18380390f9d1befd + # via twine +refurb==2.0.0 \ + --hash=sha256:8a8f1e7c131ef7dc460cbecbeaf536f5eb0ecb657c099d7823941f0e65b1cfe1 \ + --hash=sha256:fa9e950dc6edd7473642569c118f8714eefd1e6f21a15ee4210a1be853aaaf80 + # via manageprojects +requests==2.32.3 \ + --hash=sha256:41c1576e09f429d8f841b1143f51754df46557f6eb229605898a79b98c311db4 \ + --hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \ + --hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6 + # via + # cachecontrol + # cookiecutter + # pip-audit + # requests-toolbelt + # twine +requests-toolbelt==1.0.0 \ + --hash=sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6 \ + --hash=sha256:9ffec1e9f4d197a5411d14206189aac1b171d66e33c1858b558325c011358cbe \ + --hash=sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06 + # via twine +rfc3986==2.0.0 \ + --hash=sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd \ + --hash=sha256:7b542865c173d046c1f1ed2bccb7ae55f7f9535d0a5ab4809b11217d0e09556b \ + --hash=sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c + # via twine +rich==13.8.1 \ + --hash=sha256:1760a3c0848469b97b558fc61c85233e3dafb69c7a071b4d60c38099d3cd4c06 \ + --hash=sha256:8260cda28e3db6bf04d2d1ef4dbc03ba80a824c88b0e7668a0f23126a424844a + # via + # cli-base-utilities + # cookiecutter + # dragonlib (pyproject.toml) + # manageprojects + # pip-audit + # rich-click + # twine +rich-click==1.8.3 \ + --hash=sha256:636d9c040d31c5eee242201b5bf4f2d358bfae4db14bb22ec1cafa717cfd02cd \ + --hash=sha256:6d75bdfa7aa9ed2c467789a0688bc6da23fbe3a143e19aa6ad3f8bac113d2ab3 \ + --hash=sha256:ef50d4087a5a4f1e7fcee7972f78775e9b33c0709a86dc10f9f5b929ba4d51b8 + # via + # cli-base-utilities + # dragonlib (pyproject.toml) + # manageprojects +secretstorage==3.3.3 \ + --hash=sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77 \ + --hash=sha256:cd7ed5cdeb3e5d7f18a7651500e0fec895205ae9c0abf7e5e63f6768d69418a2 \ + --hash=sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99 + # via keyring +six==1.16.0 \ + --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ + --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 + # via + # html5lib + # python-dateutil +sortedcontainers==2.4.0 \ + --hash=sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88 \ + --hash=sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0 + # via cyclonedx-python-lib +text-unidecode==1.3 \ + --hash=sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8 \ + --hash=sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93 + # via python-slugify +tokenize-rt==6.0.0 \ + --hash=sha256:719a8c00371c80a9b33b8d8a648c07eed2cbcad3d841981557c7683994b89e3a \ + --hash=sha256:b9711bdfc51210211137499b5e355d3de5ec88a85d2025c520cbb921b5194367 \ + --hash=sha256:d4ff7ded2873512938b4f8cbb98c9b07118f01d30ac585a30d7a88353ca36d22 + # via pyupgrade +toml==0.10.2 \ + --hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b \ + --hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f + # via + # darker + # darkgraylib + # pip-audit +tomli==2.0.1 \ + --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ + --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f + # via cli-base-utilities +tomlkit==0.13.2 \ + --hash=sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde \ + --hash=sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79 + # via + # cli-base-utilities + # manageprojects +tox==4.20.0 \ + --hash=sha256:21a8005e3d3fe5658a8e36b8ca3ed13a4230429063c5cc2a2fdac6ee5aa0de34 \ + --hash=sha256:5b78a49b6eaaeab3ae4186415e7c97d524f762ae967c63562687c3e5f0ec23d5 + # via dragonlib (pyproject.toml) +twine==5.1.1 \ + --hash=sha256:215dbe7b4b94c2c50a7315c0275d2258399280fbb7d04182c7e55e24b5f93997 \ + --hash=sha256:3c7f799b77e4bf2e6993bf820c6896e02983ed04bf072148da9fc8c07473fb58 \ + --hash=sha256:9aa0825139c02b3434d913545c7b847a21c835e11597f5255842d457da2322db + # via dragonlib (pyproject.toml) +typeguard==4.3.0 \ + --hash=sha256:4d24c5b39a117f8a895b9da7a9b3114f04eb63bade45a4492de49b175b6f7dfa \ + --hash=sha256:92ee6a0aec9135181eae6067ebd617fd9de8d75d714fb548728a4933b1dea651 \ + --hash=sha256:fea0fb5d99b2e2389f6b0b35e0098265c57a050fb4438718eb64a5a7ac08a67f + # via dragonlib (pyproject.toml) +types-python-dateutil==2.9.0.20240906 \ + --hash=sha256:27c8cc2d058ccb14946eebcaaa503088f4f6dbc4fb6093d3d456a49aef2753f6 \ + --hash=sha256:505e12a984040ecdd2e5b8c807128b224c219ea5fb943800ff3a3e3840f435d4 \ + --hash=sha256:9706c3b68284c25adffc47319ecc7947e5bb86b3773f843c73906fd598bc176e + # via arrow +typing-extensions==4.12.2 \ + --hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \ + --hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8 + # via + # mypy + # rich-click + # typeguard +urllib3==2.2.3 \ + --hash=sha256:53042b594bc77c4b5d43f598b9eca1333bf4f884c300473192f4d4f47bab1d2d \ + --hash=sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac \ + --hash=sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9 + # via + # dragonlib (pyproject.toml) + # requests + # twine +virtualenv==20.26.5 \ + --hash=sha256:4f3ac17b81fba3ce3bd6f4ead2749a72da5929c01774948e243db9ba41df4ff6 \ + --hash=sha256:ce489cac131aa58f4b25e321d6d186171f78e6cb13fafbf32a840cee67733ff4 + # via + # pre-commit + # tox +webencodings==0.5.1 \ + --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \ + --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923 + # via html5lib +wheel==0.44.0 \ + --hash=sha256:2376a90c98cc337d18623527a97c31797bd02bad0033d41547043a1cbfbe448f \ + --hash=sha256:a29c3f2817e95ab89aa4660681ad547c0e9547f20e75b0562fe7723c9a2a9d49 + # via pip-tools +zipp==3.20.2 \ + --hash=sha256:22a9c23c796186ac7e78301bb1771c074e1f568b91bbce8157bd705d53f7e600 \ + --hash=sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350 \ + --hash=sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29 + # via importlib-metadata + +# The following packages are considered to be unsafe in a requirements file: +pip==24.2 \ + --hash=sha256:2cd581cf58ab7fcfca4ce8efa6dcacd0de5bf8d0a3eb9ec927e07405f4d9e2a2 \ + --hash=sha256:5b5e490b5e9cb275c879595064adce9ebd31b854e3e803740b72f9ccf34a45b8 \ + --hash=sha256:9a022a9c8b0cf5b9fc444c3d62800163f3c5bad729b893faa1162bd234a2f5fb + # via + # pip-api + # pip-tools +setuptools==75.1.0 \ + --hash=sha256:35ab7fd3bcd95e6b7fd704e4a1539513edad446c097797f2985e0e4b960772f2 \ + --hash=sha256:8d32e2f72308958f2609687a4278931d414ce02ddce193c6d725808d6634b084 \ + --hash=sha256:d59a21b17a275fb872a9c3dae73963160ae079f1049ed956880cd7c09b120538 + # via pip-tools diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..18b5f9b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,86 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# ./dev-cli.py update +# +--extra-index-url https://www.piwheels.org/simple + +async-timeout==4.0.3 \ + --hash=sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f \ + --hash=sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028 \ + --hash=sha256:fd893e4cb1d9dff66d0e0576cc6376a23eacbbcd43b6607e4d3ecfae1ab15367 + # via cli-base-utilities +bx-py-utils==104 \ + --hash=sha256:508cfc1d0fa6c22298f697c4efaa913337847d488d8a53eeccfae9ee106123f6 \ + --hash=sha256:77fddff99aaf2cb558ce2974ef66d0e7a4d48cca1cbb99e43eca0d8b77fa07fc \ + --hash=sha256:c92ebc4fb122e3e3c228d984d0a1f5c3284c3da6aab1a1c753f7eb1f71bdab3a + # via + # cli-base-utilities + # dragonlib (pyproject.toml) +cli-base-utilities==0.11.0 \ + --hash=sha256:22c01bffece11781db0cba2110e9bf721361d50fb8a877bd251422ebaf3daee9 \ + --hash=sha256:2c674f3af4898f97d101f6687fb45e85a4b996b93fb9a155a29b4daaf302dc43 \ + --hash=sha256:fc503f8df7e19653167ebd12551718aa0c114114b959f177be2092eab1ce1b11 + # via dragonlib (pyproject.toml) +click==8.1.7 \ + --hash=sha256:8e38806544348fdafedd47e92e90aca882377a0680918dec4c80c225a0e5ed13 \ + --hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \ + --hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de + # via + # cli-base-utilities + # dragonlib (pyproject.toml) + # rich-click +markdown-it-py==3.0.0 \ + --hash=sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 \ + --hash=sha256:6d00561ef69282f31c923e5520f8f7025f6c2bbd1d7796f3d730840cc54130a6 \ + --hash=sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb + # via rich +mdurl==0.1.2 \ + --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ + --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba + # via markdown-it-py +packaging==24.1 \ + --hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \ + --hash=sha256:270c5ae91ef4184b318179b819b2e4420e8f41246d05334899bc9dfc30c80cac \ + --hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124 + # via cli-base-utilities +pygments==2.18.0 \ + --hash=sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199 \ + --hash=sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a + # via rich +python-dateutil==2.9.0.post0 \ + --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ + --hash=sha256:5d23f9d7e398684c7dac22b46a93fb71de568cfdcd004512f7072819e41ef54c \ + --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 + # via cli-base-utilities +rich==13.8.1 \ + --hash=sha256:1760a3c0848469b97b558fc61c85233e3dafb69c7a071b4d60c38099d3cd4c06 \ + --hash=sha256:8260cda28e3db6bf04d2d1ef4dbc03ba80a824c88b0e7668a0f23126a424844a + # via + # cli-base-utilities + # dragonlib (pyproject.toml) + # rich-click +rich-click==1.8.3 \ + --hash=sha256:636d9c040d31c5eee242201b5bf4f2d358bfae4db14bb22ec1cafa717cfd02cd \ + --hash=sha256:6d75bdfa7aa9ed2c467789a0688bc6da23fbe3a143e19aa6ad3f8bac113d2ab3 \ + --hash=sha256:ef50d4087a5a4f1e7fcee7972f78775e9b33c0709a86dc10f9f5b929ba4d51b8 + # via + # cli-base-utilities + # dragonlib (pyproject.toml) +six==1.16.0 \ + --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ + --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 + # via python-dateutil +tomli==2.0.1 \ + --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ + --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f + # via cli-base-utilities +tomlkit==0.13.2 \ + --hash=sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde \ + --hash=sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79 + # via cli-base-utilities +typing-extensions==4.12.2 \ + --hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \ + --hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8 + # via rich-click diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 4bd57ea..0000000 --- a/setup.cfg +++ /dev/null @@ -1,16 +0,0 @@ -[flake8] -ignore=E115,E124,E128,E265,E301,E309,E501 -max-line-length = 119 - -[metadata] -license-file = LICENSE - -[bdist_wheel] -universal = 1 - -[nosetests] -verbose=True -verbosity=2 -detailed-errors=1 -with-doctest=1 -nocapture=1 \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100755 index dad0201..0000000 --- a/setup.py +++ /dev/null @@ -1,241 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -""" - distutils setup - ~~~~~~~~~~~~~~~ - - :copyleft: 2014-2015 by the DragonLib team, see AUTHORS for more details. - :license: GNU GPL v3 or above, see LICENSE for more details. -""" - -from __future__ import absolute_import, division, print_function - -from setuptools import setup, find_packages -import os -import sys -import subprocess -import shutil - -from dragonlib import __version__ - - -PACKAGE_ROOT = os.path.dirname(os.path.abspath(__file__)) - - -# convert creole to ReSt on-the-fly, see also: -# https://code.google.com/p/python-creole/wiki/UseInSetup -try: - from creole.setup_utils import get_long_description -except ImportError as err: - if "check" in sys.argv or "register" in sys.argv or "sdist" in sys.argv or "--long-description" in sys.argv: - raise ImportError("%s - Please install python-creole >= v0.8 - e.g.: pip install python-creole" % err) - long_description = None -else: - long_description = get_long_description(PACKAGE_ROOT) - - -if "publish" in sys.argv: - """ - 'publish' helper for setup.py - - Build and upload to PyPi, if... - ... __version__ doesn't contains "dev" - ... we are on git 'master' branch - ... git repository is 'clean' (no changed files) - - Upload with "twine", git tag the current version and git push --tag - - The cli arguments will be pass to 'twine'. So this is possible: - * Display 'twine' help page...: ./setup.py publish --help - * use testpypi................: ./setup.py publish --repository=test - - TODO: Look at: https://github.com/zestsoftware/zest.releaser - - Source: https://github.com/jedie/python-code-snippets/blob/master/CodeSnippets/setup_publish.py - copyleft 2015 Jens Diemer - GNU GPL v2+ - """ - if sys.version_info[0] == 2: - input = raw_input - - try: - # Test if wheel is installed, otherwise the user will only see: - # error: invalid command 'bdist_wheel' - import wheel - except ImportError as err: - print("\nError: %s" % err) - print("\nMaybe https://pypi.python.org/pypi/wheel is not installed or virtualenv not activated?!?") - print("e.g.:") - print(" ~/your/env/$ source bin/activate") - print(" ~/your/env/$ pip install wheel") - sys.exit(-1) - - try: - import twine - except ImportError as err: - print("\nError: %s" % err) - print("\nMaybe https://pypi.python.org/pypi/twine is not installed or virtualenv not activated?!?") - print("e.g.:") - print(" ~/your/env/$ source bin/activate") - print(" ~/your/env/$ pip install twine") - sys.exit(-1) - - def verbose_check_output(*args): - """ 'verbose' version of subprocess.check_output() """ - call_info = "Call: %r" % " ".join(args) - try: - output = subprocess.check_output(args, universal_newlines=True, stderr=subprocess.STDOUT) - except subprocess.CalledProcessError as err: - print("\n***ERROR:") - print(err.output) - raise - return call_info, output - - def verbose_check_call(*args): - """ 'verbose' version of subprocess.check_call() """ - print("\tCall: %r\n" % " ".join(args)) - subprocess.check_call(args, universal_newlines=True) - - if "dev" in __version__: - print("\nERROR: Version contains 'dev': v%s\n" % __version__) - sys.exit(-1) - - print("\nCheck if we are on 'master' branch:") - call_info, output = verbose_check_output("git", "branch", "--no-color") - print("\t%s" % call_info) - if "* master" in output: - print("OK") - else: - print("\nNOTE: It seems you are not on 'master':") - print(output) - if input("\nPublish anyhow? (Y/N)").lower() not in ("y", "j"): - print("Bye.") - sys.exit(-1) - - print("\ncheck if if git repro is clean:") - call_info, output = verbose_check_output("git", "status", "--porcelain") - print("\t%s" % call_info) - if output == "": - print("OK") - else: - print("\n *** ERROR: git repro not clean:") - print(output) - sys.exit(-1) - - print("\ncheck if pull is needed") - verbose_check_call("git", "fetch", "--all") - call_info, output = verbose_check_output("git", "log", "HEAD..origin/master", "--oneline") - print("\t%s" % call_info) - if output == "": - print("OK") - else: - print("\n *** ERROR: git repro is not up-to-date:") - print(output) - sys.exit(-1) - verbose_check_call("git", "push") - - print("\nCleanup old builds:") - def rmtree(path): - path = os.path.abspath(path) - if os.path.isdir(path): - print("\tremove tree:", path) - shutil.rmtree(path) - rmtree("./dist") - rmtree("./build") - - print("\nbuild but don't upload...") - log_filename="build.log" - with open(log_filename, "a") as log: - call_info, output = verbose_check_output( - sys.executable or "python", - "setup.py", "sdist", "bdist_wheel", "bdist_egg" - ) - print("\t%s" % call_info) - log.write(call_info) - log.write(output) - print("Build output is in log file: %r" % log_filename) - - print("\ngit tag version (will raise a error of tag already exists)") - verbose_check_call("git", "tag", "v%s" % __version__) - - print("\nUpload with twine:") - twine_args = sys.argv[1:] - twine_args.remove("publish") - twine_args.insert(1, "dist/*") - print("\ttwine upload command args: %r" % " ".join(twine_args)) - from twine.commands.upload import main as twine_upload - twine_upload(twine_args) - - print("\ngit push tag to server") - verbose_check_call("git", "push", "--tags") - - sys.exit(0) - - -if "test" in sys.argv or "nosetests" in sys.argv: - """ - nose is a optional dependency, so test import. - Otherwise the user get only the error: - error: invalid command 'nosetests' - """ - try: - import nose - except ImportError as err: - print("\nError: Can't import 'nose': %s" % err) - print("\nMaybe 'nose' is not installed or virtualenv not activated?!?") - print("e.g.:") - print(" ~/your/env/$ source bin/activate") - print(" ~/your/env/$ pip install nose") - print(" ~/your/env/$ ./setup.py nosetests\n") - sys.exit(-1) - else: - if "test" in sys.argv: - print("\nPlease use 'nosetests' instead of 'test' to cover all tests!\n") - print("e.g.:") - print(" $ ./setup.py nosetests\n") - sys.exit(-1) - - -setup( - name="dragonlib", - version=__version__, - py_modules=["dragonlib"], - provides=["dragonlib"], - install_requires=[ - "six", - ], - tests_require=[ - "nose", # https://pypi.python.org/pypi/nose - "pygments", - ], - author="Jens Diemer", - author_email="dragonlib@jensdiemer.de", - description="Library around 6809 computers like Dragon 32/64, CoCo...", - keywords="6809 Dragon CoCo", - long_description=long_description, - url="https://github.com/6809/dragonlib", - license="GPL v3+", - classifiers=[ - # https://pypi.python.org/pypi?%3Aaction=list_classifiers - "Development Status :: 4 - Beta", - "Environment :: MacOS X", - "Environment :: Win32 (MS Windows)", - "Environment :: X11 Applications", - "Environment :: Other Environment", - "Intended Audience :: Developers", - "Intended Audience :: Education", - "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", - "Operating System :: OS Independent", - "Programming Language :: Python", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: Implementation :: CPython", - "Programming Language :: Python :: Implementation :: PyPy", - "Topic :: System :: Emulators", - "Topic :: Software Development :: Assemblers", - "Topic :: Software Development :: Testing", - ], - packages=find_packages(), - include_package_data=True, - zip_safe=False, -) From d7da494dc76c0f3e64fe5cb40c946566cd21700e Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 24 Sep 2024 21:27:39 +0200 Subject: [PATCH 2/4] Run pyupgrade --- dragonlib/CoCo/basic_tokens.py | 3 -- dragonlib/api.py | 8 ++-- dragonlib/core/basic.py | 54 ++++++++++++--------------- dragonlib/core/basic_parser.py | 12 +++--- dragonlib/core/binary_files.py | 4 +- dragonlib/dragon32/basic_tokens.py | 3 -- dragonlib/dragon32/pygments_lexer.py | 3 -- dragonlib/tests/test_api.py | 4 +- dragonlib/tests/test_base.py | 8 ++-- dragonlib/tests/test_basic_parser.py | 2 - dragonlib/tests/test_binary_format.py | 2 - dragonlib/tests/test_highlighting.py | 2 - dragonlib/tests/testdata.py | 3 -- dragonlib/utils/auto_shift.py | 3 -- dragonlib/utils/byte_word_values.py | 5 +-- dragonlib/utils/iter_utils.py | 7 +--- dragonlib/utils/logging_utils.py | 8 ++-- 17 files changed, 42 insertions(+), 89 deletions(-) diff --git a/dragonlib/CoCo/basic_tokens.py b/dragonlib/CoCo/basic_tokens.py index ea79f4d..b08f275 100644 --- a/dragonlib/CoCo/basic_tokens.py +++ b/dragonlib/CoCo/basic_tokens.py @@ -1,5 +1,3 @@ -# encoding:utf8 - """ DragonPy - Dragon 32 emulator in Python ======================================= @@ -17,7 +15,6 @@ :license: GNU GPL v3 or above, see LICENSE for more details. """ -from __future__ import absolute_import, division, print_function diff --git a/dragonlib/api.py b/dragonlib/api.py index 70deeb5..5e3fb95 100644 --- a/dragonlib/api.py +++ b/dragonlib/api.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# encoding:utf8 """ DragonPy - Dragon 32 emulator in Python @@ -10,7 +9,6 @@ :license: GNU GPL v3 or above, see LICENSE for more details. """ -from __future__ import absolute_import, division, print_function import six import logging @@ -30,7 +28,7 @@ COCO2B = "CoCo" -class BaseAPI(object): +class BaseAPI: RENUM_REGEX = r""" (?P GOTO|GOSUB|THEN|ELSE ) (?P\s*) (?P[\d*,\s*]+) """ @@ -56,7 +54,7 @@ def parse_ascii_listing(self, basic_program_ascii): parser = BASICParser() parsed_lines = parser.parse(basic_program_ascii) if not parsed_lines: - log.critical("No parsed lines %s from %s ?!?" % ( + log.critical("No parsed lines {} from {} ?!?".format( repr(parsed_lines), repr(basic_program_ascii) )) log.debug("Parsed BASIC: %s", repr(parsed_lines)) @@ -86,7 +84,7 @@ def ascii_listing2program_dump(self, basic_program_ascii, program_start=None): program_dump=self.listing.basic_lines2program_dump(basic_lines, program_start) assert isinstance(program_dump, bytearray), ( - "is type: %s and not bytearray: %s" % (type(program_dump), repr(program_dump)) + "is type: {} and not bytearray: {}".format(type(program_dump), repr(program_dump)) ) return program_dump diff --git a/dragonlib/core/basic.py b/dragonlib/core/basic.py index e687a69..8380994 100644 --- a/dragonlib/core/basic.py +++ b/dragonlib/core/basic.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# encoding:utf8 """ DragonPy - Dragon 32 emulator in Python @@ -10,7 +9,6 @@ :license: GNU GPL v3 or above, see LICENSE for more details. """ -from __future__ import absolute_import, division, print_function import logging import re @@ -26,13 +24,13 @@ log = logging.getLogger(__name__) -class BasicTokenUtil(object): +class BasicTokenUtil: def __init__(self, basic_token_dict): self.basic_token_dict = basic_token_dict - self.ascii2token_dict = dict([ - (code, token) + self.ascii2token_dict = { + code: token for token, code in list(basic_token_dict.items()) - ]) + } regex = r"(%s)" % "|".join([ re.escape(statement) @@ -48,12 +46,7 @@ def token2ascii(self, value): log.info("ERROR: Token $%04x is not in BASIC_TOKENS!", value) return "" result = chr(value) - if six.PY2: - # Only for unittest, to avoid token representation as u"..." - # There is only ASCII characters possible - return str(result) - else: - return result + return result def tokens2ascii(self, values): line="" @@ -142,14 +135,14 @@ def pformat_tokens(self, tokens): for token_value in self.iter_token_values(tokens): char = self.token2ascii(token_value) if token_value > 0xff: - result.append("\t$%04x -> %s" % (token_value, repr(char))) + result.append("\t${:04x} -> {}".format(token_value, repr(char))) else: - result.append("\t $%02x -> %s" % (token_value, repr(char))) + result.append("\t ${:02x} -> {}".format(token_value, repr(char))) return result -class BasicLine(object): +class BasicLine: def __init__(self, token_util): self.token_util = token_util self.line_number = None @@ -168,7 +161,7 @@ def __init__(self, token_util): def token_load(self, line_number, tokens): self.line_number = line_number - assert tokens[-1] == 0x00, "line code %s doesn't ends with \\x00: %s" % ( + assert tokens[-1] == 0x00, "line code {} doesn't ends with \\x00: {}".format( repr(tokens), repr(tokens[-1]) ) @@ -195,7 +188,7 @@ def ascii_load(self, line_ascii): try: line_number, ascii_code = line_ascii.split(" ", 1) except ValueError as err: - msg = "Error split line number and code in line: %r (Origin error: %s)" % ( + msg = "Error split line number and code in line: {!r} (Origin error: {})".format( line_ascii, err ) raise ValueError(msg) @@ -274,7 +267,7 @@ def get_content(self, code=None): return line def __repr__(self): - return "%r: %s" % (self.get_content(), " ".join(["$%02x" % t for t in self.line_code])) + return "{!r}: {}".format(self.get_content(), " ".join(["$%02x" % t for t in self.line_code])) def log_line(self): log.critical("%r:\n\t%s", @@ -283,7 +276,7 @@ def log_line(self): ) -class BasicListing(object): +class BasicListing: def __init__(self, basic_token_dict): self.token_util = BasicTokenUtil(basic_token_dict) @@ -304,7 +297,7 @@ def dump2basic_lines(self, dump, program_start, basic_lines=None): log.debug("return: %s", repr(basic_lines)) return basic_lines - assert next_address > program_start, "Next address $%04x not bigger than program start $%04x ?!?" % ( + assert next_address > program_start, "Next address ${:04x} not bigger than program start ${:04x} ?!?".format( next_address, program_start ) @@ -370,7 +363,7 @@ def pformat_program_dump(self, program_dump, program_start, formated_dump=None): next_address = (program_dump[0] << 8) + program_dump[1] except IndexError as err: raise IndexError( - "Can't get next address from: %s program start: $%04x (Origin error: %s)" % ( + "Can't get next address from: {} program start: ${:04x} (Origin error: {})".format( repr(program_dump), program_start, err )) @@ -378,7 +371,7 @@ def pformat_program_dump(self, program_dump, program_start, formated_dump=None): formated_dump.append("$%04x -> end address" % next_address) return formated_dump - assert next_address > program_start, "Next address $%04x not bigger than program start $%04x ?!?" % ( + assert next_address > program_start, "Next address ${:04x} not bigger than program start ${:04x} ?!?".format( next_address, program_start ) @@ -423,26 +416,26 @@ def program_dump2ascii_lines(self, dump, program_start): return ascii_lines -class RenumTool(object): +class RenumTool: """ Renumber a BASIC program """ def __init__(self, renum_regex): - self.line_no_regex = re.compile("(?P\d+)(?P.+)") + self.line_no_regex = re.compile(r"(?P\d+)(?P.+)") self.renum_regex = re.compile(renum_regex, re.VERBOSE) def renum(self, ascii_listing): self.renum_dict = self.create_renum_dict(ascii_listing) log.info("renum: %s", ", ".join([ - "%s->%s" % (o, n) + "{}->{}".format(o, n) for o, n in sorted(self.renum_dict.items()) ]) ) new_listing = [] for new_number, line in enumerate(self._iter_lines(ascii_listing), 1): new_number *= 10 - line = self.line_no_regex.sub("%s\g" % new_number, line) + line = self.line_no_regex.sub(r"%s\g" % new_number, line) new_line = self.renum_regex.sub(self.renum_inline, line) log.debug("%r -> %r", line, new_line) new_listing.append(new_line) @@ -456,9 +449,9 @@ def get_destinations(self, ascii_listing): def collect_destinations(matchobj): numbers = matchobj.group("no") if numbers: - self.destinations.update(set( - [n.strip() for n in numbers.split(",")] - )) + self.destinations.update({ + n.strip() for n in numbers.split(",") + }) for line in self._iter_lines(ascii_listing): self.renum_regex.sub(collect_destinations, line) @@ -468,8 +461,7 @@ def collect_destinations(matchobj): def _iter_lines(self, ascii_listing): lines = ascii_listing.splitlines() lines = [line.strip() for line in lines if line.strip()] - for line in lines: - yield line + yield from lines def _get_new_line_number(self, line, old_number): try: diff --git a/dragonlib/core/basic_parser.py b/dragonlib/core/basic_parser.py index 0c9d3b8..671d5b8 100644 --- a/dragonlib/core/basic_parser.py +++ b/dragonlib/core/basic_parser.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# encoding:utf8 """ BASIC parser @@ -18,7 +17,6 @@ :license: GNU GPL v3 or above, see LICENSE for more details. """ -from __future__ import absolute_import, division, print_function import re @@ -34,12 +32,12 @@ CODE_TYPE_COMMENT = "COMMENT" -class BaseCode(object): +class BaseCode: def __init__(self, content): self.content = content def __repr__(self): - return "<%s:%s>" % (self.PART_TYPE, self.content) + return "<{}:{}>".format(self.PART_TYPE, self.content) class BASIC_Code(BaseCode): @@ -72,7 +70,7 @@ def pformat(self): for line_no, code_objects in sorted(self.items()): result += '%s%i: [\n' % (indent1, line_no) for code_object in code_objects: - result += '%s"""<%s:%s>""",\n' % ( + result += '{}"""<{}:{}>""",\n'.format( indent2, code_object.PART_TYPE, code_object.content ) result += '%s],\n' % indent1 @@ -84,7 +82,7 @@ def __repr__(self): return self.pformat() -class BASICParser(object): +class BASICParser: """ Split BASIC sourcecode into: * line number @@ -96,7 +94,7 @@ class BASICParser(object): def __init__(self): self.regex_line_no = re.compile( # Split the line number from the code - "^\s*(?P\d+)\s?(?P.+)\s*$", + r"^\s*(?P\d+)\s?(?P.+)\s*$", re.MULTILINE ) self.regex_split_all = re.compile( diff --git a/dragonlib/core/binary_files.py b/dragonlib/core/binary_files.py index 826e737..080a31b 100644 --- a/dragonlib/core/binary_files.py +++ b/dragonlib/core/binary_files.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# encoding:utf8 """ dragonlib @@ -10,7 +9,6 @@ :license: GNU GPL v3 or above, see LICENSE for more details. """ -from __future__ import absolute_import, division, print_function import logging import struct @@ -23,7 +21,7 @@ log = logging.getLogger(__name__) -class BinaryFile(object): +class BinaryFile: def __init__(self): self.file_type = None # $01 == BAS | $02 == BIN self.load_address = None diff --git a/dragonlib/dragon32/basic_tokens.py b/dragonlib/dragon32/basic_tokens.py index 06f48d8..2470119 100644 --- a/dragonlib/dragon32/basic_tokens.py +++ b/dragonlib/dragon32/basic_tokens.py @@ -1,5 +1,3 @@ -# encoding:utf8 - """ DragonPy - Dragon 32 emulator in Python ======================================= @@ -9,7 +7,6 @@ :license: GNU GPL v3 or above, see LICENSE for more details. """ -from __future__ import absolute_import, division, print_function DRAGON32_BASIC_TOKENS = { diff --git a/dragonlib/dragon32/pygments_lexer.py b/dragonlib/dragon32/pygments_lexer.py index 8c859cb..64de91e 100644 --- a/dragonlib/dragon32/pygments_lexer.py +++ b/dragonlib/dragon32/pygments_lexer.py @@ -1,5 +1,3 @@ -# encoding:utf8 - """ DragonLib - needful python modules for Dragon/CoCo stuff ======================================================== @@ -9,7 +7,6 @@ :license: GNU GPL v3 or above, see LICENSE for more details. """ -from __future__ import absolute_import, division, print_function import re from collections import namedtuple diff --git a/dragonlib/tests/test_api.py b/dragonlib/tests/test_api.py index e4b085c..c7517a8 100644 --- a/dragonlib/tests/test_api.py +++ b/dragonlib/tests/test_api.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# encoding:utf-8 """ Dragon Lib unittests @@ -10,7 +9,6 @@ :license: GNU GPL v3 or above, see LICENSE for more details. """ -from __future__ import absolute_import, division, print_function import os import textwrap @@ -133,7 +131,7 @@ def _prepare_text(self, txt): class Dragon32BASIC_LowLevel_ApiTest(BaseDragon32ApiTestCase): def setUp(self): - super(Dragon32BASIC_LowLevel_ApiTest, self).setUp() + super().setUp() self.token_util = self.dragon32api.listing.token_util self.basic_line = BasicLine(self.token_util) diff --git a/dragonlib/tests/test_base.py b/dragonlib/tests/test_base.py index f80405a..0d0d625 100644 --- a/dragonlib/tests/test_base.py +++ b/dragonlib/tests/test_base.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# encoding:utf-8 """ Dragon Lib unittests @@ -10,7 +9,6 @@ :license: GNU GPL v3 or above, see LICENSE for more details. """ -from __future__ import absolute_import, division, print_function import textwrap import unittest @@ -32,7 +30,7 @@ def assertEqualHex(self, hex1, hex2, msg=None): first = "$%x" % hex1 second = "$%x" % hex2 if msg is None: - msg = "%s != %s" % (first, second) + msg = "{} != {}".format(first, second) self.assertEqual(first, second, msg) def assertIsByteRange(self, value): @@ -49,7 +47,7 @@ def assertEqualHexByte(self, hex1, hex2, msg=None): first = "$%02x" % hex1 second = "$%02x" % hex2 if msg is None: - msg = "%s != %s" % (first, second) + msg = "{} != {}".format(first, second) self.assertEqual(first, second, msg) def assertEqualHexWord(self, hex1, hex2, msg=None): @@ -58,7 +56,7 @@ def assertEqualHexWord(self, hex1, hex2, msg=None): first = "$%04x" % hex1 second = "$%04x" % hex2 if msg is None: - msg = "%s != %s" % (first, second) + msg = "{} != {}".format(first, second) self.assertEqual(first, second, msg) def assertBinEqual(self, bin1, bin2, msg=None, width=16): diff --git a/dragonlib/tests/test_basic_parser.py b/dragonlib/tests/test_basic_parser.py index b21cd9f..1567276 100644 --- a/dragonlib/tests/test_basic_parser.py +++ b/dragonlib/tests/test_basic_parser.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# encoding:utf8 """ unittests for BASIC parser @@ -10,7 +9,6 @@ :license: GNU GPL v3 or above, see LICENSE for more details. """ -from __future__ import absolute_import, division, print_function import logging diff --git a/dragonlib/tests/test_binary_format.py b/dragonlib/tests/test_binary_format.py index f5eaf09..7eae9c7 100644 --- a/dragonlib/tests/test_binary_format.py +++ b/dragonlib/tests/test_binary_format.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# encoding:utf8 """ unittests for BASIC parser @@ -10,7 +9,6 @@ :license: GNU GPL v3 or above, see LICENSE for more details. """ -from __future__ import absolute_import, division, print_function import logging import sys diff --git a/dragonlib/tests/test_highlighting.py b/dragonlib/tests/test_highlighting.py index 9427ded..e712f59 100644 --- a/dragonlib/tests/test_highlighting.py +++ b/dragonlib/tests/test_highlighting.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# encoding:utf8 """ unittests @@ -10,7 +9,6 @@ :license: GNU GPL v3 or above, see LICENSE for more details. """ -from __future__ import absolute_import, division, print_function import unittest from pygments.token import Token diff --git a/dragonlib/tests/testdata.py b/dragonlib/tests/testdata.py index 5572196..aaf2bae 100644 --- a/dragonlib/tests/testdata.py +++ b/dragonlib/tests/testdata.py @@ -1,5 +1,3 @@ -# encoding:utf8 - """ DragonLib unittests =================== @@ -11,7 +9,6 @@ :license: GNU GPL v3 or above, see LICENSE for more details. """ -from __future__ import absolute_import, division, print_function LISTING_01= ('10 PRINT',) LISTING_01_BIN = ( # program address: $1234 ! diff --git a/dragonlib/utils/auto_shift.py b/dragonlib/utils/auto_shift.py index 9ef2e7d..2f60206 100644 --- a/dragonlib/utils/auto_shift.py +++ b/dragonlib/utils/auto_shift.py @@ -1,5 +1,3 @@ -# encoding:utf8 - """ DragonPy - Dragon 32 emulator in Python ======================================= @@ -9,7 +7,6 @@ :license: GNU GPL v3 or above, see LICENSE for more details. """ -from __future__ import absolute_import, division, print_function import string diff --git a/dragonlib/utils/byte_word_values.py b/dragonlib/utils/byte_word_values.py index 0323953..07302af 100644 --- a/dragonlib/utils/byte_word_values.py +++ b/dragonlib/utils/byte_word_values.py @@ -11,7 +11,6 @@ :license: GNU GPL v3 or above, see LICENSE for more details. """ -from __future__ import absolute_import, division, print_function import string @@ -86,7 +85,7 @@ def bin2hexline(data, add_addr=True, width=16): 0032 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f !"#$%&'()*+,-./ 0048 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 0123456789:;<=>? 0064 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f @ABCDEFGHIJKLMNO - 0080 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f PQRSTUVWXYZ[\]^_ + 0080 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f PQRSTUVWXYZ[\\]^_ 0096 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f `abcdefghijklmno 0112 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f pqrstuvwxyz{|}~. 0128 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f ................ @@ -99,7 +98,7 @@ def bin2hexline(data, add_addr=True, width=16): 0240 f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff ................ - with open("C:\Python27\python.exe", "rb") as f: + with open("C:\\Python27\\python.exe", "rb") as f: data = f.read(150) print("\n".join(bin2hexline(data, width=16))) diff --git a/dragonlib/utils/iter_utils.py b/dragonlib/utils/iter_utils.py index 357d8a5..0f15950 100644 --- a/dragonlib/utils/iter_utils.py +++ b/dragonlib/utils/iter_utils.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# encoding:utf-8 """ iter utilities @@ -9,16 +8,12 @@ :license: GNU GPL v3 or above, see LICENSE for more details. """ -from __future__ import absolute_import, division, print_function import six # Obsolete if http://legacy.python.org/dev/peps/pep-0467/ merged: -if six.PY2: - iter_bytes = lambda data: (ord(b) for b in data) -else: - iter_bytes = iter +iter_bytes = iter def list_replace(iterable, src, dst): diff --git a/dragonlib/utils/logging_utils.py b/dragonlib/utils/logging_utils.py index e89b328..c605e36 100755 --- a/dragonlib/utils/logging_utils.py +++ b/dragonlib/utils/logging_utils.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# encoding:utf-8 """ loggin utilities @@ -10,11 +9,10 @@ :license: GNU GPL v3 or above, see LICENSE for more details. """ -from __future__ import absolute_import, division, print_function import six from dragonlib.utils.byte_word_values import bin2hexline -xrange = six.moves.xrange +xrange = range import logging import sys @@ -108,7 +106,7 @@ def log_memory_dump(memory, start, end, mem_info, level=99): if isinstance(value, int): msg = "$%04x: $%02x (dez: %i)" % (addr, value, value) else: - msg = "$%04x: %s (is type: %s)" % (addr, repr(value), type(value)) + msg = "${:04x}: {} (is type: {})".format(addr, repr(value), type(value)) msg = "%-25s| %s" % ( msg, mem_info.get_shortest(addr) ) @@ -127,7 +125,7 @@ def pformat_word_hex_list(hex_list): def log_hexlist(byte_list, group=8, start=0x0000, level=99): def _log(level, addr, line): msg = pformat_byte_hex_list(line) - msg = "%04x - %s" % (addr, msg) + msg = "{:04x} - {}".format(addr, msg) log.log(level, msg) pos = 0 From 7fd1d10d39f2cdf4b5518b5b8a9454315b1d7dea Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 24 Sep 2024 21:29:21 +0200 Subject: [PATCH 3/4] Add .editorconfig --- .editorconfig | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d6113ce --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +# see https://editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.py] +max_line_length = 119 + +[{Makefile,**.mk}] +indent_style = tab +insert_final_newline = false + +[{*.yaml,*.yml}] +indent_size = 2 From e3fa631cfabfa6f9360398501946c9f136129d99 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 24 Sep 2024 21:47:50 +0200 Subject: [PATCH 4/4] Reformat with black and fix old code parts --- .flake8 | 7 + dragonlib/CoCo/basic_tokens.py | 215 ++++--- dragonlib/__main__.py | 1 - dragonlib/api.py | 73 ++- dragonlib/cli_app/__init__.py | 5 +- dragonlib/cli_app/update_readme_history.py | 1 + dragonlib/cli_dev/__init__.py | 7 +- dragonlib/cli_dev/code_style.py | 1 + dragonlib/cli_dev/testing.py | 1 + dragonlib/core/basic.py | 156 ++--- dragonlib/core/basic_parser.py | 33 +- dragonlib/core/binary_files.py | 22 +- dragonlib/dragon32/basic_tokens.py | 232 ++++---- dragonlib/dragon32/pygments_lexer.py | 28 +- dragonlib/tests/__init__.py | 1 - dragonlib/tests/test_api.py | 643 +++++++++++++-------- dragonlib/tests/test_base.py | 11 +- dragonlib/tests/test_basic_parser.py | 65 ++- dragonlib/tests/test_binary_format.py | 19 +- dragonlib/tests/test_highlighting.py | 19 +- dragonlib/tests/test_project_setup.py | 2 +- dragonlib/tests/testdata.py | 56 +- dragonlib/utils/auto_shift.py | 16 +- dragonlib/utils/byte_word_values.py | 36 +- dragonlib/utils/iter_utils.py | 46 +- dragonlib/utils/logging_utils.py | 159 +++-- 26 files changed, 1012 insertions(+), 843 deletions(-) create mode 100644 .flake8 diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..3bc9b4b --- /dev/null +++ b/.flake8 @@ -0,0 +1,7 @@ +# +# Move to pyproject.toml after: https://github.com/PyCQA/flake8/issues/234 +# +[flake8] +exclude = .*, dist, htmlcov +#ignore = E402 +max-line-length = 119 diff --git a/dragonlib/CoCo/basic_tokens.py b/dragonlib/CoCo/basic_tokens.py index b08f275..0807da8 100644 --- a/dragonlib/CoCo/basic_tokens.py +++ b/dragonlib/CoCo/basic_tokens.py @@ -2,11 +2,11 @@ DragonPy - Dragon 32 emulator in Python ======================================= - informations from: - + information from: + * Color BASIC 1.3: http://sourceforge.net/p/toolshed/code/ci/default/tree/cocoroms/bas.asm - + * Extended Color BASIC 1.1: http://sourceforge.net/p/toolshed/code/ci/default/tree/cocoroms/extbas.asm @@ -15,9 +15,6 @@ :license: GNU GPL v3 or above, see LICENSE for more details. """ - - - # Revesed word tokens from Color BASIC 1.3: COCO_COLOR_BASIC_TOKENS = { 0x80: "FOR", @@ -30,12 +27,12 @@ 0x87: "PRINT", 0x88: "ON", 0x89: "INPUT", - 0x8a: "END", - 0x8b: "NEXT", - 0x8c: "DIM", - 0x8d: "READ", - 0x8e: "RUN", - 0x8f: "RESTORE", + 0x8A: "END", + 0x8B: "NEXT", + 0x8C: "DIM", + 0x8D: "READ", + 0x8E: "RUN", + 0x8F: "RESTORE", 0x90: "RETURN", 0x91: "STOP", 0x92: "POKE", @@ -46,102 +43,102 @@ 0x97: "CLOAD", 0x98: "CSAVE", 0x99: "OPEN", - 0x9a: "CLOSE", - 0x9b: "LLIST", - 0x9c: "SET", - 0x9d: "RESET", - 0x9e: "CLS", - 0x9f: "MOTOR", - 0xa0: "SOUND", - 0xa1: "AUDIO", - 0xa2: "EXEC", - 0xa3: "SKIPF", - 0xa4: "TAB(", - 0xa5: "TO", - 0xa6: "SUB", - 0xa7: "THEN", - 0xa8: "NOT", - 0xa9: "STEP", - 0xaa: "OFF", - 0xab: "+", - 0xac: "-", - 0xad: "*", - 0xae: "/", - 0xaf: "^", - 0xb0: "AND", - 0xb1: "OR", - 0xb2: ">", - 0xb3: "=", - 0xb4: "<", - + 0x9A: "CLOSE", + 0x9B: "LLIST", + 0x9C: "SET", + 0x9D: "RESET", + 0x9E: "CLS", + 0x9F: "MOTOR", + 0xA0: "SOUND", + 0xA1: "AUDIO", + 0xA2: "EXEC", + 0xA3: "SKIPF", + 0xA4: "TAB(", + 0xA5: "TO", + 0xA6: "SUB", + 0xA7: "THEN", + 0xA8: "NOT", + 0xA9: "STEP", + 0xAA: "OFF", + 0xAB: "+", + 0xAC: "-", + 0xAD: "*", + 0xAE: "/", + 0xAF: "^", + 0xB0: "AND", + 0xB1: "OR", + 0xB2: ">", + 0xB3: "=", + 0xB4: "<", + # # Function tokens - all proceeded by 0xff to differentiate from operators - - 0xff80: "SGN", - 0xff81: "INT", - 0xff82: "ABS", - 0xff83: "USR", - 0xff84: "RND", - 0xff85: "SIN", - 0xff86: "PEEK", - 0xff87: "LEN", - 0xff88: "STR$", - 0xff89: "VAL", - 0xff8a: "ASC", - 0xff8b: "CHR$", - 0xff8c: "EOF", - 0xff8d: "JOYSTK", - 0xff8e: "LEFT$", - 0xff8f: "RIGHT$", - 0xff90: "MID$", - 0xff91: "POINT", - 0xff92: "INKEY$", - 0xff93: "MEM", + # + 0xFF80: "SGN", + 0xFF81: "INT", + 0xFF82: "ABS", + 0xFF83: "USR", + 0xFF84: "RND", + 0xFF85: "SIN", + 0xFF86: "PEEK", + 0xFF87: "LEN", + 0xFF88: "STR$", + 0xFF89: "VAL", + 0xFF8A: "ASC", + 0xFF8B: "CHR$", + 0xFF8C: "EOF", + 0xFF8D: "JOYSTK", + 0xFF8E: "LEFT$", + 0xFF8F: "RIGHT$", + 0xFF90: "MID$", + 0xFF91: "POINT", + 0xFF92: "INKEY$", + 0xFF93: "MEM", } - + # Revesed word tokens from Extended Color BASIC 1.1: COCO_EXTENDED_COLOR_BASIC_TOKENS = { - 0xb5: "DEL", - 0xb6: "EDIT", - 0xb7: "TRON", - 0xb8: "TROFF", - 0xb9: "DEF", - 0xba: "LET", - 0xbb: "LINE", - 0xbc: "PCLS", - 0xbd: "PSET", - 0xbe: "PRESET", - 0xbf: "SCREEN", - 0xc0: "PCLEAR", - 0xc1: "COLOR", - 0xc2: "CIRCLE", - 0xc3: "PAINT", - 0xc4: "GET", - 0xc5: "PUT", - 0xc6: "DRAW", - 0xc7: "PCOPY", - 0xc8: "PMODE", - 0xc9: "PLAY", - 0xca: "DLOAD", - 0xcb: "RENUM", - 0xcc: "FN", - 0xcd: "USING", - + 0xB5: "DEL", + 0xB6: "EDIT", + 0xB7: "TRON", + 0xB8: "TROFF", + 0xB9: "DEF", + 0xBA: "LET", + 0xBB: "LINE", + 0xBC: "PCLS", + 0xBD: "PSET", + 0xBE: "PRESET", + 0xBF: "SCREEN", + 0xC0: "PCLEAR", + 0xC1: "COLOR", + 0xC2: "CIRCLE", + 0xC3: "PAINT", + 0xC4: "GET", + 0xC5: "PUT", + 0xC6: "DRAW", + 0xC7: "PCOPY", + 0xC8: "PMODE", + 0xC9: "PLAY", + 0xCA: "DLOAD", + 0xCB: "RENUM", + 0xCC: "FN", + 0xCD: "USING", + # # Function tokens - all proceeded by 0xff to differentiate from operators - - 0xff94: "ATN", - 0xff95: "COS", - 0xff96: "TAN", - 0xff97: "EXP", - 0xff98: "FIX", - 0xff99: "LOG", - 0xff9a: "POS", - 0xff9b: "SQR", - 0xff9c: "HEX$", - 0xff9d: "VARPTR", - 0xff9e: "INSTR", - 0xff9f: "TIMER", - 0xffa0: "PPOINT", - 0xffa1: "STRING$", + # + 0xFF94: "ATN", + 0xFF95: "COS", + 0xFF96: "TAN", + 0xFF97: "EXP", + 0xFF98: "FIX", + 0xFF99: "LOG", + 0xFF9A: "POS", + 0xFF9B: "SQR", + 0xFF9C: "HEX$", + 0xFF9D: "VARPTR", + 0xFF9E: "INSTR", + 0xFF9F: "TIMER", + 0xFFA0: "PPOINT", + 0xFFA1: "STRING$", } # Merged tokens: @@ -157,7 +154,8 @@ # Generate Wiki Table for: # http://archive.worldofdragon.org/index.php?title=Tokens - print(""" + print( + """ * "CoCo A": - Tokens from Color BASIC 1.3 * "CoCo B": - Additional tokens from Extended Color BASIC 1.1 only {| class="wikitable" style="font-family: monospace; background-color:#ffffcc;" cellpadding="10" @@ -169,16 +167,17 @@ token ! CoCo B token -""") +""" + ) for value in values: coco_basic_statement = COCO_COLOR_BASIC_TOKENS.get(value, "") coco_extended_basic_statement = COCO_EXTENDED_COLOR_BASIC_TOKENS.get(value, "") dragon_statement = DRAGON32_BASIC_TOKENS.get(value, "") - if coco_basic_statement == "" and coco_extended_basic_statement=="" and dragon_statement == "": + if coco_basic_statement == coco_extended_basic_statement == dragon_statement == "": continue - if value > 0xff: + if value > 0xFF: value = "$%04x" % value else: value = "$%02x" % value @@ -188,6 +187,6 @@ print("| %s" % dragon_statement) print("| %s" % coco_basic_statement) print("| %s" % coco_extended_basic_statement) - + print("|-") print("|}") diff --git a/dragonlib/__main__.py b/dragonlib/__main__.py index 0f1ce28..8da6c7c 100644 --- a/dragonlib/__main__.py +++ b/dragonlib/__main__.py @@ -3,7 +3,6 @@ through `python -m dragonlib`. """ - from dragonlib.cli_app import main diff --git a/dragonlib/api.py b/dragonlib/api.py index 5e3fb95..9197a7c 100644 --- a/dragonlib/api.py +++ b/dragonlib/api.py @@ -10,18 +10,17 @@ """ -import six import logging from dragonlib.CoCo.basic_tokens import COCO_BASIC_TOKENS -from dragonlib.core.basic import BasicListing, RenumTool, BasicTokenUtil,\ - BasicLine +from dragonlib.core.basic import BasicLine, BasicListing, BasicTokenUtil, RenumTool from dragonlib.core.basic_parser import BASICParser from dragonlib.core.binary_files import BinaryFile from dragonlib.dragon32.basic_tokens import DRAGON32_BASIC_TOKENS from dragonlib.utils.logging_utils import log_bytes -log=logging.getLogger(__name__) + +log = logging.getLogger(__name__) DRAGON32 = "Dragon32" @@ -32,7 +31,7 @@ class BaseAPI: RENUM_REGEX = r""" (?P GOTO|GOSUB|THEN|ELSE ) (?P\s*) (?P[\d*,\s*]+) """ - + def __init__(self): self.listing = BasicListing(self.BASIC_TOKENS) self.renum_tool = RenumTool(self.RENUM_REGEX) @@ -49,14 +48,12 @@ def program_dump2ascii_lines(self, dump, program_start=None): if program_start is None: program_start = self.DEFAULT_PROGRAM_START return self.listing.program_dump2ascii_lines(dump, program_start) - + def parse_ascii_listing(self, basic_program_ascii): parser = BASICParser() parsed_lines = parser.parse(basic_program_ascii) if not parsed_lines: - log.critical("No parsed lines {} from {} ?!?".format( - repr(parsed_lines), repr(basic_program_ascii) - )) + log.critical("No parsed lines {} from {} ?!?".format(repr(parsed_lines), repr(basic_program_ascii))) log.debug("Parsed BASIC: %s", repr(parsed_lines)) return parsed_lines @@ -66,7 +63,7 @@ def ascii_listing2basic_lines(self, basic_program_ascii, program_start): basic_lines = [] for line_no, code_objects in sorted(parsed_lines.items()): basic_line = BasicLine(self.token_util) - basic_line.code_objects_load(line_no,code_objects) + basic_line.code_objects_load(line_no, code_objects) basic_lines.append(basic_line) return basic_lines @@ -82,9 +79,9 @@ def ascii_listing2program_dump(self, basic_program_ascii, program_start=None): basic_lines = self.ascii_listing2basic_lines(basic_program_ascii, program_start) - program_dump=self.listing.basic_lines2program_dump(basic_lines, program_start) - assert isinstance(program_dump, bytearray), ( - "is type: {} and not bytearray: {}".format(type(program_dump), repr(program_dump)) + program_dump = self.listing.basic_lines2program_dump(basic_lines, program_start) + assert isinstance(program_dump, bytearray), "is type: {} and not bytearray: {}".format( + type(program_dump), repr(program_dump) ) return program_dump @@ -118,7 +115,7 @@ def reformat_ascii_listing(self, basic_program_ascii): print() print(line_no, code_objects) basic_line = BasicLine(self.token_util) - basic_line.code_objects_load(line_no,code_objects) + basic_line.code_objects_load(line_no, code_objects) print(basic_line) basic_line.reformat() @@ -143,7 +140,8 @@ def bas2bin(self, basic_program_ascii, load_address=None, exec_address=None): log_bytes(tokenised_dump, msg="tokenised: %s") binary_file = BinaryFile() - binary_file.load_tokenised_dump(tokenised_dump, + binary_file.load_tokenised_dump( + tokenised_dump, load_address=load_address, exec_address=exec_address, ) @@ -169,17 +167,15 @@ def bin2bas(self, data): if binary_file.file_type != 0x01: log.error("ERROR: file type $%02X is not $01 (tokenised BASIC)!", binary_file.file_type) - ascii_lines = self.program_dump2ascii_lines(dump=binary_file.data, + ascii_lines = self.program_dump2ascii_lines( + dump=binary_file.data, # FIXME: - #program_start=bin.exec_address - program_start=binary_file.load_address + # program_start=bin.exec_address + program_start=binary_file.load_address, ) return "\n".join(ascii_lines) - - - class Dragon32API(BaseAPI): CONFIG_NAME = DRAGON32 MACHINE_NAME = "Dragon 32" @@ -198,6 +194,7 @@ class CoCoAPI(Dragon32API): """ http://sourceforge.net/p/toolshed/code/ci/default/tree/cocoroms/dragon_equivs.asm """ + CONFIG_NAME = COCO2B MACHINE_NAME = "CoCo" BASIC_TOKENS = COCO_BASIC_TOKENS @@ -206,26 +203,26 @@ class CoCoAPI(Dragon32API): def example_renum_ascii_listing(): api = Dragon32API() - ascii_listing="\n".join([ - '1 PRINT "LINE 10"', - '2 PRINT "LINE 20"', - '3 GOTO 1', - ]) - print( - api.renum_ascii_listing(ascii_listing) + ascii_listing = "\n".join( + [ + '1 PRINT "LINE 10"', + '2 PRINT "LINE 20"', + '3 GOTO 1', + ] ) + print(api.renum_ascii_listing(ascii_listing)) def test_bin2bas(): api = Dragon32API() with open(os.path.expanduser("~/DragonEnvPy3/DwRoot/AUTOLOAD.DWL"), "rb") as f: - data1=f.read() + data1 = f.read() - ascii_listing=api.bin2bas(data1) + ascii_listing = api.bin2bas(data1) print(ascii_listing) - data2 = api.bas2bin(ascii_listing, load_address=0x1e01, exec_address=0x1e01) + data2 = api.bas2bin(ascii_listing, load_address=0x1E01, exec_address=0x1E01) log_bytes(data1, "data1: %s", level=logging.CRITICAL) log_bytes(data2, "data2: %s", level=logging.CRITICAL) @@ -233,18 +230,18 @@ def test_bin2bas(): if __name__ == '__main__': import os + from dragonlib.utils.logging_utils import setup_logging setup_logging( -# level=1 # hardcore debug ;) -# level=10 # DEBUG -# level=20 # INFO + # level=1 # hardcore debug ;) + # level=10 # DEBUG + # level=20 # INFO level=30 # WARNING -# level=40 # ERROR -# level=50 # CRITICAL/FATAL -# level=99 + # level=40 # ERROR + # level=50 # CRITICAL/FATAL + # level=99 ) # example_renum_ascii_listing() test_bin2bas() - diff --git a/dragonlib/cli_app/__init__.py b/dragonlib/cli_app/__init__.py index 1b6d5f4..235e5f6 100644 --- a/dragonlib/cli_app/__init__.py +++ b/dragonlib/cli_app/__init__.py @@ -6,14 +6,13 @@ import sys import rich_click as click +from cli_base.autodiscover import import_all_files +from cli_base.cli_tools.version_info import print_version from rich import print # noqa from rich.console import Console from rich.traceback import install as rich_traceback_install from rich_click import RichGroup -from cli_base.autodiscover import import_all_files -from cli_base.cli_tools.version_info import print_version - import dragonlib from dragonlib import constants diff --git a/dragonlib/cli_app/update_readme_history.py b/dragonlib/cli_app/update_readme_history.py index f2d040f..80c61cb 100644 --- a/dragonlib/cli_app/update_readme_history.py +++ b/dragonlib/cli_app/update_readme_history.py @@ -4,6 +4,7 @@ from cli_base.cli_tools import git_history from cli_base.cli_tools.verbosity import OPTION_KWARGS_VERBOSE, setup_logging from rich import print # noqa + from dragonlib.cli_app import cli diff --git a/dragonlib/cli_dev/__init__.py b/dragonlib/cli_dev/__init__.py index 605ead0..aa849e4 100644 --- a/dragonlib/cli_dev/__init__.py +++ b/dragonlib/cli_dev/__init__.py @@ -7,15 +7,14 @@ import rich_click as click from bx_py_utils.path import assert_is_file +from cli_base.autodiscover import import_all_files +from cli_base.cli_tools.dev_tools import run_coverage, run_tox, run_unittest_cli +from cli_base.cli_tools.version_info import print_version from rich.console import Console from rich.traceback import install as rich_traceback_install from rich_click import RichGroup from typeguard import install_import_hook -from cli_base.autodiscover import import_all_files -from cli_base.cli_tools.dev_tools import run_coverage, run_tox, run_unittest_cli -from cli_base.cli_tools.version_info import print_version - import dragonlib from dragonlib import constants diff --git a/dragonlib/cli_dev/code_style.py b/dragonlib/cli_dev/code_style.py index a3e9545..d00790c 100644 --- a/dragonlib/cli_dev/code_style.py +++ b/dragonlib/cli_dev/code_style.py @@ -2,6 +2,7 @@ from cli_base.cli_tools import code_style from cli_base.cli_tools.verbosity import OPTION_KWARGS_VERBOSE from cli_base.click_defaults import OPTION_ARGS_DEFAULT_TRUE + from dragonlib.cli_dev import PACKAGE_ROOT, cli diff --git a/dragonlib/cli_dev/testing.py b/dragonlib/cli_dev/testing.py index 5ed9a11..6256dff 100644 --- a/dragonlib/cli_dev/testing.py +++ b/dragonlib/cli_dev/testing.py @@ -3,6 +3,7 @@ from cli_base.cli_tools.subprocess_utils import verbose_check_call from cli_base.cli_tools.test_utils.snapshot import UpdateTestSnapshotFiles from cli_base.cli_tools.verbosity import OPTION_KWARGS_VERBOSE + from dragonlib.cli_dev import PACKAGE_ROOT, cli diff --git a/dragonlib/core/basic.py b/dragonlib/core/basic.py index 8380994..659a299 100644 --- a/dragonlib/core/basic.py +++ b/dragonlib/core/basic.py @@ -14,11 +14,9 @@ import re from dragonlib.core import basic_parser -import six -from dragonlib.utils.iter_utils import list_replace -from dragonlib.utils.logging_utils import pformat_byte_hex_list, \ - log_program_dump from dragonlib.utils.byte_word_values import word2bytes +from dragonlib.utils.iter_utils import list_replace +from dragonlib.utils.logging_utils import log_program_dump, pformat_byte_hex_list log = logging.getLogger(__name__) @@ -27,32 +25,28 @@ class BasicTokenUtil: def __init__(self, basic_token_dict): self.basic_token_dict = basic_token_dict - self.ascii2token_dict = { - code: token - for token, code in list(basic_token_dict.items()) - } - - regex = r"(%s)" % "|".join([ - re.escape(statement) - for statement in sorted(list(self.basic_token_dict.values()), key=len, reverse=True) - ]) + self.ascii2token_dict = {code: token for token, code in list(basic_token_dict.items())} + + regex = r"(%s)" % "|".join( + [re.escape(statement) for statement in sorted(list(self.basic_token_dict.values()), key=len, reverse=True)] + ) self.regex = re.compile(regex) def token2ascii(self, value): try: result = self.basic_token_dict[value] except KeyError: - if value > 0xff: + if value > 0xFF: log.info("ERROR: Token $%04x is not in BASIC_TOKENS!", value) return "" result = chr(value) return result def tokens2ascii(self, values): - line="" + line = "" old_value = None for value in values: - if value == 0xff: + if value == 0xFF: old_value = value continue if old_value is not None: @@ -80,9 +74,9 @@ def ascii2token(self, ascii_code, debug=False): if part in self.ascii2token_dict: new_token = self.ascii2token_dict[part] log.info("\t%r -> %x", part, new_token) - if new_token > 0xff: + if new_token > 0xFF: tokens.append(new_token >> 8) - tokens.append(new_token & 0xff) + tokens.append(new_token & 0xFF) else: tokens.append(new_token) else: @@ -116,7 +110,7 @@ def code_objects2token(self, code_objects): def iter_token_values(self, tokens): token_value = None for token in tokens: - if token == 0xff: + if token == 0xFF: token_value = token continue @@ -134,7 +128,7 @@ def pformat_tokens(self, tokens): result = [] for token_value in self.iter_token_values(tokens): char = self.token2ascii(token_value) - if token_value > 0xff: + if token_value > 0xFF: result.append("\t${:04x} -> {}".format(token_value, repr(char))) else: result.append("\t ${:02x} -> {}".format(token_value, repr(char))) @@ -150,7 +144,7 @@ def __init__(self, token_util): try: colon_token = self.token_util.ascii2token_dict[":"] - except KeyError: # XXX: Always not defined as token? + except KeyError: # XXX: Always not defined as token? colon_token = ord(":") rem_token = self.token_util.ascii2token_dict["'"] else_token = self.token_util.ascii2token_dict["ELSE"] @@ -161,9 +155,7 @@ def __init__(self, token_util): def token_load(self, line_number, tokens): self.line_number = line_number - assert tokens[-1] == 0x00, "line code {} doesn't ends with \\x00: {}".format( - repr(tokens), repr(tokens[-1]) - ) + assert tokens[-1] == 0x00, "line code {} doesn't ends with \\x00: {}".format(repr(tokens), repr(tokens[-1])) """ NOTE: The BASIC interpreter changed REM shortcut and ELSE @@ -175,22 +167,18 @@ def token_load(self, line_number, tokens): http://archive.worldofdragon.org/phpBB3/viewtopic.php?f=8&t=4310&p=11632#p11630 """ for src, dst in self.tokens_replace_rules: - log.info("Relace tokens %s with $%02x", - pformat_byte_hex_list(src), dst - ) + log.info("Relace tokens %s with $%02x", pformat_byte_hex_list(src), dst) log.debug("Before..: %s", pformat_byte_hex_list(tokens)) tokens = list_replace(tokens, src, dst) log.debug("After...: %s", pformat_byte_hex_list(tokens)) - self.line_code = tokens[:-1] # rstrip \x00 + self.line_code = tokens[:-1] # rstrip \x00 def ascii_load(self, line_ascii): try: line_number, ascii_code = line_ascii.split(" ", 1) except ValueError as err: - msg = "Error split line number and code in line: {!r} (Origin error: {})".format( - line_ascii, err - ) + msg = "Error split line number and code in line: {!r} (Origin error: {})".format(line_ascii, err) raise ValueError(msg) self.line_number = int(line_number) self.line_code = self.token_util.ascii2token(ascii_code) @@ -209,30 +197,30 @@ def reformat(self): # TODO: Use BASICParser to exclude string/comments etc. space = self.token_util.ascii2token(" ")[0] - to_split=self.token_util.basic_token_dict.copy() - dont_split_tokens=self.token_util.ascii2token(":()+-*/^<=>") + to_split = self.token_util.basic_token_dict.copy() + dont_split_tokens = self.token_util.ascii2token(":()+-*/^<=>") for token_value in dont_split_tokens: try: - del(to_split[token_value]) - except KeyError: # e.g.: () are not tokens + del to_split[token_value] + except KeyError: # e.g.: () are not tokens pass - tokens=tuple(self.token_util.iter_token_values(self.line_code)) + tokens = tuple(self.token_util.iter_token_values(self.line_code)) temp = [] - was_token=False + was_token = False for no, token in enumerate(tokens): try: - next_token=tokens[no+1] + next_token = tokens[no + 1] except IndexError: - next_token=None + next_token = None if token in to_split: log.debug("X%sX" % to_split[token]) try: - if temp[-1]!=space: + if temp[-1] != space: temp.append(space) except IndexError: pass @@ -240,10 +228,10 @@ def reformat(self): if not (next_token and next_token in dont_split_tokens): temp.append(space) - was_token=True + was_token = True else: - if was_token and token==space: - was_token=False + if was_token and token == space: + was_token = False continue log.debug("Y%rY" % self.token_util.tokens2ascii([token])) temp.append(token) @@ -256,9 +244,8 @@ def reformat(self): self.line_code = temp - def get_content(self, code=None): - if code is None: # start + if code is None: # start code = self.line_code line = "%i " % self.line_number @@ -270,10 +257,7 @@ def __repr__(self): return "{!r}: {}".format(self.get_content(), " ".join(["$%02x" % t for t in self.line_code])) def log_line(self): - log.critical("%r:\n\t%s", - self.get_content(), - "\n\t".join(self.token_util.pformat_tokens(self.line_code)) - ) + log.critical("%r:\n\t%s", self.get_content(), "\n\t".join(self.token_util.pformat_tokens(self.line_code))) class BasicListing: @@ -323,11 +307,11 @@ def basic_lines2program_dump(self, basic_lines, program_start): line_tokens = line.get_tokens() + [0x00] current_address += len(line_tokens) + 2 - current_address_bytes = word2bytes(current_address) # e.g.: word2bytes(0xff09) -> (255, 9) + current_address_bytes = word2bytes(current_address) # e.g.: word2bytes(0xff09) -> (255, 9) program_dump += bytearray(current_address_bytes) - if no == count: # It's the last line + if no == count: # It's the last line line_tokens += [0x00, 0x00] program_dump += bytearray(line_tokens) @@ -350,9 +334,7 @@ def pformat_program_dump(self, program_dump, program_start, formated_dump=None): """ if formated_dump is None: formated_dump = [] - formated_dump.append( - "program start address: $%04x" % program_start - ) + formated_dump.append("program start address: $%04x" % program_start) assert isinstance(program_dump, bytearray) @@ -365,7 +347,8 @@ def pformat_program_dump(self, program_dump, program_start, formated_dump=None): raise IndexError( "Can't get next address from: {} program start: ${:04x} (Origin error: {})".format( repr(program_dump), program_start, err - )) + ) + ) if next_address == 0x0000: formated_dump.append("$%04x -> end address" % next_address) @@ -376,9 +359,7 @@ def pformat_program_dump(self, program_dump, program_start, formated_dump=None): ) length = next_address - program_start - formated_dump.append( - "$%04x -> next address (length: %i)" % (next_address, length) - ) + formated_dump.append("$%04x -> next address (length: %i)" % (next_address, length)) line_number = (program_dump[2] << 8) + program_dump[3] formated_dump.append("$%04x -> %i (line number)" % (line_number, line_number)) @@ -401,11 +382,9 @@ def ascii_listing2program_dump(self, basic_program_ascii, program_start): self.debug_listing(basic_lines) return self.basic_lines2program_dump(basic_lines, program_start) - -# def parsed_lines2program_dump(self, parsed_lines, program_start): -# for line_no, code_objects in sorted(parsed_lines.items()): -# for code_object in code_objects: - + # def parsed_lines2program_dump(self, parsed_lines, program_start): + # for line_no, code_objects in sorted(parsed_lines.items()): + # for code_object in code_objects: def program_dump2ascii_lines(self, dump, program_start): basic_lines = self.dump2basic_lines(dump, program_start) @@ -420,18 +399,14 @@ class RenumTool: """ Renumber a BASIC program """ + def __init__(self, renum_regex): self.line_no_regex = re.compile(r"(?P\d+)(?P.+)") self.renum_regex = re.compile(renum_regex, re.VERBOSE) def renum(self, ascii_listing): self.renum_dict = self.create_renum_dict(ascii_listing) - log.info("renum: %s", - ", ".join([ - "{}->{}".format(o, n) - for o, n in sorted(self.renum_dict.items()) - ]) - ) + log.info("renum: %s", ", ".join(["{}->{}".format(o, n) for o, n in sorted(self.renum_dict.items())])) new_listing = [] for new_number, line in enumerate(self._iter_lines(ascii_listing), 1): new_number *= 10 @@ -446,12 +421,11 @@ def get_destinations(self, ascii_listing): returns all line numbers that are used in a jump. """ self.destinations = set() + def collect_destinations(matchobj): numbers = matchobj.group("no") if numbers: - self.destinations.update({ - n.strip() for n in numbers.split(",") - }) + self.destinations.update({n.strip() for n in numbers.split(",")}) for line in self._iter_lines(ascii_listing): self.renum_regex.sub(collect_destinations, line) @@ -467,15 +441,12 @@ def _get_new_line_number(self, line, old_number): try: new_number = "%s" % self.renum_dict[old_number] except KeyError: - log.error( - "Error in line '%s': line no. '%s' doesn't exist.", - line, old_number - ) + log.error("Error in line '%s': line no. '%s' doesn't exist.", line, old_number) new_number = old_number return new_number def renum_inline(self, matchobj): -# log.critical(matchobj.groups()) + # log.critical(matchobj.groups()) old_numbers = matchobj.group("no") if old_numbers[-1] == " ": # e.g.: space before comment: ON X GOTO 1,2 ' Comment @@ -483,15 +454,8 @@ def renum_inline(self, matchobj): else: space_after = "" old_numbers = [n.strip() for n in old_numbers.split(",")] - new_numbers = [ - self._get_new_line_number(matchobj.group(0), old_number) - for old_number in old_numbers - ] - return "".join([ - matchobj.group("statement"), - matchobj.group("space"), - ",".join(new_numbers), space_after - ]) + new_numbers = [self._get_new_line_number(matchobj.group(0), old_number) for old_number in old_numbers] + return "".join([matchobj.group("statement"), matchobj.group("space"), ",".join(new_numbers), space_after]) def create_renum_dict(self, ascii_listing): old_numbers = [match[0] for match in self.line_no_regex.findall(ascii_listing)] @@ -529,16 +493,17 @@ def _test_reformat(): from dragonlib.utils.logging_utils import setup_logging setup_logging( -# level=1 # hardcore debug ;) -# level=10 # DEBUG -# level=20 # INFO -# level=30 # WARNING -# level=40 # ERROR -# level=50 # CRITICAL/FATAL + # level=1 # hardcore debug ;) + # level=10 # DEBUG + # level=20 # INFO + # level=30 # WARNING + # level=40 # ERROR + # level=50 # CRITICAL/FATAL level=99 ) from dragonlib.api import Dragon32API + api = Dragon32API() # filepath = os.path.join(os.path.abspath(os.path.dirname(__file__)), @@ -549,16 +514,15 @@ def _test_reformat(): # with open(filepath, "r") as f: # listing_ascii = f.read() - listing_ascii="""\ + listing_ascii = """\ 10 ONPOINT(Y,K)GOTO250,250'ONPOINT(Y,K)GOTO250,250 20 FORT=479TO 542:T(T)=0:Y(T)=28:NEXT 30 I=I+1:PRINT"FORX=1TO 2:Y(Y)=0:NEXT" 730 CLS:PRINT"FIXME: PLEASE WAIT [ ]"; """ - print( - api.reformat_ascii_listing(listing_ascii) - ) + print(api.reformat_ascii_listing(listing_ascii)) + if __name__ == "__main__": # _test_renum() diff --git a/dragonlib/core/basic_parser.py b/dragonlib/core/basic_parser.py index 671d5b8..41e4e75 100644 --- a/dragonlib/core/basic_parser.py +++ b/dragonlib/core/basic_parser.py @@ -18,10 +18,9 @@ """ - +import logging import re -import logging log = logging.getLogger(__name__) @@ -60,6 +59,7 @@ class ParsedBASIC(dict): """ Normal dict with special __repr__ """ + def pformat(self): ''' Manually pformat to force using """...""" and supress escaping apostrophe @@ -70,9 +70,7 @@ def pformat(self): for line_no, code_objects in sorted(self.items()): result += '%s%i: [\n' % (indent1, line_no) for code_object in code_objects: - result += '{}"""<{}:{}>""",\n'.format( - indent2, code_object.PART_TYPE, code_object.content - ) + result += '{}"""<{}:{}>""",\n'.format(indent2, code_object.PART_TYPE, code_object.content) result += '%s],\n' % indent1 result += "}" @@ -91,26 +89,27 @@ class BASICParser: * Strings * Comments """ + def __init__(self): self.regex_line_no = re.compile( # Split the line number from the code r"^\s*(?P\d+)\s?(?P.+)\s*$", - re.MULTILINE + re.MULTILINE, ) self.regex_split_all = re.compile( # To split a code line for parse CODE, DATA, STRING or COMMENT r""" ( " | DATA | REM | ') """, - re.VERBOSE | re.MULTILINE + re.VERBOSE | re.MULTILINE, ) self.regex_split_data = re.compile( # To consume the complete DATA until " or : r""" ( " | : ) """, - re.VERBOSE | re.MULTILINE + re.VERBOSE | re.MULTILINE, ) self.regex_split_string = re.compile( # To consume a string r""" ( " ) """, - re.VERBOSE | re.MULTILINE + re.VERBOSE | re.MULTILINE, ) def parse(self, ascii_listing): @@ -218,15 +217,17 @@ def _parse_code(self, line): if __name__ == "__main__": import unittest + from dragonlib.utils.logging_utils import setup_logging - setup_logging(log, -# level=1 # hardcore debug ;) -# level=10 # DEBUG -# level=20 # INFO - level=30 # WARNING -# level=40 # ERROR -# level=50 # CRITICAL/FATAL + setup_logging( + log, + # level=1 # hardcore debug ;) + # level=10 # DEBUG + # level=20 # INFO + level=30, # WARNING + # level=40 # ERROR + # level=50 # CRITICAL/FATAL ) unittest.main( diff --git a/dragonlib/core/binary_files.py b/dragonlib/core/binary_files.py index 080a31b..1a74953 100644 --- a/dragonlib/core/binary_files.py +++ b/dragonlib/core/binary_files.py @@ -13,7 +13,7 @@ import logging import struct -import six + from dragonlib.utils.byte_word_values import bin2hexline from dragonlib.utils.logging_utils import log_bytes @@ -23,7 +23,7 @@ class BinaryFile: def __init__(self): - self.file_type = None # $01 == BAS | $02 == BIN + self.file_type = None # $01 == BAS | $02 == BIN self.load_address = None self.length = None self.exec_address = None @@ -41,9 +41,7 @@ def verbose_value(value, fmt="$%02x"): if self.length is None: log.log(level, "Length: None") else: - log.log(level, "Length: $%04x (dez.: %i Bytes)", - self.length, self.length - ) + log.log(level, "Length: $%04x (dez.: %i Bytes)", self.length, self.length) log.log(level, "Exec Address: %s", verbose_value(self.exec_address, fmt="$%04x")) if not self.data: @@ -54,7 +52,8 @@ def verbose_value(value, fmt="$%02x"): log.log(level, line) def get_header(self): - header = struct.pack(">BBHHHB", + header = struct.pack( + ">BBHHHB", 0x55, self.file_type, self.load_address, @@ -69,10 +68,6 @@ def dump_DragonDosBinary(self): # log_bytes(self.data, "data in hex: %s", level=logging.DEBUG) self.debug2log(level=logging.DEBUG) header = self.get_header() - - if six.PY2: - return header + "".join([chr(i) for i in self.data]) - return header + self.data def load_DragonDosBinary(self, data, strip_padding=True): @@ -119,7 +114,10 @@ def load_DragonDosBinary(self, data, strip_padding=True): log.debug( "File type: $%02X Load Address: $%04X Exec Address: $%04X Length: %iBytes", - self.file_type, self.load_address, self.exec_address, self.length + self.file_type, + self.load_address, + self.exec_address, + self.length, ) if self.length != len(self.data): log.error("ERROR: Wrong data size: should be: %i Bytes but is %i Bytes!", self.length, len(self.data)) @@ -156,4 +154,4 @@ def load_tokenised_dump(self, tokenised_dump, load_address, exec_address): self.file_type = 0x01 self.load_address = load_address self.length = len(self.data) - self.exec_address = exec_address \ No newline at end of file + self.exec_address = exec_address diff --git a/dragonlib/dragon32/basic_tokens.py b/dragonlib/dragon32/basic_tokens.py index 2470119..af321f0 100644 --- a/dragonlib/dragon32/basic_tokens.py +++ b/dragonlib/dragon32/basic_tokens.py @@ -7,122 +7,120 @@ :license: GNU GPL v3 or above, see LICENSE for more details. """ - - DRAGON32_BASIC_TOKENS = { - 0x80:"FOR", - 0x81:"GO", - 0x82:"REM", - 0x83:"'", - 0x84:"ELSE", - 0x85:"IF", - 0x86:"DATA", - 0x87:"PRINT", - 0x88:"ON", - 0x89:"INPUT", - 0x8a:"END", - 0x8b:"NEXT", - 0x8c:"DIM", - 0x8d:"READ", - 0x8e:"LET", - 0x8f:"RUN", - 0x90:"RESTORE", - 0x91:"RETURN", - 0x92:"STOP", - 0x93:"POKE", - 0x94:"CONT", - 0x95:"LIST", - 0x96:"CLEAR", - 0x97:"NEW", - 0x98:"DEF", - 0x99:"CLOAD", - 0x9a:"CSAVE", - 0x9b:"OPEN", - 0x9c:"CLOSE", - 0x9d:"LLIST", - 0x9e:"SET", - 0x9f:"RESET", - 0xa0:"CLS", - 0xa1:"MOTOR", - 0xa2:"SOUND", - 0xa3:"AUDIO", - 0xa4:"EXEC", - 0xa5:"SKIPF", - 0xa6:"DEL", - 0xa7:"EDIT", - 0xa8:"TRON", - 0xa9:"TROFF", - 0xaa:"LINE", - 0xab:"PCLS", - 0xac:"PSET", - 0xad:"PRESET", - 0xae:"SCREEN", - 0xaf:"PCLEAR", - 0xb0:"COLOR", - 0xb1:"CIRCLE", - 0xb2:"PAINT", - 0xb3:"GET", - 0xb4:"PUT", - 0xb5:"DRAW", - 0xb6:"PCOPY", - 0xb7:"PMODE", - 0xb8:"PLAY", - 0xb9:"DLOAD", - 0xba:"RENUM", - 0xbb:"TAB(", - 0xbc:"TO", - 0xbd:"SUB", - 0xbe:"FN", - 0xbf:"THEN", - 0xc0:"NOT", - 0xc1:"STEP", - 0xc2:"OFF", - 0xc3:"+", - 0xc4:"-", - 0xc5:"*", - 0xc6:"/", - 0xc7:"^", - 0xc8:"AND", - 0xc9:"OR", - 0xca:">", - 0xcb:"=", - 0xcc:"<", - 0xcd:"USING", - -#Function tokens - all proceeded by 0xff to differentiate from operators -#Token Function", - 0xff80:"SGN", - 0xff81:"INT", - 0xff82:"ABS", - 0xff83:"POS", - 0xff84:"RND", - 0xff85:"SQR", - 0xff86:"LOG", - 0xff87:"EXP", - 0xff88:"SIN", - 0xff89:"COS", - 0xff8a:"TAN", - 0xff8b:"ATN", - 0xff8c:"PEEK", - 0xff8d:"LEN", - 0xff8e:"STR$", - 0xff8f:"VAL", - 0xff90:"ASC", - 0xff91:"CHR$", - 0xff92:"EOF", - 0xff93:"JOYSTK", - 0xff94:"FIX", - 0xff95:"HEX$", - 0xff96:"LEFT$", - 0xff97:"RIGHT$", - 0xff98:"MID$", - 0xff99:"POINT", - 0xff9a:"INKEY$", - 0xff9b:"MEM", - 0xff9c:"VARPTR", - 0xff9d:"INSTR", - 0xff9e:"TIMER", - 0xff9f:"PPOINT", - 0xffa0:"STRING$", - 0xffa1:"USR", + 0x80: "FOR", + 0x81: "GO", + 0x82: "REM", + 0x83: "'", + 0x84: "ELSE", + 0x85: "IF", + 0x86: "DATA", + 0x87: "PRINT", + 0x88: "ON", + 0x89: "INPUT", + 0x8A: "END", + 0x8B: "NEXT", + 0x8C: "DIM", + 0x8D: "READ", + 0x8E: "LET", + 0x8F: "RUN", + 0x90: "RESTORE", + 0x91: "RETURN", + 0x92: "STOP", + 0x93: "POKE", + 0x94: "CONT", + 0x95: "LIST", + 0x96: "CLEAR", + 0x97: "NEW", + 0x98: "DEF", + 0x99: "CLOAD", + 0x9A: "CSAVE", + 0x9B: "OPEN", + 0x9C: "CLOSE", + 0x9D: "LLIST", + 0x9E: "SET", + 0x9F: "RESET", + 0xA0: "CLS", + 0xA1: "MOTOR", + 0xA2: "SOUND", + 0xA3: "AUDIO", + 0xA4: "EXEC", + 0xA5: "SKIPF", + 0xA6: "DEL", + 0xA7: "EDIT", + 0xA8: "TRON", + 0xA9: "TROFF", + 0xAA: "LINE", + 0xAB: "PCLS", + 0xAC: "PSET", + 0xAD: "PRESET", + 0xAE: "SCREEN", + 0xAF: "PCLEAR", + 0xB0: "COLOR", + 0xB1: "CIRCLE", + 0xB2: "PAINT", + 0xB3: "GET", + 0xB4: "PUT", + 0xB5: "DRAW", + 0xB6: "PCOPY", + 0xB7: "PMODE", + 0xB8: "PLAY", + 0xB9: "DLOAD", + 0xBA: "RENUM", + 0xBB: "TAB(", + 0xBC: "TO", + 0xBD: "SUB", + 0xBE: "FN", + 0xBF: "THEN", + 0xC0: "NOT", + 0xC1: "STEP", + 0xC2: "OFF", + 0xC3: "+", + 0xC4: "-", + 0xC5: "*", + 0xC6: "/", + 0xC7: "^", + 0xC8: "AND", + 0xC9: "OR", + 0xCA: ">", + 0xCB: "=", + 0xCC: "<", + 0xCD: "USING", + # + # Function tokens - all proceeded by 0xff to differentiate from operators + # Token Function", + 0xFF80: "SGN", + 0xFF81: "INT", + 0xFF82: "ABS", + 0xFF83: "POS", + 0xFF84: "RND", + 0xFF85: "SQR", + 0xFF86: "LOG", + 0xFF87: "EXP", + 0xFF88: "SIN", + 0xFF89: "COS", + 0xFF8A: "TAN", + 0xFF8B: "ATN", + 0xFF8C: "PEEK", + 0xFF8D: "LEN", + 0xFF8E: "STR$", + 0xFF8F: "VAL", + 0xFF90: "ASC", + 0xFF91: "CHR$", + 0xFF92: "EOF", + 0xFF93: "JOYSTK", + 0xFF94: "FIX", + 0xFF95: "HEX$", + 0xFF96: "LEFT$", + 0xFF97: "RIGHT$", + 0xFF98: "MID$", + 0xFF99: "POINT", + 0xFF9A: "INKEY$", + 0xFF9B: "MEM", + 0xFF9C: "VARPTR", + 0xFF9D: "INSTR", + 0xFF9E: "TIMER", + 0xFF9F: "PPOINT", + 0xFFA0: "STRING$", + 0xFFA1: "USR", } diff --git a/dragonlib/dragon32/pygments_lexer.py b/dragonlib/dragon32/pygments_lexer.py index 64de91e..19f5027 100644 --- a/dragonlib/dragon32/pygments_lexer.py +++ b/dragonlib/dragon32/pygments_lexer.py @@ -7,21 +7,19 @@ :license: GNU GPL v3 or above, see LICENSE for more details. """ - import re from collections import namedtuple from pygments.lexer import RegexLexer from pygments.styles import get_style_by_name -from pygments.token import Text, Comment, Operator, Keyword, Name, String, \ - Number, Punctuation - +from pygments.token import Comment, Keyword, Name, Number, Operator, Punctuation, String, Text class BasicLexer(RegexLexer): """ Pygments lexer for Dragon/CoCo BASIC """ + name = 'Dragon/CoCo BASIC' aliases = ['basic'] filenames = ['*.bas'] @@ -29,44 +27,36 @@ class BasicLexer(RegexLexer): tokens = { 'root': [ (r"(REM|').*\n", Comment.Single), - (r'\s+', Text), (r'^\d+', Name.Label), - - ( r'RUN|RESTORE|STOP|RENUM|' r'GOTO|' r'OPEN|CLOSE|READ|CLOAD|CSAVE|DLOAD|LLIST|MOTOR|SKIPF|' r'LIST|CLEAR|NEW|EXEC|DEL|EDIT|TRON|TROFF', - Keyword + Keyword, ), ( - r'SOUND|AUDIOLINE|PLAY|' - r'PCLS|PSET|SCREEN|PCLEAR|COLOR|CIRCLE|PAINT|GET|PUT|DRAW|PCOPY|PMODE', - Keyword.Reserved + r'SOUND|AUDIOLINE|PLAY|' r'PCLS|PSET|SCREEN|PCLEAR|COLOR|CIRCLE|PAINT|GET|PUT|DRAW|PCOPY|PMODE', + Keyword.Reserved, ), (r'DATA|DIM|LET|DEF', Keyword.Declaration), - ( r'PRINT|CLS|INPUT|INKEY$|' r'HEX$|LEFT$|RIGHT$|MID$|STRING$|STR$|CHR$|' r'SGN|INT|ABS|POS|RND|SQR|LOG|EXP|SIN|COS|TAN|ATN|LEN|VAL|ASC', - Name.Builtin + Name.Builtin, ), - ( r'FOR|TO|STEP|NEXT|IF|THEN|ELSE|RETURN|' r'GOSUB|' r'POKE|PEEK|' r'ON|END|CONT|SET|RESET|PRESET|TAB|SUB|FN|OFF|' r'USING|EOF|JOYSTK|FIX|POINT|MEM|VARPTR|INSTR|TIMER|PPOINT|USR', - Name.Function + Name.Function, ), - (r'([+\-*/^>=<])', Operator), (r'AND|OR|NOT', Operator.Word), - (r'"[^"\n]*.', String), (r'\d+|[-+]?\d*\.\d*(e[-+]?\d+)?', Number.Float), (r'[(),:]', Punctuation), @@ -102,8 +92,8 @@ def list_styles(style_name): for ndef, ttypes in existing_styles.items(): print(ndef) for ttype in sorted(ttypes): - print("\t%s" % str(ttype).split("Token.",1)[1]) + print(f'\t{ttype}') if __name__ == "__main__": - list_styles("default") \ No newline at end of file + list_styles("default") diff --git a/dragonlib/tests/__init__.py b/dragonlib/tests/__init__.py index 32d9a2b..0ccd34f 100644 --- a/dragonlib/tests/__init__.py +++ b/dragonlib/tests/__init__.py @@ -5,7 +5,6 @@ from bx_py_utils.test_utils.deny_requests import deny_any_real_request from cli_base.cli_tools.verbosity import MAX_LOG_LEVEL, setup_logging from rich import print # noqa - from typeguard import install_import_hook diff --git a/dragonlib/tests/test_api.py b/dragonlib/tests/test_api.py index c7517a8..1941329 100644 --- a/dragonlib/tests/test_api.py +++ b/dragonlib/tests/test_api.py @@ -30,7 +30,7 @@ def padding(data, size, b=b"\x00"): quanta, leftover = divmod(len(data), size) # Pad the last quantum with zero bits if necessary if leftover: - data += (b * (size - leftover)) + data += b * (size - leftover) return data @@ -52,30 +52,23 @@ def assertListing2Dump(self, ascii_listing, program_dump, program_start, debug=F print("\n" + "_" * 79) print(" *** Debug Listing:\n%s" % ascii_listing) print(" -" * 39) - print(" *** Debug Dump:\n%s\n" % ( - "\n".join(bin2hexline(program_dump)) - )) + print(" *** Debug Dump:\n%s\n" % ("\n".join(bin2hexline(program_dump)))) print(" -" * 39) - created_program_dump = self.dragon32api.ascii_listing2program_dump( - ascii_listing, program_start - ) + created_program_dump = self.dragon32api.ascii_listing2program_dump(ascii_listing, program_start) self.assertTrue(isinstance(created_program_dump, bytearray)) if debug: - print(" *** Created dump from listing:\n%s\n" % ( - pformat_program_dump(created_program_dump) - )) + print(" *** Created dump from listing:\n%s\n" % (pformat_program_dump(created_program_dump))) print(" -" * 39) if not created_program_dump: log.critical("Created dump empty? repr: %s", repr(created_program_dump)) if debug: - print(" *** Full Dump:\n%s" % "\n".join( - self.dragon32api.pformat_program_dump( - created_program_dump, program_start - ) - )) + print( + " *** Full Dump:\n%s" + % "\n".join(self.dragon32api.pformat_program_dump(created_program_dump, program_start)) + ) self.assertEqualProgramDump(created_program_dump, program_dump, program_start) def assertDump2Listing(self, ascii_listing, program_dump): @@ -137,13 +130,16 @@ def setUp(self): def test_load_from_dump(self): dump = ( - 0x1e, 0x07, # next_address - 0x00, 0x0a, # 10 - 0xa0, # CLS - 0x00, # end of line - 0x00, 0x00 # program end + 0x1E, + 0x07, # next_address + 0x00, + 0x0A, # 10 + 0xA0, # CLS + 0x00, # end of line + 0x00, + 0x00, # program end ) - basic_lines = self.dragon32api.listing.dump2basic_lines(dump, program_start=0x1e01) + basic_lines = self.dragon32api.listing.dump2basic_lines(dump, program_start=0x1E01) ascii_listing = basic_lines[0].get_content() self.assertEqual(ascii_listing, "10 CLS") self.assertEqual(len(basic_lines), 1) @@ -152,124 +148,192 @@ def test_tokens2ascii(self): self.basic_line.token_load( line_number=50, tokens=( - 0x49, 0x24, 0x20, 0xcb, 0x20, 0xff, 0x9a, 0x3a, 0x85, 0x20, 0x49, 0x24, 0xcb, 0x22, 0x22, 0x20, 0xbf, 0x20, - 0x35, 0x30, 0x00) + 0x49, + 0x24, + 0x20, + 0xCB, + 0x20, + 0xFF, + 0x9A, + 0x3A, + 0x85, + 0x20, + 0x49, + 0x24, + 0xCB, + 0x22, + 0x22, + 0x20, + 0xBF, + 0x20, + 0x35, + 0x30, + 0x00, + ), ) code = self.basic_line.get_content() - self.assertEqual(code, - '50 I$ = INKEY$:IF I$="" THEN 50' - ) + self.assertEqual(code, '50 I$ = INKEY$:IF I$="" THEN 50') def test_ascii2tokens01(self): - basic_lines = self.dragon32api.listing.ascii_listing2basic_lines( - '10 CLS' - ) + basic_lines = self.dragon32api.listing.ascii_listing2basic_lines('10 CLS') tokens = basic_lines[0].get_tokens() - self.assertHexList(tokens, [ - 0x00, 0x0a, # 10 - 0xa0, # CLS - ]) + self.assertHexList( + tokens, + [ + 0x00, + 0x0A, # 10 + 0xA0, # CLS + ], + ) self.assertEqual(len(basic_lines), 1) def test_ascii2tokens02(self): - basic_lines = self.dragon32api.listing.ascii_listing2basic_lines( - '50 I$ = INKEY$:IF I$="" THEN 50' - ) + basic_lines = self.dragon32api.listing.ascii_listing2basic_lines('50 I$ = INKEY$:IF I$="" THEN 50') tokens = basic_lines[0].get_tokens() - self.assertHexList(tokens, [ - 0x00, 0x32, # 50 - # I$ = INKEY$:IF I$="" THEN 50 - 0x49, 0x24, 0x20, 0xcb, 0x20, 0xff, 0x9a, 0x3a, 0x85, 0x20, 0x49, 0x24, 0xcb, 0x22, 0x22, 0x20, 0xbf, 0x20, - 0x35, 0x30, - ]) + self.assertHexList( + tokens, + [ + 0x00, + 0x32, # 50 + # I$ = INKEY$:IF I$="" THEN 50 + 0x49, + 0x24, + 0x20, + 0xCB, + 0x20, + 0xFF, + 0x9A, + 0x3A, + 0x85, + 0x20, + 0x49, + 0x24, + 0xCB, + 0x22, + 0x22, + 0x20, + 0xBF, + 0x20, + 0x35, + 0x30, + ], + ) self.assertEqual(len(basic_lines), 1) def test_format_tokens(self): - tokens = (0x49, 0x24, 0x20, 0xcb, 0x20, 0xff, 0x9a) # I$ = INKEY$ + tokens = (0x49, 0x24, 0x20, 0xCB, 0x20, 0xFF, 0x9A) # I$ = INKEY$ formated_tokens = self.token_util.pformat_tokens(tokens) - self.assertEqual(formated_tokens, [ - "\t $49 -> 'I'", - "\t $24 -> '$'", - "\t $20 -> ' '", - "\t $cb -> '='", - "\t $20 -> ' '", - "\t$ff9a -> 'INKEY$'", - ]) + self.assertEqual( + formated_tokens, + [ + "\t $49 -> 'I'", + "\t $24 -> '$'", + "\t $20 -> ' '", + "\t $cb -> '='", + "\t $20 -> ' '", + "\t$ff9a -> 'INKEY$'", + ], + ) class Dragon32BASIC_HighLevel_ApiTest(BaseDragon32ApiTestCase): def test_program_dump2ascii(self): - listing = self.dragon32api.program_dump2ascii_lines( - testdata.LISTING_02_BIN, program_start=0xabcd - ) + listing = self.dragon32api.program_dump2ascii_lines(testdata.LISTING_02_BIN, program_start=0xABCD) self.assertEqual("\n".join(listing), "\n".join(testdata.LISTING_02)) def test_ascii_listing2tokens(self): basic_program_ascii = "\n".join(testdata.LISTING_02) - program_dump = self.dragon32api.ascii_listing2program_dump( - basic_program_ascii, program_start=0xabcd - ) + program_dump = self.dragon32api.ascii_listing2program_dump(basic_program_ascii, program_start=0xABCD) # print("\n".join( # self.dragon32api.pformat_program_dump(program_dump, program_start=0xabcd) # )) - self.assertEqualProgramDump( - program_dump, testdata.LISTING_02_BIN, - program_start=0xabcd - ) + self.assertEqualProgramDump(program_dump, testdata.LISTING_02_BIN, program_start=0xABCD) def test_ascii2RAM01(self): tokens = self.dragon32api.ascii_listing2program_dump("10 CLS") # log_hexlist(ram_content) # log_hexlist(dump) - self.assertEqualProgramDump(tokens, ( - 0x1e, 0x07, # next_address - 0x00, 0x0a, # 10 - 0xa0, # CLS - 0x00, # end of line - 0x00, 0x00 # program end - ), program_start=0x1e01) + self.assertEqualProgramDump( + tokens, + ( + 0x1E, + 0x07, # next_address + 0x00, + 0x0A, # 10 + 0xA0, # CLS + 0x00, # end of line + 0x00, + 0x00, # program end + ), + program_start=0x1E01, + ) def test_ascii2RAM02(self): - tokens = self.dragon32api.ascii_listing2program_dump( - "10 A=1\n" - "20 B=2\n" - ) + tokens = self.dragon32api.ascii_listing2program_dump("10 A=1\n" "20 B=2\n") # log_hexlist(ram_content) # log_hexlist(dump) - self.assertEqualProgramDump(tokens, ( - 0x1e, 0x09, # next_address - 0x00, 0x0a, # 10 - 0x41, 0xcb, 0x31, # A=1 - 0x00, # end of line - 0x1e, 0x11, # next_address - 0x00, 0x14, # 20 - 0x42, 0xcb, 0x32, # B=2 - 0x00, # end of line - 0x00, 0x00 # program end - ), program_start=0x1e01) + self.assertEqualProgramDump( + tokens, + ( + 0x1E, + 0x09, # next_address + 0x00, + 0x0A, # 10 + 0x41, + 0xCB, + 0x31, # A=1 + 0x00, # end of line + 0x1E, + 0x11, # next_address + 0x00, + 0x14, # 20 + 0x42, + 0xCB, + 0x32, # B=2 + 0x00, # end of line + 0x00, + 0x00, # program end + ), + program_start=0x1E01, + ) def test_listing2program_strings_dont_in_comment(self): """ Don't replace tokens in comments """ - ascii_listing = self._prepare_text(""" + ascii_listing = self._prepare_text( + """ 10 'IF THEN ELSE" - """) + """ + ) program_dump = ( - 0x1e, 0x15, # next address - 0x00, 0x0a, # 10 - 0x3a, # : - 0x83, # ' - 0x49, 0x46, # I, F - 0x20, # " " - 0x54, 0x48, 0x45, 0x4e, # T, H, E, N - 0x20, # " " - 0x45, 0x4c, 0x53, 0x45, # E, L, S, E - 0x22, # " - 0x00, # end of line - 0x00, 0x00, # program end - ) - self.assertListing2Dump(ascii_listing, program_dump, program_start=0x1e01, + 0x1E, + 0x15, # next address + 0x00, + 0x0A, # 10 + 0x3A, # : + 0x83, # ' + 0x49, + 0x46, # I, F + 0x20, # " " + 0x54, + 0x48, + 0x45, + 0x4E, # T, H, E, N + 0x20, # " " + 0x45, + 0x4C, + 0x53, + 0x45, # E, L, S, E + 0x22, # " + 0x00, # end of line + 0x00, + 0x00, # program end + ) + self.assertListing2Dump( + ascii_listing, + program_dump, + program_start=0x1E01, # debug=True ) self.assertDump2Listing(ascii_listing, program_dump) @@ -278,43 +342,58 @@ def test_listing2program_strings_dont_in_strings(self): """ Don't replace tokens in strings """ - ascii_listing = self._prepare_text(""" + ascii_listing = self._prepare_text( + """ 10 PRINT"FOR NEXT - """) + """ + ) program_dump = ( - 0x1e, 0x10, # next address - 0x00, 0x0a, # 10 - 0x87, # PRINT - 0x22, # " - 0x46, 0x4f, 0x52, # F, O, R - 0x20, # " " - 0x4e, 0x45, 0x58, 0x54, # N, E, X, T - 0x00, # end of line - 0x00, 0x00, # program end - ) - self.assertListing2Dump(ascii_listing, program_dump, program_start=0x1e01, + 0x1E, + 0x10, # next address + 0x00, + 0x0A, # 10 + 0x87, # PRINT + 0x22, # " + 0x46, + 0x4F, + 0x52, # F, O, R + 0x20, # " " + 0x4E, + 0x45, + 0x58, + 0x54, # N, E, X, T + 0x00, # end of line + 0x00, + 0x00, # program end + ) + self.assertListing2Dump( + ascii_listing, + program_dump, + program_start=0x1E01, # debug=True ) self.assertDump2Listing(ascii_listing, program_dump) - def test_ascii_listing2program_dump_with_bigger_line_number(self): """ Don't replace tokens in strings """ - ascii_listing = self._prepare_text(""" + ascii_listing = self._prepare_text( + """ 65000 CLS - """) - program_dump = ( - 0x1e, 0x07, # start address - 0xfd, 0xe8, # 65000 - 0xa0, # CLS - 0x00, # end of line - 0x00, 0x00, # program end + """ ) - self.assertListing2Dump(ascii_listing, program_dump, program_start=0x1e01, - debug=True + program_dump = ( + 0x1E, + 0x07, # start address + 0xFD, + 0xE8, # 65000 + 0xA0, # CLS + 0x00, # end of line + 0x00, + 0x00, # program end ) + self.assertListing2Dump(ascii_listing, program_dump, program_start=0x1E01, debug=True) self.assertDump2Listing(ascii_listing, program_dump) def test_auto_add_colon_before_comment(self): @@ -325,21 +404,29 @@ def test_auto_add_colon_before_comment(self): See also: http://archive.worldofdragon.org/phpBB3/viewtopic.php?f=8&t=4310&p=11632#p11630 """ - ascii_listing = self._prepare_text(""" + ascii_listing = self._prepare_text( + """ 100 'FOO - """) + """ + ) program_dump = ( - 0x1e, 0x0b, # next address (length: 10) - 0x00, 0x64, # 100 (line number) - 0x3a, # : ===> Insert/remove it automaticly - 0x83, # ' - 0x46, # F - 0x4f, # O - 0x4f, # O - 0x00, # EOL - 0x00, 0x00, # end address - ) - self.assertListing2Dump(ascii_listing, program_dump, program_start=0x1e01, + 0x1E, + 0x0B, # next address (length: 10) + 0x00, + 0x64, # 100 (line number) + 0x3A, # : ===> Insert/remove it automaticly + 0x83, # ' + 0x46, # F + 0x4F, # O + 0x4F, # O + 0x00, # EOL + 0x00, + 0x00, # end address + ) + self.assertListing2Dump( + ascii_listing, + program_dump, + program_start=0x1E01, # debug=True ) self.assertDump2Listing(ascii_listing, program_dump) @@ -352,32 +439,40 @@ def test_auto_add_colon_before_else(self): See also: http://archive.worldofdragon.org/phpBB3/viewtopic.php?f=8&t=4310&p=11632#p11630 """ - ascii_listing = self._prepare_text(""" + ascii_listing = self._prepare_text( + """ 100 IF A=1 THEN 10 ELSE 20 - """) + """ + ) program_dump = ( - 0x1e, 0x16, # -> next address (length: 21) - 0x00, 0x64, # -> 100 (line number) - 0x85, # -> 'IF' - 0x20, # -> ' ' - 0x41, # -> 'A' - 0xcb, # -> '=' - 0x31, # -> '1' - 0x20, # -> ' ' - 0xbf, # -> 'THEN' - 0x20, # -> ' ' - 0x31, # -> '1' - 0x30, # -> '0' - 0x20, # -> ' ' - 0x3a, # : ===> Insert/remove it automaticly - 0x84, # -> 'ELSE' - 0x20, # -> ' ' - 0x32, # -> '2' - 0x30, # -> '0' - 0x00, # -> EOL - 0x00, 0x00, # -> end address - ) - self.assertListing2Dump(ascii_listing, program_dump, program_start=0x1e01, + 0x1E, + 0x16, # -> next address (length: 21) + 0x00, + 0x64, # -> 100 (line number) + 0x85, # -> 'IF' + 0x20, # -> ' ' + 0x41, # -> 'A' + 0xCB, # -> '=' + 0x31, # -> '1' + 0x20, # -> ' ' + 0xBF, # -> 'THEN' + 0x20, # -> ' ' + 0x31, # -> '1' + 0x30, # -> '0' + 0x20, # -> ' ' + 0x3A, # : ===> Insert/remove it automaticly + 0x84, # -> 'ELSE' + 0x20, # -> ' ' + 0x32, # -> '2' + 0x30, # -> '0' + 0x00, # -> EOL + 0x00, + 0x00, # -> end address + ) + self.assertListing2Dump( + ascii_listing, + program_dump, + program_start=0x1E01, # debug=True ) self.assertDump2Listing(ascii_listing, program_dump) @@ -386,49 +481,63 @@ def test_two_byte_line_numbers(self): """ Every line number is saved as one word! """ - ascii_listing = self._prepare_text(""" + ascii_listing = self._prepare_text( + """ 254 PRINT "A" 255 PRINT "B" 256 PRINT "C" 257 PRINT "D" - """) + """ + ) program_dump = ( # program start address: $1e01 - 0x1e, 0x0b, # -> next address (length: 10) - 0x00, 0xfe, # -> 254 (line number) - 0x87, # -> 'PRINT' - 0x20, # -> ' ' - 0x22, # -> '"' - 0x41, # -> 'A' - 0x22, # -> '"' - 0x00, # -> '\x00' - 0x1e, 0x15, # -> next address (length: 10) - 0x00, 0xff, # -> 255 (line number) - 0x87, # -> 'PRINT' - 0x20, # -> ' ' - 0x22, # -> '"' - 0x42, # -> 'B' - 0x22, # -> '"' - 0x00, # -> '\x00' - 0x1e, 0x1f, # -> next address (length: 10) - 0x01, 0x00, # -> 256 (line number) - 0x87, # -> 'PRINT' - 0x20, # -> ' ' - 0x22, # -> '"' - 0x43, # -> 'C' - 0x22, # -> '"' - 0x00, # -> '\x00' - 0x1e, 0x29, # -> next address (length: 10) - 0x01, 0x01, # -> 257 (line number) - 0x87, # -> 'PRINT' - 0x20, # -> ' ' - 0x22, # -> '"' - 0x44, # -> 'D' - 0x22, # -> '"' - 0x00, # -> '\x00' - 0x00, 0x00, # -> end address - ) - self.assertListing2Dump(ascii_listing, program_dump, program_start=0x1e01, + 0x1E, + 0x0B, # -> next address (length: 10) + 0x00, + 0xFE, # -> 254 (line number) + 0x87, # -> 'PRINT' + 0x20, # -> ' ' + 0x22, # -> '"' + 0x41, # -> 'A' + 0x22, # -> '"' + 0x00, # -> '\x00' + 0x1E, + 0x15, # -> next address (length: 10) + 0x00, + 0xFF, # -> 255 (line number) + 0x87, # -> 'PRINT' + 0x20, # -> ' ' + 0x22, # -> '"' + 0x42, # -> 'B' + 0x22, # -> '"' + 0x00, # -> '\x00' + 0x1E, + 0x1F, # -> next address (length: 10) + 0x01, + 0x00, # -> 256 (line number) + 0x87, # -> 'PRINT' + 0x20, # -> ' ' + 0x22, # -> '"' + 0x43, # -> 'C' + 0x22, # -> '"' + 0x00, # -> '\x00' + 0x1E, + 0x29, # -> next address (length: 10) + 0x01, + 0x01, # -> 257 (line number) + 0x87, # -> 'PRINT' + 0x20, # -> ' ' + 0x22, # -> '"' + 0x44, # -> 'D' + 0x22, # -> '"' + 0x00, # -> '\x00' + 0x00, + 0x00, # -> end address + ) + self.assertListing2Dump( + ascii_listing, + program_dump, + program_start=0x1E01, # debug=True ) self.assertDump2Listing(ascii_listing, program_dump) @@ -436,7 +545,8 @@ def test_two_byte_line_numbers(self): class RenumTests(BaseDragon32ApiTestCase): def test_renum01(self): - old_listing = self._prepare_text(""" + old_listing = self._prepare_text( + """ 1 PRINT "ONE" 11 GOTO 12 12 PRINT "FOO":GOSUB 15 @@ -444,12 +554,16 @@ def test_renum01(self): 15 PRINT "BAR" 16 RESUME 20 PRINT "END?" - """) + """ + ) # print old_listing # print "-"*79 new_listing = self.dragon32api.renum_ascii_listing(old_listing) # print new_listing - self.assertEqual(new_listing, self._prepare_text(""" + self.assertEqual( + new_listing, + self._prepare_text( + """ 10 PRINT "ONE" 20 GOTO 30 30 PRINT "FOO":GOSUB 50 @@ -457,84 +571,122 @@ def test_renum01(self): 50 PRINT "BAR" 60 RESUME 70 PRINT "END?" - """)) + """ + ), + ) def test_missing_number01(self): - old_listing = self._prepare_text(""" + old_listing = self._prepare_text( + """ 1 GOTO 2 2 GOTO 123 ' 123 didn't exists 3 IF A=1 THEN 456 ELSE 2 ' 456 didn't exists - """) + """ + ) new_listing = self.dragon32api.renum_ascii_listing(old_listing) # print new_listing - self.assertEqual(new_listing, self._prepare_text(""" + self.assertEqual( + new_listing, + self._prepare_text( + """ 10 GOTO 20 20 GOTO 123 ' 123 didn't exists 30 IF A=1 THEN 456 ELSE 20 ' 456 didn't exists - """)) + """ + ), + ) def test_on_goto(self): - old_listing = self._prepare_text(""" + old_listing = self._prepare_text( + """ 1 ON X GOTO 2,3 2 ?"A" 3 ?"B" - """) + """ + ) new_listing = self.dragon32api.renum_ascii_listing(old_listing) # print new_listing - self.assertEqual(new_listing, self._prepare_text(""" + self.assertEqual( + new_listing, + self._prepare_text( + """ 10 ON X GOTO 20,30 20 ?"A" 30 ?"B" - """)) + """ + ), + ) def test_on_goto_spaces(self): - old_listing = self._prepare_text(""" + old_listing = self._prepare_text( + """ 1 ON X GOTO 2,30 , 4, 555 2 ?"A" 30 ?"B" 4 ?"C" 555 ?"D" - """) + """ + ) new_listing = self.dragon32api.renum_ascii_listing(old_listing) # print new_listing - self.assertEqual(new_listing, self._prepare_text(""" + self.assertEqual( + new_listing, + self._prepare_text( + """ 10 ON X GOTO 20,30,40,50 20 ?"A" 30 ?"B" 40 ?"C" 50 ?"D" - """)) + """ + ), + ) def test_on_goto_space_after(self): - old_listing = self._prepare_text(""" + old_listing = self._prepare_text( + """ 1 ON X GOTO 1,2 ' space before comment? 2 ?"A" - """) + """ + ) # print old_listing # print "-"*79 new_listing = self.dragon32api.renum_ascii_listing(old_listing) # print new_listing - self.assertEqual(new_listing, self._prepare_text(""" + self.assertEqual( + new_listing, + self._prepare_text( + """ 10 ON X GOTO 10,20 ' space before comment? 20 ?"A" - """)) + """ + ), + ) def test_on_gosub_dont_exists(self): - old_listing = self._prepare_text(""" + old_listing = self._prepare_text( + """ 1 ON X GOSUB 1,2,3 2 ?"A" - """) + """ + ) # print old_listing # print "-"*79 new_listing = self.dragon32api.renum_ascii_listing(old_listing) # print new_listing - self.assertEqual(new_listing, self._prepare_text(""" + self.assertEqual( + new_listing, + self._prepare_text( + """ 10 ON X GOSUB 10,20,3 20 ?"A" - """)) + """ + ), + ) def test_get_destinations_1(self): - listing = self._prepare_text(""" + listing = self._prepare_text( + """ 10 PRINT "ONE" 20 GOTO 30 30 PRINT "FOO":GOSUB 50 @@ -544,60 +696,56 @@ def test_get_destinations_1(self): 70 PRINT "END?" 80 ON X GOTO 10, 20, 30 ,40 ,50 90 ON X GOSUB 10, 70, 999 - """) - destinations = self.dragon32api.renum_tool.get_destinations(listing) - self.assertEqual(destinations, - [10, 20, 30, 40, 50, 70, 999] + """ ) + destinations = self.dragon32api.renum_tool.get_destinations(listing) + self.assertEqual(destinations, [10, 20, 30, 40, 50, 70, 999]) def test_on_gosub_and_goto(self): - old_listing = self._prepare_text(""" + old_listing = self._prepare_text( + """ 2 PRINT "2" 13 ON X GOSUB 18 :ONY GOSUB 2,2:NEXT:GOTO99 18 PRINT "18" 99 PRINT "99" - """) + """ + ) # print(old_listing) # print("-"*79) new_listing = self.dragon32api.renum_ascii_listing(old_listing) # print(new_listing) - self.assertEqual(new_listing, self._prepare_text(""" + self.assertEqual( + new_listing, + self._prepare_text( + """ 10 PRINT "2" 20 ON X GOSUB 30 :ONY GOSUB 10,10:NEXT:GOTO40 30 PRINT "18" 40 PRINT "99" - """)) + """ + ), + ) class Dragon32bin(BaseDragon32ApiTestCase): def test_bas2bin_bin2bas_api_1(self): bas1 = "10 PRINT" - data = self.dragon32api.bas2bin(bas1, - load_address=0x1234, exec_address=0x5678 - ) + data = self.dragon32api.bas2bin(bas1, load_address=0x1234, exec_address=0x5678) bas2 = self.dragon32api.bin2bas(data) self.assertEqual(bas2, bas1) def test_bas2bin_bin2bas_api_2(self): bas1 = "\n".join(testdata.LISTING_01) - data = self.dragon32api.bas2bin(bas1, - load_address=0xabcd, exec_address=0xefef - ) + data = self.dragon32api.bas2bin(bas1, load_address=0xABCD, exec_address=0xEFEF) bas2 = self.dragon32api.bin2bas(data) self.assertEqual(bas2, bas1) def test_bas2bin_api_1(self): - bin = self.dragon32api.bas2bin( - "\n".join(testdata.LISTING_01), - load_address=0x1234, exec_address=0x5678 - ) + bin = self.dragon32api.bas2bin("\n".join(testdata.LISTING_01), load_address=0x1234, exec_address=0x5678) self.assertBinEqual(bin, testdata.LISTING_01_DOS_DUMP) def test_bas2bin_api_2(self): - bin = self.dragon32api.bas2bin( - "\n".join(testdata.LISTING_02), - load_address=0xabcd, exec_address=0xdcba - ) + bin = self.dragon32api.bas2bin("\n".join(testdata.LISTING_02), load_address=0xABCD, exec_address=0xDCBA) self.assertBinEqual(bin, testdata.LISTING_02_DOS_DUMP) def test_bin2bas(self): @@ -627,13 +775,8 @@ def test_bin2bas(self): self.assertEqual(bas1, example_listing) - bin = self.dragon32api.bas2bin( - bas1, - load_address=0x1e01, exec_address=0x1e01 - ) + bin = self.dragon32api.bas2bin(bas1, load_address=0x1E01, exec_address=0x1E01) bin = padding(bin, size=256, b=b"\x00") self.maxDiff = None self.assertBinEqual(bin, data) - - diff --git a/dragonlib/tests/test_base.py b/dragonlib/tests/test_base.py index 0d0d625..c24ef36 100644 --- a/dragonlib/tests/test_base.py +++ b/dragonlib/tests/test_base.py @@ -19,8 +19,9 @@ class BaseTestCase(unittest.TestCase): """ Only some special assertments. """ - maxDiff=3000 - + + maxDiff = 3000 + def assertHexList(self, first, second, msg=None): first = ["$%x" % value for value in first] second = ["$%x" % value for value in second] @@ -35,11 +36,11 @@ def assertEqualHex(self, hex1, hex2, msg=None): def assertIsByteRange(self, value): self.assertTrue(0x0 <= value, "Value (dez: %i - hex: %x) is negative!" % (value, value)) - self.assertTrue(0xff >= value, "Value (dez: %i - hex: %x) is greater than 0xff!" % (value, value)) + self.assertTrue(0xFF >= value, "Value (dez: %i - hex: %x) is greater than 0xff!" % (value, value)) def assertIsWordRange(self, value): self.assertTrue(0x0 <= value, "Value (dez: %i - hex: %x) is negative!" % (value, value)) - self.assertTrue(0xffff >= value, "Value (dez: %i - hex: %x) is greater than 0xffff!" % (value, value)) + self.assertTrue(0xFFFF >= value, "Value (dez: %i - hex: %x) is greater than 0xffff!" % (value, value)) def assertEqualHexByte(self, hex1, hex2, msg=None): self.assertIsByteRange(hex1) @@ -92,4 +93,4 @@ def assertEqual_dedent(self, first, second, msg=None): "%s\n" " -----------------------------------\n" ) % (err, first, second) - raise AssertionError(msg) \ No newline at end of file + raise AssertionError(msg) diff --git a/dragonlib/tests/test_basic_parser.py b/dragonlib/tests/test_basic_parser.py index 1567276..df400f6 100644 --- a/dragonlib/tests/test_basic_parser.py +++ b/dragonlib/tests/test_basic_parser.py @@ -10,7 +10,6 @@ """ - import logging import sys import unittest @@ -52,7 +51,8 @@ def test_only_code(self): 10 CLS 20 PRINT """ - self.assertParser(ascii_listing, + self.assertParser( + ascii_listing, { 10: [ """""", @@ -66,7 +66,8 @@ def test_only_code(self): def test_string(self): ascii_listing = '10 A$="A STRING"' - self.assertParser(ascii_listing, + self.assertParser( + ascii_listing, { 10: [ """""", @@ -80,7 +81,8 @@ def test_strings(self): ascii_listing = """ 10 A$="1":B=2:C$="4":CLS:PRINT "ONLY CODE" """ - self.assertParser(ascii_listing, + self.assertParser( + ascii_listing, { 10: [ """""", @@ -98,7 +100,8 @@ def test_string_and_comment(self): ascii_listing = """ 10 A$="NO :'REM" ' BUT HERE! """ - self.assertParser(ascii_listing, + self.assertParser( + ascii_listing, { 10: [ """""", @@ -114,7 +117,8 @@ def test_string_not_terminated(self): ascii_listing = """ 10 PRINT "NOT TERMINATED STRING """ - self.assertParser(ascii_listing, + self.assertParser( + ascii_listing, { 10: [ """""", @@ -128,7 +132,8 @@ def test_data(self): ascii_listing = """ 10 DATA 1,2,A,FOO """ - self.assertParser(ascii_listing, + self.assertParser( + ascii_listing, { 10: [ """""", @@ -142,7 +147,8 @@ def test_data_with_string1(self): ascii_listing = """ 10 DATA 1,2,"A","FOO BAR",4,5 """ - self.assertParser(ascii_listing, + self.assertParser( + ascii_listing, { 10: [ """""", @@ -156,7 +162,8 @@ def test_data_string_colon(self): ascii_listing = """ 10 DATA "FOO : BAR" """ - self.assertParser(ascii_listing, + self.assertParser( + ascii_listing, { 10: [ """""", @@ -170,7 +177,8 @@ def test_code_after_data(self): ascii_listing = """ 10 DATA "FOO : BAR":PRINT 123 """ - self.assertParser(ascii_listing, + self.assertParser( + ascii_listing, { 10: [ """""", @@ -185,7 +193,8 @@ def test_comment(self): ascii_listing = """ 10 REM A COMMENT """ - self.assertParser(ascii_listing, + self.assertParser( + ascii_listing, { 10: [ """""", @@ -199,7 +208,8 @@ def test_nothing_after_comment1(self): ascii_listing = """ 10 REM """ - self.assertParser(ascii_listing, + self.assertParser( + ascii_listing, { 10: [ """""", @@ -212,7 +222,8 @@ def test_nothing_after_comment2(self): ascii_listing = """ 10 ' """ - self.assertParser(ascii_listing, + self.assertParser( + ascii_listing, { 10: [ """""", @@ -225,7 +236,8 @@ def test_no_code_after_comment(self): ascii_listing = """ 10 REM FOR "FOO : BAR":PRINT 123 """ - self.assertParser(ascii_listing, + self.assertParser( + ascii_listing, { 10: [ """""", @@ -239,7 +251,8 @@ def test_comment2(self): ascii_listing = """ 10 A=2 ' FOR "FOO : BAR":PRINT 123 """ - self.assertParser(ascii_listing, + self.assertParser( + ascii_listing, { 10: [ """""", @@ -253,7 +266,8 @@ def test_no_comment(self): ascii_listing = """ 10 B$="'" """ - self.assertParser(ascii_listing, + self.assertParser( + ascii_listing, { 10: [ """""", @@ -269,7 +283,8 @@ def test_spaces_after_line_no(self): 20 PRINT I 30 NEXT """ - self.assertParser(ascii_listing, + self.assertParser( + ascii_listing, { 10: [ """""", @@ -281,7 +296,7 @@ def test_spaces_after_line_no(self): """""", ], }, -# print_parsed_lines=True + # print_parsed_lines=True ) @@ -289,18 +304,18 @@ def test_spaces_after_line_no(self): from dragonlib.utils.logging_utils import setup_logging setup_logging( -# level=1 # hardcore debug ;) -# level=10 # DEBUG -# level=20 # INFO -# level=30 # WARNING -# level=40 # ERROR - level=50 # CRITICAL/FATAL + # level=1 # hardcore debug ;) + # level=10 # DEBUG + # level=20 # INFO + # level=30 # WARNING + # level=40 # ERROR + level=50 # CRITICAL/FATAL ) unittest.main( argv=( sys.argv[0], -# "TestBASICParser.test_spaces_after_line_no", + # "TestBASICParser.test_spaces_after_line_no", ), # verbosity=1, verbosity=2, diff --git a/dragonlib/tests/test_binary_format.py b/dragonlib/tests/test_binary_format.py index 7eae9c7..fe2a2fc 100644 --- a/dragonlib/tests/test_binary_format.py +++ b/dragonlib/tests/test_binary_format.py @@ -11,14 +11,10 @@ import logging -import sys -import unittest from dragonlib.core.binary_files import BinaryFile from dragonlib.tests import testdata from dragonlib.tests.test_base import BaseTestCase -from dragonlib.utils.byte_word_values import bin2hexline -from dragonlib.utils.logging_utils import log_bytes log = logging.getLogger(__name__) @@ -28,11 +24,9 @@ def setUp(self): self.binary = BinaryFile() def test_load_from_bin1(self): - """ test with one line listing: testdata.LISTING_01 """ + """test with one line listing: testdata.LISTING_01""" # log_bytes(testdata.LISTING_01_BIN, msg="tokenised: %s") - self.binary.load_tokenised_dump(testdata.LISTING_01_BIN, - load_address=0x1234, exec_address=0x5678 - ) + self.binary.load_tokenised_dump(testdata.LISTING_01_BIN, load_address=0x1234, exec_address=0x5678) # self.binary.debug2log(level=logging.CRITICAL) self.assertBinEqual(self.binary.get_header(), testdata.LISTING_01_DOS_HEADER) @@ -42,13 +36,11 @@ def test_load_from_bin1(self): # log_bytes(testdata.LISTING_01_DOS_DUMP, msg="DragonDOS2: %s",level=logging.CRITICAL) self.assertBinEqual(dragon_bin, testdata.LISTING_01_DOS_DUMP) - + def test_load_from_bin2(self): - """ test with bigger testdata.LISTING_02 """ + """test with bigger testdata.LISTING_02""" # log_bytes(testdata.LISTING_02_BIN, msg="tokenised: %s") - self.binary.load_tokenised_dump(testdata.LISTING_02_BIN, - load_address=0xabcd, exec_address=0xdcba - ) + self.binary.load_tokenised_dump(testdata.LISTING_02_BIN, load_address=0xABCD, exec_address=0xDCBA) # self.binary.debug2log(level=logging.CRITICAL) self.assertBinEqual(self.binary.get_header(), testdata.LISTING_02_DOS_HEADER) @@ -57,4 +49,3 @@ def test_load_from_bin2(self): # log_bytes(dragon_bin, msg="DragonDOS: %s") self.assertBinEqual(dragon_bin, testdata.LISTING_02_DOS_DUMP) - diff --git a/dragonlib/tests/test_highlighting.py b/dragonlib/tests/test_highlighting.py index e712f59..3719725 100644 --- a/dragonlib/tests/test_highlighting.py +++ b/dragonlib/tests/test_highlighting.py @@ -19,12 +19,15 @@ class TestHighlighting(unittest.TestCase): def test(self): lexer = BasicLexer() - listing='10 PRINT"FOO"' + listing = '10 PRINT"FOO"' tokensource = lexer.get_tokens(listing) - self.assertEqual(list(tokensource), [ - (Token.Name.Label, '10'), - (Token.Text, ' '), - (Token.Name.Builtin, 'PRINT'), - (Token.Literal.String, '"FOO"'), - (Token.Text, '\n') - ]) + self.assertEqual( + list(tokensource), + [ + (Token.Name.Label, '10'), + (Token.Text, ' '), + (Token.Name.Builtin, 'PRINT'), + (Token.Literal.String, '"FOO"'), + (Token.Text, '\n'), + ], + ) diff --git a/dragonlib/tests/test_project_setup.py b/dragonlib/tests/test_project_setup.py index fee77c6..1e12a27 100644 --- a/dragonlib/tests/test_project_setup.py +++ b/dragonlib/tests/test_project_setup.py @@ -2,8 +2,8 @@ from unittest import TestCase from bx_py_utils.path import assert_is_file -from manageprojects.test_utils.project_setup import check_editor_config, get_py_max_line_length from cli_base.cli_tools.code_style import assert_code_style +from manageprojects.test_utils.project_setup import check_editor_config, get_py_max_line_length from packaging.version import Version from dragonlib import __version__ diff --git a/dragonlib/tests/testdata.py b/dragonlib/tests/testdata.py index aaf2bae..295b027 100644 --- a/dragonlib/tests/testdata.py +++ b/dragonlib/tests/testdata.py @@ -9,35 +9,28 @@ :license: GNU GPL v3 or above, see LICENSE for more details. """ - -LISTING_01= ('10 PRINT',) -LISTING_01_BIN = ( # program address: $1234 ! - b"\x12\x3a" # load address - b"\x00\x0a" # line number == 10 - b"\x87" # PRINT - b"\x00" # line end - b"\x00\x00" # listing end +LISTING_01 = ('10 PRINT',) +LISTING_01_BIN = ( # program address: $1234 ! + b"\x12\x3a" # load address + b"\x00\x0a" # line number == 10 + b"\x87" # PRINT + b"\x00" # line end + b"\x00\x00" # listing end ) -LISTING_01_DOS_HEADER = ( # Dragon DOS Binary Format header - b"\x55" # head byte - b"\x01" # filetype $01==.BAS - b"\x12\x34" # load address - b"\x00\x08" # length - b"\x56\x78" # exec address - b"\xAA" # terminator +LISTING_01_DOS_HEADER = ( # Dragon DOS Binary Format header + b"\x55" # head byte + b"\x01" # filetype $01==.BAS + b"\x12\x34" # load address + b"\x00\x08" # length + b"\x56\x78" # exec address + b"\xAA" # terminator ) LISTING_01_DOS_DUMP = LISTING_01_DOS_HEADER + LISTING_01_BIN -#-------------------------------------------------------------------------------------------- +# -------------------------------------------------------------------------------------------- -LISTING_02 = ( - '10 CLS', - '20 FOR I = 0 TO 255:', - '30 POKE 1024+(I*2),I', - '40 NEXT I', - '50 I$ = INKEY$:IF I$="" THEN 50' -) -LISTING_02_BIN = ( # program address: $abcd ! +LISTING_02 = ('10 CLS', '20 FOR I = 0 TO 255:', '30 POKE 1024+(I*2),I', '40 NEXT I', '50 I$ = INKEY$:IF I$="" THEN 50') +LISTING_02_BIN = ( # program address: $abcd ! b"\xab\xd3\x00\x0a\xa0\x00" b"\xab\xe6\x00\x14\x80\x20\x49\x20\xcb\x20\x30\x20\xbc\x20\x32\x35\x35\x3a\x00" b"\xab\xf9\x00\x1e\x93\x20\x31\x30\x32\x34\xc3\x28\x49\xc5\x32\x29\x2c\x49\x00" @@ -45,13 +38,12 @@ b"\xac\x1a\x00\x32\x49\x24\x20\xcb\x20\xff\x9a\x3a\x85\x20\x49\x24\xcb\x22\x22\x20\xbf\x20\x35\x30\x00" b"\x00\x00" ) -LISTING_02_DOS_HEADER = ( # Dragon DOS Binary Format header - b"\x55" # head byte - b"\x01" # filetype $01==.BAS - b"\xab\xcd" # load address - b"\x00\x4f" # length - b"\xdc\xba" # exec address - b"\xAA" # terminator +LISTING_02_DOS_HEADER = ( # Dragon DOS Binary Format header + b"\x55" # head byte + b"\x01" # filetype $01==.BAS + b"\xab\xcd" # load address + b"\x00\x4f" # length + b"\xdc\xba" # exec address + b"\xAA" # terminator ) LISTING_02_DOS_DUMP = LISTING_02_DOS_HEADER + LISTING_02_BIN - diff --git a/dragonlib/utils/auto_shift.py b/dragonlib/utils/auto_shift.py index 2f60206..bc73845 100644 --- a/dragonlib/utils/auto_shift.py +++ b/dragonlib/utils/auto_shift.py @@ -7,10 +7,9 @@ :license: GNU GPL v3 or above, see LICENSE for more details. """ - - import string + def invert_shift(chars): """ >>> invert_shift("a") @@ -24,10 +23,10 @@ def invert_shift(chars): result = "" for char in chars: if char in string.ascii_lowercase: -# log.critical("auto shift lowercase char %s to UPPERCASE", repr(char)) + # log.critical("auto shift lowercase char %s to UPPERCASE", repr(char)) char = char.upper() elif char in string.ascii_uppercase: -# log.critical("auto shift UPPERCASE char %s to lowercase", repr(char)) + # log.critical("auto shift UPPERCASE char %s to lowercase", repr(char)) char = char.lower() result += char return result @@ -35,6 +34,9 @@ def invert_shift(chars): if __name__ == '__main__': import doctest - print(doctest.testmod( - # verbose=1 - )) + + print( + doctest.testmod( + # verbose=1 + ) + ) diff --git a/dragonlib/utils/byte_word_values.py b/dragonlib/utils/byte_word_values.py index 07302af..b79dc06 100644 --- a/dragonlib/utils/byte_word_values.py +++ b/dragonlib/utils/byte_word_values.py @@ -14,34 +14,32 @@ import string -import six - def signed5(x): - """ convert to signed 5-bit """ - if x > 0xf: # 0xf == 2**4-1 == 15 - x = x - 0x20 # 0x20 == 2**5 == 32 + """convert to signed 5-bit""" + if x > 0xF: # 0xf == 2**4-1 == 15 + x = x - 0x20 # 0x20 == 2**5 == 32 return x def signed8(x): - """ convert to signed 8-bit """ - if x > 0x7f: # 0x7f == 2**7-1 == 127 - x = x - 0x100 # 0x100 == 2**8 == 256 + """convert to signed 8-bit""" + if x > 0x7F: # 0x7f == 2**7-1 == 127 + x = x - 0x100 # 0x100 == 2**8 == 256 return x def unsigned8(x): - """ convert a signed 8-Bit value into a unsigned value """ + """convert a signed 8-Bit value into a unsigned value""" if x < 0: - x = x + 0x0100 # 0x100 == 2**8 == 256 + x = x + 0x0100 # 0x100 == 2**8 == 256 return x def signed16(x): - """ convert to signed 16-bit """ - if x > 0x7fff: # 0x7fff == 2**15-1 == 32767 - x = x - 0x10000 # 0x100 == 2**16 == 65536 + """convert to signed 16-bit""" + if x > 0x7FFF: # 0x7fff == 2**15-1 == 32767 + x = x - 0x10000 # 0x100 == 2**16 == 65536 return x @@ -58,8 +56,8 @@ def word2bytes(value): ... AssertionError """ - assert 0 <= value <= 0xffff - return (value >> 8, value & 0xff) + assert 0 <= value <= 0xFFFF + return (value >> 8, value & 0xFF) def bytes2word(byte_list): @@ -130,7 +128,7 @@ def bin2hexline(data, add_addr=True, width=16): line = [] ascii_block = "" - for i in range(width): + for _ in range(width): b = data[addr] if chr(b) in printable: @@ -162,8 +160,4 @@ def _bin2hexline_example(): if __name__ == "__main__": - import doctest - - print(doctest.testmod(verbose=0)) - - # _bin2hexline_example() + _bin2hexline_example() diff --git a/dragonlib/utils/iter_utils.py b/dragonlib/utils/iter_utils.py index 0f15950..5f086c2 100644 --- a/dragonlib/utils/iter_utils.py +++ b/dragonlib/utils/iter_utils.py @@ -9,9 +9,6 @@ """ -import six - - # Obsolete if http://legacy.python.org/dev/peps/pep-0467/ merged: iter_bytes = iter @@ -20,48 +17,48 @@ def list_replace(iterable, src, dst): """ Thanks to "EyDu": http://www.python-forum.de/viewtopic.php?f=1&t=34539 (de) - + >>> list_replace([1,2,3], (1,2), "X") ['X', 3] - + >>> list_replace([1,2,3,4], (2,3), 9) [1, 9, 4] - + >>> list_replace([1,2,3], (2,), [9,8]) [1, 9, 8, 3] - + >>> list_replace([1,2,3,4,5], (2,3,4), "X") [1, 'X', 5] - + >>> list_replace([1,2,3,4,5], (4,5), "X") [1, 2, 3, 'X'] - + >>> list_replace([1,2,3,4,5], (1,2), "X") ['X', 3, 4, 5] - + >>> list_replace([1,2,3,3,3,4,5], (3,3), "X") [1, 2, 'X', 3, 4, 5] >>> list_replace([1,2,3,3,3,4,5], (3,3), ("A","B","C")) [1, 2, 'A', 'B', 'C', 3, 4, 5] - + >>> list_replace((58, 131, 73, 70), (58, 131), 131) [131, 73, 70] """ - result=[] - iterable=list(iterable) - + result = [] + iterable = list(iterable) + try: - dst=list(dst) - except TypeError: # e.g.: int - dst=[dst] - - src=list(src) - src_len=len(src) + dst = list(dst) + except TypeError: # e.g.: int + dst = [dst] + + src = list(src) + src_len = len(src) index = 0 while index < len(iterable): - element = iterable[index:index+src_len] -# print element, src + element = iterable[index:index + src_len] + # print element, src if element == src: result += dst index += src_len @@ -69,8 +66,3 @@ def list_replace(iterable, src, dst): result.append(iterable[index]) index += 1 return result - - -if __name__ == "__main__": - import doctest - print(doctest.testmod()) \ No newline at end of file diff --git a/dragonlib/utils/logging_utils.py b/dragonlib/utils/logging_utils.py index c605e36..fb5e9f7 100755 --- a/dragonlib/utils/logging_utils.py +++ b/dragonlib/utils/logging_utils.py @@ -9,14 +9,13 @@ :license: GNU GPL v3 or above, see LICENSE for more details. """ -import six -from dragonlib.utils.byte_word_values import bin2hexline - -xrange = range import logging import sys +from dragonlib.utils.byte_word_values import bin2hexline + + log = logging.getLogger(__name__) @@ -26,18 +25,16 @@ # log.critical("Fixed Log handlers: %s", repr(log.handlers)) -def get_log_levels(additional_levels=[100,99]): - levels = additional_levels[:] - try: - # Python 3 - levels += logging._nameToLevel.values() - except AttributeError: - # Python 2 - levels += [level for level in logging._levelNames if isinstance(level, int)] - +def get_log_levels(additional_levels=None): + if additional_levels is None: + levels = [100, 99] + else: + levels = additional_levels.copy() + levels += logging._nameToLevel.values() levels.sort() return levels + LOG_LEVELS = get_log_levels() @@ -101,31 +98,32 @@ def setup_logging(level, logger_name=None, handler=None, log_formatter=None): def log_memory_dump(memory, start, end, mem_info, level=99): log.log(level, "Memory dump from $%04x to $%04x:", start, end) - for addr in xrange(start, end + 1): + for addr in range(start, end + 1): value = memory[addr] if isinstance(value, int): msg = "$%04x: $%02x (dez: %i)" % (addr, value, value) else: - msg = "${:04x}: {} (is type: {})".format(addr, repr(value), type(value)) - msg = "%-25s| %s" % ( - msg, mem_info.get_shortest(addr) - ) + msg = f"${addr:04x}: {repr(value)} (is type: {type(value)})" + msg = "%-25s| %s" % (msg, mem_info.get_shortest(addr)) log.log(level, "\t%s", msg) def pformat_hex_list(hex_list): return " ".join(["$%x" % v for v in hex_list]) + def pformat_byte_hex_list(hex_list): return " ".join(["$%02x" % v for v in hex_list]) + def pformat_word_hex_list(hex_list): return " ".join(["$%02x" % v for v in hex_list]) + def log_hexlist(byte_list, group=8, start=0x0000, level=99): def _log(level, addr, line): msg = pformat_byte_hex_list(line) - msg = "{:04x} - {}".format(addr, msg) + msg = f"{addr:04x} - {msg}" log.log(level, msg) pos = 0 @@ -169,33 +167,118 @@ def log_hexlines(data, msg="Data:", level=logging.DEBUG, width=16): def test_run(): import os import subprocess + cmd_args = [ sys.executable, os.path.join("..", "DragonPy_CLI.py"), -# "-h" -# "--log_list", - "--verbosity", "50", - "--log", "DragonPy.cpu6809,50;dragonpy.Dragon32.MC6821_PIA,40", - -# "--verbosity", " 1", # hardcode DEBUG ;) -# "--verbosity", "10", # DEBUG -# "--verbosity", "20", # INFO -# "--verbosity", "30", # WARNING -# "--verbosity", "40", # ERROR -# "--verbosity", "50", # CRITICAL/FATAL -# "--verbosity", "99", # nearly all off - "--machine", "Dragon32", "run", -# "--machine", "Vectrex", "run", -# "--max_ops", "1", -# "--trace", + # "-h" + # "--log_list", + "--verbosity", + "50", + "--log", + "DragonPy.cpu6809,50;dragonpy.Dragon32.MC6821_PIA,40", + # "--verbosity", " 1", # hardcode DEBUG ;) + # "--verbosity", "10", # DEBUG + # "--verbosity", "20", # INFO + # "--verbosity", "30", # WARNING + # "--verbosity", "40", # ERROR + # "--verbosity", "50", # CRITICAL/FATAL + # "--verbosity", "99", # nearly all off + "--machine", + "Dragon32", + "run", + # "--machine", "Vectrex", "run", + # "--max_ops", "1", + # "--trace", ] print("Startup CLI with: %s" % " ".join(cmd_args[1:])) subprocess.Popen(cmd_args, cwd="..").wait() + if __name__ == "__main__": - dump = (0x1e, 0x07, 0x00, 0x0a, 0xa0, 0x00, 0x1e, 0x1a, 0x00, 0x14, 0x80, 0x20, 0x49, 0x20, 0xcb, 0x20, 0x30, 0x20, 0xbc, 0x20, 0x32, 0x35, 0x35, 0x3a, 0x00, 0x1e, 0x2d, 0x00, 0x1e, 0x93, 0x20, 0x31, 0x30, 0x32, 0x34, 0xc3, 0x28, 0x49, 0xc5, 0x32, 0x29, 0x2c, 0x49, 0x00, 0x1e, 0x35, 0x00, 0x28, 0x8b, 0x20, 0x49, 0x00, 0x1e, 0x4e, 0x00, 0x32, 0x49, 0x24, 0x20, 0xcb, 0x20, 0xff, 0x9a, 0x3a, 0x85, 0x20, 0x49, 0x24, 0xcb, 0x22, 0x22, 0x20, 0xbf, 0x20, 0x35, 0x30, 0x00, 0x00, 0x00) + dump = ( + 0x1E, + 0x07, + 0x00, + 0x0A, + 0xA0, + 0x00, + 0x1E, + 0x1A, + 0x00, + 0x14, + 0x80, + 0x20, + 0x49, + 0x20, + 0xCB, + 0x20, + 0x30, + 0x20, + 0xBC, + 0x20, + 0x32, + 0x35, + 0x35, + 0x3A, + 0x00, + 0x1E, + 0x2D, + 0x00, + 0x1E, + 0x93, + 0x20, + 0x31, + 0x30, + 0x32, + 0x34, + 0xC3, + 0x28, + 0x49, + 0xC5, + 0x32, + 0x29, + 0x2C, + 0x49, + 0x00, + 0x1E, + 0x35, + 0x00, + 0x28, + 0x8B, + 0x20, + 0x49, + 0x00, + 0x1E, + 0x4E, + 0x00, + 0x32, + 0x49, + 0x24, + 0x20, + 0xCB, + 0x20, + 0xFF, + 0x9A, + 0x3A, + 0x85, + 0x20, + 0x49, + 0x24, + 0xCB, + 0x22, + 0x22, + 0x20, + 0xBF, + 0x20, + 0x35, + 0x30, + 0x00, + 0x00, + 0x00, + ) log_hexlist(dump) -# log_hexlist(dump, group=4) -# log_hexlist(dump, group=5) + # log_hexlist(dump, group=4) + # log_hexlist(dump, group=5) test_run()