wasi-master / rich-rst

A reStructuredText renderer for rich
https://rich-rst.readthedocs.io/en/latest/
MIT License
12 stars 3 forks source link

[FEAT]: Integrate into `rich-click` #9

Open dwreeves opened 3 months ago

dwreeves commented 3 months ago

Description

Hello! For rich-click 1.9.0, I'd love to support this library.

We have an issue open on our end:

https://github.com/ewels/rich-click/issues/172

There are a few barriers to adding support, however. Here's a good example here of one of the issues. When I use rst, I get this:

image

For the following code example and python hello.py --help:

# hello.py
import rich_click as click

click.rich_click.TEXT_MARKUP = "rst"

@click.group("greetings")
def greetings_cli():
    """CLI for greetings."""

@greetings_cli.command("english")
@click.argument("name")
def english(name):
    """Greet in English"""
    print(f"Hello, {name}!")

@greetings_cli.command("french")
@click.argument("name")
def french(name):
    """Greet in ``French``."""
    print(f"Bonjour, {name}!")

if __name__ == "__main__":
    greetings_cli()

This being due to the \n\n that gets added at the end of each document. It doesn't seem like there is a way to override that, at the moment.

There may be a few other issues; I haven't tested it all that much!

It may be a few months before we get to this, but just putting it on your radar. I may end up opening a few PRs relating to this to help fix our issues (in a backwards compatible way, of course).

Additional Information

No response

BrianPugh commented 3 months ago

Just chiming in, I would also benefit from the final \n\n being stripped; I may also be investigating soon.

dwreeves commented 3 months ago

Hi @BrianPugh,

First, thank you for your work on that PR and thank you for echoing my concern!

A question / request: for adding RST support to rich-click, we don't want a \n to render at the end of any text snippet.

However, if I'm interpreting your PR correctly, it adds a \n at the end of each rendered chunk:

+        if visitor.renderables and isinstance(visitor.renderables[-1], Text):
+            visitor.renderables[-1].rstrip()
+            visitor.renderables[-1].end = "\n"

I don't have an opinion on defaults, but I do need a way to be able to override that. Otherwise, rich-rst cannot work for us.

There are some solutions that I think would work fine. One would be to add another kwarg to RestructuredText, something like:

class RestructuredText(JupyterMixin):

    def __init__(
        self,
        markup: str,
        code_theme: Optional[Union[str, SyntaxTheme]] = "monokai",
        show_errors: Optional[bool] = True,
        guess_lexer: Optional[bool] = False,
        default_lexer: Optional[str] = "python",
        filename: Optional[str] = "<rst-document>",
+        end: str = "\n"
    ) -> None:
        self.markup = markup
        self.code_theme = code_theme
        self.log_errors = show_errors
        self.guess_lexer = guess_lexer
        self.default_lexer = default_lexer
        self.filename = filename
+        self.end = end

which gets passed into the RSTVisitor.

Another would be to expose the visitor class as a class attr of RestructuredText:

class RestructuredText(JupyterMixin):

+    visitor_class: Type[RSTVisitor] = RSTVisitor

# ...

    def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
        # Parse the `markup` into a RST `document`.
        option_parser = docutils.frontend.OptionParser(components=(docutils.parsers.rst.Parser,))
        settings = option_parser.get_default_values()
        settings.report_level = 69
        source = docutils.io.StringInput(self.markup)
        document = docutils.utils.new_document(self.filename, settings)
        rst_parser = docutils.parsers.rst.Parser()
        rst_parser.parse(source.read(), document)

        # Render the RST `document` using Rich.
+        visitor = self.visitor_class(
            document,
            console=console,
            code_theme=self.code_theme,
            guess_lexer=self.guess_lexer,
            default_lexer=self.default_lexer,
        )

Or both.

Anything like these changes makes life significantly easier for us.

BrianPugh commented 3 months ago

I think the end="\n" parameter would be useful, but I also think that the RestructuredText class should mimic the official rich.Markdown class as much as possible.

The proposed change:

+        if visitor.renderables and isinstance(visitor.renderables[-1], Text):
+            visitor.renderables[-1].rstrip()
+            visitor.renderables[-1].end = "\n"

does 2 things. The first line strips the two trailing \n\n characters of the final rich object (if it's a Text object). The next line sets the end attribute back to the standard default \n. I'm not actually 100% sure when the end attribute is used.

My use-case is incredibly similar to your use-case (it's for my similar Cyclopts library). The PR as-is is probably what you actually want.

With #12, consider the following demo:

from rich import box, console, print
from rich.panel import Panel
from rich_rst import RestructuredText

panel_description = RestructuredText("Hello World")

panel = Panel(
    panel_description,
    box=box.ROUNDED,
    expand=True,
    title_align="left",
    title="Panel Title",
)

print(panel)

Produces the following output (which I believe is what both of us wants):

$ python rst-demo.py
╭─ Panel Title ──────────────────────────────────────────────────────────────────╮
│ Hello World                                                                    │
╰────────────────────────────────────────────────────────────────────────────────╯
dwreeves commented 3 months ago

Ah yes, you are right, my mistake, sorry! Just tested:

image

Ignore what I said! 😅