From 1585098513f2d21722eb7fb0418d669976922e09 Mon Sep 17 00:00:00 2001 From: Roland Thomas Date: Sat, 31 May 2025 09:29:24 -0400 Subject: [PATCH] Add init init-global to subparsers --- falyx/__main__.py | 47 ++++++++++++++++++++++-- falyx/falyx.py | 6 +++- falyx/parsers/__init__.py | 4 ++- falyx/parsers/parsers.py | 76 +++++++++++++++++++++++++++++++++++---- falyx/version.py | 2 +- pyproject.toml | 2 +- 6 files changed, 125 insertions(+), 12 deletions(-) diff --git a/falyx/__main__.py b/falyx/__main__.py index b821951..7978b95 100644 --- a/falyx/__main__.py +++ b/falyx/__main__.py @@ -8,12 +8,13 @@ Licensed under the MIT License. See LICENSE file for details. import asyncio import os import sys +from argparse import ArgumentParser, Namespace, _SubParsersAction from pathlib import Path from typing import Any from falyx.config import loader from falyx.falyx import Falyx -from falyx.parsers import CommandArgumentParser +from falyx.parsers import CommandArgumentParser, get_root_parser, get_subparsers def find_falyx_config() -> Path | None: @@ -48,6 +49,42 @@ def init_config(parser: CommandArgumentParser) -> None: ) +def init_callback(args: Namespace) -> None: + """Callback for the init command.""" + if args.command == "init": + from falyx.init import init_project + + init_project(args.name) + elif args.command == "init_global": + from falyx.init import init_global + + init_global() + + +def get_parsers() -> tuple[ArgumentParser, _SubParsersAction]: + root_parser: ArgumentParser = get_root_parser() + subparsers = get_subparsers(root_parser) + init_parser = subparsers.add_parser( + "init", + help="Initialize a new Falyx project", + description="Create a new Falyx project with mock configuration files.", + epilog="If no name is provided, the current directory will be used.", + ) + init_parser.add_argument( + "name", + type=str, + help="Name of the new Falyx project", + default=".", + nargs="?", + ) + subparsers.add_parser( + "init-global", + help="Initialize Falyx global configuration", + description="Create a global Falyx configuration at ~/.config/falyx/.", + ) + return root_parser, subparsers + + def main() -> Any: bootstrap_path = bootstrap() if not bootstrap_path: @@ -60,17 +97,23 @@ def main() -> Any: init_project, aliases=["init"], argument_config=init_config, + help_epilogue="If no name is provided, the current directory will be used.", ) flx.add_command( "G", "Initialize Falyx global configuration", init_global, aliases=["init-global"], + help_text="Create a global Falyx configuration at ~/.config/falyx/.", ) else: flx = loader(bootstrap_path) - return asyncio.run(flx.run()) + root_parser, subparsers = get_parsers() + + return asyncio.run( + flx.run(root_parser=root_parser, subparsers=subparsers, callback=init_callback) + ) if __name__ == "__main__": diff --git a/falyx/falyx.py b/falyx/falyx.py index 1459223..7f4005a 100644 --- a/falyx/falyx.py +++ b/falyx/falyx.py @@ -25,7 +25,7 @@ import asyncio import logging import shlex import sys -from argparse import Namespace +from argparse import ArgumentParser, Namespace, _SubParsersAction from difflib import get_close_matches from enum import Enum from functools import cached_property @@ -1029,6 +1029,8 @@ class Falyx: async def run( self, falyx_parsers: FalyxParsers | None = None, + root_parser: ArgumentParser | None = None, + subparsers: _SubParsersAction | None = None, callback: Callable[..., Any] | None = None, ) -> None: """Run Falyx CLI with structured subcommands.""" @@ -1046,6 +1048,8 @@ class Falyx: self.description, self.epilog, commands=self.commands, + root_parser=root_parser, + subparsers=subparsers, ) self.cli_args = falyx_parsers.parse_args() self.options.from_namespace(self.cli_args, "cli_args") diff --git a/falyx/parsers/__init__.py b/falyx/parsers/__init__.py index 683e27b..1ea2c34 100644 --- a/falyx/parsers/__init__.py +++ b/falyx/parsers/__init__.py @@ -6,12 +6,14 @@ Licensed under the MIT License. See LICENSE file for details. """ from .argparse import Argument, ArgumentAction, CommandArgumentParser -from .parsers import FalyxParsers, get_arg_parsers +from .parsers import FalyxParsers, get_arg_parsers, get_root_parser, get_subparsers __all__ = [ "Argument", "ArgumentAction", "CommandArgumentParser", "get_arg_parsers", + "get_root_parser", + "get_subparsers", "FalyxParsers", ] diff --git a/falyx/parsers/parsers.py b/falyx/parsers/parsers.py index 3391523..60c2559 100644 --- a/falyx/parsers/parsers.py +++ b/falyx/parsers/parsers.py @@ -40,7 +40,7 @@ class FalyxParsers: return self.as_dict().get(name) -def get_arg_parsers( +def get_root_parser( prog: str | None = "falyx", usage: str | None = None, description: str | None = "Falyx CLI - Run structured async command workflows.", @@ -55,9 +55,7 @@ def get_arg_parsers( add_help: bool = True, allow_abbrev: bool = True, exit_on_error: bool = True, - commands: dict[str, Command] | None = None, -) -> FalyxParsers: - """Returns the argument parser for the CLI.""" +) -> ArgumentParser: parser = ArgumentParser( prog=prog, usage=usage, @@ -86,7 +84,70 @@ def get_arg_parsers( help="Enable default lifecycle debug logging", ) parser.add_argument("--version", action="store_true", help="Show Falyx version") - subparsers = parser.add_subparsers(dest="command") + return parser + + +def get_subparsers( + parser: ArgumentParser, + title: str = "Falyx Commands", + description: str | None = "Available commands for the Falyx CLI.", +) -> _SubParsersAction: + """Create and return a subparsers action for the given parser.""" + if not isinstance(parser, ArgumentParser): + raise TypeError("parser must be an instance of ArgumentParser") + subparsers = parser.add_subparsers( + title=title, + description=description, + metavar="COMMAND", + dest="command", + ) + return subparsers + + +def get_arg_parsers( + prog: str | None = "falyx", + usage: str | None = None, + description: str | None = "Falyx CLI - Run structured async command workflows.", + epilog: ( + str | None + ) = "Tip: Use 'falyx run ?[COMMAND]' to preview any command from the CLI.", + parents: Sequence[ArgumentParser] | None = None, + prefix_chars: str = "-", + fromfile_prefix_chars: str | None = None, + argument_default: Any = None, + conflict_handler: str = "error", + add_help: bool = True, + allow_abbrev: bool = True, + exit_on_error: bool = True, + commands: dict[str, Command] | None = None, + root_parser: ArgumentParser | None = None, + subparsers: _SubParsersAction | None = None, +) -> FalyxParsers: + """Returns the argument parser for the CLI.""" + if root_parser is None: + parser = get_root_parser( + prog=prog, + usage=usage, + description=description, + epilog=epilog, + parents=parents, + prefix_chars=prefix_chars, + fromfile_prefix_chars=fromfile_prefix_chars, + argument_default=argument_default, + conflict_handler=conflict_handler, + add_help=add_help, + allow_abbrev=allow_abbrev, + exit_on_error=exit_on_error, + ) + else: + if not isinstance(root_parser, ArgumentParser): + raise TypeError("root_parser must be an instance of ArgumentParser") + parser = root_parser + + if subparsers is None: + subparsers = get_subparsers(parser) + if not isinstance(subparsers, _SubParsersAction): + raise TypeError("subparsers must be an instance of _SubParsersAction") run_description = ["Run a command by its key or alias.\n"] run_description.append("commands:") @@ -105,7 +166,9 @@ def get_arg_parsers( epilog=run_epilog, formatter_class=RawDescriptionHelpFormatter, ) - run_parser.add_argument("name", help="Run a command by its key or alias") + run_parser.add_argument( + "name", help="Run a command by its key or alias", metavar="COMMAND" + ) run_parser.add_argument( "--summary", action="store_true", @@ -143,6 +206,7 @@ def get_arg_parsers( "command_args", nargs=REMAINDER, help="Arguments to pass to the command (if applicable)", + metavar="ARGS", ) run_all_parser = subparsers.add_parser( diff --git a/falyx/version.py b/falyx/version.py index 6728003..fe54c5e 100644 --- a/falyx/version.py +++ b/falyx/version.py @@ -1 +1 @@ -__version__ = "0.1.42" +__version__ = "0.1.43" diff --git a/pyproject.toml b/pyproject.toml index 2127479..572d937 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "falyx" -version = "0.1.42" +version = "0.1.43" description = "Reliable and introspectable async CLI action framework." authors = ["Roland Thomas Jr "] license = "MIT"