python-ansimarkup icon indicating copy to clipboard operation
python-ansimarkup copied to clipboard

Escaping text

Open pawamoy opened this issue 3 years ago • 1 comments

Hello! Thanks for ansimarkup, it's great and I love it 🙂

I just encountered an issue though, where the text I'm printing through the ansiprint function contains text such as <l type="V">2.0</l> (XML) which triggers a MismatchedTag: closing tag "</l>" has no corresponding opening tag.

I read the README and searched the issues, but I didn't find anything about this particular issue. So, I'm kinda opening a feature request I guess?


I would like to be able to "escape" parts of the text to be printed.

I see two methods, providing an escape function or class that would:

  • wrap the text in special markers, telling the rest of the code not to interprete any tag in this wrapped text
  • replace < and > with <lt> and <gt> (or similar) that would then be re-parsed/printed as < as >

Here's how I would apply this new feature on my code:

 ansiprint(
     template.render(
         {
             "title": title,
-            "command": command,
-            "output": output,
+            "command": escape(command),
+            "output": escape(output),
         },
     ),
 )

I'm willing to try and send a PR if this is something you'd consider having 🙂

pawamoy avatar Apr 26 '21 09:04 pawamoy

Hello, gentle ping 🙂

pawamoy avatar Feb 14 '22 16:02 pawamoy

Hi @pawamoy. This one really slipped though — sorry for that. I've implemented raw string handling in release 2.0.0. They work like this:

>>> from ansimarkup import ansiprint, parse, raw
>>> ansiprint("<b><r>", raw("<l type='V'>2.0</l>"), "</r></b>")
 <l type='V'>2.0</l>  # printed in bold red (note the leading space caused)

>>> s = parse("<b><r>", raw("<l type='V'>2.0</l>"), "</r></b>")
>>> print(s)
<l type='V'>2.0</l>

Another idea was to have something like an <ESC><l type='V'>2.0</l></ESC> , but I decided against it for now. Frankly, I think the template string approach is best (and was available all along):

>>> from ansimarkup import parse
>>> s = parse("<b><r>%s</r></b>")
>>> print(s % "<l type='V'>2.0</l>")
<l type='V'>2.0</l> 

gvalkov avatar Aug 09 '23 19:08 gvalkov

Hey @gvalkov, that's amazing, thanks! I'll try this soon and report back :slightly_smiling_face:

pawamoy avatar Aug 17 '23 19:08 pawamoy

Unfortunately this does not work for my use case. Users can provide their own templates, and my own code is responsible for escaping parts of the variables that get rendered by this template. I need a text-only solution while the current implementation relies on Python and isinstance checks.

I cannot use temporary placeholders either, as the actual values must be available for correct rendering. For example output gets indented by two spaces. If I replace it by a placeholder before rendering + ansprint, and replace it back after, it will have no indentation. In short the Jinja features would be rendered ineffective.

I thought about escaping the text myself, and then unescaping it after, but it won't work either, because by the time everything is rendered and ansi-printed, I cannot distinguish which parts should be unescaped anymore.

So IMO, for my use-case there's no other solution than ansiprint dealing with escaping itself, with something like <ESC>, as you mentioned :confused:

EDIT: unless I use markup that has very small chances of appearing elsewhere to escape parts of the rendered text. I'll try that and report back.

pawamoy avatar Sep 18 '23 12:09 pawamoy

Yep, I was able to make it work :slightly_smiling_face: Could have done that earlier haha.

from ansimarkup import parse

LT = "#FAILPRINT_LT#"
GT = "#FAILPRINT_GT#"

def escape(text: str) -> str:
    return text.replace("<", LT).replace(">", GT)

def unescape(text: str) -> str:
    return text.replace(LT, "<").replace(GT, ">")

template = (
    "{% if success %}<green>✓</green>"
    "{% elif nofail %}<yellow>✗</yellow>"
    "{% else %}<red>✗</red>{% endif %} "
    "<bold>{{ title or command|e }}</bold>"
    "{% if failure %} ({{ code }}){% endif %}"
    "{% if failure and output and not quiet %}\n"
    "{{ ('  > ' + command|e + '\n') if title and command else '' }}"
    "{{ output|indent(2 * ' ')|e }}{% endif %}"
)

env = Env(...)
env.filters["e"] = escape
rendered = env.from_string(template).render(...)
print(unescape(parse(rendered)))

pawamoy avatar Sep 18 '23 12:09 pawamoy