Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Default values data #22

Merged
merged 11 commits into from
Sep 17, 2024
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"python.REPL.enableREPLSmartSend": false
}
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Added `cat` command: Users can now see the source of an `install.cfg` file with `ibi cat`.
* Added `command` macro, which explicitly removes line breaks.
* Added that each line is executed as command individually.

* added custom default options for fast repeating installing.

## Removed

* Removed implicit removal of line breaks.
* removed automatic added default key for os systems.


### Changed
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ Options:
-h, --help Show this message and exit.

Commands:

default Default command to create custom default settings with add,...
cat Shows source of installation instructions config file.
install Installs with config and parameters given.
show Shows installation instructions for your specified config file...
Expand Down
213 changes: 179 additions & 34 deletions installation_instruction/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@
# limitations under the License.

from sys import exit
from os.path import isfile, isdir
from subprocess import run
import platform

import os
import click
import json
import platformdirs

from .__init__ import __version__, __description__, __repository__, __author__, __author_email__, __license__
from .get_flags_and_options_from_schema import _get_flags_and_options
Expand All @@ -29,30 +31,6 @@
License: {__license__}
Repository: {__repository__}"""

def _get_system(option_types):
"""
Returns the os from the list of possible os systems defined in the schema.

:param option_types: list of system from the schema.
:type option_types: list
:return: os system from input list or None.
:rtype: string or None
"""

system = platform.system()
system_names = {
'Linux': 'linux',
'Darwin': 'mac',
'Windows': 'win',
}

new_default = system_names.get(system,None)
for type in option_types:
if new_default in type.lower():
return type

return None


class ConfigReadCommand(click.MultiCommand):
"""
Expand All @@ -74,18 +52,11 @@ def get_command(self, ctx, config_file: str) -> click.Command|None:

try:
instruction = InstallationInstruction.from_file(config_file)
options = _get_flags_and_options(instruction.schema, getattr(instruction, "misc", None))
options = _get_flags_and_options(instruction.schema, getattr(instruction, "misc", None),inst=True)
except Exception as e:
_red_echo("Error (parsing options from schema): " + str(e))
exit(1)

#set new default value for __os__ Option
for option in options:
if '__os__' in option.name:
system_default = _get_system(option.type.choices)
if system_default:
option.default = system_default

def callback(**kwargs):
inst = instruction.validate_and_render(kwargs)
if inst[1]:
Expand All @@ -112,6 +83,158 @@ def callback(**kwargs):
params=options,
callback=callback,
)

class ConfigCommandGroup(click.Group):
"""
Custom click command group class for default commands with subcommands.
"""
def __init__(self, *args, **kwargs):
super().__init__(
*args,
**kwargs,
subcommand_metavar="CONFIG_FILE/FOLDER/GIT_REPO_URL [OPTIONS]...",
options_metavar="",
)

def get_command(self, ctx, config_file: str , **kwargs) -> click.Command|None:

(_temp_dir, config_file) = _get_install_config_file(config_file)

user_data_dir = platformdirs.user_data_dir("default_data_local","installation_instruction")
os.makedirs(user_data_dir, exist_ok=True)
PATH_TO_DEFAULT_FILE = os.path.join(user_data_dir, "DEFAULT_DATA.json")
ctx.obj['path'] = PATH_TO_DEFAULT_FILE
default_data = {}
if isfile(PATH_TO_DEFAULT_FILE):
with open(PATH_TO_DEFAULT_FILE,"r") as f:
default_data = json.load(f)
ctx.obj['default_data'] = default_data
ctx.obj['new_file'] = False
else:
ctx.obj['new_file'] = True
try:
instruction = InstallationInstruction.from_file(config_file)
schema = instruction.parse_schema()
title = schema.get("$id")
ctx.obj['title'] = title
options, ctx.obj['defaults'] = _get_flags_and_options(instruction.schema, getattr(instruction, "misc", None))
except Exception as e:
_red_echo("Error (parsing options from schema): " + str(e))
exit(1)

def callback(**kwargs):
new_file = ctx.obj['new_file']
PATH_TO_DEFAULT_FILE = ctx.obj['path']
if ctx.obj["MODE"] == "add":
orig_default = ctx.obj['defaults']
default_data = {}
if not new_file:
with open(PATH_TO_DEFAULT_FILE,"r") as f:
default_data = json.load(f)
title = ctx.obj['title']
exist_project = title in default_data.keys()
defaults_settings = {}
if exist_project:
defaults_settings = default_data.get(title)
for option in options:
if option.name in kwargs.keys():
if kwargs.get(option.name) == None:
pass
elif type(option.type) == click.Choice:
if kwargs.get(option.name) in option.type.choices:
defaults_settings[option.name]= kwargs.get(option.name)
else:
_red_echo(f"There is no {kwargs.get(option.name)} option in {option.name}")
else:
if type(option.type)== click.types.StringParamType:
defaults_settings[option.name]= kwargs.get(option.name)
elif type(option.type)== click.types.IntParamType:
try:
def_val = int(kwargs.get(option.name))
except ValueError:
_red_echo(f"{kwargs.get(option.name)} is no int!")
exit(1)
defaults_settings[option.name]= def_val
elif type(option.type)== click.types.FloatParamType:
try:
def_val = float(kwargs.get(option.name))
except ValueError:
_red_echo(f"{kwargs.get(option.name)} is no float!")
exit(1)
defaults_settings[option.name]= def_val
elif type(option.type)== click.types.BoolParamType:
if defaults_settings.get(option.name) == True:
defaults_settings[option.name] = False
else:
defaults_settings[option.name] = kwargs.get(option.name, False)
remove = []
for key in defaults_settings.keys():
if str(defaults_settings.get(key)) == str(orig_default.get(key)):
remove.append(key)
for key in remove:
defaults_settings.pop(key)
if defaults_settings:
default_data[title] = defaults_settings
else:
default_data.pop(title)
if not default_data:
os.remove(PATH_TO_DEFAULT_FILE)
click.echo(f"removed default file as all setting were the same like the defaults of the developer.")
else:
with open(PATH_TO_DEFAULT_FILE,"w") as f:
json.dump(default_data, f,indent= 4)
if new_file:
click.echo("successfully created a new default file at:")
click.echo(PATH_TO_DEFAULT_FILE)
if exist_project:
click.echo(f"successfully added {title} to the default_data.")
else:
click.echo(f"successfully applied changes to {title} in the default_data.")

elif ctx.obj["MODE"] == "remove":
if not isfile(PATH_TO_DEFAULT_FILE):
_red_echo(f"There exists no Default File to remove from.")
exit(1)

with open(PATH_TO_DEFAULT_FILE,"r") as f:
default_data = json.load(f)
title = ctx.obj['title']
deleted_item = default_data.pop(title,None)
if not deleted_item:
_red_echo(f"There exists no project to remove.")
else:
with open(PATH_TO_DEFAULT_FILE,"w") as f:
json.dump(default_data, f,indent=4)
click.echo(f"successfully deleted {title} from the default_data.")
if not default_data:
os.remove(PATH_TO_DEFAULT_FILE)
click.echo(f"removed default file as it was empty.")

elif ctx.obj["MODE"] == "list":
if not isfile(PATH_TO_DEFAULT_FILE):
_red_echo(f"There exists no default file to list projects from.")
exit(1)
with open(PATH_TO_DEFAULT_FILE,"r") as f:
default_data = json.load(f)

title = ctx.obj['title']
if not default_data.get(title,False):
click.echo(f"{title} has no entry in the default file.")
else:
click.echo("")
click.echo(f"{title} has the following user default configurations:")
click.echo("")
dic = default_data.get(title)
for i in dic.keys():
click.echo(f"{i}: {dic.get(i)}")
click.echo("")

exit(0)
return click.Command(
name=config_file,
params=options,
callback=callback,
)

@click.command(help="Shows source of installation instructions config file.")
@click.argument("path")
Expand All @@ -133,6 +256,27 @@ def install(ctx, verbose):
ctx.obj['MODE'] = "install"
ctx.obj['INSTALL_VERBOSE'] = verbose

@click.group( context_settings={"help_option_names": ["-h", "--help"]}, help="Default command to create custom default settings with add, remove, and list.")
@click.pass_context
def default(ctx):
if ctx.invoked_subcommand is None:
click.echo('default needs a subcommand.(add, remove or list)')

@default.command(cls=ConfigCommandGroup,help="Add a new project configuration or change existing ones.")
@click.pass_context
def add(ctx):
ctx.obj['MODE'] = "add"

@default.command(cls=ConfigCommandGroup, help="Remove an existing default configuration of a project.")
@click.pass_context
def remove(ctx):
ctx.obj['MODE'] = "remove"

@default.command(cls=ConfigCommandGroup, help="Lists default configurations of a project.")
@click.pass_context
def list(ctx):
ctx.obj['MODE'] = "list"

@click.group(context_settings={"help_option_names": ["-h", "--help"]}, help=__description__)
@click.version_option(version=__version__, message=VERSION_STRING)
@click.pass_context
Expand All @@ -142,6 +286,7 @@ def main(ctx):
main.add_command(cat)
main.add_command(show)
main.add_command(install)
main.add_command(default)

if __name__ == "__main__":
main()
Expand Down
36 changes: 31 additions & 5 deletions installation_instruction/get_flags_and_options_from_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@

import click
from click import Option, Choice
import os
import platformdirs
import json
from os.path import isfile

SCHEMA_TO_CLICK_TYPE_MAPPING = {
"string": click.STRING,
Expand All @@ -22,7 +26,7 @@
"boolean": click.BOOL,
}

def _get_flags_and_options(schema: dict, misc: dict = None) -> list[Option]:
def _get_flags_and_options(schema: dict, misc: dict = None, inst: bool = False) -> list[Option]:
"""
Generates Click flags and options from a JSON schema.

Expand All @@ -33,18 +37,35 @@
:rtype: list[Option]
"""
options = []
alt_default = {}
required_args = set(schema.get('required', []))

description = misc.get("description", {}) if misc is not None else {}

change_default = False
if inst:
project_title = schema.get("$id")
user_data_dir = platformdirs.user_data_dir("default_data_local","installation_instruction")
PATH_TO_DEFAULT_FILE = os.path.join(user_data_dir, "DEFAULT_DATA.json")
default_data = {}
if isfile(PATH_TO_DEFAULT_FILE):
with open(PATH_TO_DEFAULT_FILE,"r") as f:
default_data = json.load(f)

Check warning on line 53 in installation_instruction/get_flags_and_options_from_schema.py

View check run for this annotation

Codecov / codecov/patch

installation_instruction/get_flags_and_options_from_schema.py#L52-L53

Added lines #L52 - L53 were not covered by tests
if project_title in default_data.keys():
default_data = default_data.get(project_title)
change_default = True

Check warning on line 56 in installation_instruction/get_flags_and_options_from_schema.py

View check run for this annotation

Codecov / codecov/patch

installation_instruction/get_flags_and_options_from_schema.py#L55-L56

Added lines #L55 - L56 were not covered by tests


for key, value in schema.get('properties', {}).items():
pretty_key = key
pretty_key = pretty_key.replace('_', '-').replace(' ', '-')
option_name = '--{}'.format(pretty_key)
option_type = value.get('type', 'string')
option_description = value.get('description', '') or description.get(key, "")
option_default = value.get('default', None)

if change_default and key in default_data.keys():
option_default = default_data.get(key)

Check warning on line 66 in installation_instruction/get_flags_and_options_from_schema.py

View check run for this annotation

Codecov / codecov/patch

installation_instruction/get_flags_and_options_from_schema.py#L66

Added line #L66 was not covered by tests
else:
option_default = value.get('default', None)
if "enum" in value:
option_type = Choice( value["enum"] )
else:
Expand All @@ -54,7 +75,11 @@
is_flag=(option_type == click.BOOL)
if is_flag and required:
option_name = option_name + "/--no-{}".format(pretty_key)


if not inst:
alt_default[key]=option_default
required = False
option_default = None

Check warning on line 82 in installation_instruction/get_flags_and_options_from_schema.py

View check run for this annotation

Codecov / codecov/patch

installation_instruction/get_flags_and_options_from_schema.py#L80-L82

Added lines #L80 - L82 were not covered by tests
options.append(Option(
param_decls=[option_name],
type=option_type,
Expand All @@ -65,5 +90,6 @@
show_choices=True,
is_flag=is_flag,
))

if not inst:
return options, alt_default

Check warning on line 94 in installation_instruction/get_flags_and_options_from_schema.py

View check run for this annotation

Codecov / codecov/patch

installation_instruction/get_flags_and_options_from_schema.py#L94

Added line #L94 was not covered by tests
return options
2 changes: 1 addition & 1 deletion installation_instruction/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,4 +167,4 @@ def _load_template_from_string(string: str) -> Template:
trim_blocks=True,
lstrip_blocks=True
)
return env.from_string(string)
return env.from_string(string)
1 change: 1 addition & 0 deletions installation_instruction/installation_instruction.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ def parse_schema(self) -> dict:
"""
result = {}

result["$id"] = self.schema.get("$id","")
result["title"] = self.schema.get("title", "")
result["description"] = self.schema.get("description", "")
result["properties"] = {}
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ dependencies = [
"PyYAML",
"click < 9.0.0a0",
"GitPython",
"platformdirs",
]

[project.optional-dependencies]
Expand Down
Loading