You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
297 lines
7.8 KiB
297 lines
7.8 KiB
4 years ago
|
"""Filters for processing ANSI colors within Jinja templates."""
|
||
|
|
||
|
# Copyright (c) IPython Development Team.
|
||
|
# Distributed under the terms of the Modified BSD License.
|
||
|
|
||
|
import re
|
||
|
import jinja2
|
||
|
|
||
|
__all__ = [
|
||
|
'strip_ansi',
|
||
|
'ansi2html',
|
||
|
'ansi2latex'
|
||
|
]
|
||
|
|
||
|
_ANSI_RE = re.compile('\x1b\\[(.*?)([@-~])')
|
||
|
|
||
|
_ANSI_COLORS = (
|
||
|
'ansi-black',
|
||
|
'ansi-red',
|
||
|
'ansi-green',
|
||
|
'ansi-yellow',
|
||
|
'ansi-blue',
|
||
|
'ansi-magenta',
|
||
|
'ansi-cyan',
|
||
|
'ansi-white',
|
||
|
'ansi-black-intense',
|
||
|
'ansi-red-intense',
|
||
|
'ansi-green-intense',
|
||
|
'ansi-yellow-intense',
|
||
|
'ansi-blue-intense',
|
||
|
'ansi-magenta-intense',
|
||
|
'ansi-cyan-intense',
|
||
|
'ansi-white-intense',
|
||
|
)
|
||
|
|
||
|
|
||
|
def strip_ansi(source):
|
||
|
"""
|
||
|
Remove ANSI escape codes from text.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
source : str
|
||
|
Source to remove the ANSI from
|
||
|
|
||
|
"""
|
||
|
return _ANSI_RE.sub('', source)
|
||
|
|
||
|
|
||
|
def ansi2html(text):
|
||
|
"""
|
||
|
Convert ANSI colors to HTML colors.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
text : unicode
|
||
|
Text containing ANSI colors to convert to HTML
|
||
|
|
||
|
"""
|
||
|
text = jinja2.utils.escape(text)
|
||
|
return _ansi2anything(text, _htmlconverter)
|
||
|
|
||
|
|
||
|
def ansi2latex(text):
|
||
|
"""
|
||
|
Convert ANSI colors to LaTeX colors.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
text : unicode
|
||
|
Text containing ANSI colors to convert to LaTeX
|
||
|
|
||
|
"""
|
||
|
return _ansi2anything(text, _latexconverter)
|
||
|
|
||
|
|
||
|
def _htmlconverter(fg, bg, bold, underline, inverse):
|
||
|
"""
|
||
|
Return start and end tags for given foreground/background/bold/underline.
|
||
|
|
||
|
"""
|
||
|
if (fg, bg, bold, underline, inverse) == (None, None, False, False, False):
|
||
|
return '', ''
|
||
|
|
||
|
classes = []
|
||
|
styles = []
|
||
|
|
||
|
if inverse:
|
||
|
fg, bg = bg, fg
|
||
|
|
||
|
if isinstance(fg, int):
|
||
|
classes.append(_ANSI_COLORS[fg] + '-fg')
|
||
|
elif fg:
|
||
|
styles.append('color: rgb({},{},{})'.format(*fg))
|
||
|
elif inverse:
|
||
|
classes.append('ansi-default-inverse-fg')
|
||
|
|
||
|
if isinstance(bg, int):
|
||
|
classes.append(_ANSI_COLORS[bg] + '-bg')
|
||
|
elif bg:
|
||
|
styles.append('background-color: rgb({},{},{})'.format(*bg))
|
||
|
elif inverse:
|
||
|
classes.append('ansi-default-inverse-bg')
|
||
|
|
||
|
if bold:
|
||
|
classes.append('ansi-bold')
|
||
|
|
||
|
if underline:
|
||
|
classes.append('ansi-underline')
|
||
|
|
||
|
starttag = '<span'
|
||
|
if classes:
|
||
|
starttag += ' class="' + ' '.join(classes) + '"'
|
||
|
if styles:
|
||
|
starttag += ' style="' + '; '.join(styles) + '"'
|
||
|
starttag += '>'
|
||
|
return starttag, '</span>'
|
||
|
|
||
|
|
||
|
def _latexconverter(fg, bg, bold, underline, inverse):
|
||
|
"""
|
||
|
Return start and end markup given foreground/background/bold/underline.
|
||
|
|
||
|
"""
|
||
|
if (fg, bg, bold, underline, inverse) == (None, None, False, False, False):
|
||
|
return '', ''
|
||
|
|
||
|
starttag, endtag = '', ''
|
||
|
|
||
|
if inverse:
|
||
|
fg, bg = bg, fg
|
||
|
|
||
|
if isinstance(fg, int):
|
||
|
starttag += r'\textcolor{' + _ANSI_COLORS[fg] + '}{'
|
||
|
endtag = '}' + endtag
|
||
|
elif fg:
|
||
|
# See http://tex.stackexchange.com/a/291102/13684
|
||
|
starttag += r'\def\tcRGB{\textcolor[RGB]}\expandafter'
|
||
|
starttag += r'\tcRGB\expandafter{\detokenize{%s,%s,%s}}{' % fg
|
||
|
endtag = '}' + endtag
|
||
|
elif inverse:
|
||
|
starttag += r'\textcolor{ansi-default-inverse-fg}{'
|
||
|
endtag = '}' + endtag
|
||
|
|
||
|
if isinstance(bg, int):
|
||
|
starttag += r'\setlength{\fboxsep}{0pt}'
|
||
|
starttag += r'\colorbox{' + _ANSI_COLORS[bg] + '}{'
|
||
|
endtag = r'\strut}' + endtag
|
||
|
elif bg:
|
||
|
starttag += r'\setlength{\fboxsep}{0pt}'
|
||
|
# See http://tex.stackexchange.com/a/291102/13684
|
||
|
starttag += r'\def\cbRGB{\colorbox[RGB]}\expandafter'
|
||
|
starttag += r'\cbRGB\expandafter{\detokenize{%s,%s,%s}}{' % bg
|
||
|
endtag = r'\strut}' + endtag
|
||
|
elif inverse:
|
||
|
starttag += r'\setlength{\fboxsep}{0pt}'
|
||
|
starttag += r'\colorbox{ansi-default-inverse-bg}{'
|
||
|
endtag = r'\strut}' + endtag
|
||
|
|
||
|
if bold:
|
||
|
starttag += r'\textbf{'
|
||
|
endtag = '}' + endtag
|
||
|
|
||
|
if underline:
|
||
|
starttag += r'\underline{'
|
||
|
endtag = '}' + endtag
|
||
|
|
||
|
return starttag, endtag
|
||
|
|
||
|
|
||
|
def _ansi2anything(text, converter):
|
||
|
r"""
|
||
|
Convert ANSI colors to HTML or LaTeX.
|
||
|
|
||
|
See https://en.wikipedia.org/wiki/ANSI_escape_code
|
||
|
|
||
|
Accepts codes like '\x1b[32m' (red) and '\x1b[1;32m' (bold, red).
|
||
|
|
||
|
Non-color escape sequences (not ending with 'm') are filtered out.
|
||
|
|
||
|
Ideally, this should have the same behavior as the function
|
||
|
fixConsole() in notebook/notebook/static/base/js/utils.js.
|
||
|
|
||
|
"""
|
||
|
fg, bg = None, None
|
||
|
bold = False
|
||
|
underline = False
|
||
|
inverse = False
|
||
|
numbers = []
|
||
|
out = []
|
||
|
|
||
|
while text:
|
||
|
m = _ANSI_RE.search(text)
|
||
|
if m:
|
||
|
if m.group(2) == 'm':
|
||
|
try:
|
||
|
# Empty code is same as code 0
|
||
|
numbers = [int(n) if n else 0
|
||
|
for n in m.group(1).split(';')]
|
||
|
except ValueError:
|
||
|
pass # Invalid color specification
|
||
|
else:
|
||
|
pass # Not a color code
|
||
|
chunk, text = text[:m.start()], text[m.end():]
|
||
|
else:
|
||
|
chunk, text = text, ''
|
||
|
|
||
|
if chunk:
|
||
|
starttag, endtag = converter(
|
||
|
fg + 8 if bold and fg in range(8) else fg,
|
||
|
bg, bold, underline, inverse)
|
||
|
out.append(starttag)
|
||
|
out.append(chunk)
|
||
|
out.append(endtag)
|
||
|
|
||
|
while numbers:
|
||
|
n = numbers.pop(0)
|
||
|
if n == 0:
|
||
|
# Code 0 (same as empty code): reset everything
|
||
|
fg = bg = None
|
||
|
bold = underline = inverse = False
|
||
|
elif n == 1:
|
||
|
bold = True
|
||
|
elif n == 4:
|
||
|
underline = True
|
||
|
elif n == 5:
|
||
|
# Code 5: blinking
|
||
|
bold = True
|
||
|
elif n == 7:
|
||
|
inverse = True
|
||
|
elif n in (21, 22):
|
||
|
bold = False
|
||
|
elif n == 24:
|
||
|
underline = False
|
||
|
elif n == 27:
|
||
|
inverse = False
|
||
|
elif 30 <= n <= 37:
|
||
|
fg = n - 30
|
||
|
elif n == 38:
|
||
|
try:
|
||
|
fg = _get_extended_color(numbers)
|
||
|
except ValueError:
|
||
|
numbers.clear()
|
||
|
elif n == 39:
|
||
|
fg = None
|
||
|
elif 40 <= n <= 47:
|
||
|
bg = n - 40
|
||
|
elif n == 48:
|
||
|
try:
|
||
|
bg = _get_extended_color(numbers)
|
||
|
except ValueError:
|
||
|
numbers.clear()
|
||
|
elif n == 49:
|
||
|
bg = None
|
||
|
elif 90 <= n <= 97:
|
||
|
fg = n - 90 + 8
|
||
|
elif 100 <= n <= 107:
|
||
|
bg = n - 100 + 8
|
||
|
else:
|
||
|
pass # Unknown codes are ignored
|
||
|
return ''.join(out)
|
||
|
|
||
|
|
||
|
def _get_extended_color(numbers):
|
||
|
n = numbers.pop(0)
|
||
|
if n == 2 and len(numbers) >= 3:
|
||
|
# 24-bit RGB
|
||
|
r = numbers.pop(0)
|
||
|
g = numbers.pop(0)
|
||
|
b = numbers.pop(0)
|
||
|
if not all(0 <= c <= 255 for c in (r, g, b)):
|
||
|
raise ValueError()
|
||
|
elif n == 5 and len(numbers) >= 1:
|
||
|
# 256 colors
|
||
|
idx = numbers.pop(0)
|
||
|
if idx < 0:
|
||
|
raise ValueError()
|
||
|
elif idx < 16:
|
||
|
# 16 default terminal colors
|
||
|
return idx
|
||
|
elif idx < 232:
|
||
|
# 6x6x6 color cube, see http://stackoverflow.com/a/27165165/500098
|
||
|
r = (idx - 16) // 36
|
||
|
r = 55 + r * 40 if r > 0 else 0
|
||
|
g = ((idx - 16) % 36) // 6
|
||
|
g = 55 + g * 40 if g > 0 else 0
|
||
|
b = (idx - 16) % 6
|
||
|
b = 55 + b * 40 if b > 0 else 0
|
||
|
elif idx < 256:
|
||
|
# grayscale, see http://stackoverflow.com/a/27165165/500098
|
||
|
r = g = b = (idx - 232) * 10 + 8
|
||
|
else:
|
||
|
raise ValueError()
|
||
|
else:
|
||
|
raise ValueError()
|
||
|
return r, g, b
|