python-ansimarkup
python-ansimarkup copied to clipboard
Escaping text
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 🙂
Hello, gentle ping 🙂
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>
Hey @gvalkov, that's amazing, thanks! I'll try this soon and report back :slightly_smiling_face:
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.
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)))