snk-cli 0.0.1__py3-none-any.whl

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.
@@ -0,0 +1,175 @@
1
+ from dataclasses import dataclass
2
+ from pathlib import Path
3
+ from snakemake import (
4
+ Workflow,
5
+ update_config,
6
+ load_configfile,
7
+ dict_to_key_value_args,
8
+ common,
9
+ )
10
+ from snakemake.persistence import Persistence
11
+ from ..utils import check_command_available
12
+ import os
13
+
14
+ @dataclass
15
+ class PersistenceMock(Persistence):
16
+ """
17
+ Mock for workflow.persistence
18
+ """
19
+
20
+ conda_env_path: Path = None
21
+ _metadata_path: Path = None
22
+ _incomplete_path: Path = None
23
+ shadow_path: Path = None
24
+ conda_env_archive_path: Path = None
25
+ container_img_path: Path = None
26
+ aux_path: Path = None
27
+
28
+ def create_snakemake_workflow(
29
+ snakefile,
30
+ cache=None,
31
+ lint=None,
32
+ cores=1,
33
+ nodes=None,
34
+ max_threads=None,
35
+ resources=dict(),
36
+ overwrite_threads=None,
37
+ overwrite_scatter=None,
38
+ overwrite_resource_scopes=None,
39
+ default_resources=None,
40
+ overwrite_resources=None,
41
+ config=dict(),
42
+ configfiles=None,
43
+ config_args=None,
44
+ workdir=None,
45
+ printshellcmds=False,
46
+ rerun_triggers=["mtime", "params", "input", "software-env", "code"],
47
+ conda_cleanup_envs=False,
48
+ latency_wait=3,
49
+ print_compilation=False,
50
+ debug=False,
51
+ all_temp=False,
52
+ jobscript=None,
53
+ overwrite_shellcmd=None,
54
+ restart_times=0,
55
+ attempt=1,
56
+ verbose=False,
57
+ use_conda=False,
58
+ use_singularity=False,
59
+ use_env_modules=False,
60
+ singularity_args="",
61
+ conda_frontend="conda",
62
+ conda_prefix=None,
63
+ conda_cleanup_pkgs=None,
64
+ list_conda_envs=False,
65
+ singularity_prefix=None,
66
+ shadow_prefix=None,
67
+ scheduler="ilp",
68
+ scheduler_ilp_solver=None,
69
+ mode=common.Mode.default,
70
+ wrapper_prefix=None,
71
+ default_remote_prefix="",
72
+ assume_shared_fs=True,
73
+ keep_metadata=True,
74
+ edit_notebook=None,
75
+ envvars=None,
76
+ overwrite_groups=None,
77
+ group_components=None,
78
+ max_inventory_wait_time=20,
79
+ execute_subworkflows=True,
80
+ conda_not_block_search_path_envvars=False,
81
+ scheduler_solver_path=None,
82
+ conda_base_path=None,
83
+ local_groupid="local",
84
+ ):
85
+ overwrite_config = dict()
86
+ if configfiles is None:
87
+ configfiles = []
88
+ for f in configfiles:
89
+ # get values to override. Later configfiles override earlier ones.
90
+ update_config(overwrite_config, load_configfile(f))
91
+ # convert provided paths to absolute paths
92
+ configfiles = list(map(os.path.abspath, configfiles))
93
+
94
+ # directly specified elements override any configfiles
95
+ if config:
96
+ update_config(overwrite_config, config)
97
+ if config_args is None:
98
+ config_args = dict_to_key_value_args(config)
99
+
100
+ if workdir:
101
+ if not os.path.exists(workdir):
102
+ os.makedirs(workdir)
103
+ workdir = os.path.abspath(workdir)
104
+ os.chdir(workdir)
105
+
106
+ if check_command_available("mamba"):
107
+ conda_frontend = "mamba"
108
+
109
+ workflow = Workflow(
110
+ snakefile=snakefile,
111
+ rerun_triggers=rerun_triggers,
112
+ jobscript=jobscript,
113
+ overwrite_shellcmd=overwrite_shellcmd,
114
+ overwrite_config=overwrite_config,
115
+ overwrite_workdir=workdir,
116
+ overwrite_configfiles=configfiles,
117
+ overwrite_clusterconfig=dict(),
118
+ overwrite_threads=overwrite_threads,
119
+ max_threads=max_threads,
120
+ overwrite_scatter=overwrite_scatter,
121
+ overwrite_groups=overwrite_groups,
122
+ overwrite_resources=overwrite_resources,
123
+ overwrite_resource_scopes=overwrite_resource_scopes,
124
+ group_components=group_components,
125
+ config_args=config_args,
126
+ debug=debug,
127
+ verbose=verbose,
128
+ use_conda=use_conda or list_conda_envs or conda_cleanup_envs,
129
+ use_singularity=use_singularity,
130
+ use_env_modules=use_env_modules,
131
+ conda_frontend=conda_frontend,
132
+ conda_prefix=conda_prefix,
133
+ conda_cleanup_pkgs=conda_cleanup_pkgs,
134
+ singularity_prefix=singularity_prefix,
135
+ shadow_prefix=shadow_prefix,
136
+ singularity_args=singularity_args,
137
+ scheduler_type=scheduler,
138
+ scheduler_ilp_solver=scheduler_ilp_solver,
139
+ mode=mode,
140
+ wrapper_prefix=wrapper_prefix,
141
+ printshellcmds=printshellcmds,
142
+ restart_times=restart_times,
143
+ attempt=attempt,
144
+ default_remote_provider=None,
145
+ default_remote_prefix=default_remote_prefix,
146
+ run_local=True,
147
+ assume_shared_fs=assume_shared_fs,
148
+ default_resources=default_resources,
149
+ cache=cache,
150
+ cores=cores,
151
+ nodes=nodes,
152
+ resources=resources,
153
+ edit_notebook=edit_notebook,
154
+ envvars=envvars,
155
+ max_inventory_wait_time=max_inventory_wait_time,
156
+ conda_not_block_search_path_envvars=conda_not_block_search_path_envvars,
157
+ execute_subworkflows=execute_subworkflows,
158
+ scheduler_solver_path=scheduler_solver_path,
159
+ conda_base_path=conda_base_path,
160
+ check_envvars=not lint, # for linting, we do not need to check whether requested envvars exist
161
+ all_temp=all_temp,
162
+ local_groupid=local_groupid,
163
+ keep_metadata=keep_metadata,
164
+ latency_wait=latency_wait,
165
+ )
166
+
167
+ persistence = PersistenceMock(
168
+ conda_env_path=Path(conda_prefix).resolve() if conda_prefix else None,
169
+ conda_env_archive_path=os.path.join(Path(".snakemake"), "conda-archive"),
170
+ )
171
+ if hasattr(workflow, "_persistence"):
172
+ workflow._persistence = persistence
173
+ else:
174
+ workflow.persistence = persistence
175
+ return workflow
snk_cli/testing.py ADDED
@@ -0,0 +1,19 @@
1
+ from typer.testing import CliRunner, Result
2
+ from dataclasses import dataclass
3
+ from typing import List
4
+ from snk_cli import CLI
5
+ import sys
6
+
7
+ @dataclass
8
+ class SnkCliRunner:
9
+ """Dynamically create a CLI Runner for testing"""
10
+
11
+ cli: CLI
12
+ runner = CliRunner(mix_stderr=False)
13
+
14
+ def invoke(self, args: List[str]) -> Result:
15
+ old_argv = sys.argv
16
+ sys.argv = ['cli'] + args # ensure that the CLI is invoked with the correct arguments
17
+ result = self.runner.invoke(self.cli.app, args)
18
+ sys.argv = old_argv
19
+ return result
snk_cli/utils.py ADDED
@@ -0,0 +1,149 @@
1
+ from typing import List
2
+ from pathlib import Path
3
+ from datetime import datetime
4
+ import typer
5
+ import sys
6
+ import collections # MutableMapping import hack
7
+
8
+ if sys.version_info.major == 3 and sys.version_info.minor >= 10:
9
+ from collections.abc import MutableMapping
10
+ else:
11
+ from collections import MutableMapping
12
+ from snk_cli.options import Option
13
+
14
+
15
+ def check_command_available(command: str):
16
+ """
17
+ Check if a command is available.
18
+ Args:
19
+ command (str): The command to check.
20
+ Returns:
21
+ bool: True if the command is available, False otherwise.
22
+ Examples:
23
+ >>> CLI.check_command_available('ls')
24
+ """
25
+ from shutil import which
26
+
27
+ return which(command) is not None
28
+
29
+
30
+ def flatten(d, parent_key="", sep=":"):
31
+ """
32
+ Flattens a nested dictionary.
33
+ Args:
34
+ d (dict): The dictionary to flatten.
35
+ parent_key (str, optional): The parent key of the dictionary. Defaults to ''.
36
+ sep (str, optional): The separator to use between keys. Defaults to ':'.
37
+ Returns:
38
+ dict: A flattened dictionary.
39
+ Examples:
40
+ >>> d = {'a': {'b': 1, 'c': 2}, 'd': 3}
41
+ >>> flatten(d)
42
+ {'a:b': 1, 'a:c': 2, 'd': 3}
43
+ """
44
+ items = []
45
+ for k, v in d.items():
46
+ new_key = parent_key + sep + k if parent_key else k
47
+ if isinstance(v, MutableMapping):
48
+ items.extend(flatten(v, new_key, sep=sep).items())
49
+ else:
50
+ items.append((new_key, v))
51
+ return dict(items)
52
+
53
+
54
+ def convert_key_to_snakemake_format(key, value, sep=":"):
55
+ """
56
+ Convert key to a format that can be passed over the cli to snakemake
57
+ """
58
+ result_dict = {}
59
+ parts = key.split(sep)
60
+ current_dict = result_dict
61
+
62
+ for part in parts[:-1]:
63
+ current_dict = current_dict.setdefault(part, {})
64
+
65
+ current_dict[parts[-1]] = value
66
+
67
+ return result_dict
68
+
69
+
70
+ def serialise(d):
71
+ """
72
+ Serialises a data structure into a string.
73
+ Args:
74
+ d (any): The data structure to serialise.
75
+ Returns:
76
+ any: The serialised data structure.
77
+ Examples:
78
+ >>> serialise({'a': 1, 'b': 2})
79
+ {'a': '1', 'b': '2'}
80
+ """
81
+ if isinstance(d, Path) or isinstance(d, datetime):
82
+ return str(d)
83
+
84
+ if isinstance(d, list):
85
+ return [serialise(x) for x in d]
86
+
87
+ if isinstance(d, dict):
88
+ for k, v in d.items():
89
+ d.update({k: serialise(v)})
90
+
91
+ # return anything else, like a string or number
92
+ return d
93
+
94
+
95
+ def parse_config_args(args: List[str], options: List[Option]):
96
+ """
97
+ Parses a list of arguments and a list of options.
98
+ Args:
99
+ args (List[str]): A list of arguments.
100
+ options (List[Option]): A list of options.
101
+ Returns:
102
+ (List[str], List[dict]): A tuple of parsed arguments and config.
103
+ Examples:
104
+ >>> parse_config_args(['-name', 'John', '-age', '20'], [{'name': 'name', 'default': '', 'help': '', 'type': 'str', 'required': True}, {'name': 'age', 'default': '', 'help': '', 'type': 'int', 'required': True}])
105
+ (['John', '20'], [{'name': 'name', 'John'}, {'age': 20}])
106
+ """
107
+ names = [op.name for op in options]
108
+ config = []
109
+ parsed: List[str] = []
110
+ flag = None
111
+ for arg in args:
112
+ if flag:
113
+ name = flag.lstrip("-")
114
+ op = next(op for op in options if op.name == name)
115
+ if op.updated is False and op.default == serialise(arg):
116
+ # skip args that don't change
117
+ flag = None
118
+ continue
119
+ if ":" in op.original_key:
120
+ samkemake_format_config = convert_key_to_snakemake_format(
121
+ op.original_key, arg
122
+ )
123
+ name = list(samkemake_format_config.keys())[0]
124
+ arg = samkemake_format_config[name]
125
+ config.append({name: serialise(arg)})
126
+ flag = None
127
+ continue
128
+ if arg.startswith("-") and arg.lstrip("-") in names:
129
+ flag = arg
130
+ continue
131
+ parsed.append(arg)
132
+ parsed.sort()
133
+ return parsed, config
134
+
135
+
136
+ def get_default_type(v):
137
+ default_type = type(v)
138
+ if default_type == list and len(v) > 0:
139
+ return f"List[{type(v[0]).__name__}]"
140
+ return str(default_type.__name__)
141
+
142
+
143
+ def dag_filetype_callback(ctx: typer.Context, file: Path):
144
+ allowed = [".pdf", ".png", ".svg"]
145
+ if ctx.resilient_parsing or not file:
146
+ return
147
+ if file.suffix not in allowed:
148
+ raise typer.BadParameter(f"Dag file suffix must be one of {','.join(allowed)}!")
149
+ return file
snk_cli/validate.py ADDED
@@ -0,0 +1,66 @@
1
+ from pathlib import Path
2
+ from typing import Any, Dict, Union
3
+ from .config import SnkConfig
4
+ from .options.utils import types
5
+ import inspect
6
+
7
+ class ValidationError(Exception):
8
+ """Base class for all validation exceptions"""
9
+
10
+
11
+ def validate_config(config: Dict[str, Any], snk_config_path: Path) -> None:
12
+ """
13
+ Validates the config against the snk config.
14
+ Will convert values to the correct type if possible.
15
+ Args:
16
+ config (dict): The config to validate.
17
+ snk_config_path (Path): The path to the snk config.
18
+ """
19
+ snk_config_path = Path(snk_config_path)
20
+ # if relative path, make absolute to
21
+ if not snk_config_path.is_absolute():
22
+ frame = inspect.currentframe().f_back
23
+ workflow = frame.f_globals.get("workflow")
24
+ snk_config_path = Path(workflow.current_basedir) / snk_config_path
25
+
26
+ snk_config = SnkConfig.from_path(snk_config_path)
27
+ validate_and_transform_in_place(config, snk_config.cli)
28
+
29
+ ValidationDict = Dict[str, Union["ValidationDict", Dict[str, str]]]
30
+
31
+ def validate_and_transform_in_place(config: Dict[str, Any], validation: ValidationDict, replace_none: bool = True) -> None:
32
+ """
33
+ Validates the config against the snk config.
34
+ Will convert values to the correct type if possible.
35
+ Args:
36
+ config (dict): The config to validate.
37
+ validation (dict): The validation dict.
38
+ replace_none (bool): If True, replace 'None' with None.
39
+ """
40
+ for key, value in list(config.items()):
41
+ if key not in validation:
42
+ continue # Optionally handle unexpected keys
43
+ if value == 'None' and replace_none:
44
+ config[key] = None
45
+ continue
46
+ if value is None:
47
+ continue
48
+ val_info = validation[key]
49
+ if isinstance(val_info, dict) and 'type' in val_info:
50
+ # Direct type validation
51
+ val_type = types.get(val_info["type"], None)
52
+ if val_type is None:
53
+ raise ValueError(f"Unknown type '{val_info['type']}'")
54
+ try:
55
+ if getattr(val_type, "__origin__", None) == list:
56
+ val_type = val_type.__args__[0]
57
+ if not isinstance(value, list):
58
+ raise ValueError(f"Expected a list for key '{key}'")
59
+ config[key] = [val_type(v) for v in value]
60
+ else:
61
+ config[key] = val_type(value)
62
+ except (ValueError, TypeError) as e:
63
+ raise ValueError(f"Type conversion error for key '{key}': {e}")
64
+ elif isinstance(value, dict):
65
+ # Nested dictionary validation
66
+ validate_and_transform_in_place(value, val_info)
snk_cli/workflow.py ADDED
@@ -0,0 +1,177 @@
1
+ from pathlib import Path
2
+ import sys
3
+ from typing import Optional
4
+ from git import Repo, InvalidGitRepositoryError
5
+ import importlib.util
6
+ import os
7
+
8
+ class Workflow:
9
+ """
10
+ Represents a workflow.
11
+ Attributes:
12
+ path (Path): The path to the workflow.
13
+ repo (Repo): The git repository of the workflow.
14
+ name (str): The name of the workflow.
15
+ """
16
+
17
+ def __init__(self, path: Path) -> None:
18
+ """
19
+ Initializes a Workflow object.
20
+ Args:
21
+ path (Path): The path to the workflow.
22
+ Returns:
23
+ None
24
+ Notes:
25
+ Initializes the `repo` and `name` attributes.
26
+ """
27
+ self.path = path
28
+ self.editable = self.check_is_editable()
29
+ if self.editable: # editable mode
30
+ self.repo = None
31
+ else:
32
+ try:
33
+ self.repo = Repo(path)
34
+ except InvalidGitRepositoryError:
35
+ self.repo = None
36
+ self.name = self.path.name
37
+
38
+
39
+ @property
40
+ def tag(self):
41
+ """
42
+ Gets the tag of the workflow.
43
+ Returns:
44
+ str: The tag of the workflow, or None if no tag is found.
45
+ """
46
+ try:
47
+ tag = self.repo.git.describe(["--tags", "--exact-match"])
48
+ except Exception:
49
+ tag = None
50
+ return tag
51
+
52
+ @property
53
+ def commit(self):
54
+ """
55
+ Gets the commit SHA of the workflow.
56
+ Returns:
57
+ str: The commit SHA of the workflow.
58
+ """
59
+ try:
60
+ sha = self.repo.head.object.hexsha
61
+ commit = self.repo.git.rev_parse(sha, short=8)
62
+ except Exception:
63
+ commit = None
64
+ return commit
65
+
66
+ @property
67
+ def version(self):
68
+ """
69
+ Gets the version of the workflow.
70
+ Returns:
71
+ str: The version of the workflow, or None if no version is found.
72
+ """
73
+ if self.repo is None:
74
+ return None
75
+ if self.tag:
76
+ version = self.tag
77
+ else:
78
+ version = self.commit
79
+ return version
80
+
81
+ @property
82
+ def executable(self):
83
+ """
84
+ Gets the executable of the workflow.
85
+ Returns:
86
+ Path: The path to the workflow executable.
87
+ """
88
+ workflow_bin_dir = self.path.parent.parent / "bin"
89
+ name = self.name
90
+ if sys.platform.startswith("win"):
91
+ name += ".exe"
92
+ return workflow_bin_dir / name
93
+
94
+ @property
95
+ def conda_prefix_dir(self):
96
+ """
97
+ Gets the conda prefix directory of the workflow. If in editable mode, the conda prefix directory is
98
+ located in the .snakemake directory. Otherwise, it is located in the .conda directory in the workflow
99
+ directory.
100
+
101
+ Returns:
102
+ Path: The path to the conda prefix directory.
103
+ """
104
+ return Path(".snakemake") / "conda" if self.editable else self.path / ".conda"
105
+
106
+ @property
107
+ def singularity_prefix_dir(self):
108
+ """
109
+ Gets the singularity prefix directory of the workflow.
110
+ Returns:
111
+ Path: The path to the singularity prefix directory.
112
+ """
113
+ if " " in str(self.path):
114
+ # sigh, snakemake singularity does not support spaces in the path
115
+ # https://github.com/snakemake/snakemake/blob/2ecb21ba04088b9e6850447760f713784cf8b775/snakemake/deployment/singularity.py#L130C1-L131C1
116
+ return None
117
+ return Path(".snakemake") / "singularity" if self.editable else self.path / ".singularity"
118
+
119
+ def _is_editable_pip_install(self):
120
+ # This function now acts as a method within the Workflow class
121
+ package_spec = importlib.util.find_spec(self.name)
122
+ if package_spec is None:
123
+ return False # Package is not installed
124
+
125
+ package_location = package_spec.origin
126
+ site_packages_paths = [p for p in sys.path if 'site-packages' in p]
127
+ is_inside_site_packages = any(package_location.startswith(sp) for sp in site_packages_paths)
128
+
129
+ if not is_inside_site_packages:
130
+ return True
131
+
132
+ for sp in site_packages_paths:
133
+ egg_link_path = os.path.join(sp, self.name + '.egg-link')
134
+ if os.path.isfile(egg_link_path):
135
+ return True
136
+
137
+ return False
138
+
139
+ def check_is_editable(self):
140
+ """Is the workflow editable?"""
141
+ if self.path.is_symlink():
142
+ return True
143
+ try:
144
+ return self._is_editable_pip_install()
145
+ except Exception:
146
+ return False
147
+
148
+ def _find_folder(self, name) -> Optional[Path]:
149
+ """Search for folder"""
150
+ if (self.path / "workflow" / name).exists():
151
+ return self.path / "workflow" / name
152
+ if (self.path / name).exists():
153
+ return self.path / name
154
+ return None
155
+
156
+ @property
157
+ def profiles(self):
158
+ workflow_profile_dir = self._find_folder("profiles")
159
+ if workflow_profile_dir:
160
+ return [p for p in workflow_profile_dir.glob("*") if p.is_dir() and (p / "config.yaml").exists()]
161
+ return []
162
+
163
+ @property
164
+ def environments(self):
165
+ workflow_environments_dir = self._find_folder("envs")
166
+ if workflow_environments_dir:
167
+ return [e for e in workflow_environments_dir.glob("*.yaml")] + [
168
+ e for e in workflow_environments_dir.glob("*.yml")
169
+ ]
170
+ return []
171
+
172
+ @property
173
+ def scripts(self):
174
+ workflow_environments_dir = self._find_folder("scripts")
175
+ if workflow_environments_dir:
176
+ return [s for s in workflow_environments_dir.iterdir() if s.is_file()]
177
+ return []
@@ -0,0 +1,29 @@
1
+ Metadata-Version: 2.1
2
+ Name: snk-cli
3
+ Version: 0.0.1
4
+ Project-URL: Documentation, https://github.com/unknown/snk-cli#readme
5
+ Project-URL: Issues, https://github.com/unknown/snk-cli/issues
6
+ Project-URL: Source, https://github.com/unknown/snk-cli
7
+ Author-email: Wytamma Wirth <wytamma.wirth@me.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE.txt
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Programming Language :: Python
12
+ Classifier: Programming Language :: Python :: 3.8
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: Implementation :: CPython
18
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
19
+ Requires-Python: >=3.8
20
+ Requires-Dist: art~=5.9
21
+ Requires-Dist: datrie>=0.8.2
22
+ Requires-Dist: gitpython~=3.1
23
+ Requires-Dist: makefun~=1.15
24
+ Requires-Dist: pulp<2.8
25
+ Requires-Dist: snakemake<9,>=7
26
+ Requires-Dist: typer[all]~=0.9.0
27
+ Description-Content-Type: text/markdown
28
+
29
+ # snk-cli
@@ -0,0 +1,25 @@
1
+ snk_cli/__about__.py,sha256=I1WJyeCzQRHJXTtCdvlSm6jF2FfYm8nKcOMZELFuzCE,131
2
+ snk_cli/__init__.py,sha256=iZGwZ9O_cUMK1OC2v6HxIHeLf-TQ_aKxPbVrtdRU0gI,167
3
+ snk_cli/cli.py,sha256=K6hs5PpThGDPtXYrYxgRcUhwm1MEKcQSKH4rra-Nq1Y,7807
4
+ snk_cli/dynamic_typer.py,sha256=h743N18SYtYXivhDM6yp8fenmNFHKWa5zTGgazPBk40,9137
5
+ snk_cli/testing.py,sha256=F8kElQOM73XAfo5OVmpNU0KILPp88059PTwJkDt9aWc,556
6
+ snk_cli/utils.py,sha256=aFUZjbKNfUtqtPL1SPQ4qae2FkDJQ36QRUA0YRnqfpM,4482
7
+ snk_cli/validate.py,sha256=4VKstHpDmM2AuyPjkvQXenClMskOjZ5dJyW1FPuiqrU,2686
8
+ snk_cli/workflow.py,sha256=fMyZ1nXtDMoLf3Aa3csOC5E1IokV8EcSQp84hSajnIE,5626
9
+ snk_cli/config/__init__.py,sha256=pbFmOEXCm6dG0eIsAoozMmXN9VomL5uuO29oHTmu3Ew,30
10
+ snk_cli/config/config.py,sha256=eK4qh0jSS-hU1CYqFR3JD780Zju3nngOs-AGgjWNf2E,8309
11
+ snk_cli/config/utils.py,sha256=7nhOas6Q1WV7EEdlKbVRkbTprx0SMCzDJ6kbx-vKmZc,1378
12
+ snk_cli/options/__init__.py,sha256=Vz2wNDyY_AacPrU439TteFvEXvBEdmb0dDYlvJN_LzA,41
13
+ snk_cli/options/option.py,sha256=4IzW6l0fMN-ilagkGVmivNm7dkEBvfu5iVuO7WuOx7Y,340
14
+ snk_cli/options/utils.py,sha256=6ofFLX6UbIj0RqwQxfN-sL7oShy2a4RMXEQZrDSoGWk,3838
15
+ snk_cli/subcommands/__init__.py,sha256=Q0kkzHRumlpGQdLSYGcGKZJRtQXYDVx-F_4IvP-274s,140
16
+ snk_cli/subcommands/config.py,sha256=wv-m-gAgK1BnKCeUBLf3XAh_Xmyz7oJMTlafdhpL3NY,2268
17
+ snk_cli/subcommands/env.py,sha256=XHrWbb0IHHA6e7oEyFjt_M-pPovTCDV9ZpvpKN7R_98,8051
18
+ snk_cli/subcommands/profile.py,sha256=4xY6DV7H_Cylx9wsyBtRB7LpNrqydEa-FiU0hS4bodw,2032
19
+ snk_cli/subcommands/run.py,sha256=bgwGbVqpMuw1rsssz051OPHX4FyjYxXbJU-fTq5i6Og,17139
20
+ snk_cli/subcommands/script.py,sha256=2cHbFwpdUhLFC7naPbUxtmdBYYGYUx8-GGF-TZ-jl80,5126
21
+ snk_cli/subcommands/utils.py,sha256=z2UK40oqzDClzhajtTBoRiUmp2A_ztDm3wueIpL0BdA,5470
22
+ snk_cli-0.0.1.dist-info/METADATA,sha256=JEV1XOPqrtkaV9q0scaHhzbkhyUsxtORi6bDWvn_CRY,1095
23
+ snk_cli-0.0.1.dist-info/WHEEL,sha256=TJPnKdtrSue7xZ_AVGkp9YXcvDrobsjBds1du3Nx6dc,87
24
+ snk_cli-0.0.1.dist-info/licenses/LICENSE.txt,sha256=2EtbIEsC6oT0hDoWB8mP643X4l51wwQRdhofPSN0MLc,1101
25
+ snk_cli-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.21.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024-present Wytamma Wirth <wytamma.wirth@me.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.