Textualize / rich

Rich is a Python library for rich text and beautiful formatting in the terminal.
https://rich.readthedocs.io/en/latest/
MIT License
48.85k stars 1.71k forks source link

[BUG] Link markups fail when paths contain square brakets. #3203

Open Erotemic opened 10 months ago

Erotemic commented 10 months ago

Describe the bug

It seems like rich.print cannot handle paths that contain square brakets using the [link] directive.

MWE:

import rich
import pathlib

path = pathlib.Path('/data/store/Media/Music/Bon Iver/For Emma, Forever Ago [2007]')
try:
    rich.print(rf'[link={path}]{path}[/link]')
except Exception as ex:
    print(f'ex={ex!r}')

This causes:

MarkupError: closing tag '[/link]' at position 129 doesn't match any open tag

Furthermore, the standard things that I could think of to escape the square brakets don't seem to work.

candidates = [
    r'[link=some_path\[with_brakets]]AnyOtherText[/link]',
    r'[link=some_path\\[with_brakets]]AnyOtherText[/link]',
    r'[link=some_path\[with_brakets\]]AnyOtherText[/link]',
    r'[link=some_path\\[with_brakets\\]]AnyOtherText[/link]',
    r'[link=some_path [with_brakets]AnyOtherText[/link]',
    r'[link=some_path [[with_brakets]AnyOtherText[/link]',
    r'[link="some_path [with_brakets]"]AnyOtherText[/link]',
    r'[link="some_path \[with_brakets]"]AnyOtherText[/link]',

    # These "work", but the link still doesn't respect the braket
    r'[link=some_path with_brakets\\]]AnyOtherText[/link]',
    r'[link=some_path with_brakets]AnyOtherText[/link]',
]

for candidate in candidates:
    try:
        rich.print(candidate)
    except Exception as ex:
        rich.print(f'[red]FAILED ex={rich.markup.escape(repr(ex))}')
    else:
        rich.print(f'[green]PASSED: {rich.markup.escape(candidate)}')

Platform

Click to expand OS: Ubuntu 22.04.3 LTS (x86_64) Python version: 3.11.2 (main, Apr 1 2023, 18:27:37) [GCC 11.3.0] (64-bit runtime) Python platform: Linux-6.2.0-36-generic-x86_64-with-glibc2.35 ``` ╭───────────────────────── ─────────────────────────╮ │ A high level console interface. │ │ │ │ ╭──────────────────────────────────────────────────────────────────────────────╮ │ │ │ │ │ │ ╰──────────────────────────────────────────────────────────────────────────────╯ │ │ │ │ color_system = 'truecolor' │ │ encoding = 'utf-8' │ │ file = <_io.TextIOWrapper name='' mode='w' encoding='utf-8'> │ │ height = 87 │ │ is_alt_screen = False │ │ is_dumb_terminal = False │ │ is_interactive = True │ │ is_jupyter = False │ │ is_terminal = True │ │ legacy_windows = False │ │ no_color = False │ │ options = ConsoleOptions( │ │ size=ConsoleDimensions(width=180, height=87), │ │ legacy_windows=False, │ │ min_width=1, │ │ max_width=180, │ │ is_terminal=True, │ │ encoding='utf-8', │ │ max_height=87, │ │ justify=None, │ │ overflow=None, │ │ no_wrap=False, │ │ highlight=None, │ │ markup=None, │ │ height=None │ │ ) │ │ quiet = False │ │ record = False │ │ safe_box = True │ │ size = ConsoleDimensions(width=180, height=87) │ │ soft_wrap = False │ │ stderr = False │ │ style = None │ │ tab_size = 8 │ │ width = 180 │ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭─── ────╮ │ Windows features available. │ │ │ │ ╭───────────────────────────────────────────────────╮ │ │ │ WindowsConsoleFeatures(vt=False, truecolor=False) │ │ │ ╰───────────────────────────────────────────────────╯ │ │ │ │ truecolor = False │ │ vt = False │ ╰───────────────────────────────────────────────────────╯ ╭────── Environment Variables ───────╮ │ { │ │ 'TERM': 'xterm-256color', │ │ 'COLORTERM': 'truecolor', │ │ 'CLICOLOR': None, │ │ 'NO_COLOR': None, │ │ 'TERM_PROGRAM': None, │ │ 'COLUMNS': None, │ │ 'LINES': None, │ │ 'JUPYTER_COLUMNS': None, │ │ 'JUPYTER_LINES': None, │ │ 'JPY_PARENT_PID': None, │ │ 'VSCODE_VERBOSE_LOGGING': None │ │ } │ ╰────────────────────────────────────╯ platform="Linux" rich==13.7.0 rich_argparse==1.1.0 ```

Is there an escape character specific for link? Or is there a way I can "encode-away" the braket in the path? Or is this just a bug that markup links don't currently support?

github-actions[bot] commented 10 months ago

Thank you for your issue. Give us a little time to review it.

PS. You might want to check the FAQ if you haven't done so already.

This is an automated reply, generated by FAQtory

Erotemic commented 10 months ago

A funny collary to this issue:

The text:

[blue][link=/data/store/Media/Music/Deep Purple]Deep Purple[/link][/blue]

In the block:

    ├─╼ [blue][link=/data/store/Media/Music/Jethro Tull]Jethro Tull[/link][/blue]
    ├─╼ [blue][link=/data/store/Media/Music/Bread]Bread[/link][/blue]
    ├─╼ [blue][link=/data/store/Media/Music/Deep Purple]Deep Purple[/link][/blue]
    ├─╼ [blue][link=/data/store/Media/Music/Tenacious D]Tenacious D[/link][/blue]
    ├─╼ [blue][link=/data/store/Media/Music/The Shins]The Shins[/link][/blue]
    ├─╼ [blue][link=/data/store/Media/Music/Dragonforce]Dragonforce[/link][/blue]
    ├─╼ [blue][link=/data/store/Media/Music/Red Hot Chili Peppers]Red Hot Chili Peppers[/link][/blue]

Renders like this:

image

However:

rich.print('[blue][link=file:///data/store/Media/Music/Deep%20Purple]Deep Purple[/link][/blue]')

does work.

Erotemic commented 10 months ago

I was able to solve this by replaceing the brakets with "%5B" and "%5D":

import rich
import pathlib
import os

path = pathlib.Path('/data/store/Media/Music/Bon Iver/For Emma, Forever Ago [2007]')
try:
    encoded_path = 'file://' + os.fspath(path).replace(' ', '%20').replace('[', '%5B').replace(']', '%5D')
    escaped_path = rich.markup.escape(os.fspath(path))
    rich.print(rf'[blue][link={encoded_path}]{escaped_path}[/link][/blue]')
except Exception as ex:
    print(f'ex={ex!r}')

It might be nice to mention URL encoding in the links docs and refer to urllib.parse (e.g. 'file://' + urllib.parse.quote(os.fspath(path)))

rhuygen commented 6 months ago

I found that when the brackets are surrounded by blanks, the MarkupError will not occur.