1
2
3
4
5
6
7
8
9 """
10 Syntax highlighter for Python values. Currently provides special
11 colorization support for:
12
13 - lists, tuples, sets, frozensets, dicts
14 - numbers
15 - strings
16 - compiled regexps
17
18 The highlighter also takes care of line-wrapping, and automatically
19 stops generating repr output as soon as it has exceeded the specified
20 number of lines (which should make it faster than pprint for large
21 values). It does I{not} bother to do automatic cycle detection,
22 because maxlines is typically around 5, so it's really not worth it.
23
24 The syntax-highlighted output is encoded using a
25 L{ParsedEpytextDocstring}, which can then be used to generate output in
26 a variety of formats.
27 """
28 __docformat__ = 'epytext en'
29
30
31
32
33
34 import types, re
35 import epydoc.apidoc
36 from epydoc.util import decode_with_backslashreplace
37 from epydoc.util import plaintext_to_html, plaintext_to_latex
38 from epydoc.compat import *
39 import sre_parse, sre_constants
40
41 from epydoc.markup.epytext import Element, ParsedEpytextDocstring
42
44 return type(pyval).__name__ == 'SRE_Pattern'
45
47 """
48 An object uesd to keep track of the current state of the pyval
49 colorizer. The L{mark()}/L{restore()} methods can be used to set
50 a backup point, and restore back to that backup point. This is
51 used by several colorization methods that first try colorizing
52 their object on a single line (setting linebreakok=False); and
53 then fall back on a multi-line output if that fails. The L{score}
54 variable is used to keep track of a 'score', reflecting how good
55 we think this repr is. E.g., unhelpful values like '<Foo instance
56 at 0x12345>' get low scores. If the score is too low, we'll use
57 the parse-derived repr instead.
58 """
60 self.result = []
61 self.charpos = 0
62 self.lineno = 1
63 self.linebreakok = True
64
65
66 self.score = 0
67
69 return (len(self.result), self.charpos,
70 self.lineno, self.linebreakok, self.score)
71
73 n, self.charpos, self.lineno, self.linebreakok, self.score = mark
74 del self.result[n:]
75
77 """A control-flow exception that is raised when PyvalColorizer
78 exeeds the maximum number of allowed lines."""
79
81 """A control-flow exception that is raised when PyvalColorizer
82 generates a string containing a newline, but the state object's
83 linebreakok variable is False."""
84
86 """
87 @ivar score: A score, evaluating how good this repr is.
88 @ivar is_complete: True if this colorized repr completely describes
89 the object.
90 """
91 - def __init__(self, tree, score, is_complete):
95
96 -def colorize_pyval(pyval, parse_repr=None, min_score=None,
97 linelen=75, maxlines=5, linebreakok=True, sort=True):
100
102 """
103 Syntax highlighter for Python values.
104 """
105
106 - def __init__(self, linelen=75, maxlines=5, linebreakok=True, sort=True):
107 self.linelen = linelen
108 self.maxlines = maxlines
109 self.linebreakok = linebreakok
110 self.sort = sort
111
112
113
114
115
116 GROUP_TAG = 'variable-group'
117 COMMA_TAG = 'variable-op'
118 COLON_TAG = 'variable-op'
119 CONST_TAG = None
120 NUMBER_TAG = None
121 QUOTE_TAG = 'variable-quote'
122 STRING_TAG = 'variable-string'
123
124 RE_CHAR_TAG = None
125 RE_GROUP_TAG = 're-group'
126 RE_REF_TAG = 're-ref'
127 RE_OP_TAG = 're-op'
128 RE_FLAGS_TAG = 're-flags'
129
130 ELLIPSIS = Element('code', u'...', style='variable-ellipsis')
131 LINEWRAP = Element('symbol', u'crarr')
132 UNKNOWN_REPR = Element('code', u'??', style='variable-unknown')
133
134 GENERIC_OBJECT_RE = re.compile(r'^<.* at 0x[0-9a-f]+>$', re.IGNORECASE)
135
136 ESCAPE_UNICODE = False
137
138
139
140
141
142 - def colorize(self, pyval, parse_repr=None, min_score=None):
143 """
144 @return: A L{ColorizedPyvalRepr} describing the given pyval.
145 """
146 UNKNOWN = epydoc.apidoc.UNKNOWN
147
148 state = _ColorizerState()
149 state.linebreakok = self.linebreakok
150
151
152 try:
153 if pyval is not UNKNOWN:
154 self._colorize(pyval, state)
155 elif parse_repr not in (None, UNKNOWN):
156 self._output(parse_repr, None, state)
157 else:
158 state.result.append(PyvalColorizer.UNKNOWN_REPR)
159 is_complete = True
160 except (_Maxlines, _Linebreak):
161 if self.linebreakok:
162 state.result.append('\n')
163 state.result.append(self.ELLIPSIS)
164 else:
165 if state.result[-1] is self.LINEWRAP:
166 state.result.pop()
167 self._trim_result(state.result, 3)
168 state.result.append(self.ELLIPSIS)
169 is_complete = False
170
171 if (pyval is not UNKNOWN and parse_repr not in (None, UNKNOWN)
172 and min_score is not None and state.score < min_score):
173 return self.colorize(UNKNOWN, parse_repr)
174
175 tree = Element('epytext', *state.result)
176 return ColorizedPyvalRepr(tree, state.score, is_complete)
177
179 pyval_type = type(pyval)
180 state.score += 1
181
182 if pyval is None or pyval is True or pyval is False:
183 self._output(unicode(pyval), self.CONST_TAG, state)
184 elif pyval_type in (int, float, long, types.ComplexType):
185 self._output(unicode(pyval), self.NUMBER_TAG, state)
186 elif pyval_type is str:
187 self._colorize_str(pyval, state, '', 'string-escape')
188 elif pyval_type is unicode:
189 if self.ESCAPE_UNICODE:
190 self._colorize_str(pyval, state, 'u', 'unicode-escape')
191 else:
192 self._colorize_str(pyval, state, 'u', None)
193 elif pyval_type is list:
194 self._multiline(self._colorize_iter, pyval, state, '[', ']')
195 elif pyval_type is tuple:
196 self._multiline(self._colorize_iter, pyval, state, '(', ')')
197 elif pyval_type is set:
198 self._multiline(self._colorize_iter, self._sort(pyval),
199 state, 'set([', '])')
200 elif pyval_type is frozenset:
201 self._multiline(self._colorize_iter, self._sort(pyval),
202 state, 'frozenset([', '])')
203 elif pyval_type is dict:
204 self._multiline(self._colorize_dict, self._sort(pyval.items()),
205 state, '{', '}')
206 elif is_re_pattern(pyval):
207 self._colorize_re(pyval, state)
208 else:
209 try:
210 pyval_repr = repr(pyval)
211 if not isinstance(pyval_repr, (str, unicode)):
212 pyval_repr = unicode(pyval_repr)
213 pyval_repr_ok = True
214 except KeyboardInterrupt:
215 raise
216 except:
217 pyval_repr_ok = False
218 state.score -= 100
219
220 if pyval_repr_ok:
221 if self.GENERIC_OBJECT_RE.match(pyval_repr):
222 state.score -= 5
223 self._output(pyval_repr, None, state)
224 else:
225 state.result.append(self.UNKNOWN_REPR)
226
228 if not self.sort: return items
229 try: return sorted(items)
230 except KeyboardInterrupt: raise
231 except: return items
232
234 while num_chars > 0:
235 if not result: return
236 if isinstance(result[-1], Element):
237 assert len(result[-1].children) == 1
238 trim = min(num_chars, len(result[-1].children[0]))
239 result[-1].children[0] = result[-1].children[0][:-trim]
240 if not result[-1].children[0]: result.pop()
241 num_chars -= trim
242 else:
243 trim = min(num_chars, len(result[-1]))
244 result[-1] = result[-1][:-trim]
245 if not result[-1]: result.pop()
246 num_chars -= trim
247
248
249
250
251
253 """
254 Helper for container-type colorizers. First, try calling
255 C{func(pyval, state, *args)} with linebreakok set to false;
256 and if that fails, then try again with it set to true.
257 """
258 linebreakok = state.linebreakok
259 mark = state.mark()
260
261 try:
262 state.linebreakok = False
263 func(pyval, state, *args)
264 state.linebreakok = linebreakok
265
266 except _Linebreak:
267 if not linebreakok:
268 raise
269 state.restore(mark)
270 func(pyval, state, *args)
271
273 self._output(prefix, self.GROUP_TAG, state)
274 indent = state.charpos
275 for i, elt in enumerate(pyval):
276 if i>=1:
277 if state.linebreakok:
278 self._output(',', self.COMMA_TAG, state)
279 self._output('\n'+' '*indent, None, state)
280 else:
281 self._output(', ', self.COMMA_TAG, state)
282 self._colorize(elt, state)
283 self._output(suffix, self.GROUP_TAG, state)
284
286 self._output(prefix, self.GROUP_TAG, state)
287 indent = state.charpos
288 for i, (key, val) in enumerate(items):
289 if i>=1:
290 if state.linebreakok:
291 self._output(',', self.COMMA_TAG, state)
292 self._output('\n'+' '*indent, None, state)
293 else:
294 self._output(', ', self.COMMA_TAG, state)
295 self._colorize(key, state)
296 self._output(': ', self.COLON_TAG, state)
297 self._colorize(val, state)
298 self._output(suffix, self.GROUP_TAG, state)
299
301
302 if '\n' in pyval and state.linebreakok: quote = "'''"
303 else: quote = "'"
304
305 if state.linebreakok:
306 lines = pyval.split('\n')
307 else:
308 lines = [pyval]
309
310 self._output(prefix+quote, self.QUOTE_TAG, state)
311
312 for i, line in enumerate(lines):
313 if i>0: self._output('\n', None, state)
314 if encoding: line = line.encode(encoding)
315 self._output(line, self.STRING_TAG, state)
316
317 self._output(