typer icon indicating copy to clipboard operation
typer copied to clipboard

Potential bug with new lines in the help output when markdown mode is used

Open renardeinside opened this issue 2 years ago • 7 comments

First Check

  • [X] I added a very descriptive title to this issue.
  • [X] I used the GitHub search to find a similar issue and didn't find it.
  • [X] I searched the Typer documentation, with the integrated search.
  • [X] I already searched in Google "How to X in Typer" and didn't find any information.
  • [X] I already read and followed all the tutorial in the docs and didn't find an answer.
  • [X] I already checked if it is not related to Typer but to Click.

Commit to Help

  • [X] I commit to help with one of those options 👆

Example Code

import typer

app = typer.Typer(rich_markup_mode="markdown", name="tester-app")

@app.command(name="tester-cmd", help="""
    Header

    Line 1
    Line 2
    Line 3
""")
def cmd():
    pass

if __name__ == "__main__":
    app()

Description

  • Create the typer application as above
  • Run this application with --help switch
  • Lines are not properly formatted (they don't start from a newline):

Input:

> tester-app tester-cmd --help

Output:

 Usage: dbx tester-cmd [OPTIONS]                                                                                                                                            
                                                                                                                                                                            
Header                                                                                                                                                                     
Line 1 Line 2 Line 3     

Expected output:

Header                                                                                                                                                                     
Line 1 
Line 2
Line 3     

If I run the same command with: app = typer.Typer(rich_markup_mode="rich", name="tester-app")

I get the expected result:

 Header                                                                                                                                                                     
 Line 1                                                                                                                                                                     
 Line 2                                                                                                                                                                     
 Line 3   

If I add more newlines in markdown mode, I get the following: Input:

@app.command(name="tester-cmd", help="""
    Header

    Line 1

    Line 2

    Line 3
""")
def cmd():
    pass        

Output:

 Header                                                                                                                                                                     
 Line 1 Line 2 Line 3 

It only works if I add 3 newlines: Input:

@app.command(name="tester-cmd", help="""
    Header

    Line 1



    Line 2



    Line 3
""")
def cmd():
    pass

Output:

 Header                                                                                                                                                                     
 Line 1                                                                                                                                                                     
                                                                                                                                                                            
 Line 2                                                                                                                                                                     
                                                                                                                                                                            
 Line 3 

But it's also not the desired output. Desired output would be something similar to rich formatting:

 Header                                                                                                                                                                     
 Line 1                                                                                                                                                                     
 Line 2                                                                                                                                                                     
 Line 3   

Operating System

macOS

Operating System Details

Apple M1, macOs Monterey 12.2.1

Typer Version

0.6.1

Python Version

Python 3.9.12

Additional Context

No response

renardeinside avatar Aug 21 '22 20:08 renardeinside

For those who were running into the same issue, here is a quick monkey patch I've prepared:

  1. add the custom output formatter:
import inspect
from typing import Union, Iterable

import click
from rich.console import group
from rich.markdown import Markdown
from rich.text import Text
from typer.core import MarkupMode
from typer.rich_utils import MARKUP_MODE_MARKDOWN, STYLE_HELPTEXT_FIRST_LINE, _make_rich_rext


@group()
def _get_custom_help_text(
    *,
    obj: Union[click.Command, click.Group],
    markup_mode: MarkupMode,
) -> Iterable[Union[Markdown, Text]]:
    # Fetch and dedent the help text
    help_text = inspect.cleandoc(obj.help or "")

    # Trim off anything that comes after \f on its own line
    help_text = help_text.partition("\f")[0]

    # Get the first paragraph
    first_line = help_text.split("\n\n")[0]
    # Remove single linebreaks
    if markup_mode != MARKUP_MODE_MARKDOWN and not first_line.startswith("\b"):
        first_line = first_line.replace("\n", " ")
    yield _make_rich_rext(
        text=first_line.strip(),
        style=STYLE_HELPTEXT_FIRST_LINE,
        markup_mode=markup_mode,
    )

    # Get remaining lines, remove single line breaks and format as dim
    remaining_paragraphs = help_text.split("\n\n")[1:]
    if remaining_paragraphs:
        remaining_lines = inspect.cleandoc("\n\n".join(remaining_paragraphs).replace("<br/>", "\\"))
        yield _make_rich_rext(
            text=remaining_lines,
            style="cyan",
            markup_mode=markup_mode,
        )

  1. Monkey-patch the original method before initializing the app object:
import typer.rich_utils

typer.rich_utils._get_help_text = _get_custom_help_text

This allows you to use <br/> tag , which is recognized by other markdown readers, for example by mkdocs-click.

Some output limitations of the method above:

  • <br/> can only be specified in command help, this method won't work in arguments/options help strings
  • <br/> shall only be specified once and and the end of the string that is followed by a non-empty line.

renardeinside avatar Aug 24 '22 21:08 renardeinside

We've also just run into this issue. Markdown that is rendered correctly by Rich is not rendered correctly when fed through a Typer docstring. @renardeinside's monkeypatch may work but a proper fix would be much more palatable.

jhamman avatar Dec 17 '22 00:12 jhamman

One more bump, it makes the Markdown mode not really usable unfortunately.

jaklan avatar Dec 27 '22 21:12 jaklan

It's now over a year since this problem was reported and Markdown support remains completely broken. Is this library abandonware by now? It'd be a real pity.

MartinSoto avatar Nov 22 '23 15:11 MartinSoto

+1 for https://github.com/tiangolo/typer/pull/671 to get merged. This is a real bummer when using rich and markdown in help docs.

demizer avatar Dec 15 '23 07:12 demizer

Yeah, would love to see a fix for this!

benrhodes26 avatar Feb 21 '24 15:02 benrhodes26

Is this library abandonware by now?

It isn't 🙂. Open-source maintenance is quite a bit of work, and we're facing some backlog, but we're still committed to maintaining and improving typer. In the next few months, we hope to be making more progress on that backlog.

Thanks all for your comments & contributions, they're very much appreciated!

svlandeg avatar Feb 29 '24 16:02 svlandeg