diff options
Diffstat (limited to 'gamechestcli')
| -rw-r--r-- | gamechestcli/.python-version | 1 | ||||
| -rw-r--r-- | gamechestcli/README.rst | 0 | ||||
| -rw-r--r-- | gamechestcli/gamechest/cliactions/run.py | 30 | ||||
| -rw-r--r-- | gamechestcli/gamechest/runners/extract.py | 102 | ||||
| -rw-r--r-- | gamechestcli/pyproject.toml | 21 | ||||
| -rw-r--r-- | gamechestcli/requirements-dev.txt | 2 | ||||
| -rw-r--r-- | gamechestcli/requirements.txt | 5 | ||||
| -rwxr-xr-x | gamechestcli/src/gamechest/__main__.py (renamed from gamechestcli/__main__.py) | 3 | ||||
| -rw-r--r-- | gamechestcli/src/gamechest/cliactions/install.py (renamed from gamechestcli/gamechest/cliactions/install.py) | 17 | ||||
| -rw-r--r-- | gamechestcli/src/gamechest/cliactions/remove.py (renamed from gamechestcli/gamechest/cliactions/remove.py) | 0 | ||||
| -rw-r--r-- | gamechestcli/src/gamechest/cliactions/run.py | 59 | ||||
| -rw-r--r-- | gamechestcli/src/gamechest/config.py (renamed from gamechestcli/gamechest/config.py) | 0 | ||||
| -rw-r--r-- | gamechestcli/src/gamechest/consts.py (renamed from gamechestcli/gamechest/consts.py) | 0 | ||||
| -rw-r--r-- | gamechestcli/src/gamechest/gameconfig.py (renamed from gamechestcli/gamechest/gameconfig.py) | 5 | ||||
| -rw-r--r-- | gamechestcli/src/gamechest/gamedb.py (renamed from gamechestcli/gamechest/gamedb.py) | 29 | ||||
| -rw-r--r-- | gamechestcli/src/gamechest/processor.py (renamed from gamechestcli/gamechest/processor.py) | 0 | ||||
| -rw-r--r-- | gamechestcli/src/gamechest/runners/download.py (renamed from gamechestcli/gamechest/runners/download.py) | 0 | ||||
| -rw-r--r-- | gamechestcli/src/gamechest/runners/extract.py | 224 | ||||
| -rw-r--r-- | gamechestcli/src/gamechest/runners/install.py (renamed from gamechestcli/gamechest/runners/install.py) | 2 | ||||
| -rw-r--r-- | gamechestcli/src/gamechest/runners/remove.py (renamed from gamechestcli/gamechest/runners/remove.py) | 0 | ||||
| -rw-r--r-- | gamechestcli/src/gamechest/runners/runnerbase.py (renamed from gamechestcli/gamechest/runners/runnerbase.py) | 0 | ||||
| -rw-r--r-- | gamechestcli/src/gamechest/runners/runnermulti.py (renamed from gamechestcli/gamechest/runners/runnermulti.py) | 0 | ||||
| -rw-r--r-- | gamechestcli/src/gamechest/statusdb.py (renamed from gamechestcli/gamechest/statusdb.py) | 0 | ||||
| -rw-r--r-- | gamechestcli/src/gamechest/structures.py (renamed from gamechestcli/gamechest/structures.py) | 0 | ||||
| -rw-r--r-- | gamechestcli/uv.lock | 343 |
25 files changed, 683 insertions, 160 deletions
diff --git a/gamechestcli/.python-version b/gamechestcli/.python-version new file mode 100644 index 0000000..24ee5b1 --- /dev/null +++ b/gamechestcli/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/gamechestcli/README.rst b/gamechestcli/README.rst new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/gamechestcli/README.rst diff --git a/gamechestcli/gamechest/cliactions/run.py b/gamechestcli/gamechest/cliactions/run.py deleted file mode 100644 index d6791d0..0000000 --- a/gamechestcli/gamechest/cliactions/run.py +++ /dev/null @@ -1,30 +0,0 @@ -import subprocess -import sys - -from ..gamedb import GameDB -from ..statusdb import StatusDB - - -def run_game(game_db, profile_id, game_info): - command = game_db.get_game_command(profile_id, game_info['id']) - #tools_bin_path = config.get_games_saves_tools_bin_path() - #new_env = dict(os.environ) - #new_env['PATH'] = f'{tools_bin_path}:{new_env["PATH"]}' - # path to mod/run scripts are already prepended by get_game_command, no - # need to modify the path environment variable. - subprocess.run(command) - - -def run(game_id, profile_id): - game_db = GameDB() - status_db = StatusDB() - game_info = game_db.get_game_info(game_id) - if not status_db.is_installed(game_info): - # games is already installed - print('Game', game_id, 'is not installed, aborting.', file=sys.stderr) - return - run_game(game_db, profile_id, game_info) - - -if __name__ == "__main__": - run(*sys.argv[1:]) diff --git a/gamechestcli/gamechest/runners/extract.py b/gamechestcli/gamechest/runners/extract.py deleted file mode 100644 index 6f13d89..0000000 --- a/gamechestcli/gamechest/runners/extract.py +++ /dev/null @@ -1,102 +0,0 @@ -import os -import re -import struct -import subprocess - -import humanfriendly - -from ..structures import Progress -from .runnerbase import RunnerBase, neutral_locale_variables - - -class Extract(RunnerBase): - - _progress_re = re.compile(r'^\s*(\S+)\s+\[([^]]+)/s\]\s+ETA\s+(\S+)\s*$') - - def __init__(self, src, dst): - import sys - print('src', src, 'dst', dst, file=sys.stderr) - common_parameters = dict( - encoding='utf8', - env={**os.environ, - **neutral_locale_variables, - }, - ) - self.src_size = os.stat(src).st_size - self.pv_proc = subprocess.Popen( - [ - 'pv', - '--force', - '--format', '%b %a %e', - src, - ], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - **common_parameters, - ) - lzip_command = '/usr/bin/lzip' - plzip_command = '/usr/bin/plzip' - uncompress_command_to_use = [lzip_command, '--decompress'] - first_word_magic = 0 - with open(src, 'rb') as stream: - first_word_magic = struct.unpack('>L', stream.read(4))[0] - if first_word_magic == 0x4c5a4950: # lzip magic - if os.path.exists(plzip_command): - uncompress_command_to_use = [plzip_command, '--decompress'] - elif first_word_magic == 0x28B52FFD: - uncompress_command_to_use = ['zstd', '-T0', '-d', '--stdout'] - self.zip_proc = subprocess.Popen( - uncompress_command_to_use, - stdin=self.pv_proc.stdout, - stdout=subprocess.PIPE, - **common_parameters, - ) - self.tar_proc = subprocess.Popen( - [ - 'tar', - '-C', dst, - '-xf', '-' - ], - stdin=self.zip_proc.stdout, - **common_parameters, - ) - self.last_progress = Progress() - - def get_read_fd(self): - return self.pv_proc.stderr - - def progress_read(self): - line = self.pv_proc.stderr.readline() - if match := self._progress_re.search(line): - written_bytes = humanfriendly.parse_size(match.group(1)) - self.last_progress = Progress( - nbbytes=written_bytes, - percent=int(100 * written_bytes / self.src_size), - speed=humanfriendly.parse_size(match.group(2)), - eta=match.group(3), - ) - return self.last_progress - - def terminate(self): - for proc in (self.pv_proc, self.zip_proc, self.tar_proc): - proc.terminate() - - def poll(self): - return self.tar_proc.poll() - - def close(self): - self.pv_proc.wait() - self.zip_proc.wait() - self.tar_proc.wait() - - -if __name__ == '__main__': - import contextlib - import sys - with contextlib.suppress(KeyboardInterrupt): - with Extract(sys.argv[1], sys.argv[2]) as runner: - while runner.poll() is None: - print(runner.progress_read()) - rc = runner.poll() - print('ended with code:', rc) - print('test main ended') diff --git a/gamechestcli/pyproject.toml b/gamechestcli/pyproject.toml new file mode 100644 index 0000000..804fd94 --- /dev/null +++ b/gamechestcli/pyproject.toml @@ -0,0 +1,21 @@ +[project] +name = "gamechest" +version = "0.1.0" +description = "Add your description here" +readme = "README.rst" +requires-python = ">=3.13" +dependencies = [ + "docopt-ng>=0.9.0", + "humanfriendly>=10.0", + "pyyaml>=6.0.3", + "rich>=14.3.2", + "xdg>=6.0.0", +] +[project.scripts] +gamechest = "gamechest:__main__.main" + +[dependency-groups] +dev = [ + "ipdb>=0.13.13", + "ipython>=9.10.0", +] diff --git a/gamechestcli/requirements-dev.txt b/gamechestcli/requirements-dev.txt deleted file mode 100644 index fe96c42..0000000 --- a/gamechestcli/requirements-dev.txt +++ /dev/null @@ -1,2 +0,0 @@ -ipython -ipdb diff --git a/gamechestcli/requirements.txt b/gamechestcli/requirements.txt deleted file mode 100644 index 638d583..0000000 --- a/gamechestcli/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -docopt -humanfriendly -pyyaml -rich -xdg diff --git a/gamechestcli/__main__.py b/gamechestcli/src/gamechest/__main__.py index 73d5bab..8452b99 100755 --- a/gamechestcli/__main__.py +++ b/gamechestcli/src/gamechest/__main__.py @@ -9,6 +9,9 @@ Usage: gamechest install <GAME_ID> gamechest list [--installed|--not-installed] gamechest showconfig +Options: + --profile_id=<PROFILE_ID>, -p <PROFILE_ID> + use profile <PROFILE_ID> instead of the default one. ''' import sys diff --git a/gamechestcli/gamechest/cliactions/install.py b/gamechestcli/src/gamechest/cliactions/install.py index 508dd48..90e9ac4 100644 --- a/gamechestcli/gamechest/cliactions/install.py +++ b/gamechestcli/src/gamechest/cliactions/install.py @@ -8,12 +8,21 @@ from ..statusdb import StatusDB def install_game(status_db, game_info): remote_basedir = config.get_remote_basedir() - source = f'{remote_basedir}/{game_info["package_name"]}' + package_name = game_info.get("package_name") dest = config.get_games_install_basedir() / game_info['id'] dest.mkdir(parents=True, exist_ok=True) - title = 'Installing game...' - task = functools.partial(Install, source, dest) - processor.process_task(title, task) + # now as of 2026-01-14: not having a package_name or having it set to none + # means there is no installation archive (all is managed by the runner), + # thus consider it installed with version given without extracting + # anything. + if package_name: + title = 'Installing game...' + source = f'{remote_basedir}/{package_name}' + task = functools.partial(Install, source, dest) + processor.process_task(title, task) + else: + (dest / "dummy").mkdir(parents=True, exist_ok=True) + status_db.set_installed(game_info) diff --git a/gamechestcli/gamechest/cliactions/remove.py b/gamechestcli/src/gamechest/cliactions/remove.py index 6573aff..6573aff 100644 --- a/gamechestcli/gamechest/cliactions/remove.py +++ b/gamechestcli/src/gamechest/cliactions/remove.py diff --git a/gamechestcli/src/gamechest/cliactions/run.py b/gamechestcli/src/gamechest/cliactions/run.py new file mode 100644 index 0000000..1c55cc4 --- /dev/null +++ b/gamechestcli/src/gamechest/cliactions/run.py @@ -0,0 +1,59 @@ +import os +import subprocess +import sys +from pathlib import Path + +from ..gameconfig import config +from ..gamedb import GameDB +from ..statusdb import StatusDB + + +def run_game(game_db, profile_id, game_info): + command = game_db.get_game_command(profile_id, game_info['id']) + profile_dir = Path(config.get_profile_dir(profile_id)) + profile_game_dir = profile_dir / game_info['id'] + game_dir = config.get_games_install_basedir() / game_info['id'] + # note: glob('*/') globs regular files too, thus I filter on is_dir. + game_dir = list(item + for item in game_dir.glob('*') if item.is_dir())[-1] + game_dir = Path(game_dir) + #tools_bin_path = config.get_games_saves_tools_bin_path() + #new_env = dict(os.environ) + #new_env['PATH'] = f'{tools_bin_path}:{new_env["PATH"]}' + # path to mod/run scripts are already prepended by get_game_command, no + # need to modify the path environment variable. + profile_lockdir = profile_dir / '.lock' + try: + profile_lockdir.mkdir(parents=True, exist_ok=False) + except FileExistsError: + print(f'ERROR: profile is already locked, {profile_lockdir}', + file=sys.stderr) + sys.exit(1) + try: + subprocess.run( + command, + env={ + **os.environ, + 'PROFILE_DIR': str(profile_game_dir), + 'GAME_DIR': str(game_dir), + 'GAME_ID': game_info['id'], + }, + ) + finally: + profile_lockdir.rmdir() + print(f'INFO: {profile_lockdir} removed') + + +def run(game_id, profile_id): + game_db = GameDB() + status_db = StatusDB() + game_info = game_db.get_game_info(game_id) + if not status_db.is_installed(game_info): + # games is already installed + print('Game', game_id, 'is not installed, aborting.', file=sys.stderr) + return + run_game(game_db, profile_id, game_info) + + +if __name__ == "__main__": + run(*sys.argv[1:]) diff --git a/gamechestcli/gamechest/config.py b/gamechestcli/src/gamechest/config.py index cd0c7e8..cd0c7e8 100644 --- a/gamechestcli/gamechest/config.py +++ b/gamechestcli/src/gamechest/config.py diff --git a/gamechestcli/gamechest/consts.py b/gamechestcli/src/gamechest/consts.py index 091fd70..091fd70 100644 --- a/gamechestcli/gamechest/consts.py +++ b/gamechestcli/src/gamechest/consts.py diff --git a/gamechestcli/gamechest/gameconfig.py b/gamechestcli/src/gamechest/gameconfig.py index 5e46efa..0b3d855 100644 --- a/gamechestcli/gamechest/gameconfig.py +++ b/gamechestcli/src/gamechest/gameconfig.py @@ -34,11 +34,14 @@ class GameConfig: game_config_path.mkdir(parents=True, exist_ok=True) game_config_filepath = game_config_path / 'config.yaml' self.game_config_filepath = game_config_filepath + self.config = {} with contextlib.ExitStack() as stack: stack.enter_context(contextlib.suppress(FileNotFoundError)) fdin = stack.enter_context(open(game_config_filepath, 'r', encoding='utf8')) - self.config = yaml.safe_load(fdin) + # in case yaml file is empty, None will be returned, in this case + # fallback to empty dict. + self.config = yaml.safe_load(fdin) or {} self.config = { **DEFAULT_CONFIG_DICT, **self.config, diff --git a/gamechestcli/gamechest/gamedb.py b/gamechestcli/src/gamechest/gamedb.py index bec90d3..1f9e8d8 100644 --- a/gamechestcli/gamechest/gamedb.py +++ b/gamechestcli/src/gamechest/gamedb.py @@ -27,24 +27,25 @@ class GameDB: def get_game_command(self, profile_id, game_id=None): game_info = self.get_game_info(game_id) game_mods = game_info.get('mods', []) - game_mods += ['locked-run-profiledir'] - - game_dir = config.get_games_install_basedir() / game_info['id'] - # note: glob('*/') globs regular files too, thus I filter on is_dir. - game_dir = list(item - for item in game_dir.glob('*') if item.is_dir())[-1] - + #game_mods += ['locked-run-profiledir'] + #game_dir = config.get_games_install_basedir() / game_info['id'] + ## note: glob('*/') globs regular files too, thus I filter on is_dir. + #game_dir = list(item + # for item in game_dir.glob('*') if item.is_dir())[-1] tools_bin_path = config.get_games_saves_tools_bin_path() - profile_dir = config.get_profile_dir(profile_id) - + #profile_dir = config.get_profile_dir(profile_id) + command_from_game_info = [ + f'{tools_bin_path}/runners/{game_info["command"][0]}', + *game_info['command'][1:], + ] if game_info.get('command') else [ + f'{tools_bin_path}/runners/run-{game_info["id"]}', + ] command = [ + # mods *[f'{tools_bin_path}/mod-{item}' for item in game_mods], - profile_dir, - game_dir, - *[item if index > 0 else f'{tools_bin_path}/runners/{item}' - for index, item in enumerate(game_info['command'])], + # runner + *command_from_game_info, ] - return command def get_ids(self): diff --git a/gamechestcli/gamechest/processor.py b/gamechestcli/src/gamechest/processor.py index e31d023..e31d023 100644 --- a/gamechestcli/gamechest/processor.py +++ b/gamechestcli/src/gamechest/processor.py diff --git a/gamechestcli/gamechest/runners/download.py b/gamechestcli/src/gamechest/runners/download.py index 8629b16..8629b16 100644 --- a/gamechestcli/gamechest/runners/download.py +++ b/gamechestcli/src/gamechest/runners/download.py diff --git a/gamechestcli/src/gamechest/runners/extract.py b/gamechestcli/src/gamechest/runners/extract.py new file mode 100644 index 0000000..970c956 --- /dev/null +++ b/gamechestcli/src/gamechest/runners/extract.py @@ -0,0 +1,224 @@ +import os +import re +import select +import struct +import subprocess +import threading +import zipfile + +import humanfriendly + +from ..structures import Progress +from .runnerbase import RunnerBase, neutral_locale_variables + + +class ExtractZip(RunnerBase): + """ + Simple zip wrapper without multithreading. Should not be an issue as zip + is used for little archives only, bigger archives are tar+zstd with + multithreaded algorithms. So keep this class simple. + """ + + def __init__(self, src, dst): + self.last_progress = Progress() + self.read_fd, self.write_fd = os.pipe() + self.cancel_event = threading.Event() + + # No need to lock this unless GIL is disabled. + self.written_bytes = 0 + self.total_bytes = 0 + self.progress = 0 + + def extract_and_report(): + try: + chunk_size = 1024**2 # 1MiB chunks + with zipfile.ZipFile(src, 'r') as zip_ref: + self.total_bytes = sum(getattr(file_info, 'file_size', 0) for file_info in zip_ref.infolist() if not file_info.is_dir()) + for file_info in zip_ref.infolist(): + if file_info.is_dir(): + continue + with zip_ref.open(file_info) as source_file: + target_path = os.path.join(dst, file_info.filename) + os.makedirs(os.path.dirname(target_path), exist_ok=True) + with open(target_path, 'wb') as target_file: + while not self.cancel_event.is_set(): + chunk = source_file.read(chunk_size) + if not chunk or self.cancel_event.is_set(): + # if we have read everything from the + # source file, OR, we got a cancel event + # in-between the read and the write, we + # break. + break + target_file.write(chunk) + self.written_bytes += len(chunk) + self.progress = (self.written_bytes / self.total_bytes) * 100 + #progress_message = f"Progress: {progress:.2f}% ({bytes_written} of {total_bytes} bytes)\n" + os.write(self.write_fd, b'.') + finally: + self.cancel_event.set() + self.progress = 100 + os.write(self.write_fd, b'.') + #os.close(self.read_fd) + #os.close(self.write_fd) + + # Create a thread for extraction + extraction_thread = threading.Thread(target=extract_and_report) + extraction_thread.start() + self.extraction_thread = extraction_thread + + + def get_read_fd(self): + return self.read_fd + + def progress_read(self): + ready, _, _ = select.select([self.read_fd], [], []) + if ready: + # flush any present data from the pipe + os.read(self.read_fd, 1024) + self.last_progress = Progress( + nbbytes=self.written_bytes, + percent=int(100 * self.written_bytes / self.total_bytes), + #speed=humanfriendly.parse_size(match.group(2)), + speed=0, + eta="UNK", + ) + return self.last_progress + + def terminate(self): + self.close() # same as self.close, can be called multiple time + + def poll(self): + return None if not self.cancel_event.is_set() else 0 + + def close(self): + self.cancel_event.set() + self.extraction_thread.join() + + +class ExtractTar(RunnerBase): + + _progress_re = re.compile(r'^\s*(\S+)\s+\[([^]]+)/s\]\s+ETA\s+(\S+)\s*$') + + def __init__(self, src, dst, compression_type): + common_parameters = dict( + encoding='utf8', + env={**os.environ, + **neutral_locale_variables, + }, + ) + self.src_size = os.stat(src).st_size + self.pv_proc = subprocess.Popen( + [ + 'pv', + '--force', + '--format', '%b %a %e', + src, + ], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + **common_parameters, + ) + lzip_command = '/usr/bin/lzip' + plzip_command = '/usr/bin/plzip' + uncompress_command_to_use = [lzip_command, '--decompress'] + if compression_type == 'lzip': + if os.path.exists(plzip_command): + uncompress_command_to_use = [plzip_command, '--decompress'] + elif compression_type == 'zstd': + uncompress_command_to_use = ['zstd', '-T0', '-d', '--stdout'] + self.zip_proc = subprocess.Popen( + uncompress_command_to_use, + stdin=self.pv_proc.stdout, + stdout=subprocess.PIPE, + **common_parameters, + ) + self.tar_proc = subprocess.Popen( + [ + 'tar', + '-C', dst, + '-xf', '-' + ], + stdin=self.zip_proc.stdout, + **common_parameters, + ) + self.last_progress = Progress() + + def get_read_fd(self): + return self.pv_proc.stderr + + def progress_read(self): + line = self.pv_proc.stderr.readline() + if match := self._progress_re.search(line): + written_bytes = humanfriendly.parse_size(match.group(1)) + self.last_progress = Progress( + nbbytes=written_bytes, + percent=int(100 * written_bytes / self.src_size), + speed=humanfriendly.parse_size(match.group(2)), + eta=match.group(3), + ) + return self.last_progress + + def terminate(self): + for proc in (self.pv_proc, self.zip_proc, self.tar_proc): + proc.terminate() + + def poll(self): + return self.tar_proc.poll() + + def close(self): + self.pv_proc.wait() + self.zip_proc.wait() + self.tar_proc.wait() + + +class Extract(RunnerBase): + def __init__(self, src, dst): + zip_magics = ( + b'PK\x03\x04', + b'PK\x05\x06', # empty + b'PK\x07\x08', # spanned + ) + + first_word_magic = 0 + first_word_magic_b = b'' + compression_type = None + with open(src, 'rb') as stream: + first_word_magic_b = stream.read(4) + first_word_magic = struct.unpack('>L', first_word_magic_b)[0] + if first_word_magic_b in zip_magics: + compression_type = 'zip' + elif first_word_magic == 0x4c5a4950: # lzip magic + compression_type = 'lzip' + elif first_word_magic == 0x28B52FFD: # zstd magic + compression_type = 'zstd' + if compression_type == 'zip': + self.inner_class = ExtractZip(src, dst) + else: + self.inner_class = ExtractTar(src, dst, compression_type) + + def get_read_fd(self): + return self.inner_class.get_read_fd() + + def progress_read(self): + return self.inner_class.progress_read() + + def terminate(self): + return self.inner_class.terminate() + + def poll(self): + return self.inner_class.poll() + + def close(self): + return self.inner_class.close() + + +if __name__ == '__main__': + import contextlib + import sys + with contextlib.suppress(KeyboardInterrupt): + with Extract(sys.argv[1], sys.argv[2]) as runner: + while runner.poll() is None: + print(runner.progress_read()) + rc = runner.poll() + print('ended with code:', rc) + print('test main ended') diff --git a/gamechestcli/gamechest/runners/install.py b/gamechestcli/src/gamechest/runners/install.py index 7b2ff7c..0b37304 100644 --- a/gamechestcli/gamechest/runners/install.py +++ b/gamechestcli/src/gamechest/runners/install.py @@ -1,5 +1,4 @@ import functools -import os from pathlib import Path from .download import Download @@ -24,7 +23,6 @@ if __name__ == '__main__': import contextlib import selectors import sys - import time print('main test') with contextlib.ExitStack() as stack: stack.enter_context(contextlib.suppress(KeyboardInterrupt)) diff --git a/gamechestcli/gamechest/runners/remove.py b/gamechestcli/src/gamechest/runners/remove.py index 87177f3..87177f3 100644 --- a/gamechestcli/gamechest/runners/remove.py +++ b/gamechestcli/src/gamechest/runners/remove.py diff --git a/gamechestcli/gamechest/runners/runnerbase.py b/gamechestcli/src/gamechest/runners/runnerbase.py index ac64ebb..ac64ebb 100644 --- a/gamechestcli/gamechest/runners/runnerbase.py +++ b/gamechestcli/src/gamechest/runners/runnerbase.py diff --git a/gamechestcli/gamechest/runners/runnermulti.py b/gamechestcli/src/gamechest/runners/runnermulti.py index 2a5e17c..2a5e17c 100644 --- a/gamechestcli/gamechest/runners/runnermulti.py +++ b/gamechestcli/src/gamechest/runners/runnermulti.py diff --git a/gamechestcli/gamechest/statusdb.py b/gamechestcli/src/gamechest/statusdb.py index 3f2af31..3f2af31 100644 --- a/gamechestcli/gamechest/statusdb.py +++ b/gamechestcli/src/gamechest/statusdb.py diff --git a/gamechestcli/gamechest/structures.py b/gamechestcli/src/gamechest/structures.py index 35a8861..35a8861 100644 --- a/gamechestcli/gamechest/structures.py +++ b/gamechestcli/src/gamechest/structures.py diff --git a/gamechestcli/uv.lock b/gamechestcli/uv.lock new file mode 100644 index 0000000..a6019da --- /dev/null +++ b/gamechestcli/uv.lock @@ -0,0 +1,343 @@ +version = 1 +revision = 3 +requires-python = ">=3.13" + +[[package]] +name = "asttokens" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/a5/8e3f9b6771b0b408517c82d97aed8f2036509bc247d46114925e32fe33f0/asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7", size = 62308, upload-time = "2025-11-15T16:43:48.578Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a", size = 27047, upload-time = "2025-11-15T16:43:16.109Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "decorator" +version = "5.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, +] + +[[package]] +name = "docopt-ng" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/50/8d6806cf13138127692ae6ff79ddeb4e25eb3b0bcc3c1bd033e7e04531a9/docopt_ng-0.9.0.tar.gz", hash = "sha256:91c6da10b5bb6f2e9e25345829fb8278c78af019f6fc40887ad49b060483b1d7", size = 32264, upload-time = "2023-05-30T20:46:25.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/4a/c3b77fc1a24510b08918b43a473410c0168f6e657118807015f1f1edceea/docopt_ng-0.9.0-py3-none-any.whl", hash = "sha256:bfe4c8b03f9fca424c24ee0b4ffa84bf7391cb18c29ce0f6a8227a3b01b81ff9", size = 16689, upload-time = "2023-05-30T20:46:45.294Z" }, +] + +[[package]] +name = "executing" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, +] + +[[package]] +name = "gamechestcli" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "docopt-ng" }, + { name = "humanfriendly" }, + { name = "pyyaml" }, + { name = "rich" }, + { name = "xdg" }, +] + +[package.dev-dependencies] +dev = [ + { name = "ipdb" }, + { name = "ipython" }, +] + +[package.metadata] +requires-dist = [ + { name = "docopt-ng", specifier = ">=0.9.0" }, + { name = "humanfriendly", specifier = ">=10.0" }, + { name = "pyyaml", specifier = ">=6.0.3" }, + { name = "rich", specifier = ">=14.3.2" }, + { name = "xdg", specifier = ">=6.0.0" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "ipdb", specifier = ">=0.13.13" }, + { name = "ipython", specifier = ">=9.10.0" }, +] + +[[package]] +name = "humanfriendly" +version = "10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyreadline3", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/3f/2c29224acb2e2df4d2046e4c73ee2662023c58ff5b113c4c1adac0886c43/humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc", size = 360702, upload-time = "2021-09-17T21:40:43.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", size = 86794, upload-time = "2021-09-17T21:40:39.897Z" }, +] + +[[package]] +name = "ipdb" +version = "0.13.13" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "decorator" }, + { name = "ipython" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/1b/7e07e7b752017f7693a0f4d41c13e5ca29ce8cbcfdcc1fd6c4ad8c0a27a0/ipdb-0.13.13.tar.gz", hash = "sha256:e3ac6018ef05126d442af680aad863006ec19d02290561ac88b8b1c0b0cfc726", size = 17042, upload-time = "2023-03-09T15:40:57.487Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/4c/b075da0092003d9a55cf2ecc1cae9384a1ca4f650d51b00fc59875fe76f6/ipdb-0.13.13-py3-none-any.whl", hash = "sha256:45529994741c4ab6d2388bfa5d7b725c2cf7fe9deffabdb8a6113aa5ed449ed4", size = 12130, upload-time = "2023-03-09T15:40:55.021Z" }, +] + +[[package]] +name = "ipython" +version = "9.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "ipython-pygments-lexers" }, + { name = "jedi" }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/60/2111715ea11f39b1535bed6024b7dec7918b71e5e5d30855a5b503056b50/ipython-9.10.0.tar.gz", hash = "sha256:cd9e656be97618a0676d058134cd44e6dc7012c0e5cb36a9ce96a8c904adaf77", size = 4426526, upload-time = "2026-02-02T10:00:33.594Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/aa/898dec789a05731cd5a9f50605b7b44a72bd198fd0d4528e11fc610177cc/ipython-9.10.0-py3-none-any.whl", hash = "sha256:c6ab68cc23bba8c7e18e9b932797014cc61ea7fd6f19de180ab9ba73e65ee58d", size = 622774, upload-time = "2026-02-02T10:00:31.503Z" }, +] + +[[package]] +name = "ipython-pygments-lexers" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" }, +] + +[[package]] +name = "jedi" +version = "0.19.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, +] + +[[package]] +name = "matplotlib-inline" +version = "0.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/74/97e72a36efd4ae2bccb3463284300f8953f199b5ffbc04cbbb0ec78f74b1/matplotlib_inline-0.2.1.tar.gz", hash = "sha256:e1ee949c340d771fc39e241ea75683deb94762c8fa5f2927ec57c83c4dffa9fe", size = 8110, upload-time = "2025-10-23T09:00:22.126Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76", size = 9516, upload-time = "2025-10-23T09:00:20.675Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "parso" +version = "0.8.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/de/53e0bcf53d13e005bd8c92e7855142494f41171b34c2536b86187474184d/parso-0.8.5.tar.gz", hash = "sha256:034d7354a9a018bdce352f48b2a8a450f05e9d6ee85db84764e9b6bd96dafe5a", size = 401205, upload-time = "2025-08-23T15:15:28.028Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl", hash = "sha256:646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887", size = 106668, upload-time = "2025-08-23T15:15:25.663Z" }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.52" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pyreadline3" +version = "3.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/49/4cea918a08f02817aabae639e3d0ac046fef9f9180518a3ad394e22da148/pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7", size = 99839, upload-time = "2024-09-19T02:40:10.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/dc/491b7661614ab97483abf2056be1deee4dc2490ecbf7bff9ab5cdbac86e1/pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6", size = 83178, upload-time = "2024-09-19T02:40:08.598Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "rich" +version = "14.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/99/a4cab2acbb884f80e558b0771e97e21e939c5dfb460f488d19df485e8298/rich-14.3.2.tar.gz", hash = "sha256:e712f11c1a562a11843306f5ed999475f09ac31ffb64281f73ab29ffdda8b3b8", size = 230143, upload-time = "2026-02-01T16:20:47.908Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/45/615f5babd880b4bd7d405cc0dc348234c5ffb6ed1ea33e152ede08b2072d/rich-14.3.2-py3-none-any.whl", hash = "sha256:08e67c3e90884651da3239ea668222d19bea7b589149d8014a21c633420dbb69", size = 309963, upload-time = "2026-02-01T16:20:46.078Z" }, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, + { name = "pure-eval" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, +] + +[[package]] +name = "traitlets" +version = "5.14.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621, upload-time = "2024-04-19T11:11:49.746Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" }, +] + +[[package]] +name = "wcwidth" +version = "0.5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/62/a7c072fbfefb2980a00f99ca994279cb9ecf310cb2e6b2a4d2a28fe192b3/wcwidth-0.5.3.tar.gz", hash = "sha256:53123b7af053c74e9fe2e92ac810301f6139e64379031f7124574212fb3b4091", size = 157587, upload-time = "2026-01-31T03:52:10.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/c1/d73f12f8cdb1891334a2ccf7389eed244d3941e74d80dd220badb937f3fb/wcwidth-0.5.3-py3-none-any.whl", hash = "sha256:d584eff31cd4753e1e5ff6c12e1edfdb324c995713f75d26c29807bb84bf649e", size = 92981, upload-time = "2026-01-31T03:52:09.14Z" }, +] + +[[package]] +name = "xdg" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/b9/0e6e6f19fb75cf5e1758f4f33c1256738f718966700cffc0fde2f966218b/xdg-6.0.0.tar.gz", hash = "sha256:24278094f2d45e846d1eb28a2ebb92d7b67fc0cab5249ee3ce88c95f649a1c92", size = 3453, upload-time = "2023-02-27T19:27:44.309Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/54/3516c1cf349060fc3578686d271eba242f10ec00b4530c2985af9faac49b/xdg-6.0.0-py3-none-any.whl", hash = "sha256:df3510755b4395157fc04fc3b02467c777f3b3ca383257397f09ab0d4c16f936", size = 3855, upload-time = "2023-02-27T19:27:42.151Z" }, +] |
