Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions startle/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from enum import Enum
from typing import Any, Literal

from rich.cells import cell_len
from rich.text import Text

from .arg import Arg, Name
Expand Down Expand Up @@ -155,3 +156,35 @@ def var_args_usage_line(arg: Arg) -> Text:

def var_kwargs_usage_line(arg: Arg) -> Text:
return Text.assemble("[", _repeated(_opt_usage(arg, "usage line")), "]")


def wrap_usage(name: str, components: list[Text], width: int) -> Text:
"""
Custom word-wrapping for usage lines that avoids splitting individual components
(e.g. we want "--foo bar" or "[--foo bar]" stay together).
Continuation lines are indented to align after the program name.

This was needed because (afaik) there is no way to declare non-breaking spaces in rich,
and even in that case I would prefer to use actual space char.
"""

indent = len(name) + 1
lines: list[Text] = []
current = Text(f"{name} ")
current_len = indent

for comp in components:
comp_len = cell_len(comp.plain)
if current_len > indent and current_len + comp_len + 1 > width:
lines.append(current)
current = Text(" " * indent) + comp
current_len = indent + comp_len
else:
if current_len > indent:
current.append(" ")
current_len += 1
current = Text.assemble(current, comp)
current_len += comp_len

lines.append(current)
return Text("\n").join(lines)
13 changes: 10 additions & 3 deletions startle/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any, Literal

from ._help import Sty, help, usage, var_args_usage_line, var_kwargs_usage_line
from ._help import (
Sty,
help,
usage,
var_args_usage_line,
var_kwargs_usage_line,
wrap_usage,
)
from .arg import Arg, Name
from .error import (
DuplicateOptionError,
Expand Down Expand Up @@ -539,7 +546,7 @@ def print_help(

# (2) then print usage line
console.print(Text("Usage:", style=Sty.title))
usage_components = [Text(f" {name}")]
usage_components: list[Text] = []
pos_only_str = Text(" ").join([
usage(arg, "usage line") for arg in positional_only
])
Expand All @@ -554,7 +561,7 @@ def print_help(
if self._var_kwargs:
usage_components.append(var_kwargs_usage_line(self._var_kwargs))

console.print(Text(" ").join(usage_components))
console.print(wrap_usage(f" {name}", usage_components, console.width or 80))

if usage_only:
console.print()
Expand Down
3 changes: 2 additions & 1 deletion tests/test_help/test_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,8 @@ class FusionConfig:
Fuse two monsters with polymerization.

[{TS}]Usage:[/]
fuse.py [{NS} {OS}]--left-path[/] [{VS}]<text>[/] [{NS} {OS}]--right-path[/] [{VS}]<text>[/] [{NS} {OS}]--output-path[/] [{VS}]<text>[/] [[{NS} {OS}]--components[/] [{VS}]<text>[/] [dim][[/][{VS} dim]<text>[/][dim] ...][/]] [[{NS} {OS}]--alpha[/] [{VS}]<float>[/]]
fuse.py [{NS} {OS}]--left-path[/] [{VS}]<text>[/] [{NS} {OS}]--right-path[/] [{VS}]<text>[/] [{NS} {OS}]--output-path[/] [{VS}]<text>[/] [[{NS} {OS}]--components[/] [{VS}]<text>[/] [dim][[/][{VS} dim]<text>[/][dim] ...][/]]
[[{NS} {OS}]--alpha[/] [{VS}]<float>[/]]

[{TS}]where[/]
[dim](pos. or opt.)[/] [{NS} {OS}]-l[/][{OS} dim]|[/][{NS} {OS}]--left-path[/] [{VS}]<text>[/] [i]Path to the first monster.[/] [yellow](required)[/]
Expand Down
6 changes: 4 additions & 2 deletions tests/test_recursive/test_recursive_parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -671,7 +671,8 @@ def test_recursive_dataclass_help(
Fuse two monsters with polymerization.

[{TS}]Usage:[/]
fuse.py [{NS} {OS}]--left-path[/] [{VS}]<text>[/] [{NS} {OS}]--right-path[/] [{VS}]<text>[/] [{NS} {OS}]--output-path[/] [{VS}]<text>[/] [[{NS} {OS}]--components[/] [{VS}]<text>[/] [dim][[/][{VS} dim]<text>[/][dim] ...][/]] [[{NS} {OS}]--alpha[/] [{VS}]<float>[/]]
fuse.py [{NS} {OS}]--left-path[/] [{VS}]<text>[/] [{NS} {OS}]--right-path[/] [{VS}]<text>[/] [{NS} {OS}]--output-path[/] [{VS}]<text>[/] [[{NS} {OS}]--components[/] [{VS}]<text>[/] [dim][[/][{VS} dim]<text>[/][dim] ...][/]]
[[{NS} {OS}]--alpha[/] [{VS}]<float>[/]]

[{TS}]where[/]
[dim](option)[/] [{NS} {OS}]-l[/][{OS} dim]|[/][{NS} {OS}]--left-path[/] [{VS}]<text>[/] [i]Path to the first monster.[/] [yellow](required)[/]
Expand Down Expand Up @@ -760,7 +761,8 @@ def test_recursive_dataclass_help_2() -> None:
Fuse two monsters with polymerization.

[{TS}]Usage:[/]
fuse.py [{NS} {OS}]--left-path[/] [{VS}]<text>[/] [{NS} {OS}]--right-path[/] [{VS}]<text>[/] [{NS} {OS}]--output-path[/] [{VS}]<text>[/] [[{NS} {OS}]--components[/] [{VS}]<text>[/] [dim][[/][{VS} dim]<text>[/][dim] ...][/]] [[{NS} {OS}]--alpha[/] [{VS}]<float>[/]]
fuse.py [{NS} {OS}]--left-path[/] [{VS}]<text>[/] [{NS} {OS}]--right-path[/] [{VS}]<text>[/] [{NS} {OS}]--output-path[/] [{VS}]<text>[/] [[{NS} {OS}]--components[/] [{VS}]<text>[/] [dim][[/][{VS} dim]<text>[/][dim] ...][/]]
[[{NS} {OS}]--alpha[/] [{VS}]<float>[/]]

[{TS}]where[/]
[dim](option)[/] [{NS} {OS}]--left-path[/] [{VS}]<text>[/] [i]Path to the first monster.[/] [yellow](required)[/]
Expand Down
6 changes: 4 additions & 2 deletions tests/test_recursive/test_recursive_start.py
Original file line number Diff line number Diff line change
Expand Up @@ -664,7 +664,8 @@ def test_recursive_dataclass_help(fuse: Callable[..., Any]) -> None:
Fuse two monsters with polymerization.

[{TS}]Usage:[/]
fuse.py [{NS} {OS}]--left-path[/] [{VS}]<text>[/] [{NS} {OS}]--right-path[/] [{VS}]<text>[/] [{NS} {OS}]--output-path[/] [{VS}]<text>[/] [[{NS} {OS}]--components[/] [{VS}]<text>[/] [dim][[/][{VS} dim]<text>[/][dim] ...][/]] [[{NS} {OS}]--alpha[/] [{VS}]<float>[/]]
fuse.py [{NS} {OS}]--left-path[/] [{VS}]<text>[/] [{NS} {OS}]--right-path[/] [{VS}]<text>[/] [{NS} {OS}]--output-path[/] [{VS}]<text>[/] [[{NS} {OS}]--components[/] [{VS}]<text>[/] [dim][[/][{VS} dim]<text>[/][dim] ...][/]]
[[{NS} {OS}]--alpha[/] [{VS}]<float>[/]]

[{TS}]where[/]
[dim](option)[/] [{NS} {OS}]-l[/][{OS} dim]|[/][{NS} {OS}]--left-path[/] [{VS}]<text>[/] [i]Path to the first monster.[/] [yellow](required)[/]
Expand Down Expand Up @@ -741,7 +742,8 @@ def test_recursive_dataclass_help_2() -> None:
Fuse two monsters with polymerization.

[{TS}]Usage:[/]
fuse.py [{NS} {OS}]--left-path[/] [{VS}]<text>[/] [{NS} {OS}]--right-path[/] [{VS}]<text>[/] [{NS} {OS}]--output-path[/] [{VS}]<text>[/] [[{NS} {OS}]--components[/] [{VS}]<text>[/] [dim][[/][{VS} dim]<text>[/][dim] ...][/]] [[{NS} {OS}]--alpha[/] [{VS}]<float>[/]]
fuse.py [{NS} {OS}]--left-path[/] [{VS}]<text>[/] [{NS} {OS}]--right-path[/] [{VS}]<text>[/] [{NS} {OS}]--output-path[/] [{VS}]<text>[/] [[{NS} {OS}]--components[/] [{VS}]<text>[/] [dim][[/][{VS} dim]<text>[/][dim] ...][/]]
[[{NS} {OS}]--alpha[/] [{VS}]<float>[/]]

[{TS}]where[/]
[dim](option)[/] [{NS} {OS}]--left-path[/] [{VS}]<text>[/] [i]Path to the first monster.[/] [yellow](required)[/]
Expand Down
Loading