diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 6fe09f5..0da4bcd 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -38,17 +38,17 @@ jobs: - name: Run semantic release (for tests) if: ${{ github.event_name != 'workflow_dispatch' }} - run: makim release.dry --verbose + run: makim --verbose release.dry - name: Run semantic release if: ${{ github.event_name == 'workflow_dispatch' }} run: | poetry config pypi-token.pypi ${PYPI_TOKEN} - makim release.ci --verbose + makim --verbose release.ci - name: Generate documentation with changes from semantic-release if: ${{ github.event_name == 'workflow_dispatch' }} - run: makim docs.build --verbose + run: makim --verbose docs.build - name: GitHub Pages action if: ${{ github.event_name == 'workflow_dispatch' }} diff --git a/.makim.yaml b/.makim.yaml index 18217fe..b33674e 100644 --- a/.makim.yaml +++ b/.makim.yaml @@ -62,7 +62,7 @@ groups: targets: linter: help: Run linter tools - run: pre-commit run --all-files --verbose + run: pre-commit run --all-files unittest: help: Run tests @@ -103,13 +103,10 @@ groups: shell: bash run: | export VERBOSE_FLAG='{{ "--verbose" if args.verbose_mode else "" }}' - makim --makim-file $MAKIM_FILE --help - makim --makim-file $MAKIM_FILE --version - makim --makim-file $MAKIM_FILE clean $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE default.clean $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE build $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE default.build $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE build --clean $VERBOSE_FLAG + makim $VERBOSE_FLAG --file $MAKIM_FILE --help + makim $VERBOSE_FLAG --file $MAKIM_FILE --version + makim $VERBOSE_FLAG --file $MAKIM_FILE default.clean + makim $VERBOSE_FLAG --file $MAKIM_FILE default.build complex: help: Test makim using a complex makimfile @@ -123,37 +120,37 @@ groups: shell: bash run: | export VERBOSE_FLAG='{{ "--verbose" if args.verbose_mode else "" }}' - makim --makim-file $MAKIM_FILE --help - makim --makim-file $MAKIM_FILE --version - makim --makim-file $MAKIM_FILE default.lint --verbose - makim --makim-file $MAKIM_FILE build.clean-gcda --verbose - makim --makim-file $MAKIM_FILE build.clean --verbose - makim --makim-file $MAKIM_FILE build.install --verbose - makim --makim-file $MAKIM_FILE build.release --verbose - makim --makim-file $MAKIM_FILE build.release --build-type debug --verbose - makim --makim-file $MAKIM_FILE build.release --extras '-D' --verbose - makim --makim-file $MAKIM_FILE build.release --clean --verbose - makim --makim-file $MAKIM_FILE build.dev --verbose - makim --makim-file $MAKIM_FILE env.create-file --verbose - makim --makim-file $MAKIM_FILE conda.build --verbose - makim --makim-file $MAKIM_FILE release.ci --verbose - makim --makim-file $MAKIM_FILE release.dry --verbose - makim --makim-file $MAKIM_FILE docs.api --verbose - makim --makim-file $MAKIM_FILE docs.build --verbose - makim --makim-file $MAKIM_FILE docs.clean --verbose - makim --makim-file $MAKIM_FILE docs.preview --verbose - makim --makim-file $MAKIM_FILE tests.sanitizer --verbose - makim --makim-file $MAKIM_FILE tests.code-coverage --verbose - makim --makim-file $MAKIM_FILE tests.gen-object --verbose - makim --makim-file $MAKIM_FILE tests.gen-ast --verbose - makim --makim-file $MAKIM_FILE tests.gen-llvm-ir --verbose - makim --makim-file $MAKIM_FILE tests.examples --verbose - makim --makim-file $MAKIM_FILE tests.all --verbose - makim --makim-file $MAKIM_FILE debug.fibonacci --verbose - makim --makim-file $MAKIM_FILE print.local-env-vars --verbose - makim --makim-file $MAKIM_FILE print.makim-env-vars --verbose - makim --makim-file $MAKIM_FILE print.dotenv-var --verbose - makim --makim-file $MAKIM_FILE print.nested --verbose + makim $VERBOSE_FLAG --file $MAKIM_FILE --help + makim $VERBOSE_FLAG --file $MAKIM_FILE --version + makim $VERBOSE_FLAG --file $MAKIM_FILE default.lint + makim $VERBOSE_FLAG --file $MAKIM_FILE build.clean-gcda + makim $VERBOSE_FLAG --file $MAKIM_FILE build.clean + makim $VERBOSE_FLAG --file $MAKIM_FILE build.install + makim $VERBOSE_FLAG --file $MAKIM_FILE build.release + makim $VERBOSE_FLAG --file $MAKIM_FILE build.release --build-type debug + makim $VERBOSE_FLAG --file $MAKIM_FILE build.release --extras '-D' + makim $VERBOSE_FLAG --file $MAKIM_FILE build.release --clean + makim $VERBOSE_FLAG --file $MAKIM_FILE build.dev + makim $VERBOSE_FLAG --file $MAKIM_FILE env.create-file + makim $VERBOSE_FLAG --file $MAKIM_FILE conda.build + makim $VERBOSE_FLAG --file $MAKIM_FILE release.ci + makim $VERBOSE_FLAG --file $MAKIM_FILE release.dry + makim $VERBOSE_FLAG --file $MAKIM_FILE docs.api + makim $VERBOSE_FLAG --file $MAKIM_FILE docs.build + makim $VERBOSE_FLAG --file $MAKIM_FILE docs.clean + makim $VERBOSE_FLAG --file $MAKIM_FILE docs.preview + makim $VERBOSE_FLAG --file $MAKIM_FILE tests.sanitizer + makim $VERBOSE_FLAG --file $MAKIM_FILE tests.code-coverage + makim $VERBOSE_FLAG --file $MAKIM_FILE tests.gen-object + makim $VERBOSE_FLAG --file $MAKIM_FILE tests.gen-ast + makim $VERBOSE_FLAG --file $MAKIM_FILE tests.gen-llvm-ir + makim $VERBOSE_FLAG --file $MAKIM_FILE tests.examples + makim $VERBOSE_FLAG --file $MAKIM_FILE tests.all + makim $VERBOSE_FLAG --file $MAKIM_FILE debug.fibonacci + makim $VERBOSE_FLAG --file $MAKIM_FILE print.local-env-vars + makim $VERBOSE_FLAG --file $MAKIM_FILE print.makim-env-vars + makim $VERBOSE_FLAG --file $MAKIM_FILE print.dotenv-var + makim $VERBOSE_FLAG --file $MAKIM_FILE print.nested containers: help: Test makim with containers-sugar @@ -168,7 +165,7 @@ groups: run: | export VERBOSE_FLAG='{{ "--verbose" if args.verbose_mode else "" }}' cd ./tests/ - makim --makim-file $MAKIM_FILE containers.run --verbose + makim $VERBOSE_FLAG --file $MAKIM_FILE containers.run unittest: help: Test makim using a unittest makimfile @@ -182,16 +179,16 @@ groups: shell: bash run: | export VERBOSE_FLAG='{{ "--verbose" if args.verbose_mode else "" }}' - makim --makim-file $MAKIM_FILE --help - makim --makim-file $MAKIM_FILE --version - makim --makim-file $MAKIM_FILE tests.test-1 $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE tests.test-2 --all $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE tests.test-3-a $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE tests.test-3-b $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE tests.test-4 $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE tests.test-4 --trigger-dep $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE tests.test-5 $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE tests.test-6 $VERBOSE_FLAG + makim $VERBOSE_FLAG --file $MAKIM_FILE --help + makim $VERBOSE_FLAG --file $MAKIM_FILE --version + makim $VERBOSE_FLAG --file $MAKIM_FILE tests.test-1 + makim $VERBOSE_FLAG --file $MAKIM_FILE tests.test-2 --all + makim $VERBOSE_FLAG --file $MAKIM_FILE tests.test-3-a + makim $VERBOSE_FLAG --file $MAKIM_FILE tests.test-3-b + makim $VERBOSE_FLAG --file $MAKIM_FILE tests.test-4 + makim $VERBOSE_FLAG --file $MAKIM_FILE tests.test-4 --trigger-dep + makim $VERBOSE_FLAG --file $MAKIM_FILE tests.test-5 + makim $VERBOSE_FLAG --file $MAKIM_FILE tests.test-6 vars-env: help: Test makim using env makimfile @@ -205,24 +202,31 @@ groups: shell: bash run: | export VERBOSE_FLAG='{{ "--verbose" if args.verbose_mode else "" }}' - makim --makim-file $MAKIM_FILE --help - makim --makim-file $MAKIM_FILE --version - makim --makim-file $MAKIM_FILE global-scope.test-var-env-file $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE global-scope.test-var-env $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE group-scope.test-var-env-file $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE group-scope.test-var-env $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE target-scope.test-var-env-file $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE target-scope.test-var-env $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE rerender-env.from-global $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE rerender-env.from-group $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE rerender-env.from-target $VERBOSE_FLAG + makim $VERBOSE_FLAG --file $MAKIM_FILE --help + makim $VERBOSE_FLAG --file $MAKIM_FILE --version + makim $VERBOSE_FLAG --file $MAKIM_FILE global-scope.test-var-env-file + makim $VERBOSE_FLAG --file $MAKIM_FILE global-scope.test-var-env + makim $VERBOSE_FLAG --file $MAKIM_FILE group-scope.test-var-env-file + makim $VERBOSE_FLAG --file $MAKIM_FILE group-scope.test-var-env + makim $VERBOSE_FLAG --file $MAKIM_FILE target-scope.test-var-env-file + makim $VERBOSE_FLAG --file $MAKIM_FILE target-scope.test-var-env + makim $VERBOSE_FLAG --file $MAKIM_FILE rerender-env.from-global + makim $VERBOSE_FLAG --file $MAKIM_FILE rerender-env.from-group + makim $VERBOSE_FLAG --file $MAKIM_FILE rerender-env.from-target bash: help: Test makim shell attribute with bash + shell: bash + args: + verbose-mode: + help: Run the all the tests in verbose mode + type: bool + action: store_true run: | - makim --makim-file tests/.makim-bash-main-scope.yaml main-scope.test - makim --makim-file tests/.makim-bash-group-scope.yaml group-scope.test - makim --makim-file tests/.makim-bash-target-scope.yaml target-scope.test + export VERBOSE_FLAG='{{ "--verbose" if args.verbose_mode else "" }}' + makim $VERBOSE_FLAG --file tests/.makim-bash-main-scope.yaml main-scope.test + makim $VERBOSE_FLAG --file tests/.makim-bash-group-scope.yaml group-scope.test + makim $VERBOSE_FLAG --file tests/.makim-bash-target-scope.yaml target-scope.test working-directory-absolute-path: help: | @@ -237,17 +241,17 @@ groups: shell: bash run: | export VERBOSE_FLAG='{{ "--verbose" if args.verbose_mode else "" }}' - makim --makim-file $MAKIM_FILE --help - makim --makim-file $MAKIM_FILE --version - makim --makim-file $MAKIM_FILE group-no-path.target-no-path $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE group-no-path.target-absolute $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE group-no-path.target-relative $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE group-relative.target-no-path $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE group-relative.target-absolute $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE group-relative.target-relative $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE group-absolute.target-no-path $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE group-absolute.target-absolute $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE group-absolute.target-relative $VERBOSE_FLAG + makim $VERBOSE_FLAG --file $MAKIM_FILE --help + makim $VERBOSE_FLAG --file $MAKIM_FILE --version + makim $VERBOSE_FLAG --file $MAKIM_FILE group-no-path.target-no-path + makim $VERBOSE_FLAG --file $MAKIM_FILE group-no-path.target-absolute + makim $VERBOSE_FLAG --file $MAKIM_FILE group-no-path.target-relative + makim $VERBOSE_FLAG --file $MAKIM_FILE group-relative.target-no-path + makim $VERBOSE_FLAG --file $MAKIM_FILE group-relative.target-absolute + makim $VERBOSE_FLAG --file $MAKIM_FILE group-relative.target-relative + makim $VERBOSE_FLAG --file $MAKIM_FILE group-absolute.target-no-path + makim $VERBOSE_FLAG --file $MAKIM_FILE group-absolute.target-absolute + makim $VERBOSE_FLAG --file $MAKIM_FILE group-absolute.target-relative working-directory-no-path: help: Test makim with working-directory for global no-path and its various combinations with group and target working-directory @@ -263,17 +267,17 @@ groups: export VERBOSE_FLAG='{{ "--verbose" if args.verbose_mode else "" }}' export MAKIM_FILE="$(pwd)/${MAKIM_FILE}" cd /tmp - makim --makim-file $MAKIM_FILE --help - makim --makim-file $MAKIM_FILE --version - makim --makim-file $MAKIM_FILE group-no-path.target-no-path $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE group-no-path.target-absolute $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE group-no-path.target-relative $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE group-absolute.target-no-path $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE group-absolute.target-absolute $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE group-absolute.target-relative $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE group-relative.target-no-path $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE group-relative.target-absolute $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE group-relative.target-relative $VERBOSE_FLAG + makim $VERBOSE_FLAG --file $MAKIM_FILE --help + makim $VERBOSE_FLAG --file $MAKIM_FILE --version + makim $VERBOSE_FLAG --file $MAKIM_FILE group-no-path.target-no-path + makim $VERBOSE_FLAG --file $MAKIM_FILE group-no-path.target-absolute + makim $VERBOSE_FLAG --file $MAKIM_FILE group-no-path.target-relative + makim $VERBOSE_FLAG --file $MAKIM_FILE group-absolute.target-no-path + makim $VERBOSE_FLAG --file $MAKIM_FILE group-absolute.target-absolute + makim $VERBOSE_FLAG --file $MAKIM_FILE group-absolute.target-relative + makim $VERBOSE_FLAG --file $MAKIM_FILE group-relative.target-no-path + makim $VERBOSE_FLAG --file $MAKIM_FILE group-relative.target-absolute + makim $VERBOSE_FLAG --file $MAKIM_FILE group-relative.target-relative working-directory-relative-path: help: Test makim with working-directory for global no-path and its various combinations with group and target working-directory @@ -290,14 +294,14 @@ groups: export MAKIM_FILE="$(pwd)/${MAKIM_FILE}" mkdir -p /tmp/global-relative cd /tmp - makim --makim-file $MAKIM_FILE --help - makim --makim-file $MAKIM_FILE --version - makim --makim-file $MAKIM_FILE group-no-path.target-no-path $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE group-no-path.target-absolute $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE group-no-path.target-relative $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE group-absolute.target-no-path $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE group-absolute.target-absolute $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE group-absolute.target-relative $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE group-relative.target-no-path $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE group-relative.target-absolute $VERBOSE_FLAG - makim --makim-file $MAKIM_FILE group-relative.target-relative $VERBOSE_FLAG + makim $VERBOSE_FLAG --file $MAKIM_FILE --help + makim $VERBOSE_FLAG --file $MAKIM_FILE --version + makim $VERBOSE_FLAG --file $MAKIM_FILE group-no-path.target-no-path + makim $VERBOSE_FLAG --file $MAKIM_FILE group-no-path.target-absolute + makim $VERBOSE_FLAG --file $MAKIM_FILE group-no-path.target-relative + makim $VERBOSE_FLAG --file $MAKIM_FILE group-absolute.target-no-path + makim $VERBOSE_FLAG --file $MAKIM_FILE group-absolute.target-absolute + makim $VERBOSE_FLAG --file $MAKIM_FILE group-absolute.target-relative + makim $VERBOSE_FLAG --file $MAKIM_FILE group-relative.target-no-path + makim $VERBOSE_FLAG --file $MAKIM_FILE group-relative.target-absolute + makim $VERBOSE_FLAG --file $MAKIM_FILE group-relative.target-relative diff --git a/pyproject.toml b/pyproject.toml index 360f947..48ea259 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,16 +20,17 @@ exclude = [ ] [tool.poetry.scripts] -"makim" = "makim.__main__:app" +"makim" = "makim.__main__:run_app" [tool.poetry.dependencies] -python = "^3.8.1" +python = ">=3.8.1,<4" sh = ">=2.0.0" pyyaml = ">=5.0" jinja2 = ">=2.0" xonsh = ">=0.14.0" python-dotenv = ">=0.21.1" colorama = ">=0.4.6" +typer = ">=0.9.0" [tool.poetry.group.dev.dependencies] containers-sugar = "1.9.0" @@ -98,6 +99,7 @@ quote-style = "single" [tool.bandit] exclude_dirs = ["tests"] targets = "src/makim/" +skips = ["B102"] [tool.vulture] exclude = ["tests"] diff --git a/src/makim/__init__.py b/src/makim/__init__.py index 6ab0068..d732f9d 100644 --- a/src/makim/__init__.py +++ b/src/makim/__init__.py @@ -6,4 +6,4 @@ __version__ = '1.9.1' # semantic-release -from makim.makim import Makim # noqa: F401 +from makim.core import Makim # noqa: F401 diff --git a/src/makim/__main__.py b/src/makim/__main__.py index f28fe83..21f1593 100644 --- a/src/makim/__main__.py +++ b/src/makim/__main__.py @@ -1,5 +1,5 @@ """Makim app to be called from `python -m`.""" -from makim.cli import app +from makim.cli import run_app if __name__ == '__main__': - app() + run_app() diff --git a/src/makim/cli.py b/src/makim/cli.py index 3c6820e..0e86c91 100644 --- a/src/makim/cli.py +++ b/src/makim/cli.py @@ -1,199 +1,342 @@ """Cli functions to define the arguments and to call Makim.""" -import argparse -import os +from __future__ import annotations + import sys -from pathlib import Path +from typing import Any, Callable, Dict, Optional, Type, Union, cast + +import click +import typer from makim import Makim, __version__ +app = typer.Typer( + help=( + 'Makim is a tool that helps you to organize ' + 'and simplify your helper commands.' + ), + epilog=( + 'If you have any problem, open an issue at: ' + 'https://github.com/osl-incubator/makim' + ), +) -class CustomHelpFormatter(argparse.RawTextHelpFormatter): - """Formatter for generating usage messages and argument help strings. +makim = Makim() - Only the name of this class is considered a public API. All the methods - provided by the class are considered an implementation detail. - """ - def __init__( - self, - prog, - indent_increment=2, - max_help_position=4, - width=None, - **kwargs, - ): - """Define the parameters for the argparse help text.""" - super().__init__( - prog, - indent_increment=indent_increment, - max_help_position=max_help_position, - width=width, - **kwargs, - ) +@app.callback(invoke_without_command=True) +def main( + ctx: typer.Context, + version: bool = typer.Option( + None, + '--version', + '-v', + is_flag=True, + help='Show the version and exit', + ), + file: str = typer.Option( + '.makim.yaml', + '--file', + help='Makim config file', + ), + dry_run: bool = typer.Option( + None, + '--dry-run', + is_flag=True, + help='Execute the command in dry mode', + ), + verbose: bool = typer.Option( + None, + '--verbose', + is_flag=True, + help='Execute the command in verbose mode', + ), +) -> None: + """Process envers for specific flags, otherwise show the help menu.""" + typer.echo(f'Makim file: {file}') + if version: + typer.echo(f'Version: {__version__}') + raise typer.Exit() -makim = Makim() + if ctx.invoked_subcommand is None: + typer.echo(ctx.get_help()) + raise typer.Exit(0) -def _get_args(): +def map_type_from_string(type_name) -> Type: """ - Define the arguments for the CLI. + Return a type object mapped from the type name. - note: when added new flags, update the list of flags to be - skipped at extract_makim_args function. + Parameters + ---------- + type_name : str + The string representation of the type. + + Returns + ------- + type + The corresponding Python type. """ - makim_file_default = str(Path(os.getcwd()) / '.makim.yaml') - - parser = argparse.ArgumentParser( - prog='Makim', - description=( - 'Makim is a tool that helps you to organize ' - 'and simplify your helper commands.' - ), - epilog=( - 'If you have any problem, open an issue at: ' - 'https://github.com/osl-incubator/makim' - ), - add_help=False, - formatter_class=CustomHelpFormatter, - ) - parser.add_argument( - '--help', - '-h', - action='store_true', - help='Show the help menu', - ) + type_mapping = { + 'str': str, + 'string': str, + 'int': int, + 'integer': int, + 'float': float, + 'bool': bool, + 'boolean': bool, + } + return type_mapping.get(type_name, str) + + +def normalize_string_type(type_name) -> str: + """ + Normalize the user type definition to the correct name. - parser.add_argument( - '--version', - action='store_true', - help='Show the version of the installed Makim tool.', - ) + Parameters + ---------- + type_name : str + The string representation of the type. - parser.add_argument( - '--verbose', - action='store_true', - help='Show the commands to be executed.', + Returns + ------- + str + The corresponding makim type name. + """ + type_mapping = { + 'str': 'str', + 'string': 'str', + 'int': 'int', + 'integer': 'int', + 'float': 'float', + 'bool': 'bool', + 'boolean': 'bool', + # Add more mappings as needed + } + return type_mapping.get(type_name, 'str') + + +def get_default_value( + arg_type: str, value: Any +) -> Optional[Union[str, int, float, bool]]: + """Return the default value regarding its type in a string format.""" + if arg_type == 'bool': + return False + + return value + + +def get_default_value_str(arg_type: str, value: Any) -> str: + """Return the default value regarding its type in a string format.""" + if arg_type == 'str': + return f'"{value}"' + + if arg_type == 'bool': + return 'False' + + return f'{value or 0}' + + +def create_args_string(args: Dict[str, str]) -> str: + """Return a string for arguments for a function for typer.""" + args_rendered = [] + + arg_template = ( + '{arg_name}: {arg_type} = typer.Option(' + '{default_value}, ' + '"--{name_flag}", ' + 'help="{help_text}"' + ')' ) - parser.add_argument( - '--dry-run', - action='store_true', - help="Show the commands but don't execute them.", - ) + args_data = cast(Dict[str, Dict[str, str]], args.get('args', {})) + for name, spec in args_data.items(): + name_clean = name.replace('-', '_') + arg_type = normalize_string_type(spec.get('type', 'str')) + help_text = spec.get('help', '') + default_value = '...' + + if not spec.get('required', False): + default_value = spec.get('default', '') + default_value = get_default_value_str(arg_type, default_value) + + arg_str = arg_template.format( + **{ + 'arg_name': name_clean, + 'arg_type': arg_type, + 'default_value': default_value, + 'name_flag': name, + 'help_text': help_text.replace('\n', '\\n'), + } + ) - parser.add_argument( - '--makim-file', - type=str, - default=makim_file_default, - help='Specify a custom location for the makim file.', - ) + args_rendered.append(arg_str) - try: - idx = sys.argv.index('--makim-file') - makim_file = sys.argv[idx + 1] - except ValueError: - makim_file = makim_file_default - - makim.load(makim_file) - target_help = [] - groups = makim.global_data.get('groups', []) - for group in groups: - target_help.append('\n' + group + ':') - target_help.append('-' * (len(group) + 1)) - for target_name, target_data in groups[group]['targets'].items(): - target_name_qualified = f'{group}.{target_name}' - help_text = target_data['help'] if 'help' in target_data else '' - target_help.append(f' {target_name_qualified} => {help_text}') - - if 'args' in target_data: - target_help.append(' ARGS:') - - for arg_name, arg_data in target_data['args'].items(): - target_help.append( - f' --{arg_name}: ({arg_data["type"]}) ' - f'{arg_data["help"]}' - ) - - parser.add_argument( - 'target', - nargs='?', - default=None, - help=( - 'Specify the target command to be performed. Options are:\n' - + '\n'.join(target_help) - ), - ) + return ', '.join(args_rendered) - return parser +def apply_click_options( + command_function: Callable, options: Dict[str, str] +) -> Callable: + """ + Apply Click options to a Typer command function. + + Parameters + ---------- + command_function : callable + The Typer command function to which options will be applied. + options : dict + A dictionary of options to apply. + + Returns + ------- + callable + The command function with options applied. + """ + for opt_name, opt_details in options.items(): + opt_args: dict[str, Optional[Union[str, int, float, bool, Type]]] = {} + + opt_data = cast(Dict[str, str], opt_details) + opt_type_str = normalize_string_type(opt_data.get('type', 'str')) + opt_default = get_default_value(opt_type_str, opt_data.get('default')) + + if opt_type_str == 'bool': + opt_args.update({'is_flag': True}) + + opt_args.update( + { + 'default': opt_default, + 'type': map_type_from_string(opt_type_str), + 'help': opt_data.get('help', ''), + 'show_default': True, + } + ) -def show_version(): - """Show version.""" - print(__version__) + click_option = click.option( + f'--{opt_name}', + **opt_args, # type: ignore[arg-type] + ) + command_function = click_option(command_function) + return command_function -def extract_makim_args(): - """Extract makim arguments from the CLI call.""" - makim_args = {} - index_to_remove = [] - for ind, arg in enumerate(list(sys.argv)): - if arg in [ - '--help', - '--version', - '--verbose', - '--makim-file', - '--dry-run', - ]: - continue - if not arg.startswith('--'): - continue +def create_dynamic_command(name: str, args: Dict[str, str]) -> None: + """ + Dynamically create a Typer command with the specified options. + + Parameters + ---------- + name : str + The command name. + args : dict + The command arguments and options. + """ + args_str = create_args_string(args) + args_param_list = [f'"target": "{name}"'] - index_to_remove.append(ind) + args_data = cast(Dict[str, str], args.get('args', {})) - arg_name = None - arg_value = None + for arg in list(args_data.keys()): + arg_clean = arg.replace('-', '_') + args_param_list.append(f'"--{arg}": {arg_clean}') - next_ind = ind + 1 + args_param_str = '{' + ','.join(args_param_list) + '}' + decorator = app.command(name, help=args.get('help', '')) + + function_code = ( + f'def dynamic_command({args_str}):\n' + f' makim.run({args_param_str})\n' + '\n' + ) - arg_name = sys.argv[ind] + local_vars: Dict[str, Any] = {} + try: + exec(function_code, globals(), local_vars) + except Exception as e: + breakpoint() + print(e) + print(function_code) + dynamic_command = decorator(local_vars['dynamic_command']) + + # Apply Click options to the Typer command + if 'args' in args: + options_data = cast(Dict[str, str], args.get('args', {})) + dynamic_command = apply_click_options(dynamic_command, options_data) + + +def extract_root_config() -> Dict[str, str | bool]: + """Extract the root configuration from the CLI.""" + params = sys.argv[1:] + + root_args_values_count = { + '--dry-run': 0, + '--file': 1, + '--help': 0, # not necessary to store this value + '--verbose': 0, + '--version': 0, # not necessary to store this value + } + + # default values + makim_file = '.makim.yaml' + dry_run = False + verbose = False - if ( - len(sys.argv) == next_ind - or len(sys.argv) > next_ind - and sys.argv[next_ind].startswith('--') - ): - arg_value = True - else: - arg_value = sys.argv[next_ind] - index_to_remove.append(next_ind) + try: + idx = 0 + while idx < len(params): + arg = params[idx] + if arg not in root_args_values_count: + break + + if arg == '--file': + makim_file = params[idx + 1] + elif arg == '--dry-run': + dry_run = True + elif arg == '--verbose': + verbose = True + + idx += 1 + root_args_values_count[arg] + except Exception: + red_text = typer.style( + 'The makim config file was not correctly detected. ' + 'Using the default .makim.yaml.', + fg=typer.colors.RED, + bold=True, + ) + typer.echo(red_text, err=True, color=True) - makim_args[arg_name] = arg_value + return { + 'file': makim_file, + 'dry_run': dry_run, + 'verbose': verbose, + } - # remove exclusive makim flags from original sys.argv - for ind in sorted(index_to_remove, reverse=True): - sys.argv.pop(ind) - return makim_args +def run_app() -> None: + """Run the typer app.""" + root_config = extract_root_config() + makim.load( + file=cast(str, root_config.get('file', '.makim.yaml')), + dry_run=cast(bool, root_config.get('dry_run', False)), + verbose=cast(bool, root_config.get('verbose', False)), + ) -def app(): - """Call the makim program with the arguments defined by the user.""" - makim_args = extract_makim_args() - args_parser = _get_args() - args = args_parser.parse_args() + # create targets data + # group_names = list(makim.global_data.get('groups', {}).keys()) + targets: Dict[str, Any] = {} + for group_name, group_data in makim.global_data.get('groups', {}).items(): + for target_name, target_data in group_data.get('targets', {}).items(): + targets[f'{group_name}.{target_name}'] = target_data - if args.version: - return show_version() + # Add dynamically created commands to Typer app + for name, args in targets.items(): + create_dynamic_command(name, args) - if not args.target or args.help: - return args_parser.print_help() + app() - if args.help: - return args_parser.print_help() - makim.load(args.makim_file) - makim_args.update(dict(args._get_kwargs())) - return makim.run(makim_args) +if __name__ == '__main__': + run_app() diff --git a/src/makim/makim.py b/src/makim/core.py similarity index 96% rename from src/makim/makim.py rename to src/makim/core.py index 9946f13..15a9d4a 100644 --- a/src/makim/makim.py +++ b/src/makim/core.py @@ -56,7 +56,9 @@ def _print_warning(self, message: str): class Makim(PrintPlugin): """Makim main class.""" - makim_file: str = '.makim.yaml' + file: str = '.makim.yaml' + dry_run: bool = False + verbose: bool = False global_data: dict = {} shell_app: sh.Command = sh.xonsh @@ -78,6 +80,11 @@ def __init__(self): os.environ['RAISE_SUBPROC_ERROR'] = '1' os.environ['XONSH_SHOW_TRACEBACK'] = '0' + # default + self.file = '.makim.yaml' + self.dry_run = False + self.verbose = False + def _call_shell_app(self, cmd): fd, filepath = tempfile.mkstemp(suffix='.makim', text=True) @@ -113,7 +120,7 @@ def _call_shell_app(self, cmd): os.close(fd) def _check_makim_file(self): - return Path(self.makim_file).exists() + return Path(self.file).exists() def _verify_target_conditional(self, conditional): # todo: implement verification @@ -182,7 +189,7 @@ def _change_group_data(self, group_name=None): os._exit(MakimError.MAKIM_GROUP_NOT_FOUND.value) def _load_config_data(self): - with open(self.makim_file, 'r') as f: + with open(self.file, 'r') as f: # escape template tags content = escape_template_tag(f.read()) content_io = io.StringIO(content) @@ -234,7 +241,7 @@ def _load_dotenv(self, data_scope: dict) -> dict: if not env_file.startswith('/'): # use makim file as reference for the working directory # for the .env file - env_file = str(Path(self.makim_file).parent / env_file) + env_file = str(Path(self.file).parent / env_file) if not Path(env_file).exists(): self._print_error('[EE] The given env-file was not found.') @@ -339,10 +346,7 @@ def _run_dependencies(self, args: dict): return makim_dep = deepcopy(self) args_dep_original = { - 'makim_file': args['makim_file'], 'help': args.get('help', False), - 'verbose': args.get('verbose', False), - 'dry-run': args.get('dry-run', False), 'version': args.get('version', False), 'args': {}, } @@ -392,7 +396,7 @@ def _run_dependencies(self, args: dict): args=original_args_clean, env=self.env_scoped ) if not yaml.safe_load(result): - if args.get('verbose'): + if self.verbose: self._print_info( '[II] Skipping dependency: ' f'{dep_data.get("target")}' @@ -419,7 +423,7 @@ def _run_command(self, args: dict): self.env_scoped = deepcopy(env) - args_input = {'makim_file': args['makim_file']} + args_input = {'file': self.file} for k, v in self.target_data.get('args', {}).items(): if not isinstance(v, dict): raise Exception('`args` attribute should be a dictionary.') @@ -452,7 +456,7 @@ def _run_command(self, args: dict): cmd = unescape_template_tag(str(cmd)) cmd = Template(cmd).render(args=args_input, env=env, vars=variables) - if args.get('verbose'): + if self.verbose: self._print_info('=' * 80) self._print_info( 'TARGET: ' + f'{self.group_name}.{self.target_name}' @@ -467,7 +471,7 @@ def _run_command(self, args: dict): self._print_info('>>> ' + cmd.replace('\n', '\n>>> ')) self._print_info('=' * 80) - if not args.get('dry_run') and cmd: + if not self.dry_run and cmd: self._call_shell_app(cmd) # move back the environment variable to the previous values @@ -476,9 +480,12 @@ def _run_command(self, args: dict): # public methods - def load(self, makim_file: str): + def load(self, file: str, dry_run: bool = False, verbose: bool = False): """Load makim configuration.""" - self.makim_file = makim_file + self.file = file + self.dry_run = dry_run + self.verbose = verbose + self._load_config_data() self._verify_config() self._load_shell_app() diff --git a/tests/.makim-simple.yaml b/tests/.makim-simple.yaml index 7422121..1d9b152 100644 --- a/tests/.makim-simple.yaml +++ b/tests/.makim-simple.yaml @@ -32,7 +32,7 @@ groups: type: bool action: store_true dependencies: - - target: clean + - target: default.clean if: {{ args.clean or args.clean_all }} args: all: {{ args.clean_all }} diff --git a/tests/test_failure.py b/tests/test_failure.py index fc322b3..48a8a70 100644 --- a/tests/test_failure.py +++ b/tests/test_failure.py @@ -4,9 +4,8 @@ from pathlib import Path -import pytest - import makim +import pytest from makim.errors import MakimError @@ -24,12 +23,12 @@ def test_failure(target, args, error_code): makim_file = Path(__file__).parent / '.makim-unittest.yaml' m = makim.Makim() - m.load(makim_file) + m.load(file=makim_file) args.update( { 'target': target, - 'makim_file': makim_file, + 'file': makim_file, } ) # mock the exit function used by makim diff --git a/tests/test_success.py b/tests/test_success.py index e9b02b6..0999100 100644 --- a/tests/test_success.py +++ b/tests/test_success.py @@ -1,9 +1,8 @@ """Tests for `makim` package.""" from pathlib import Path -import pytest - import makim +import pytest @pytest.mark.parametrize( @@ -25,12 +24,12 @@ def test_success(target, args): makim_file = Path(__file__).parent / '.makim-unittest.yaml' m = makim.Makim() - m.load(makim_file) + m.load(file=makim_file) args.update( { 'target': target, - 'makim_file': makim_file, + 'file': makim_file, } )