Skip to content
Open
2 changes: 1 addition & 1 deletion docs_src/arguments/help/tutorial006.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import typer


def main(name: str = typer.Argument("World", metavar="✨username✨")):
def main(name: str = typer.Argument("World", metavar="✨user✨")):
print(f"Hello {name}")


Expand Down
2 changes: 1 addition & 1 deletion docs_src/arguments/help/tutorial006_an.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from typing_extensions import Annotated


def main(name: Annotated[str, typer.Argument(metavar="✨username✨")] = "World"):
def main(name: Annotated[str, typer.Argument(metavar="✨user✨")] = "World"):
print(f"Hello {name}")


Expand Down
44 changes: 43 additions & 1 deletion tests/test_rich_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def main(

result = runner.invoke(app, ["--help"])
assert "Usage" in result.stdout
assert "name" in result.stdout
assert "NAME" in result.stdout
assert "option-1" in result.stdout
assert "option-2" in result.stdout
assert result.stdout.count("[default: None]") == 0
Expand All @@ -99,3 +99,45 @@ def main(bar: str):
result = runner.invoke(app, ["--help"])
assert "Usage" in result.stdout
assert "BAR" in result.stdout


def test_rich_help_metavar():
app = typer.Typer(rich_markup_mode="rich")

@app.command()
def main(
*,
arg1: int,
arg2: int = 42,
arg3: int = typer.Argument(...),
arg4: int = typer.Argument(42),
arg5: int = typer.Option(...),
arg6: int = typer.Option(42),
arg7: int = typer.Argument(42, metavar="meta7"),
arg8: int = typer.Argument(metavar="ARG8"),
arg9: int = typer.Argument(metavar="arg9"),
):
pass # pragma: no cover

result = runner.invoke(app, ["--help"])
# assert "Usage: main [OPTIONS] ARG1 ARG3 [ARG4] [meta7] ARG8 arg9" in result.stdout
assert "Usage: main [OPTIONS] ARG1 ARG3 [ARG4] meta7 ARG8 arg9" in result.stdout
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The commented line on L123 and L133 would be the correct assert once #1409 is merged. Suggest to review #1409 first before this one.


out_nospace = result.stdout.replace(" ", "")

# arguments
assert "ARG1INTEGER" in out_nospace
assert "ARG3INTEGER" in out_nospace
assert "[ARG4]INTEGER" in out_nospace
assert "meta7INTEGER" in out_nospace
# assert "[meta7]INTEGER" in out_nospace
assert "ARG8INTEGER" in out_nospace
assert "arg9INTEGER" in out_nospace

assert "arg7" not in result.stdout.lower()
assert "ARG9" not in result.stdout

# options
assert "arg2INTEGER" in out_nospace
assert "arg5INTEGER" in out_nospace
assert "arg6INTEGER" in out_nospace
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,17 @@

runner = CliRunner()

app = typer.Typer()
app = typer.Typer(rich_markup_mode=None)
app.command()(mod.main)


def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
assert "[OPTIONS] ✨username✨" in result.output
assert "[OPTIONS] ✨user✨" in result.output
assert "Arguments" in result.output
assert "✨username✨" in result.output
assert "✨user✨" in result.output
assert "name" not in result.output
assert "[default: World]" in result.output


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,17 @@

runner = CliRunner()

app = typer.Typer()
app = typer.Typer(rich_markup_mode=None)
app.command()(mod.main)


def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
assert "[OPTIONS] ✨username✨" in result.output
assert "[OPTIONS] ✨user✨" in result.output
assert "Arguments" in result.output
assert "✨username✨" in result.output
assert "✨user✨" in result.output
assert "name" not in result.output
assert "[default: World]" in result.output


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import subprocess
import sys

import typer
from typer.testing import CliRunner

from docs_src.arguments.help import tutorial006_an as mod

runner = CliRunner()

app = typer.Typer(rich_markup_mode="rich")
app.command()(mod.main)


def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
assert "[OPTIONS] ✨user✨" in result.output
assert "Arguments" in result.output
assert "✨user✨" in result.output
assert "name" not in result.output
assert "[default: World]" in result.output


def test_call_arg():
result = runner.invoke(app, ["Camila"])
assert result.exit_code == 0
assert "Hello Camila" in result.output


def test_script():
result = subprocess.run(
[sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
capture_output=True,
encoding="utf-8",
)
assert "Usage" in result.stdout
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import subprocess
import sys

import typer
from typer.testing import CliRunner

from docs_src.arguments.help import tutorial006 as mod

runner = CliRunner()

app = typer.Typer(rich_markup_mode="rich")
app.command()(mod.main)


def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
assert "[OPTIONS] ✨user✨" in result.output
assert "Arguments" in result.output
assert "✨user✨" in result.output
assert "name" not in result.output
assert "[default: World]" in result.output


def test_call_arg():
result = runner.invoke(app, ["Camila"])
assert result.exit_code == 0
assert "Hello Camila" in result.output


def test_script():
result = subprocess.run(
[sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
capture_output=True,
encoding="utf-8",
)
assert "Usage" in result.stdout
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ def test_main_help():
def test_create_help():
result = runner.invoke(app, ["create", "--help"])
assert result.exit_code == 0
assert "username" in result.output
assert "USERNAME" in result.output
assert "The username to create" in result.output
assert "Secondary Arguments" in result.output
assert "lastname" in result.output
assert "LASTNAME" in result.output
assert "The last name of the new user" in result.output
assert "--force" in result.output
assert "--no-force" in result.output
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ def test_main_help():
def test_create_help():
result = runner.invoke(app, ["create", "--help"])
assert result.exit_code == 0
assert "username" in result.output
assert "USERNAME" in result.output
assert "The username to create" in result.output
assert "Secondary Arguments" in result.output
assert "lastname" in result.output
assert "LASTNAME" in result.output
assert "The last name of the new user" in result.output
assert "--force" in result.output
assert "--no-force" in result.output
Expand Down
73 changes: 41 additions & 32 deletions typer/rich_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@
STYLE_SWITCH = "bold green"
STYLE_NEGATIVE_OPTION = "bold magenta"
STYLE_NEGATIVE_SWITCH = "bold red"
STYLE_METAVAR = "bold yellow"
STYLE_METAVAR_SEPARATOR = "dim"
STYLE_TYPES = "bold yellow"
STYLE_TYPES_SEPARATOR = "dim"
STYLE_USAGE = "yellow"
STYLE_USAGE_COMMAND = "bold"
STYLE_DEPRECATED = "red"
Expand Down Expand Up @@ -113,7 +113,7 @@ class OptionHighlighter(RegexHighlighter):
highlights = [
r"(^|\W)(?P<switch>\-\w+)(?![a-zA-Z0-9])",
r"(^|\W)(?P<option>\-\-[\w\-]+)(?![a-zA-Z0-9])",
r"(?P<metavar>\<[^\>]+\>)",
r"(?P<types>\<[^\>]+\>)",
r"(?P<usage>Usage: )",
]

Expand All @@ -137,8 +137,8 @@ def _get_rich_console(stderr: bool = False) -> Console:
"switch": STYLE_SWITCH,
"negative_option": STYLE_NEGATIVE_OPTION,
"negative_switch": STYLE_NEGATIVE_SWITCH,
"metavar": STYLE_METAVAR,
"metavar_sep": STYLE_METAVAR_SEPARATOR,
"types": STYLE_TYPES,
"types_sep": STYLE_TYPES_SEPARATOR,
"usage": STYLE_USAGE,
},
),
Expand Down Expand Up @@ -361,38 +361,47 @@ def _print_options_panel(
opt_short_strs = []
secondary_opt_long_strs = []
secondary_opt_short_strs = []

# check whether argument has a metavar name or type set
metavar_name = None
metavar_type = None
# TODO: when deprecating Click < 8.2, make ctx required
signature = inspect.signature(param.make_metavar)
if "ctx" in signature.parameters:
metavar_str = param.make_metavar(ctx=ctx)
else:
# Click < 8.2
metavar_str = param.make_metavar() # type: ignore[call-arg]
if isinstance(param, click.Argument):
metavar_name = metavar_str
if isinstance(param, click.Option):
metavar_type = metavar_str
Comment on lines +377 to +378
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this part is crucial to ensure the current Enum tests won't fail. This is the part that will replace Choice with something like [simple|conv|lstm]


for opt_str in param.opts:
if "--" in opt_str:
opt_long_strs.append(opt_str)
elif metavar_name:
opt_short_strs.append(metavar_name)
else:
opt_short_strs.append(opt_str)
for opt_str in param.secondary_opts:
if "--" in opt_str:
secondary_opt_long_strs.append(opt_str)
elif metavar_name: # pragma: no cover
secondary_opt_short_strs.append(metavar_name)
Comment on lines +390 to +391
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not too sure yet about these secondary_opts, probably requires another test (instead of having pragma cover).

else:
secondary_opt_short_strs.append(opt_str)

# Column for a metavar, if we have one
metavar = Text(style=STYLE_METAVAR, overflow="fold")
# TODO: when deprecating Click < 8.2, make ctx required
signature = inspect.signature(param.make_metavar)
if "ctx" in signature.parameters:
metavar_str = param.make_metavar(ctx=ctx)
else:
# Click < 8.2
metavar_str = param.make_metavar() # type: ignore[call-arg]
# Column for recording the type
types = Text(style=STYLE_TYPES, overflow="fold")

# Do it ourselves if this is a positional argument
if (
isinstance(param, click.Argument)
and param.name
and metavar_str == param.name.upper()
):
metavar_str = param.type.name.upper()

# Skip booleans and choices (handled above)
if metavar_str != "BOOLEAN":
metavar.append(metavar_str)
# Fetch type
if metavar_type and metavar_type != "BOOLEAN":
types.append(metavar_type)
else:
type_str = param.type.name.upper()
if type_str != "BOOLEAN":
types.append(type_str)

# Range - from
# https://github.com/pallets/click/blob/c63c70dabd3f86ca68678b4f00951f78f52d0270/src/click/core.py#L2698-L2706 # noqa: E501
Expand All @@ -404,22 +413,22 @@ def _print_options_panel(
):
range_str = param.type._describe_range()
if range_str:
metavar.append(RANGE_STRING.format(range_str))
types.append(RANGE_STRING.format(range_str))

# Required asterisk
required: Union[str, Text] = ""
if param.required:
required = Text(REQUIRED_SHORT_STRING, style=STYLE_REQUIRED_SHORT)

# Highlighter to make [ | ] and <> dim
class MetavarHighlighter(RegexHighlighter):
class TypesHighlighter(RegexHighlighter):
highlights = [
r"^(?P<metavar_sep>(\[|<))",
r"(?P<metavar_sep>\|)",
r"(?P<metavar_sep>(\]|>)$)",
r"^(?P<types_sep>(\[|<))",
r"(?P<types_sep>\|)",
r"(?P<types_sep>(\]|>)$)",
]

metavar_highlighter = MetavarHighlighter()
types_highlighter = TypesHighlighter()

required_rows.append(required)
options_rows.append(
Expand All @@ -428,7 +437,7 @@ class MetavarHighlighter(RegexHighlighter):
highlighter(",".join(opt_short_strs)),
negative_highlighter(",".join(secondary_opt_long_strs)),
negative_highlighter(",".join(secondary_opt_short_strs)),
metavar_highlighter(metavar),
types_highlighter(types),
_get_parameter_help(
param=param,
ctx=ctx,
Expand Down
Loading