latch 2.51.2.dev5__tar.gz → 2.51.2.dev6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {latch-2.51.2.dev5/latch.egg-info → latch-2.51.2.dev6}/PKG-INFO +1 -1
- {latch-2.51.2.dev5 → latch-2.51.2.dev6/latch.egg-info}/PKG-INFO +1 -1
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch.egg-info/SOURCES.txt +5 -2
- latch-2.51.2.dev6/latch_cli/menus/__init__.py +8 -0
- latch-2.51.2.dev6/latch_cli/menus/common.py +181 -0
- latch-2.51.2.dev6/latch_cli/menus/vt100.py +165 -0
- latch-2.51.2.dev6/latch_cli/menus/win32.py +241 -0
- latch-2.51.2.dev6/latch_cli/menus/win32_types.py +232 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/execute/main.py +2 -2
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/get_executions.py +38 -31
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/get_params.py +1 -8
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/launch.py +1 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/local_dev_old.py +2 -2
- latch-2.51.2.dev6/latch_cli/services/preview.py +71 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/setup.py +1 -1
- latch-2.51.2.dev5/latch_cli/menus.py +0 -378
- latch-2.51.2.dev5/latch_cli/services/preview.py +0 -179
- latch-2.51.2.dev5/tests/__init__.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/LICENSE +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/MANIFEST.in +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/README.md +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/__init__.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/account.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/executions.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/functions/__init__.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/functions/messages.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/functions/operators.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/functions/secrets.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/ldata/__init__.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/ldata/_transfer/__init__.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/ldata/_transfer/download.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/ldata/_transfer/manager.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/ldata/_transfer/node.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/ldata/_transfer/progress.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/ldata/_transfer/remote_copy.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/ldata/_transfer/throttle.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/ldata/_transfer/upload.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/ldata/_transfer/utils.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/ldata/path.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/ldata/type.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/registry/__init__.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/registry/project.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/registry/record.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/registry/table.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/registry/types.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/registry/upstream_types/__init__.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/registry/upstream_types/types.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/registry/upstream_types/values.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/registry/utils.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/resources/__init__.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/resources/conditional.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/resources/dynamic.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/resources/launch_plan.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/resources/map_tasks.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/resources/reference_workflow.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/resources/tasks.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/resources/workflow.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/types/__init__.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/types/directory.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/types/file.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/types/glob.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/types/json.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/types/metadata.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/types/utils.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/utils.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/verified/__init__.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/verified/deseq2.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/verified/mafft.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/verified/pathway.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/verified/rnaseq.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch/verified/trim_galore.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch.egg-info/dependency_links.txt +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch.egg-info/entry_points.txt +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch.egg-info/requires.txt +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch.egg-info/top_level.txt +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/__init__.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/auth/__init__.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/auth/csrf.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/auth/oauth2.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/auth/pkce.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/auth/utils.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/centromere/__init__.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/centromere/ctx.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/centromere/utils.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/click_utils.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/constants.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/docker_utils/__init__.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/exceptions/__init__.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/exceptions/cache.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/exceptions/errors.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/exceptions/handler.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/exceptions/traceback.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/main.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/nextflow/__init__.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/nextflow/config.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/nextflow/utils.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/nextflow/workflow.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/__init__.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/cp/__init__.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/cp/autocomplete.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/cp/glob.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/cp/main.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/cp/utils.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/execute/__init__.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/execute/utils.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/get.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/__init__.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/__pycache__/__init__.cpython-310.pyc +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/__pycache__/__init__.cpython-311.pyc +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/__pycache__/__init__.cpython-38.pyc +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/__pycache__/__init__.cpython-39.pyc +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/__pycache__/init.cpython-310.pyc +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/__pycache__/init.cpython-311.pyc +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/__pycache__/init.cpython-38.pyc +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/__pycache__/init.cpython-39.pyc +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/assemble_and_sort/.env +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/assemble_and_sort/LICENSE +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/assemble_and_sort/README.md +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/assemble_and_sort/__init__.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/assemble_and_sort/__pycache__/__init__.cpython-310.pyc +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/assemble_and_sort/assemble.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/assemble_and_sort/sort.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/assemble_and_sort/system-requirements.txt +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/common/.dockerignore +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/example_conda/__init__.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/example_conda/__pycache__/__init__.cpython-310.pyc +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/example_conda/conda_task.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/example_conda/environment.yaml +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/example_docker/__init__.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/example_docker/task.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/example_nf_integration/latch_metadata/__pycache__/__init__.cpython-311.pyc +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/example_nfcore/Dockerfile +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/example_nfcore/__init__.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/example_nfcore/task.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/example_r/__init__.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/example_r/__pycache__/__init__.cpython-310.pyc +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/example_r/environment.R +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/example_r/r_task.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/example_snakemake/.latch/latch_entrypoint +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/example_snakemake/Dockerfile +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/example_snakemake/Snakefile +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/example_snakemake/config.yaml +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/example_snakemake/environment.yaml +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/example_snakemake/latch_metadata.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/example_snakemake/scripts/plot-quals.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/example_snakemake/version +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/init.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/template/LICENSE +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/template/README.md +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/template/__init__.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/template/__pycache__/__init__.cpython-310.pyc +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/init/template/task.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/local_dev.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/login.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/ls.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/mkdir.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/move.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/register/__init__.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/register/constants.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/register/register.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/register/utils.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/rm.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/stop_pod.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/sync.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/test_data/__init__.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/test_data/ls.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/test_data/remove.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/test_data/upload.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/test_data/utils.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/services/workspace.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/snakemake/__init__.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/snakemake/config/__init__.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/snakemake/config/parser.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/snakemake/config/utils.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/snakemake/serialize.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/snakemake/serialize_utils.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/snakemake/single_task_snakemake.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/snakemake/utils.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/snakemake/workflow.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/tinyrequests.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/utils/__init__.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/utils/path.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/latch_cli/workflow_config.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/pyproject.toml +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/setup.cfg +0 -0
- {latch-2.51.2.dev5/latch_cli/tui → latch-2.51.2.dev6/tests}/__init__.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/tests/cp/__init__.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/tests/fixtures.py +0 -0
- {latch-2.51.2.dev5 → latch-2.51.2.dev6}/tests/test_ls.py +0 -0
|
@@ -63,7 +63,6 @@ latch_cli/__init__.py
|
|
|
63
63
|
latch_cli/click_utils.py
|
|
64
64
|
latch_cli/constants.py
|
|
65
65
|
latch_cli/main.py
|
|
66
|
-
latch_cli/menus.py
|
|
67
66
|
latch_cli/tinyrequests.py
|
|
68
67
|
latch_cli/workflow_config.py
|
|
69
68
|
latch_cli/auth/__init__.py
|
|
@@ -80,6 +79,11 @@ latch_cli/exceptions/cache.py
|
|
|
80
79
|
latch_cli/exceptions/errors.py
|
|
81
80
|
latch_cli/exceptions/handler.py
|
|
82
81
|
latch_cli/exceptions/traceback.py
|
|
82
|
+
latch_cli/menus/__init__.py
|
|
83
|
+
latch_cli/menus/common.py
|
|
84
|
+
latch_cli/menus/vt100.py
|
|
85
|
+
latch_cli/menus/win32.py
|
|
86
|
+
latch_cli/menus/win32_types.py
|
|
83
87
|
latch_cli/nextflow/__init__.py
|
|
84
88
|
latch_cli/nextflow/config.py
|
|
85
89
|
latch_cli/nextflow/utils.py
|
|
@@ -172,7 +176,6 @@ latch_cli/snakemake/workflow.py
|
|
|
172
176
|
latch_cli/snakemake/config/__init__.py
|
|
173
177
|
latch_cli/snakemake/config/parser.py
|
|
174
178
|
latch_cli/snakemake/config/utils.py
|
|
175
|
-
latch_cli/tui/__init__.py
|
|
176
179
|
latch_cli/utils/__init__.py
|
|
177
180
|
latch_cli/utils/path.py
|
|
178
181
|
tests/__init__.py
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from typing import Callable, Generic, Tuple
|
|
3
|
+
|
|
4
|
+
from git import Optional
|
|
5
|
+
from typing_extensions import TypedDict, TypeVar
|
|
6
|
+
|
|
7
|
+
old_print = print
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def buffered_print() -> Tuple[Callable, Callable]:
|
|
11
|
+
buffer = []
|
|
12
|
+
|
|
13
|
+
def __print(*args):
|
|
14
|
+
for arg in args:
|
|
15
|
+
buffer.append(arg)
|
|
16
|
+
|
|
17
|
+
def __show():
|
|
18
|
+
nonlocal buffer
|
|
19
|
+
old_print("".join(buffer), flush=True, end="")
|
|
20
|
+
buffer = []
|
|
21
|
+
|
|
22
|
+
return __print, __show
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# Allows for exactly one print per render, removing any weird flashing
|
|
26
|
+
# behavior and also speeding things up considerably
|
|
27
|
+
print, show = buffered_print()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def clear(k: int):
|
|
31
|
+
"""
|
|
32
|
+
Clear `k` lines below the cursor, returning the cursor to its original position
|
|
33
|
+
"""
|
|
34
|
+
print(f"\x1b[2K\x1b[1E" * (k) + f"\x1b[{k}F")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def draw_box(
|
|
38
|
+
ul_corner_pos: Tuple[int, int],
|
|
39
|
+
height: int,
|
|
40
|
+
width: int,
|
|
41
|
+
color: Optional[str] = None,
|
|
42
|
+
):
|
|
43
|
+
if height <= 0 or width <= 0:
|
|
44
|
+
return
|
|
45
|
+
move_cursor(ul_corner_pos)
|
|
46
|
+
draw_horizontal_line(width, make_corner=True, color=color)
|
|
47
|
+
draw_vertical_line(height, make_corner=True, color=color)
|
|
48
|
+
draw_horizontal_line(width, left=True, make_corner=True, color=color)
|
|
49
|
+
draw_vertical_line(height, up=True, make_corner=True, color=color)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def clear_screen():
|
|
53
|
+
print("\x1b[2J")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def remove_cursor():
|
|
57
|
+
print("\x1b[?25l")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def reveal_cursor():
|
|
61
|
+
print("\x1b[?25h")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def move_cursor(pos: Tuple[int, int]):
|
|
65
|
+
"""
|
|
66
|
+
Move the cursor to a given (x, y) coordinate
|
|
67
|
+
"""
|
|
68
|
+
x, y = pos
|
|
69
|
+
if x < 0 or y < 0:
|
|
70
|
+
return
|
|
71
|
+
print(f"\x1b[{y};{x}H")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def move_cursor_up(n: int):
|
|
75
|
+
if n <= 0:
|
|
76
|
+
return
|
|
77
|
+
print(f"\x1b[{n}A")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def line_up(n: int):
|
|
81
|
+
"""Moves to the start of the destination line"""
|
|
82
|
+
if n <= 0:
|
|
83
|
+
return
|
|
84
|
+
print(f"\x1b[{n}F")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def move_cursor_down(n: int):
|
|
88
|
+
if n <= 0:
|
|
89
|
+
return
|
|
90
|
+
print(f"\x1b[{n}B")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def line_down(n: int):
|
|
94
|
+
"""Moves to the start of the destination line"""
|
|
95
|
+
if n <= 0:
|
|
96
|
+
return
|
|
97
|
+
print(f"\x1b[{n}E")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def move_cursor_right(n: int):
|
|
101
|
+
if n <= 0:
|
|
102
|
+
return
|
|
103
|
+
print(f"\x1b[{n}C")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def move_cursor_left(n: int):
|
|
107
|
+
if n <= 0:
|
|
108
|
+
return
|
|
109
|
+
print(f"\x1b[{n}D")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def current_cursor_position() -> Tuple[int, int]:
|
|
113
|
+
res = b""
|
|
114
|
+
sys.stdout.write("\x1b[6n")
|
|
115
|
+
sys.stdout.flush()
|
|
116
|
+
while not res.endswith(b"R"):
|
|
117
|
+
res += sys.stdin.buffer.read(1)
|
|
118
|
+
y, x = res.strip(b"\x1b[R").split(b";")
|
|
119
|
+
return int(x), int(y)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def draw_vertical_line(
|
|
123
|
+
height: int,
|
|
124
|
+
up: bool = False,
|
|
125
|
+
make_corner: bool = False,
|
|
126
|
+
color: Optional[str] = None,
|
|
127
|
+
):
|
|
128
|
+
"""
|
|
129
|
+
Draws a vertical line with given `height`, going upwards if `up` is True
|
|
130
|
+
and downwards otherwise.
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
if height <= 0:
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
if color is not None:
|
|
137
|
+
print(color)
|
|
138
|
+
sep = "\x1b[1A" if up else "\x1b[1B"
|
|
139
|
+
for i in range(height):
|
|
140
|
+
if i == 0 and make_corner:
|
|
141
|
+
corner = "\u2514" if up else "\u2510"
|
|
142
|
+
print(f"{corner}\x1b[1D{sep}")
|
|
143
|
+
else:
|
|
144
|
+
print(f"\u2502\x1b[1D{sep}")
|
|
145
|
+
if color is not None:
|
|
146
|
+
print("\x1b[0m")
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def draw_horizontal_line(
|
|
150
|
+
width: int,
|
|
151
|
+
left: bool = False,
|
|
152
|
+
make_corner: bool = False,
|
|
153
|
+
color: Optional[str] = None,
|
|
154
|
+
):
|
|
155
|
+
"""
|
|
156
|
+
Draws a horizontal line with given `width`, going to the left if `left` is True
|
|
157
|
+
and to the right otherwise.
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
if width <= 0:
|
|
161
|
+
return
|
|
162
|
+
|
|
163
|
+
if color is not None:
|
|
164
|
+
print(color)
|
|
165
|
+
sep = "\x1b[2D" if left else ""
|
|
166
|
+
for i in range(width):
|
|
167
|
+
if i == 0 and make_corner:
|
|
168
|
+
corner = "\u2518" if left else "\u250c"
|
|
169
|
+
print(f"{corner}{sep}")
|
|
170
|
+
else:
|
|
171
|
+
print(f"\u2500{sep}")
|
|
172
|
+
if color is not None:
|
|
173
|
+
print("\x1b[0m")
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
T = TypeVar("T")
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class SelectOption(TypedDict, Generic[T]):
|
|
180
|
+
display_name: str
|
|
181
|
+
value: T
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import termios
|
|
4
|
+
import tty
|
|
5
|
+
from functools import wraps
|
|
6
|
+
from typing import Callable, List, Optional, TypeVar
|
|
7
|
+
|
|
8
|
+
from typing_extensions import ParamSpec
|
|
9
|
+
|
|
10
|
+
from latch_cli.click_utils import AnsiCodes
|
|
11
|
+
|
|
12
|
+
from . import common
|
|
13
|
+
|
|
14
|
+
P = ParamSpec("P")
|
|
15
|
+
T = TypeVar("T")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def raw_input(f: Callable[P, T]) -> Callable[P, T]:
|
|
19
|
+
@wraps(f)
|
|
20
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
|
|
21
|
+
old_settings = termios.tcgetattr(sys.stdin.fileno())
|
|
22
|
+
tty.setraw(sys.stdin.fileno())
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
return f(*args, **kwargs)
|
|
26
|
+
finally:
|
|
27
|
+
termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, old_settings)
|
|
28
|
+
|
|
29
|
+
return wrapper
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def read_next_byte() -> bytes:
|
|
33
|
+
b = sys.stdin.buffer.read(1)
|
|
34
|
+
if b in (
|
|
35
|
+
b"\x03", # CTRL C
|
|
36
|
+
b"\x04", # CTRL D
|
|
37
|
+
b"q",
|
|
38
|
+
b"Q",
|
|
39
|
+
):
|
|
40
|
+
raise KeyboardInterrupt
|
|
41
|
+
return b
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def read_bytes(num_bytes: int) -> bytes:
|
|
45
|
+
if num_bytes < 0:
|
|
46
|
+
raise ValueError(f"cannot read {num_bytes} bytes")
|
|
47
|
+
result = b""
|
|
48
|
+
for _ in range(num_bytes):
|
|
49
|
+
result += read_next_byte()
|
|
50
|
+
return result
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@raw_input
|
|
54
|
+
def select_tui(
|
|
55
|
+
title: str, options: List[common.SelectOption[T]], clear_terminal: bool = True
|
|
56
|
+
) -> Optional[T]:
|
|
57
|
+
"""
|
|
58
|
+
Renders a terminal UI that allows users to select one of the options
|
|
59
|
+
listed in `options`
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
title: The title of the selection window.
|
|
63
|
+
options: A list of names for each of the options.
|
|
64
|
+
clear_terminal: Whether or not to clear the entire terminal window
|
|
65
|
+
before displaying - default False
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
if len(options) == 0:
|
|
69
|
+
raise ValueError("No options given")
|
|
70
|
+
|
|
71
|
+
def render(
|
|
72
|
+
curr_selected: int,
|
|
73
|
+
start_index: int = 0,
|
|
74
|
+
max_per_page: int = 10,
|
|
75
|
+
indent: str = " ",
|
|
76
|
+
) -> int:
|
|
77
|
+
if curr_selected < 0 or curr_selected >= len(options):
|
|
78
|
+
curr_selected = 0
|
|
79
|
+
|
|
80
|
+
print(title)
|
|
81
|
+
common.line_down(2)
|
|
82
|
+
|
|
83
|
+
num_lines_rendered = 4 # 4 "extra" lines for header + footer
|
|
84
|
+
|
|
85
|
+
for i in range(start_index, start_index + max_per_page):
|
|
86
|
+
if i >= len(options):
|
|
87
|
+
break
|
|
88
|
+
name = options[i]["display_name"]
|
|
89
|
+
if i == curr_selected:
|
|
90
|
+
color = AnsiCodes.color
|
|
91
|
+
bold = AnsiCodes.bold
|
|
92
|
+
reset = AnsiCodes.full_reset
|
|
93
|
+
|
|
94
|
+
prefix = indent[:-2] + "> "
|
|
95
|
+
|
|
96
|
+
print(f"{color}{bold}{prefix}{name}{reset}\x1b[1E")
|
|
97
|
+
else:
|
|
98
|
+
print(f"{indent}{name}\x1b[1E")
|
|
99
|
+
num_lines_rendered += 1
|
|
100
|
+
|
|
101
|
+
common.line_down(1)
|
|
102
|
+
|
|
103
|
+
control_str = "[ARROW-KEYS] Navigate\t[ENTER] Select\t[Q] Quit"
|
|
104
|
+
print(control_str)
|
|
105
|
+
common.line_up(num_lines_rendered - 1)
|
|
106
|
+
|
|
107
|
+
common.show()
|
|
108
|
+
|
|
109
|
+
return num_lines_rendered
|
|
110
|
+
|
|
111
|
+
curr_selected = 0
|
|
112
|
+
start_index = 0
|
|
113
|
+
_, term_height = os.get_terminal_size()
|
|
114
|
+
common.remove_cursor()
|
|
115
|
+
|
|
116
|
+
max_per_page = min(len(options), term_height - 4)
|
|
117
|
+
|
|
118
|
+
if clear_terminal:
|
|
119
|
+
common.clear_screen()
|
|
120
|
+
common.move_cursor((0, 0))
|
|
121
|
+
else:
|
|
122
|
+
print("\n" * (max_per_page + 3))
|
|
123
|
+
common.move_cursor_up(max_per_page + 4)
|
|
124
|
+
|
|
125
|
+
num_lines_rendered = render(
|
|
126
|
+
curr_selected,
|
|
127
|
+
start_index=start_index,
|
|
128
|
+
max_per_page=max_per_page,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
while True:
|
|
133
|
+
b = read_bytes(1)
|
|
134
|
+
if b == b"\r":
|
|
135
|
+
return options[curr_selected]["value"]
|
|
136
|
+
elif b == b"\x1b":
|
|
137
|
+
b = read_bytes(2)
|
|
138
|
+
if b == b"[A": # Up Arrow
|
|
139
|
+
curr_selected = max(curr_selected - 1, 0)
|
|
140
|
+
if (
|
|
141
|
+
curr_selected - start_index < max_per_page // 2
|
|
142
|
+
and start_index > 0
|
|
143
|
+
):
|
|
144
|
+
start_index -= 1
|
|
145
|
+
elif b == b"[B": # Down Arrow
|
|
146
|
+
curr_selected = min(curr_selected + 1, len(options) - 1)
|
|
147
|
+
if (
|
|
148
|
+
curr_selected - start_index > max_per_page // 2
|
|
149
|
+
and start_index < len(options) - max_per_page
|
|
150
|
+
):
|
|
151
|
+
start_index += 1
|
|
152
|
+
else:
|
|
153
|
+
continue
|
|
154
|
+
common.clear(num_lines_rendered)
|
|
155
|
+
num_lines_rendered = render(
|
|
156
|
+
curr_selected,
|
|
157
|
+
start_index=start_index,
|
|
158
|
+
max_per_page=max_per_page,
|
|
159
|
+
)
|
|
160
|
+
except KeyboardInterrupt:
|
|
161
|
+
...
|
|
162
|
+
finally:
|
|
163
|
+
common.clear(num_lines_rendered)
|
|
164
|
+
common.reveal_cursor()
|
|
165
|
+
common.show()
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import msvcrt
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
from ctypes import c_ulong, pointer, windll
|
|
5
|
+
from ctypes.wintypes import DWORD, HANDLE
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from enum import Enum
|
|
8
|
+
from functools import wraps
|
|
9
|
+
from typing import TYPE_CHECKING, Callable, List, Optional, TypeVar
|
|
10
|
+
|
|
11
|
+
from typing_extensions import ParamSpec
|
|
12
|
+
|
|
13
|
+
from latch_cli.click_utils import AnsiCodes
|
|
14
|
+
|
|
15
|
+
from . import common
|
|
16
|
+
from .win32_types import (
|
|
17
|
+
INPUT_RECORD,
|
|
18
|
+
KEY_EVENT_RECORD,
|
|
19
|
+
MOUSE_EVENT_RECORD,
|
|
20
|
+
STD_INPUT_HANDLE,
|
|
21
|
+
EventTypes,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# assert sys.platform == "win32"
|
|
25
|
+
|
|
26
|
+
P = ParamSpec("P")
|
|
27
|
+
T = TypeVar("T")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def raw_input(f: Callable[P, T]) -> Callable[P, T]:
|
|
31
|
+
# ayush: got most of this from
|
|
32
|
+
# https://github.com/prompt-toolkit/python-prompt-toolkit/blob/669541123c9a72da1fda662cbd0a18ffe9e6d113/src/prompt_toolkit/input/win32.py
|
|
33
|
+
@wraps(f)
|
|
34
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
|
|
35
|
+
|
|
36
|
+
original_mode = DWORD()
|
|
37
|
+
handle = HANDLE(windll.kernel32.GetStdHandle(STD_INPUT_HANDLE))
|
|
38
|
+
windll.kernel32.GetConsoleMode(handle, pointer(original_mode))
|
|
39
|
+
|
|
40
|
+
enable_echo_input = 0x0004
|
|
41
|
+
enable_line_input = 0x0002
|
|
42
|
+
enable_processed_input = 0x0001
|
|
43
|
+
|
|
44
|
+
windll.kernel32.SetConsoleMode(
|
|
45
|
+
handle,
|
|
46
|
+
original_mode.value
|
|
47
|
+
& ~(enable_echo_input | enable_line_input | enable_processed_input),
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
return f(*args, **kwargs)
|
|
52
|
+
finally:
|
|
53
|
+
windll.kernel32.SetConsoleMode(handle, original_mode)
|
|
54
|
+
|
|
55
|
+
return wrapper
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class Special(Enum):
|
|
59
|
+
left = "left"
|
|
60
|
+
right = "right"
|
|
61
|
+
up = "up"
|
|
62
|
+
down = "down"
|
|
63
|
+
enter = "enter"
|
|
64
|
+
ctrlc = "ctrlc"
|
|
65
|
+
ctrld = "ctrld"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass
|
|
69
|
+
class KeyInput:
|
|
70
|
+
special: Optional[Special]
|
|
71
|
+
value: str
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# ignoring most things except for arrow keys / enter
|
|
75
|
+
def get_key_input() -> Optional[KeyInput]:
|
|
76
|
+
# ayush:
|
|
77
|
+
# https://github.com/prompt-toolkit/python-prompt-toolkit/blob/669541123c9a72da1fda662cbd0a18ffe9e6d113/src/prompt_toolkit/input/win32.py#L127
|
|
78
|
+
handle: HANDLE
|
|
79
|
+
if sys.stdin.isatty():
|
|
80
|
+
handle = HANDLE(windll.kernel32.GetStdHandle(STD_INPUT_HANDLE))
|
|
81
|
+
else:
|
|
82
|
+
_fdcon = os.open("CONIN$", os.O_RDWR | os.O_BINARY)
|
|
83
|
+
handle = HANDLE(msvcrt.get_osfhandle(_fdcon))
|
|
84
|
+
|
|
85
|
+
max_count = 2048 # Max events to read at the same time.
|
|
86
|
+
|
|
87
|
+
read = DWORD(0)
|
|
88
|
+
arrtype = INPUT_RECORD * max_count
|
|
89
|
+
input_records = arrtype()
|
|
90
|
+
|
|
91
|
+
windll.kernel32.ReadConsoleInputW(
|
|
92
|
+
handle, pointer(input_records), max_count, pointer(read)
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
for i in range(read.value):
|
|
96
|
+
ir = input_records[i]
|
|
97
|
+
|
|
98
|
+
if ir.EventType not in EventTypes:
|
|
99
|
+
continue
|
|
100
|
+
|
|
101
|
+
ev = getattr(ir.Event, EventTypes[ir.EventType])
|
|
102
|
+
|
|
103
|
+
if not (isinstance(ev, KEY_EVENT_RECORD) and ev.KeyDown):
|
|
104
|
+
continue
|
|
105
|
+
|
|
106
|
+
u_char = ev.uChar.UnicodeChar
|
|
107
|
+
|
|
108
|
+
special: Optional[Special] = None
|
|
109
|
+
if u_char == "\x00": # special keys, e.g. arrow keys
|
|
110
|
+
if ev.VirtualKeyCode == 37:
|
|
111
|
+
special = Special.left
|
|
112
|
+
elif ev.VirtualKeyCode == 38:
|
|
113
|
+
special = Special.up
|
|
114
|
+
elif ev.VirtualKeyCode == 39:
|
|
115
|
+
special = Special.right
|
|
116
|
+
elif ev.VirtualKeyCode == 40:
|
|
117
|
+
special = Special.down
|
|
118
|
+
elif u_char == "\x0d":
|
|
119
|
+
special = Special.enter
|
|
120
|
+
elif u_char == "\x03":
|
|
121
|
+
special = Special.ctrlc
|
|
122
|
+
elif u_char == "\x04":
|
|
123
|
+
special = Special.ctrld
|
|
124
|
+
|
|
125
|
+
return KeyInput(special, u_char)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@raw_input
|
|
129
|
+
def select_tui(
|
|
130
|
+
title: str, options: List[common.SelectOption[T]], clear_terminal: bool = True
|
|
131
|
+
) -> Optional[T]:
|
|
132
|
+
"""
|
|
133
|
+
Renders a terminal UI that allows users to select one of the options
|
|
134
|
+
listed in `options`
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
title: The title of the selection window.
|
|
138
|
+
options: A list of names for each of the options.
|
|
139
|
+
clear_terminal: Whether or not to clear the entire terminal window
|
|
140
|
+
before displaying - default False
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
if len(options) == 0:
|
|
144
|
+
raise ValueError("No options given")
|
|
145
|
+
|
|
146
|
+
def render(
|
|
147
|
+
curr_selected: int,
|
|
148
|
+
start_index: int = 0,
|
|
149
|
+
max_per_page: int = 10,
|
|
150
|
+
indent: str = " ",
|
|
151
|
+
) -> int:
|
|
152
|
+
if curr_selected < 0 or curr_selected >= len(options):
|
|
153
|
+
curr_selected = 0
|
|
154
|
+
|
|
155
|
+
print(title)
|
|
156
|
+
common.line_down(2)
|
|
157
|
+
|
|
158
|
+
num_lines_rendered = 4 # 4 "extra" lines for header + footer
|
|
159
|
+
|
|
160
|
+
for i in range(start_index, start_index + max_per_page):
|
|
161
|
+
if i >= len(options):
|
|
162
|
+
break
|
|
163
|
+
name = options[i]["display_name"]
|
|
164
|
+
if i == curr_selected:
|
|
165
|
+
color = AnsiCodes.color
|
|
166
|
+
bold = AnsiCodes.bold
|
|
167
|
+
reset = AnsiCodes.full_reset
|
|
168
|
+
|
|
169
|
+
prefix = indent[:-2] + "> "
|
|
170
|
+
|
|
171
|
+
print(f"{color}{bold}{prefix}{name}{reset}\x1b[1E")
|
|
172
|
+
else:
|
|
173
|
+
print(f"{indent}{name}\x1b[1E")
|
|
174
|
+
num_lines_rendered += 1
|
|
175
|
+
|
|
176
|
+
common.line_down(1)
|
|
177
|
+
|
|
178
|
+
control_str = "[ARROW-KEYS] Navigate\t[ENTER] Select\t[Q] Quit"
|
|
179
|
+
print(control_str)
|
|
180
|
+
common.line_up(num_lines_rendered - 1)
|
|
181
|
+
|
|
182
|
+
common.show()
|
|
183
|
+
|
|
184
|
+
return num_lines_rendered
|
|
185
|
+
|
|
186
|
+
curr_selected = 0
|
|
187
|
+
start_index = 0
|
|
188
|
+
_, term_height = os.get_terminal_size()
|
|
189
|
+
common.remove_cursor()
|
|
190
|
+
|
|
191
|
+
max_per_page = min(len(options), term_height - 4)
|
|
192
|
+
|
|
193
|
+
if clear_terminal:
|
|
194
|
+
common.clear_screen()
|
|
195
|
+
common.move_cursor((0, 0))
|
|
196
|
+
else:
|
|
197
|
+
print("\n" * (max_per_page + 3))
|
|
198
|
+
common.move_cursor_up(max_per_page + 4)
|
|
199
|
+
|
|
200
|
+
num_lines_rendered = render(
|
|
201
|
+
curr_selected,
|
|
202
|
+
start_index=start_index,
|
|
203
|
+
max_per_page=max_per_page,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
try:
|
|
207
|
+
while True:
|
|
208
|
+
k = get_key_input()
|
|
209
|
+
if k is None:
|
|
210
|
+
continue
|
|
211
|
+
|
|
212
|
+
if k.special == Special.enter:
|
|
213
|
+
return options[curr_selected]["value"]
|
|
214
|
+
elif k.special == Special.up: # Up Arrow
|
|
215
|
+
curr_selected = max(curr_selected - 1, 0)
|
|
216
|
+
if curr_selected - start_index < max_per_page // 2 and start_index > 0:
|
|
217
|
+
start_index -= 1
|
|
218
|
+
elif k.special == Special.down: # Down Arrow
|
|
219
|
+
curr_selected = min(curr_selected + 1, len(options) - 1)
|
|
220
|
+
if (
|
|
221
|
+
curr_selected - start_index > max_per_page // 2
|
|
222
|
+
and start_index < len(options) - max_per_page
|
|
223
|
+
):
|
|
224
|
+
start_index += 1
|
|
225
|
+
elif k.special in {Special.ctrlc, Special.ctrld} or k.value in {"q", "Q"}:
|
|
226
|
+
return
|
|
227
|
+
else:
|
|
228
|
+
continue
|
|
229
|
+
|
|
230
|
+
common.clear(num_lines_rendered)
|
|
231
|
+
num_lines_rendered = render(
|
|
232
|
+
curr_selected,
|
|
233
|
+
start_index=start_index,
|
|
234
|
+
max_per_page=max_per_page,
|
|
235
|
+
)
|
|
236
|
+
except KeyboardInterrupt:
|
|
237
|
+
...
|
|
238
|
+
finally:
|
|
239
|
+
common.clear(num_lines_rendered)
|
|
240
|
+
common.reveal_cursor()
|
|
241
|
+
common.show()
|