-
-
Notifications
You must be signed in to change notification settings - Fork 680
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
🐛 Fix escaping in help text when rich
is installed but not used
#1089
base: master
Are you sure you want to change the base?
Conversation
@@ -20,6 +20,7 @@ def main(arg: str): | |||
assert "Hello World" in result.stdout | |||
|
|||
result = runner.invoke(app, ["--help"]) | |||
assert "ARG [required]" in result.stdout |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fails with master
, as pointed out in #1058 (it'll have a slash)
""" | ||
|
||
|
||
app = typer.Typer(help="Demo App", epilog="The end", rich_markup_mode=None) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The point of this new test is this: checking that rich_markup_mode=None
, with rich
installed, still produces correct results (showing [required], default values, etc and not accidentally 'removing' them because of incorrect escaping behaviour)
def _parse_html(ctx: click.Context, input_text: str) -> str: | ||
rich_markup_mode = None | ||
if hasattr(ctx, "obj") and isinstance(ctx.obj, dict): | ||
rich_markup_mode = ctx.obj.get("TYPER_RICH_MARKUP_MODE", None) | ||
if has_rich and rich_markup_mode and rich_markup_mode == "rich": # pragma: no cover | ||
return rich_utils.rich_to_html(input_text) | ||
return input_text |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤔 I think we should move the computation of rich_markup_mode
in get_docs_for_click
so we don't do it more than necessary (and we keep this function simple)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea fair enough, I had it there first 😁 Somehow that felt like cluttering the get_docs_for_click
function, and conceptually it's nicer to pass around ctx
everywhere. But I agree that it means repeating the same computation (though they should be simple, fast checks/retrievals)
def _parse_html(ctx: click.Context, input_text: str) -> str: | ||
rich_markup_mode = None | ||
if hasattr(ctx, "obj") and isinstance(ctx.obj, dict): | ||
rich_markup_mode = ctx.obj.get("TYPER_RICH_MARKUP_MODE", None) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should also consider pulling this "TYPER_RICH_MARKUP_MODE"
value out as a variable and putting it somewhere central.
Background
With PR #847, we implemented functionality to ensure that help descriptions with Rich tags (e.g.
[bold]
) would get rendered properly in Markdown (instead of just showing the "raw" brackets). This uses Rich's functionality fromconsole.export_html
.Unfortunately, a problem occurred at the time because meta information like
[required]
looks like Rich tags, but with no known formatting they would just get stripped off byconsole.export_html()
. That then meant we had to escape those tags at the point in the code where they're being added to the rest of the help string - this happens inget_help_record
for bothTyperOption
andTyperArgument
:Current issue
Now, as pointed out in #1058, there's now an issue when the escaping happens if
rich
is installed (triggering theif
clause in the code above) BUT the Typer app actually hasrich_markup_mode=None
. The help text will in this case show the backslash from the escaping, which is clearly unwanted behaviour.Fix (proposal)
To remedy, we need to know at the time of escaping what the value is of
rich_markup_mode
. This is not trivial, asTyperOption
andTyperArgument
are unaware of this information. And we can't just pass it through from theTyper
app either, becauseget_click_param()
creates empty/defaultOptionInfo
/ArgumentInfo
objects which will lead to default values downstream. I don't see a way to make things work via this route.Instead, this PR proposes to store a custom value in
ctx.obj
. This object is being passed around anyway, and as far as I understood,ctx.obj
may be used for these kind of application-specific settings. The code I've written tries to be super careful not to mess up any current usages of this object:Then, right before the escaping we can check this value and refrain from escaping if Rich is not enabled.
Additional complication
When
utils docs
is called to create a markdown version of a file, we need to make sure that we only callrich_to_html
on text that has been properly escaped, i.e. whenrich
is installed ANDrich_markup_mode == "rich"
. This requires us to inspect the value ofctx.obj
also in the function_parse_html
. I've added another unit test to double check this behaviour whenrich_markup_mode=None
- cftest_doc_no_typer()