rich icon indicating copy to clipboard operation
rich copied to clipboard

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

Open Erotemic opened this issue 2 years ago • 4 comments

  • [x] I've checked docs and closed issues for possible solutions.
  • [x] I can't find my issue in the FAQ.

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

╭───────────────────────── <class 'rich.console.Console'> ─────────────────────────╮
│ A high level console interface.                                                  │
│                                                                                  │
│ ╭──────────────────────────────────────────────────────────────────────────────╮ │
│ │ <console width=180 ColorSystem.TRUECOLOR>                                    │ │
│ ╰──────────────────────────────────────────────────────────────────────────────╯ │
│                                                                                  │
│     color_system = 'truecolor'                                                   │
│         encoding = 'utf-8'                                                       │
│             file = <_io.TextIOWrapper name='<stdout>' 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                                                           │
╰──────────────────────────────────────────────────────────────────────────────────╯
╭─── <class 'rich._windows.WindowsConsoleFeatures'> ────╮
│ 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?

Erotemic avatar Nov 15 '23 22:11 Erotemic

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

github-actions[bot] avatar Nov 15 '23 22:11 github-actions[bot]

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 avatar Nov 15 '23 22:11 Erotemic

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)))

Erotemic avatar Nov 15 '23 22:11 Erotemic

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

rhuygen avatar Mar 14 '24 16:03 rhuygen