idmtools-cli 0.0.0.dev0__py3-none-any.whl → 0.0.3__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.
- idmtools_cli/__init__.py +12 -8
- idmtools_cli/cli/__init__.py +1 -0
- idmtools_cli/cli/common_project_templates.json +19 -0
- idmtools_cli/cli/config_file.py +199 -0
- idmtools_cli/cli/entrypoint.py +45 -0
- idmtools_cli/cli/gitrepo.py +393 -0
- idmtools_cli/cli/init.py +113 -0
- idmtools_cli/cli/package.py +106 -0
- idmtools_cli/cli/system_info.py +158 -0
- idmtools_cli/cli/utils.py +49 -0
- idmtools_cli/iplatform_cli.py +141 -0
- idmtools_cli/main.py +52 -0
- idmtools_cli/utils/__init__.py +1 -0
- idmtools_cli/utils/cols.py +135 -0
- idmtools_cli/utils/formatters.py +105 -0
- idmtools_cli/utils/utils.py +63 -0
- idmtools_cli-0.0.3.dist-info/METADATA +208 -0
- idmtools_cli-0.0.3.dist-info/RECORD +22 -0
- idmtools_cli-0.0.3.dist-info/entry_points.txt +2 -0
- idmtools_cli-0.0.3.dist-info/licenses/LICENSE.TXT +3 -0
- idmtools_cli-0.0.0.dev0.dist-info/METADATA +0 -41
- idmtools_cli-0.0.0.dev0.dist-info/RECORD +0 -5
- {idmtools_cli-0.0.0.dev0.dist-info → idmtools_cli-0.0.3.dist-info}/WHEEL +0 -0
- {idmtools_cli-0.0.0.dev0.dist-info → idmtools_cli-0.0.3.dist-info}/top_level.txt +0 -0
idmtools_cli/__init__.py
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
"""iidmtools_cli version definition file."""
|
|
2
|
+
try:
|
|
3
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
4
|
+
except ImportError:
|
|
5
|
+
# Python < 3.8
|
|
6
|
+
from importlib_metadata import version, PackageNotFoundError
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
__version__ = version("idmtools-cli") # Use your actual package name
|
|
10
|
+
except PackageNotFoundError:
|
|
11
|
+
# Package not installed, use fallback
|
|
12
|
+
__version__ = "0.0.0+unknown"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Package init file."""
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[{
|
|
2
|
+
"name": "reproducible-science",
|
|
3
|
+
"url": "gh:mkrapp/cookiecutter-reproducible-science",
|
|
4
|
+
"description": "A boilerplate for reproducible and transparent science with close resemblances to the philosophy of Cookiecutter Data Science: A logical, reasonably standardized, but flexible project structure for doing and sharing data science work.",
|
|
5
|
+
"info": "https://github.com/mkrapp/cookiecutter-reproducible-science"
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"name": "docker-science",
|
|
9
|
+
"url": "git@github.com:docker-science/cookiecutter-docker-science.git",
|
|
10
|
+
"description": "This project is a tiny template for machine learning projects developed in Docker environments. In machine learning tasks, projects glow uniquely to fit target tasks, but in the initial state, most directory structure and targets in Makefile are common. Cookiecutter Docker Science generates initial directories which fits simple machine learning tasks.",
|
|
11
|
+
"info": "https://docker-science.github.io/"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"name": "data-science",
|
|
15
|
+
"url": "https://github.com/drivendata/cookiecutter-data-science",
|
|
16
|
+
"description": "A logical, reasonably standardized, but flexible project structure for doing and sharing data science work.",
|
|
17
|
+
"info": "https://docker-science.github.io/"
|
|
18
|
+
}
|
|
19
|
+
]
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"""Defines the config cli group and commands."""
|
|
2
|
+
import configparser
|
|
3
|
+
import dataclasses
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
import click
|
|
7
|
+
from click import secho
|
|
8
|
+
from colorama import Fore, Style
|
|
9
|
+
from idmtools.registry.platform_specification import PlatformPlugins
|
|
10
|
+
from idmtools_cli.cli.entrypoint import cli
|
|
11
|
+
|
|
12
|
+
IGNORED_PLATFORMS = ["Test", "Slurm", "File", "Container", "SSMT", "TestExecute", "Process"]
|
|
13
|
+
AVAILABLE_PLATFORMS = PlatformPlugins().get_plugin_map()
|
|
14
|
+
for platform in IGNORED_PLATFORMS:
|
|
15
|
+
if platform in AVAILABLE_PLATFORMS:
|
|
16
|
+
del AVAILABLE_PLATFORMS[platform]
|
|
17
|
+
HIDDEN_FIELD_REGEX = re.compile('^_.+$')
|
|
18
|
+
FIELD_BLACKLIST = ['platform_type_map', 'supported_types', 'plugin_key', 'docker_image']
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@cli.group()
|
|
22
|
+
@click.option("--config_path", prompt="Path to the idmtools.ini file",
|
|
23
|
+
help="Path to the idmtools.ini file",
|
|
24
|
+
default=os.path.join(os.getcwd(), "idmtools.ini"),
|
|
25
|
+
type=click.Path(dir_okay=False, file_okay=True, exists=False, writable=True, resolve_path=True))
|
|
26
|
+
@click.option("--global-config/--no-global-config", default=False, help="Allow generating config in the platform default global location")
|
|
27
|
+
@click.pass_context
|
|
28
|
+
def config(ctx, config_path, global_config):
|
|
29
|
+
"""
|
|
30
|
+
Contains commands related to the creation of idmtools.ini.
|
|
31
|
+
|
|
32
|
+
With the config command, you can :
|
|
33
|
+
- Generate an idmtools.ini file in the current directory
|
|
34
|
+
- Add a configuration block
|
|
35
|
+
"""
|
|
36
|
+
ctx.ensure_object(dict)
|
|
37
|
+
if global_config:
|
|
38
|
+
from idmtools import IdmConfigParser
|
|
39
|
+
config_path = IdmConfigParser.get_global_configuration_name()
|
|
40
|
+
|
|
41
|
+
# Create a config parser and read the file if it exist
|
|
42
|
+
# The comment prefixes and allow_no_value is a truck to keep the comments even while editing
|
|
43
|
+
cp = configparser.ConfigParser(comment_prefixes='/', allow_no_value=True)
|
|
44
|
+
if os.path.exists(config_path):
|
|
45
|
+
cp.read_file(open(config_path))
|
|
46
|
+
|
|
47
|
+
# Store the config parser in the context
|
|
48
|
+
ctx.obj["cp"] = cp
|
|
49
|
+
ctx.obj["path"] = config_path
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def slugify(value):
|
|
53
|
+
"""
|
|
54
|
+
Slugify the option.
|
|
55
|
+
|
|
56
|
+
This means upper-casing and replacing spaces with "-"
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
value: Item to slugify
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Slugified string
|
|
63
|
+
"""
|
|
64
|
+
value = value.upper()
|
|
65
|
+
value = value.replace(" ", "_")
|
|
66
|
+
return value
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def validate_block_name(context, value):
|
|
70
|
+
"""
|
|
71
|
+
Validate if a block name exists, and if so, should we overwrite it.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
context: Context object
|
|
75
|
+
value: Value to check
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
Slugified value name
|
|
79
|
+
"""
|
|
80
|
+
cp = context.obj["cp"]
|
|
81
|
+
value = slugify(value)
|
|
82
|
+
if value in cp.sections():
|
|
83
|
+
secho(f"The {value} block already exists in the selected ini file.", fg="bright_yellow")
|
|
84
|
+
click.confirm(click.style("Do you want to continue and overwrite the existing block?", fg="bright_yellow"), default=False, abort=True)
|
|
85
|
+
|
|
86
|
+
# Remove the block from the config parser
|
|
87
|
+
del cp[value]
|
|
88
|
+
|
|
89
|
+
context.obj['cp'] = cp
|
|
90
|
+
|
|
91
|
+
return value
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@config.command()
|
|
95
|
+
@click.option("--block_name", prompt="New block name",
|
|
96
|
+
help="Name of the new block in the file",
|
|
97
|
+
callback=lambda c, p, v: validate_block_name(c, v),
|
|
98
|
+
type=click.STRING)
|
|
99
|
+
@click.option('--platform', default='COMPS', type=click.Choice(AVAILABLE_PLATFORMS.keys()), prompt="Platform type")
|
|
100
|
+
@click.pass_context
|
|
101
|
+
def block(ctx, block_name, platform):
|
|
102
|
+
"""
|
|
103
|
+
Command to create/replace a block in the selected idmtools.ini.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
ctx: Context containing the path of idmtools.ini and the associated configparser
|
|
107
|
+
block_name: Name of the block to create/replace
|
|
108
|
+
platform: Selected platform
|
|
109
|
+
"""
|
|
110
|
+
config_path = ctx.obj['path']
|
|
111
|
+
print("\n" + Style.BRIGHT + "-" * 50)
|
|
112
|
+
print("idmtools.ini Utility")
|
|
113
|
+
print(f"- INI Location: {config_path}")
|
|
114
|
+
print(f"- Selected block: {block_name}")
|
|
115
|
+
print(f"- Selected platform: {platform}")
|
|
116
|
+
print("-" * 50 + Style.NORMAL + "\n")
|
|
117
|
+
|
|
118
|
+
# Retrieve the platform and its associated fields
|
|
119
|
+
platform_obj = AVAILABLE_PLATFORMS[platform]
|
|
120
|
+
fields = dataclasses.fields(platform_obj.get_type())
|
|
121
|
+
|
|
122
|
+
# Dictionary to store user choices and field defaults
|
|
123
|
+
# Store both to allow the fields callback functions to access the previous user choices regardless of defaults
|
|
124
|
+
values = {"type": platform}
|
|
125
|
+
defaults = {}
|
|
126
|
+
|
|
127
|
+
# Ask about each field
|
|
128
|
+
# The field needs to contain a `help` section in the metadata to be considered
|
|
129
|
+
for field in filter(lambda f: "help" in f.metadata, fields):
|
|
130
|
+
|
|
131
|
+
# Display the help message
|
|
132
|
+
print(f"{Fore.CYAN}{field.metadata['help']}{Fore.RESET}")
|
|
133
|
+
|
|
134
|
+
# Retrieve the metadata
|
|
135
|
+
md = dict(field.metadata)
|
|
136
|
+
|
|
137
|
+
# If a callback exists -> execute it
|
|
138
|
+
if "callback" in md:
|
|
139
|
+
md.update(md["callback"](values, field))
|
|
140
|
+
|
|
141
|
+
# Create the default
|
|
142
|
+
field_default = md.get("default", field.default if field.default is not None else '')
|
|
143
|
+
defaults[field.name] = field.default
|
|
144
|
+
|
|
145
|
+
# Handle the choices if any
|
|
146
|
+
prompt_type = click.Choice(md["choices"]) if "choices" in md else field.type
|
|
147
|
+
|
|
148
|
+
# Retrieve the validation function if any
|
|
149
|
+
if "validate" in md:
|
|
150
|
+
validation = md["validate"]
|
|
151
|
+
else:
|
|
152
|
+
validation = lambda v: (True, None) # noqa: E731
|
|
153
|
+
|
|
154
|
+
# Prompt the user
|
|
155
|
+
while True:
|
|
156
|
+
user_input = click.prompt(field.name, type=prompt_type, default=field_default, prompt_suffix=f": {Fore.GREEN}")
|
|
157
|
+
|
|
158
|
+
# Call the validation
|
|
159
|
+
result, msg = validation(user_input)
|
|
160
|
+
|
|
161
|
+
# If positive, get out
|
|
162
|
+
if result:
|
|
163
|
+
break
|
|
164
|
+
|
|
165
|
+
# Else display the error message
|
|
166
|
+
secho(msg, fg="bright_red")
|
|
167
|
+
|
|
168
|
+
# Store the value
|
|
169
|
+
values[field.name] = user_input if user_input != "" else None
|
|
170
|
+
print(Fore.RESET)
|
|
171
|
+
|
|
172
|
+
# Remove the default values from the values
|
|
173
|
+
for k, d in defaults.items():
|
|
174
|
+
if values[k] == d:
|
|
175
|
+
del values[k]
|
|
176
|
+
|
|
177
|
+
# Display a validation prompt
|
|
178
|
+
print("The following block will be added to the file:\n")
|
|
179
|
+
longest_param = max(len(p) for p in values)
|
|
180
|
+
block_parameters = "\n".join(f"{param.ljust(longest_param)} = {value}" for param, value in values.items())
|
|
181
|
+
block_headers = f"[{block_name}]"
|
|
182
|
+
block = block_headers + "\n" + block_parameters
|
|
183
|
+
secho(f"{block}\n", fg="bright_blue")
|
|
184
|
+
|
|
185
|
+
# If we decide to go ahead -> write to file
|
|
186
|
+
if click.confirm("Do you want to write this block to the file?", default=True):
|
|
187
|
+
# First re-write the content of the config parser
|
|
188
|
+
cp = ctx.obj["cp"]
|
|
189
|
+
with open(config_path, 'w') as fp:
|
|
190
|
+
cp.write(fp)
|
|
191
|
+
fp.writelines("\n" + block)
|
|
192
|
+
|
|
193
|
+
secho("Block written successfully!", fg="bright_green")
|
|
194
|
+
else:
|
|
195
|
+
secho("Aborted...", fg="bright_red")
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
if __name__ == '__main__':
|
|
199
|
+
config(["block", '--block_name', 'test'])
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Base click group definition."""
|
|
2
|
+
import logging
|
|
3
|
+
from idmtools import IdmConfigParser
|
|
4
|
+
from idmtools.core.logging import setup_logging, IdmToolsLoggingConfig
|
|
5
|
+
import click
|
|
6
|
+
from click_plugins import with_plugins
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
from importlib.metadata import entry_points
|
|
10
|
+
except ImportError:
|
|
11
|
+
from importlib_metadata import entry_points # for python 3.7
|
|
12
|
+
from idmtools_cli.iplatform_cli import IPlatformCLI
|
|
13
|
+
|
|
14
|
+
# Decorator for CLI functions that will require a platform object passed down to them
|
|
15
|
+
pass_platform_cli = click.make_pass_decorator(IPlatformCLI)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_filtered_entry_points(group):
|
|
19
|
+
"""
|
|
20
|
+
Get entry points for a specific group, compatible across Python versions.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
group (str): The entry point group to filter by.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
An iterable of entry point objects for the specified group.
|
|
27
|
+
"""
|
|
28
|
+
user_entry_points = entry_points()
|
|
29
|
+
# For Python 3.10 and newer, use the select method if available
|
|
30
|
+
if hasattr(user_entry_points, 'select'):
|
|
31
|
+
return user_entry_points.select(group=group)
|
|
32
|
+
else:
|
|
33
|
+
# For Python 3.9 and earlier, manually filter the entry points
|
|
34
|
+
return (ep for ep in user_entry_points.get(group, []))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@with_plugins(get_filtered_entry_points('idmtools_cli.cli_plugins'))
|
|
38
|
+
@click.group()
|
|
39
|
+
@click.option('--debug/--no-debug', default=False, help="When selected, enables console level logging")
|
|
40
|
+
def cli(debug):
|
|
41
|
+
"""Allows you to perform multiple idmtools commands."""
|
|
42
|
+
IdmConfigParser()
|
|
43
|
+
# init config by just calling config parser
|
|
44
|
+
if debug:
|
|
45
|
+
setup_logging(IdmToolsLoggingConfig(console=True, level=logging.DEBUG, force=True))
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
"""Define the gitrepo group cli command."""
|
|
2
|
+
import os
|
|
3
|
+
import json
|
|
4
|
+
from logging import getLogger
|
|
5
|
+
import click
|
|
6
|
+
from click import secho
|
|
7
|
+
from colorama import Fore
|
|
8
|
+
from typing import Optional, List
|
|
9
|
+
|
|
10
|
+
from idmtools.core.logging import SUCCESS
|
|
11
|
+
from idmtools_cli.cli.entrypoint import cli
|
|
12
|
+
from idmtools.utils.gitrepo import GitRepo, REPO_OWNER, GITHUB_HOME, REPO_NAME
|
|
13
|
+
from idmtools.registry.master_plugin_registry import MasterPluginRegistry
|
|
14
|
+
|
|
15
|
+
user_logger = getLogger('user')
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@cli.group(short_help="Contains commands related to examples download.")
|
|
19
|
+
def gitrepo():
|
|
20
|
+
"""Contains command to download examples."""
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@gitrepo.command()
|
|
25
|
+
@click.option('--raw', default=False, type=bool, help="Files in detail")
|
|
26
|
+
def view(raw: Optional[bool]):
|
|
27
|
+
"""
|
|
28
|
+
Display all idmtools available examples.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
raw: True/False - display results in details or simplified format
|
|
32
|
+
"""
|
|
33
|
+
list_examples(raw)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def list_examples(raw: bool):
|
|
37
|
+
"""
|
|
38
|
+
Display all idmtools available examples.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
raw: True/False - display results in details or simplified format
|
|
42
|
+
"""
|
|
43
|
+
examples = get_plugins_examples()
|
|
44
|
+
if raw:
|
|
45
|
+
user_logger.info(json.dumps(examples, indent=3))
|
|
46
|
+
exit(0)
|
|
47
|
+
for plugin, urls in examples.items():
|
|
48
|
+
user_logger.info(f'\n{plugin}')
|
|
49
|
+
urls = [urls] if isinstance(urls, str) else urls
|
|
50
|
+
url_list = [f' - {url}' for url in urls]
|
|
51
|
+
user_logger.info('\n'.join(url_list))
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# alias under examples
|
|
55
|
+
@cli.group(help="Display a list of examples organized by plugin type.")
|
|
56
|
+
def examples():
|
|
57
|
+
"""Examples group command. alias for gitrepo command."""
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@examples.command(name='list', help="List examples available")
|
|
62
|
+
@click.option('--raw', default=False, type=bool, help="Files in detail")
|
|
63
|
+
def list_m(raw: Optional[bool]):
|
|
64
|
+
"""Alternate path for gitrepo."""
|
|
65
|
+
list_examples(raw)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@gitrepo.command()
|
|
69
|
+
@click.option('--owner', default=REPO_OWNER, help="Repo owner")
|
|
70
|
+
@click.option('--page', default=1, help="Pagination")
|
|
71
|
+
def repos(owner: Optional[str], page: Optional[int]):
|
|
72
|
+
"""
|
|
73
|
+
Display all public repos of the owner.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
owner: Repo owner
|
|
77
|
+
page: Result pagination
|
|
78
|
+
"""
|
|
79
|
+
gr = GitRepo(owner)
|
|
80
|
+
try:
|
|
81
|
+
repos = gr.list_public_repos(page=page)
|
|
82
|
+
except Exception as ex:
|
|
83
|
+
secho(str(ex), fg="yellow")
|
|
84
|
+
exit(1)
|
|
85
|
+
repos_full = [f' - {GITHUB_HOME}/{r}' for r in repos]
|
|
86
|
+
user_logger.log(SUCCESS, f"GitHub Owner: {gr.repo_owner}")
|
|
87
|
+
user_logger.info('\n'.join(repos_full))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@gitrepo.command()
|
|
91
|
+
@click.option('--owner', default=REPO_OWNER, help="Repo owner")
|
|
92
|
+
@click.option('--repo', default=REPO_NAME, help="Repo name")
|
|
93
|
+
def releases(owner: Optional[str], repo: Optional[str]):
|
|
94
|
+
"""
|
|
95
|
+
Display all the releases of the repo.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
owner: Repo owner
|
|
99
|
+
repo: Repo name
|
|
100
|
+
"""
|
|
101
|
+
gr = GitRepo(owner, repo)
|
|
102
|
+
try:
|
|
103
|
+
rels = gr.list_repo_releases()
|
|
104
|
+
except Exception as ex:
|
|
105
|
+
secho(str(ex), fg="yellow")
|
|
106
|
+
exit(1)
|
|
107
|
+
rels_list = [f' - {r}' for r in rels]
|
|
108
|
+
secho(f'The Repo: {gr.repo_home_url}', fg="green")
|
|
109
|
+
user_logger.info('\n'.join(rels_list))
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@gitrepo.command()
|
|
113
|
+
@click.option('--url', required=True, help="Repo files url")
|
|
114
|
+
@click.option('--raw', default=False, type=bool, help="Display files in detail")
|
|
115
|
+
def peep(url: Optional[str], raw: Optional[bool]):
|
|
116
|
+
"""
|
|
117
|
+
Display all current files/dirs of the repo folder (not recursive).
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
url: GitHub repo files url (required)
|
|
121
|
+
raw: Display details or not
|
|
122
|
+
"""
|
|
123
|
+
user_logger.info(f'Peep: {url}')
|
|
124
|
+
user_logger.info('Processing...')
|
|
125
|
+
try:
|
|
126
|
+
result = GitRepo().peep(url)
|
|
127
|
+
except Exception as ex:
|
|
128
|
+
secho(f'Failed to access: {url}', fg="yellow")
|
|
129
|
+
user_logger.error(ex)
|
|
130
|
+
exit(1)
|
|
131
|
+
|
|
132
|
+
secho(f"Item Count: {len(result)}", fg="green")
|
|
133
|
+
if raw:
|
|
134
|
+
user_logger.info(json.dumps(result, indent=3))
|
|
135
|
+
exit(0)
|
|
136
|
+
|
|
137
|
+
for file in result:
|
|
138
|
+
if file['type'] == 'dir':
|
|
139
|
+
secho(f" - {file['name']}", fg="yellow")
|
|
140
|
+
else:
|
|
141
|
+
secho(f" - {file['name']}")
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@gitrepo.command()
|
|
145
|
+
@click.option('--type', default=None, multiple=True, help="Download examples by type (COMPSPlatform, PythonTask, etc)")
|
|
146
|
+
@click.option('--url', default=None, multiple=True, help="Repo files url")
|
|
147
|
+
@click.option('--output', default='./', help="Files download destination")
|
|
148
|
+
def download(type: Optional[str], url: Optional[str], output: Optional[str]):
|
|
149
|
+
"""
|
|
150
|
+
Download files from GitHub repo to user location.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
type: Object type (COMPSPlatform, PythonTask, etc)
|
|
154
|
+
url: GitHub repo files url
|
|
155
|
+
output: Local folder
|
|
156
|
+
|
|
157
|
+
Returns: Files download count
|
|
158
|
+
"""
|
|
159
|
+
download_github_repo(output, url, example_types=type)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@examples.command(name='download')
|
|
163
|
+
@click.option('--type', default=None, multiple=True, help="Download examples by type (COMPSPlatform, PythonTask, etc)")
|
|
164
|
+
@click.option('--url', default=None, multiple=True, help="Repo files url")
|
|
165
|
+
@click.option('--output', default='./', help="Files download destination")
|
|
166
|
+
def download_alias(type: Optional[str], url: Optional[List[str]], output: Optional[str]):
|
|
167
|
+
"""
|
|
168
|
+
Download examples from specified location.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
type: Object type (COMPSPlatform, PythonTask, etc)
|
|
172
|
+
url: GitHub repo files url
|
|
173
|
+
output: Local folder
|
|
174
|
+
|
|
175
|
+
Returns: Files download count
|
|
176
|
+
"""
|
|
177
|
+
download_github_repo(output, url, example_types=list(type))
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def download_github_repo(output, urls: List[str], example_types: List[str] = None):
|
|
181
|
+
"""
|
|
182
|
+
Download github repo.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
output: Output folder
|
|
186
|
+
urls: Urls to download
|
|
187
|
+
example_types: List of example types to download
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
None
|
|
191
|
+
"""
|
|
192
|
+
total = 0
|
|
193
|
+
if example_types:
|
|
194
|
+
urls = list(urls)
|
|
195
|
+
urls.extend(get_examples_by_types(list(example_types)))
|
|
196
|
+
urls = list(filter(None, urls)) if urls else None
|
|
197
|
+
option, file_dict = choice(urls)
|
|
198
|
+
secho(f"This is your selection: {option}", fg="bright_blue")
|
|
199
|
+
# If we decide to go ahead -> write to file
|
|
200
|
+
if click.confirm("Do you want to go ahead to download files?", default=True):
|
|
201
|
+
simplified_option, duplicated = remove_duplicated_files(option, file_dict)
|
|
202
|
+
secho(f'Removed duplicated files: {duplicated}', fg="bright_red")
|
|
203
|
+
for i in simplified_option:
|
|
204
|
+
total += download_file(i, file_dict[i], output)
|
|
205
|
+
|
|
206
|
+
secho(f"Total Files: {total}", fg="yellow")
|
|
207
|
+
secho("Download successfully!", fg="bright_green")
|
|
208
|
+
else:
|
|
209
|
+
secho("Aborted...", fg="bright_red")
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def get_examples_by_types(example_types: List[str]) -> List[str]:
|
|
213
|
+
"""
|
|
214
|
+
Get examples from Plugins.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
example_types: List of types(plugins) to pull examples from
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
List of strings to download
|
|
221
|
+
"""
|
|
222
|
+
items = get_plugins_examples()
|
|
223
|
+
result = []
|
|
224
|
+
for example in example_types:
|
|
225
|
+
if example in items:
|
|
226
|
+
result.extend(items[example])
|
|
227
|
+
else:
|
|
228
|
+
user_logger.warning(f"Cannot find example type {example}")
|
|
229
|
+
return result
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def download_file(option: int, url: str, output: str):
|
|
233
|
+
"""
|
|
234
|
+
Use GitRepo utility to download files.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
option: file index
|
|
238
|
+
url: file url
|
|
239
|
+
output: local folder to save files
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
file count
|
|
243
|
+
"""
|
|
244
|
+
# Display file information
|
|
245
|
+
click.echo(f"\nDownloading Files {option if option else ''}: '{url}'")
|
|
246
|
+
click.echo(f'Local Folder: {os.path.abspath(output)}')
|
|
247
|
+
secho('Processing...')
|
|
248
|
+
|
|
249
|
+
# Start to download files
|
|
250
|
+
gr = GitRepo()
|
|
251
|
+
total = gr.download(path=url, output_dir=output)
|
|
252
|
+
return total
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def get_plugins_examples():
|
|
256
|
+
"""
|
|
257
|
+
Collect all idmtools files.
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
files urls as dict
|
|
261
|
+
|
|
262
|
+
Notes:
|
|
263
|
+
test_examples = {
|
|
264
|
+
'TestA': 'https://github.com/dustin/py-github/tree/main/github/data',
|
|
265
|
+
'TestB': 'https://github.com/dustin/py-github/tree/main/github',
|
|
266
|
+
'TestC': 'https://github.com/dustin/py-github/tree/main/github/__init__.py',
|
|
267
|
+
'TestD': ['https://github.com/dustin/py-github/tree/main/github',
|
|
268
|
+
'https://github.com/dustin/py-github/tree/main/github/data']
|
|
269
|
+
}
|
|
270
|
+
"""
|
|
271
|
+
# Collect all idmtools examples
|
|
272
|
+
plugin_map = MasterPluginRegistry().get_plugin_map()
|
|
273
|
+
|
|
274
|
+
example_plugins = {}
|
|
275
|
+
for spec_name, plugin in plugin_map.items():
|
|
276
|
+
try:
|
|
277
|
+
plugin_url_list = plugin.get_example_urls()
|
|
278
|
+
if len(plugin_url_list) > 0:
|
|
279
|
+
example_plugins[spec_name] = plugin_url_list
|
|
280
|
+
except Exception as ex:
|
|
281
|
+
user_logger.error(ex)
|
|
282
|
+
|
|
283
|
+
return example_plugins
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def choice(urls: list = None):
|
|
287
|
+
"""
|
|
288
|
+
Take urls as user selection or prompt user for file selections.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
urls: user provided files
|
|
292
|
+
|
|
293
|
+
Returns: True/False and results (List)
|
|
294
|
+
"""
|
|
295
|
+
if urls is None:
|
|
296
|
+
files = get_plugins_examples()
|
|
297
|
+
|
|
298
|
+
# Collect all files and remove duplicates
|
|
299
|
+
url_list = []
|
|
300
|
+
for exp_urls in files.values():
|
|
301
|
+
exp_urls = [exp_urls] if isinstance(exp_urls, str) else exp_urls
|
|
302
|
+
url_list.extend(list(map(str.lower, exp_urls)))
|
|
303
|
+
else:
|
|
304
|
+
url_list = urls
|
|
305
|
+
|
|
306
|
+
# Remove duplicates
|
|
307
|
+
url_list = list(set(url_list))
|
|
308
|
+
# Soring urls
|
|
309
|
+
url_list = sorted(url_list, reverse=False)
|
|
310
|
+
|
|
311
|
+
# Provide index to each file
|
|
312
|
+
file_dict = {}
|
|
313
|
+
for i in range(len(url_list)):
|
|
314
|
+
file_dict[i + 1] = url_list[i]
|
|
315
|
+
|
|
316
|
+
# Pre-view files for user to select
|
|
317
|
+
file_list = [f' {i}. {url}' for i, url in file_dict.items()]
|
|
318
|
+
user_logger.info('File List:')
|
|
319
|
+
user_logger.info('\n'.join(file_list))
|
|
320
|
+
|
|
321
|
+
if urls:
|
|
322
|
+
# Return without user prompt for selection
|
|
323
|
+
return ['all'], file_dict
|
|
324
|
+
|
|
325
|
+
# Make sure user makes correct selection
|
|
326
|
+
choice_set = set(range(1, len(url_list) + 1))
|
|
327
|
+
choice_set.add('all')
|
|
328
|
+
while True:
|
|
329
|
+
user_input = click.prompt(
|
|
330
|
+
f"\nSelect files (multiple) for download (all or 1-{len(url_list)} separated by space)", type=str,
|
|
331
|
+
default='all',
|
|
332
|
+
prompt_suffix=f": {Fore.GREEN}")
|
|
333
|
+
valid, result = validate(user_input, choice_set)
|
|
334
|
+
|
|
335
|
+
if valid:
|
|
336
|
+
user_input = sorted(result, reverse=False)
|
|
337
|
+
break
|
|
338
|
+
|
|
339
|
+
# Else display the error message
|
|
340
|
+
secho(f'This is not correct choice: {result}', fg="bright_red")
|
|
341
|
+
|
|
342
|
+
# Return user selection and indexed files
|
|
343
|
+
return user_input, file_dict
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def validate(user_input: object, choice_set: set):
|
|
347
|
+
"""
|
|
348
|
+
Validate user_input against num_set.
|
|
349
|
+
|
|
350
|
+
Args:
|
|
351
|
+
user_input: user input
|
|
352
|
+
choice_set: test against this set
|
|
353
|
+
|
|
354
|
+
Returns: True/False and result (List)
|
|
355
|
+
"""
|
|
356
|
+
# Normalize user selection
|
|
357
|
+
selection = user_input.lower().strip().split(' ')
|
|
358
|
+
selection = list(filter(None, selection))
|
|
359
|
+
selection = [int(a) if a.isdigit() else a for a in selection]
|
|
360
|
+
|
|
361
|
+
# Find difference
|
|
362
|
+
extra = set(selection) - choice_set
|
|
363
|
+
|
|
364
|
+
# Return True/False along with selection details
|
|
365
|
+
if len(extra) == 0 and len(selection) > 0:
|
|
366
|
+
if 'all' in selection:
|
|
367
|
+
selection = ['all']
|
|
368
|
+
return True, selection
|
|
369
|
+
else:
|
|
370
|
+
return False, list(extra)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def remove_duplicated_files(user_selected: list, file_dict: dict):
|
|
374
|
+
"""
|
|
375
|
+
Removed duplicated files.
|
|
376
|
+
|
|
377
|
+
Args:
|
|
378
|
+
user_selected: user selection
|
|
379
|
+
file_dict: all files
|
|
380
|
+
|
|
381
|
+
Returns: simplified selection, duplicated selection
|
|
382
|
+
"""
|
|
383
|
+
if 'all' in user_selected:
|
|
384
|
+
user_selected = range(1, len(file_dict) + 1)
|
|
385
|
+
|
|
386
|
+
duplicated_selection = []
|
|
387
|
+
for i in range(len(user_selected)):
|
|
388
|
+
pre = [] if i == 0 else user_selected[0:i]
|
|
389
|
+
if any([file_dict[user_selected[i]].startswith(file_dict[j]) for j in pre]):
|
|
390
|
+
duplicated_selection.append(user_selected[i])
|
|
391
|
+
|
|
392
|
+
simplified_selection = [i for i in user_selected if i not in duplicated_selection]
|
|
393
|
+
return simplified_selection, duplicated_selection
|