##// END OF EJS Templates
Use raw string when invalid escape sequence present....
Matthias Bussonnier -
Show More
@@ -1,645 +1,645 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 Pdb debugger class.
3 Pdb debugger class.
4
4
5 Modified from the standard pdb.Pdb class to avoid including readline, so that
5 Modified from the standard pdb.Pdb class to avoid including readline, so that
6 the command line completion of other programs which include this isn't
6 the command line completion of other programs which include this isn't
7 damaged.
7 damaged.
8
8
9 In the future, this class will be expanded with improvements over the standard
9 In the future, this class will be expanded with improvements over the standard
10 pdb.
10 pdb.
11
11
12 The code in this file is mainly lifted out of cmd.py in Python 2.2, with minor
12 The code in this file is mainly lifted out of cmd.py in Python 2.2, with minor
13 changes. Licensing should therefore be under the standard Python terms. For
13 changes. Licensing should therefore be under the standard Python terms. For
14 details on the PSF (Python Software Foundation) standard license, see:
14 details on the PSF (Python Software Foundation) standard license, see:
15
15
16 https://docs.python.org/2/license.html
16 https://docs.python.org/2/license.html
17 """
17 """
18
18
19 #*****************************************************************************
19 #*****************************************************************************
20 #
20 #
21 # This file is licensed under the PSF license.
21 # This file is licensed under the PSF license.
22 #
22 #
23 # Copyright (C) 2001 Python Software Foundation, www.python.org
23 # Copyright (C) 2001 Python Software Foundation, www.python.org
24 # Copyright (C) 2005-2006 Fernando Perez. <fperez@colorado.edu>
24 # Copyright (C) 2005-2006 Fernando Perez. <fperez@colorado.edu>
25 #
25 #
26 #
26 #
27 #*****************************************************************************
27 #*****************************************************************************
28
28
29 import bdb
29 import bdb
30 import functools
30 import functools
31 import inspect
31 import inspect
32 import linecache
32 import linecache
33 import sys
33 import sys
34 import warnings
34 import warnings
35 import re
35 import re
36
36
37 from IPython import get_ipython
37 from IPython import get_ipython
38 from IPython.utils import PyColorize
38 from IPython.utils import PyColorize
39 from IPython.utils import coloransi, py3compat
39 from IPython.utils import coloransi, py3compat
40 from IPython.core.excolors import exception_colors
40 from IPython.core.excolors import exception_colors
41 from IPython.testing.skipdoctest import skip_doctest
41 from IPython.testing.skipdoctest import skip_doctest
42
42
43
43
44 prompt = 'ipdb> '
44 prompt = 'ipdb> '
45
45
46 #We have to check this directly from sys.argv, config struct not yet available
46 #We have to check this directly from sys.argv, config struct not yet available
47 from pdb import Pdb as OldPdb
47 from pdb import Pdb as OldPdb
48
48
49 # Allow the set_trace code to operate outside of an ipython instance, even if
49 # Allow the set_trace code to operate outside of an ipython instance, even if
50 # it does so with some limitations. The rest of this support is implemented in
50 # it does so with some limitations. The rest of this support is implemented in
51 # the Tracer constructor.
51 # the Tracer constructor.
52
52
53 def make_arrow(pad):
53 def make_arrow(pad):
54 """generate the leading arrow in front of traceback or debugger"""
54 """generate the leading arrow in front of traceback or debugger"""
55 if pad >= 2:
55 if pad >= 2:
56 return '-'*(pad-2) + '> '
56 return '-'*(pad-2) + '> '
57 elif pad == 1:
57 elif pad == 1:
58 return '>'
58 return '>'
59 return ''
59 return ''
60
60
61
61
62 def BdbQuit_excepthook(et, ev, tb, excepthook=None):
62 def BdbQuit_excepthook(et, ev, tb, excepthook=None):
63 """Exception hook which handles `BdbQuit` exceptions.
63 """Exception hook which handles `BdbQuit` exceptions.
64
64
65 All other exceptions are processed using the `excepthook`
65 All other exceptions are processed using the `excepthook`
66 parameter.
66 parameter.
67 """
67 """
68 warnings.warn("`BdbQuit_excepthook` is deprecated since version 5.1",
68 warnings.warn("`BdbQuit_excepthook` is deprecated since version 5.1",
69 DeprecationWarning, stacklevel=2)
69 DeprecationWarning, stacklevel=2)
70 if et==bdb.BdbQuit:
70 if et==bdb.BdbQuit:
71 print('Exiting Debugger.')
71 print('Exiting Debugger.')
72 elif excepthook is not None:
72 elif excepthook is not None:
73 excepthook(et, ev, tb)
73 excepthook(et, ev, tb)
74 else:
74 else:
75 # Backwards compatibility. Raise deprecation warning?
75 # Backwards compatibility. Raise deprecation warning?
76 BdbQuit_excepthook.excepthook_ori(et,ev,tb)
76 BdbQuit_excepthook.excepthook_ori(et,ev,tb)
77
77
78
78
79 def BdbQuit_IPython_excepthook(self,et,ev,tb,tb_offset=None):
79 def BdbQuit_IPython_excepthook(self,et,ev,tb,tb_offset=None):
80 warnings.warn(
80 warnings.warn(
81 "`BdbQuit_IPython_excepthook` is deprecated since version 5.1",
81 "`BdbQuit_IPython_excepthook` is deprecated since version 5.1",
82 DeprecationWarning, stacklevel=2)
82 DeprecationWarning, stacklevel=2)
83 print('Exiting Debugger.')
83 print('Exiting Debugger.')
84
84
85
85
86 class Tracer(object):
86 class Tracer(object):
87 """
87 """
88 DEPRECATED
88 DEPRECATED
89
89
90 Class for local debugging, similar to pdb.set_trace.
90 Class for local debugging, similar to pdb.set_trace.
91
91
92 Instances of this class, when called, behave like pdb.set_trace, but
92 Instances of this class, when called, behave like pdb.set_trace, but
93 providing IPython's enhanced capabilities.
93 providing IPython's enhanced capabilities.
94
94
95 This is implemented as a class which must be initialized in your own code
95 This is implemented as a class which must be initialized in your own code
96 and not as a standalone function because we need to detect at runtime
96 and not as a standalone function because we need to detect at runtime
97 whether IPython is already active or not. That detection is done in the
97 whether IPython is already active or not. That detection is done in the
98 constructor, ensuring that this code plays nicely with a running IPython,
98 constructor, ensuring that this code plays nicely with a running IPython,
99 while functioning acceptably (though with limitations) if outside of it.
99 while functioning acceptably (though with limitations) if outside of it.
100 """
100 """
101
101
102 @skip_doctest
102 @skip_doctest
103 def __init__(self, colors=None):
103 def __init__(self, colors=None):
104 """
104 """
105 DEPRECATED
105 DEPRECATED
106
106
107 Create a local debugger instance.
107 Create a local debugger instance.
108
108
109 Parameters
109 Parameters
110 ----------
110 ----------
111
111
112 colors : str, optional
112 colors : str, optional
113 The name of the color scheme to use, it must be one of IPython's
113 The name of the color scheme to use, it must be one of IPython's
114 valid color schemes. If not given, the function will default to
114 valid color schemes. If not given, the function will default to
115 the current IPython scheme when running inside IPython, and to
115 the current IPython scheme when running inside IPython, and to
116 'NoColor' otherwise.
116 'NoColor' otherwise.
117
117
118 Examples
118 Examples
119 --------
119 --------
120 ::
120 ::
121
121
122 from IPython.core.debugger import Tracer; debug_here = Tracer()
122 from IPython.core.debugger import Tracer; debug_here = Tracer()
123
123
124 Later in your code::
124 Later in your code::
125
125
126 debug_here() # -> will open up the debugger at that point.
126 debug_here() # -> will open up the debugger at that point.
127
127
128 Once the debugger activates, you can use all of its regular commands to
128 Once the debugger activates, you can use all of its regular commands to
129 step through code, set breakpoints, etc. See the pdb documentation
129 step through code, set breakpoints, etc. See the pdb documentation
130 from the Python standard library for usage details.
130 from the Python standard library for usage details.
131 """
131 """
132 warnings.warn("`Tracer` is deprecated since version 5.1, directly use "
132 warnings.warn("`Tracer` is deprecated since version 5.1, directly use "
133 "`IPython.core.debugger.Pdb.set_trace()`",
133 "`IPython.core.debugger.Pdb.set_trace()`",
134 DeprecationWarning, stacklevel=2)
134 DeprecationWarning, stacklevel=2)
135
135
136 ip = get_ipython()
136 ip = get_ipython()
137 if ip is None:
137 if ip is None:
138 # Outside of ipython, we set our own exception hook manually
138 # Outside of ipython, we set our own exception hook manually
139 sys.excepthook = functools.partial(BdbQuit_excepthook,
139 sys.excepthook = functools.partial(BdbQuit_excepthook,
140 excepthook=sys.excepthook)
140 excepthook=sys.excepthook)
141 def_colors = 'NoColor'
141 def_colors = 'NoColor'
142 else:
142 else:
143 # In ipython, we use its custom exception handler mechanism
143 # In ipython, we use its custom exception handler mechanism
144 def_colors = ip.colors
144 def_colors = ip.colors
145 ip.set_custom_exc((bdb.BdbQuit,), BdbQuit_IPython_excepthook)
145 ip.set_custom_exc((bdb.BdbQuit,), BdbQuit_IPython_excepthook)
146
146
147 if colors is None:
147 if colors is None:
148 colors = def_colors
148 colors = def_colors
149
149
150 # The stdlib debugger internally uses a modified repr from the `repr`
150 # The stdlib debugger internally uses a modified repr from the `repr`
151 # module, that limits the length of printed strings to a hardcoded
151 # module, that limits the length of printed strings to a hardcoded
152 # limit of 30 characters. That much trimming is too aggressive, let's
152 # limit of 30 characters. That much trimming is too aggressive, let's
153 # at least raise that limit to 80 chars, which should be enough for
153 # at least raise that limit to 80 chars, which should be enough for
154 # most interactive uses.
154 # most interactive uses.
155 try:
155 try:
156 try:
156 try:
157 from reprlib import aRepr # Py 3
157 from reprlib import aRepr # Py 3
158 except ImportError:
158 except ImportError:
159 from repr import aRepr # Py 2
159 from repr import aRepr # Py 2
160 aRepr.maxstring = 80
160 aRepr.maxstring = 80
161 except:
161 except:
162 # This is only a user-facing convenience, so any error we encounter
162 # This is only a user-facing convenience, so any error we encounter
163 # here can be warned about but can be otherwise ignored. These
163 # here can be warned about but can be otherwise ignored. These
164 # printouts will tell us about problems if this API changes
164 # printouts will tell us about problems if this API changes
165 import traceback
165 import traceback
166 traceback.print_exc()
166 traceback.print_exc()
167
167
168 self.debugger = Pdb(colors)
168 self.debugger = Pdb(colors)
169
169
170 def __call__(self):
170 def __call__(self):
171 """Starts an interactive debugger at the point where called.
171 """Starts an interactive debugger at the point where called.
172
172
173 This is similar to the pdb.set_trace() function from the std lib, but
173 This is similar to the pdb.set_trace() function from the std lib, but
174 using IPython's enhanced debugger."""
174 using IPython's enhanced debugger."""
175
175
176 self.debugger.set_trace(sys._getframe().f_back)
176 self.debugger.set_trace(sys._getframe().f_back)
177
177
178
178
179 RGX_EXTRA_INDENT = re.compile('(?<=\n)\s+')
179 RGX_EXTRA_INDENT = re.compile(r'(?<=\n)\s+')
180
180
181
181
182 def strip_indentation(multiline_string):
182 def strip_indentation(multiline_string):
183 return RGX_EXTRA_INDENT.sub('', multiline_string)
183 return RGX_EXTRA_INDENT.sub('', multiline_string)
184
184
185
185
186 def decorate_fn_with_doc(new_fn, old_fn, additional_text=""):
186 def decorate_fn_with_doc(new_fn, old_fn, additional_text=""):
187 """Make new_fn have old_fn's doc string. This is particularly useful
187 """Make new_fn have old_fn's doc string. This is particularly useful
188 for the ``do_...`` commands that hook into the help system.
188 for the ``do_...`` commands that hook into the help system.
189 Adapted from from a comp.lang.python posting
189 Adapted from from a comp.lang.python posting
190 by Duncan Booth."""
190 by Duncan Booth."""
191 def wrapper(*args, **kw):
191 def wrapper(*args, **kw):
192 return new_fn(*args, **kw)
192 return new_fn(*args, **kw)
193 if old_fn.__doc__:
193 if old_fn.__doc__:
194 wrapper.__doc__ = strip_indentation(old_fn.__doc__) + additional_text
194 wrapper.__doc__ = strip_indentation(old_fn.__doc__) + additional_text
195 return wrapper
195 return wrapper
196
196
197
197
198 def _file_lines(fname):
198 def _file_lines(fname):
199 """Return the contents of a named file as a list of lines.
199 """Return the contents of a named file as a list of lines.
200
200
201 This function never raises an IOError exception: if the file can't be
201 This function never raises an IOError exception: if the file can't be
202 read, it simply returns an empty list."""
202 read, it simply returns an empty list."""
203
203
204 try:
204 try:
205 outfile = open(fname)
205 outfile = open(fname)
206 except IOError:
206 except IOError:
207 return []
207 return []
208 else:
208 else:
209 out = outfile.readlines()
209 out = outfile.readlines()
210 outfile.close()
210 outfile.close()
211 return out
211 return out
212
212
213
213
214 class Pdb(OldPdb):
214 class Pdb(OldPdb):
215 """Modified Pdb class, does not load readline.
215 """Modified Pdb class, does not load readline.
216
216
217 for a standalone version that uses prompt_toolkit, see
217 for a standalone version that uses prompt_toolkit, see
218 `IPython.terminal.debugger.TerminalPdb` and
218 `IPython.terminal.debugger.TerminalPdb` and
219 `IPython.terminal.debugger.set_trace()`
219 `IPython.terminal.debugger.set_trace()`
220 """
220 """
221
221
222 def __init__(self, color_scheme=None, completekey=None,
222 def __init__(self, color_scheme=None, completekey=None,
223 stdin=None, stdout=None, context=5):
223 stdin=None, stdout=None, context=5):
224
224
225 # Parent constructor:
225 # Parent constructor:
226 try:
226 try:
227 self.context = int(context)
227 self.context = int(context)
228 if self.context <= 0:
228 if self.context <= 0:
229 raise ValueError("Context must be a positive integer")
229 raise ValueError("Context must be a positive integer")
230 except (TypeError, ValueError):
230 except (TypeError, ValueError):
231 raise ValueError("Context must be a positive integer")
231 raise ValueError("Context must be a positive integer")
232
232
233 OldPdb.__init__(self, completekey, stdin, stdout)
233 OldPdb.__init__(self, completekey, stdin, stdout)
234
234
235 # IPython changes...
235 # IPython changes...
236 self.shell = get_ipython()
236 self.shell = get_ipython()
237
237
238 if self.shell is None:
238 if self.shell is None:
239 save_main = sys.modules['__main__']
239 save_main = sys.modules['__main__']
240 # No IPython instance running, we must create one
240 # No IPython instance running, we must create one
241 from IPython.terminal.interactiveshell import \
241 from IPython.terminal.interactiveshell import \
242 TerminalInteractiveShell
242 TerminalInteractiveShell
243 self.shell = TerminalInteractiveShell.instance()
243 self.shell = TerminalInteractiveShell.instance()
244 # needed by any code which calls __import__("__main__") after
244 # needed by any code which calls __import__("__main__") after
245 # the debugger was entered. See also #9941.
245 # the debugger was entered. See also #9941.
246 sys.modules['__main__'] = save_main
246 sys.modules['__main__'] = save_main
247
247
248 if color_scheme is not None:
248 if color_scheme is not None:
249 warnings.warn(
249 warnings.warn(
250 "The `color_scheme` argument is deprecated since version 5.1",
250 "The `color_scheme` argument is deprecated since version 5.1",
251 DeprecationWarning, stacklevel=2)
251 DeprecationWarning, stacklevel=2)
252 else:
252 else:
253 color_scheme = self.shell.colors
253 color_scheme = self.shell.colors
254
254
255 self.aliases = {}
255 self.aliases = {}
256
256
257 # Create color table: we copy the default one from the traceback
257 # Create color table: we copy the default one from the traceback
258 # module and add a few attributes needed for debugging
258 # module and add a few attributes needed for debugging
259 self.color_scheme_table = exception_colors()
259 self.color_scheme_table = exception_colors()
260
260
261 # shorthands
261 # shorthands
262 C = coloransi.TermColors
262 C = coloransi.TermColors
263 cst = self.color_scheme_table
263 cst = self.color_scheme_table
264
264
265 cst['NoColor'].colors.prompt = C.NoColor
265 cst['NoColor'].colors.prompt = C.NoColor
266 cst['NoColor'].colors.breakpoint_enabled = C.NoColor
266 cst['NoColor'].colors.breakpoint_enabled = C.NoColor
267 cst['NoColor'].colors.breakpoint_disabled = C.NoColor
267 cst['NoColor'].colors.breakpoint_disabled = C.NoColor
268
268
269 cst['Linux'].colors.prompt = C.Green
269 cst['Linux'].colors.prompt = C.Green
270 cst['Linux'].colors.breakpoint_enabled = C.LightRed
270 cst['Linux'].colors.breakpoint_enabled = C.LightRed
271 cst['Linux'].colors.breakpoint_disabled = C.Red
271 cst['Linux'].colors.breakpoint_disabled = C.Red
272
272
273 cst['LightBG'].colors.prompt = C.Blue
273 cst['LightBG'].colors.prompt = C.Blue
274 cst['LightBG'].colors.breakpoint_enabled = C.LightRed
274 cst['LightBG'].colors.breakpoint_enabled = C.LightRed
275 cst['LightBG'].colors.breakpoint_disabled = C.Red
275 cst['LightBG'].colors.breakpoint_disabled = C.Red
276
276
277 cst['Neutral'].colors.prompt = C.Blue
277 cst['Neutral'].colors.prompt = C.Blue
278 cst['Neutral'].colors.breakpoint_enabled = C.LightRed
278 cst['Neutral'].colors.breakpoint_enabled = C.LightRed
279 cst['Neutral'].colors.breakpoint_disabled = C.Red
279 cst['Neutral'].colors.breakpoint_disabled = C.Red
280
280
281
281
282 # Add a python parser so we can syntax highlight source while
282 # Add a python parser so we can syntax highlight source while
283 # debugging.
283 # debugging.
284 self.parser = PyColorize.Parser(style=color_scheme)
284 self.parser = PyColorize.Parser(style=color_scheme)
285 self.set_colors(color_scheme)
285 self.set_colors(color_scheme)
286
286
287 # Set the prompt - the default prompt is '(Pdb)'
287 # Set the prompt - the default prompt is '(Pdb)'
288 self.prompt = prompt
288 self.prompt = prompt
289
289
290 def set_colors(self, scheme):
290 def set_colors(self, scheme):
291 """Shorthand access to the color table scheme selector method."""
291 """Shorthand access to the color table scheme selector method."""
292 self.color_scheme_table.set_active_scheme(scheme)
292 self.color_scheme_table.set_active_scheme(scheme)
293 self.parser.style = scheme
293 self.parser.style = scheme
294
294
295 def interaction(self, frame, traceback):
295 def interaction(self, frame, traceback):
296 try:
296 try:
297 OldPdb.interaction(self, frame, traceback)
297 OldPdb.interaction(self, frame, traceback)
298 except KeyboardInterrupt:
298 except KeyboardInterrupt:
299 sys.stdout.write('\n' + self.shell.get_exception_only())
299 sys.stdout.write('\n' + self.shell.get_exception_only())
300
300
301 def new_do_up(self, arg):
301 def new_do_up(self, arg):
302 OldPdb.do_up(self, arg)
302 OldPdb.do_up(self, arg)
303 do_u = do_up = decorate_fn_with_doc(new_do_up, OldPdb.do_up)
303 do_u = do_up = decorate_fn_with_doc(new_do_up, OldPdb.do_up)
304
304
305 def new_do_down(self, arg):
305 def new_do_down(self, arg):
306 OldPdb.do_down(self, arg)
306 OldPdb.do_down(self, arg)
307
307
308 do_d = do_down = decorate_fn_with_doc(new_do_down, OldPdb.do_down)
308 do_d = do_down = decorate_fn_with_doc(new_do_down, OldPdb.do_down)
309
309
310 def new_do_frame(self, arg):
310 def new_do_frame(self, arg):
311 OldPdb.do_frame(self, arg)
311 OldPdb.do_frame(self, arg)
312
312
313 def new_do_quit(self, arg):
313 def new_do_quit(self, arg):
314
314
315 if hasattr(self, 'old_all_completions'):
315 if hasattr(self, 'old_all_completions'):
316 self.shell.Completer.all_completions=self.old_all_completions
316 self.shell.Completer.all_completions=self.old_all_completions
317
317
318 return OldPdb.do_quit(self, arg)
318 return OldPdb.do_quit(self, arg)
319
319
320 do_q = do_quit = decorate_fn_with_doc(new_do_quit, OldPdb.do_quit)
320 do_q = do_quit = decorate_fn_with_doc(new_do_quit, OldPdb.do_quit)
321
321
322 def new_do_restart(self, arg):
322 def new_do_restart(self, arg):
323 """Restart command. In the context of ipython this is exactly the same
323 """Restart command. In the context of ipython this is exactly the same
324 thing as 'quit'."""
324 thing as 'quit'."""
325 self.msg("Restart doesn't make sense here. Using 'quit' instead.")
325 self.msg("Restart doesn't make sense here. Using 'quit' instead.")
326 return self.do_quit(arg)
326 return self.do_quit(arg)
327
327
328 def print_stack_trace(self, context=None):
328 def print_stack_trace(self, context=None):
329 if context is None:
329 if context is None:
330 context = self.context
330 context = self.context
331 try:
331 try:
332 context=int(context)
332 context=int(context)
333 if context <= 0:
333 if context <= 0:
334 raise ValueError("Context must be a positive integer")
334 raise ValueError("Context must be a positive integer")
335 except (TypeError, ValueError):
335 except (TypeError, ValueError):
336 raise ValueError("Context must be a positive integer")
336 raise ValueError("Context must be a positive integer")
337 try:
337 try:
338 for frame_lineno in self.stack:
338 for frame_lineno in self.stack:
339 self.print_stack_entry(frame_lineno, context=context)
339 self.print_stack_entry(frame_lineno, context=context)
340 except KeyboardInterrupt:
340 except KeyboardInterrupt:
341 pass
341 pass
342
342
343 def print_stack_entry(self,frame_lineno, prompt_prefix='\n-> ',
343 def print_stack_entry(self,frame_lineno, prompt_prefix='\n-> ',
344 context=None):
344 context=None):
345 if context is None:
345 if context is None:
346 context = self.context
346 context = self.context
347 try:
347 try:
348 context=int(context)
348 context=int(context)
349 if context <= 0:
349 if context <= 0:
350 raise ValueError("Context must be a positive integer")
350 raise ValueError("Context must be a positive integer")
351 except (TypeError, ValueError):
351 except (TypeError, ValueError):
352 raise ValueError("Context must be a positive integer")
352 raise ValueError("Context must be a positive integer")
353 print(self.format_stack_entry(frame_lineno, '', context))
353 print(self.format_stack_entry(frame_lineno, '', context))
354
354
355 # vds: >>
355 # vds: >>
356 frame, lineno = frame_lineno
356 frame, lineno = frame_lineno
357 filename = frame.f_code.co_filename
357 filename = frame.f_code.co_filename
358 self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
358 self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
359 # vds: <<
359 # vds: <<
360
360
361 def format_stack_entry(self, frame_lineno, lprefix=': ', context=None):
361 def format_stack_entry(self, frame_lineno, lprefix=': ', context=None):
362 if context is None:
362 if context is None:
363 context = self.context
363 context = self.context
364 try:
364 try:
365 context=int(context)
365 context=int(context)
366 if context <= 0:
366 if context <= 0:
367 print("Context must be a positive integer")
367 print("Context must be a positive integer")
368 except (TypeError, ValueError):
368 except (TypeError, ValueError):
369 print("Context must be a positive integer")
369 print("Context must be a positive integer")
370 try:
370 try:
371 import reprlib # Py 3
371 import reprlib # Py 3
372 except ImportError:
372 except ImportError:
373 import repr as reprlib # Py 2
373 import repr as reprlib # Py 2
374
374
375 ret = []
375 ret = []
376
376
377 Colors = self.color_scheme_table.active_colors
377 Colors = self.color_scheme_table.active_colors
378 ColorsNormal = Colors.Normal
378 ColorsNormal = Colors.Normal
379 tpl_link = u'%s%%s%s' % (Colors.filenameEm, ColorsNormal)
379 tpl_link = u'%s%%s%s' % (Colors.filenameEm, ColorsNormal)
380 tpl_call = u'%s%%s%s%%s%s' % (Colors.vName, Colors.valEm, ColorsNormal)
380 tpl_call = u'%s%%s%s%%s%s' % (Colors.vName, Colors.valEm, ColorsNormal)
381 tpl_line = u'%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal)
381 tpl_line = u'%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal)
382 tpl_line_em = u'%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line,
382 tpl_line_em = u'%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line,
383 ColorsNormal)
383 ColorsNormal)
384
384
385 frame, lineno = frame_lineno
385 frame, lineno = frame_lineno
386
386
387 return_value = ''
387 return_value = ''
388 if '__return__' in frame.f_locals:
388 if '__return__' in frame.f_locals:
389 rv = frame.f_locals['__return__']
389 rv = frame.f_locals['__return__']
390 #return_value += '->'
390 #return_value += '->'
391 return_value += reprlib.repr(rv) + '\n'
391 return_value += reprlib.repr(rv) + '\n'
392 ret.append(return_value)
392 ret.append(return_value)
393
393
394 #s = filename + '(' + `lineno` + ')'
394 #s = filename + '(' + `lineno` + ')'
395 filename = self.canonic(frame.f_code.co_filename)
395 filename = self.canonic(frame.f_code.co_filename)
396 link = tpl_link % py3compat.cast_unicode(filename)
396 link = tpl_link % py3compat.cast_unicode(filename)
397
397
398 if frame.f_code.co_name:
398 if frame.f_code.co_name:
399 func = frame.f_code.co_name
399 func = frame.f_code.co_name
400 else:
400 else:
401 func = "<lambda>"
401 func = "<lambda>"
402
402
403 call = ''
403 call = ''
404 if func != '?':
404 if func != '?':
405 if '__args__' in frame.f_locals:
405 if '__args__' in frame.f_locals:
406 args = reprlib.repr(frame.f_locals['__args__'])
406 args = reprlib.repr(frame.f_locals['__args__'])
407 else:
407 else:
408 args = '()'
408 args = '()'
409 call = tpl_call % (func, args)
409 call = tpl_call % (func, args)
410
410
411 # The level info should be generated in the same format pdb uses, to
411 # The level info should be generated in the same format pdb uses, to
412 # avoid breaking the pdbtrack functionality of python-mode in *emacs.
412 # avoid breaking the pdbtrack functionality of python-mode in *emacs.
413 if frame is self.curframe:
413 if frame is self.curframe:
414 ret.append('> ')
414 ret.append('> ')
415 else:
415 else:
416 ret.append(' ')
416 ret.append(' ')
417 ret.append(u'%s(%s)%s\n' % (link,lineno,call))
417 ret.append(u'%s(%s)%s\n' % (link,lineno,call))
418
418
419 start = lineno - 1 - context//2
419 start = lineno - 1 - context//2
420 lines = linecache.getlines(filename)
420 lines = linecache.getlines(filename)
421 start = min(start, len(lines) - context)
421 start = min(start, len(lines) - context)
422 start = max(start, 0)
422 start = max(start, 0)
423 lines = lines[start : start + context]
423 lines = lines[start : start + context]
424
424
425 for i,line in enumerate(lines):
425 for i,line in enumerate(lines):
426 show_arrow = (start + 1 + i == lineno)
426 show_arrow = (start + 1 + i == lineno)
427 linetpl = (frame is self.curframe or show_arrow) \
427 linetpl = (frame is self.curframe or show_arrow) \
428 and tpl_line_em \
428 and tpl_line_em \
429 or tpl_line
429 or tpl_line
430 ret.append(self.__format_line(linetpl, filename,
430 ret.append(self.__format_line(linetpl, filename,
431 start + 1 + i, line,
431 start + 1 + i, line,
432 arrow = show_arrow) )
432 arrow = show_arrow) )
433 return ''.join(ret)
433 return ''.join(ret)
434
434
435 def __format_line(self, tpl_line, filename, lineno, line, arrow = False):
435 def __format_line(self, tpl_line, filename, lineno, line, arrow = False):
436 bp_mark = ""
436 bp_mark = ""
437 bp_mark_color = ""
437 bp_mark_color = ""
438
438
439 new_line, err = self.parser.format2(line, 'str')
439 new_line, err = self.parser.format2(line, 'str')
440 if not err:
440 if not err:
441 line = new_line
441 line = new_line
442
442
443 bp = None
443 bp = None
444 if lineno in self.get_file_breaks(filename):
444 if lineno in self.get_file_breaks(filename):
445 bps = self.get_breaks(filename, lineno)
445 bps = self.get_breaks(filename, lineno)
446 bp = bps[-1]
446 bp = bps[-1]
447
447
448 if bp:
448 if bp:
449 Colors = self.color_scheme_table.active_colors
449 Colors = self.color_scheme_table.active_colors
450 bp_mark = str(bp.number)
450 bp_mark = str(bp.number)
451 bp_mark_color = Colors.breakpoint_enabled
451 bp_mark_color = Colors.breakpoint_enabled
452 if not bp.enabled:
452 if not bp.enabled:
453 bp_mark_color = Colors.breakpoint_disabled
453 bp_mark_color = Colors.breakpoint_disabled
454
454
455 numbers_width = 7
455 numbers_width = 7
456 if arrow:
456 if arrow:
457 # This is the line with the error
457 # This is the line with the error
458 pad = numbers_width - len(str(lineno)) - len(bp_mark)
458 pad = numbers_width - len(str(lineno)) - len(bp_mark)
459 num = '%s%s' % (make_arrow(pad), str(lineno))
459 num = '%s%s' % (make_arrow(pad), str(lineno))
460 else:
460 else:
461 num = '%*s' % (numbers_width - len(bp_mark), str(lineno))
461 num = '%*s' % (numbers_width - len(bp_mark), str(lineno))
462
462
463 return tpl_line % (bp_mark_color + bp_mark, num, line)
463 return tpl_line % (bp_mark_color + bp_mark, num, line)
464
464
465
465
466 def print_list_lines(self, filename, first, last):
466 def print_list_lines(self, filename, first, last):
467 """The printing (as opposed to the parsing part of a 'list'
467 """The printing (as opposed to the parsing part of a 'list'
468 command."""
468 command."""
469 try:
469 try:
470 Colors = self.color_scheme_table.active_colors
470 Colors = self.color_scheme_table.active_colors
471 ColorsNormal = Colors.Normal
471 ColorsNormal = Colors.Normal
472 tpl_line = '%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal)
472 tpl_line = '%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal)
473 tpl_line_em = '%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line, ColorsNormal)
473 tpl_line_em = '%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line, ColorsNormal)
474 src = []
474 src = []
475 if filename == "<string>" and hasattr(self, "_exec_filename"):
475 if filename == "<string>" and hasattr(self, "_exec_filename"):
476 filename = self._exec_filename
476 filename = self._exec_filename
477
477
478 for lineno in range(first, last+1):
478 for lineno in range(first, last+1):
479 line = linecache.getline(filename, lineno)
479 line = linecache.getline(filename, lineno)
480 if not line:
480 if not line:
481 break
481 break
482
482
483 if lineno == self.curframe.f_lineno:
483 if lineno == self.curframe.f_lineno:
484 line = self.__format_line(tpl_line_em, filename, lineno, line, arrow = True)
484 line = self.__format_line(tpl_line_em, filename, lineno, line, arrow = True)
485 else:
485 else:
486 line = self.__format_line(tpl_line, filename, lineno, line, arrow = False)
486 line = self.__format_line(tpl_line, filename, lineno, line, arrow = False)
487
487
488 src.append(line)
488 src.append(line)
489 self.lineno = lineno
489 self.lineno = lineno
490
490
491 print(''.join(src))
491 print(''.join(src))
492
492
493 except KeyboardInterrupt:
493 except KeyboardInterrupt:
494 pass
494 pass
495
495
496 def do_list(self, arg):
496 def do_list(self, arg):
497 """Print lines of code from the current stack frame
497 """Print lines of code from the current stack frame
498 """
498 """
499 self.lastcmd = 'list'
499 self.lastcmd = 'list'
500 last = None
500 last = None
501 if arg:
501 if arg:
502 try:
502 try:
503 x = eval(arg, {}, {})
503 x = eval(arg, {}, {})
504 if type(x) == type(()):
504 if type(x) == type(()):
505 first, last = x
505 first, last = x
506 first = int(first)
506 first = int(first)
507 last = int(last)
507 last = int(last)
508 if last < first:
508 if last < first:
509 # Assume it's a count
509 # Assume it's a count
510 last = first + last
510 last = first + last
511 else:
511 else:
512 first = max(1, int(x) - 5)
512 first = max(1, int(x) - 5)
513 except:
513 except:
514 print('*** Error in argument:', repr(arg))
514 print('*** Error in argument:', repr(arg))
515 return
515 return
516 elif self.lineno is None:
516 elif self.lineno is None:
517 first = max(1, self.curframe.f_lineno - 5)
517 first = max(1, self.curframe.f_lineno - 5)
518 else:
518 else:
519 first = self.lineno + 1
519 first = self.lineno + 1
520 if last is None:
520 if last is None:
521 last = first + 10
521 last = first + 10
522 self.print_list_lines(self.curframe.f_code.co_filename, first, last)
522 self.print_list_lines(self.curframe.f_code.co_filename, first, last)
523
523
524 # vds: >>
524 # vds: >>
525 lineno = first
525 lineno = first
526 filename = self.curframe.f_code.co_filename
526 filename = self.curframe.f_code.co_filename
527 self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
527 self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
528 # vds: <<
528 # vds: <<
529
529
530 do_l = do_list
530 do_l = do_list
531
531
532 def getsourcelines(self, obj):
532 def getsourcelines(self, obj):
533 lines, lineno = inspect.findsource(obj)
533 lines, lineno = inspect.findsource(obj)
534 if inspect.isframe(obj) and obj.f_globals is obj.f_locals:
534 if inspect.isframe(obj) and obj.f_globals is obj.f_locals:
535 # must be a module frame: do not try to cut a block out of it
535 # must be a module frame: do not try to cut a block out of it
536 return lines, 1
536 return lines, 1
537 elif inspect.ismodule(obj):
537 elif inspect.ismodule(obj):
538 return lines, 1
538 return lines, 1
539 return inspect.getblock(lines[lineno:]), lineno+1
539 return inspect.getblock(lines[lineno:]), lineno+1
540
540
541 def do_longlist(self, arg):
541 def do_longlist(self, arg):
542 """Print lines of code from the current stack frame.
542 """Print lines of code from the current stack frame.
543
543
544 Shows more lines than 'list' does.
544 Shows more lines than 'list' does.
545 """
545 """
546 self.lastcmd = 'longlist'
546 self.lastcmd = 'longlist'
547 try:
547 try:
548 lines, lineno = self.getsourcelines(self.curframe)
548 lines, lineno = self.getsourcelines(self.curframe)
549 except OSError as err:
549 except OSError as err:
550 self.error(err)
550 self.error(err)
551 return
551 return
552 last = lineno + len(lines)
552 last = lineno + len(lines)
553 self.print_list_lines(self.curframe.f_code.co_filename, lineno, last)
553 self.print_list_lines(self.curframe.f_code.co_filename, lineno, last)
554 do_ll = do_longlist
554 do_ll = do_longlist
555
555
556 def do_debug(self, arg):
556 def do_debug(self, arg):
557 """debug code
557 """debug code
558 Enter a recursive debugger that steps through the code
558 Enter a recursive debugger that steps through the code
559 argument (which is an arbitrary expression or statement to be
559 argument (which is an arbitrary expression or statement to be
560 executed in the current environment).
560 executed in the current environment).
561 """
561 """
562 sys.settrace(None)
562 sys.settrace(None)
563 globals = self.curframe.f_globals
563 globals = self.curframe.f_globals
564 locals = self.curframe_locals
564 locals = self.curframe_locals
565 p = self.__class__(completekey=self.completekey,
565 p = self.__class__(completekey=self.completekey,
566 stdin=self.stdin, stdout=self.stdout)
566 stdin=self.stdin, stdout=self.stdout)
567 p.use_rawinput = self.use_rawinput
567 p.use_rawinput = self.use_rawinput
568 p.prompt = "(%s) " % self.prompt.strip()
568 p.prompt = "(%s) " % self.prompt.strip()
569 self.message("ENTERING RECURSIVE DEBUGGER")
569 self.message("ENTERING RECURSIVE DEBUGGER")
570 sys.call_tracing(p.run, (arg, globals, locals))
570 sys.call_tracing(p.run, (arg, globals, locals))
571 self.message("LEAVING RECURSIVE DEBUGGER")
571 self.message("LEAVING RECURSIVE DEBUGGER")
572 sys.settrace(self.trace_dispatch)
572 sys.settrace(self.trace_dispatch)
573 self.lastcmd = p.lastcmd
573 self.lastcmd = p.lastcmd
574
574
575 def do_pdef(self, arg):
575 def do_pdef(self, arg):
576 """Print the call signature for any callable object.
576 """Print the call signature for any callable object.
577
577
578 The debugger interface to %pdef"""
578 The debugger interface to %pdef"""
579 namespaces = [('Locals', self.curframe.f_locals),
579 namespaces = [('Locals', self.curframe.f_locals),
580 ('Globals', self.curframe.f_globals)]
580 ('Globals', self.curframe.f_globals)]
581 self.shell.find_line_magic('pdef')(arg, namespaces=namespaces)
581 self.shell.find_line_magic('pdef')(arg, namespaces=namespaces)
582
582
583 def do_pdoc(self, arg):
583 def do_pdoc(self, arg):
584 """Print the docstring for an object.
584 """Print the docstring for an object.
585
585
586 The debugger interface to %pdoc."""
586 The debugger interface to %pdoc."""
587 namespaces = [('Locals', self.curframe.f_locals),
587 namespaces = [('Locals', self.curframe.f_locals),
588 ('Globals', self.curframe.f_globals)]
588 ('Globals', self.curframe.f_globals)]
589 self.shell.find_line_magic('pdoc')(arg, namespaces=namespaces)
589 self.shell.find_line_magic('pdoc')(arg, namespaces=namespaces)
590
590
591 def do_pfile(self, arg):
591 def do_pfile(self, arg):
592 """Print (or run through pager) the file where an object is defined.
592 """Print (or run through pager) the file where an object is defined.
593
593
594 The debugger interface to %pfile.
594 The debugger interface to %pfile.
595 """
595 """
596 namespaces = [('Locals', self.curframe.f_locals),
596 namespaces = [('Locals', self.curframe.f_locals),
597 ('Globals', self.curframe.f_globals)]
597 ('Globals', self.curframe.f_globals)]
598 self.shell.find_line_magic('pfile')(arg, namespaces=namespaces)
598 self.shell.find_line_magic('pfile')(arg, namespaces=namespaces)
599
599
600 def do_pinfo(self, arg):
600 def do_pinfo(self, arg):
601 """Provide detailed information about an object.
601 """Provide detailed information about an object.
602
602
603 The debugger interface to %pinfo, i.e., obj?."""
603 The debugger interface to %pinfo, i.e., obj?."""
604 namespaces = [('Locals', self.curframe.f_locals),
604 namespaces = [('Locals', self.curframe.f_locals),
605 ('Globals', self.curframe.f_globals)]
605 ('Globals', self.curframe.f_globals)]
606 self.shell.find_line_magic('pinfo')(arg, namespaces=namespaces)
606 self.shell.find_line_magic('pinfo')(arg, namespaces=namespaces)
607
607
608 def do_pinfo2(self, arg):
608 def do_pinfo2(self, arg):
609 """Provide extra detailed information about an object.
609 """Provide extra detailed information about an object.
610
610
611 The debugger interface to %pinfo2, i.e., obj??."""
611 The debugger interface to %pinfo2, i.e., obj??."""
612 namespaces = [('Locals', self.curframe.f_locals),
612 namespaces = [('Locals', self.curframe.f_locals),
613 ('Globals', self.curframe.f_globals)]
613 ('Globals', self.curframe.f_globals)]
614 self.shell.find_line_magic('pinfo2')(arg, namespaces=namespaces)
614 self.shell.find_line_magic('pinfo2')(arg, namespaces=namespaces)
615
615
616 def do_psource(self, arg):
616 def do_psource(self, arg):
617 """Print (or run through pager) the source code for an object."""
617 """Print (or run through pager) the source code for an object."""
618 namespaces = [('Locals', self.curframe.f_locals),
618 namespaces = [('Locals', self.curframe.f_locals),
619 ('Globals', self.curframe.f_globals)]
619 ('Globals', self.curframe.f_globals)]
620 self.shell.find_line_magic('psource')(arg, namespaces=namespaces)
620 self.shell.find_line_magic('psource')(arg, namespaces=namespaces)
621
621
622 def do_where(self, arg):
622 def do_where(self, arg):
623 """w(here)
623 """w(here)
624 Print a stack trace, with the most recent frame at the bottom.
624 Print a stack trace, with the most recent frame at the bottom.
625 An arrow indicates the "current frame", which determines the
625 An arrow indicates the "current frame", which determines the
626 context of most commands. 'bt' is an alias for this command.
626 context of most commands. 'bt' is an alias for this command.
627
627
628 Take a number as argument as an (optional) number of context line to
628 Take a number as argument as an (optional) number of context line to
629 print"""
629 print"""
630 if arg:
630 if arg:
631 context = int(arg)
631 context = int(arg)
632 self.print_stack_trace(context)
632 self.print_stack_trace(context)
633 else:
633 else:
634 self.print_stack_trace()
634 self.print_stack_trace()
635
635
636 do_w = do_where
636 do_w = do_where
637
637
638
638
639 def set_trace(frame=None):
639 def set_trace(frame=None):
640 """
640 """
641 Start debugging from `frame`.
641 Start debugging from `frame`.
642
642
643 If frame is not specified, debugging starts from caller's frame.
643 If frame is not specified, debugging starts from caller's frame.
644 """
644 """
645 Pdb().set_trace(frame or sys._getframe().f_back)
645 Pdb().set_trace(frame or sys._getframe().f_back)
@@ -1,438 +1,438 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Utilities for path handling.
3 Utilities for path handling.
4 """
4 """
5
5
6 # Copyright (c) IPython Development Team.
6 # Copyright (c) IPython Development Team.
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8
8
9 import os
9 import os
10 import sys
10 import sys
11 import errno
11 import errno
12 import shutil
12 import shutil
13 import random
13 import random
14 import glob
14 import glob
15 from warnings import warn
15 from warnings import warn
16
16
17 from IPython.utils.process import system
17 from IPython.utils.process import system
18 from IPython.utils import py3compat
18 from IPython.utils import py3compat
19 from IPython.utils.decorators import undoc
19 from IPython.utils.decorators import undoc
20
20
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22 # Code
22 # Code
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24
24
25 fs_encoding = sys.getfilesystemencoding()
25 fs_encoding = sys.getfilesystemencoding()
26
26
27 def _writable_dir(path):
27 def _writable_dir(path):
28 """Whether `path` is a directory, to which the user has write access."""
28 """Whether `path` is a directory, to which the user has write access."""
29 return os.path.isdir(path) and os.access(path, os.W_OK)
29 return os.path.isdir(path) and os.access(path, os.W_OK)
30
30
31 if sys.platform == 'win32':
31 if sys.platform == 'win32':
32 def _get_long_path_name(path):
32 def _get_long_path_name(path):
33 """Get a long path name (expand ~) on Windows using ctypes.
33 """Get a long path name (expand ~) on Windows using ctypes.
34
34
35 Examples
35 Examples
36 --------
36 --------
37
37
38 >>> get_long_path_name('c:\\docume~1')
38 >>> get_long_path_name('c:\\docume~1')
39 'c:\\\\Documents and Settings'
39 'c:\\\\Documents and Settings'
40
40
41 """
41 """
42 try:
42 try:
43 import ctypes
43 import ctypes
44 except ImportError:
44 except ImportError:
45 raise ImportError('you need to have ctypes installed for this to work')
45 raise ImportError('you need to have ctypes installed for this to work')
46 _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW
46 _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW
47 _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p,
47 _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p,
48 ctypes.c_uint ]
48 ctypes.c_uint ]
49
49
50 buf = ctypes.create_unicode_buffer(260)
50 buf = ctypes.create_unicode_buffer(260)
51 rv = _GetLongPathName(path, buf, 260)
51 rv = _GetLongPathName(path, buf, 260)
52 if rv == 0 or rv > 260:
52 if rv == 0 or rv > 260:
53 return path
53 return path
54 else:
54 else:
55 return buf.value
55 return buf.value
56 else:
56 else:
57 def _get_long_path_name(path):
57 def _get_long_path_name(path):
58 """Dummy no-op."""
58 """Dummy no-op."""
59 return path
59 return path
60
60
61
61
62
62
63 def get_long_path_name(path):
63 def get_long_path_name(path):
64 """Expand a path into its long form.
64 """Expand a path into its long form.
65
65
66 On Windows this expands any ~ in the paths. On other platforms, it is
66 On Windows this expands any ~ in the paths. On other platforms, it is
67 a null operation.
67 a null operation.
68 """
68 """
69 return _get_long_path_name(path)
69 return _get_long_path_name(path)
70
70
71
71
72 def unquote_filename(name, win32=(sys.platform=='win32')):
72 def unquote_filename(name, win32=(sys.platform=='win32')):
73 """ On Windows, remove leading and trailing quotes from filenames.
73 """ On Windows, remove leading and trailing quotes from filenames.
74
74
75 This function has been deprecated and should not be used any more:
75 This function has been deprecated and should not be used any more:
76 unquoting is now taken care of by :func:`IPython.utils.process.arg_split`.
76 unquoting is now taken care of by :func:`IPython.utils.process.arg_split`.
77 """
77 """
78 warn("'unquote_filename' is deprecated since IPython 5.0 and should not "
78 warn("'unquote_filename' is deprecated since IPython 5.0 and should not "
79 "be used anymore", DeprecationWarning, stacklevel=2)
79 "be used anymore", DeprecationWarning, stacklevel=2)
80 if win32:
80 if win32:
81 if name.startswith(("'", '"')) and name.endswith(("'", '"')):
81 if name.startswith(("'", '"')) and name.endswith(("'", '"')):
82 name = name[1:-1]
82 name = name[1:-1]
83 return name
83 return name
84
84
85
85
86 def compress_user(path):
86 def compress_user(path):
87 """Reverse of :func:`os.path.expanduser`
87 """Reverse of :func:`os.path.expanduser`
88 """
88 """
89 home = os.path.expanduser('~')
89 home = os.path.expanduser('~')
90 if path.startswith(home):
90 if path.startswith(home):
91 path = "~" + path[len(home):]
91 path = "~" + path[len(home):]
92 return path
92 return path
93
93
94 def get_py_filename(name, force_win32=None):
94 def get_py_filename(name, force_win32=None):
95 """Return a valid python filename in the current directory.
95 """Return a valid python filename in the current directory.
96
96
97 If the given name is not a file, it adds '.py' and searches again.
97 If the given name is not a file, it adds '.py' and searches again.
98 Raises IOError with an informative message if the file isn't found.
98 Raises IOError with an informative message if the file isn't found.
99 """
99 """
100
100
101 name = os.path.expanduser(name)
101 name = os.path.expanduser(name)
102 if force_win32 is not None:
102 if force_win32 is not None:
103 warn("The 'force_win32' argument to 'get_py_filename' is deprecated "
103 warn("The 'force_win32' argument to 'get_py_filename' is deprecated "
104 "since IPython 5.0 and should not be used anymore",
104 "since IPython 5.0 and should not be used anymore",
105 DeprecationWarning, stacklevel=2)
105 DeprecationWarning, stacklevel=2)
106 if not os.path.isfile(name) and not name.endswith('.py'):
106 if not os.path.isfile(name) and not name.endswith('.py'):
107 name += '.py'
107 name += '.py'
108 if os.path.isfile(name):
108 if os.path.isfile(name):
109 return name
109 return name
110 else:
110 else:
111 raise IOError('File `%r` not found.' % name)
111 raise IOError('File `%r` not found.' % name)
112
112
113
113
114 def filefind(filename, path_dirs=None):
114 def filefind(filename, path_dirs=None):
115 """Find a file by looking through a sequence of paths.
115 """Find a file by looking through a sequence of paths.
116
116
117 This iterates through a sequence of paths looking for a file and returns
117 This iterates through a sequence of paths looking for a file and returns
118 the full, absolute path of the first occurrence of the file. If no set of
118 the full, absolute path of the first occurrence of the file. If no set of
119 path dirs is given, the filename is tested as is, after running through
119 path dirs is given, the filename is tested as is, after running through
120 :func:`expandvars` and :func:`expanduser`. Thus a simple call::
120 :func:`expandvars` and :func:`expanduser`. Thus a simple call::
121
121
122 filefind('myfile.txt')
122 filefind('myfile.txt')
123
123
124 will find the file in the current working dir, but::
124 will find the file in the current working dir, but::
125
125
126 filefind('~/myfile.txt')
126 filefind('~/myfile.txt')
127
127
128 Will find the file in the users home directory. This function does not
128 Will find the file in the users home directory. This function does not
129 automatically try any paths, such as the cwd or the user's home directory.
129 automatically try any paths, such as the cwd or the user's home directory.
130
130
131 Parameters
131 Parameters
132 ----------
132 ----------
133 filename : str
133 filename : str
134 The filename to look for.
134 The filename to look for.
135 path_dirs : str, None or sequence of str
135 path_dirs : str, None or sequence of str
136 The sequence of paths to look for the file in. If None, the filename
136 The sequence of paths to look for the file in. If None, the filename
137 need to be absolute or be in the cwd. If a string, the string is
137 need to be absolute or be in the cwd. If a string, the string is
138 put into a sequence and the searched. If a sequence, walk through
138 put into a sequence and the searched. If a sequence, walk through
139 each element and join with ``filename``, calling :func:`expandvars`
139 each element and join with ``filename``, calling :func:`expandvars`
140 and :func:`expanduser` before testing for existence.
140 and :func:`expanduser` before testing for existence.
141
141
142 Returns
142 Returns
143 -------
143 -------
144 Raises :exc:`IOError` or returns absolute path to file.
144 Raises :exc:`IOError` or returns absolute path to file.
145 """
145 """
146
146
147 # If paths are quoted, abspath gets confused, strip them...
147 # If paths are quoted, abspath gets confused, strip them...
148 filename = filename.strip('"').strip("'")
148 filename = filename.strip('"').strip("'")
149 # If the input is an absolute path, just check it exists
149 # If the input is an absolute path, just check it exists
150 if os.path.isabs(filename) and os.path.isfile(filename):
150 if os.path.isabs(filename) and os.path.isfile(filename):
151 return filename
151 return filename
152
152
153 if path_dirs is None:
153 if path_dirs is None:
154 path_dirs = ("",)
154 path_dirs = ("",)
155 elif isinstance(path_dirs, str):
155 elif isinstance(path_dirs, str):
156 path_dirs = (path_dirs,)
156 path_dirs = (path_dirs,)
157
157
158 for path in path_dirs:
158 for path in path_dirs:
159 if path == '.': path = os.getcwd()
159 if path == '.': path = os.getcwd()
160 testname = expand_path(os.path.join(path, filename))
160 testname = expand_path(os.path.join(path, filename))
161 if os.path.isfile(testname):
161 if os.path.isfile(testname):
162 return os.path.abspath(testname)
162 return os.path.abspath(testname)
163
163
164 raise IOError("File %r does not exist in any of the search paths: %r" %
164 raise IOError("File %r does not exist in any of the search paths: %r" %
165 (filename, path_dirs) )
165 (filename, path_dirs) )
166
166
167
167
168 class HomeDirError(Exception):
168 class HomeDirError(Exception):
169 pass
169 pass
170
170
171
171
172 def get_home_dir(require_writable=False):
172 def get_home_dir(require_writable=False):
173 """Return the 'home' directory, as a unicode string.
173 """Return the 'home' directory, as a unicode string.
174
174
175 Uses os.path.expanduser('~'), and checks for writability.
175 Uses os.path.expanduser('~'), and checks for writability.
176
176
177 See stdlib docs for how this is determined.
177 See stdlib docs for how this is determined.
178 $HOME is first priority on *ALL* platforms.
178 $HOME is first priority on *ALL* platforms.
179
179
180 Parameters
180 Parameters
181 ----------
181 ----------
182
182
183 require_writable : bool [default: False]
183 require_writable : bool [default: False]
184 if True:
184 if True:
185 guarantees the return value is a writable directory, otherwise
185 guarantees the return value is a writable directory, otherwise
186 raises HomeDirError
186 raises HomeDirError
187 if False:
187 if False:
188 The path is resolved, but it is not guaranteed to exist or be writable.
188 The path is resolved, but it is not guaranteed to exist or be writable.
189 """
189 """
190
190
191 homedir = os.path.expanduser('~')
191 homedir = os.path.expanduser('~')
192 # Next line will make things work even when /home/ is a symlink to
192 # Next line will make things work even when /home/ is a symlink to
193 # /usr/home as it is on FreeBSD, for example
193 # /usr/home as it is on FreeBSD, for example
194 homedir = os.path.realpath(homedir)
194 homedir = os.path.realpath(homedir)
195
195
196 if not _writable_dir(homedir) and os.name == 'nt':
196 if not _writable_dir(homedir) and os.name == 'nt':
197 # expanduser failed, use the registry to get the 'My Documents' folder.
197 # expanduser failed, use the registry to get the 'My Documents' folder.
198 try:
198 try:
199 try:
199 try:
200 import winreg as wreg # Py 3
200 import winreg as wreg # Py 3
201 except ImportError:
201 except ImportError:
202 import _winreg as wreg # Py 2
202 import _winreg as wreg # Py 2
203 key = wreg.OpenKey(
203 key = wreg.OpenKey(
204 wreg.HKEY_CURRENT_USER,
204 wreg.HKEY_CURRENT_USER,
205 "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
205 r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
206 )
206 )
207 homedir = wreg.QueryValueEx(key,'Personal')[0]
207 homedir = wreg.QueryValueEx(key,'Personal')[0]
208 key.Close()
208 key.Close()
209 except:
209 except:
210 pass
210 pass
211
211
212 if (not require_writable) or _writable_dir(homedir):
212 if (not require_writable) or _writable_dir(homedir):
213 return py3compat.cast_unicode(homedir, fs_encoding)
213 return py3compat.cast_unicode(homedir, fs_encoding)
214 else:
214 else:
215 raise HomeDirError('%s is not a writable dir, '
215 raise HomeDirError('%s is not a writable dir, '
216 'set $HOME environment variable to override' % homedir)
216 'set $HOME environment variable to override' % homedir)
217
217
218 def get_xdg_dir():
218 def get_xdg_dir():
219 """Return the XDG_CONFIG_HOME, if it is defined and exists, else None.
219 """Return the XDG_CONFIG_HOME, if it is defined and exists, else None.
220
220
221 This is only for non-OS X posix (Linux,Unix,etc.) systems.
221 This is only for non-OS X posix (Linux,Unix,etc.) systems.
222 """
222 """
223
223
224 env = os.environ
224 env = os.environ
225
225
226 if os.name == 'posix' and sys.platform != 'darwin':
226 if os.name == 'posix' and sys.platform != 'darwin':
227 # Linux, Unix, AIX, etc.
227 # Linux, Unix, AIX, etc.
228 # use ~/.config if empty OR not set
228 # use ~/.config if empty OR not set
229 xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config')
229 xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config')
230 if xdg and _writable_dir(xdg):
230 if xdg and _writable_dir(xdg):
231 return py3compat.cast_unicode(xdg, fs_encoding)
231 return py3compat.cast_unicode(xdg, fs_encoding)
232
232
233 return None
233 return None
234
234
235
235
236 def get_xdg_cache_dir():
236 def get_xdg_cache_dir():
237 """Return the XDG_CACHE_HOME, if it is defined and exists, else None.
237 """Return the XDG_CACHE_HOME, if it is defined and exists, else None.
238
238
239 This is only for non-OS X posix (Linux,Unix,etc.) systems.
239 This is only for non-OS X posix (Linux,Unix,etc.) systems.
240 """
240 """
241
241
242 env = os.environ
242 env = os.environ
243
243
244 if os.name == 'posix' and sys.platform != 'darwin':
244 if os.name == 'posix' and sys.platform != 'darwin':
245 # Linux, Unix, AIX, etc.
245 # Linux, Unix, AIX, etc.
246 # use ~/.cache if empty OR not set
246 # use ~/.cache if empty OR not set
247 xdg = env.get("XDG_CACHE_HOME", None) or os.path.join(get_home_dir(), '.cache')
247 xdg = env.get("XDG_CACHE_HOME", None) or os.path.join(get_home_dir(), '.cache')
248 if xdg and _writable_dir(xdg):
248 if xdg and _writable_dir(xdg):
249 return py3compat.cast_unicode(xdg, fs_encoding)
249 return py3compat.cast_unicode(xdg, fs_encoding)
250
250
251 return None
251 return None
252
252
253
253
254 @undoc
254 @undoc
255 def get_ipython_dir():
255 def get_ipython_dir():
256 warn("get_ipython_dir has moved to the IPython.paths module since IPython 4.0.", stacklevel=2)
256 warn("get_ipython_dir has moved to the IPython.paths module since IPython 4.0.", stacklevel=2)
257 from IPython.paths import get_ipython_dir
257 from IPython.paths import get_ipython_dir
258 return get_ipython_dir()
258 return get_ipython_dir()
259
259
260 @undoc
260 @undoc
261 def get_ipython_cache_dir():
261 def get_ipython_cache_dir():
262 warn("get_ipython_cache_dir has moved to the IPython.paths module since IPython 4.0.", stacklevel=2)
262 warn("get_ipython_cache_dir has moved to the IPython.paths module since IPython 4.0.", stacklevel=2)
263 from IPython.paths import get_ipython_cache_dir
263 from IPython.paths import get_ipython_cache_dir
264 return get_ipython_cache_dir()
264 return get_ipython_cache_dir()
265
265
266 @undoc
266 @undoc
267 def get_ipython_package_dir():
267 def get_ipython_package_dir():
268 warn("get_ipython_package_dir has moved to the IPython.paths module since IPython 4.0.", stacklevel=2)
268 warn("get_ipython_package_dir has moved to the IPython.paths module since IPython 4.0.", stacklevel=2)
269 from IPython.paths import get_ipython_package_dir
269 from IPython.paths import get_ipython_package_dir
270 return get_ipython_package_dir()
270 return get_ipython_package_dir()
271
271
272 @undoc
272 @undoc
273 def get_ipython_module_path(module_str):
273 def get_ipython_module_path(module_str):
274 warn("get_ipython_module_path has moved to the IPython.paths module since IPython 4.0.", stacklevel=2)
274 warn("get_ipython_module_path has moved to the IPython.paths module since IPython 4.0.", stacklevel=2)
275 from IPython.paths import get_ipython_module_path
275 from IPython.paths import get_ipython_module_path
276 return get_ipython_module_path(module_str)
276 return get_ipython_module_path(module_str)
277
277
278 @undoc
278 @undoc
279 def locate_profile(profile='default'):
279 def locate_profile(profile='default'):
280 warn("locate_profile has moved to the IPython.paths module since IPython 4.0.", stacklevel=2)
280 warn("locate_profile has moved to the IPython.paths module since IPython 4.0.", stacklevel=2)
281 from IPython.paths import locate_profile
281 from IPython.paths import locate_profile
282 return locate_profile(profile=profile)
282 return locate_profile(profile=profile)
283
283
284 def expand_path(s):
284 def expand_path(s):
285 """Expand $VARS and ~names in a string, like a shell
285 """Expand $VARS and ~names in a string, like a shell
286
286
287 :Examples:
287 :Examples:
288
288
289 In [2]: os.environ['FOO']='test'
289 In [2]: os.environ['FOO']='test'
290
290
291 In [3]: expand_path('variable FOO is $FOO')
291 In [3]: expand_path('variable FOO is $FOO')
292 Out[3]: 'variable FOO is test'
292 Out[3]: 'variable FOO is test'
293 """
293 """
294 # This is a pretty subtle hack. When expand user is given a UNC path
294 # This is a pretty subtle hack. When expand user is given a UNC path
295 # on Windows (\\server\share$\%username%), os.path.expandvars, removes
295 # on Windows (\\server\share$\%username%), os.path.expandvars, removes
296 # the $ to get (\\server\share\%username%). I think it considered $
296 # the $ to get (\\server\share\%username%). I think it considered $
297 # alone an empty var. But, we need the $ to remains there (it indicates
297 # alone an empty var. But, we need the $ to remains there (it indicates
298 # a hidden share).
298 # a hidden share).
299 if os.name=='nt':
299 if os.name=='nt':
300 s = s.replace('$\\', 'IPYTHON_TEMP')
300 s = s.replace('$\\', 'IPYTHON_TEMP')
301 s = os.path.expandvars(os.path.expanduser(s))
301 s = os.path.expandvars(os.path.expanduser(s))
302 if os.name=='nt':
302 if os.name=='nt':
303 s = s.replace('IPYTHON_TEMP', '$\\')
303 s = s.replace('IPYTHON_TEMP', '$\\')
304 return s
304 return s
305
305
306
306
307 def unescape_glob(string):
307 def unescape_glob(string):
308 """Unescape glob pattern in `string`."""
308 """Unescape glob pattern in `string`."""
309 def unescape(s):
309 def unescape(s):
310 for pattern in '*[]!?':
310 for pattern in '*[]!?':
311 s = s.replace(r'\{0}'.format(pattern), pattern)
311 s = s.replace(r'\{0}'.format(pattern), pattern)
312 return s
312 return s
313 return '\\'.join(map(unescape, string.split('\\\\')))
313 return '\\'.join(map(unescape, string.split('\\\\')))
314
314
315
315
316 def shellglob(args):
316 def shellglob(args):
317 """
317 """
318 Do glob expansion for each element in `args` and return a flattened list.
318 Do glob expansion for each element in `args` and return a flattened list.
319
319
320 Unmatched glob pattern will remain as-is in the returned list.
320 Unmatched glob pattern will remain as-is in the returned list.
321
321
322 """
322 """
323 expanded = []
323 expanded = []
324 # Do not unescape backslash in Windows as it is interpreted as
324 # Do not unescape backslash in Windows as it is interpreted as
325 # path separator:
325 # path separator:
326 unescape = unescape_glob if sys.platform != 'win32' else lambda x: x
326 unescape = unescape_glob if sys.platform != 'win32' else lambda x: x
327 for a in args:
327 for a in args:
328 expanded.extend(glob.glob(a) or [unescape(a)])
328 expanded.extend(glob.glob(a) or [unescape(a)])
329 return expanded
329 return expanded
330
330
331
331
332 def target_outdated(target,deps):
332 def target_outdated(target,deps):
333 """Determine whether a target is out of date.
333 """Determine whether a target is out of date.
334
334
335 target_outdated(target,deps) -> 1/0
335 target_outdated(target,deps) -> 1/0
336
336
337 deps: list of filenames which MUST exist.
337 deps: list of filenames which MUST exist.
338 target: single filename which may or may not exist.
338 target: single filename which may or may not exist.
339
339
340 If target doesn't exist or is older than any file listed in deps, return
340 If target doesn't exist or is older than any file listed in deps, return
341 true, otherwise return false.
341 true, otherwise return false.
342 """
342 """
343 try:
343 try:
344 target_time = os.path.getmtime(target)
344 target_time = os.path.getmtime(target)
345 except os.error:
345 except os.error:
346 return 1
346 return 1
347 for dep in deps:
347 for dep in deps:
348 dep_time = os.path.getmtime(dep)
348 dep_time = os.path.getmtime(dep)
349 if dep_time > target_time:
349 if dep_time > target_time:
350 #print "For target",target,"Dep failed:",dep # dbg
350 #print "For target",target,"Dep failed:",dep # dbg
351 #print "times (dep,tar):",dep_time,target_time # dbg
351 #print "times (dep,tar):",dep_time,target_time # dbg
352 return 1
352 return 1
353 return 0
353 return 0
354
354
355
355
356 def target_update(target,deps,cmd):
356 def target_update(target,deps,cmd):
357 """Update a target with a given command given a list of dependencies.
357 """Update a target with a given command given a list of dependencies.
358
358
359 target_update(target,deps,cmd) -> runs cmd if target is outdated.
359 target_update(target,deps,cmd) -> runs cmd if target is outdated.
360
360
361 This is just a wrapper around target_outdated() which calls the given
361 This is just a wrapper around target_outdated() which calls the given
362 command if target is outdated."""
362 command if target is outdated."""
363
363
364 if target_outdated(target,deps):
364 if target_outdated(target,deps):
365 system(cmd)
365 system(cmd)
366
366
367
367
368 ENOLINK = 1998
368 ENOLINK = 1998
369
369
370 def link(src, dst):
370 def link(src, dst):
371 """Hard links ``src`` to ``dst``, returning 0 or errno.
371 """Hard links ``src`` to ``dst``, returning 0 or errno.
372
372
373 Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't
373 Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't
374 supported by the operating system.
374 supported by the operating system.
375 """
375 """
376
376
377 if not hasattr(os, "link"):
377 if not hasattr(os, "link"):
378 return ENOLINK
378 return ENOLINK
379 link_errno = 0
379 link_errno = 0
380 try:
380 try:
381 os.link(src, dst)
381 os.link(src, dst)
382 except OSError as e:
382 except OSError as e:
383 link_errno = e.errno
383 link_errno = e.errno
384 return link_errno
384 return link_errno
385
385
386
386
387 def link_or_copy(src, dst):
387 def link_or_copy(src, dst):
388 """Attempts to hardlink ``src`` to ``dst``, copying if the link fails.
388 """Attempts to hardlink ``src`` to ``dst``, copying if the link fails.
389
389
390 Attempts to maintain the semantics of ``shutil.copy``.
390 Attempts to maintain the semantics of ``shutil.copy``.
391
391
392 Because ``os.link`` does not overwrite files, a unique temporary file
392 Because ``os.link`` does not overwrite files, a unique temporary file
393 will be used if the target already exists, then that file will be moved
393 will be used if the target already exists, then that file will be moved
394 into place.
394 into place.
395 """
395 """
396
396
397 if os.path.isdir(dst):
397 if os.path.isdir(dst):
398 dst = os.path.join(dst, os.path.basename(src))
398 dst = os.path.join(dst, os.path.basename(src))
399
399
400 link_errno = link(src, dst)
400 link_errno = link(src, dst)
401 if link_errno == errno.EEXIST:
401 if link_errno == errno.EEXIST:
402 if os.stat(src).st_ino == os.stat(dst).st_ino:
402 if os.stat(src).st_ino == os.stat(dst).st_ino:
403 # dst is already a hard link to the correct file, so we don't need
403 # dst is already a hard link to the correct file, so we don't need
404 # to do anything else. If we try to link and rename the file
404 # to do anything else. If we try to link and rename the file
405 # anyway, we get duplicate files - see http://bugs.python.org/issue21876
405 # anyway, we get duplicate files - see http://bugs.python.org/issue21876
406 return
406 return
407
407
408 new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), )
408 new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), )
409 try:
409 try:
410 link_or_copy(src, new_dst)
410 link_or_copy(src, new_dst)
411 except:
411 except:
412 try:
412 try:
413 os.remove(new_dst)
413 os.remove(new_dst)
414 except OSError:
414 except OSError:
415 pass
415 pass
416 raise
416 raise
417 os.rename(new_dst, dst)
417 os.rename(new_dst, dst)
418 elif link_errno != 0:
418 elif link_errno != 0:
419 # Either link isn't supported, or the filesystem doesn't support
419 # Either link isn't supported, or the filesystem doesn't support
420 # linking, or 'src' and 'dst' are on different filesystems.
420 # linking, or 'src' and 'dst' are on different filesystems.
421 shutil.copy(src, dst)
421 shutil.copy(src, dst)
422
422
423 def ensure_dir_exists(path, mode=0o755):
423 def ensure_dir_exists(path, mode=0o755):
424 """ensure that a directory exists
424 """ensure that a directory exists
425
425
426 If it doesn't exist, try to create it and protect against a race condition
426 If it doesn't exist, try to create it and protect against a race condition
427 if another process is doing the same.
427 if another process is doing the same.
428
428
429 The default permissions are 755, which differ from os.makedirs default of 777.
429 The default permissions are 755, which differ from os.makedirs default of 777.
430 """
430 """
431 if not os.path.exists(path):
431 if not os.path.exists(path):
432 try:
432 try:
433 os.makedirs(path, mode=mode)
433 os.makedirs(path, mode=mode)
434 except OSError as e:
434 except OSError as e:
435 if e.errno != errno.EEXIST:
435 if e.errno != errno.EEXIST:
436 raise
436 raise
437 elif not os.path.isdir(path):
437 elif not os.path.isdir(path):
438 raise IOError("%r exists but is not a directory" % path)
438 raise IOError("%r exists but is not a directory" % path)
@@ -1,772 +1,772 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Utilities for working with strings and text.
3 Utilities for working with strings and text.
4
4
5 Inheritance diagram:
5 Inheritance diagram:
6
6
7 .. inheritance-diagram:: IPython.utils.text
7 .. inheritance-diagram:: IPython.utils.text
8 :parts: 3
8 :parts: 3
9 """
9 """
10
10
11 import os
11 import os
12 import re
12 import re
13 import sys
13 import sys
14 import textwrap
14 import textwrap
15 from string import Formatter
15 from string import Formatter
16 from pathlib import Path
16 from pathlib import Path
17
17
18 from IPython.utils import py3compat
18 from IPython.utils import py3compat
19
19
20 # datetime.strftime date format for ipython
20 # datetime.strftime date format for ipython
21 if sys.platform == 'win32':
21 if sys.platform == 'win32':
22 date_format = "%B %d, %Y"
22 date_format = "%B %d, %Y"
23 else:
23 else:
24 date_format = "%B %-d, %Y"
24 date_format = "%B %-d, %Y"
25
25
26 class LSString(str):
26 class LSString(str):
27 """String derivative with a special access attributes.
27 """String derivative with a special access attributes.
28
28
29 These are normal strings, but with the special attributes:
29 These are normal strings, but with the special attributes:
30
30
31 .l (or .list) : value as list (split on newlines).
31 .l (or .list) : value as list (split on newlines).
32 .n (or .nlstr): original value (the string itself).
32 .n (or .nlstr): original value (the string itself).
33 .s (or .spstr): value as whitespace-separated string.
33 .s (or .spstr): value as whitespace-separated string.
34 .p (or .paths): list of path objects (requires path.py package)
34 .p (or .paths): list of path objects (requires path.py package)
35
35
36 Any values which require transformations are computed only once and
36 Any values which require transformations are computed only once and
37 cached.
37 cached.
38
38
39 Such strings are very useful to efficiently interact with the shell, which
39 Such strings are very useful to efficiently interact with the shell, which
40 typically only understands whitespace-separated options for commands."""
40 typically only understands whitespace-separated options for commands."""
41
41
42 def get_list(self):
42 def get_list(self):
43 try:
43 try:
44 return self.__list
44 return self.__list
45 except AttributeError:
45 except AttributeError:
46 self.__list = self.split('\n')
46 self.__list = self.split('\n')
47 return self.__list
47 return self.__list
48
48
49 l = list = property(get_list)
49 l = list = property(get_list)
50
50
51 def get_spstr(self):
51 def get_spstr(self):
52 try:
52 try:
53 return self.__spstr
53 return self.__spstr
54 except AttributeError:
54 except AttributeError:
55 self.__spstr = self.replace('\n',' ')
55 self.__spstr = self.replace('\n',' ')
56 return self.__spstr
56 return self.__spstr
57
57
58 s = spstr = property(get_spstr)
58 s = spstr = property(get_spstr)
59
59
60 def get_nlstr(self):
60 def get_nlstr(self):
61 return self
61 return self
62
62
63 n = nlstr = property(get_nlstr)
63 n = nlstr = property(get_nlstr)
64
64
65 def get_paths(self):
65 def get_paths(self):
66 try:
66 try:
67 return self.__paths
67 return self.__paths
68 except AttributeError:
68 except AttributeError:
69 self.__paths = [Path(p) for p in self.split('\n') if os.path.exists(p)]
69 self.__paths = [Path(p) for p in self.split('\n') if os.path.exists(p)]
70 return self.__paths
70 return self.__paths
71
71
72 p = paths = property(get_paths)
72 p = paths = property(get_paths)
73
73
74 # FIXME: We need to reimplement type specific displayhook and then add this
74 # FIXME: We need to reimplement type specific displayhook and then add this
75 # back as a custom printer. This should also be moved outside utils into the
75 # back as a custom printer. This should also be moved outside utils into the
76 # core.
76 # core.
77
77
78 # def print_lsstring(arg):
78 # def print_lsstring(arg):
79 # """ Prettier (non-repr-like) and more informative printer for LSString """
79 # """ Prettier (non-repr-like) and more informative printer for LSString """
80 # print "LSString (.p, .n, .l, .s available). Value:"
80 # print "LSString (.p, .n, .l, .s available). Value:"
81 # print arg
81 # print arg
82 #
82 #
83 #
83 #
84 # print_lsstring = result_display.when_type(LSString)(print_lsstring)
84 # print_lsstring = result_display.when_type(LSString)(print_lsstring)
85
85
86
86
87 class SList(list):
87 class SList(list):
88 """List derivative with a special access attributes.
88 """List derivative with a special access attributes.
89
89
90 These are normal lists, but with the special attributes:
90 These are normal lists, but with the special attributes:
91
91
92 * .l (or .list) : value as list (the list itself).
92 * .l (or .list) : value as list (the list itself).
93 * .n (or .nlstr): value as a string, joined on newlines.
93 * .n (or .nlstr): value as a string, joined on newlines.
94 * .s (or .spstr): value as a string, joined on spaces.
94 * .s (or .spstr): value as a string, joined on spaces.
95 * .p (or .paths): list of path objects (requires path.py package)
95 * .p (or .paths): list of path objects (requires path.py package)
96
96
97 Any values which require transformations are computed only once and
97 Any values which require transformations are computed only once and
98 cached."""
98 cached."""
99
99
100 def get_list(self):
100 def get_list(self):
101 return self
101 return self
102
102
103 l = list = property(get_list)
103 l = list = property(get_list)
104
104
105 def get_spstr(self):
105 def get_spstr(self):
106 try:
106 try:
107 return self.__spstr
107 return self.__spstr
108 except AttributeError:
108 except AttributeError:
109 self.__spstr = ' '.join(self)
109 self.__spstr = ' '.join(self)
110 return self.__spstr
110 return self.__spstr
111
111
112 s = spstr = property(get_spstr)
112 s = spstr = property(get_spstr)
113
113
114 def get_nlstr(self):
114 def get_nlstr(self):
115 try:
115 try:
116 return self.__nlstr
116 return self.__nlstr
117 except AttributeError:
117 except AttributeError:
118 self.__nlstr = '\n'.join(self)
118 self.__nlstr = '\n'.join(self)
119 return self.__nlstr
119 return self.__nlstr
120
120
121 n = nlstr = property(get_nlstr)
121 n = nlstr = property(get_nlstr)
122
122
123 def get_paths(self):
123 def get_paths(self):
124 try:
124 try:
125 return self.__paths
125 return self.__paths
126 except AttributeError:
126 except AttributeError:
127 self.__paths = [Path(p) for p in self if os.path.exists(p)]
127 self.__paths = [Path(p) for p in self if os.path.exists(p)]
128 return self.__paths
128 return self.__paths
129
129
130 p = paths = property(get_paths)
130 p = paths = property(get_paths)
131
131
132 def grep(self, pattern, prune = False, field = None):
132 def grep(self, pattern, prune = False, field = None):
133 """ Return all strings matching 'pattern' (a regex or callable)
133 """ Return all strings matching 'pattern' (a regex or callable)
134
134
135 This is case-insensitive. If prune is true, return all items
135 This is case-insensitive. If prune is true, return all items
136 NOT matching the pattern.
136 NOT matching the pattern.
137
137
138 If field is specified, the match must occur in the specified
138 If field is specified, the match must occur in the specified
139 whitespace-separated field.
139 whitespace-separated field.
140
140
141 Examples::
141 Examples::
142
142
143 a.grep( lambda x: x.startswith('C') )
143 a.grep( lambda x: x.startswith('C') )
144 a.grep('Cha.*log', prune=1)
144 a.grep('Cha.*log', prune=1)
145 a.grep('chm', field=-1)
145 a.grep('chm', field=-1)
146 """
146 """
147
147
148 def match_target(s):
148 def match_target(s):
149 if field is None:
149 if field is None:
150 return s
150 return s
151 parts = s.split()
151 parts = s.split()
152 try:
152 try:
153 tgt = parts[field]
153 tgt = parts[field]
154 return tgt
154 return tgt
155 except IndexError:
155 except IndexError:
156 return ""
156 return ""
157
157
158 if isinstance(pattern, str):
158 if isinstance(pattern, str):
159 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
159 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
160 else:
160 else:
161 pred = pattern
161 pred = pattern
162 if not prune:
162 if not prune:
163 return SList([el for el in self if pred(match_target(el))])
163 return SList([el for el in self if pred(match_target(el))])
164 else:
164 else:
165 return SList([el for el in self if not pred(match_target(el))])
165 return SList([el for el in self if not pred(match_target(el))])
166
166
167 def fields(self, *fields):
167 def fields(self, *fields):
168 """ Collect whitespace-separated fields from string list
168 """ Collect whitespace-separated fields from string list
169
169
170 Allows quick awk-like usage of string lists.
170 Allows quick awk-like usage of string lists.
171
171
172 Example data (in var a, created by 'a = !ls -l')::
172 Example data (in var a, created by 'a = !ls -l')::
173
173
174 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
174 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
175 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
175 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
176
176
177 * ``a.fields(0)`` is ``['-rwxrwxrwx', 'drwxrwxrwx+']``
177 * ``a.fields(0)`` is ``['-rwxrwxrwx', 'drwxrwxrwx+']``
178 * ``a.fields(1,0)`` is ``['1 -rwxrwxrwx', '6 drwxrwxrwx+']``
178 * ``a.fields(1,0)`` is ``['1 -rwxrwxrwx', '6 drwxrwxrwx+']``
179 (note the joining by space).
179 (note the joining by space).
180 * ``a.fields(-1)`` is ``['ChangeLog', 'IPython']``
180 * ``a.fields(-1)`` is ``['ChangeLog', 'IPython']``
181
181
182 IndexErrors are ignored.
182 IndexErrors are ignored.
183
183
184 Without args, fields() just split()'s the strings.
184 Without args, fields() just split()'s the strings.
185 """
185 """
186 if len(fields) == 0:
186 if len(fields) == 0:
187 return [el.split() for el in self]
187 return [el.split() for el in self]
188
188
189 res = SList()
189 res = SList()
190 for el in [f.split() for f in self]:
190 for el in [f.split() for f in self]:
191 lineparts = []
191 lineparts = []
192
192
193 for fd in fields:
193 for fd in fields:
194 try:
194 try:
195 lineparts.append(el[fd])
195 lineparts.append(el[fd])
196 except IndexError:
196 except IndexError:
197 pass
197 pass
198 if lineparts:
198 if lineparts:
199 res.append(" ".join(lineparts))
199 res.append(" ".join(lineparts))
200
200
201 return res
201 return res
202
202
203 def sort(self,field= None, nums = False):
203 def sort(self,field= None, nums = False):
204 """ sort by specified fields (see fields())
204 """ sort by specified fields (see fields())
205
205
206 Example::
206 Example::
207
207
208 a.sort(1, nums = True)
208 a.sort(1, nums = True)
209
209
210 Sorts a by second field, in numerical order (so that 21 > 3)
210 Sorts a by second field, in numerical order (so that 21 > 3)
211
211
212 """
212 """
213
213
214 #decorate, sort, undecorate
214 #decorate, sort, undecorate
215 if field is not None:
215 if field is not None:
216 dsu = [[SList([line]).fields(field), line] for line in self]
216 dsu = [[SList([line]).fields(field), line] for line in self]
217 else:
217 else:
218 dsu = [[line, line] for line in self]
218 dsu = [[line, line] for line in self]
219 if nums:
219 if nums:
220 for i in range(len(dsu)):
220 for i in range(len(dsu)):
221 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
221 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
222 try:
222 try:
223 n = int(numstr)
223 n = int(numstr)
224 except ValueError:
224 except ValueError:
225 n = 0
225 n = 0
226 dsu[i][0] = n
226 dsu[i][0] = n
227
227
228
228
229 dsu.sort()
229 dsu.sort()
230 return SList([t[1] for t in dsu])
230 return SList([t[1] for t in dsu])
231
231
232
232
233 # FIXME: We need to reimplement type specific displayhook and then add this
233 # FIXME: We need to reimplement type specific displayhook and then add this
234 # back as a custom printer. This should also be moved outside utils into the
234 # back as a custom printer. This should also be moved outside utils into the
235 # core.
235 # core.
236
236
237 # def print_slist(arg):
237 # def print_slist(arg):
238 # """ Prettier (non-repr-like) and more informative printer for SList """
238 # """ Prettier (non-repr-like) and more informative printer for SList """
239 # print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):"
239 # print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):"
240 # if hasattr(arg, 'hideonce') and arg.hideonce:
240 # if hasattr(arg, 'hideonce') and arg.hideonce:
241 # arg.hideonce = False
241 # arg.hideonce = False
242 # return
242 # return
243 #
243 #
244 # nlprint(arg) # This was a nested list printer, now removed.
244 # nlprint(arg) # This was a nested list printer, now removed.
245 #
245 #
246 # print_slist = result_display.when_type(SList)(print_slist)
246 # print_slist = result_display.when_type(SList)(print_slist)
247
247
248
248
249 def indent(instr,nspaces=4, ntabs=0, flatten=False):
249 def indent(instr,nspaces=4, ntabs=0, flatten=False):
250 """Indent a string a given number of spaces or tabstops.
250 """Indent a string a given number of spaces or tabstops.
251
251
252 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
252 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
253
253
254 Parameters
254 Parameters
255 ----------
255 ----------
256
256
257 instr : basestring
257 instr : basestring
258 The string to be indented.
258 The string to be indented.
259 nspaces : int (default: 4)
259 nspaces : int (default: 4)
260 The number of spaces to be indented.
260 The number of spaces to be indented.
261 ntabs : int (default: 0)
261 ntabs : int (default: 0)
262 The number of tabs to be indented.
262 The number of tabs to be indented.
263 flatten : bool (default: False)
263 flatten : bool (default: False)
264 Whether to scrub existing indentation. If True, all lines will be
264 Whether to scrub existing indentation. If True, all lines will be
265 aligned to the same indentation. If False, existing indentation will
265 aligned to the same indentation. If False, existing indentation will
266 be strictly increased.
266 be strictly increased.
267
267
268 Returns
268 Returns
269 -------
269 -------
270
270
271 str|unicode : string indented by ntabs and nspaces.
271 str|unicode : string indented by ntabs and nspaces.
272
272
273 """
273 """
274 if instr is None:
274 if instr is None:
275 return
275 return
276 ind = '\t'*ntabs+' '*nspaces
276 ind = '\t'*ntabs+' '*nspaces
277 if flatten:
277 if flatten:
278 pat = re.compile(r'^\s*', re.MULTILINE)
278 pat = re.compile(r'^\s*', re.MULTILINE)
279 else:
279 else:
280 pat = re.compile(r'^', re.MULTILINE)
280 pat = re.compile(r'^', re.MULTILINE)
281 outstr = re.sub(pat, ind, instr)
281 outstr = re.sub(pat, ind, instr)
282 if outstr.endswith(os.linesep+ind):
282 if outstr.endswith(os.linesep+ind):
283 return outstr[:-len(ind)]
283 return outstr[:-len(ind)]
284 else:
284 else:
285 return outstr
285 return outstr
286
286
287
287
288 def list_strings(arg):
288 def list_strings(arg):
289 """Always return a list of strings, given a string or list of strings
289 """Always return a list of strings, given a string or list of strings
290 as input.
290 as input.
291
291
292 Examples
292 Examples
293 --------
293 --------
294 ::
294 ::
295
295
296 In [7]: list_strings('A single string')
296 In [7]: list_strings('A single string')
297 Out[7]: ['A single string']
297 Out[7]: ['A single string']
298
298
299 In [8]: list_strings(['A single string in a list'])
299 In [8]: list_strings(['A single string in a list'])
300 Out[8]: ['A single string in a list']
300 Out[8]: ['A single string in a list']
301
301
302 In [9]: list_strings(['A','list','of','strings'])
302 In [9]: list_strings(['A','list','of','strings'])
303 Out[9]: ['A', 'list', 'of', 'strings']
303 Out[9]: ['A', 'list', 'of', 'strings']
304 """
304 """
305
305
306 if isinstance(arg, str):
306 if isinstance(arg, str):
307 return [arg]
307 return [arg]
308 else:
308 else:
309 return arg
309 return arg
310
310
311
311
312 def marquee(txt='',width=78,mark='*'):
312 def marquee(txt='',width=78,mark='*'):
313 """Return the input string centered in a 'marquee'.
313 """Return the input string centered in a 'marquee'.
314
314
315 Examples
315 Examples
316 --------
316 --------
317 ::
317 ::
318
318
319 In [16]: marquee('A test',40)
319 In [16]: marquee('A test',40)
320 Out[16]: '**************** A test ****************'
320 Out[16]: '**************** A test ****************'
321
321
322 In [17]: marquee('A test',40,'-')
322 In [17]: marquee('A test',40,'-')
323 Out[17]: '---------------- A test ----------------'
323 Out[17]: '---------------- A test ----------------'
324
324
325 In [18]: marquee('A test',40,' ')
325 In [18]: marquee('A test',40,' ')
326 Out[18]: ' A test '
326 Out[18]: ' A test '
327
327
328 """
328 """
329 if not txt:
329 if not txt:
330 return (mark*width)[:width]
330 return (mark*width)[:width]
331 nmark = (width-len(txt)-2)//len(mark)//2
331 nmark = (width-len(txt)-2)//len(mark)//2
332 if nmark < 0: nmark =0
332 if nmark < 0: nmark =0
333 marks = mark*nmark
333 marks = mark*nmark
334 return '%s %s %s' % (marks,txt,marks)
334 return '%s %s %s' % (marks,txt,marks)
335
335
336
336
337 ini_spaces_re = re.compile(r'^(\s+)')
337 ini_spaces_re = re.compile(r'^(\s+)')
338
338
339 def num_ini_spaces(strng):
339 def num_ini_spaces(strng):
340 """Return the number of initial spaces in a string"""
340 """Return the number of initial spaces in a string"""
341
341
342 ini_spaces = ini_spaces_re.match(strng)
342 ini_spaces = ini_spaces_re.match(strng)
343 if ini_spaces:
343 if ini_spaces:
344 return ini_spaces.end()
344 return ini_spaces.end()
345 else:
345 else:
346 return 0
346 return 0
347
347
348
348
349 def format_screen(strng):
349 def format_screen(strng):
350 """Format a string for screen printing.
350 """Format a string for screen printing.
351
351
352 This removes some latex-type format codes."""
352 This removes some latex-type format codes."""
353 # Paragraph continue
353 # Paragraph continue
354 par_re = re.compile(r'\\$',re.MULTILINE)
354 par_re = re.compile(r'\\$',re.MULTILINE)
355 strng = par_re.sub('',strng)
355 strng = par_re.sub('',strng)
356 return strng
356 return strng
357
357
358
358
359 def dedent(text):
359 def dedent(text):
360 """Equivalent of textwrap.dedent that ignores unindented first line.
360 """Equivalent of textwrap.dedent that ignores unindented first line.
361
361
362 This means it will still dedent strings like:
362 This means it will still dedent strings like:
363 '''foo
363 '''foo
364 is a bar
364 is a bar
365 '''
365 '''
366
366
367 For use in wrap_paragraphs.
367 For use in wrap_paragraphs.
368 """
368 """
369
369
370 if text.startswith('\n'):
370 if text.startswith('\n'):
371 # text starts with blank line, don't ignore the first line
371 # text starts with blank line, don't ignore the first line
372 return textwrap.dedent(text)
372 return textwrap.dedent(text)
373
373
374 # split first line
374 # split first line
375 splits = text.split('\n',1)
375 splits = text.split('\n',1)
376 if len(splits) == 1:
376 if len(splits) == 1:
377 # only one line
377 # only one line
378 return textwrap.dedent(text)
378 return textwrap.dedent(text)
379
379
380 first, rest = splits
380 first, rest = splits
381 # dedent everything but the first line
381 # dedent everything but the first line
382 rest = textwrap.dedent(rest)
382 rest = textwrap.dedent(rest)
383 return '\n'.join([first, rest])
383 return '\n'.join([first, rest])
384
384
385
385
386 def wrap_paragraphs(text, ncols=80):
386 def wrap_paragraphs(text, ncols=80):
387 """Wrap multiple paragraphs to fit a specified width.
387 """Wrap multiple paragraphs to fit a specified width.
388
388
389 This is equivalent to textwrap.wrap, but with support for multiple
389 This is equivalent to textwrap.wrap, but with support for multiple
390 paragraphs, as separated by empty lines.
390 paragraphs, as separated by empty lines.
391
391
392 Returns
392 Returns
393 -------
393 -------
394
394
395 list of complete paragraphs, wrapped to fill `ncols` columns.
395 list of complete paragraphs, wrapped to fill `ncols` columns.
396 """
396 """
397 paragraph_re = re.compile(r'\n(\s*\n)+', re.MULTILINE)
397 paragraph_re = re.compile(r'\n(\s*\n)+', re.MULTILINE)
398 text = dedent(text).strip()
398 text = dedent(text).strip()
399 paragraphs = paragraph_re.split(text)[::2] # every other entry is space
399 paragraphs = paragraph_re.split(text)[::2] # every other entry is space
400 out_ps = []
400 out_ps = []
401 indent_re = re.compile(r'\n\s+', re.MULTILINE)
401 indent_re = re.compile(r'\n\s+', re.MULTILINE)
402 for p in paragraphs:
402 for p in paragraphs:
403 # presume indentation that survives dedent is meaningful formatting,
403 # presume indentation that survives dedent is meaningful formatting,
404 # so don't fill unless text is flush.
404 # so don't fill unless text is flush.
405 if indent_re.search(p) is None:
405 if indent_re.search(p) is None:
406 # wrap paragraph
406 # wrap paragraph
407 p = textwrap.fill(p, ncols)
407 p = textwrap.fill(p, ncols)
408 out_ps.append(p)
408 out_ps.append(p)
409 return out_ps
409 return out_ps
410
410
411
411
412 def long_substr(data):
412 def long_substr(data):
413 """Return the longest common substring in a list of strings.
413 """Return the longest common substring in a list of strings.
414
414
415 Credit: http://stackoverflow.com/questions/2892931/longest-common-substring-from-more-than-two-strings-python
415 Credit: http://stackoverflow.com/questions/2892931/longest-common-substring-from-more-than-two-strings-python
416 """
416 """
417 substr = ''
417 substr = ''
418 if len(data) > 1 and len(data[0]) > 0:
418 if len(data) > 1 and len(data[0]) > 0:
419 for i in range(len(data[0])):
419 for i in range(len(data[0])):
420 for j in range(len(data[0])-i+1):
420 for j in range(len(data[0])-i+1):
421 if j > len(substr) and all(data[0][i:i+j] in x for x in data):
421 if j > len(substr) and all(data[0][i:i+j] in x for x in data):
422 substr = data[0][i:i+j]
422 substr = data[0][i:i+j]
423 elif len(data) == 1:
423 elif len(data) == 1:
424 substr = data[0]
424 substr = data[0]
425 return substr
425 return substr
426
426
427
427
428 def strip_email_quotes(text):
428 def strip_email_quotes(text):
429 """Strip leading email quotation characters ('>').
429 """Strip leading email quotation characters ('>').
430
430
431 Removes any combination of leading '>' interspersed with whitespace that
431 Removes any combination of leading '>' interspersed with whitespace that
432 appears *identically* in all lines of the input text.
432 appears *identically* in all lines of the input text.
433
433
434 Parameters
434 Parameters
435 ----------
435 ----------
436 text : str
436 text : str
437
437
438 Examples
438 Examples
439 --------
439 --------
440
440
441 Simple uses::
441 Simple uses::
442
442
443 In [2]: strip_email_quotes('> > text')
443 In [2]: strip_email_quotes('> > text')
444 Out[2]: 'text'
444 Out[2]: 'text'
445
445
446 In [3]: strip_email_quotes('> > text\\n> > more')
446 In [3]: strip_email_quotes('> > text\\n> > more')
447 Out[3]: 'text\\nmore'
447 Out[3]: 'text\\nmore'
448
448
449 Note how only the common prefix that appears in all lines is stripped::
449 Note how only the common prefix that appears in all lines is stripped::
450
450
451 In [4]: strip_email_quotes('> > text\\n> > more\\n> more...')
451 In [4]: strip_email_quotes('> > text\\n> > more\\n> more...')
452 Out[4]: '> text\\n> more\\nmore...'
452 Out[4]: '> text\\n> more\\nmore...'
453
453
454 So if any line has no quote marks ('>') , then none are stripped from any
454 So if any line has no quote marks ('>') , then none are stripped from any
455 of them ::
455 of them ::
456
456
457 In [5]: strip_email_quotes('> > text\\n> > more\\nlast different')
457 In [5]: strip_email_quotes('> > text\\n> > more\\nlast different')
458 Out[5]: '> > text\\n> > more\\nlast different'
458 Out[5]: '> > text\\n> > more\\nlast different'
459 """
459 """
460 lines = text.splitlines()
460 lines = text.splitlines()
461 matches = set()
461 matches = set()
462 for line in lines:
462 for line in lines:
463 prefix = re.match(r'^(\s*>[ >]*)', line)
463 prefix = re.match(r'^(\s*>[ >]*)', line)
464 if prefix:
464 if prefix:
465 matches.add(prefix.group(1))
465 matches.add(prefix.group(1))
466 else:
466 else:
467 break
467 break
468 else:
468 else:
469 prefix = long_substr(list(matches))
469 prefix = long_substr(list(matches))
470 if prefix:
470 if prefix:
471 strip = len(prefix)
471 strip = len(prefix)
472 text = '\n'.join([ ln[strip:] for ln in lines])
472 text = '\n'.join([ ln[strip:] for ln in lines])
473 return text
473 return text
474
474
475 def strip_ansi(source):
475 def strip_ansi(source):
476 """
476 """
477 Remove ansi escape codes from text.
477 Remove ansi escape codes from text.
478
478
479 Parameters
479 Parameters
480 ----------
480 ----------
481 source : str
481 source : str
482 Source to remove the ansi from
482 Source to remove the ansi from
483 """
483 """
484 return re.sub(r'\033\[(\d|;)+?m', '', source)
484 return re.sub(r'\033\[(\d|;)+?m', '', source)
485
485
486
486
487 class EvalFormatter(Formatter):
487 class EvalFormatter(Formatter):
488 """A String Formatter that allows evaluation of simple expressions.
488 """A String Formatter that allows evaluation of simple expressions.
489
489
490 Note that this version interprets a : as specifying a format string (as per
490 Note that this version interprets a : as specifying a format string (as per
491 standard string formatting), so if slicing is required, you must explicitly
491 standard string formatting), so if slicing is required, you must explicitly
492 create a slice.
492 create a slice.
493
493
494 This is to be used in templating cases, such as the parallel batch
494 This is to be used in templating cases, such as the parallel batch
495 script templates, where simple arithmetic on arguments is useful.
495 script templates, where simple arithmetic on arguments is useful.
496
496
497 Examples
497 Examples
498 --------
498 --------
499 ::
499 ::
500
500
501 In [1]: f = EvalFormatter()
501 In [1]: f = EvalFormatter()
502 In [2]: f.format('{n//4}', n=8)
502 In [2]: f.format('{n//4}', n=8)
503 Out[2]: '2'
503 Out[2]: '2'
504
504
505 In [3]: f.format("{greeting[slice(2,4)]}", greeting="Hello")
505 In [3]: f.format("{greeting[slice(2,4)]}", greeting="Hello")
506 Out[3]: 'll'
506 Out[3]: 'll'
507 """
507 """
508 def get_field(self, name, args, kwargs):
508 def get_field(self, name, args, kwargs):
509 v = eval(name, kwargs)
509 v = eval(name, kwargs)
510 return v, name
510 return v, name
511
511
512 #XXX: As of Python 3.4, the format string parsing no longer splits on a colon
512 #XXX: As of Python 3.4, the format string parsing no longer splits on a colon
513 # inside [], so EvalFormatter can handle slicing. Once we only support 3.4 and
513 # inside [], so EvalFormatter can handle slicing. Once we only support 3.4 and
514 # above, it should be possible to remove FullEvalFormatter.
514 # above, it should be possible to remove FullEvalFormatter.
515
515
516 class FullEvalFormatter(Formatter):
516 class FullEvalFormatter(Formatter):
517 """A String Formatter that allows evaluation of simple expressions.
517 """A String Formatter that allows evaluation of simple expressions.
518
518
519 Any time a format key is not found in the kwargs,
519 Any time a format key is not found in the kwargs,
520 it will be tried as an expression in the kwargs namespace.
520 it will be tried as an expression in the kwargs namespace.
521
521
522 Note that this version allows slicing using [1:2], so you cannot specify
522 Note that this version allows slicing using [1:2], so you cannot specify
523 a format string. Use :class:`EvalFormatter` to permit format strings.
523 a format string. Use :class:`EvalFormatter` to permit format strings.
524
524
525 Examples
525 Examples
526 --------
526 --------
527 ::
527 ::
528
528
529 In [1]: f = FullEvalFormatter()
529 In [1]: f = FullEvalFormatter()
530 In [2]: f.format('{n//4}', n=8)
530 In [2]: f.format('{n//4}', n=8)
531 Out[2]: '2'
531 Out[2]: '2'
532
532
533 In [3]: f.format('{list(range(5))[2:4]}')
533 In [3]: f.format('{list(range(5))[2:4]}')
534 Out[3]: '[2, 3]'
534 Out[3]: '[2, 3]'
535
535
536 In [4]: f.format('{3*2}')
536 In [4]: f.format('{3*2}')
537 Out[4]: '6'
537 Out[4]: '6'
538 """
538 """
539 # copied from Formatter._vformat with minor changes to allow eval
539 # copied from Formatter._vformat with minor changes to allow eval
540 # and replace the format_spec code with slicing
540 # and replace the format_spec code with slicing
541 def vformat(self, format_string, args, kwargs):
541 def vformat(self, format_string, args, kwargs):
542 result = []
542 result = []
543 for literal_text, field_name, format_spec, conversion in \
543 for literal_text, field_name, format_spec, conversion in \
544 self.parse(format_string):
544 self.parse(format_string):
545
545
546 # output the literal text
546 # output the literal text
547 if literal_text:
547 if literal_text:
548 result.append(literal_text)
548 result.append(literal_text)
549
549
550 # if there's a field, output it
550 # if there's a field, output it
551 if field_name is not None:
551 if field_name is not None:
552 # this is some markup, find the object and do
552 # this is some markup, find the object and do
553 # the formatting
553 # the formatting
554
554
555 if format_spec:
555 if format_spec:
556 # override format spec, to allow slicing:
556 # override format spec, to allow slicing:
557 field_name = ':'.join([field_name, format_spec])
557 field_name = ':'.join([field_name, format_spec])
558
558
559 # eval the contents of the field for the object
559 # eval the contents of the field for the object
560 # to be formatted
560 # to be formatted
561 obj = eval(field_name, kwargs)
561 obj = eval(field_name, kwargs)
562
562
563 # do any conversion on the resulting object
563 # do any conversion on the resulting object
564 obj = self.convert_field(obj, conversion)
564 obj = self.convert_field(obj, conversion)
565
565
566 # format the object and append to the result
566 # format the object and append to the result
567 result.append(self.format_field(obj, ''))
567 result.append(self.format_field(obj, ''))
568
568
569 return ''.join(py3compat.cast_unicode(s) for s in result)
569 return ''.join(py3compat.cast_unicode(s) for s in result)
570
570
571
571
572 class DollarFormatter(FullEvalFormatter):
572 class DollarFormatter(FullEvalFormatter):
573 """Formatter allowing Itpl style $foo replacement, for names and attribute
573 """Formatter allowing Itpl style $foo replacement, for names and attribute
574 access only. Standard {foo} replacement also works, and allows full
574 access only. Standard {foo} replacement also works, and allows full
575 evaluation of its arguments.
575 evaluation of its arguments.
576
576
577 Examples
577 Examples
578 --------
578 --------
579 ::
579 ::
580
580
581 In [1]: f = DollarFormatter()
581 In [1]: f = DollarFormatter()
582 In [2]: f.format('{n//4}', n=8)
582 In [2]: f.format('{n//4}', n=8)
583 Out[2]: '2'
583 Out[2]: '2'
584
584
585 In [3]: f.format('23 * 76 is $result', result=23*76)
585 In [3]: f.format('23 * 76 is $result', result=23*76)
586 Out[3]: '23 * 76 is 1748'
586 Out[3]: '23 * 76 is 1748'
587
587
588 In [4]: f.format('$a or {b}', a=1, b=2)
588 In [4]: f.format('$a or {b}', a=1, b=2)
589 Out[4]: '1 or 2'
589 Out[4]: '1 or 2'
590 """
590 """
591 _dollar_pattern_ignore_single_quote = re.compile("(.*?)\$(\$?[\w\.]+)(?=([^']*'[^']*')*[^']*$)")
591 _dollar_pattern_ignore_single_quote = re.compile(r"(.*?)\$(\$?[\w\.]+)(?=([^']*'[^']*')*[^']*$)")
592 def parse(self, fmt_string):
592 def parse(self, fmt_string):
593 for literal_txt, field_name, format_spec, conversion \
593 for literal_txt, field_name, format_spec, conversion \
594 in Formatter.parse(self, fmt_string):
594 in Formatter.parse(self, fmt_string):
595
595
596 # Find $foo patterns in the literal text.
596 # Find $foo patterns in the literal text.
597 continue_from = 0
597 continue_from = 0
598 txt = ""
598 txt = ""
599 for m in self._dollar_pattern_ignore_single_quote.finditer(literal_txt):
599 for m in self._dollar_pattern_ignore_single_quote.finditer(literal_txt):
600 new_txt, new_field = m.group(1,2)
600 new_txt, new_field = m.group(1,2)
601 # $$foo --> $foo
601 # $$foo --> $foo
602 if new_field.startswith("$"):
602 if new_field.startswith("$"):
603 txt += new_txt + new_field
603 txt += new_txt + new_field
604 else:
604 else:
605 yield (txt + new_txt, new_field, "", None)
605 yield (txt + new_txt, new_field, "", None)
606 txt = ""
606 txt = ""
607 continue_from = m.end()
607 continue_from = m.end()
608
608
609 # Re-yield the {foo} style pattern
609 # Re-yield the {foo} style pattern
610 yield (txt + literal_txt[continue_from:], field_name, format_spec, conversion)
610 yield (txt + literal_txt[continue_from:], field_name, format_spec, conversion)
611
611
612 #-----------------------------------------------------------------------------
612 #-----------------------------------------------------------------------------
613 # Utils to columnize a list of string
613 # Utils to columnize a list of string
614 #-----------------------------------------------------------------------------
614 #-----------------------------------------------------------------------------
615
615
616 def _col_chunks(l, max_rows, row_first=False):
616 def _col_chunks(l, max_rows, row_first=False):
617 """Yield successive max_rows-sized column chunks from l."""
617 """Yield successive max_rows-sized column chunks from l."""
618 if row_first:
618 if row_first:
619 ncols = (len(l) // max_rows) + (len(l) % max_rows > 0)
619 ncols = (len(l) // max_rows) + (len(l) % max_rows > 0)
620 for i in range(ncols):
620 for i in range(ncols):
621 yield [l[j] for j in range(i, len(l), ncols)]
621 yield [l[j] for j in range(i, len(l), ncols)]
622 else:
622 else:
623 for i in range(0, len(l), max_rows):
623 for i in range(0, len(l), max_rows):
624 yield l[i:(i + max_rows)]
624 yield l[i:(i + max_rows)]
625
625
626
626
627 def _find_optimal(rlist, row_first=False, separator_size=2, displaywidth=80):
627 def _find_optimal(rlist, row_first=False, separator_size=2, displaywidth=80):
628 """Calculate optimal info to columnize a list of string"""
628 """Calculate optimal info to columnize a list of string"""
629 for max_rows in range(1, len(rlist) + 1):
629 for max_rows in range(1, len(rlist) + 1):
630 col_widths = list(map(max, _col_chunks(rlist, max_rows, row_first)))
630 col_widths = list(map(max, _col_chunks(rlist, max_rows, row_first)))
631 sumlength = sum(col_widths)
631 sumlength = sum(col_widths)
632 ncols = len(col_widths)
632 ncols = len(col_widths)
633 if sumlength + separator_size * (ncols - 1) <= displaywidth:
633 if sumlength + separator_size * (ncols - 1) <= displaywidth:
634 break
634 break
635 return {'num_columns': ncols,
635 return {'num_columns': ncols,
636 'optimal_separator_width': (displaywidth - sumlength) // (ncols - 1) if (ncols - 1) else 0,
636 'optimal_separator_width': (displaywidth - sumlength) // (ncols - 1) if (ncols - 1) else 0,
637 'max_rows': max_rows,
637 'max_rows': max_rows,
638 'column_widths': col_widths
638 'column_widths': col_widths
639 }
639 }
640
640
641
641
642 def _get_or_default(mylist, i, default=None):
642 def _get_or_default(mylist, i, default=None):
643 """return list item number, or default if don't exist"""
643 """return list item number, or default if don't exist"""
644 if i >= len(mylist):
644 if i >= len(mylist):
645 return default
645 return default
646 else :
646 else :
647 return mylist[i]
647 return mylist[i]
648
648
649
649
650 def compute_item_matrix(items, row_first=False, empty=None, *args, **kwargs) :
650 def compute_item_matrix(items, row_first=False, empty=None, *args, **kwargs) :
651 """Returns a nested list, and info to columnize items
651 """Returns a nested list, and info to columnize items
652
652
653 Parameters
653 Parameters
654 ----------
654 ----------
655
655
656 items
656 items
657 list of strings to columize
657 list of strings to columize
658 row_first : (default False)
658 row_first : (default False)
659 Whether to compute columns for a row-first matrix instead of
659 Whether to compute columns for a row-first matrix instead of
660 column-first (default).
660 column-first (default).
661 empty : (default None)
661 empty : (default None)
662 default value to fill list if needed
662 default value to fill list if needed
663 separator_size : int (default=2)
663 separator_size : int (default=2)
664 How much characters will be used as a separation between each columns.
664 How much characters will be used as a separation between each columns.
665 displaywidth : int (default=80)
665 displaywidth : int (default=80)
666 The width of the area onto which the columns should enter
666 The width of the area onto which the columns should enter
667
667
668 Returns
668 Returns
669 -------
669 -------
670
670
671 strings_matrix
671 strings_matrix
672
672
673 nested list of string, the outer most list contains as many list as
673 nested list of string, the outer most list contains as many list as
674 rows, the innermost lists have each as many element as columns. If the
674 rows, the innermost lists have each as many element as columns. If the
675 total number of elements in `items` does not equal the product of
675 total number of elements in `items` does not equal the product of
676 rows*columns, the last element of some lists are filled with `None`.
676 rows*columns, the last element of some lists are filled with `None`.
677
677
678 dict_info
678 dict_info
679 some info to make columnize easier:
679 some info to make columnize easier:
680
680
681 num_columns
681 num_columns
682 number of columns
682 number of columns
683 max_rows
683 max_rows
684 maximum number of rows (final number may be less)
684 maximum number of rows (final number may be less)
685 column_widths
685 column_widths
686 list of with of each columns
686 list of with of each columns
687 optimal_separator_width
687 optimal_separator_width
688 best separator width between columns
688 best separator width between columns
689
689
690 Examples
690 Examples
691 --------
691 --------
692 ::
692 ::
693
693
694 In [1]: l = ['aaa','b','cc','d','eeeee','f','g','h','i','j','k','l']
694 In [1]: l = ['aaa','b','cc','d','eeeee','f','g','h','i','j','k','l']
695 In [2]: list, info = compute_item_matrix(l, displaywidth=12)
695 In [2]: list, info = compute_item_matrix(l, displaywidth=12)
696 In [3]: list
696 In [3]: list
697 Out[3]: [['aaa', 'f', 'k'], ['b', 'g', 'l'], ['cc', 'h', None], ['d', 'i', None], ['eeeee', 'j', None]]
697 Out[3]: [['aaa', 'f', 'k'], ['b', 'g', 'l'], ['cc', 'h', None], ['d', 'i', None], ['eeeee', 'j', None]]
698 In [4]: ideal = {'num_columns': 3, 'column_widths': [5, 1, 1], 'optimal_separator_width': 2, 'max_rows': 5}
698 In [4]: ideal = {'num_columns': 3, 'column_widths': [5, 1, 1], 'optimal_separator_width': 2, 'max_rows': 5}
699 In [5]: all((info[k] == ideal[k] for k in ideal.keys()))
699 In [5]: all((info[k] == ideal[k] for k in ideal.keys()))
700 Out[5]: True
700 Out[5]: True
701 """
701 """
702 info = _find_optimal(list(map(len, items)), row_first, *args, **kwargs)
702 info = _find_optimal(list(map(len, items)), row_first, *args, **kwargs)
703 nrow, ncol = info['max_rows'], info['num_columns']
703 nrow, ncol = info['max_rows'], info['num_columns']
704 if row_first:
704 if row_first:
705 return ([[_get_or_default(items, r * ncol + c, default=empty) for c in range(ncol)] for r in range(nrow)], info)
705 return ([[_get_or_default(items, r * ncol + c, default=empty) for c in range(ncol)] for r in range(nrow)], info)
706 else:
706 else:
707 return ([[_get_or_default(items, c * nrow + r, default=empty) for c in range(ncol)] for r in range(nrow)], info)
707 return ([[_get_or_default(items, c * nrow + r, default=empty) for c in range(ncol)] for r in range(nrow)], info)
708
708
709
709
710 def columnize(items, row_first=False, separator=' ', displaywidth=80, spread=False):
710 def columnize(items, row_first=False, separator=' ', displaywidth=80, spread=False):
711 """ Transform a list of strings into a single string with columns.
711 """ Transform a list of strings into a single string with columns.
712
712
713 Parameters
713 Parameters
714 ----------
714 ----------
715 items : sequence of strings
715 items : sequence of strings
716 The strings to process.
716 The strings to process.
717
717
718 row_first : (default False)
718 row_first : (default False)
719 Whether to compute columns for a row-first matrix instead of
719 Whether to compute columns for a row-first matrix instead of
720 column-first (default).
720 column-first (default).
721
721
722 separator : str, optional [default is two spaces]
722 separator : str, optional [default is two spaces]
723 The string that separates columns.
723 The string that separates columns.
724
724
725 displaywidth : int, optional [default is 80]
725 displaywidth : int, optional [default is 80]
726 Width of the display in number of characters.
726 Width of the display in number of characters.
727
727
728 Returns
728 Returns
729 -------
729 -------
730 The formatted string.
730 The formatted string.
731 """
731 """
732 if not items:
732 if not items:
733 return '\n'
733 return '\n'
734 matrix, info = compute_item_matrix(items, row_first=row_first, separator_size=len(separator), displaywidth=displaywidth)
734 matrix, info = compute_item_matrix(items, row_first=row_first, separator_size=len(separator), displaywidth=displaywidth)
735 if spread:
735 if spread:
736 separator = separator.ljust(int(info['optimal_separator_width']))
736 separator = separator.ljust(int(info['optimal_separator_width']))
737 fmatrix = [filter(None, x) for x in matrix]
737 fmatrix = [filter(None, x) for x in matrix]
738 sjoin = lambda x : separator.join([ y.ljust(w, ' ') for y, w in zip(x, info['column_widths'])])
738 sjoin = lambda x : separator.join([ y.ljust(w, ' ') for y, w in zip(x, info['column_widths'])])
739 return '\n'.join(map(sjoin, fmatrix))+'\n'
739 return '\n'.join(map(sjoin, fmatrix))+'\n'
740
740
741
741
742 def get_text_list(list_, last_sep=' and ', sep=", ", wrap_item_with=""):
742 def get_text_list(list_, last_sep=' and ', sep=", ", wrap_item_with=""):
743 """
743 """
744 Return a string with a natural enumeration of items
744 Return a string with a natural enumeration of items
745
745
746 >>> get_text_list(['a', 'b', 'c', 'd'])
746 >>> get_text_list(['a', 'b', 'c', 'd'])
747 'a, b, c and d'
747 'a, b, c and d'
748 >>> get_text_list(['a', 'b', 'c'], ' or ')
748 >>> get_text_list(['a', 'b', 'c'], ' or ')
749 'a, b or c'
749 'a, b or c'
750 >>> get_text_list(['a', 'b', 'c'], ', ')
750 >>> get_text_list(['a', 'b', 'c'], ', ')
751 'a, b, c'
751 'a, b, c'
752 >>> get_text_list(['a', 'b'], ' or ')
752 >>> get_text_list(['a', 'b'], ' or ')
753 'a or b'
753 'a or b'
754 >>> get_text_list(['a'])
754 >>> get_text_list(['a'])
755 'a'
755 'a'
756 >>> get_text_list([])
756 >>> get_text_list([])
757 ''
757 ''
758 >>> get_text_list(['a', 'b'], wrap_item_with="`")
758 >>> get_text_list(['a', 'b'], wrap_item_with="`")
759 '`a` and `b`'
759 '`a` and `b`'
760 >>> get_text_list(['a', 'b', 'c', 'd'], " = ", sep=" + ")
760 >>> get_text_list(['a', 'b', 'c', 'd'], " = ", sep=" + ")
761 'a + b + c = d'
761 'a + b + c = d'
762 """
762 """
763 if len(list_) == 0:
763 if len(list_) == 0:
764 return ''
764 return ''
765 if wrap_item_with:
765 if wrap_item_with:
766 list_ = ['%s%s%s' % (wrap_item_with, item, wrap_item_with) for
766 list_ = ['%s%s%s' % (wrap_item_with, item, wrap_item_with) for
767 item in list_]
767 item in list_]
768 if len(list_) == 1:
768 if len(list_) == 1:
769 return list_[0]
769 return list_[0]
770 return '%s%s%s' % (
770 return '%s%s%s' % (
771 sep.join(i for i in list_[:-1]),
771 sep.join(i for i in list_[:-1]),
772 last_sep, list_[-1])
772 last_sep, list_[-1])
General Comments 0
You need to be logged in to leave comments. Login now