##// END OF EJS Templates
Merge pull request #9037 from tayfun/linux-term-colors...
Min RK -
r21832:3e9017c2 merge
parent child Browse files
Show More
@@ -1,416 +1,416 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Classes for handling input/output prompts.
2 """Classes for handling input/output prompts.
3
3
4 Authors:
4 Authors:
5
5
6 * Fernando Perez
6 * Fernando Perez
7 * Brian Granger
7 * Brian Granger
8 * Thomas Kluyver
8 * Thomas Kluyver
9 """
9 """
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Copyright (C) 2008-2011 The IPython Development Team
12 # Copyright (C) 2008-2011 The IPython Development Team
13 # Copyright (C) 2001-2007 Fernando Perez <fperez@colorado.edu>
13 # Copyright (C) 2001-2007 Fernando Perez <fperez@colorado.edu>
14 #
14 #
15 # Distributed under the terms of the BSD License. The full license is in
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
16 # the file COPYING, distributed as part of this software.
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Imports
20 # Imports
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 import os
23 import os
24 import re
24 import re
25 import socket
25 import socket
26 import sys
26 import sys
27 import time
27 import time
28
28
29 from string import Formatter
29 from string import Formatter
30
30
31 from traitlets.config.configurable import Configurable
31 from traitlets.config.configurable import Configurable
32 from IPython.core import release
32 from IPython.core import release
33 from IPython.utils import coloransi, py3compat
33 from IPython.utils import coloransi, py3compat
34 from traitlets import (Unicode, Instance, Dict, Bool, Int)
34 from traitlets import (Unicode, Instance, Dict, Bool, Int)
35
35
36 from IPython.utils.PyColorize import LightBGColors, LinuxColors, NoColor
36 from IPython.utils.PyColorize import LightBGColors, LinuxColors, NoColor
37
37
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39 # Color schemes for prompts
39 # Color schemes for prompts
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41
41
42 InputColors = coloransi.InputTermColors # just a shorthand
42 InputColors = coloransi.InputTermColors # just a shorthand
43 Colors = coloransi.TermColors # just a shorthand
43 Colors = coloransi.TermColors # just a shorthand
44
44
45 color_lists = dict(normal=Colors(), inp=InputColors(), nocolor=coloransi.NoColors())
45 color_lists = dict(normal=Colors(), inp=InputColors(), nocolor=coloransi.NoColors())
46
46
47 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
48 # Utilities
48 # Utilities
49 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
50
50
51 class LazyEvaluate(object):
51 class LazyEvaluate(object):
52 """This is used for formatting strings with values that need to be updated
52 """This is used for formatting strings with values that need to be updated
53 at that time, such as the current time or working directory."""
53 at that time, such as the current time or working directory."""
54 def __init__(self, func, *args, **kwargs):
54 def __init__(self, func, *args, **kwargs):
55 self.func = func
55 self.func = func
56 self.args = args
56 self.args = args
57 self.kwargs = kwargs
57 self.kwargs = kwargs
58
58
59 def __call__(self, **kwargs):
59 def __call__(self, **kwargs):
60 self.kwargs.update(kwargs)
60 self.kwargs.update(kwargs)
61 return self.func(*self.args, **self.kwargs)
61 return self.func(*self.args, **self.kwargs)
62
62
63 def __str__(self):
63 def __str__(self):
64 return str(self())
64 return str(self())
65
65
66 def __unicode__(self):
66 def __unicode__(self):
67 return py3compat.unicode_type(self())
67 return py3compat.unicode_type(self())
68
68
69 def __format__(self, format_spec):
69 def __format__(self, format_spec):
70 return format(self(), format_spec)
70 return format(self(), format_spec)
71
71
72 def multiple_replace(dict, text):
72 def multiple_replace(dict, text):
73 """ Replace in 'text' all occurrences of any key in the given
73 """ Replace in 'text' all occurrences of any key in the given
74 dictionary by its corresponding value. Returns the new string."""
74 dictionary by its corresponding value. Returns the new string."""
75
75
76 # Function by Xavier Defrang, originally found at:
76 # Function by Xavier Defrang, originally found at:
77 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81330
77 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81330
78
78
79 # Create a regular expression from the dictionary keys
79 # Create a regular expression from the dictionary keys
80 regex = re.compile("(%s)" % "|".join(map(re.escape, dict.keys())))
80 regex = re.compile("(%s)" % "|".join(map(re.escape, dict.keys())))
81 # For each match, look-up corresponding value in dictionary
81 # For each match, look-up corresponding value in dictionary
82 return regex.sub(lambda mo: dict[mo.string[mo.start():mo.end()]], text)
82 return regex.sub(lambda mo: dict[mo.string[mo.start():mo.end()]], text)
83
83
84 #-----------------------------------------------------------------------------
84 #-----------------------------------------------------------------------------
85 # Special characters that can be used in prompt templates, mainly bash-like
85 # Special characters that can be used in prompt templates, mainly bash-like
86 #-----------------------------------------------------------------------------
86 #-----------------------------------------------------------------------------
87
87
88 # If $HOME isn't defined (Windows), make it an absurd string so that it can
88 # If $HOME isn't defined (Windows), make it an absurd string so that it can
89 # never be expanded out into '~'. Basically anything which can never be a
89 # never be expanded out into '~'. Basically anything which can never be a
90 # reasonable directory name will do, we just want the $HOME -> '~' operation
90 # reasonable directory name will do, we just want the $HOME -> '~' operation
91 # to become a no-op. We pre-compute $HOME here so it's not done on every
91 # to become a no-op. We pre-compute $HOME here so it's not done on every
92 # prompt call.
92 # prompt call.
93
93
94 # FIXME:
94 # FIXME:
95
95
96 # - This should be turned into a class which does proper namespace management,
96 # - This should be turned into a class which does proper namespace management,
97 # since the prompt specials need to be evaluated in a certain namespace.
97 # since the prompt specials need to be evaluated in a certain namespace.
98 # Currently it's just globals, which need to be managed manually by code
98 # Currently it's just globals, which need to be managed manually by code
99 # below.
99 # below.
100
100
101 # - I also need to split up the color schemes from the prompt specials
101 # - I also need to split up the color schemes from the prompt specials
102 # somehow. I don't have a clean design for that quite yet.
102 # somehow. I don't have a clean design for that quite yet.
103
103
104 HOME = py3compat.str_to_unicode(os.environ.get("HOME","//////:::::ZZZZZ,,,~~~"))
104 HOME = py3compat.str_to_unicode(os.environ.get("HOME","//////:::::ZZZZZ,,,~~~"))
105
105
106 # This is needed on FreeBSD, and maybe other systems which symlink /home to
106 # This is needed on FreeBSD, and maybe other systems which symlink /home to
107 # /usr/home, but retain the $HOME variable as pointing to /home
107 # /usr/home, but retain the $HOME variable as pointing to /home
108 HOME = os.path.realpath(HOME)
108 HOME = os.path.realpath(HOME)
109
109
110 # We precompute a few more strings here for the prompt_specials, which are
110 # We precompute a few more strings here for the prompt_specials, which are
111 # fixed once ipython starts. This reduces the runtime overhead of computing
111 # fixed once ipython starts. This reduces the runtime overhead of computing
112 # prompt strings.
112 # prompt strings.
113 USER = py3compat.str_to_unicode(os.environ.get("USER",''))
113 USER = py3compat.str_to_unicode(os.environ.get("USER",''))
114 HOSTNAME = py3compat.str_to_unicode(socket.gethostname())
114 HOSTNAME = py3compat.str_to_unicode(socket.gethostname())
115 HOSTNAME_SHORT = HOSTNAME.split(".")[0]
115 HOSTNAME_SHORT = HOSTNAME.split(".")[0]
116
116
117 # IronPython doesn't currently have os.getuid() even if
117 # IronPython doesn't currently have os.getuid() even if
118 # os.name == 'posix'; 2/8/2014
118 # os.name == 'posix'; 2/8/2014
119 ROOT_SYMBOL = "#" if (os.name=='nt' or sys.platform=='cli' or os.getuid()==0) else "$"
119 ROOT_SYMBOL = "#" if (os.name=='nt' or sys.platform=='cli' or os.getuid()==0) else "$"
120
120
121 prompt_abbreviations = {
121 prompt_abbreviations = {
122 # Prompt/history count
122 # Prompt/history count
123 '%n' : '{color.number}' '{count}' '{color.prompt}',
123 '%n' : '{color.number}' '{count}' '{color.prompt}',
124 r'\#': '{color.number}' '{count}' '{color.prompt}',
124 r'\#': '{color.number}' '{count}' '{color.prompt}',
125 # Just the prompt counter number, WITHOUT any coloring wrappers, so users
125 # Just the prompt counter number, WITHOUT any coloring wrappers, so users
126 # can get numbers displayed in whatever color they want.
126 # can get numbers displayed in whatever color they want.
127 r'\N': '{count}',
127 r'\N': '{count}',
128
128
129 # Prompt/history count, with the actual digits replaced by dots or
129 # Prompt/history count, with the actual digits replaced by dots or
130 # spaces. Used mainly in continuation prompts (prompt_in2).
130 # spaces. Used mainly in continuation prompts (prompt_in2).
131 r'\D': '{dots}',
131 r'\D': '{dots}',
132 r'\S': '{spaces}',
132 r'\S': '{spaces}',
133
133
134 # Current time
134 # Current time
135 r'\T' : '{time}',
135 r'\T' : '{time}',
136 # Current working directory
136 # Current working directory
137 r'\w': '{cwd}',
137 r'\w': '{cwd}',
138 # Basename of current working directory.
138 # Basename of current working directory.
139 # (use os.sep to make this portable across OSes)
139 # (use os.sep to make this portable across OSes)
140 r'\W' : '{cwd_last}',
140 r'\W' : '{cwd_last}',
141 # These X<N> are an extension to the normal bash prompts. They return
141 # These X<N> are an extension to the normal bash prompts. They return
142 # N terms of the path, after replacing $HOME with '~'
142 # N terms of the path, after replacing $HOME with '~'
143 r'\X0': '{cwd_x[0]}',
143 r'\X0': '{cwd_x[0]}',
144 r'\X1': '{cwd_x[1]}',
144 r'\X1': '{cwd_x[1]}',
145 r'\X2': '{cwd_x[2]}',
145 r'\X2': '{cwd_x[2]}',
146 r'\X3': '{cwd_x[3]}',
146 r'\X3': '{cwd_x[3]}',
147 r'\X4': '{cwd_x[4]}',
147 r'\X4': '{cwd_x[4]}',
148 r'\X5': '{cwd_x[5]}',
148 r'\X5': '{cwd_x[5]}',
149 # Y<N> are similar to X<N>, but they show '~' if it's the directory
149 # Y<N> are similar to X<N>, but they show '~' if it's the directory
150 # N+1 in the list. Somewhat like %cN in tcsh.
150 # N+1 in the list. Somewhat like %cN in tcsh.
151 r'\Y0': '{cwd_y[0]}',
151 r'\Y0': '{cwd_y[0]}',
152 r'\Y1': '{cwd_y[1]}',
152 r'\Y1': '{cwd_y[1]}',
153 r'\Y2': '{cwd_y[2]}',
153 r'\Y2': '{cwd_y[2]}',
154 r'\Y3': '{cwd_y[3]}',
154 r'\Y3': '{cwd_y[3]}',
155 r'\Y4': '{cwd_y[4]}',
155 r'\Y4': '{cwd_y[4]}',
156 r'\Y5': '{cwd_y[5]}',
156 r'\Y5': '{cwd_y[5]}',
157 # Hostname up to first .
157 # Hostname up to first .
158 r'\h': HOSTNAME_SHORT,
158 r'\h': HOSTNAME_SHORT,
159 # Full hostname
159 # Full hostname
160 r'\H': HOSTNAME,
160 r'\H': HOSTNAME,
161 # Username of current user
161 # Username of current user
162 r'\u': USER,
162 r'\u': USER,
163 # Escaped '\'
163 # Escaped '\'
164 '\\\\': '\\',
164 '\\\\': '\\',
165 # Newline
165 # Newline
166 r'\n': '\n',
166 r'\n': '\n',
167 # Carriage return
167 # Carriage return
168 r'\r': '\r',
168 r'\r': '\r',
169 # Release version
169 # Release version
170 r'\v': release.version,
170 r'\v': release.version,
171 # Root symbol ($ or #)
171 # Root symbol ($ or #)
172 r'\$': ROOT_SYMBOL,
172 r'\$': ROOT_SYMBOL,
173 }
173 }
174
174
175 #-----------------------------------------------------------------------------
175 #-----------------------------------------------------------------------------
176 # More utilities
176 # More utilities
177 #-----------------------------------------------------------------------------
177 #-----------------------------------------------------------------------------
178
178
179 def cwd_filt(depth):
179 def cwd_filt(depth):
180 """Return the last depth elements of the current working directory.
180 """Return the last depth elements of the current working directory.
181
181
182 $HOME is always replaced with '~'.
182 $HOME is always replaced with '~'.
183 If depth==0, the full path is returned."""
183 If depth==0, the full path is returned."""
184
184
185 cwd = py3compat.getcwd().replace(HOME,"~")
185 cwd = py3compat.getcwd().replace(HOME,"~")
186 out = os.sep.join(cwd.split(os.sep)[-depth:])
186 out = os.sep.join(cwd.split(os.sep)[-depth:])
187 return out or os.sep
187 return out or os.sep
188
188
189 def cwd_filt2(depth):
189 def cwd_filt2(depth):
190 """Return the last depth elements of the current working directory.
190 """Return the last depth elements of the current working directory.
191
191
192 $HOME is always replaced with '~'.
192 $HOME is always replaced with '~'.
193 If depth==0, the full path is returned."""
193 If depth==0, the full path is returned."""
194
194
195 full_cwd = py3compat.getcwd()
195 full_cwd = py3compat.getcwd()
196 cwd = full_cwd.replace(HOME,"~").split(os.sep)
196 cwd = full_cwd.replace(HOME,"~").split(os.sep)
197 if '~' in cwd and len(cwd) == depth+1:
197 if '~' in cwd and len(cwd) == depth+1:
198 depth += 1
198 depth += 1
199 drivepart = ''
199 drivepart = ''
200 if sys.platform == 'win32' and len(cwd) > depth:
200 if sys.platform == 'win32' and len(cwd) > depth:
201 drivepart = os.path.splitdrive(full_cwd)[0]
201 drivepart = os.path.splitdrive(full_cwd)[0]
202 out = drivepart + '/'.join(cwd[-depth:])
202 out = drivepart + '/'.join(cwd[-depth:])
203
203
204 return out or os.sep
204 return out or os.sep
205
205
206 #-----------------------------------------------------------------------------
206 #-----------------------------------------------------------------------------
207 # Prompt classes
207 # Prompt classes
208 #-----------------------------------------------------------------------------
208 #-----------------------------------------------------------------------------
209
209
210 lazily_evaluate = {'time': LazyEvaluate(time.strftime, "%H:%M:%S"),
210 lazily_evaluate = {'time': LazyEvaluate(time.strftime, "%H:%M:%S"),
211 'cwd': LazyEvaluate(py3compat.getcwd),
211 'cwd': LazyEvaluate(py3compat.getcwd),
212 'cwd_last': LazyEvaluate(lambda: py3compat.getcwd().split(os.sep)[-1]),
212 'cwd_last': LazyEvaluate(lambda: py3compat.getcwd().split(os.sep)[-1]),
213 'cwd_x': [LazyEvaluate(lambda: py3compat.getcwd().replace(HOME,"~"))] +\
213 'cwd_x': [LazyEvaluate(lambda: py3compat.getcwd().replace(HOME,"~"))] +\
214 [LazyEvaluate(cwd_filt, x) for x in range(1,6)],
214 [LazyEvaluate(cwd_filt, x) for x in range(1,6)],
215 'cwd_y': [LazyEvaluate(cwd_filt2, x) for x in range(6)]
215 'cwd_y': [LazyEvaluate(cwd_filt2, x) for x in range(6)]
216 }
216 }
217
217
218 def _lenlastline(s):
218 def _lenlastline(s):
219 """Get the length of the last line. More intelligent than
219 """Get the length of the last line. More intelligent than
220 len(s.splitlines()[-1]).
220 len(s.splitlines()[-1]).
221 """
221 """
222 if not s or s.endswith(('\n', '\r')):
222 if not s or s.endswith(('\n', '\r')):
223 return 0
223 return 0
224 return len(s.splitlines()[-1])
224 return len(s.splitlines()[-1])
225
225
226
226
227 invisible_chars_re = re.compile('\001[^\001\002]*\002')
227 invisible_chars_re = re.compile('\001[^\001\002]*\002')
228 def _invisible_characters(s):
228 def _invisible_characters(s):
229 """
229 """
230 Get the number of invisible ANSI characters in s. Invisible characters
230 Get the number of invisible ANSI characters in s. Invisible characters
231 must be delimited by \001 and \002.
231 must be delimited by \001 and \002.
232 """
232 """
233 return _lenlastline(s) - _lenlastline(invisible_chars_re.sub('', s))
233 return _lenlastline(s) - _lenlastline(invisible_chars_re.sub('', s))
234
234
235 class UserNSFormatter(Formatter):
235 class UserNSFormatter(Formatter):
236 """A Formatter that falls back on a shell's user_ns and __builtins__ for name resolution"""
236 """A Formatter that falls back on a shell's user_ns and __builtins__ for name resolution"""
237 def __init__(self, shell):
237 def __init__(self, shell):
238 self.shell = shell
238 self.shell = shell
239
239
240 def get_value(self, key, args, kwargs):
240 def get_value(self, key, args, kwargs):
241 # try regular formatting first:
241 # try regular formatting first:
242 try:
242 try:
243 return Formatter.get_value(self, key, args, kwargs)
243 return Formatter.get_value(self, key, args, kwargs)
244 except Exception:
244 except Exception:
245 pass
245 pass
246 # next, look in user_ns and builtins:
246 # next, look in user_ns and builtins:
247 for container in (self.shell.user_ns, __builtins__):
247 for container in (self.shell.user_ns, __builtins__):
248 if key in container:
248 if key in container:
249 return container[key]
249 return container[key]
250 # nothing found, put error message in its place
250 # nothing found, put error message in its place
251 return "<ERROR: '%s' not found>" % key
251 return "<ERROR: '%s' not found>" % key
252
252
253
253
254 class PromptManager(Configurable):
254 class PromptManager(Configurable):
255 """This is the primary interface for producing IPython's prompts."""
255 """This is the primary interface for producing IPython's prompts."""
256 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
256 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
257
257
258 color_scheme_table = Instance(coloransi.ColorSchemeTable, allow_none=True)
258 color_scheme_table = Instance(coloransi.ColorSchemeTable, allow_none=True)
259 color_scheme = Unicode('Linux', config=True)
259 color_scheme = Unicode('Linux', config=True)
260 def _color_scheme_changed(self, name, new_value):
260 def _color_scheme_changed(self, name, new_value):
261 self.color_scheme_table.set_active_scheme(new_value)
261 self.color_scheme_table.set_active_scheme(new_value)
262 for pname in ['in', 'in2', 'out', 'rewrite']:
262 for pname in ['in', 'in2', 'out', 'rewrite']:
263 # We need to recalculate the number of invisible characters
263 # We need to recalculate the number of invisible characters
264 self.update_prompt(pname)
264 self.update_prompt(pname)
265
265
266 lazy_evaluate_fields = Dict(help="""
266 lazy_evaluate_fields = Dict(help="""
267 This maps field names used in the prompt templates to functions which
267 This maps field names used in the prompt templates to functions which
268 will be called when the prompt is rendered. This allows us to include
268 will be called when the prompt is rendered. This allows us to include
269 things like the current time in the prompts. Functions are only called
269 things like the current time in the prompts. Functions are only called
270 if they are used in the prompt.
270 if they are used in the prompt.
271 """)
271 """)
272 def _lazy_evaluate_fields_default(self): return lazily_evaluate.copy()
272 def _lazy_evaluate_fields_default(self): return lazily_evaluate.copy()
273
273
274 in_template = Unicode('In [\\#]: ', config=True,
274 in_template = Unicode('In [\\#]: ', config=True,
275 help="Input prompt. '\\#' will be transformed to the prompt number")
275 help="Input prompt. '\\#' will be transformed to the prompt number")
276 in2_template = Unicode(' .\\D.: ', config=True,
276 in2_template = Unicode(' .\\D.: ', config=True,
277 help="Continuation prompt.")
277 help="Continuation prompt.")
278 out_template = Unicode('Out[\\#]: ', config=True,
278 out_template = Unicode('Out[\\#]: ', config=True,
279 help="Output prompt. '\\#' will be transformed to the prompt number")
279 help="Output prompt. '\\#' will be transformed to the prompt number")
280
280
281 justify = Bool(True, config=True, help="""
281 justify = Bool(True, config=True, help="""
282 If True (default), each prompt will be right-aligned with the
282 If True (default), each prompt will be right-aligned with the
283 preceding one.
283 preceding one.
284 """)
284 """)
285
285
286 # We actually store the expanded templates here:
286 # We actually store the expanded templates here:
287 templates = Dict()
287 templates = Dict()
288
288
289 # The number of characters in the last prompt rendered, not including
289 # The number of characters in the last prompt rendered, not including
290 # colour characters.
290 # colour characters.
291 width = Int()
291 width = Int()
292 txtwidth = Int() # Not including right-justification
292 txtwidth = Int() # Not including right-justification
293
293
294 # The number of characters in each prompt which don't contribute to width
294 # The number of characters in each prompt which don't contribute to width
295 invisible_chars = Dict()
295 invisible_chars = Dict()
296 def _invisible_chars_default(self):
296 def _invisible_chars_default(self):
297 return {'in': 0, 'in2': 0, 'out': 0, 'rewrite':0}
297 return {'in': 0, 'in2': 0, 'out': 0, 'rewrite':0}
298
298
299 def __init__(self, shell, **kwargs):
299 def __init__(self, shell, **kwargs):
300 super(PromptManager, self).__init__(shell=shell, **kwargs)
300 super(PromptManager, self).__init__(shell=shell, **kwargs)
301
301
302 # Prepare colour scheme table
302 # Prepare colour scheme table
303 self.color_scheme_table = coloransi.ColorSchemeTable([NoColor,
303 self.color_scheme_table = coloransi.ColorSchemeTable([NoColor,
304 LinuxColors, LightBGColors], self.color_scheme)
304 LinuxColors, LightBGColors], self.color_scheme)
305
305
306 self._formatter = UserNSFormatter(shell)
306 self._formatter = UserNSFormatter(shell)
307 # Prepare templates & numbers of invisible characters
307 # Prepare templates & numbers of invisible characters
308 self.update_prompt('in', self.in_template)
308 self.update_prompt('in', self.in_template)
309 self.update_prompt('in2', self.in2_template)
309 self.update_prompt('in2', self.in2_template)
310 self.update_prompt('out', self.out_template)
310 self.update_prompt('out', self.out_template)
311 self.update_prompt('rewrite')
311 self.update_prompt('rewrite')
312 self.on_trait_change(self._update_prompt_trait, ['in_template',
312 self.on_trait_change(self._update_prompt_trait, ['in_template',
313 'in2_template', 'out_template'])
313 'in2_template', 'out_template'])
314
314
315 def update_prompt(self, name, new_template=None):
315 def update_prompt(self, name, new_template=None):
316 """This is called when a prompt template is updated. It processes
316 """This is called when a prompt template is updated. It processes
317 abbreviations used in the prompt template (like \#) and calculates how
317 abbreviations used in the prompt template (like \#) and calculates how
318 many invisible characters (ANSI colour escapes) the resulting prompt
318 many invisible characters (ANSI colour escapes) the resulting prompt
319 contains.
319 contains.
320
320
321 It is also called for each prompt on changing the colour scheme. In both
321 It is also called for each prompt on changing the colour scheme. In both
322 cases, traitlets should take care of calling this automatically.
322 cases, traitlets should take care of calling this automatically.
323 """
323 """
324 if new_template is not None:
324 if new_template is not None:
325 self.templates[name] = multiple_replace(prompt_abbreviations, new_template)
325 self.templates[name] = multiple_replace(prompt_abbreviations, new_template)
326 # We count invisible characters (colour escapes) on the last line of the
326 # We count invisible characters (colour escapes) on the last line of the
327 # prompt, to calculate the width for lining up subsequent prompts.
327 # prompt, to calculate the width for lining up subsequent prompts.
328 invis_chars = _invisible_characters(self._render(name, color=True))
328 invis_chars = _invisible_characters(self._render(name, color=True))
329 self.invisible_chars[name] = invis_chars
329 self.invisible_chars[name] = invis_chars
330
330
331 def _update_prompt_trait(self, traitname, new_template):
331 def _update_prompt_trait(self, traitname, new_template):
332 name = traitname[:-9] # Cut off '_template'
332 name = traitname[:-9] # Cut off '_template'
333 self.update_prompt(name, new_template)
333 self.update_prompt(name, new_template)
334
334
335 def _render(self, name, color=True, **kwargs):
335 def _render(self, name, color=True, **kwargs):
336 """Render but don't justify, or update the width or txtwidth attributes.
336 """Render but don't justify, or update the width or txtwidth attributes.
337 """
337 """
338 if name == 'rewrite':
338 if name == 'rewrite':
339 return self._render_rewrite(color=color)
339 return self._render_rewrite(color=color)
340
340
341 if color:
341 if color:
342 scheme = self.color_scheme_table.active_colors
342 scheme = self.color_scheme_table.active_colors
343 if name=='out':
343 if name=='out':
344 colors = color_lists['normal']
344 colors = color_lists['normal']
345 colors.number, colors.prompt, colors.normal = \
345 colors.number, colors.prompt, colors.normal = \
346 scheme.out_number, scheme.out_prompt, scheme.normal
346 scheme.out_number, scheme.out_prompt, scheme.normal
347 else:
347 else:
348 colors = color_lists['inp']
348 colors = color_lists['inp']
349 colors.number, colors.prompt, colors.normal = \
349 colors.number, colors.prompt, colors.normal = \
350 scheme.in_number, scheme.in_prompt, scheme.normal
350 scheme.in_number, scheme.in_prompt, scheme.in_normal
351 if name=='in2':
351 if name=='in2':
352 colors.prompt = scheme.in_prompt2
352 colors.prompt = scheme.in_prompt2
353 else:
353 else:
354 # No color
354 # No color
355 colors = color_lists['nocolor']
355 colors = color_lists['nocolor']
356 colors.number, colors.prompt, colors.normal = '', '', ''
356 colors.number, colors.prompt, colors.normal = '', '', ''
357
357
358 count = self.shell.execution_count # Shorthand
358 count = self.shell.execution_count # Shorthand
359 # Build the dictionary to be passed to string formatting
359 # Build the dictionary to be passed to string formatting
360 fmtargs = dict(color=colors, count=count,
360 fmtargs = dict(color=colors, count=count,
361 dots="."*len(str(count)), spaces=" "*len(str(count)),
361 dots="."*len(str(count)), spaces=" "*len(str(count)),
362 width=self.width, txtwidth=self.txtwidth)
362 width=self.width, txtwidth=self.txtwidth)
363 fmtargs.update(self.lazy_evaluate_fields)
363 fmtargs.update(self.lazy_evaluate_fields)
364 fmtargs.update(kwargs)
364 fmtargs.update(kwargs)
365
365
366 # Prepare the prompt
366 # Prepare the prompt
367 prompt = colors.prompt + self.templates[name] + colors.normal
367 prompt = colors.prompt + self.templates[name] + colors.normal
368
368
369 # Fill in required fields
369 # Fill in required fields
370 return self._formatter.format(prompt, **fmtargs)
370 return self._formatter.format(prompt, **fmtargs)
371
371
372 def _render_rewrite(self, color=True):
372 def _render_rewrite(self, color=True):
373 """Render the ---> rewrite prompt."""
373 """Render the ---> rewrite prompt."""
374 if color:
374 if color:
375 scheme = self.color_scheme_table.active_colors
375 scheme = self.color_scheme_table.active_colors
376 # We need a non-input version of these escapes
376 # We need a non-input version of these escapes
377 color_prompt = scheme.in_prompt.replace("\001","").replace("\002","")
377 color_prompt = scheme.in_prompt.replace("\001","").replace("\002","")
378 color_normal = scheme.normal
378 color_normal = scheme.normal
379 else:
379 else:
380 color_prompt, color_normal = '', ''
380 color_prompt, color_normal = '', ''
381
381
382 return color_prompt + "-> ".rjust(self.txtwidth, "-") + color_normal
382 return color_prompt + "-> ".rjust(self.txtwidth, "-") + color_normal
383
383
384 def render(self, name, color=True, just=None, **kwargs):
384 def render(self, name, color=True, just=None, **kwargs):
385 """
385 """
386 Render the selected prompt.
386 Render the selected prompt.
387
387
388 Parameters
388 Parameters
389 ----------
389 ----------
390 name : str
390 name : str
391 Which prompt to render. One of 'in', 'in2', 'out', 'rewrite'
391 Which prompt to render. One of 'in', 'in2', 'out', 'rewrite'
392 color : bool
392 color : bool
393 If True (default), include ANSI escape sequences for a coloured prompt.
393 If True (default), include ANSI escape sequences for a coloured prompt.
394 just : bool
394 just : bool
395 If True, justify the prompt to the width of the last prompt. The
395 If True, justify the prompt to the width of the last prompt. The
396 default is stored in self.justify.
396 default is stored in self.justify.
397 **kwargs :
397 **kwargs :
398 Additional arguments will be passed to the string formatting operation,
398 Additional arguments will be passed to the string formatting operation,
399 so they can override the values that would otherwise fill in the
399 so they can override the values that would otherwise fill in the
400 template.
400 template.
401
401
402 Returns
402 Returns
403 -------
403 -------
404 A string containing the rendered prompt.
404 A string containing the rendered prompt.
405 """
405 """
406 res = self._render(name, color=color, **kwargs)
406 res = self._render(name, color=color, **kwargs)
407
407
408 # Handle justification of prompt
408 # Handle justification of prompt
409 invis_chars = self.invisible_chars[name] if color else 0
409 invis_chars = self.invisible_chars[name] if color else 0
410 self.txtwidth = _lenlastline(res) - invis_chars
410 self.txtwidth = _lenlastline(res) - invis_chars
411 just = self.justify if (just is None) else just
411 just = self.justify if (just is None) else just
412 # If the prompt spans more than one line, don't try to justify it:
412 # If the prompt spans more than one line, don't try to justify it:
413 if just and name != 'in' and ('\n' not in res) and ('\r' not in res):
413 if just and name != 'in' and ('\n' not in res) and ('\r' not in res):
414 res = res.rjust(self.width + invis_chars)
414 res = res.rjust(self.width + invis_chars)
415 self.width = _lenlastline(res) - invis_chars
415 self.width = _lenlastline(res) - invis_chars
416 return res
416 return res
@@ -1,339 +1,342 b''
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 TermColors, InputTermColors ,ColorScheme, ColorSchemeTable
54 from IPython.utils.coloransi import TermColors, InputTermColors ,ColorScheme, ColorSchemeTable
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 'header' : Colors.NoColor,
77 'header' : Colors.NoColor,
78 token.NUMBER : Colors.NoColor,
78 token.NUMBER : Colors.NoColor,
79 token.OP : Colors.NoColor,
79 token.OP : Colors.NoColor,
80 token.STRING : Colors.NoColor,
80 token.STRING : Colors.NoColor,
81 tokenize.COMMENT : Colors.NoColor,
81 tokenize.COMMENT : Colors.NoColor,
82 token.NAME : Colors.NoColor,
82 token.NAME : Colors.NoColor,
83 token.ERRORTOKEN : Colors.NoColor,
83 token.ERRORTOKEN : Colors.NoColor,
84
84
85 _KEYWORD : Colors.NoColor,
85 _KEYWORD : Colors.NoColor,
86 _TEXT : Colors.NoColor,
86 _TEXT : Colors.NoColor,
87
87
88 'in_prompt' : InputTermColors.NoColor, # Input prompt
88 'in_prompt' : InputTermColors.NoColor, # Input prompt
89 'in_number' : InputTermColors.NoColor, # Input prompt number
89 'in_number' : InputTermColors.NoColor, # Input prompt number
90 'in_prompt2' : InputTermColors.NoColor, # Continuation prompt
90 'in_prompt2' : InputTermColors.NoColor, # Continuation prompt
91 'in_normal' : InputTermColors.NoColor, # color off (usu. Colors.Normal)
91
92
92 'out_prompt' : InputTermColors.NoColor, # Output prompt
93 'out_prompt' : Colors.NoColor, # Output prompt
93 'out_number' : InputTermColors.NoColor, # Output prompt number
94 'out_number' : Colors.NoColor, # Output prompt number
94
95
95 'normal' : InputTermColors.NoColor # color off (usu. Colors.Normal)
96 'normal' : Colors.NoColor # color off (usu. Colors.Normal)
96 } )
97 } )
97
98
98 LinuxColors = ColorScheme(
99 LinuxColors = ColorScheme(
99 'Linux',{
100 'Linux',{
100 'header' : Colors.LightRed,
101 'header' : Colors.LightRed,
101 token.NUMBER : Colors.LightCyan,
102 token.NUMBER : Colors.LightCyan,
102 token.OP : Colors.Yellow,
103 token.OP : Colors.Yellow,
103 token.STRING : Colors.LightBlue,
104 token.STRING : Colors.LightBlue,
104 tokenize.COMMENT : Colors.LightRed,
105 tokenize.COMMENT : Colors.LightRed,
105 token.NAME : Colors.Normal,
106 token.NAME : Colors.Normal,
106 token.ERRORTOKEN : Colors.Red,
107 token.ERRORTOKEN : Colors.Red,
107
108
108 _KEYWORD : Colors.LightGreen,
109 _KEYWORD : Colors.LightGreen,
109 _TEXT : Colors.Yellow,
110 _TEXT : Colors.Yellow,
110
111
111 'in_prompt' : InputTermColors.Green,
112 'in_prompt' : InputTermColors.Green,
112 'in_number' : InputTermColors.LightGreen,
113 'in_number' : InputTermColors.LightGreen,
113 'in_prompt2' : InputTermColors.Green,
114 'in_prompt2' : InputTermColors.Green,
115 'in_normal' : InputTermColors.Normal, # color off (usu. Colors.Normal)
114
116
115 'out_prompt' : InputTermColors.Red,
117 'out_prompt' : Colors.Red,
116 'out_number' : InputTermColors.LightRed,
118 'out_number' : Colors.LightRed,
117
119
118 'normal' : InputTermColors.Normal # color off (usu. Colors.Normal)
120 'normal' : Colors.Normal # color off (usu. Colors.Normal)
119 } )
121 } )
120
122
121 LightBGColors = ColorScheme(
123 LightBGColors = ColorScheme(
122 'LightBG',{
124 'LightBG',{
123 'header' : Colors.Red,
125 'header' : Colors.Red,
124 token.NUMBER : Colors.Cyan,
126 token.NUMBER : Colors.Cyan,
125 token.OP : Colors.Blue,
127 token.OP : Colors.Blue,
126 token.STRING : Colors.Blue,
128 token.STRING : Colors.Blue,
127 tokenize.COMMENT : Colors.Red,
129 tokenize.COMMENT : Colors.Red,
128 token.NAME : Colors.Normal,
130 token.NAME : Colors.Normal,
129 token.ERRORTOKEN : Colors.Red,
131 token.ERRORTOKEN : Colors.Red,
130
132
131 _KEYWORD : Colors.Green,
133 _KEYWORD : Colors.Green,
132 _TEXT : Colors.Blue,
134 _TEXT : Colors.Blue,
133
135
134 'in_prompt' : InputTermColors.Blue,
136 'in_prompt' : InputTermColors.Blue,
135 'in_number' : InputTermColors.LightBlue,
137 'in_number' : InputTermColors.LightBlue,
136 'in_prompt2' : InputTermColors.Blue,
138 'in_prompt2' : InputTermColors.Blue,
139 'in_normal' : InputTermColors.Normal, # color off (usu. Colors.Normal)
137
140
138 'out_prompt' : InputTermColors.Red,
141 'out_prompt' : Colors.Red,
139 'out_number' : InputTermColors.LightRed,
142 'out_number' : Colors.LightRed,
140
143
141 'normal' : InputTermColors.Normal # color off (usu. Colors.Normal)
144 'normal' : Colors.Normal # color off (usu. Colors.Normal)
142 } )
145 } )
143
146
144 # Build table of color schemes (needed by the parser)
147 # Build table of color schemes (needed by the parser)
145 ANSICodeColors = ColorSchemeTable([NoColor,LinuxColors,LightBGColors],
148 ANSICodeColors = ColorSchemeTable([NoColor,LinuxColors,LightBGColors],
146 _scheme_default)
149 _scheme_default)
147
150
148 class Parser:
151 class Parser:
149 """ Format colored Python source.
152 """ Format colored Python source.
150 """
153 """
151
154
152 def __init__(self, color_table=None,out = sys.stdout):
155 def __init__(self, color_table=None,out = sys.stdout):
153 """ Create a parser with a specified color table and output channel.
156 """ Create a parser with a specified color table and output channel.
154
157
155 Call format() to process code.
158 Call format() to process code.
156 """
159 """
157 self.color_table = color_table and color_table or ANSICodeColors
160 self.color_table = color_table and color_table or ANSICodeColors
158 self.out = out
161 self.out = out
159
162
160 def format(self, raw, out = None, scheme = ''):
163 def format(self, raw, out = None, scheme = ''):
161 return self.format2(raw, out, scheme)[0]
164 return self.format2(raw, out, scheme)[0]
162
165
163 def format2(self, raw, out = None, scheme = ''):
166 def format2(self, raw, out = None, scheme = ''):
164 """ Parse and send the colored source.
167 """ Parse and send the colored source.
165
168
166 If out and scheme are not specified, the defaults (given to
169 If out and scheme are not specified, the defaults (given to
167 constructor) are used.
170 constructor) are used.
168
171
169 out should be a file-type object. Optionally, out can be given as the
172 out should be a file-type object. Optionally, out can be given as the
170 string 'str' and the parser will automatically return the output in a
173 string 'str' and the parser will automatically return the output in a
171 string."""
174 string."""
172
175
173 string_output = 0
176 string_output = 0
174 if out == 'str' or self.out == 'str' or \
177 if out == 'str' or self.out == 'str' or \
175 isinstance(self.out,StringIO):
178 isinstance(self.out,StringIO):
176 # XXX - I don't really like this state handling logic, but at this
179 # XXX - I don't really like this state handling logic, but at this
177 # point I don't want to make major changes, so adding the
180 # point I don't want to make major changes, so adding the
178 # isinstance() check is the simplest I can do to ensure correct
181 # isinstance() check is the simplest I can do to ensure correct
179 # behavior.
182 # behavior.
180 out_old = self.out
183 out_old = self.out
181 self.out = StringIO()
184 self.out = StringIO()
182 string_output = 1
185 string_output = 1
183 elif out is not None:
186 elif out is not None:
184 self.out = out
187 self.out = out
185
188
186 # Fast return of the unmodified input for NoColor scheme
189 # Fast return of the unmodified input for NoColor scheme
187 if scheme == 'NoColor':
190 if scheme == 'NoColor':
188 error = False
191 error = False
189 self.out.write(raw)
192 self.out.write(raw)
190 if string_output:
193 if string_output:
191 return raw,error
194 return raw,error
192 else:
195 else:
193 return None,error
196 return None,error
194
197
195 # local shorthands
198 # local shorthands
196 colors = self.color_table[scheme].colors
199 colors = self.color_table[scheme].colors
197 self.colors = colors # put in object so __call__ sees it
200 self.colors = colors # put in object so __call__ sees it
198
201
199 # Remove trailing whitespace and normalize tabs
202 # Remove trailing whitespace and normalize tabs
200 self.raw = raw.expandtabs().rstrip()
203 self.raw = raw.expandtabs().rstrip()
201
204
202 # store line offsets in self.lines
205 # store line offsets in self.lines
203 self.lines = [0, 0]
206 self.lines = [0, 0]
204 pos = 0
207 pos = 0
205 raw_find = self.raw.find
208 raw_find = self.raw.find
206 lines_append = self.lines.append
209 lines_append = self.lines.append
207 while 1:
210 while 1:
208 pos = raw_find('\n', pos) + 1
211 pos = raw_find('\n', pos) + 1
209 if not pos: break
212 if not pos: break
210 lines_append(pos)
213 lines_append(pos)
211 lines_append(len(self.raw))
214 lines_append(len(self.raw))
212
215
213 # parse the source and write it
216 # parse the source and write it
214 self.pos = 0
217 self.pos = 0
215 text = StringIO(self.raw)
218 text = StringIO(self.raw)
216
219
217 error = False
220 error = False
218 try:
221 try:
219 for atoken in generate_tokens(text.readline):
222 for atoken in generate_tokens(text.readline):
220 self(*atoken)
223 self(*atoken)
221 except tokenize.TokenError as ex:
224 except tokenize.TokenError as ex:
222 msg = ex.args[0]
225 msg = ex.args[0]
223 line = ex.args[1][0]
226 line = ex.args[1][0]
224 self.out.write("%s\n\n*** ERROR: %s%s%s\n" %
227 self.out.write("%s\n\n*** ERROR: %s%s%s\n" %
225 (colors[token.ERRORTOKEN],
228 (colors[token.ERRORTOKEN],
226 msg, self.raw[self.lines[line]:],
229 msg, self.raw[self.lines[line]:],
227 colors.normal)
230 colors.normal)
228 )
231 )
229 error = True
232 error = True
230 self.out.write(colors.normal+'\n')
233 self.out.write(colors.normal+'\n')
231 if string_output:
234 if string_output:
232 output = self.out.getvalue()
235 output = self.out.getvalue()
233 self.out = out_old
236 self.out = out_old
234 return (output, error)
237 return (output, error)
235 return (None, error)
238 return (None, error)
236
239
237 def __call__(self, toktype, toktext, start_pos, end_pos, line):
240 def __call__(self, toktype, toktext, start_pos, end_pos, line):
238 """ Token handler, with syntax highlighting."""
241 """ Token handler, with syntax highlighting."""
239 (srow,scol) = start_pos
242 (srow,scol) = start_pos
240 (erow,ecol) = end_pos
243 (erow,ecol) = end_pos
241 colors = self.colors
244 colors = self.colors
242 owrite = self.out.write
245 owrite = self.out.write
243
246
244 # line separator, so this works across platforms
247 # line separator, so this works across platforms
245 linesep = os.linesep
248 linesep = os.linesep
246
249
247 # calculate new positions
250 # calculate new positions
248 oldpos = self.pos
251 oldpos = self.pos
249 newpos = self.lines[srow] + scol
252 newpos = self.lines[srow] + scol
250 self.pos = newpos + len(toktext)
253 self.pos = newpos + len(toktext)
251
254
252 # send the original whitespace, if needed
255 # send the original whitespace, if needed
253 if newpos > oldpos:
256 if newpos > oldpos:
254 owrite(self.raw[oldpos:newpos])
257 owrite(self.raw[oldpos:newpos])
255
258
256 # skip indenting tokens
259 # skip indenting tokens
257 if toktype in [token.INDENT, token.DEDENT]:
260 if toktype in [token.INDENT, token.DEDENT]:
258 self.pos = newpos
261 self.pos = newpos
259 return
262 return
260
263
261 # map token type to a color group
264 # map token type to a color group
262 if token.LPAR <= toktype <= token.OP:
265 if token.LPAR <= toktype <= token.OP:
263 toktype = token.OP
266 toktype = token.OP
264 elif toktype == token.NAME and keyword.iskeyword(toktext):
267 elif toktype == token.NAME and keyword.iskeyword(toktext):
265 toktype = _KEYWORD
268 toktype = _KEYWORD
266 color = colors.get(toktype, colors[_TEXT])
269 color = colors.get(toktype, colors[_TEXT])
267
270
268 #print '<%s>' % toktext, # dbg
271 #print '<%s>' % toktext, # dbg
269
272
270 # Triple quoted strings must be handled carefully so that backtracking
273 # Triple quoted strings must be handled carefully so that backtracking
271 # in pagers works correctly. We need color terminators on _each_ line.
274 # in pagers works correctly. We need color terminators on _each_ line.
272 if linesep in toktext:
275 if linesep in toktext:
273 toktext = toktext.replace(linesep, '%s%s%s' %
276 toktext = toktext.replace(linesep, '%s%s%s' %
274 (colors.normal,linesep,color))
277 (colors.normal,linesep,color))
275
278
276 # send text
279 # send text
277 owrite('%s%s%s' % (color,toktext,colors.normal))
280 owrite('%s%s%s' % (color,toktext,colors.normal))
278
281
279 def main(argv=None):
282 def main(argv=None):
280 """Run as a command-line script: colorize a python file or stdin using ANSI
283 """Run as a command-line script: colorize a python file or stdin using ANSI
281 color escapes and print to stdout.
284 color escapes and print to stdout.
282
285
283 Inputs:
286 Inputs:
284
287
285 - argv(None): a list of strings like sys.argv[1:] giving the command-line
288 - argv(None): a list of strings like sys.argv[1:] giving the command-line
286 arguments. If None, use sys.argv[1:].
289 arguments. If None, use sys.argv[1:].
287 """
290 """
288
291
289 usage_msg = """%prog [options] [filename]
292 usage_msg = """%prog [options] [filename]
290
293
291 Colorize a python file or stdin using ANSI color escapes and print to stdout.
294 Colorize a python file or stdin using ANSI color escapes and print to stdout.
292 If no filename is given, or if filename is -, read standard input."""
295 If no filename is given, or if filename is -, read standard input."""
293
296
294 import optparse
297 import optparse
295 parser = optparse.OptionParser(usage=usage_msg)
298 parser = optparse.OptionParser(usage=usage_msg)
296 newopt = parser.add_option
299 newopt = parser.add_option
297 newopt('-s','--scheme',metavar='NAME',dest='scheme_name',action='store',
300 newopt('-s','--scheme',metavar='NAME',dest='scheme_name',action='store',
298 choices=['Linux','LightBG','NoColor'],default=_scheme_default,
301 choices=['Linux','LightBG','NoColor'],default=_scheme_default,
299 help="give the color scheme to use. Currently only 'Linux'\
302 help="give the color scheme to use. Currently only 'Linux'\
300 (default) and 'LightBG' and 'NoColor' are implemented (give without\
303 (default) and 'LightBG' and 'NoColor' are implemented (give without\
301 quotes)")
304 quotes)")
302
305
303 opts,args = parser.parse_args(argv)
306 opts,args = parser.parse_args(argv)
304
307
305 if len(args) > 1:
308 if len(args) > 1:
306 parser.error("you must give at most one filename.")
309 parser.error("you must give at most one filename.")
307
310
308 if len(args) == 0:
311 if len(args) == 0:
309 fname = '-' # no filename given; setup to read from stdin
312 fname = '-' # no filename given; setup to read from stdin
310 else:
313 else:
311 fname = args[0]
314 fname = args[0]
312
315
313 if fname == '-':
316 if fname == '-':
314 stream = sys.stdin
317 stream = sys.stdin
315 else:
318 else:
316 try:
319 try:
317 stream = open(fname)
320 stream = open(fname)
318 except IOError as msg:
321 except IOError as msg:
319 print(msg, file=sys.stderr)
322 print(msg, file=sys.stderr)
320 sys.exit(1)
323 sys.exit(1)
321
324
322 parser = Parser()
325 parser = Parser()
323
326
324 # we need nested try blocks because pre-2.5 python doesn't support unified
327 # we need nested try blocks because pre-2.5 python doesn't support unified
325 # try-except-finally
328 # try-except-finally
326 try:
329 try:
327 try:
330 try:
328 # write colorized version to stdout
331 # write colorized version to stdout
329 parser.format(stream.read(),scheme=opts.scheme_name)
332 parser.format(stream.read(),scheme=opts.scheme_name)
330 except IOError as msg:
333 except IOError as msg:
331 # if user reads through a pager and quits, don't print traceback
334 # if user reads through a pager and quits, don't print traceback
332 if msg.args != (32,'Broken pipe'):
335 if msg.args != (32,'Broken pipe'):
333 raise
336 raise
334 finally:
337 finally:
335 if stream is not sys.stdin:
338 if stream is not sys.stdin:
336 stream.close() # in case a non-handled exception happened above
339 stream.close() # in case a non-handled exception happened above
337
340
338 if __name__ == "__main__":
341 if __name__ == "__main__":
339 main()
342 main()
General Comments 0
You need to be logged in to leave comments. Login now