##// END OF EJS Templates
Defer import of optparse
Thomas Kluyver -
Show More
@@ -1,310 +1,310 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
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 StringIO
42 42 import keyword
43 43 import os
44 import optparse
45 44 import sys
46 45 import token
47 46 import tokenize
48 47
49 48 try:
50 49 generate_tokens = tokenize.generate_tokens
51 50 except AttributeError:
52 51 # Python 3. Note that we use the undocumented _tokenize because it expects
53 52 # strings, not bytes. See also Python issue #9969.
54 53 generate_tokens = tokenize._tokenize
55 54
56 55 from IPython.utils.coloransi import *
57 56
58 57 #############################################################################
59 58 ### Python Source Parser (does Hilighting)
60 59 #############################################################################
61 60
62 61 _KEYWORD = token.NT_OFFSET + 1
63 62 _TEXT = token.NT_OFFSET + 2
64 63
65 64 #****************************************************************************
66 65 # Builtin color schemes
67 66
68 67 Colors = TermColors # just a shorthand
69 68
70 69 # Build a few color schemes
71 70 NoColor = ColorScheme(
72 71 'NoColor',{
73 72 token.NUMBER : Colors.NoColor,
74 73 token.OP : Colors.NoColor,
75 74 token.STRING : Colors.NoColor,
76 75 tokenize.COMMENT : Colors.NoColor,
77 76 token.NAME : Colors.NoColor,
78 77 token.ERRORTOKEN : Colors.NoColor,
79 78
80 79 _KEYWORD : Colors.NoColor,
81 80 _TEXT : Colors.NoColor,
82 81
83 82 'normal' : Colors.NoColor # color off (usu. Colors.Normal)
84 83 } )
85 84
86 85 LinuxColors = ColorScheme(
87 86 'Linux',{
88 87 token.NUMBER : Colors.LightCyan,
89 88 token.OP : Colors.Yellow,
90 89 token.STRING : Colors.LightBlue,
91 90 tokenize.COMMENT : Colors.LightRed,
92 91 token.NAME : Colors.Normal,
93 92 token.ERRORTOKEN : Colors.Red,
94 93
95 94 _KEYWORD : Colors.LightGreen,
96 95 _TEXT : Colors.Yellow,
97 96
98 97 'normal' : Colors.Normal # color off (usu. Colors.Normal)
99 98 } )
100 99
101 100 LightBGColors = ColorScheme(
102 101 'LightBG',{
103 102 token.NUMBER : Colors.Cyan,
104 103 token.OP : Colors.Blue,
105 104 token.STRING : Colors.Blue,
106 105 tokenize.COMMENT : Colors.Red,
107 106 token.NAME : Colors.Normal,
108 107 token.ERRORTOKEN : Colors.Red,
109 108
110 109 _KEYWORD : Colors.Green,
111 110 _TEXT : Colors.Blue,
112 111
113 112 'normal' : Colors.Normal # color off (usu. Colors.Normal)
114 113 } )
115 114
116 115 # Build table of color schemes (needed by the parser)
117 116 ANSICodeColors = ColorSchemeTable([NoColor,LinuxColors,LightBGColors],
118 117 _scheme_default)
119 118
120 119 class Parser:
121 120 """ Format colored Python source.
122 121 """
123 122
124 123 def __init__(self, color_table=None,out = sys.stdout):
125 124 """ Create a parser with a specified color table and output channel.
126 125
127 126 Call format() to process code.
128 127 """
129 128 self.color_table = color_table and color_table or ANSICodeColors
130 129 self.out = out
131 130
132 131 def format(self, raw, out = None, scheme = ''):
133 132 return self.format2(raw, out, scheme)[0]
134 133
135 134 def format2(self, raw, out = None, scheme = ''):
136 135 """ Parse and send the colored source.
137 136
138 137 If out and scheme are not specified, the defaults (given to
139 138 constructor) are used.
140 139
141 140 out should be a file-type object. Optionally, out can be given as the
142 141 string 'str' and the parser will automatically return the output in a
143 142 string."""
144 143
145 144 string_output = 0
146 145 if out == 'str' or self.out == 'str' or \
147 146 isinstance(self.out,StringIO.StringIO):
148 147 # XXX - I don't really like this state handling logic, but at this
149 148 # point I don't want to make major changes, so adding the
150 149 # isinstance() check is the simplest I can do to ensure correct
151 150 # behavior.
152 151 out_old = self.out
153 152 self.out = StringIO.StringIO()
154 153 string_output = 1
155 154 elif out is not None:
156 155 self.out = out
157 156
158 157 # Fast return of the unmodified input for NoColor scheme
159 158 if scheme == 'NoColor':
160 159 error = False
161 160 self.out.write(raw)
162 161 if string_output:
163 162 return raw,error
164 163 else:
165 164 return None,error
166 165
167 166 # local shorthands
168 167 colors = self.color_table[scheme].colors
169 168 self.colors = colors # put in object so __call__ sees it
170 169
171 170 # Remove trailing whitespace and normalize tabs
172 171 self.raw = raw.expandtabs().rstrip()
173 172
174 173 # store line offsets in self.lines
175 174 self.lines = [0, 0]
176 175 pos = 0
177 176 raw_find = self.raw.find
178 177 lines_append = self.lines.append
179 178 while 1:
180 179 pos = raw_find('\n', pos) + 1
181 180 if not pos: break
182 181 lines_append(pos)
183 182 lines_append(len(self.raw))
184 183
185 184 # parse the source and write it
186 185 self.pos = 0
187 186 text = StringIO.StringIO(self.raw)
188 187
189 188 error = False
190 189 try:
191 190 for atoken in generate_tokens(text.readline):
192 191 self(*atoken)
193 192 except tokenize.TokenError as ex:
194 193 msg = ex.args[0]
195 194 line = ex.args[1][0]
196 195 self.out.write("%s\n\n*** ERROR: %s%s%s\n" %
197 196 (colors[token.ERRORTOKEN],
198 197 msg, self.raw[self.lines[line]:],
199 198 colors.normal)
200 199 )
201 200 error = True
202 201 self.out.write(colors.normal+'\n')
203 202 if string_output:
204 203 output = self.out.getvalue()
205 204 self.out = out_old
206 205 return (output, error)
207 206 return (None, error)
208 207
209 208 def __call__(self, toktype, toktext, start_pos, end_pos, line):
210 209 """ Token handler, with syntax highlighting."""
211 210 (srow,scol) = start_pos
212 211 (erow,ecol) = end_pos
213 212 colors = self.colors
214 213 owrite = self.out.write
215 214
216 215 # line separator, so this works across platforms
217 216 linesep = os.linesep
218 217
219 218 # calculate new positions
220 219 oldpos = self.pos
221 220 newpos = self.lines[srow] + scol
222 221 self.pos = newpos + len(toktext)
223 222
224 223 # send the original whitespace, if needed
225 224 if newpos > oldpos:
226 225 owrite(self.raw[oldpos:newpos])
227 226
228 227 # skip indenting tokens
229 228 if toktype in [token.INDENT, token.DEDENT]:
230 229 self.pos = newpos
231 230 return
232 231
233 232 # map token type to a color group
234 233 if token.LPAR <= toktype and toktype <= token.OP:
235 234 toktype = token.OP
236 235 elif toktype == token.NAME and keyword.iskeyword(toktext):
237 236 toktype = _KEYWORD
238 237 color = colors.get(toktype, colors[_TEXT])
239 238
240 239 #print '<%s>' % toktext, # dbg
241 240
242 241 # Triple quoted strings must be handled carefully so that backtracking
243 242 # in pagers works correctly. We need color terminators on _each_ line.
244 243 if linesep in toktext:
245 244 toktext = toktext.replace(linesep, '%s%s%s' %
246 245 (colors.normal,linesep,color))
247 246
248 247 # send text
249 248 owrite('%s%s%s' % (color,toktext,colors.normal))
250 249
251 250 def main(argv=None):
252 251 """Run as a command-line script: colorize a python file or stdin using ANSI
253 252 color escapes and print to stdout.
254 253
255 254 Inputs:
256 255
257 256 - argv(None): a list of strings like sys.argv[1:] giving the command-line
258 257 arguments. If None, use sys.argv[1:].
259 258 """
260 259
261 260 usage_msg = """%prog [options] [filename]
262 261
263 262 Colorize a python file or stdin using ANSI color escapes and print to stdout.
264 263 If no filename is given, or if filename is -, read standard input."""
265 264
265 import optparse
266 266 parser = optparse.OptionParser(usage=usage_msg)
267 267 newopt = parser.add_option
268 268 newopt('-s','--scheme',metavar='NAME',dest='scheme_name',action='store',
269 269 choices=['Linux','LightBG','NoColor'],default=_scheme_default,
270 270 help="give the color scheme to use. Currently only 'Linux'\
271 271 (default) and 'LightBG' and 'NoColor' are implemented (give without\
272 272 quotes)")
273 273
274 274 opts,args = parser.parse_args(argv)
275 275
276 276 if len(args) > 1:
277 277 parser.error("you must give at most one filename.")
278 278
279 279 if len(args) == 0:
280 280 fname = '-' # no filename given; setup to read from stdin
281 281 else:
282 282 fname = args[0]
283 283
284 284 if fname == '-':
285 285 stream = sys.stdin
286 286 else:
287 287 try:
288 288 stream = open(fname)
289 289 except IOError as msg:
290 290 print(msg, file=sys.stderr)
291 291 sys.exit(1)
292 292
293 293 parser = Parser()
294 294
295 295 # we need nested try blocks because pre-2.5 python doesn't support unified
296 296 # try-except-finally
297 297 try:
298 298 try:
299 299 # write colorized version to stdout
300 300 parser.format(stream.read(),scheme=opts.scheme_name)
301 301 except IOError as msg:
302 302 # if user reads through a pager and quits, don't print traceback
303 303 if msg.args != (32,'Broken pipe'):
304 304 raise
305 305 finally:
306 306 if stream is not sys.stdin:
307 307 stream.close() # in case a non-handled exception happened above
308 308
309 309 if __name__ == "__main__":
310 310 main()
General Comments 0
You need to be logged in to leave comments. Login now