feat: enhance help command UX, completions, and CLI tips
- Expanded help command to accept: - `-k/--key` for detailed help on a specific command - `-t/--tag` for tag-filtered listings - `-T/--tldr` for quick usage examples - Updated TLDR flag to support `-T` short form and refined help text. - Improved `_render_help()` to show contextual CLI tips after help or TLDR output. - Adjusted completer to yield both upper and lower case completions without mutating the prefix. - Standardized CLI tip strings in root/arg parsers to reference `help` and `preview` subcommands instead of menu `run ?` syntax. - Passed `options_manager` to history/help/exit commands for consistency. - Allowed `help_command` to display TLDR examples when invoked without a key. - Added test assertions for help command key/alias consistency. - Bumped version to 0.1.82.
This commit is contained in:
@ -366,7 +366,7 @@ class Command(BaseModel):
|
||||
)
|
||||
return (
|
||||
f"[{self.style}]{program}[/]{command_keys}",
|
||||
f"[dim]{self.description}[/dim]",
|
||||
f"[dim]{self.help_text or self.description}[/dim]",
|
||||
"",
|
||||
)
|
||||
|
||||
|
@ -120,7 +120,6 @@ class FalyxCompleter(Completer):
|
||||
Yields:
|
||||
Completion: Matching keys or aliases from all registered commands.
|
||||
"""
|
||||
prefix = prefix.upper()
|
||||
keys = [self.falyx.exit_command.key]
|
||||
keys.extend(self.falyx.exit_command.aliases)
|
||||
if self.falyx.history_command:
|
||||
@ -134,7 +133,9 @@ class FalyxCompleter(Completer):
|
||||
keys.extend(cmd.aliases)
|
||||
for key in keys:
|
||||
if key.upper().startswith(prefix):
|
||||
yield Completion(key, start_position=-len(prefix))
|
||||
yield Completion(key.upper(), start_position=-len(prefix))
|
||||
elif key.lower().startswith(prefix):
|
||||
yield Completion(key.lower(), start_position=-len(prefix))
|
||||
|
||||
def _ensure_quote(self, text: str) -> str:
|
||||
"""
|
||||
|
@ -291,6 +291,7 @@ class Falyx:
|
||||
ignore_in_history=True,
|
||||
options_manager=self.options,
|
||||
program=self.program,
|
||||
help_text="Exit the program.",
|
||||
)
|
||||
|
||||
def _get_history_command(self) -> Command:
|
||||
@ -301,6 +302,7 @@ class Falyx:
|
||||
command_style=OneColors.DARK_YELLOW,
|
||||
aliases=["HISTORY"],
|
||||
program=self.program,
|
||||
options_manager=self.options,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-n",
|
||||
@ -387,10 +389,16 @@ class Falyx:
|
||||
async def _render_help(
|
||||
self, tag: str = "", key: str | None = None, tldr: bool = False
|
||||
) -> None:
|
||||
if tldr and not key:
|
||||
if self.help_command and self.help_command.arg_parser:
|
||||
self.help_command.arg_parser.render_tldr()
|
||||
self.console.print(f"[bold]tip:[/bold] {self.get_tip()}")
|
||||
return None
|
||||
if key:
|
||||
_, command, args, kwargs = await self.get_command(key)
|
||||
if command and tldr and command.arg_parser:
|
||||
command.arg_parser.render_tldr()
|
||||
self.console.print(f"[bold]tip:[/bold] {self.get_tip()}")
|
||||
return None
|
||||
elif command and tldr and not command.arg_parser:
|
||||
self.console.print(
|
||||
@ -398,6 +406,7 @@ class Falyx:
|
||||
)
|
||||
elif command and command.arg_parser:
|
||||
command.arg_parser.render_help()
|
||||
self.console.print(f"[bold]tip:[/bold] {self.get_tip()}")
|
||||
return None
|
||||
elif command and not command.arg_parser:
|
||||
self.console.print(
|
||||
@ -415,7 +424,7 @@ class Falyx:
|
||||
]
|
||||
if not commands:
|
||||
self.console.print(f"'{tag}'... Nothing to show here")
|
||||
return
|
||||
return None
|
||||
for command in commands:
|
||||
usage, description, _ = command.help_signature
|
||||
self.console.print(
|
||||
@ -424,7 +433,8 @@ class Falyx:
|
||||
(0, 2),
|
||||
)
|
||||
)
|
||||
return
|
||||
self.console.print(f"[bold]tip:[/bold] {self.get_tip()}")
|
||||
return None
|
||||
|
||||
self.console.print("[bold]help:[/bold]")
|
||||
for command in self.commands.values():
|
||||
@ -473,8 +483,9 @@ class Falyx:
|
||||
command_key="H",
|
||||
command_description="Help",
|
||||
command_style=OneColors.LIGHT_YELLOW,
|
||||
aliases=["?", "HELP"],
|
||||
aliases=["HELP", "?"],
|
||||
program=self.program,
|
||||
options_manager=self.options,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-t",
|
||||
@ -483,11 +494,27 @@ class Falyx:
|
||||
default="",
|
||||
help="Optional tag to filter commands by.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-k",
|
||||
"--key",
|
||||
nargs="?",
|
||||
default=None,
|
||||
help="Optional command key or alias to get detailed help for.",
|
||||
)
|
||||
parser.add_tldr_examples(
|
||||
[
|
||||
("", "Show all commands."),
|
||||
("-k [COMMAND]", "Show detailed help for a specific command."),
|
||||
("-Tk [COMMAND]", "Show quick usage examples for a specific command."),
|
||||
("--tldr", "Show these quick usage examples."),
|
||||
("--tag [TAG]", "Show commands with the specified tag."),
|
||||
]
|
||||
)
|
||||
return Command(
|
||||
key="H",
|
||||
aliases=["?", "HELP"],
|
||||
aliases=["HELP", "?"],
|
||||
description="Help",
|
||||
help_text="Show this help menu",
|
||||
help_text="Show this help menu.",
|
||||
action=Action("Help", self._render_help),
|
||||
style=OneColors.LIGHT_YELLOW,
|
||||
arg_parser=parser,
|
||||
@ -652,6 +679,7 @@ class Falyx:
|
||||
style: str = OneColors.DARK_RED,
|
||||
confirm: bool = False,
|
||||
confirm_message: str = "Are you sure?",
|
||||
help_text: str = "Exit the program.",
|
||||
) -> None:
|
||||
"""Updates the back command of the menu."""
|
||||
self._validate_command_key(key)
|
||||
@ -669,6 +697,7 @@ class Falyx:
|
||||
ignore_in_history=True,
|
||||
options_manager=self.options,
|
||||
program=self.program,
|
||||
help_text=help_text,
|
||||
)
|
||||
|
||||
def add_submenu(
|
||||
@ -682,7 +711,12 @@ class Falyx:
|
||||
key, description, submenu.menu, style=style, simple_help_signature=True
|
||||
)
|
||||
if submenu.exit_command.key == "X":
|
||||
submenu.update_exit_command(key="B", description="Back", aliases=["BACK"])
|
||||
submenu.update_exit_command(
|
||||
key="B",
|
||||
description="Back",
|
||||
aliases=["BACK"],
|
||||
help_text="Go back to the previous menu.",
|
||||
)
|
||||
|
||||
def add_commands(self, commands: list[Command] | list[dict]) -> None:
|
||||
"""Adds a list of Command instances or config dicts."""
|
||||
|
@ -156,9 +156,9 @@ class CommandArgumentParser:
|
||||
|
||||
if "tldr" not in self._dest_set:
|
||||
tldr = Argument(
|
||||
("--tldr",),
|
||||
("--tldr", "-T"),
|
||||
action=ArgumentAction.TLDR,
|
||||
help="Show quick usage examples and exit.",
|
||||
help="Show quick usage examples.",
|
||||
dest="tldr",
|
||||
)
|
||||
self._register_argument(tldr)
|
||||
@ -1408,10 +1408,13 @@ class CommandArgumentParser:
|
||||
FalyxMode.RUN_ALL,
|
||||
FalyxMode.HELP,
|
||||
}
|
||||
is_help_command = self.aliases[0] == "HELP" and self.command_key == "H"
|
||||
|
||||
program = self.program or "falyx"
|
||||
command = self.aliases[0] if self.aliases else self.command_key
|
||||
if is_cli_mode:
|
||||
if is_help_command and is_cli_mode:
|
||||
command = f"[{self.command_style}]{program} help[/{self.command_style}]"
|
||||
elif is_cli_mode:
|
||||
command = (
|
||||
f"[{self.command_style}]{program} run {command}[/{self.command_style}]"
|
||||
)
|
||||
|
@ -59,7 +59,7 @@ def get_root_parser(
|
||||
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 ?' to show available commands.",
|
||||
epilog: str | None = "Tip: Use 'falyx help' to show available commands.",
|
||||
parents: Sequence[ArgumentParser] | None = None,
|
||||
prefix_chars: str = "-",
|
||||
fromfile_prefix_chars: str | None = None,
|
||||
@ -178,7 +178,7 @@ def get_arg_parsers(
|
||||
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.",
|
||||
) = "Tip: Use 'falyx preview [COMMAND]' to preview any command from the CLI.",
|
||||
parents: Sequence[ArgumentParser] | None = None,
|
||||
prefix_chars: str = "-",
|
||||
fromfile_prefix_chars: str | None = None,
|
||||
@ -240,7 +240,7 @@ def get_arg_parsers(
|
||||
- Use `falyx run ?[COMMAND]` from the CLI to preview a command.
|
||||
"""
|
||||
if epilog is None:
|
||||
epilog = f"Tip: Use '{prog} run ?' to show available commands."
|
||||
epilog = f"Tip: Use '{prog} help' to show available commands."
|
||||
if root_parser is None:
|
||||
parser = get_root_parser(
|
||||
prog=prog,
|
||||
@ -281,7 +281,7 @@ def get_arg_parsers(
|
||||
command_description = command.help_text or command.description
|
||||
run_description.append(f"{' '*24}{command_description}")
|
||||
run_epilog = (
|
||||
f"Tip: Use '{prog} run ?[COMMAND]' to preview commands by their key or alias."
|
||||
f"Tip: Use '{prog} preview [COMMAND]' to preview commands by their key or alias."
|
||||
)
|
||||
run_parser = subparsers.add_parser(
|
||||
"run",
|
||||
@ -377,10 +377,6 @@ def get_arg_parsers(
|
||||
|
||||
help_parser = subparsers.add_parser("help", help="List all available commands")
|
||||
|
||||
help_parser.add_argument(
|
||||
"-t", "--tag", help="Filter commands by tag (case-insensitive)", default=None
|
||||
)
|
||||
|
||||
help_parser.add_argument(
|
||||
"-k",
|
||||
"--key",
|
||||
@ -395,6 +391,10 @@ def get_arg_parsers(
|
||||
help="Show a simplified TLDR examples of a command if available",
|
||||
)
|
||||
|
||||
help_parser.add_argument(
|
||||
"-t", "--tag", help="Filter commands by tag (case-insensitive)", default=None
|
||||
)
|
||||
|
||||
version_parser = subparsers.add_parser("version", help=f"Show {prog} version")
|
||||
|
||||
return FalyxParsers(
|
||||
|
@ -1 +1 @@
|
||||
__version__ = "0.1.81"
|
||||
__version__ = "0.1.82"
|
||||
|
@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "falyx"
|
||||
version = "0.1.81"
|
||||
version = "0.1.82"
|
||||
description = "Reliable and introspectable async CLI action framework."
|
||||
authors = ["Roland Thomas Jr <roland@rtj.dev>"]
|
||||
license = "MIT"
|
||||
|
@ -6,6 +6,8 @@ from falyx import Falyx
|
||||
@pytest.mark.asyncio
|
||||
async def test_help_command(capsys):
|
||||
flx = Falyx()
|
||||
assert flx.help_command.arg_parser.aliases[0] == "HELP"
|
||||
assert flx.help_command.arg_parser.command_key == "H"
|
||||
await flx.run_key("H")
|
||||
|
||||
captured = capsys.readouterr()
|
||||
|
Reference in New Issue
Block a user