Textualize / rich

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

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

Open Erotemic opened 1 year ago

Erotemic commented 1 year 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 1 year 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 1 year 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 1 year 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 8 months ago

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