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 (
|
return (
|
||||||
f"[{self.style}]{program}[/]{command_keys}",
|
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:
|
Yields:
|
||||||
Completion: Matching keys or aliases from all registered commands.
|
Completion: Matching keys or aliases from all registered commands.
|
||||||
"""
|
"""
|
||||||
prefix = prefix.upper()
|
|
||||||
keys = [self.falyx.exit_command.key]
|
keys = [self.falyx.exit_command.key]
|
||||||
keys.extend(self.falyx.exit_command.aliases)
|
keys.extend(self.falyx.exit_command.aliases)
|
||||||
if self.falyx.history_command:
|
if self.falyx.history_command:
|
||||||
@ -134,7 +133,9 @@ class FalyxCompleter(Completer):
|
|||||||
keys.extend(cmd.aliases)
|
keys.extend(cmd.aliases)
|
||||||
for key in keys:
|
for key in keys:
|
||||||
if key.upper().startswith(prefix):
|
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:
|
def _ensure_quote(self, text: str) -> str:
|
||||||
"""
|
"""
|
||||||
|
@ -291,6 +291,7 @@ class Falyx:
|
|||||||
ignore_in_history=True,
|
ignore_in_history=True,
|
||||||
options_manager=self.options,
|
options_manager=self.options,
|
||||||
program=self.program,
|
program=self.program,
|
||||||
|
help_text="Exit the program.",
|
||||||
)
|
)
|
||||||
|
|
||||||
def _get_history_command(self) -> Command:
|
def _get_history_command(self) -> Command:
|
||||||
@ -301,6 +302,7 @@ class Falyx:
|
|||||||
command_style=OneColors.DARK_YELLOW,
|
command_style=OneColors.DARK_YELLOW,
|
||||||
aliases=["HISTORY"],
|
aliases=["HISTORY"],
|
||||||
program=self.program,
|
program=self.program,
|
||||||
|
options_manager=self.options,
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-n",
|
"-n",
|
||||||
@ -387,10 +389,16 @@ class Falyx:
|
|||||||
async def _render_help(
|
async def _render_help(
|
||||||
self, tag: str = "", key: str | None = None, tldr: bool = False
|
self, tag: str = "", key: str | None = None, tldr: bool = False
|
||||||
) -> None:
|
) -> 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:
|
if key:
|
||||||
_, command, args, kwargs = await self.get_command(key)
|
_, command, args, kwargs = await self.get_command(key)
|
||||||
if command and tldr and command.arg_parser:
|
if command and tldr and command.arg_parser:
|
||||||
command.arg_parser.render_tldr()
|
command.arg_parser.render_tldr()
|
||||||
|
self.console.print(f"[bold]tip:[/bold] {self.get_tip()}")
|
||||||
return None
|
return None
|
||||||
elif command and tldr and not command.arg_parser:
|
elif command and tldr and not command.arg_parser:
|
||||||
self.console.print(
|
self.console.print(
|
||||||
@ -398,6 +406,7 @@ class Falyx:
|
|||||||
)
|
)
|
||||||
elif command and command.arg_parser:
|
elif command and command.arg_parser:
|
||||||
command.arg_parser.render_help()
|
command.arg_parser.render_help()
|
||||||
|
self.console.print(f"[bold]tip:[/bold] {self.get_tip()}")
|
||||||
return None
|
return None
|
||||||
elif command and not command.arg_parser:
|
elif command and not command.arg_parser:
|
||||||
self.console.print(
|
self.console.print(
|
||||||
@ -415,7 +424,7 @@ class Falyx:
|
|||||||
]
|
]
|
||||||
if not commands:
|
if not commands:
|
||||||
self.console.print(f"'{tag}'... Nothing to show here")
|
self.console.print(f"'{tag}'... Nothing to show here")
|
||||||
return
|
return None
|
||||||
for command in commands:
|
for command in commands:
|
||||||
usage, description, _ = command.help_signature
|
usage, description, _ = command.help_signature
|
||||||
self.console.print(
|
self.console.print(
|
||||||
@ -424,7 +433,8 @@ class Falyx:
|
|||||||
(0, 2),
|
(0, 2),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return
|
self.console.print(f"[bold]tip:[/bold] {self.get_tip()}")
|
||||||
|
return None
|
||||||
|
|
||||||
self.console.print("[bold]help:[/bold]")
|
self.console.print("[bold]help:[/bold]")
|
||||||
for command in self.commands.values():
|
for command in self.commands.values():
|
||||||
@ -473,8 +483,9 @@ class Falyx:
|
|||||||
command_key="H",
|
command_key="H",
|
||||||
command_description="Help",
|
command_description="Help",
|
||||||
command_style=OneColors.LIGHT_YELLOW,
|
command_style=OneColors.LIGHT_YELLOW,
|
||||||
aliases=["?", "HELP"],
|
aliases=["HELP", "?"],
|
||||||
program=self.program,
|
program=self.program,
|
||||||
|
options_manager=self.options,
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-t",
|
"-t",
|
||||||
@ -483,11 +494,27 @@ class Falyx:
|
|||||||
default="",
|
default="",
|
||||||
help="Optional tag to filter commands by.",
|
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(
|
return Command(
|
||||||
key="H",
|
key="H",
|
||||||
aliases=["?", "HELP"],
|
aliases=["HELP", "?"],
|
||||||
description="Help",
|
description="Help",
|
||||||
help_text="Show this help menu",
|
help_text="Show this help menu.",
|
||||||
action=Action("Help", self._render_help),
|
action=Action("Help", self._render_help),
|
||||||
style=OneColors.LIGHT_YELLOW,
|
style=OneColors.LIGHT_YELLOW,
|
||||||
arg_parser=parser,
|
arg_parser=parser,
|
||||||
@ -652,6 +679,7 @@ class Falyx:
|
|||||||
style: str = OneColors.DARK_RED,
|
style: str = OneColors.DARK_RED,
|
||||||
confirm: bool = False,
|
confirm: bool = False,
|
||||||
confirm_message: str = "Are you sure?",
|
confirm_message: str = "Are you sure?",
|
||||||
|
help_text: str = "Exit the program.",
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Updates the back command of the menu."""
|
"""Updates the back command of the menu."""
|
||||||
self._validate_command_key(key)
|
self._validate_command_key(key)
|
||||||
@ -669,6 +697,7 @@ class Falyx:
|
|||||||
ignore_in_history=True,
|
ignore_in_history=True,
|
||||||
options_manager=self.options,
|
options_manager=self.options,
|
||||||
program=self.program,
|
program=self.program,
|
||||||
|
help_text=help_text,
|
||||||
)
|
)
|
||||||
|
|
||||||
def add_submenu(
|
def add_submenu(
|
||||||
@ -682,7 +711,12 @@ class Falyx:
|
|||||||
key, description, submenu.menu, style=style, simple_help_signature=True
|
key, description, submenu.menu, style=style, simple_help_signature=True
|
||||||
)
|
)
|
||||||
if submenu.exit_command.key == "X":
|
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:
|
def add_commands(self, commands: list[Command] | list[dict]) -> None:
|
||||||
"""Adds a list of Command instances or config dicts."""
|
"""Adds a list of Command instances or config dicts."""
|
||||||
|
@ -156,9 +156,9 @@ class CommandArgumentParser:
|
|||||||
|
|
||||||
if "tldr" not in self._dest_set:
|
if "tldr" not in self._dest_set:
|
||||||
tldr = Argument(
|
tldr = Argument(
|
||||||
("--tldr",),
|
("--tldr", "-T"),
|
||||||
action=ArgumentAction.TLDR,
|
action=ArgumentAction.TLDR,
|
||||||
help="Show quick usage examples and exit.",
|
help="Show quick usage examples.",
|
||||||
dest="tldr",
|
dest="tldr",
|
||||||
)
|
)
|
||||||
self._register_argument(tldr)
|
self._register_argument(tldr)
|
||||||
@ -1408,10 +1408,13 @@ class CommandArgumentParser:
|
|||||||
FalyxMode.RUN_ALL,
|
FalyxMode.RUN_ALL,
|
||||||
FalyxMode.HELP,
|
FalyxMode.HELP,
|
||||||
}
|
}
|
||||||
|
is_help_command = self.aliases[0] == "HELP" and self.command_key == "H"
|
||||||
|
|
||||||
program = self.program or "falyx"
|
program = self.program or "falyx"
|
||||||
command = self.aliases[0] if self.aliases else self.command_key
|
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 = (
|
command = (
|
||||||
f"[{self.command_style}]{program} run {command}[/{self.command_style}]"
|
f"[{self.command_style}]{program} run {command}[/{self.command_style}]"
|
||||||
)
|
)
|
||||||
|
@ -59,7 +59,7 @@ def get_root_parser(
|
|||||||
prog: str | None = "falyx",
|
prog: str | None = "falyx",
|
||||||
usage: str | None = None,
|
usage: str | None = None,
|
||||||
description: str | None = "Falyx CLI - Run structured async command workflows.",
|
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,
|
parents: Sequence[ArgumentParser] | None = None,
|
||||||
prefix_chars: str = "-",
|
prefix_chars: str = "-",
|
||||||
fromfile_prefix_chars: str | None = None,
|
fromfile_prefix_chars: str | None = None,
|
||||||
@ -178,7 +178,7 @@ def get_arg_parsers(
|
|||||||
description: str | None = "Falyx CLI - Run structured async command workflows.",
|
description: str | None = "Falyx CLI - Run structured async command workflows.",
|
||||||
epilog: (
|
epilog: (
|
||||||
str | None
|
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,
|
parents: Sequence[ArgumentParser] | None = None,
|
||||||
prefix_chars: str = "-",
|
prefix_chars: str = "-",
|
||||||
fromfile_prefix_chars: str | None = None,
|
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.
|
- Use `falyx run ?[COMMAND]` from the CLI to preview a command.
|
||||||
"""
|
"""
|
||||||
if epilog is None:
|
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:
|
if root_parser is None:
|
||||||
parser = get_root_parser(
|
parser = get_root_parser(
|
||||||
prog=prog,
|
prog=prog,
|
||||||
@ -281,7 +281,7 @@ def get_arg_parsers(
|
|||||||
command_description = command.help_text or command.description
|
command_description = command.help_text or command.description
|
||||||
run_description.append(f"{' '*24}{command_description}")
|
run_description.append(f"{' '*24}{command_description}")
|
||||||
run_epilog = (
|
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_parser = subparsers.add_parser(
|
||||||
"run",
|
"run",
|
||||||
@ -377,10 +377,6 @@ def get_arg_parsers(
|
|||||||
|
|
||||||
help_parser = subparsers.add_parser("help", help="List all available commands")
|
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(
|
help_parser.add_argument(
|
||||||
"-k",
|
"-k",
|
||||||
"--key",
|
"--key",
|
||||||
@ -395,6 +391,10 @@ def get_arg_parsers(
|
|||||||
help="Show a simplified TLDR examples of a command if available",
|
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")
|
version_parser = subparsers.add_parser("version", help=f"Show {prog} version")
|
||||||
|
|
||||||
return FalyxParsers(
|
return FalyxParsers(
|
||||||
|
@ -1 +1 @@
|
|||||||
__version__ = "0.1.81"
|
__version__ = "0.1.82"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "falyx"
|
name = "falyx"
|
||||||
version = "0.1.81"
|
version = "0.1.82"
|
||||||
description = "Reliable and introspectable async CLI action framework."
|
description = "Reliable and introspectable async CLI action framework."
|
||||||
authors = ["Roland Thomas Jr <roland@rtj.dev>"]
|
authors = ["Roland Thomas Jr <roland@rtj.dev>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
@ -6,6 +6,8 @@ from falyx import Falyx
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_help_command(capsys):
|
async def test_help_command(capsys):
|
||||||
flx = Falyx()
|
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")
|
await flx.run_key("H")
|
||||||
|
|
||||||
captured = capsys.readouterr()
|
captured = capsys.readouterr()
|
||||||
|
Reference in New Issue
Block a user