ansi.py
157 lines
| 4.6 KiB
| text/x-python
|
PythonLexer
|
r10676 | """Filters for processing ANSI colors within Jinja templates. | ||
""" | ||||
#----------------------------------------------------------------------------- | ||||
# Copyright (c) 2013, the IPython Development Team. | ||||
# | ||||
# Distributed under the terms of the Modified BSD License. | ||||
# | ||||
# The full license is in the file COPYING.txt, distributed with this software. | ||||
#----------------------------------------------------------------------------- | ||||
#----------------------------------------------------------------------------- | ||||
# Imports | ||||
#----------------------------------------------------------------------------- | ||||
|
r10485 | import re | ||
|
r10946 | from IPython.utils import coloransi | ||
|
r10485 | |||
|
r10676 | #----------------------------------------------------------------------------- | ||
# Classes and functions | ||||
#----------------------------------------------------------------------------- | ||||
|
r10485 | |||
|
r11088 | __all__ = [ | ||
'remove_ansi', | ||||
'ansi2html', | ||||
'single_ansi2latex', | ||||
'ansi2latex' | ||||
] | ||||
|
r10676 | def remove_ansi(source): | ||
""" | ||||
Remove ansi from text | ||||
Parameters | ||||
---------- | ||||
source : str | ||||
Source to remove the ansi from | ||||
""" | ||||
|
r11435 | return re.sub(r'\033\[(\d|;)+?m', '', source) | ||
|
r10485 | |||
|
r10676 | |||
def ansi2html(text): | ||||
""" | ||||
Conver ansi colors to html colors. | ||||
Parameters | ||||
---------- | ||||
text : str | ||||
Text containing ansi colors to convert to html | ||||
""" | ||||
|
r10485 | ansi_colormap = { | ||
'30': 'ansiblack', | ||||
'31': 'ansired', | ||||
'32': 'ansigreen', | ||||
'33': 'ansiyellow', | ||||
'34': 'ansiblue', | ||||
'35': 'ansipurple', | ||||
'36': 'ansicyan', | ||||
'37': 'ansigrey', | ||||
'01': 'ansibold', | ||||
} | ||||
# do ampersand first | ||||
|
r10676 | text = text.replace('&', '&') | ||
|
r10485 | html_escapes = { | ||
'<': '<', | ||||
'>': '>', | ||||
"'": ''', | ||||
'"': '"', | ||||
'`': '`', | ||||
} | ||||
|
r10624 | |||
|
r10485 | for c, escape in html_escapes.iteritems(): | ||
|
r10676 | text = text.replace(c, escape) | ||
|
r10485 | |||
ansi_re = re.compile('\x1b' + r'\[([\dA-Fa-f;]*?)m') | ||||
|
r10676 | m = ansi_re.search(text) | ||
|
r10485 | opened = False | ||
cmds = [] | ||||
opener = '' | ||||
closer = '' | ||||
while m: | ||||
cmds = m.groups()[0].split(';') | ||||
closer = '</span>' if opened else '' | ||||
|
r10624 | |||
|
r10485 | # True if there is there more than one element in cmds, *or* | ||
# if there is only one but it is not equal to a string of zeroes. | ||||
opened = len(cmds) > 1 or cmds[0] != '0' * len(cmds[0]) | ||||
classes = [] | ||||
for cmd in cmds: | ||||
if cmd in ansi_colormap: | ||||
classes.append(ansi_colormap.get(cmd)) | ||||
if classes: | ||||
opener = '<span class="%s">' % (' '.join(classes)) | ||||
else: | ||||
opener = '' | ||||
|
r10676 | text = re.sub(ansi_re, closer + opener, text, 1) | ||
|
r10485 | |||
|
r10676 | m = ansi_re.search(text) | ||
|
r10485 | |||
if opened: | ||||
|
r10676 | text += '</span>' | ||
return text | ||||
|
r10943 | |||
def single_ansi2latex(code): | ||||
"""Converts single ansi markup to latex format | ||||
Return latex code and number of open brackets. | ||||
""" | ||||
for color in coloransi.color_templates: | ||||
|
r11495 | |||
#Make sure to get the color code (which is a part of the overall style) | ||||
# i.e. 0;31 is valid | ||||
# 31 is also valid, and means the same thing | ||||
#coloransi.color_templates stores the longer of the two formats %d;%d | ||||
#Get the short format so we can parse that too. Short format only exist | ||||
#if no other formating is applied (the other number must be a 0)! | ||||
style_code = getattr(coloransi.TermColors, color[0]) | ||||
color_code = style_code.split(';')[1] | ||||
is_normal = style_code.split(';')[0] == '0' | ||||
|
r10943 | # regular fonts | ||
|
r11495 | if (code == style_code) or (is_normal and code == color_code): | ||
|
r10943 | return '\\'+color[0].lower()+'{', 1 | ||
# bold fonts | ||||
|
r11495 | if code == style_code[:3]+str(1)+style_code[3:]: | ||
|
r10943 | return '\\textbf{\\textcolor{'+color[0].lower()+'}{', 2 | ||
return '', 0 | ||||
def ansi2latex(text): | ||||
"""Converts ansi formated text to latex version | ||||
based on https://bitbucket.org/birkenfeld/sphinx-contrib/ansi.py | ||||
""" | ||||
color_pattern = re.compile('\x1b\\[([^m]+)m') | ||||
last_end = 0 | ||||
openbrack = 0 | ||||
outstring = '' | ||||
for match in color_pattern.finditer(text): | ||||
head = text[last_end:match.start()] | ||||
outstring += head | ||||
if openbrack: | ||||
outstring += '}'*openbrack | ||||
openbrack = 0 | ||||
|
r11547 | if not (match.group() == coloransi.TermColors.Normal or openbrack): | ||
|
r10943 | texform, openbrack = single_ansi2latex(match.group()) | ||
outstring += texform | ||||
last_end = match.end() | ||||
|
r11496 | |||
#Add the remainer of the string and THEN close any remaining color brackets. | ||||
outstring += text[last_end:] | ||||
|
r10943 | if openbrack: | ||
outstring += '}'*openbrack | ||||
return outstring.strip() | ||||