##// END OF EJS Templates
tiny refactor to nbconvert ansi filter...
Paul Ivanov -
Show More
@@ -1,177 +1,178
1 """Filters for processing ANSI colors within Jinja templates.
1 """Filters for processing ANSI colors within Jinja templates.
2 """
2 """
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (c) 2013, the IPython Development Team.
4 # Copyright (c) 2013, the IPython Development Team.
5 #
5 #
6 # Distributed under the terms of the Modified BSD License.
6 # Distributed under the terms of the Modified BSD License.
7 #
7 #
8 # The full license is in the file COPYING.txt, distributed with this software.
8 # The full license is in the file COPYING.txt, distributed with this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 import re
15 import re
16 from IPython.utils import coloransi
16 from IPython.utils import coloransi
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Classes and functions
19 # Classes and functions
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 __all__ = [
22 __all__ = [
23 'strip_ansi',
23 'strip_ansi',
24 'ansi2html',
24 'ansi2html',
25 'single_ansi2latex',
25 'single_ansi2latex',
26 'ansi2latex'
26 'ansi2latex'
27 ]
27 ]
28
28
29 def strip_ansi(source):
29 def strip_ansi(source):
30 """
30 """
31 Remove ansi from text
31 Remove ansi from text
32
32
33 Parameters
33 Parameters
34 ----------
34 ----------
35 source : str
35 source : str
36 Source to remove the ansi from
36 Source to remove the ansi from
37 """
37 """
38
38
39 return re.sub(r'\033\[(\d|;)+?m', '', source)
39 return re.sub(r'\033\[(\d|;)+?m', '', source)
40
40
41
41
42 def ansi2html(text):
43 """
44 Conver ansi colors to html colors.
45
46 Parameters
47 ----------
48 text : str
49 Text containing ansi colors to convert to html
50 """
51
52 ansi_colormap = {
42 ansi_colormap = {
53 '30': 'ansiblack',
43 '30': 'ansiblack',
54 '31': 'ansired',
44 '31': 'ansired',
55 '32': 'ansigreen',
45 '32': 'ansigreen',
56 '33': 'ansiyellow',
46 '33': 'ansiyellow',
57 '34': 'ansiblue',
47 '34': 'ansiblue',
58 '35': 'ansipurple',
48 '35': 'ansipurple',
59 '36': 'ansicyan',
49 '36': 'ansicyan',
60 '37': 'ansigrey',
50 '37': 'ansigrey',
61 '01': 'ansibold',
51 '01': 'ansibold',
62 }
52 }
63
53
64 # do ampersand first
65 text = text.replace('&', '&')
66 html_escapes = {
54 html_escapes = {
67 '<': '&lt;',
55 '<': '&lt;',
68 '>': '&gt;',
56 '>': '&gt;',
69 "'": '&apos;',
57 "'": '&apos;',
70 '"': '&quot;',
58 '"': '&quot;',
71 '`': '&#96;',
59 '`': '&#96;',
72 }
60 }
61 ansi_re = re.compile('\x1b' + r'\[([\dA-Fa-f;]*?)m')
62
63 def ansi2html(text):
64 """
65 Convert ansi colors to html colors.
66
67 Parameters
68 ----------
69 text : str
70 Text containing ansi colors to convert to html
71 """
72
73 # do ampersand first
74 text = text.replace('&', '&amp;')
73
75
74 for c, escape in html_escapes.items():
76 for c, escape in html_escapes.items():
75 text = text.replace(c, escape)
77 text = text.replace(c, escape)
76
78
77 ansi_re = re.compile('\x1b' + r'\[([\dA-Fa-f;]*?)m')
78 m = ansi_re.search(text)
79 m = ansi_re.search(text)
79 opened = False
80 opened = False
80 cmds = []
81 cmds = []
81 opener = ''
82 opener = ''
82 closer = ''
83 closer = ''
83 while m:
84 while m:
84 cmds = m.groups()[0].split(';')
85 cmds = m.groups()[0].split(';')
85 closer = '</span>' if opened else ''
86 closer = '</span>' if opened else ''
86
87
87 # True if there is there more than one element in cmds, *or*
88 # True if there is there more than one element in cmds, *or*
88 # if there is only one but it is not equal to a string of zeroes.
89 # if there is only one but it is not equal to a string of zeroes.
89 opened = len(cmds) > 1 or cmds[0] != '0' * len(cmds[0])
90 opened = len(cmds) > 1 or cmds[0] != '0' * len(cmds[0])
90 classes = []
91 classes = []
91 for cmd in cmds:
92 for cmd in cmds:
92 if cmd in ansi_colormap:
93 if cmd in ansi_colormap:
93 classes.append(ansi_colormap.get(cmd))
94 classes.append(ansi_colormap[cmd])
94
95
95 if classes:
96 if classes:
96 opener = '<span class="%s">' % (' '.join(classes))
97 opener = '<span class="%s">' % (' '.join(classes))
97 else:
98 else:
98 opener = ''
99 opener = ''
99 text = re.sub(ansi_re, closer + opener, text, 1)
100 text = re.sub(ansi_re, closer + opener, text, 1)
100
101
101 m = ansi_re.search(text)
102 m = ansi_re.search(text)
102
103
103 if opened:
104 if opened:
104 text += '</span>'
105 text += '</span>'
105 return text
106 return text
106
107
107
108
108 def single_ansi2latex(code):
109 def single_ansi2latex(code):
109 """Converts single ansi markup to latex format.
110 """Converts single ansi markup to latex format.
110
111
111 Return latex code and number of open brackets.
112 Return latex code and number of open brackets.
112
113
113 Accepts codes like '\x1b[1;32m' (bold, red) and the short form '\x1b[32m' (red)
114 Accepts codes like '\x1b[1;32m' (bold, red) and the short form '\x1b[32m' (red)
114
115
115 Colors are matched to those defined in coloransi, which defines colors
116 Colors are matched to those defined in coloransi, which defines colors
116 using the 0, 1 (bold) and 5 (blinking) styles. Styles 1 and 5 are
117 using the 0, 1 (bold) and 5 (blinking) styles. Styles 1 and 5 are
117 interpreted as bold. All other styles are mapped to 0. Note that in
118 interpreted as bold. All other styles are mapped to 0. Note that in
118 coloransi, a style of 1 does not just mean bold; for example, Brown is
119 coloransi, a style of 1 does not just mean bold; for example, Brown is
119 "0;33", but Yellow is "1;33". An empty string is returned for unrecognised
120 "0;33", but Yellow is "1;33". An empty string is returned for unrecognised
120 codes and the "reset" code '\x1b[m'.
121 codes and the "reset" code '\x1b[m'.
121 """
122 """
122 components = code.split(';')
123 components = code.split(';')
123 if len(components) > 1:
124 if len(components) > 1:
124 # Style is digits after '['
125 # Style is digits after '['
125 style = int(components[0].split('[')[-1])
126 style = int(components[0].split('[')[-1])
126 color = components[1][:-1]
127 color = components[1][:-1]
127 else:
128 else:
128 style = 0
129 style = 0
129 color = components[0][-3:-1]
130 color = components[0][-3:-1]
130
131
131 # If the style is not normal (0), bold (1) or blinking (5) then treat it as normal
132 # If the style is not normal (0), bold (1) or blinking (5) then treat it as normal
132 if style not in [0, 1, 5]:
133 if style not in [0, 1, 5]:
133 style = 0
134 style = 0
134
135
135 for name, tcode in coloransi.color_templates:
136 for name, tcode in coloransi.color_templates:
136 tstyle, tcolor = tcode.split(';')
137 tstyle, tcolor = tcode.split(';')
137 tstyle = int(tstyle)
138 tstyle = int(tstyle)
138 if tstyle == style and tcolor == color:
139 if tstyle == style and tcolor == color:
139 break
140 break
140 else:
141 else:
141 return '', 0
142 return '', 0
142
143
143 if style == 5:
144 if style == 5:
144 name = name[5:] # BlinkRed -> Red, etc
145 name = name[5:] # BlinkRed -> Red, etc
145 name = name.lower()
146 name = name.lower()
146
147
147 if style in [1, 5]:
148 if style in [1, 5]:
148 return r'\textbf{\color{'+name+'}', 1
149 return r'\textbf{\color{'+name+'}', 1
149 else:
150 else:
150 return r'{\color{'+name+'}', 1
151 return r'{\color{'+name+'}', 1
151
152
152 def ansi2latex(text):
153 def ansi2latex(text):
153 """Converts ansi formated text to latex version
154 """Converts ansi formated text to latex version
154
155
155 based on https://bitbucket.org/birkenfeld/sphinx-contrib/ansi.py
156 based on https://bitbucket.org/birkenfeld/sphinx-contrib/ansi.py
156 """
157 """
157 color_pattern = re.compile('\x1b\\[([^m]*)m')
158 color_pattern = re.compile('\x1b\\[([^m]*)m')
158 last_end = 0
159 last_end = 0
159 openbrack = 0
160 openbrack = 0
160 outstring = ''
161 outstring = ''
161 for match in color_pattern.finditer(text):
162 for match in color_pattern.finditer(text):
162 head = text[last_end:match.start()]
163 head = text[last_end:match.start()]
163 outstring += head
164 outstring += head
164 if openbrack:
165 if openbrack:
165 outstring += '}'*openbrack
166 outstring += '}'*openbrack
166 openbrack = 0
167 openbrack = 0
167 code = match.group()
168 code = match.group()
168 if not (code == coloransi.TermColors.Normal or openbrack):
169 if not (code == coloransi.TermColors.Normal or openbrack):
169 texform, openbrack = single_ansi2latex(code)
170 texform, openbrack = single_ansi2latex(code)
170 outstring += texform
171 outstring += texform
171 last_end = match.end()
172 last_end = match.end()
172
173
173 # Add the remainer of the string and THEN close any remaining color brackets.
174 # Add the remainer of the string and THEN close any remaining color brackets.
174 outstring += text[last_end:]
175 outstring += text[last_end:]
175 if openbrack:
176 if openbrack:
176 outstring += '}'*openbrack
177 outstring += '}'*openbrack
177 return outstring.strip()
178 return outstring.strip()
General Comments 0
You need to be logged in to leave comments. Login now