From 97ede1d9db3c05867bfaf467bf198ffd8d29e46e Mon Sep 17 00:00:00 2001 From: Adam McKellar Date: Sun, 19 May 2024 19:26:12 +0200 Subject: [PATCH 1/2] Added CLI (#10) Added cli which has show command and can show installation instructions for user given parameters. --- CHANGELOG.md | 1 + README.md | 20 +++++++- installation_instruction/__main__.py | 68 ++++++++++++++++++++++++++++ pyproject.toml | 5 +- 4 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 installation_instruction/__main__.py diff --git a/CHANGELOG.md b/CHANGELOG.md index ea38a2b..31c8537 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +* Added cli for rendering installation instructions for end users. * Added config section in readme. * Added examples. * Added many tests. diff --git a/README.md b/README.md index 6d645ca..b5d9b90 100644 --- a/README.md +++ b/README.md @@ -11,11 +11,29 @@ +## CLI Usage + +``` +Usage: ibi [OPTIONS] COMMAND [ARGS]... + +Options: + --help Show this message and exit. + +Commands: + show Shows installation instructions for your specified config file... +``` + +Options are dynamically created with the schema part of the config file. + +> [!TIP] +> Show help for a config file with: `ibi show CONFIG_FILE --help`. + + ## Config The config is comprised of a single file. (Currently there is no fixed filename.) For ease of use you should use the file extension `.yml.jinja` and develope said config file as two seperate files at first. -The config file has two parts delimited by `------` (6 or more `-`). +The config file has two parts delimited by `------` (6 or more `-`). The first part is the schema (*What is valid user input?*). The second part is the template (*What is the actual command for said user input?*). The first part must be a valid [JSON Schema](https://json-schema.org/) in [JSON](https://www.json.org/json-en.html) or to JSON capabilites restricted [YAML](https://yaml.org/) and the second part must be a valid [jinja2 template](https://jinja.palletsprojects.com/en/3.0.x/templates/). The exception to this is that `anyOf` and `oneOf` are only usable for enum like behaviour on the schema side. diff --git a/installation_instruction/__main__.py b/installation_instruction/__main__.py new file mode 100644 index 0000000..d0b8cb4 --- /dev/null +++ b/installation_instruction/__main__.py @@ -0,0 +1,68 @@ +# Copyright 2024 Adam McKellar, Kanushka Gupta, Timo Ege + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from sys import argv, exit +from os.path import isfile + +import click + +from .get_flags_and_options_from_schema import get_flags_and_options +from .installation_instruction import InstallationInstruction + + +class ConfigReadCommand(click.MultiCommand): + def __init__(self, *args, **kwargs): + super().__init__( + *args, + **kwargs, + subcommand_metavar="CONFIG_FILE [OPTIONS]...", + options_metavar="", + ) + + def get_command(self, ctx, config_file: str) -> click.Command|None: + if not isfile(config_file): + click.echo("Config file not found.") + return None + + instruction = InstallationInstruction.from_file(config_file) + options = get_flags_and_options(instruction.schema) + + def callback(**kwargs): + inst = instruction.validate_and_render(kwargs) + if not inst[1]: + print(inst[0]) + exit(0) + else: + print(inst[0]) + exit(1) + + return click.Command( + name=config_file, + params=options, + callback=callback, + ) + + +@click.command(cls=ConfigReadCommand, help="Shows installation instructions for your specified config file and parameters.") +def show(): + pass + +@click.group() +def main(): + pass + +main.add_command(show) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index edf19bf..904c1f5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,7 @@ dependencies = [ "jinja2", "jsonschema", "PyYAML", - "click" + "click < 9.0.0", ] [project.optional-dependencies] @@ -46,6 +46,9 @@ docs = [ "sphinx-autoapi", ] +# Here you can list scripts. (Listed is a shim to the cli.) +[project.scripts] +ibi = "installation_instruction.__main__:main" # The following section contains setuptools-specific configuration # options. For a full reference of available options, check the overview From 6b501edf489851b45eeff1200602f9d490c7f8ea Mon Sep 17 00:00:00 2001 From: Adam McKellar Date: Sun, 19 May 2024 19:41:05 +0200 Subject: [PATCH 2/2] Refactor the callback method in the ConfigReadCommand class to simplify the code and improve readability. --- installation_instruction/__main__.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/installation_instruction/__main__.py b/installation_instruction/__main__.py index d0b8cb4..094b790 100644 --- a/installation_instruction/__main__.py +++ b/installation_instruction/__main__.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from sys import argv, exit +from sys import exit from os.path import isfile import click @@ -22,6 +22,10 @@ class ConfigReadCommand(click.MultiCommand): + """ + Custom click command class to read config file and show installation instructions with parameters. + """ + def __init__(self, *args, **kwargs): super().__init__( *args, @@ -40,12 +44,8 @@ def get_command(self, ctx, config_file: str) -> click.Command|None: def callback(**kwargs): inst = instruction.validate_and_render(kwargs) - if not inst[1]: - print(inst[0]) - exit(0) - else: - print(inst[0]) - exit(1) + print(inst[0]) + exit(0 if not inst[1] else 1) return click.Command( name=config_file,