##// END OF EJS Templates
Merge pull request #8960 from sieben/simplify_comparison...
Min RK -
r21784:c0f6be23 merge
parent child Browse files
Show More
@@ -1,360 +1,360
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 Provides a reload() function that acts recursively.
3 Provides a reload() function that acts recursively.
4
4
5 Python's normal :func:`python:reload` function only reloads the module that it's
5 Python's normal :func:`python:reload` function only reloads the module that it's
6 passed. The :func:`reload` function in this module also reloads everything
6 passed. The :func:`reload` function in this module also reloads everything
7 imported from that module, which is useful when you're changing files deep
7 imported from that module, which is useful when you're changing files deep
8 inside a package.
8 inside a package.
9
9
10 To use this as your default reload function, type this for Python 2::
10 To use this as your default reload function, type this for Python 2::
11
11
12 import __builtin__
12 import __builtin__
13 from IPython.lib import deepreload
13 from IPython.lib import deepreload
14 __builtin__.reload = deepreload.reload
14 __builtin__.reload = deepreload.reload
15
15
16 Or this for Python 3::
16 Or this for Python 3::
17
17
18 import builtins
18 import builtins
19 from IPython.lib import deepreload
19 from IPython.lib import deepreload
20 builtins.reload = deepreload.reload
20 builtins.reload = deepreload.reload
21
21
22 A reference to the original :func:`python:reload` is stored in this module as
22 A reference to the original :func:`python:reload` is stored in this module as
23 :data:`original_reload`, so you can restore it later.
23 :data:`original_reload`, so you can restore it later.
24
24
25 This code is almost entirely based on knee.py, which is a Python
25 This code is almost entirely based on knee.py, which is a Python
26 re-implementation of hierarchical module import.
26 re-implementation of hierarchical module import.
27 """
27 """
28 from __future__ import print_function
28 from __future__ import print_function
29 #*****************************************************************************
29 #*****************************************************************************
30 # Copyright (C) 2001 Nathaniel Gray <n8gray@caltech.edu>
30 # Copyright (C) 2001 Nathaniel Gray <n8gray@caltech.edu>
31 #
31 #
32 # Distributed under the terms of the BSD License. The full license is in
32 # Distributed under the terms of the BSD License. The full license is in
33 # the file COPYING, distributed as part of this software.
33 # the file COPYING, distributed as part of this software.
34 #*****************************************************************************
34 #*****************************************************************************
35
35
36 from contextlib import contextmanager
36 from contextlib import contextmanager
37 import imp
37 import imp
38 import sys
38 import sys
39
39
40 from types import ModuleType
40 from types import ModuleType
41 from warnings import warn
41 from warnings import warn
42
42
43 from IPython.utils.py3compat import builtin_mod, builtin_mod_name
43 from IPython.utils.py3compat import builtin_mod, builtin_mod_name
44
44
45 original_import = builtin_mod.__import__
45 original_import = builtin_mod.__import__
46
46
47 @contextmanager
47 @contextmanager
48 def replace_import_hook(new_import):
48 def replace_import_hook(new_import):
49 saved_import = builtin_mod.__import__
49 saved_import = builtin_mod.__import__
50 builtin_mod.__import__ = new_import
50 builtin_mod.__import__ = new_import
51 try:
51 try:
52 yield
52 yield
53 finally:
53 finally:
54 builtin_mod.__import__ = saved_import
54 builtin_mod.__import__ = saved_import
55
55
56 def get_parent(globals, level):
56 def get_parent(globals, level):
57 """
57 """
58 parent, name = get_parent(globals, level)
58 parent, name = get_parent(globals, level)
59
59
60 Return the package that an import is being performed in. If globals comes
60 Return the package that an import is being performed in. If globals comes
61 from the module foo.bar.bat (not itself a package), this returns the
61 from the module foo.bar.bat (not itself a package), this returns the
62 sys.modules entry for foo.bar. If globals is from a package's __init__.py,
62 sys.modules entry for foo.bar. If globals is from a package's __init__.py,
63 the package's entry in sys.modules is returned.
63 the package's entry in sys.modules is returned.
64
64
65 If globals doesn't come from a package or a module in a package, or a
65 If globals doesn't come from a package or a module in a package, or a
66 corresponding entry is not found in sys.modules, None is returned.
66 corresponding entry is not found in sys.modules, None is returned.
67 """
67 """
68 orig_level = level
68 orig_level = level
69
69
70 if not level or not isinstance(globals, dict):
70 if not level or not isinstance(globals, dict):
71 return None, ''
71 return None, ''
72
72
73 pkgname = globals.get('__package__', None)
73 pkgname = globals.get('__package__', None)
74
74
75 if pkgname is not None:
75 if pkgname is not None:
76 # __package__ is set, so use it
76 # __package__ is set, so use it
77 if not hasattr(pkgname, 'rindex'):
77 if not hasattr(pkgname, 'rindex'):
78 raise ValueError('__package__ set to non-string')
78 raise ValueError('__package__ set to non-string')
79 if len(pkgname) == 0:
79 if len(pkgname) == 0:
80 if level > 0:
80 if level > 0:
81 raise ValueError('Attempted relative import in non-package')
81 raise ValueError('Attempted relative import in non-package')
82 return None, ''
82 return None, ''
83 name = pkgname
83 name = pkgname
84 else:
84 else:
85 # __package__ not set, so figure it out and set it
85 # __package__ not set, so figure it out and set it
86 if '__name__' not in globals:
86 if '__name__' not in globals:
87 return None, ''
87 return None, ''
88 modname = globals['__name__']
88 modname = globals['__name__']
89
89
90 if '__path__' in globals:
90 if '__path__' in globals:
91 # __path__ is set, so modname is already the package name
91 # __path__ is set, so modname is already the package name
92 globals['__package__'] = name = modname
92 globals['__package__'] = name = modname
93 else:
93 else:
94 # Normal module, so work out the package name if any
94 # Normal module, so work out the package name if any
95 lastdot = modname.rfind('.')
95 lastdot = modname.rfind('.')
96 if lastdot < 0 and level > 0:
96 if lastdot < 0 < level:
97 raise ValueError("Attempted relative import in non-package")
97 raise ValueError("Attempted relative import in non-package")
98 if lastdot < 0:
98 if lastdot < 0:
99 globals['__package__'] = None
99 globals['__package__'] = None
100 return None, ''
100 return None, ''
101 globals['__package__'] = name = modname[:lastdot]
101 globals['__package__'] = name = modname[:lastdot]
102
102
103 dot = len(name)
103 dot = len(name)
104 for x in range(level, 1, -1):
104 for x in range(level, 1, -1):
105 try:
105 try:
106 dot = name.rindex('.', 0, dot)
106 dot = name.rindex('.', 0, dot)
107 except ValueError:
107 except ValueError:
108 raise ValueError("attempted relative import beyond top-level "
108 raise ValueError("attempted relative import beyond top-level "
109 "package")
109 "package")
110 name = name[:dot]
110 name = name[:dot]
111
111
112 try:
112 try:
113 parent = sys.modules[name]
113 parent = sys.modules[name]
114 except:
114 except:
115 if orig_level < 1:
115 if orig_level < 1:
116 warn("Parent module '%.200s' not found while handling absolute "
116 warn("Parent module '%.200s' not found while handling absolute "
117 "import" % name)
117 "import" % name)
118 parent = None
118 parent = None
119 else:
119 else:
120 raise SystemError("Parent module '%.200s' not loaded, cannot "
120 raise SystemError("Parent module '%.200s' not loaded, cannot "
121 "perform relative import" % name)
121 "perform relative import" % name)
122
122
123 # We expect, but can't guarantee, if parent != None, that:
123 # We expect, but can't guarantee, if parent != None, that:
124 # - parent.__name__ == name
124 # - parent.__name__ == name
125 # - parent.__dict__ is globals
125 # - parent.__dict__ is globals
126 # If this is violated... Who cares?
126 # If this is violated... Who cares?
127 return parent, name
127 return parent, name
128
128
129 def load_next(mod, altmod, name, buf):
129 def load_next(mod, altmod, name, buf):
130 """
130 """
131 mod, name, buf = load_next(mod, altmod, name, buf)
131 mod, name, buf = load_next(mod, altmod, name, buf)
132
132
133 altmod is either None or same as mod
133 altmod is either None or same as mod
134 """
134 """
135
135
136 if len(name) == 0:
136 if len(name) == 0:
137 # completely empty module name should only happen in
137 # completely empty module name should only happen in
138 # 'from . import' (or '__import__("")')
138 # 'from . import' (or '__import__("")')
139 return mod, None, buf
139 return mod, None, buf
140
140
141 dot = name.find('.')
141 dot = name.find('.')
142 if dot == 0:
142 if dot == 0:
143 raise ValueError('Empty module name')
143 raise ValueError('Empty module name')
144
144
145 if dot < 0:
145 if dot < 0:
146 subname = name
146 subname = name
147 next = None
147 next = None
148 else:
148 else:
149 subname = name[:dot]
149 subname = name[:dot]
150 next = name[dot+1:]
150 next = name[dot+1:]
151
151
152 if buf != '':
152 if buf != '':
153 buf += '.'
153 buf += '.'
154 buf += subname
154 buf += subname
155
155
156 result = import_submodule(mod, subname, buf)
156 result = import_submodule(mod, subname, buf)
157 if result is None and mod != altmod:
157 if result is None and mod != altmod:
158 result = import_submodule(altmod, subname, subname)
158 result = import_submodule(altmod, subname, subname)
159 if result is not None:
159 if result is not None:
160 buf = subname
160 buf = subname
161
161
162 if result is None:
162 if result is None:
163 raise ImportError("No module named %.200s" % name)
163 raise ImportError("No module named %.200s" % name)
164
164
165 return result, next, buf
165 return result, next, buf
166
166
167 # Need to keep track of what we've already reloaded to prevent cyclic evil
167 # Need to keep track of what we've already reloaded to prevent cyclic evil
168 found_now = {}
168 found_now = {}
169
169
170 def import_submodule(mod, subname, fullname):
170 def import_submodule(mod, subname, fullname):
171 """m = import_submodule(mod, subname, fullname)"""
171 """m = import_submodule(mod, subname, fullname)"""
172 # Require:
172 # Require:
173 # if mod == None: subname == fullname
173 # if mod == None: subname == fullname
174 # else: mod.__name__ + "." + subname == fullname
174 # else: mod.__name__ + "." + subname == fullname
175
175
176 global found_now
176 global found_now
177 if fullname in found_now and fullname in sys.modules:
177 if fullname in found_now and fullname in sys.modules:
178 m = sys.modules[fullname]
178 m = sys.modules[fullname]
179 else:
179 else:
180 print('Reloading', fullname)
180 print('Reloading', fullname)
181 found_now[fullname] = 1
181 found_now[fullname] = 1
182 oldm = sys.modules.get(fullname, None)
182 oldm = sys.modules.get(fullname, None)
183
183
184 if mod is None:
184 if mod is None:
185 path = None
185 path = None
186 elif hasattr(mod, '__path__'):
186 elif hasattr(mod, '__path__'):
187 path = mod.__path__
187 path = mod.__path__
188 else:
188 else:
189 return None
189 return None
190
190
191 try:
191 try:
192 # This appears to be necessary on Python 3, because imp.find_module()
192 # This appears to be necessary on Python 3, because imp.find_module()
193 # tries to import standard libraries (like io) itself, and we don't
193 # tries to import standard libraries (like io) itself, and we don't
194 # want them to be processed by our deep_import_hook.
194 # want them to be processed by our deep_import_hook.
195 with replace_import_hook(original_import):
195 with replace_import_hook(original_import):
196 fp, filename, stuff = imp.find_module(subname, path)
196 fp, filename, stuff = imp.find_module(subname, path)
197 except ImportError:
197 except ImportError:
198 return None
198 return None
199
199
200 try:
200 try:
201 m = imp.load_module(fullname, fp, filename, stuff)
201 m = imp.load_module(fullname, fp, filename, stuff)
202 except:
202 except:
203 # load_module probably removed name from modules because of
203 # load_module probably removed name from modules because of
204 # the error. Put back the original module object.
204 # the error. Put back the original module object.
205 if oldm:
205 if oldm:
206 sys.modules[fullname] = oldm
206 sys.modules[fullname] = oldm
207 raise
207 raise
208 finally:
208 finally:
209 if fp: fp.close()
209 if fp: fp.close()
210
210
211 add_submodule(mod, m, fullname, subname)
211 add_submodule(mod, m, fullname, subname)
212
212
213 return m
213 return m
214
214
215 def add_submodule(mod, submod, fullname, subname):
215 def add_submodule(mod, submod, fullname, subname):
216 """mod.{subname} = submod"""
216 """mod.{subname} = submod"""
217 if mod is None:
217 if mod is None:
218 return #Nothing to do here.
218 return #Nothing to do here.
219
219
220 if submod is None:
220 if submod is None:
221 submod = sys.modules[fullname]
221 submod = sys.modules[fullname]
222
222
223 setattr(mod, subname, submod)
223 setattr(mod, subname, submod)
224
224
225 return
225 return
226
226
227 def ensure_fromlist(mod, fromlist, buf, recursive):
227 def ensure_fromlist(mod, fromlist, buf, recursive):
228 """Handle 'from module import a, b, c' imports."""
228 """Handle 'from module import a, b, c' imports."""
229 if not hasattr(mod, '__path__'):
229 if not hasattr(mod, '__path__'):
230 return
230 return
231 for item in fromlist:
231 for item in fromlist:
232 if not hasattr(item, 'rindex'):
232 if not hasattr(item, 'rindex'):
233 raise TypeError("Item in ``from list'' not a string")
233 raise TypeError("Item in ``from list'' not a string")
234 if item == '*':
234 if item == '*':
235 if recursive:
235 if recursive:
236 continue # avoid endless recursion
236 continue # avoid endless recursion
237 try:
237 try:
238 all = mod.__all__
238 all = mod.__all__
239 except AttributeError:
239 except AttributeError:
240 pass
240 pass
241 else:
241 else:
242 ret = ensure_fromlist(mod, all, buf, 1)
242 ret = ensure_fromlist(mod, all, buf, 1)
243 if not ret:
243 if not ret:
244 return 0
244 return 0
245 elif not hasattr(mod, item):
245 elif not hasattr(mod, item):
246 import_submodule(mod, item, buf + '.' + item)
246 import_submodule(mod, item, buf + '.' + item)
247
247
248 def deep_import_hook(name, globals=None, locals=None, fromlist=None, level=-1):
248 def deep_import_hook(name, globals=None, locals=None, fromlist=None, level=-1):
249 """Replacement for __import__()"""
249 """Replacement for __import__()"""
250 parent, buf = get_parent(globals, level)
250 parent, buf = get_parent(globals, level)
251
251
252 head, name, buf = load_next(parent, None if level < 0 else parent, name, buf)
252 head, name, buf = load_next(parent, None if level < 0 else parent, name, buf)
253
253
254 tail = head
254 tail = head
255 while name:
255 while name:
256 tail, name, buf = load_next(tail, tail, name, buf)
256 tail, name, buf = load_next(tail, tail, name, buf)
257
257
258 # If tail is None, both get_parent and load_next found
258 # If tail is None, both get_parent and load_next found
259 # an empty module name: someone called __import__("") or
259 # an empty module name: someone called __import__("") or
260 # doctored faulty bytecode
260 # doctored faulty bytecode
261 if tail is None:
261 if tail is None:
262 raise ValueError('Empty module name')
262 raise ValueError('Empty module name')
263
263
264 if not fromlist:
264 if not fromlist:
265 return head
265 return head
266
266
267 ensure_fromlist(tail, fromlist, buf, 0)
267 ensure_fromlist(tail, fromlist, buf, 0)
268 return tail
268 return tail
269
269
270 modules_reloading = {}
270 modules_reloading = {}
271
271
272 def deep_reload_hook(m):
272 def deep_reload_hook(m):
273 """Replacement for reload()."""
273 """Replacement for reload()."""
274 if not isinstance(m, ModuleType):
274 if not isinstance(m, ModuleType):
275 raise TypeError("reload() argument must be module")
275 raise TypeError("reload() argument must be module")
276
276
277 name = m.__name__
277 name = m.__name__
278
278
279 if name not in sys.modules:
279 if name not in sys.modules:
280 raise ImportError("reload(): module %.200s not in sys.modules" % name)
280 raise ImportError("reload(): module %.200s not in sys.modules" % name)
281
281
282 global modules_reloading
282 global modules_reloading
283 try:
283 try:
284 return modules_reloading[name]
284 return modules_reloading[name]
285 except:
285 except:
286 modules_reloading[name] = m
286 modules_reloading[name] = m
287
287
288 dot = name.rfind('.')
288 dot = name.rfind('.')
289 if dot < 0:
289 if dot < 0:
290 subname = name
290 subname = name
291 path = None
291 path = None
292 else:
292 else:
293 try:
293 try:
294 parent = sys.modules[name[:dot]]
294 parent = sys.modules[name[:dot]]
295 except KeyError:
295 except KeyError:
296 modules_reloading.clear()
296 modules_reloading.clear()
297 raise ImportError("reload(): parent %.200s not in sys.modules" % name[:dot])
297 raise ImportError("reload(): parent %.200s not in sys.modules" % name[:dot])
298 subname = name[dot+1:]
298 subname = name[dot+1:]
299 path = getattr(parent, "__path__", None)
299 path = getattr(parent, "__path__", None)
300
300
301 try:
301 try:
302 # This appears to be necessary on Python 3, because imp.find_module()
302 # This appears to be necessary on Python 3, because imp.find_module()
303 # tries to import standard libraries (like io) itself, and we don't
303 # tries to import standard libraries (like io) itself, and we don't
304 # want them to be processed by our deep_import_hook.
304 # want them to be processed by our deep_import_hook.
305 with replace_import_hook(original_import):
305 with replace_import_hook(original_import):
306 fp, filename, stuff = imp.find_module(subname, path)
306 fp, filename, stuff = imp.find_module(subname, path)
307 finally:
307 finally:
308 modules_reloading.clear()
308 modules_reloading.clear()
309
309
310 try:
310 try:
311 newm = imp.load_module(name, fp, filename, stuff)
311 newm = imp.load_module(name, fp, filename, stuff)
312 except:
312 except:
313 # load_module probably removed name from modules because of
313 # load_module probably removed name from modules because of
314 # the error. Put back the original module object.
314 # the error. Put back the original module object.
315 sys.modules[name] = m
315 sys.modules[name] = m
316 raise
316 raise
317 finally:
317 finally:
318 if fp: fp.close()
318 if fp: fp.close()
319
319
320 modules_reloading.clear()
320 modules_reloading.clear()
321 return newm
321 return newm
322
322
323 # Save the original hooks
323 # Save the original hooks
324 try:
324 try:
325 original_reload = builtin_mod.reload
325 original_reload = builtin_mod.reload
326 except AttributeError:
326 except AttributeError:
327 original_reload = imp.reload # Python 3
327 original_reload = imp.reload # Python 3
328
328
329 # Replacement for reload()
329 # Replacement for reload()
330 def reload(module, exclude=('sys', 'os.path', builtin_mod_name, '__main__')):
330 def reload(module, exclude=('sys', 'os.path', builtin_mod_name, '__main__')):
331 """Recursively reload all modules used in the given module. Optionally
331 """Recursively reload all modules used in the given module. Optionally
332 takes a list of modules to exclude from reloading. The default exclude
332 takes a list of modules to exclude from reloading. The default exclude
333 list contains sys, __main__, and __builtin__, to prevent, e.g., resetting
333 list contains sys, __main__, and __builtin__, to prevent, e.g., resetting
334 display, exception, and io hooks.
334 display, exception, and io hooks.
335 """
335 """
336 global found_now
336 global found_now
337 for i in exclude:
337 for i in exclude:
338 found_now[i] = 1
338 found_now[i] = 1
339 try:
339 try:
340 with replace_import_hook(deep_import_hook):
340 with replace_import_hook(deep_import_hook):
341 return deep_reload_hook(module)
341 return deep_reload_hook(module)
342 finally:
342 finally:
343 found_now = {}
343 found_now = {}
344
344
345
345
346 def _dreload(module, **kwargs):
346 def _dreload(module, **kwargs):
347 """
347 """
348 **deprecated**
348 **deprecated**
349
349
350 import reload explicitly from `IPython.lib.deepreload` to use it
350 import reload explicitly from `IPython.lib.deepreload` to use it
351
351
352 """
352 """
353 warn("""
353 warn("""
354 injecting `dreload` in interactive namespace is deprecated, please import `reload` explicitly from `IPython.lib.deepreload`
354 injecting `dreload` in interactive namespace is deprecated, please import `reload` explicitly from `IPython.lib.deepreload`
355 """, DeprecationWarning, stacklevel=2)
355 """, DeprecationWarning, stacklevel=2)
356 reload(module, **kwargs)
356 reload(module, **kwargs)
357
357
358 # Uncomment the following to automatically activate deep reloading whenever
358 # Uncomment the following to automatically activate deep reloading whenever
359 # this module is imported
359 # this module is imported
360 #builtin_mod.reload = reload
360 #builtin_mod.reload = reload
@@ -1,315 +1,315
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 Class and program to colorize python source code for ANSI terminals.
3 Class and program to colorize python source code for ANSI terminals.
4
4
5 Based on an HTML code highlighter by Jurgen Hermann found at:
5 Based on an HTML code highlighter by Jurgen Hermann found at:
6 http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52298
6 http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52298
7
7
8 Modifications by Fernando Perez (fperez@colorado.edu).
8 Modifications by Fernando Perez (fperez@colorado.edu).
9
9
10 Information on the original HTML highlighter follows:
10 Information on the original HTML highlighter follows:
11
11
12 MoinMoin - Python Source Parser
12 MoinMoin - Python Source Parser
13
13
14 Title: Colorize Python source using the built-in tokenizer
14 Title: Colorize Python source using the built-in tokenizer
15
15
16 Submitter: Jurgen Hermann
16 Submitter: Jurgen Hermann
17 Last Updated:2001/04/06
17 Last Updated:2001/04/06
18
18
19 Version no:1.2
19 Version no:1.2
20
20
21 Description:
21 Description:
22
22
23 This code is part of MoinMoin (http://moin.sourceforge.net/) and converts
23 This code is part of MoinMoin (http://moin.sourceforge.net/) and converts
24 Python source code to HTML markup, rendering comments, keywords,
24 Python source code to HTML markup, rendering comments, keywords,
25 operators, numeric and string literals in different colors.
25 operators, numeric and string literals in different colors.
26
26
27 It shows how to use the built-in keyword, token and tokenize modules to
27 It shows how to use the built-in keyword, token and tokenize modules to
28 scan Python source code and re-emit it with no changes to its original
28 scan Python source code and re-emit it with no changes to its original
29 formatting (which is the hard part).
29 formatting (which is the hard part).
30 """
30 """
31 from __future__ import print_function
31 from __future__ import print_function
32 from __future__ import absolute_import
32 from __future__ import absolute_import
33 from __future__ import unicode_literals
33 from __future__ import unicode_literals
34
34
35 __all__ = ['ANSICodeColors','Parser']
35 __all__ = ['ANSICodeColors','Parser']
36
36
37 _scheme_default = 'Linux'
37 _scheme_default = 'Linux'
38
38
39
39
40 # Imports
40 # Imports
41 import keyword
41 import keyword
42 import os
42 import os
43 import sys
43 import sys
44 import token
44 import token
45 import tokenize
45 import tokenize
46
46
47 try:
47 try:
48 generate_tokens = tokenize.generate_tokens
48 generate_tokens = tokenize.generate_tokens
49 except AttributeError:
49 except AttributeError:
50 # Python 3. Note that we use the undocumented _tokenize because it expects
50 # Python 3. Note that we use the undocumented _tokenize because it expects
51 # strings, not bytes. See also Python issue #9969.
51 # strings, not bytes. See also Python issue #9969.
52 generate_tokens = tokenize._tokenize
52 generate_tokens = tokenize._tokenize
53
53
54 from IPython.utils.coloransi import *
54 from IPython.utils.coloransi import *
55 from IPython.utils.py3compat import PY3
55 from IPython.utils.py3compat import PY3
56
56
57 if PY3:
57 if PY3:
58 from io import StringIO
58 from io import StringIO
59 else:
59 else:
60 from StringIO import StringIO
60 from StringIO import StringIO
61
61
62 #############################################################################
62 #############################################################################
63 ### Python Source Parser (does Hilighting)
63 ### Python Source Parser (does Hilighting)
64 #############################################################################
64 #############################################################################
65
65
66 _KEYWORD = token.NT_OFFSET + 1
66 _KEYWORD = token.NT_OFFSET + 1
67 _TEXT = token.NT_OFFSET + 2
67 _TEXT = token.NT_OFFSET + 2
68
68
69 #****************************************************************************
69 #****************************************************************************
70 # Builtin color schemes
70 # Builtin color schemes
71
71
72 Colors = TermColors # just a shorthand
72 Colors = TermColors # just a shorthand
73
73
74 # Build a few color schemes
74 # Build a few color schemes
75 NoColor = ColorScheme(
75 NoColor = ColorScheme(
76 'NoColor',{
76 'NoColor',{
77 token.NUMBER : Colors.NoColor,
77 token.NUMBER : Colors.NoColor,
78 token.OP : Colors.NoColor,
78 token.OP : Colors.NoColor,
79 token.STRING : Colors.NoColor,
79 token.STRING : Colors.NoColor,
80 tokenize.COMMENT : Colors.NoColor,
80 tokenize.COMMENT : Colors.NoColor,
81 token.NAME : Colors.NoColor,
81 token.NAME : Colors.NoColor,
82 token.ERRORTOKEN : Colors.NoColor,
82 token.ERRORTOKEN : Colors.NoColor,
83
83
84 _KEYWORD : Colors.NoColor,
84 _KEYWORD : Colors.NoColor,
85 _TEXT : Colors.NoColor,
85 _TEXT : Colors.NoColor,
86
86
87 'normal' : Colors.NoColor # color off (usu. Colors.Normal)
87 'normal' : Colors.NoColor # color off (usu. Colors.Normal)
88 } )
88 } )
89
89
90 LinuxColors = ColorScheme(
90 LinuxColors = ColorScheme(
91 'Linux',{
91 'Linux',{
92 token.NUMBER : Colors.LightCyan,
92 token.NUMBER : Colors.LightCyan,
93 token.OP : Colors.Yellow,
93 token.OP : Colors.Yellow,
94 token.STRING : Colors.LightBlue,
94 token.STRING : Colors.LightBlue,
95 tokenize.COMMENT : Colors.LightRed,
95 tokenize.COMMENT : Colors.LightRed,
96 token.NAME : Colors.Normal,
96 token.NAME : Colors.Normal,
97 token.ERRORTOKEN : Colors.Red,
97 token.ERRORTOKEN : Colors.Red,
98
98
99 _KEYWORD : Colors.LightGreen,
99 _KEYWORD : Colors.LightGreen,
100 _TEXT : Colors.Yellow,
100 _TEXT : Colors.Yellow,
101
101
102 'normal' : Colors.Normal # color off (usu. Colors.Normal)
102 'normal' : Colors.Normal # color off (usu. Colors.Normal)
103 } )
103 } )
104
104
105 LightBGColors = ColorScheme(
105 LightBGColors = ColorScheme(
106 'LightBG',{
106 'LightBG',{
107 token.NUMBER : Colors.Cyan,
107 token.NUMBER : Colors.Cyan,
108 token.OP : Colors.Blue,
108 token.OP : Colors.Blue,
109 token.STRING : Colors.Blue,
109 token.STRING : Colors.Blue,
110 tokenize.COMMENT : Colors.Red,
110 tokenize.COMMENT : Colors.Red,
111 token.NAME : Colors.Normal,
111 token.NAME : Colors.Normal,
112 token.ERRORTOKEN : Colors.Red,
112 token.ERRORTOKEN : Colors.Red,
113
113
114 _KEYWORD : Colors.Green,
114 _KEYWORD : Colors.Green,
115 _TEXT : Colors.Blue,
115 _TEXT : Colors.Blue,
116
116
117 'normal' : Colors.Normal # color off (usu. Colors.Normal)
117 'normal' : Colors.Normal # color off (usu. Colors.Normal)
118 } )
118 } )
119
119
120 # Build table of color schemes (needed by the parser)
120 # Build table of color schemes (needed by the parser)
121 ANSICodeColors = ColorSchemeTable([NoColor,LinuxColors,LightBGColors],
121 ANSICodeColors = ColorSchemeTable([NoColor,LinuxColors,LightBGColors],
122 _scheme_default)
122 _scheme_default)
123
123
124 class Parser:
124 class Parser:
125 """ Format colored Python source.
125 """ Format colored Python source.
126 """
126 """
127
127
128 def __init__(self, color_table=None,out = sys.stdout):
128 def __init__(self, color_table=None,out = sys.stdout):
129 """ Create a parser with a specified color table and output channel.
129 """ Create a parser with a specified color table and output channel.
130
130
131 Call format() to process code.
131 Call format() to process code.
132 """
132 """
133 self.color_table = color_table and color_table or ANSICodeColors
133 self.color_table = color_table and color_table or ANSICodeColors
134 self.out = out
134 self.out = out
135
135
136 def format(self, raw, out = None, scheme = ''):
136 def format(self, raw, out = None, scheme = ''):
137 return self.format2(raw, out, scheme)[0]
137 return self.format2(raw, out, scheme)[0]
138
138
139 def format2(self, raw, out = None, scheme = ''):
139 def format2(self, raw, out = None, scheme = ''):
140 """ Parse and send the colored source.
140 """ Parse and send the colored source.
141
141
142 If out and scheme are not specified, the defaults (given to
142 If out and scheme are not specified, the defaults (given to
143 constructor) are used.
143 constructor) are used.
144
144
145 out should be a file-type object. Optionally, out can be given as the
145 out should be a file-type object. Optionally, out can be given as the
146 string 'str' and the parser will automatically return the output in a
146 string 'str' and the parser will automatically return the output in a
147 string."""
147 string."""
148
148
149 string_output = 0
149 string_output = 0
150 if out == 'str' or self.out == 'str' or \
150 if out == 'str' or self.out == 'str' or \
151 isinstance(self.out,StringIO):
151 isinstance(self.out,StringIO):
152 # XXX - I don't really like this state handling logic, but at this
152 # XXX - I don't really like this state handling logic, but at this
153 # point I don't want to make major changes, so adding the
153 # point I don't want to make major changes, so adding the
154 # isinstance() check is the simplest I can do to ensure correct
154 # isinstance() check is the simplest I can do to ensure correct
155 # behavior.
155 # behavior.
156 out_old = self.out
156 out_old = self.out
157 self.out = StringIO()
157 self.out = StringIO()
158 string_output = 1
158 string_output = 1
159 elif out is not None:
159 elif out is not None:
160 self.out = out
160 self.out = out
161
161
162 # Fast return of the unmodified input for NoColor scheme
162 # Fast return of the unmodified input for NoColor scheme
163 if scheme == 'NoColor':
163 if scheme == 'NoColor':
164 error = False
164 error = False
165 self.out.write(raw)
165 self.out.write(raw)
166 if string_output:
166 if string_output:
167 return raw,error
167 return raw,error
168 else:
168 else:
169 return None,error
169 return None,error
170
170
171 # local shorthands
171 # local shorthands
172 colors = self.color_table[scheme].colors
172 colors = self.color_table[scheme].colors
173 self.colors = colors # put in object so __call__ sees it
173 self.colors = colors # put in object so __call__ sees it
174
174
175 # Remove trailing whitespace and normalize tabs
175 # Remove trailing whitespace and normalize tabs
176 self.raw = raw.expandtabs().rstrip()
176 self.raw = raw.expandtabs().rstrip()
177
177
178 # store line offsets in self.lines
178 # store line offsets in self.lines
179 self.lines = [0, 0]
179 self.lines = [0, 0]
180 pos = 0
180 pos = 0
181 raw_find = self.raw.find
181 raw_find = self.raw.find
182 lines_append = self.lines.append
182 lines_append = self.lines.append
183 while 1:
183 while 1:
184 pos = raw_find('\n', pos) + 1
184 pos = raw_find('\n', pos) + 1
185 if not pos: break
185 if not pos: break
186 lines_append(pos)
186 lines_append(pos)
187 lines_append(len(self.raw))
187 lines_append(len(self.raw))
188
188
189 # parse the source and write it
189 # parse the source and write it
190 self.pos = 0
190 self.pos = 0
191 text = StringIO(self.raw)
191 text = StringIO(self.raw)
192
192
193 error = False
193 error = False
194 try:
194 try:
195 for atoken in generate_tokens(text.readline):
195 for atoken in generate_tokens(text.readline):
196 self(*atoken)
196 self(*atoken)
197 except tokenize.TokenError as ex:
197 except tokenize.TokenError as ex:
198 msg = ex.args[0]
198 msg = ex.args[0]
199 line = ex.args[1][0]
199 line = ex.args[1][0]
200 self.out.write("%s\n\n*** ERROR: %s%s%s\n" %
200 self.out.write("%s\n\n*** ERROR: %s%s%s\n" %
201 (colors[token.ERRORTOKEN],
201 (colors[token.ERRORTOKEN],
202 msg, self.raw[self.lines[line]:],
202 msg, self.raw[self.lines[line]:],
203 colors.normal)
203 colors.normal)
204 )
204 )
205 error = True
205 error = True
206 self.out.write(colors.normal+'\n')
206 self.out.write(colors.normal+'\n')
207 if string_output:
207 if string_output:
208 output = self.out.getvalue()
208 output = self.out.getvalue()
209 self.out = out_old
209 self.out = out_old
210 return (output, error)
210 return (output, error)
211 return (None, error)
211 return (None, error)
212
212
213 def __call__(self, toktype, toktext, start_pos, end_pos, line):
213 def __call__(self, toktype, toktext, start_pos, end_pos, line):
214 """ Token handler, with syntax highlighting."""
214 """ Token handler, with syntax highlighting."""
215 (srow,scol) = start_pos
215 (srow,scol) = start_pos
216 (erow,ecol) = end_pos
216 (erow,ecol) = end_pos
217 colors = self.colors
217 colors = self.colors
218 owrite = self.out.write
218 owrite = self.out.write
219
219
220 # line separator, so this works across platforms
220 # line separator, so this works across platforms
221 linesep = os.linesep
221 linesep = os.linesep
222
222
223 # calculate new positions
223 # calculate new positions
224 oldpos = self.pos
224 oldpos = self.pos
225 newpos = self.lines[srow] + scol
225 newpos = self.lines[srow] + scol
226 self.pos = newpos + len(toktext)
226 self.pos = newpos + len(toktext)
227
227
228 # send the original whitespace, if needed
228 # send the original whitespace, if needed
229 if newpos > oldpos:
229 if newpos > oldpos:
230 owrite(self.raw[oldpos:newpos])
230 owrite(self.raw[oldpos:newpos])
231
231
232 # skip indenting tokens
232 # skip indenting tokens
233 if toktype in [token.INDENT, token.DEDENT]:
233 if toktype in [token.INDENT, token.DEDENT]:
234 self.pos = newpos
234 self.pos = newpos
235 return
235 return
236
236
237 # map token type to a color group
237 # map token type to a color group
238 if token.LPAR <= toktype and toktype <= token.OP:
238 if token.LPAR <= toktype <= token.OP:
239 toktype = token.OP
239 toktype = token.OP
240 elif toktype == token.NAME and keyword.iskeyword(toktext):
240 elif toktype == token.NAME and keyword.iskeyword(toktext):
241 toktype = _KEYWORD
241 toktype = _KEYWORD
242 color = colors.get(toktype, colors[_TEXT])
242 color = colors.get(toktype, colors[_TEXT])
243
243
244 #print '<%s>' % toktext, # dbg
244 #print '<%s>' % toktext, # dbg
245
245
246 # Triple quoted strings must be handled carefully so that backtracking
246 # Triple quoted strings must be handled carefully so that backtracking
247 # in pagers works correctly. We need color terminators on _each_ line.
247 # in pagers works correctly. We need color terminators on _each_ line.
248 if linesep in toktext:
248 if linesep in toktext:
249 toktext = toktext.replace(linesep, '%s%s%s' %
249 toktext = toktext.replace(linesep, '%s%s%s' %
250 (colors.normal,linesep,color))
250 (colors.normal,linesep,color))
251
251
252 # send text
252 # send text
253 owrite('%s%s%s' % (color,toktext,colors.normal))
253 owrite('%s%s%s' % (color,toktext,colors.normal))
254
254
255 def main(argv=None):
255 def main(argv=None):
256 """Run as a command-line script: colorize a python file or stdin using ANSI
256 """Run as a command-line script: colorize a python file or stdin using ANSI
257 color escapes and print to stdout.
257 color escapes and print to stdout.
258
258
259 Inputs:
259 Inputs:
260
260
261 - argv(None): a list of strings like sys.argv[1:] giving the command-line
261 - argv(None): a list of strings like sys.argv[1:] giving the command-line
262 arguments. If None, use sys.argv[1:].
262 arguments. If None, use sys.argv[1:].
263 """
263 """
264
264
265 usage_msg = """%prog [options] [filename]
265 usage_msg = """%prog [options] [filename]
266
266
267 Colorize a python file or stdin using ANSI color escapes and print to stdout.
267 Colorize a python file or stdin using ANSI color escapes and print to stdout.
268 If no filename is given, or if filename is -, read standard input."""
268 If no filename is given, or if filename is -, read standard input."""
269
269
270 import optparse
270 import optparse
271 parser = optparse.OptionParser(usage=usage_msg)
271 parser = optparse.OptionParser(usage=usage_msg)
272 newopt = parser.add_option
272 newopt = parser.add_option
273 newopt('-s','--scheme',metavar='NAME',dest='scheme_name',action='store',
273 newopt('-s','--scheme',metavar='NAME',dest='scheme_name',action='store',
274 choices=['Linux','LightBG','NoColor'],default=_scheme_default,
274 choices=['Linux','LightBG','NoColor'],default=_scheme_default,
275 help="give the color scheme to use. Currently only 'Linux'\
275 help="give the color scheme to use. Currently only 'Linux'\
276 (default) and 'LightBG' and 'NoColor' are implemented (give without\
276 (default) and 'LightBG' and 'NoColor' are implemented (give without\
277 quotes)")
277 quotes)")
278
278
279 opts,args = parser.parse_args(argv)
279 opts,args = parser.parse_args(argv)
280
280
281 if len(args) > 1:
281 if len(args) > 1:
282 parser.error("you must give at most one filename.")
282 parser.error("you must give at most one filename.")
283
283
284 if len(args) == 0:
284 if len(args) == 0:
285 fname = '-' # no filename given; setup to read from stdin
285 fname = '-' # no filename given; setup to read from stdin
286 else:
286 else:
287 fname = args[0]
287 fname = args[0]
288
288
289 if fname == '-':
289 if fname == '-':
290 stream = sys.stdin
290 stream = sys.stdin
291 else:
291 else:
292 try:
292 try:
293 stream = open(fname)
293 stream = open(fname)
294 except IOError as msg:
294 except IOError as msg:
295 print(msg, file=sys.stderr)
295 print(msg, file=sys.stderr)
296 sys.exit(1)
296 sys.exit(1)
297
297
298 parser = Parser()
298 parser = Parser()
299
299
300 # we need nested try blocks because pre-2.5 python doesn't support unified
300 # we need nested try blocks because pre-2.5 python doesn't support unified
301 # try-except-finally
301 # try-except-finally
302 try:
302 try:
303 try:
303 try:
304 # write colorized version to stdout
304 # write colorized version to stdout
305 parser.format(stream.read(),scheme=opts.scheme_name)
305 parser.format(stream.read(),scheme=opts.scheme_name)
306 except IOError as msg:
306 except IOError as msg:
307 # if user reads through a pager and quits, don't print traceback
307 # if user reads through a pager and quits, don't print traceback
308 if msg.args != (32,'Broken pipe'):
308 if msg.args != (32,'Broken pipe'):
309 raise
309 raise
310 finally:
310 finally:
311 if stream is not sys.stdin:
311 if stream is not sys.stdin:
312 stream.close() # in case a non-handled exception happened above
312 stream.close() # in case a non-handled exception happened above
313
313
314 if __name__ == "__main__":
314 if __name__ == "__main__":
315 main()
315 main()
General Comments 0
You need to be logged in to leave comments. Login now