##// END OF EJS Templates
Use 'Linux' (dark bg) colours for Windows...
Thomas Kluyver -
Show More
@@ -1,374 +1,382 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 Class and program to colorize python source code for ANSI terminals.
4 4
5 5 Based on an HTML code highlighter by Jurgen Hermann found at:
6 6 http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52298
7 7
8 8 Modifications by Fernando Perez (fperez@colorado.edu).
9 9
10 10 Information on the original HTML highlighter follows:
11 11
12 12 MoinMoin - Python Source Parser
13 13
14 14 Title: Colorize Python source using the built-in tokenizer
15 15
16 16 Submitter: Jurgen Hermann
17 17 Last Updated:2001/04/06
18 18
19 19 Version no:1.2
20 20
21 21 Description:
22 22
23 23 This code is part of MoinMoin (http://moin.sourceforge.net/) and converts
24 24 Python source code to HTML markup, rendering comments, keywords,
25 25 operators, numeric and string literals in different colors.
26 26
27 27 It shows how to use the built-in keyword, token and tokenize modules to
28 28 scan Python source code and re-emit it with no changes to its original
29 29 formatting (which is the hard part).
30 30 """
31 31 from __future__ import print_function
32 32 from __future__ import absolute_import
33 33 from __future__ import unicode_literals
34 34
35 35 __all__ = ['ANSICodeColors','Parser']
36 36
37 37 _scheme_default = 'Linux'
38 38
39 39
40 40 # Imports
41 41 import keyword
42 42 import os
43 43 import sys
44 44 import token
45 45 import tokenize
46 46
47 47 try:
48 48 generate_tokens = tokenize.generate_tokens
49 49 except AttributeError:
50 50 # Python 3. Note that we use the undocumented _tokenize because it expects
51 51 # strings, not bytes. See also Python issue #9969.
52 52 generate_tokens = tokenize._tokenize
53 53
54 54 from IPython.utils.coloransi import TermColors, InputTermColors ,ColorScheme, ColorSchemeTable
55 55 from IPython.utils.py3compat import PY3
56 56
57 57 from .colorable import Colorable
58 58
59 59 if PY3:
60 60 from io import StringIO
61 61 else:
62 62 from StringIO import StringIO
63 63
64 64 #############################################################################
65 65 ### Python Source Parser (does Hilighting)
66 66 #############################################################################
67 67
68 68 _KEYWORD = token.NT_OFFSET + 1
69 69 _TEXT = token.NT_OFFSET + 2
70 70
71 71 #****************************************************************************
72 72 # Builtin color schemes
73 73
74 74 Colors = TermColors # just a shorthand
75 75
76 76 # Build a few color schemes
77 77 NoColor = ColorScheme(
78 78 'NoColor',{
79 79 'header' : Colors.NoColor,
80 80 token.NUMBER : Colors.NoColor,
81 81 token.OP : Colors.NoColor,
82 82 token.STRING : Colors.NoColor,
83 83 tokenize.COMMENT : Colors.NoColor,
84 84 token.NAME : Colors.NoColor,
85 85 token.ERRORTOKEN : Colors.NoColor,
86 86
87 87 _KEYWORD : Colors.NoColor,
88 88 _TEXT : Colors.NoColor,
89 89
90 90 'in_prompt' : InputTermColors.NoColor, # Input prompt
91 91 'in_number' : InputTermColors.NoColor, # Input prompt number
92 92 'in_prompt2' : InputTermColors.NoColor, # Continuation prompt
93 93 'in_normal' : InputTermColors.NoColor, # color off (usu. Colors.Normal)
94 94
95 95 'out_prompt' : Colors.NoColor, # Output prompt
96 96 'out_number' : Colors.NoColor, # Output prompt number
97 97
98 98 'normal' : Colors.NoColor # color off (usu. Colors.Normal)
99 99 } )
100 100
101 101 LinuxColors = ColorScheme(
102 102 'Linux',{
103 103 'header' : Colors.LightRed,
104 104 token.NUMBER : Colors.LightCyan,
105 105 token.OP : Colors.Yellow,
106 106 token.STRING : Colors.LightBlue,
107 107 tokenize.COMMENT : Colors.LightRed,
108 108 token.NAME : Colors.Normal,
109 109 token.ERRORTOKEN : Colors.Red,
110 110
111 111 _KEYWORD : Colors.LightGreen,
112 112 _TEXT : Colors.Yellow,
113 113
114 114 'in_prompt' : InputTermColors.Green,
115 115 'in_number' : InputTermColors.LightGreen,
116 116 'in_prompt2' : InputTermColors.Green,
117 117 'in_normal' : InputTermColors.Normal, # color off (usu. Colors.Normal)
118 118
119 119 'out_prompt' : Colors.Red,
120 120 'out_number' : Colors.LightRed,
121 121
122 122 'normal' : Colors.Normal # color off (usu. Colors.Normal)
123 123 } )
124 124
125 125 NeutralColors = ColorScheme(
126 126 'Neutral',{
127 127 'header' : Colors.Red,
128 128 token.NUMBER : Colors.Cyan,
129 129 token.OP : Colors.Blue,
130 130 token.STRING : Colors.Blue,
131 131 tokenize.COMMENT : Colors.Red,
132 132 token.NAME : Colors.Normal,
133 133 token.ERRORTOKEN : Colors.Red,
134 134
135 135 _KEYWORD : Colors.Green,
136 136 _TEXT : Colors.Blue,
137 137
138 138 'in_prompt' : InputTermColors.Blue,
139 139 'in_number' : InputTermColors.LightBlue,
140 140 'in_prompt2' : InputTermColors.Blue,
141 141 'in_normal' : InputTermColors.Normal, # color off (usu. Colors.Normal)
142 142
143 143 'out_prompt' : Colors.Red,
144 144 'out_number' : Colors.LightRed,
145 145
146 146 'normal' : Colors.Normal # color off (usu. Colors.Normal)
147 147 } )
148 148
149
149 # Hack: the 'neutral' colours are not very visible on a dark background on
150 # Windows. Since Windows command prompts have a dark background by default, and
151 # relatively few users are likely to alter that, we will use the 'Linux' colours,
152 # designed for a dark background, as the default on Windows. Changing it here
153 # avoids affecting the prompt colours rendered by prompt_toolkit, where the
154 # neutral defaults do work OK.
155
156 if os.name == 'nt':
157 NeutralColors = LinuxColors.copy(name='Neutral')
150 158
151 159 LightBGColors = ColorScheme(
152 160 'LightBG',{
153 161 'header' : Colors.Red,
154 162 token.NUMBER : Colors.Cyan,
155 163 token.OP : Colors.Blue,
156 164 token.STRING : Colors.Blue,
157 165 tokenize.COMMENT : Colors.Red,
158 166 token.NAME : Colors.Normal,
159 167 token.ERRORTOKEN : Colors.Red,
160 168
161 169
162 170 _KEYWORD : Colors.Green,
163 171 _TEXT : Colors.Blue,
164 172
165 173 'in_prompt' : InputTermColors.Blue,
166 174 'in_number' : InputTermColors.LightBlue,
167 175 'in_prompt2' : InputTermColors.Blue,
168 176 'in_normal' : InputTermColors.Normal, # color off (usu. Colors.Normal)
169 177
170 178 'out_prompt' : Colors.Red,
171 179 'out_number' : Colors.LightRed,
172 180
173 181 'normal' : Colors.Normal # color off (usu. Colors.Normal)
174 182 } )
175 183
176 184 # Build table of color schemes (needed by the parser)
177 185 ANSICodeColors = ColorSchemeTable([NoColor,LinuxColors,LightBGColors, NeutralColors],
178 186 _scheme_default)
179 187
180 188 class Parser(Colorable):
181 189 """ Format colored Python source.
182 190 """
183 191
184 192 def __init__(self, color_table=None, out = sys.stdout, parent=None, style=None):
185 193 """ Create a parser with a specified color table and output channel.
186 194
187 195 Call format() to process code.
188 196 """
189 197
190 198 super(Parser, self).__init__(parent=parent)
191 199
192 200 self.color_table = color_table and color_table or ANSICodeColors
193 201 self.out = out
194 202
195 203 def format(self, raw, out = None, scheme = ''):
196 204 return self.format2(raw, out, scheme)[0]
197 205
198 206 def format2(self, raw, out = None, scheme = ''):
199 207 """ Parse and send the colored source.
200 208
201 209 If out and scheme are not specified, the defaults (given to
202 210 constructor) are used.
203 211
204 212 out should be a file-type object. Optionally, out can be given as the
205 213 string 'str' and the parser will automatically return the output in a
206 214 string."""
207 215
208 216 string_output = 0
209 217 if out == 'str' or self.out == 'str' or \
210 218 isinstance(self.out,StringIO):
211 219 # XXX - I don't really like this state handling logic, but at this
212 220 # point I don't want to make major changes, so adding the
213 221 # isinstance() check is the simplest I can do to ensure correct
214 222 # behavior.
215 223 out_old = self.out
216 224 self.out = StringIO()
217 225 string_output = 1
218 226 elif out is not None:
219 227 self.out = out
220 228
221 229 # Fast return of the unmodified input for NoColor scheme
222 230 if scheme == 'NoColor':
223 231 error = False
224 232 self.out.write(raw)
225 233 if string_output:
226 234 return raw,error
227 235 else:
228 236 return None,error
229 237
230 238 # local shorthands
231 239 colors = self.color_table[scheme].colors
232 240 self.colors = colors # put in object so __call__ sees it
233 241
234 242 # Remove trailing whitespace and normalize tabs
235 243 self.raw = raw.expandtabs().rstrip()
236 244
237 245 # store line offsets in self.lines
238 246 self.lines = [0, 0]
239 247 pos = 0
240 248 raw_find = self.raw.find
241 249 lines_append = self.lines.append
242 250 while 1:
243 251 pos = raw_find('\n', pos) + 1
244 252 if not pos: break
245 253 lines_append(pos)
246 254 lines_append(len(self.raw))
247 255
248 256 # parse the source and write it
249 257 self.pos = 0
250 258 text = StringIO(self.raw)
251 259
252 260 error = False
253 261 try:
254 262 for atoken in generate_tokens(text.readline):
255 263 self(*atoken)
256 264 except tokenize.TokenError as ex:
257 265 msg = ex.args[0]
258 266 line = ex.args[1][0]
259 267 self.out.write("%s\n\n*** ERROR: %s%s%s\n" %
260 268 (colors[token.ERRORTOKEN],
261 269 msg, self.raw[self.lines[line]:],
262 270 colors.normal)
263 271 )
264 272 error = True
265 273 self.out.write(colors.normal+'\n')
266 274 if string_output:
267 275 output = self.out.getvalue()
268 276 self.out = out_old
269 277 return (output, error)
270 278 return (None, error)
271 279
272 280 def __call__(self, toktype, toktext, start_pos, end_pos, line):
273 281 """ Token handler, with syntax highlighting."""
274 282 (srow,scol) = start_pos
275 283 (erow,ecol) = end_pos
276 284 colors = self.colors
277 285 owrite = self.out.write
278 286
279 287 # line separator, so this works across platforms
280 288 linesep = os.linesep
281 289
282 290 # calculate new positions
283 291 oldpos = self.pos
284 292 newpos = self.lines[srow] + scol
285 293 self.pos = newpos + len(toktext)
286 294
287 295 # send the original whitespace, if needed
288 296 if newpos > oldpos:
289 297 owrite(self.raw[oldpos:newpos])
290 298
291 299 # skip indenting tokens
292 300 if toktype in [token.INDENT, token.DEDENT]:
293 301 self.pos = newpos
294 302 return
295 303
296 304 # map token type to a color group
297 305 if token.LPAR <= toktype <= token.OP:
298 306 toktype = token.OP
299 307 elif toktype == token.NAME and keyword.iskeyword(toktext):
300 308 toktype = _KEYWORD
301 309 color = colors.get(toktype, colors[_TEXT])
302 310
303 311 #print '<%s>' % toktext, # dbg
304 312
305 313 # Triple quoted strings must be handled carefully so that backtracking
306 314 # in pagers works correctly. We need color terminators on _each_ line.
307 315 if linesep in toktext:
308 316 toktext = toktext.replace(linesep, '%s%s%s' %
309 317 (colors.normal,linesep,color))
310 318
311 319 # send text
312 320 owrite('%s%s%s' % (color,toktext,colors.normal))
313 321
314 322 def main(argv=None):
315 323 """Run as a command-line script: colorize a python file or stdin using ANSI
316 324 color escapes and print to stdout.
317 325
318 326 Inputs:
319 327
320 328 - argv(None): a list of strings like sys.argv[1:] giving the command-line
321 329 arguments. If None, use sys.argv[1:].
322 330 """
323 331
324 332 usage_msg = """%prog [options] [filename]
325 333
326 334 Colorize a python file or stdin using ANSI color escapes and print to stdout.
327 335 If no filename is given, or if filename is -, read standard input."""
328 336
329 337 import optparse
330 338 parser = optparse.OptionParser(usage=usage_msg)
331 339 newopt = parser.add_option
332 340 newopt('-s','--scheme',metavar='NAME',dest='scheme_name',action='store',
333 341 choices=['Linux','LightBG','NoColor'],default=_scheme_default,
334 342 help="give the color scheme to use. Currently only 'Linux'\
335 343 (default) and 'LightBG' and 'NoColor' are implemented (give without\
336 344 quotes)")
337 345
338 346 opts,args = parser.parse_args(argv)
339 347
340 348 if len(args) > 1:
341 349 parser.error("you must give at most one filename.")
342 350
343 351 if len(args) == 0:
344 352 fname = '-' # no filename given; setup to read from stdin
345 353 else:
346 354 fname = args[0]
347 355
348 356 if fname == '-':
349 357 stream = sys.stdin
350 358 else:
351 359 try:
352 360 stream = open(fname)
353 361 except IOError as msg:
354 362 print(msg, file=sys.stderr)
355 363 sys.exit(1)
356 364
357 365 parser = Parser()
358 366
359 367 # we need nested try blocks because pre-2.5 python doesn't support unified
360 368 # try-except-finally
361 369 try:
362 370 try:
363 371 # write colorized version to stdout
364 372 parser.format(stream.read(),scheme=opts.scheme_name)
365 373 except IOError as msg:
366 374 # if user reads through a pager and quits, don't print traceback
367 375 if msg.args != (32,'Broken pipe'):
368 376 raise
369 377 finally:
370 378 if stream is not sys.stdin:
371 379 stream.close() # in case a non-handled exception happened above
372 380
373 381 if __name__ == "__main__":
374 382 main()
General Comments 0
You need to be logged in to leave comments. Login now