##// END OF EJS Templates
ignore the single quotes string when parsing
ICanWaitAndFishAllDay -
Show More
@@ -1,899 +1,901 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tests for the key interactiveshell module.
2 """Tests for the key interactiveshell module.
3
3
4 Historically the main classes in interactiveshell have been under-tested. This
4 Historically the main classes in interactiveshell have been under-tested. This
5 module should grow as many single-method tests as possible to trap many of the
5 module should grow as many single-method tests as possible to trap many of the
6 recurring bugs we seem to encounter with high-level interaction.
6 recurring bugs we seem to encounter with high-level interaction.
7 """
7 """
8
8
9 # Copyright (c) IPython Development Team.
9 # Copyright (c) IPython Development Team.
10 # Distributed under the terms of the Modified BSD License.
10 # Distributed under the terms of the Modified BSD License.
11
11
12 import ast
12 import ast
13 import os
13 import os
14 import signal
14 import signal
15 import shutil
15 import shutil
16 import sys
16 import sys
17 import tempfile
17 import tempfile
18 import unittest
18 import unittest
19 from unittest import mock
19 from unittest import mock
20 from io import StringIO
20 from io import StringIO
21
21
22 from os.path import join
22 from os.path import join
23
23
24 import nose.tools as nt
24 import nose.tools as nt
25
25
26 from IPython.core.error import InputRejected
26 from IPython.core.error import InputRejected
27 from IPython.core.inputtransformer import InputTransformer
27 from IPython.core.inputtransformer import InputTransformer
28 from IPython.testing.decorators import (
28 from IPython.testing.decorators import (
29 skipif, skip_win32, onlyif_unicode_paths, onlyif_cmds_exist,
29 skipif, skip_win32, onlyif_unicode_paths, onlyif_cmds_exist,
30 )
30 )
31 from IPython.testing import tools as tt
31 from IPython.testing import tools as tt
32 from IPython.utils.process import find_cmd
32 from IPython.utils.process import find_cmd
33 from IPython.utils import py3compat
33 from IPython.utils import py3compat
34
34
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36 # Globals
36 # Globals
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38 # This is used by every single test, no point repeating it ad nauseam
38 # This is used by every single test, no point repeating it ad nauseam
39 ip = get_ipython()
39 ip = get_ipython()
40
40
41 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42 # Tests
42 # Tests
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44
44
45 class DerivedInterrupt(KeyboardInterrupt):
45 class DerivedInterrupt(KeyboardInterrupt):
46 pass
46 pass
47
47
48 class InteractiveShellTestCase(unittest.TestCase):
48 class InteractiveShellTestCase(unittest.TestCase):
49 def test_naked_string_cells(self):
49 def test_naked_string_cells(self):
50 """Test that cells with only naked strings are fully executed"""
50 """Test that cells with only naked strings are fully executed"""
51 # First, single-line inputs
51 # First, single-line inputs
52 ip.run_cell('"a"\n')
52 ip.run_cell('"a"\n')
53 self.assertEqual(ip.user_ns['_'], 'a')
53 self.assertEqual(ip.user_ns['_'], 'a')
54 # And also multi-line cells
54 # And also multi-line cells
55 ip.run_cell('"""a\nb"""\n')
55 ip.run_cell('"""a\nb"""\n')
56 self.assertEqual(ip.user_ns['_'], 'a\nb')
56 self.assertEqual(ip.user_ns['_'], 'a\nb')
57
57
58 def test_run_empty_cell(self):
58 def test_run_empty_cell(self):
59 """Just make sure we don't get a horrible error with a blank
59 """Just make sure we don't get a horrible error with a blank
60 cell of input. Yes, I did overlook that."""
60 cell of input. Yes, I did overlook that."""
61 old_xc = ip.execution_count
61 old_xc = ip.execution_count
62 res = ip.run_cell('')
62 res = ip.run_cell('')
63 self.assertEqual(ip.execution_count, old_xc)
63 self.assertEqual(ip.execution_count, old_xc)
64 self.assertEqual(res.execution_count, None)
64 self.assertEqual(res.execution_count, None)
65
65
66 def test_run_cell_multiline(self):
66 def test_run_cell_multiline(self):
67 """Multi-block, multi-line cells must execute correctly.
67 """Multi-block, multi-line cells must execute correctly.
68 """
68 """
69 src = '\n'.join(["x=1",
69 src = '\n'.join(["x=1",
70 "y=2",
70 "y=2",
71 "if 1:",
71 "if 1:",
72 " x += 1",
72 " x += 1",
73 " y += 1",])
73 " y += 1",])
74 res = ip.run_cell(src)
74 res = ip.run_cell(src)
75 self.assertEqual(ip.user_ns['x'], 2)
75 self.assertEqual(ip.user_ns['x'], 2)
76 self.assertEqual(ip.user_ns['y'], 3)
76 self.assertEqual(ip.user_ns['y'], 3)
77 self.assertEqual(res.success, True)
77 self.assertEqual(res.success, True)
78 self.assertEqual(res.result, None)
78 self.assertEqual(res.result, None)
79
79
80 def test_multiline_string_cells(self):
80 def test_multiline_string_cells(self):
81 "Code sprinkled with multiline strings should execute (GH-306)"
81 "Code sprinkled with multiline strings should execute (GH-306)"
82 ip.run_cell('tmp=0')
82 ip.run_cell('tmp=0')
83 self.assertEqual(ip.user_ns['tmp'], 0)
83 self.assertEqual(ip.user_ns['tmp'], 0)
84 res = ip.run_cell('tmp=1;"""a\nb"""\n')
84 res = ip.run_cell('tmp=1;"""a\nb"""\n')
85 self.assertEqual(ip.user_ns['tmp'], 1)
85 self.assertEqual(ip.user_ns['tmp'], 1)
86 self.assertEqual(res.success, True)
86 self.assertEqual(res.success, True)
87 self.assertEqual(res.result, "a\nb")
87 self.assertEqual(res.result, "a\nb")
88
88
89 def test_dont_cache_with_semicolon(self):
89 def test_dont_cache_with_semicolon(self):
90 "Ending a line with semicolon should not cache the returned object (GH-307)"
90 "Ending a line with semicolon should not cache the returned object (GH-307)"
91 oldlen = len(ip.user_ns['Out'])
91 oldlen = len(ip.user_ns['Out'])
92 for cell in ['1;', '1;1;']:
92 for cell in ['1;', '1;1;']:
93 res = ip.run_cell(cell, store_history=True)
93 res = ip.run_cell(cell, store_history=True)
94 newlen = len(ip.user_ns['Out'])
94 newlen = len(ip.user_ns['Out'])
95 self.assertEqual(oldlen, newlen)
95 self.assertEqual(oldlen, newlen)
96 self.assertIsNone(res.result)
96 self.assertIsNone(res.result)
97 i = 0
97 i = 0
98 #also test the default caching behavior
98 #also test the default caching behavior
99 for cell in ['1', '1;1']:
99 for cell in ['1', '1;1']:
100 ip.run_cell(cell, store_history=True)
100 ip.run_cell(cell, store_history=True)
101 newlen = len(ip.user_ns['Out'])
101 newlen = len(ip.user_ns['Out'])
102 i += 1
102 i += 1
103 self.assertEqual(oldlen+i, newlen)
103 self.assertEqual(oldlen+i, newlen)
104
104
105 def test_syntax_error(self):
105 def test_syntax_error(self):
106 res = ip.run_cell("raise = 3")
106 res = ip.run_cell("raise = 3")
107 self.assertIsInstance(res.error_before_exec, SyntaxError)
107 self.assertIsInstance(res.error_before_exec, SyntaxError)
108
108
109 def test_In_variable(self):
109 def test_In_variable(self):
110 "Verify that In variable grows with user input (GH-284)"
110 "Verify that In variable grows with user input (GH-284)"
111 oldlen = len(ip.user_ns['In'])
111 oldlen = len(ip.user_ns['In'])
112 ip.run_cell('1;', store_history=True)
112 ip.run_cell('1;', store_history=True)
113 newlen = len(ip.user_ns['In'])
113 newlen = len(ip.user_ns['In'])
114 self.assertEqual(oldlen+1, newlen)
114 self.assertEqual(oldlen+1, newlen)
115 self.assertEqual(ip.user_ns['In'][-1],'1;')
115 self.assertEqual(ip.user_ns['In'][-1],'1;')
116
116
117 def test_magic_names_in_string(self):
117 def test_magic_names_in_string(self):
118 ip.run_cell('a = """\n%exit\n"""')
118 ip.run_cell('a = """\n%exit\n"""')
119 self.assertEqual(ip.user_ns['a'], '\n%exit\n')
119 self.assertEqual(ip.user_ns['a'], '\n%exit\n')
120
120
121 def test_trailing_newline(self):
121 def test_trailing_newline(self):
122 """test that running !(command) does not raise a SyntaxError"""
122 """test that running !(command) does not raise a SyntaxError"""
123 ip.run_cell('!(true)\n', False)
123 ip.run_cell('!(true)\n', False)
124 ip.run_cell('!(true)\n\n\n', False)
124 ip.run_cell('!(true)\n\n\n', False)
125
125
126 def test_gh_597(self):
126 def test_gh_597(self):
127 """Pretty-printing lists of objects with non-ascii reprs may cause
127 """Pretty-printing lists of objects with non-ascii reprs may cause
128 problems."""
128 problems."""
129 class Spam(object):
129 class Spam(object):
130 def __repr__(self):
130 def __repr__(self):
131 return "\xe9"*50
131 return "\xe9"*50
132 import IPython.core.formatters
132 import IPython.core.formatters
133 f = IPython.core.formatters.PlainTextFormatter()
133 f = IPython.core.formatters.PlainTextFormatter()
134 f([Spam(),Spam()])
134 f([Spam(),Spam()])
135
135
136
136
137 def test_future_flags(self):
137 def test_future_flags(self):
138 """Check that future flags are used for parsing code (gh-777)"""
138 """Check that future flags are used for parsing code (gh-777)"""
139 ip.run_cell('from __future__ import barry_as_FLUFL')
139 ip.run_cell('from __future__ import barry_as_FLUFL')
140 try:
140 try:
141 ip.run_cell('prfunc_return_val = 1 <> 2')
141 ip.run_cell('prfunc_return_val = 1 <> 2')
142 assert 'prfunc_return_val' in ip.user_ns
142 assert 'prfunc_return_val' in ip.user_ns
143 finally:
143 finally:
144 # Reset compiler flags so we don't mess up other tests.
144 # Reset compiler flags so we don't mess up other tests.
145 ip.compile.reset_compiler_flags()
145 ip.compile.reset_compiler_flags()
146
146
147 def test_can_pickle(self):
147 def test_can_pickle(self):
148 "Can we pickle objects defined interactively (GH-29)"
148 "Can we pickle objects defined interactively (GH-29)"
149 ip = get_ipython()
149 ip = get_ipython()
150 ip.reset()
150 ip.reset()
151 ip.run_cell(("class Mylist(list):\n"
151 ip.run_cell(("class Mylist(list):\n"
152 " def __init__(self,x=[]):\n"
152 " def __init__(self,x=[]):\n"
153 " list.__init__(self,x)"))
153 " list.__init__(self,x)"))
154 ip.run_cell("w=Mylist([1,2,3])")
154 ip.run_cell("w=Mylist([1,2,3])")
155
155
156 from pickle import dumps
156 from pickle import dumps
157
157
158 # We need to swap in our main module - this is only necessary
158 # We need to swap in our main module - this is only necessary
159 # inside the test framework, because IPython puts the interactive module
159 # inside the test framework, because IPython puts the interactive module
160 # in place (but the test framework undoes this).
160 # in place (but the test framework undoes this).
161 _main = sys.modules['__main__']
161 _main = sys.modules['__main__']
162 sys.modules['__main__'] = ip.user_module
162 sys.modules['__main__'] = ip.user_module
163 try:
163 try:
164 res = dumps(ip.user_ns["w"])
164 res = dumps(ip.user_ns["w"])
165 finally:
165 finally:
166 sys.modules['__main__'] = _main
166 sys.modules['__main__'] = _main
167 self.assertTrue(isinstance(res, bytes))
167 self.assertTrue(isinstance(res, bytes))
168
168
169 def test_global_ns(self):
169 def test_global_ns(self):
170 "Code in functions must be able to access variables outside them."
170 "Code in functions must be able to access variables outside them."
171 ip = get_ipython()
171 ip = get_ipython()
172 ip.run_cell("a = 10")
172 ip.run_cell("a = 10")
173 ip.run_cell(("def f(x):\n"
173 ip.run_cell(("def f(x):\n"
174 " return x + a"))
174 " return x + a"))
175 ip.run_cell("b = f(12)")
175 ip.run_cell("b = f(12)")
176 self.assertEqual(ip.user_ns["b"], 22)
176 self.assertEqual(ip.user_ns["b"], 22)
177
177
178 def test_bad_custom_tb(self):
178 def test_bad_custom_tb(self):
179 """Check that InteractiveShell is protected from bad custom exception handlers"""
179 """Check that InteractiveShell is protected from bad custom exception handlers"""
180 ip.set_custom_exc((IOError,), lambda etype,value,tb: 1/0)
180 ip.set_custom_exc((IOError,), lambda etype,value,tb: 1/0)
181 self.assertEqual(ip.custom_exceptions, (IOError,))
181 self.assertEqual(ip.custom_exceptions, (IOError,))
182 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
182 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
183 ip.run_cell(u'raise IOError("foo")')
183 ip.run_cell(u'raise IOError("foo")')
184 self.assertEqual(ip.custom_exceptions, ())
184 self.assertEqual(ip.custom_exceptions, ())
185
185
186 def test_bad_custom_tb_return(self):
186 def test_bad_custom_tb_return(self):
187 """Check that InteractiveShell is protected from bad return types in custom exception handlers"""
187 """Check that InteractiveShell is protected from bad return types in custom exception handlers"""
188 ip.set_custom_exc((NameError,),lambda etype,value,tb, tb_offset=None: 1)
188 ip.set_custom_exc((NameError,),lambda etype,value,tb, tb_offset=None: 1)
189 self.assertEqual(ip.custom_exceptions, (NameError,))
189 self.assertEqual(ip.custom_exceptions, (NameError,))
190 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
190 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
191 ip.run_cell(u'a=abracadabra')
191 ip.run_cell(u'a=abracadabra')
192 self.assertEqual(ip.custom_exceptions, ())
192 self.assertEqual(ip.custom_exceptions, ())
193
193
194 def test_drop_by_id(self):
194 def test_drop_by_id(self):
195 myvars = {"a":object(), "b":object(), "c": object()}
195 myvars = {"a":object(), "b":object(), "c": object()}
196 ip.push(myvars, interactive=False)
196 ip.push(myvars, interactive=False)
197 for name in myvars:
197 for name in myvars:
198 assert name in ip.user_ns, name
198 assert name in ip.user_ns, name
199 assert name in ip.user_ns_hidden, name
199 assert name in ip.user_ns_hidden, name
200 ip.user_ns['b'] = 12
200 ip.user_ns['b'] = 12
201 ip.drop_by_id(myvars)
201 ip.drop_by_id(myvars)
202 for name in ["a", "c"]:
202 for name in ["a", "c"]:
203 assert name not in ip.user_ns, name
203 assert name not in ip.user_ns, name
204 assert name not in ip.user_ns_hidden, name
204 assert name not in ip.user_ns_hidden, name
205 assert ip.user_ns['b'] == 12
205 assert ip.user_ns['b'] == 12
206 ip.reset()
206 ip.reset()
207
207
208 def test_var_expand(self):
208 def test_var_expand(self):
209 ip.user_ns['f'] = u'Ca\xf1o'
209 ip.user_ns['f'] = u'Ca\xf1o'
210 self.assertEqual(ip.var_expand(u'echo $f'), u'echo Ca\xf1o')
210 self.assertEqual(ip.var_expand(u'echo $f'), u'echo Ca\xf1o')
211 self.assertEqual(ip.var_expand(u'echo {f}'), u'echo Ca\xf1o')
211 self.assertEqual(ip.var_expand(u'echo {f}'), u'echo Ca\xf1o')
212 self.assertEqual(ip.var_expand(u'echo {f[:-1]}'), u'echo Ca\xf1')
212 self.assertEqual(ip.var_expand(u'echo {f[:-1]}'), u'echo Ca\xf1')
213 self.assertEqual(ip.var_expand(u'echo {1*2}'), u'echo 2')
213 self.assertEqual(ip.var_expand(u'echo {1*2}'), u'echo 2')
214
215 self.assertEqual(ip.var_expand(u"grep x | awk '{print $1}'"), u"grep x | awk '{print $1}'")
214
216
215 ip.user_ns['f'] = b'Ca\xc3\xb1o'
217 ip.user_ns['f'] = b'Ca\xc3\xb1o'
216 # This should not raise any exception:
218 # This should not raise any exception:
217 ip.var_expand(u'echo $f')
219 ip.var_expand(u'echo $f')
218
220
219 def test_var_expand_local(self):
221 def test_var_expand_local(self):
220 """Test local variable expansion in !system and %magic calls"""
222 """Test local variable expansion in !system and %magic calls"""
221 # !system
223 # !system
222 ip.run_cell('def test():\n'
224 ip.run_cell('def test():\n'
223 ' lvar = "ttt"\n'
225 ' lvar = "ttt"\n'
224 ' ret = !echo {lvar}\n'
226 ' ret = !echo {lvar}\n'
225 ' return ret[0]\n')
227 ' return ret[0]\n')
226 res = ip.user_ns['test']()
228 res = ip.user_ns['test']()
227 nt.assert_in('ttt', res)
229 nt.assert_in('ttt', res)
228
230
229 # %magic
231 # %magic
230 ip.run_cell('def makemacro():\n'
232 ip.run_cell('def makemacro():\n'
231 ' macroname = "macro_var_expand_locals"\n'
233 ' macroname = "macro_var_expand_locals"\n'
232 ' %macro {macroname} codestr\n')
234 ' %macro {macroname} codestr\n')
233 ip.user_ns['codestr'] = "str(12)"
235 ip.user_ns['codestr'] = "str(12)"
234 ip.run_cell('makemacro()')
236 ip.run_cell('makemacro()')
235 nt.assert_in('macro_var_expand_locals', ip.user_ns)
237 nt.assert_in('macro_var_expand_locals', ip.user_ns)
236
238
237 def test_var_expand_self(self):
239 def test_var_expand_self(self):
238 """Test variable expansion with the name 'self', which was failing.
240 """Test variable expansion with the name 'self', which was failing.
239
241
240 See https://github.com/ipython/ipython/issues/1878#issuecomment-7698218
242 See https://github.com/ipython/ipython/issues/1878#issuecomment-7698218
241 """
243 """
242 ip.run_cell('class cTest:\n'
244 ip.run_cell('class cTest:\n'
243 ' classvar="see me"\n'
245 ' classvar="see me"\n'
244 ' def test(self):\n'
246 ' def test(self):\n'
245 ' res = !echo Variable: {self.classvar}\n'
247 ' res = !echo Variable: {self.classvar}\n'
246 ' return res[0]\n')
248 ' return res[0]\n')
247 nt.assert_in('see me', ip.user_ns['cTest']().test())
249 nt.assert_in('see me', ip.user_ns['cTest']().test())
248
250
249 def test_bad_var_expand(self):
251 def test_bad_var_expand(self):
250 """var_expand on invalid formats shouldn't raise"""
252 """var_expand on invalid formats shouldn't raise"""
251 # SyntaxError
253 # SyntaxError
252 self.assertEqual(ip.var_expand(u"{'a':5}"), u"{'a':5}")
254 self.assertEqual(ip.var_expand(u"{'a':5}"), u"{'a':5}")
253 # NameError
255 # NameError
254 self.assertEqual(ip.var_expand(u"{asdf}"), u"{asdf}")
256 self.assertEqual(ip.var_expand(u"{asdf}"), u"{asdf}")
255 # ZeroDivisionError
257 # ZeroDivisionError
256 self.assertEqual(ip.var_expand(u"{1/0}"), u"{1/0}")
258 self.assertEqual(ip.var_expand(u"{1/0}"), u"{1/0}")
257
259
258 def test_silent_postexec(self):
260 def test_silent_postexec(self):
259 """run_cell(silent=True) doesn't invoke pre/post_run_cell callbacks"""
261 """run_cell(silent=True) doesn't invoke pre/post_run_cell callbacks"""
260 pre_explicit = mock.Mock()
262 pre_explicit = mock.Mock()
261 pre_always = mock.Mock()
263 pre_always = mock.Mock()
262 post_explicit = mock.Mock()
264 post_explicit = mock.Mock()
263 post_always = mock.Mock()
265 post_always = mock.Mock()
264
266
265 ip.events.register('pre_run_cell', pre_explicit)
267 ip.events.register('pre_run_cell', pre_explicit)
266 ip.events.register('pre_execute', pre_always)
268 ip.events.register('pre_execute', pre_always)
267 ip.events.register('post_run_cell', post_explicit)
269 ip.events.register('post_run_cell', post_explicit)
268 ip.events.register('post_execute', post_always)
270 ip.events.register('post_execute', post_always)
269
271
270 try:
272 try:
271 ip.run_cell("1", silent=True)
273 ip.run_cell("1", silent=True)
272 assert pre_always.called
274 assert pre_always.called
273 assert not pre_explicit.called
275 assert not pre_explicit.called
274 assert post_always.called
276 assert post_always.called
275 assert not post_explicit.called
277 assert not post_explicit.called
276 # double-check that non-silent exec did what we expected
278 # double-check that non-silent exec did what we expected
277 # silent to avoid
279 # silent to avoid
278 ip.run_cell("1")
280 ip.run_cell("1")
279 assert pre_explicit.called
281 assert pre_explicit.called
280 assert post_explicit.called
282 assert post_explicit.called
281 finally:
283 finally:
282 # remove post-exec
284 # remove post-exec
283 ip.events.unregister('pre_run_cell', pre_explicit)
285 ip.events.unregister('pre_run_cell', pre_explicit)
284 ip.events.unregister('pre_execute', pre_always)
286 ip.events.unregister('pre_execute', pre_always)
285 ip.events.unregister('post_run_cell', post_explicit)
287 ip.events.unregister('post_run_cell', post_explicit)
286 ip.events.unregister('post_execute', post_always)
288 ip.events.unregister('post_execute', post_always)
287
289
288 def test_silent_noadvance(self):
290 def test_silent_noadvance(self):
289 """run_cell(silent=True) doesn't advance execution_count"""
291 """run_cell(silent=True) doesn't advance execution_count"""
290 ec = ip.execution_count
292 ec = ip.execution_count
291 # silent should force store_history=False
293 # silent should force store_history=False
292 ip.run_cell("1", store_history=True, silent=True)
294 ip.run_cell("1", store_history=True, silent=True)
293
295
294 self.assertEqual(ec, ip.execution_count)
296 self.assertEqual(ec, ip.execution_count)
295 # double-check that non-silent exec did what we expected
297 # double-check that non-silent exec did what we expected
296 # silent to avoid
298 # silent to avoid
297 ip.run_cell("1", store_history=True)
299 ip.run_cell("1", store_history=True)
298 self.assertEqual(ec+1, ip.execution_count)
300 self.assertEqual(ec+1, ip.execution_count)
299
301
300 def test_silent_nodisplayhook(self):
302 def test_silent_nodisplayhook(self):
301 """run_cell(silent=True) doesn't trigger displayhook"""
303 """run_cell(silent=True) doesn't trigger displayhook"""
302 d = dict(called=False)
304 d = dict(called=False)
303
305
304 trap = ip.display_trap
306 trap = ip.display_trap
305 save_hook = trap.hook
307 save_hook = trap.hook
306
308
307 def failing_hook(*args, **kwargs):
309 def failing_hook(*args, **kwargs):
308 d['called'] = True
310 d['called'] = True
309
311
310 try:
312 try:
311 trap.hook = failing_hook
313 trap.hook = failing_hook
312 res = ip.run_cell("1", silent=True)
314 res = ip.run_cell("1", silent=True)
313 self.assertFalse(d['called'])
315 self.assertFalse(d['called'])
314 self.assertIsNone(res.result)
316 self.assertIsNone(res.result)
315 # double-check that non-silent exec did what we expected
317 # double-check that non-silent exec did what we expected
316 # silent to avoid
318 # silent to avoid
317 ip.run_cell("1")
319 ip.run_cell("1")
318 self.assertTrue(d['called'])
320 self.assertTrue(d['called'])
319 finally:
321 finally:
320 trap.hook = save_hook
322 trap.hook = save_hook
321
323
322 def test_ofind_line_magic(self):
324 def test_ofind_line_magic(self):
323 from IPython.core.magic import register_line_magic
325 from IPython.core.magic import register_line_magic
324
326
325 @register_line_magic
327 @register_line_magic
326 def lmagic(line):
328 def lmagic(line):
327 "A line magic"
329 "A line magic"
328
330
329 # Get info on line magic
331 # Get info on line magic
330 lfind = ip._ofind('lmagic')
332 lfind = ip._ofind('lmagic')
331 info = dict(found=True, isalias=False, ismagic=True,
333 info = dict(found=True, isalias=False, ismagic=True,
332 namespace = 'IPython internal', obj= lmagic.__wrapped__,
334 namespace = 'IPython internal', obj= lmagic.__wrapped__,
333 parent = None)
335 parent = None)
334 nt.assert_equal(lfind, info)
336 nt.assert_equal(lfind, info)
335
337
336 def test_ofind_cell_magic(self):
338 def test_ofind_cell_magic(self):
337 from IPython.core.magic import register_cell_magic
339 from IPython.core.magic import register_cell_magic
338
340
339 @register_cell_magic
341 @register_cell_magic
340 def cmagic(line, cell):
342 def cmagic(line, cell):
341 "A cell magic"
343 "A cell magic"
342
344
343 # Get info on cell magic
345 # Get info on cell magic
344 find = ip._ofind('cmagic')
346 find = ip._ofind('cmagic')
345 info = dict(found=True, isalias=False, ismagic=True,
347 info = dict(found=True, isalias=False, ismagic=True,
346 namespace = 'IPython internal', obj= cmagic.__wrapped__,
348 namespace = 'IPython internal', obj= cmagic.__wrapped__,
347 parent = None)
349 parent = None)
348 nt.assert_equal(find, info)
350 nt.assert_equal(find, info)
349
351
350 def test_ofind_property_with_error(self):
352 def test_ofind_property_with_error(self):
351 class A(object):
353 class A(object):
352 @property
354 @property
353 def foo(self):
355 def foo(self):
354 raise NotImplementedError()
356 raise NotImplementedError()
355 a = A()
357 a = A()
356
358
357 found = ip._ofind('a.foo', [('locals', locals())])
359 found = ip._ofind('a.foo', [('locals', locals())])
358 info = dict(found=True, isalias=False, ismagic=False,
360 info = dict(found=True, isalias=False, ismagic=False,
359 namespace='locals', obj=A.foo, parent=a)
361 namespace='locals', obj=A.foo, parent=a)
360 nt.assert_equal(found, info)
362 nt.assert_equal(found, info)
361
363
362 def test_ofind_multiple_attribute_lookups(self):
364 def test_ofind_multiple_attribute_lookups(self):
363 class A(object):
365 class A(object):
364 @property
366 @property
365 def foo(self):
367 def foo(self):
366 raise NotImplementedError()
368 raise NotImplementedError()
367
369
368 a = A()
370 a = A()
369 a.a = A()
371 a.a = A()
370 a.a.a = A()
372 a.a.a = A()
371
373
372 found = ip._ofind('a.a.a.foo', [('locals', locals())])
374 found = ip._ofind('a.a.a.foo', [('locals', locals())])
373 info = dict(found=True, isalias=False, ismagic=False,
375 info = dict(found=True, isalias=False, ismagic=False,
374 namespace='locals', obj=A.foo, parent=a.a.a)
376 namespace='locals', obj=A.foo, parent=a.a.a)
375 nt.assert_equal(found, info)
377 nt.assert_equal(found, info)
376
378
377 def test_ofind_slotted_attributes(self):
379 def test_ofind_slotted_attributes(self):
378 class A(object):
380 class A(object):
379 __slots__ = ['foo']
381 __slots__ = ['foo']
380 def __init__(self):
382 def __init__(self):
381 self.foo = 'bar'
383 self.foo = 'bar'
382
384
383 a = A()
385 a = A()
384 found = ip._ofind('a.foo', [('locals', locals())])
386 found = ip._ofind('a.foo', [('locals', locals())])
385 info = dict(found=True, isalias=False, ismagic=False,
387 info = dict(found=True, isalias=False, ismagic=False,
386 namespace='locals', obj=a.foo, parent=a)
388 namespace='locals', obj=a.foo, parent=a)
387 nt.assert_equal(found, info)
389 nt.assert_equal(found, info)
388
390
389 found = ip._ofind('a.bar', [('locals', locals())])
391 found = ip._ofind('a.bar', [('locals', locals())])
390 info = dict(found=False, isalias=False, ismagic=False,
392 info = dict(found=False, isalias=False, ismagic=False,
391 namespace=None, obj=None, parent=a)
393 namespace=None, obj=None, parent=a)
392 nt.assert_equal(found, info)
394 nt.assert_equal(found, info)
393
395
394 def test_ofind_prefers_property_to_instance_level_attribute(self):
396 def test_ofind_prefers_property_to_instance_level_attribute(self):
395 class A(object):
397 class A(object):
396 @property
398 @property
397 def foo(self):
399 def foo(self):
398 return 'bar'
400 return 'bar'
399 a = A()
401 a = A()
400 a.__dict__['foo'] = 'baz'
402 a.__dict__['foo'] = 'baz'
401 nt.assert_equal(a.foo, 'bar')
403 nt.assert_equal(a.foo, 'bar')
402 found = ip._ofind('a.foo', [('locals', locals())])
404 found = ip._ofind('a.foo', [('locals', locals())])
403 nt.assert_is(found['obj'], A.foo)
405 nt.assert_is(found['obj'], A.foo)
404
406
405 def test_custom_syntaxerror_exception(self):
407 def test_custom_syntaxerror_exception(self):
406 called = []
408 called = []
407 def my_handler(shell, etype, value, tb, tb_offset=None):
409 def my_handler(shell, etype, value, tb, tb_offset=None):
408 called.append(etype)
410 called.append(etype)
409 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
411 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
410
412
411 ip.set_custom_exc((SyntaxError,), my_handler)
413 ip.set_custom_exc((SyntaxError,), my_handler)
412 try:
414 try:
413 ip.run_cell("1f")
415 ip.run_cell("1f")
414 # Check that this was called, and only once.
416 # Check that this was called, and only once.
415 self.assertEqual(called, [SyntaxError])
417 self.assertEqual(called, [SyntaxError])
416 finally:
418 finally:
417 # Reset the custom exception hook
419 # Reset the custom exception hook
418 ip.set_custom_exc((), None)
420 ip.set_custom_exc((), None)
419
421
420 def test_custom_exception(self):
422 def test_custom_exception(self):
421 called = []
423 called = []
422 def my_handler(shell, etype, value, tb, tb_offset=None):
424 def my_handler(shell, etype, value, tb, tb_offset=None):
423 called.append(etype)
425 called.append(etype)
424 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
426 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
425
427
426 ip.set_custom_exc((ValueError,), my_handler)
428 ip.set_custom_exc((ValueError,), my_handler)
427 try:
429 try:
428 res = ip.run_cell("raise ValueError('test')")
430 res = ip.run_cell("raise ValueError('test')")
429 # Check that this was called, and only once.
431 # Check that this was called, and only once.
430 self.assertEqual(called, [ValueError])
432 self.assertEqual(called, [ValueError])
431 # Check that the error is on the result object
433 # Check that the error is on the result object
432 self.assertIsInstance(res.error_in_exec, ValueError)
434 self.assertIsInstance(res.error_in_exec, ValueError)
433 finally:
435 finally:
434 # Reset the custom exception hook
436 # Reset the custom exception hook
435 ip.set_custom_exc((), None)
437 ip.set_custom_exc((), None)
436
438
437 def test_mktempfile(self):
439 def test_mktempfile(self):
438 filename = ip.mktempfile()
440 filename = ip.mktempfile()
439 # Check that we can open the file again on Windows
441 # Check that we can open the file again on Windows
440 with open(filename, 'w') as f:
442 with open(filename, 'w') as f:
441 f.write('abc')
443 f.write('abc')
442
444
443 filename = ip.mktempfile(data='blah')
445 filename = ip.mktempfile(data='blah')
444 with open(filename, 'r') as f:
446 with open(filename, 'r') as f:
445 self.assertEqual(f.read(), 'blah')
447 self.assertEqual(f.read(), 'blah')
446
448
447 def test_new_main_mod(self):
449 def test_new_main_mod(self):
448 # Smoketest to check that this accepts a unicode module name
450 # Smoketest to check that this accepts a unicode module name
449 name = u'jiefmw'
451 name = u'jiefmw'
450 mod = ip.new_main_mod(u'%s.py' % name, name)
452 mod = ip.new_main_mod(u'%s.py' % name, name)
451 self.assertEqual(mod.__name__, name)
453 self.assertEqual(mod.__name__, name)
452
454
453 def test_get_exception_only(self):
455 def test_get_exception_only(self):
454 try:
456 try:
455 raise KeyboardInterrupt
457 raise KeyboardInterrupt
456 except KeyboardInterrupt:
458 except KeyboardInterrupt:
457 msg = ip.get_exception_only()
459 msg = ip.get_exception_only()
458 self.assertEqual(msg, 'KeyboardInterrupt\n')
460 self.assertEqual(msg, 'KeyboardInterrupt\n')
459
461
460 try:
462 try:
461 raise DerivedInterrupt("foo")
463 raise DerivedInterrupt("foo")
462 except KeyboardInterrupt:
464 except KeyboardInterrupt:
463 msg = ip.get_exception_only()
465 msg = ip.get_exception_only()
464 self.assertEqual(msg, 'IPython.core.tests.test_interactiveshell.DerivedInterrupt: foo\n')
466 self.assertEqual(msg, 'IPython.core.tests.test_interactiveshell.DerivedInterrupt: foo\n')
465
467
466 def test_inspect_text(self):
468 def test_inspect_text(self):
467 ip.run_cell('a = 5')
469 ip.run_cell('a = 5')
468 text = ip.object_inspect_text('a')
470 text = ip.object_inspect_text('a')
469 self.assertIsInstance(text, str)
471 self.assertIsInstance(text, str)
470
472
471
473
472 class TestSafeExecfileNonAsciiPath(unittest.TestCase):
474 class TestSafeExecfileNonAsciiPath(unittest.TestCase):
473
475
474 @onlyif_unicode_paths
476 @onlyif_unicode_paths
475 def setUp(self):
477 def setUp(self):
476 self.BASETESTDIR = tempfile.mkdtemp()
478 self.BASETESTDIR = tempfile.mkdtemp()
477 self.TESTDIR = join(self.BASETESTDIR, u"Γ₯Àâ")
479 self.TESTDIR = join(self.BASETESTDIR, u"Γ₯Àâ")
478 os.mkdir(self.TESTDIR)
480 os.mkdir(self.TESTDIR)
479 with open(join(self.TESTDIR, u"Γ₯Àâtestscript.py"), "w") as sfile:
481 with open(join(self.TESTDIR, u"Γ₯Àâtestscript.py"), "w") as sfile:
480 sfile.write("pass\n")
482 sfile.write("pass\n")
481 self.oldpath = os.getcwd()
483 self.oldpath = os.getcwd()
482 os.chdir(self.TESTDIR)
484 os.chdir(self.TESTDIR)
483 self.fname = u"Γ₯Àâtestscript.py"
485 self.fname = u"Γ₯Àâtestscript.py"
484
486
485 def tearDown(self):
487 def tearDown(self):
486 os.chdir(self.oldpath)
488 os.chdir(self.oldpath)
487 shutil.rmtree(self.BASETESTDIR)
489 shutil.rmtree(self.BASETESTDIR)
488
490
489 @onlyif_unicode_paths
491 @onlyif_unicode_paths
490 def test_1(self):
492 def test_1(self):
491 """Test safe_execfile with non-ascii path
493 """Test safe_execfile with non-ascii path
492 """
494 """
493 ip.safe_execfile(self.fname, {}, raise_exceptions=True)
495 ip.safe_execfile(self.fname, {}, raise_exceptions=True)
494
496
495 class ExitCodeChecks(tt.TempFileMixin):
497 class ExitCodeChecks(tt.TempFileMixin):
496 def test_exit_code_ok(self):
498 def test_exit_code_ok(self):
497 self.system('exit 0')
499 self.system('exit 0')
498 self.assertEqual(ip.user_ns['_exit_code'], 0)
500 self.assertEqual(ip.user_ns['_exit_code'], 0)
499
501
500 def test_exit_code_error(self):
502 def test_exit_code_error(self):
501 self.system('exit 1')
503 self.system('exit 1')
502 self.assertEqual(ip.user_ns['_exit_code'], 1)
504 self.assertEqual(ip.user_ns['_exit_code'], 1)
503
505
504 @skipif(not hasattr(signal, 'SIGALRM'))
506 @skipif(not hasattr(signal, 'SIGALRM'))
505 def test_exit_code_signal(self):
507 def test_exit_code_signal(self):
506 self.mktmp("import signal, time\n"
508 self.mktmp("import signal, time\n"
507 "signal.setitimer(signal.ITIMER_REAL, 0.1)\n"
509 "signal.setitimer(signal.ITIMER_REAL, 0.1)\n"
508 "time.sleep(1)\n")
510 "time.sleep(1)\n")
509 self.system("%s %s" % (sys.executable, self.fname))
511 self.system("%s %s" % (sys.executable, self.fname))
510 self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGALRM)
512 self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGALRM)
511
513
512 @onlyif_cmds_exist("csh")
514 @onlyif_cmds_exist("csh")
513 def test_exit_code_signal_csh(self):
515 def test_exit_code_signal_csh(self):
514 SHELL = os.environ.get('SHELL', None)
516 SHELL = os.environ.get('SHELL', None)
515 os.environ['SHELL'] = find_cmd("csh")
517 os.environ['SHELL'] = find_cmd("csh")
516 try:
518 try:
517 self.test_exit_code_signal()
519 self.test_exit_code_signal()
518 finally:
520 finally:
519 if SHELL is not None:
521 if SHELL is not None:
520 os.environ['SHELL'] = SHELL
522 os.environ['SHELL'] = SHELL
521 else:
523 else:
522 del os.environ['SHELL']
524 del os.environ['SHELL']
523
525
524 class TestSystemRaw(unittest.TestCase, ExitCodeChecks):
526 class TestSystemRaw(unittest.TestCase, ExitCodeChecks):
525 system = ip.system_raw
527 system = ip.system_raw
526
528
527 @onlyif_unicode_paths
529 @onlyif_unicode_paths
528 def test_1(self):
530 def test_1(self):
529 """Test system_raw with non-ascii cmd
531 """Test system_raw with non-ascii cmd
530 """
532 """
531 cmd = u'''python -c "'Γ₯Àâ'" '''
533 cmd = u'''python -c "'Γ₯Àâ'" '''
532 ip.system_raw(cmd)
534 ip.system_raw(cmd)
533
535
534 @mock.patch('subprocess.call', side_effect=KeyboardInterrupt)
536 @mock.patch('subprocess.call', side_effect=KeyboardInterrupt)
535 @mock.patch('os.system', side_effect=KeyboardInterrupt)
537 @mock.patch('os.system', side_effect=KeyboardInterrupt)
536 def test_control_c(self, *mocks):
538 def test_control_c(self, *mocks):
537 try:
539 try:
538 self.system("sleep 1 # wont happen")
540 self.system("sleep 1 # wont happen")
539 except KeyboardInterrupt:
541 except KeyboardInterrupt:
540 self.fail("system call should intercept "
542 self.fail("system call should intercept "
541 "keyboard interrupt from subprocess.call")
543 "keyboard interrupt from subprocess.call")
542 self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGINT)
544 self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGINT)
543
545
544 # TODO: Exit codes are currently ignored on Windows.
546 # TODO: Exit codes are currently ignored on Windows.
545 class TestSystemPipedExitCode(unittest.TestCase, ExitCodeChecks):
547 class TestSystemPipedExitCode(unittest.TestCase, ExitCodeChecks):
546 system = ip.system_piped
548 system = ip.system_piped
547
549
548 @skip_win32
550 @skip_win32
549 def test_exit_code_ok(self):
551 def test_exit_code_ok(self):
550 ExitCodeChecks.test_exit_code_ok(self)
552 ExitCodeChecks.test_exit_code_ok(self)
551
553
552 @skip_win32
554 @skip_win32
553 def test_exit_code_error(self):
555 def test_exit_code_error(self):
554 ExitCodeChecks.test_exit_code_error(self)
556 ExitCodeChecks.test_exit_code_error(self)
555
557
556 @skip_win32
558 @skip_win32
557 def test_exit_code_signal(self):
559 def test_exit_code_signal(self):
558 ExitCodeChecks.test_exit_code_signal(self)
560 ExitCodeChecks.test_exit_code_signal(self)
559
561
560 class TestModules(unittest.TestCase, tt.TempFileMixin):
562 class TestModules(unittest.TestCase, tt.TempFileMixin):
561 def test_extraneous_loads(self):
563 def test_extraneous_loads(self):
562 """Test we're not loading modules on startup that we shouldn't.
564 """Test we're not loading modules on startup that we shouldn't.
563 """
565 """
564 self.mktmp("import sys\n"
566 self.mktmp("import sys\n"
565 "print('numpy' in sys.modules)\n"
567 "print('numpy' in sys.modules)\n"
566 "print('ipyparallel' in sys.modules)\n"
568 "print('ipyparallel' in sys.modules)\n"
567 "print('ipykernel' in sys.modules)\n"
569 "print('ipykernel' in sys.modules)\n"
568 )
570 )
569 out = "False\nFalse\nFalse\n"
571 out = "False\nFalse\nFalse\n"
570 tt.ipexec_validate(self.fname, out)
572 tt.ipexec_validate(self.fname, out)
571
573
572 class Negator(ast.NodeTransformer):
574 class Negator(ast.NodeTransformer):
573 """Negates all number literals in an AST."""
575 """Negates all number literals in an AST."""
574 def visit_Num(self, node):
576 def visit_Num(self, node):
575 node.n = -node.n
577 node.n = -node.n
576 return node
578 return node
577
579
578 class TestAstTransform(unittest.TestCase):
580 class TestAstTransform(unittest.TestCase):
579 def setUp(self):
581 def setUp(self):
580 self.negator = Negator()
582 self.negator = Negator()
581 ip.ast_transformers.append(self.negator)
583 ip.ast_transformers.append(self.negator)
582
584
583 def tearDown(self):
585 def tearDown(self):
584 ip.ast_transformers.remove(self.negator)
586 ip.ast_transformers.remove(self.negator)
585
587
586 def test_run_cell(self):
588 def test_run_cell(self):
587 with tt.AssertPrints('-34'):
589 with tt.AssertPrints('-34'):
588 ip.run_cell('print (12 + 22)')
590 ip.run_cell('print (12 + 22)')
589
591
590 # A named reference to a number shouldn't be transformed.
592 # A named reference to a number shouldn't be transformed.
591 ip.user_ns['n'] = 55
593 ip.user_ns['n'] = 55
592 with tt.AssertNotPrints('-55'):
594 with tt.AssertNotPrints('-55'):
593 ip.run_cell('print (n)')
595 ip.run_cell('print (n)')
594
596
595 def test_timeit(self):
597 def test_timeit(self):
596 called = set()
598 called = set()
597 def f(x):
599 def f(x):
598 called.add(x)
600 called.add(x)
599 ip.push({'f':f})
601 ip.push({'f':f})
600
602
601 with tt.AssertPrints("std. dev. of"):
603 with tt.AssertPrints("std. dev. of"):
602 ip.run_line_magic("timeit", "-n1 f(1)")
604 ip.run_line_magic("timeit", "-n1 f(1)")
603 self.assertEqual(called, {-1})
605 self.assertEqual(called, {-1})
604 called.clear()
606 called.clear()
605
607
606 with tt.AssertPrints("std. dev. of"):
608 with tt.AssertPrints("std. dev. of"):
607 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
609 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
608 self.assertEqual(called, {-2, -3})
610 self.assertEqual(called, {-2, -3})
609
611
610 def test_time(self):
612 def test_time(self):
611 called = []
613 called = []
612 def f(x):
614 def f(x):
613 called.append(x)
615 called.append(x)
614 ip.push({'f':f})
616 ip.push({'f':f})
615
617
616 # Test with an expression
618 # Test with an expression
617 with tt.AssertPrints("Wall time: "):
619 with tt.AssertPrints("Wall time: "):
618 ip.run_line_magic("time", "f(5+9)")
620 ip.run_line_magic("time", "f(5+9)")
619 self.assertEqual(called, [-14])
621 self.assertEqual(called, [-14])
620 called[:] = []
622 called[:] = []
621
623
622 # Test with a statement (different code path)
624 # Test with a statement (different code path)
623 with tt.AssertPrints("Wall time: "):
625 with tt.AssertPrints("Wall time: "):
624 ip.run_line_magic("time", "a = f(-3 + -2)")
626 ip.run_line_magic("time", "a = f(-3 + -2)")
625 self.assertEqual(called, [5])
627 self.assertEqual(called, [5])
626
628
627 def test_macro(self):
629 def test_macro(self):
628 ip.push({'a':10})
630 ip.push({'a':10})
629 # The AST transformation makes this do a+=-1
631 # The AST transformation makes this do a+=-1
630 ip.define_macro("amacro", "a+=1\nprint(a)")
632 ip.define_macro("amacro", "a+=1\nprint(a)")
631
633
632 with tt.AssertPrints("9"):
634 with tt.AssertPrints("9"):
633 ip.run_cell("amacro")
635 ip.run_cell("amacro")
634 with tt.AssertPrints("8"):
636 with tt.AssertPrints("8"):
635 ip.run_cell("amacro")
637 ip.run_cell("amacro")
636
638
637 class IntegerWrapper(ast.NodeTransformer):
639 class IntegerWrapper(ast.NodeTransformer):
638 """Wraps all integers in a call to Integer()"""
640 """Wraps all integers in a call to Integer()"""
639 def visit_Num(self, node):
641 def visit_Num(self, node):
640 if isinstance(node.n, int):
642 if isinstance(node.n, int):
641 return ast.Call(func=ast.Name(id='Integer', ctx=ast.Load()),
643 return ast.Call(func=ast.Name(id='Integer', ctx=ast.Load()),
642 args=[node], keywords=[])
644 args=[node], keywords=[])
643 return node
645 return node
644
646
645 class TestAstTransform2(unittest.TestCase):
647 class TestAstTransform2(unittest.TestCase):
646 def setUp(self):
648 def setUp(self):
647 self.intwrapper = IntegerWrapper()
649 self.intwrapper = IntegerWrapper()
648 ip.ast_transformers.append(self.intwrapper)
650 ip.ast_transformers.append(self.intwrapper)
649
651
650 self.calls = []
652 self.calls = []
651 def Integer(*args):
653 def Integer(*args):
652 self.calls.append(args)
654 self.calls.append(args)
653 return args
655 return args
654 ip.push({"Integer": Integer})
656 ip.push({"Integer": Integer})
655
657
656 def tearDown(self):
658 def tearDown(self):
657 ip.ast_transformers.remove(self.intwrapper)
659 ip.ast_transformers.remove(self.intwrapper)
658 del ip.user_ns['Integer']
660 del ip.user_ns['Integer']
659
661
660 def test_run_cell(self):
662 def test_run_cell(self):
661 ip.run_cell("n = 2")
663 ip.run_cell("n = 2")
662 self.assertEqual(self.calls, [(2,)])
664 self.assertEqual(self.calls, [(2,)])
663
665
664 # This shouldn't throw an error
666 # This shouldn't throw an error
665 ip.run_cell("o = 2.0")
667 ip.run_cell("o = 2.0")
666 self.assertEqual(ip.user_ns['o'], 2.0)
668 self.assertEqual(ip.user_ns['o'], 2.0)
667
669
668 def test_timeit(self):
670 def test_timeit(self):
669 called = set()
671 called = set()
670 def f(x):
672 def f(x):
671 called.add(x)
673 called.add(x)
672 ip.push({'f':f})
674 ip.push({'f':f})
673
675
674 with tt.AssertPrints("std. dev. of"):
676 with tt.AssertPrints("std. dev. of"):
675 ip.run_line_magic("timeit", "-n1 f(1)")
677 ip.run_line_magic("timeit", "-n1 f(1)")
676 self.assertEqual(called, {(1,)})
678 self.assertEqual(called, {(1,)})
677 called.clear()
679 called.clear()
678
680
679 with tt.AssertPrints("std. dev. of"):
681 with tt.AssertPrints("std. dev. of"):
680 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
682 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
681 self.assertEqual(called, {(2,), (3,)})
683 self.assertEqual(called, {(2,), (3,)})
682
684
683 class ErrorTransformer(ast.NodeTransformer):
685 class ErrorTransformer(ast.NodeTransformer):
684 """Throws an error when it sees a number."""
686 """Throws an error when it sees a number."""
685 def visit_Num(self, node):
687 def visit_Num(self, node):
686 raise ValueError("test")
688 raise ValueError("test")
687
689
688 class TestAstTransformError(unittest.TestCase):
690 class TestAstTransformError(unittest.TestCase):
689 def test_unregistering(self):
691 def test_unregistering(self):
690 err_transformer = ErrorTransformer()
692 err_transformer = ErrorTransformer()
691 ip.ast_transformers.append(err_transformer)
693 ip.ast_transformers.append(err_transformer)
692
694
693 with tt.AssertPrints("unregister", channel='stderr'):
695 with tt.AssertPrints("unregister", channel='stderr'):
694 ip.run_cell("1 + 2")
696 ip.run_cell("1 + 2")
695
697
696 # This should have been removed.
698 # This should have been removed.
697 nt.assert_not_in(err_transformer, ip.ast_transformers)
699 nt.assert_not_in(err_transformer, ip.ast_transformers)
698
700
699
701
700 class StringRejector(ast.NodeTransformer):
702 class StringRejector(ast.NodeTransformer):
701 """Throws an InputRejected when it sees a string literal.
703 """Throws an InputRejected when it sees a string literal.
702
704
703 Used to verify that NodeTransformers can signal that a piece of code should
705 Used to verify that NodeTransformers can signal that a piece of code should
704 not be executed by throwing an InputRejected.
706 not be executed by throwing an InputRejected.
705 """
707 """
706
708
707 def visit_Str(self, node):
709 def visit_Str(self, node):
708 raise InputRejected("test")
710 raise InputRejected("test")
709
711
710
712
711 class TestAstTransformInputRejection(unittest.TestCase):
713 class TestAstTransformInputRejection(unittest.TestCase):
712
714
713 def setUp(self):
715 def setUp(self):
714 self.transformer = StringRejector()
716 self.transformer = StringRejector()
715 ip.ast_transformers.append(self.transformer)
717 ip.ast_transformers.append(self.transformer)
716
718
717 def tearDown(self):
719 def tearDown(self):
718 ip.ast_transformers.remove(self.transformer)
720 ip.ast_transformers.remove(self.transformer)
719
721
720 def test_input_rejection(self):
722 def test_input_rejection(self):
721 """Check that NodeTransformers can reject input."""
723 """Check that NodeTransformers can reject input."""
722
724
723 expect_exception_tb = tt.AssertPrints("InputRejected: test")
725 expect_exception_tb = tt.AssertPrints("InputRejected: test")
724 expect_no_cell_output = tt.AssertNotPrints("'unsafe'", suppress=False)
726 expect_no_cell_output = tt.AssertNotPrints("'unsafe'", suppress=False)
725
727
726 # Run the same check twice to verify that the transformer is not
728 # Run the same check twice to verify that the transformer is not
727 # disabled after raising.
729 # disabled after raising.
728 with expect_exception_tb, expect_no_cell_output:
730 with expect_exception_tb, expect_no_cell_output:
729 ip.run_cell("'unsafe'")
731 ip.run_cell("'unsafe'")
730
732
731 with expect_exception_tb, expect_no_cell_output:
733 with expect_exception_tb, expect_no_cell_output:
732 res = ip.run_cell("'unsafe'")
734 res = ip.run_cell("'unsafe'")
733
735
734 self.assertIsInstance(res.error_before_exec, InputRejected)
736 self.assertIsInstance(res.error_before_exec, InputRejected)
735
737
736 def test__IPYTHON__():
738 def test__IPYTHON__():
737 # This shouldn't raise a NameError, that's all
739 # This shouldn't raise a NameError, that's all
738 __IPYTHON__
740 __IPYTHON__
739
741
740
742
741 class DummyRepr(object):
743 class DummyRepr(object):
742 def __repr__(self):
744 def __repr__(self):
743 return "DummyRepr"
745 return "DummyRepr"
744
746
745 def _repr_html_(self):
747 def _repr_html_(self):
746 return "<b>dummy</b>"
748 return "<b>dummy</b>"
747
749
748 def _repr_javascript_(self):
750 def _repr_javascript_(self):
749 return "console.log('hi');", {'key': 'value'}
751 return "console.log('hi');", {'key': 'value'}
750
752
751
753
752 def test_user_variables():
754 def test_user_variables():
753 # enable all formatters
755 # enable all formatters
754 ip.display_formatter.active_types = ip.display_formatter.format_types
756 ip.display_formatter.active_types = ip.display_formatter.format_types
755
757
756 ip.user_ns['dummy'] = d = DummyRepr()
758 ip.user_ns['dummy'] = d = DummyRepr()
757 keys = {'dummy', 'doesnotexist'}
759 keys = {'dummy', 'doesnotexist'}
758 r = ip.user_expressions({ key:key for key in keys})
760 r = ip.user_expressions({ key:key for key in keys})
759
761
760 nt.assert_equal(keys, set(r.keys()))
762 nt.assert_equal(keys, set(r.keys()))
761 dummy = r['dummy']
763 dummy = r['dummy']
762 nt.assert_equal({'status', 'data', 'metadata'}, set(dummy.keys()))
764 nt.assert_equal({'status', 'data', 'metadata'}, set(dummy.keys()))
763 nt.assert_equal(dummy['status'], 'ok')
765 nt.assert_equal(dummy['status'], 'ok')
764 data = dummy['data']
766 data = dummy['data']
765 metadata = dummy['metadata']
767 metadata = dummy['metadata']
766 nt.assert_equal(data.get('text/html'), d._repr_html_())
768 nt.assert_equal(data.get('text/html'), d._repr_html_())
767 js, jsmd = d._repr_javascript_()
769 js, jsmd = d._repr_javascript_()
768 nt.assert_equal(data.get('application/javascript'), js)
770 nt.assert_equal(data.get('application/javascript'), js)
769 nt.assert_equal(metadata.get('application/javascript'), jsmd)
771 nt.assert_equal(metadata.get('application/javascript'), jsmd)
770
772
771 dne = r['doesnotexist']
773 dne = r['doesnotexist']
772 nt.assert_equal(dne['status'], 'error')
774 nt.assert_equal(dne['status'], 'error')
773 nt.assert_equal(dne['ename'], 'NameError')
775 nt.assert_equal(dne['ename'], 'NameError')
774
776
775 # back to text only
777 # back to text only
776 ip.display_formatter.active_types = ['text/plain']
778 ip.display_formatter.active_types = ['text/plain']
777
779
778 def test_user_expression():
780 def test_user_expression():
779 # enable all formatters
781 # enable all formatters
780 ip.display_formatter.active_types = ip.display_formatter.format_types
782 ip.display_formatter.active_types = ip.display_formatter.format_types
781 query = {
783 query = {
782 'a' : '1 + 2',
784 'a' : '1 + 2',
783 'b' : '1/0',
785 'b' : '1/0',
784 }
786 }
785 r = ip.user_expressions(query)
787 r = ip.user_expressions(query)
786 import pprint
788 import pprint
787 pprint.pprint(r)
789 pprint.pprint(r)
788 nt.assert_equal(set(r.keys()), set(query.keys()))
790 nt.assert_equal(set(r.keys()), set(query.keys()))
789 a = r['a']
791 a = r['a']
790 nt.assert_equal({'status', 'data', 'metadata'}, set(a.keys()))
792 nt.assert_equal({'status', 'data', 'metadata'}, set(a.keys()))
791 nt.assert_equal(a['status'], 'ok')
793 nt.assert_equal(a['status'], 'ok')
792 data = a['data']
794 data = a['data']
793 metadata = a['metadata']
795 metadata = a['metadata']
794 nt.assert_equal(data.get('text/plain'), '3')
796 nt.assert_equal(data.get('text/plain'), '3')
795
797
796 b = r['b']
798 b = r['b']
797 nt.assert_equal(b['status'], 'error')
799 nt.assert_equal(b['status'], 'error')
798 nt.assert_equal(b['ename'], 'ZeroDivisionError')
800 nt.assert_equal(b['ename'], 'ZeroDivisionError')
799
801
800 # back to text only
802 # back to text only
801 ip.display_formatter.active_types = ['text/plain']
803 ip.display_formatter.active_types = ['text/plain']
802
804
803
805
804
806
805
807
806
808
807 class TestSyntaxErrorTransformer(unittest.TestCase):
809 class TestSyntaxErrorTransformer(unittest.TestCase):
808 """Check that SyntaxError raised by an input transformer is handled by run_cell()"""
810 """Check that SyntaxError raised by an input transformer is handled by run_cell()"""
809
811
810 class SyntaxErrorTransformer(InputTransformer):
812 class SyntaxErrorTransformer(InputTransformer):
811
813
812 def push(self, line):
814 def push(self, line):
813 pos = line.find('syntaxerror')
815 pos = line.find('syntaxerror')
814 if pos >= 0:
816 if pos >= 0:
815 e = SyntaxError('input contains "syntaxerror"')
817 e = SyntaxError('input contains "syntaxerror"')
816 e.text = line
818 e.text = line
817 e.offset = pos + 1
819 e.offset = pos + 1
818 raise e
820 raise e
819 return line
821 return line
820
822
821 def reset(self):
823 def reset(self):
822 pass
824 pass
823
825
824 def setUp(self):
826 def setUp(self):
825 self.transformer = TestSyntaxErrorTransformer.SyntaxErrorTransformer()
827 self.transformer = TestSyntaxErrorTransformer.SyntaxErrorTransformer()
826 ip.input_splitter.python_line_transforms.append(self.transformer)
828 ip.input_splitter.python_line_transforms.append(self.transformer)
827 ip.input_transformer_manager.python_line_transforms.append(self.transformer)
829 ip.input_transformer_manager.python_line_transforms.append(self.transformer)
828
830
829 def tearDown(self):
831 def tearDown(self):
830 ip.input_splitter.python_line_transforms.remove(self.transformer)
832 ip.input_splitter.python_line_transforms.remove(self.transformer)
831 ip.input_transformer_manager.python_line_transforms.remove(self.transformer)
833 ip.input_transformer_manager.python_line_transforms.remove(self.transformer)
832
834
833 def test_syntaxerror_input_transformer(self):
835 def test_syntaxerror_input_transformer(self):
834 with tt.AssertPrints('1234'):
836 with tt.AssertPrints('1234'):
835 ip.run_cell('1234')
837 ip.run_cell('1234')
836 with tt.AssertPrints('SyntaxError: invalid syntax'):
838 with tt.AssertPrints('SyntaxError: invalid syntax'):
837 ip.run_cell('1 2 3') # plain python syntax error
839 ip.run_cell('1 2 3') # plain python syntax error
838 with tt.AssertPrints('SyntaxError: input contains "syntaxerror"'):
840 with tt.AssertPrints('SyntaxError: input contains "syntaxerror"'):
839 ip.run_cell('2345 # syntaxerror') # input transformer syntax error
841 ip.run_cell('2345 # syntaxerror') # input transformer syntax error
840 with tt.AssertPrints('3456'):
842 with tt.AssertPrints('3456'):
841 ip.run_cell('3456')
843 ip.run_cell('3456')
842
844
843
845
844
846
845 def test_warning_suppression():
847 def test_warning_suppression():
846 ip.run_cell("import warnings")
848 ip.run_cell("import warnings")
847 try:
849 try:
848 with tt.AssertPrints("UserWarning: asdf", channel="stderr"):
850 with tt.AssertPrints("UserWarning: asdf", channel="stderr"):
849 ip.run_cell("warnings.warn('asdf')")
851 ip.run_cell("warnings.warn('asdf')")
850 # Here's the real test -- if we run that again, we should get the
852 # Here's the real test -- if we run that again, we should get the
851 # warning again. Traditionally, each warning was only issued once per
853 # warning again. Traditionally, each warning was only issued once per
852 # IPython session (approximately), even if the user typed in new and
854 # IPython session (approximately), even if the user typed in new and
853 # different code that should have also triggered the warning, leading
855 # different code that should have also triggered the warning, leading
854 # to much confusion.
856 # to much confusion.
855 with tt.AssertPrints("UserWarning: asdf", channel="stderr"):
857 with tt.AssertPrints("UserWarning: asdf", channel="stderr"):
856 ip.run_cell("warnings.warn('asdf')")
858 ip.run_cell("warnings.warn('asdf')")
857 finally:
859 finally:
858 ip.run_cell("del warnings")
860 ip.run_cell("del warnings")
859
861
860
862
861 def test_deprecation_warning():
863 def test_deprecation_warning():
862 ip.run_cell("""
864 ip.run_cell("""
863 import warnings
865 import warnings
864 def wrn():
866 def wrn():
865 warnings.warn(
867 warnings.warn(
866 "I AM A WARNING",
868 "I AM A WARNING",
867 DeprecationWarning
869 DeprecationWarning
868 )
870 )
869 """)
871 """)
870 try:
872 try:
871 with tt.AssertPrints("I AM A WARNING", channel="stderr"):
873 with tt.AssertPrints("I AM A WARNING", channel="stderr"):
872 ip.run_cell("wrn()")
874 ip.run_cell("wrn()")
873 finally:
875 finally:
874 ip.run_cell("del warnings")
876 ip.run_cell("del warnings")
875 ip.run_cell("del wrn")
877 ip.run_cell("del wrn")
876
878
877
879
878 class TestImportNoDeprecate(tt.TempFileMixin):
880 class TestImportNoDeprecate(tt.TempFileMixin):
879
881
880 def setup(self):
882 def setup(self):
881 """Make a valid python temp file."""
883 """Make a valid python temp file."""
882 self.mktmp("""
884 self.mktmp("""
883 import warnings
885 import warnings
884 def wrn():
886 def wrn():
885 warnings.warn(
887 warnings.warn(
886 "I AM A WARNING",
888 "I AM A WARNING",
887 DeprecationWarning
889 DeprecationWarning
888 )
890 )
889 """)
891 """)
890
892
891 def test_no_dep(self):
893 def test_no_dep(self):
892 """
894 """
893 No deprecation warning should be raised from imported functions
895 No deprecation warning should be raised from imported functions
894 """
896 """
895 ip.run_cell("from {} import wrn".format(self.fname))
897 ip.run_cell("from {} import wrn".format(self.fname))
896
898
897 with tt.AssertNotPrints("I AM A WARNING"):
899 with tt.AssertNotPrints("I AM A WARNING"):
898 ip.run_cell("wrn()")
900 ip.run_cell("wrn()")
899 ip.run_cell("del wrn")
901 ip.run_cell("del wrn")
@@ -1,776 +1,776 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 try:
16 try:
17 from pathlib import Path
17 from pathlib import Path
18 except ImportError:
18 except ImportError:
19 # for Python 3.3
19 # for Python 3.3
20 from pathlib2 import Path
20 from pathlib2 import Path
21
21
22 from IPython.utils import py3compat
22 from IPython.utils import py3compat
23
23
24 # datetime.strftime date format for ipython
24 # datetime.strftime date format for ipython
25 if sys.platform == 'win32':
25 if sys.platform == 'win32':
26 date_format = "%B %d, %Y"
26 date_format = "%B %d, %Y"
27 else:
27 else:
28 date_format = "%B %-d, %Y"
28 date_format = "%B %-d, %Y"
29
29
30 class LSString(str):
30 class LSString(str):
31 """String derivative with a special access attributes.
31 """String derivative with a special access attributes.
32
32
33 These are normal strings, but with the special attributes:
33 These are normal strings, but with the special attributes:
34
34
35 .l (or .list) : value as list (split on newlines).
35 .l (or .list) : value as list (split on newlines).
36 .n (or .nlstr): original value (the string itself).
36 .n (or .nlstr): original value (the string itself).
37 .s (or .spstr): value as whitespace-separated string.
37 .s (or .spstr): value as whitespace-separated string.
38 .p (or .paths): list of path objects (requires path.py package)
38 .p (or .paths): list of path objects (requires path.py package)
39
39
40 Any values which require transformations are computed only once and
40 Any values which require transformations are computed only once and
41 cached.
41 cached.
42
42
43 Such strings are very useful to efficiently interact with the shell, which
43 Such strings are very useful to efficiently interact with the shell, which
44 typically only understands whitespace-separated options for commands."""
44 typically only understands whitespace-separated options for commands."""
45
45
46 def get_list(self):
46 def get_list(self):
47 try:
47 try:
48 return self.__list
48 return self.__list
49 except AttributeError:
49 except AttributeError:
50 self.__list = self.split('\n')
50 self.__list = self.split('\n')
51 return self.__list
51 return self.__list
52
52
53 l = list = property(get_list)
53 l = list = property(get_list)
54
54
55 def get_spstr(self):
55 def get_spstr(self):
56 try:
56 try:
57 return self.__spstr
57 return self.__spstr
58 except AttributeError:
58 except AttributeError:
59 self.__spstr = self.replace('\n',' ')
59 self.__spstr = self.replace('\n',' ')
60 return self.__spstr
60 return self.__spstr
61
61
62 s = spstr = property(get_spstr)
62 s = spstr = property(get_spstr)
63
63
64 def get_nlstr(self):
64 def get_nlstr(self):
65 return self
65 return self
66
66
67 n = nlstr = property(get_nlstr)
67 n = nlstr = property(get_nlstr)
68
68
69 def get_paths(self):
69 def get_paths(self):
70 try:
70 try:
71 return self.__paths
71 return self.__paths
72 except AttributeError:
72 except AttributeError:
73 self.__paths = [Path(p) for p in self.split('\n') if os.path.exists(p)]
73 self.__paths = [Path(p) for p in self.split('\n') if os.path.exists(p)]
74 return self.__paths
74 return self.__paths
75
75
76 p = paths = property(get_paths)
76 p = paths = property(get_paths)
77
77
78 # FIXME: We need to reimplement type specific displayhook and then add this
78 # FIXME: We need to reimplement type specific displayhook and then add this
79 # back as a custom printer. This should also be moved outside utils into the
79 # back as a custom printer. This should also be moved outside utils into the
80 # core.
80 # core.
81
81
82 # def print_lsstring(arg):
82 # def print_lsstring(arg):
83 # """ Prettier (non-repr-like) and more informative printer for LSString """
83 # """ Prettier (non-repr-like) and more informative printer for LSString """
84 # print "LSString (.p, .n, .l, .s available). Value:"
84 # print "LSString (.p, .n, .l, .s available). Value:"
85 # print arg
85 # print arg
86 #
86 #
87 #
87 #
88 # print_lsstring = result_display.when_type(LSString)(print_lsstring)
88 # print_lsstring = result_display.when_type(LSString)(print_lsstring)
89
89
90
90
91 class SList(list):
91 class SList(list):
92 """List derivative with a special access attributes.
92 """List derivative with a special access attributes.
93
93
94 These are normal lists, but with the special attributes:
94 These are normal lists, but with the special attributes:
95
95
96 * .l (or .list) : value as list (the list itself).
96 * .l (or .list) : value as list (the list itself).
97 * .n (or .nlstr): value as a string, joined on newlines.
97 * .n (or .nlstr): value as a string, joined on newlines.
98 * .s (or .spstr): value as a string, joined on spaces.
98 * .s (or .spstr): value as a string, joined on spaces.
99 * .p (or .paths): list of path objects (requires path.py package)
99 * .p (or .paths): list of path objects (requires path.py package)
100
100
101 Any values which require transformations are computed only once and
101 Any values which require transformations are computed only once and
102 cached."""
102 cached."""
103
103
104 def get_list(self):
104 def get_list(self):
105 return self
105 return self
106
106
107 l = list = property(get_list)
107 l = list = property(get_list)
108
108
109 def get_spstr(self):
109 def get_spstr(self):
110 try:
110 try:
111 return self.__spstr
111 return self.__spstr
112 except AttributeError:
112 except AttributeError:
113 self.__spstr = ' '.join(self)
113 self.__spstr = ' '.join(self)
114 return self.__spstr
114 return self.__spstr
115
115
116 s = spstr = property(get_spstr)
116 s = spstr = property(get_spstr)
117
117
118 def get_nlstr(self):
118 def get_nlstr(self):
119 try:
119 try:
120 return self.__nlstr
120 return self.__nlstr
121 except AttributeError:
121 except AttributeError:
122 self.__nlstr = '\n'.join(self)
122 self.__nlstr = '\n'.join(self)
123 return self.__nlstr
123 return self.__nlstr
124
124
125 n = nlstr = property(get_nlstr)
125 n = nlstr = property(get_nlstr)
126
126
127 def get_paths(self):
127 def get_paths(self):
128 try:
128 try:
129 return self.__paths
129 return self.__paths
130 except AttributeError:
130 except AttributeError:
131 self.__paths = [Path(p) for p in self if os.path.exists(p)]
131 self.__paths = [Path(p) for p in self if os.path.exists(p)]
132 return self.__paths
132 return self.__paths
133
133
134 p = paths = property(get_paths)
134 p = paths = property(get_paths)
135
135
136 def grep(self, pattern, prune = False, field = None):
136 def grep(self, pattern, prune = False, field = None):
137 """ Return all strings matching 'pattern' (a regex or callable)
137 """ Return all strings matching 'pattern' (a regex or callable)
138
138
139 This is case-insensitive. If prune is true, return all items
139 This is case-insensitive. If prune is true, return all items
140 NOT matching the pattern.
140 NOT matching the pattern.
141
141
142 If field is specified, the match must occur in the specified
142 If field is specified, the match must occur in the specified
143 whitespace-separated field.
143 whitespace-separated field.
144
144
145 Examples::
145 Examples::
146
146
147 a.grep( lambda x: x.startswith('C') )
147 a.grep( lambda x: x.startswith('C') )
148 a.grep('Cha.*log', prune=1)
148 a.grep('Cha.*log', prune=1)
149 a.grep('chm', field=-1)
149 a.grep('chm', field=-1)
150 """
150 """
151
151
152 def match_target(s):
152 def match_target(s):
153 if field is None:
153 if field is None:
154 return s
154 return s
155 parts = s.split()
155 parts = s.split()
156 try:
156 try:
157 tgt = parts[field]
157 tgt = parts[field]
158 return tgt
158 return tgt
159 except IndexError:
159 except IndexError:
160 return ""
160 return ""
161
161
162 if isinstance(pattern, str):
162 if isinstance(pattern, str):
163 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
163 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
164 else:
164 else:
165 pred = pattern
165 pred = pattern
166 if not prune:
166 if not prune:
167 return SList([el for el in self if pred(match_target(el))])
167 return SList([el for el in self if pred(match_target(el))])
168 else:
168 else:
169 return SList([el for el in self if not pred(match_target(el))])
169 return SList([el for el in self if not pred(match_target(el))])
170
170
171 def fields(self, *fields):
171 def fields(self, *fields):
172 """ Collect whitespace-separated fields from string list
172 """ Collect whitespace-separated fields from string list
173
173
174 Allows quick awk-like usage of string lists.
174 Allows quick awk-like usage of string lists.
175
175
176 Example data (in var a, created by 'a = !ls -l')::
176 Example data (in var a, created by 'a = !ls -l')::
177
177
178 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
178 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
179 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
179 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
180
180
181 * ``a.fields(0)`` is ``['-rwxrwxrwx', 'drwxrwxrwx+']``
181 * ``a.fields(0)`` is ``['-rwxrwxrwx', 'drwxrwxrwx+']``
182 * ``a.fields(1,0)`` is ``['1 -rwxrwxrwx', '6 drwxrwxrwx+']``
182 * ``a.fields(1,0)`` is ``['1 -rwxrwxrwx', '6 drwxrwxrwx+']``
183 (note the joining by space).
183 (note the joining by space).
184 * ``a.fields(-1)`` is ``['ChangeLog', 'IPython']``
184 * ``a.fields(-1)`` is ``['ChangeLog', 'IPython']``
185
185
186 IndexErrors are ignored.
186 IndexErrors are ignored.
187
187
188 Without args, fields() just split()'s the strings.
188 Without args, fields() just split()'s the strings.
189 """
189 """
190 if len(fields) == 0:
190 if len(fields) == 0:
191 return [el.split() for el in self]
191 return [el.split() for el in self]
192
192
193 res = SList()
193 res = SList()
194 for el in [f.split() for f in self]:
194 for el in [f.split() for f in self]:
195 lineparts = []
195 lineparts = []
196
196
197 for fd in fields:
197 for fd in fields:
198 try:
198 try:
199 lineparts.append(el[fd])
199 lineparts.append(el[fd])
200 except IndexError:
200 except IndexError:
201 pass
201 pass
202 if lineparts:
202 if lineparts:
203 res.append(" ".join(lineparts))
203 res.append(" ".join(lineparts))
204
204
205 return res
205 return res
206
206
207 def sort(self,field= None, nums = False):
207 def sort(self,field= None, nums = False):
208 """ sort by specified fields (see fields())
208 """ sort by specified fields (see fields())
209
209
210 Example::
210 Example::
211
211
212 a.sort(1, nums = True)
212 a.sort(1, nums = True)
213
213
214 Sorts a by second field, in numerical order (so that 21 > 3)
214 Sorts a by second field, in numerical order (so that 21 > 3)
215
215
216 """
216 """
217
217
218 #decorate, sort, undecorate
218 #decorate, sort, undecorate
219 if field is not None:
219 if field is not None:
220 dsu = [[SList([line]).fields(field), line] for line in self]
220 dsu = [[SList([line]).fields(field), line] for line in self]
221 else:
221 else:
222 dsu = [[line, line] for line in self]
222 dsu = [[line, line] for line in self]
223 if nums:
223 if nums:
224 for i in range(len(dsu)):
224 for i in range(len(dsu)):
225 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
225 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
226 try:
226 try:
227 n = int(numstr)
227 n = int(numstr)
228 except ValueError:
228 except ValueError:
229 n = 0
229 n = 0
230 dsu[i][0] = n
230 dsu[i][0] = n
231
231
232
232
233 dsu.sort()
233 dsu.sort()
234 return SList([t[1] for t in dsu])
234 return SList([t[1] for t in dsu])
235
235
236
236
237 # FIXME: We need to reimplement type specific displayhook and then add this
237 # FIXME: We need to reimplement type specific displayhook and then add this
238 # back as a custom printer. This should also be moved outside utils into the
238 # back as a custom printer. This should also be moved outside utils into the
239 # core.
239 # core.
240
240
241 # def print_slist(arg):
241 # def print_slist(arg):
242 # """ Prettier (non-repr-like) and more informative printer for SList """
242 # """ Prettier (non-repr-like) and more informative printer for SList """
243 # print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):"
243 # print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):"
244 # if hasattr(arg, 'hideonce') and arg.hideonce:
244 # if hasattr(arg, 'hideonce') and arg.hideonce:
245 # arg.hideonce = False
245 # arg.hideonce = False
246 # return
246 # return
247 #
247 #
248 # nlprint(arg) # This was a nested list printer, now removed.
248 # nlprint(arg) # This was a nested list printer, now removed.
249 #
249 #
250 # print_slist = result_display.when_type(SList)(print_slist)
250 # print_slist = result_display.when_type(SList)(print_slist)
251
251
252
252
253 def indent(instr,nspaces=4, ntabs=0, flatten=False):
253 def indent(instr,nspaces=4, ntabs=0, flatten=False):
254 """Indent a string a given number of spaces or tabstops.
254 """Indent a string a given number of spaces or tabstops.
255
255
256 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
256 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
257
257
258 Parameters
258 Parameters
259 ----------
259 ----------
260
260
261 instr : basestring
261 instr : basestring
262 The string to be indented.
262 The string to be indented.
263 nspaces : int (default: 4)
263 nspaces : int (default: 4)
264 The number of spaces to be indented.
264 The number of spaces to be indented.
265 ntabs : int (default: 0)
265 ntabs : int (default: 0)
266 The number of tabs to be indented.
266 The number of tabs to be indented.
267 flatten : bool (default: False)
267 flatten : bool (default: False)
268 Whether to scrub existing indentation. If True, all lines will be
268 Whether to scrub existing indentation. If True, all lines will be
269 aligned to the same indentation. If False, existing indentation will
269 aligned to the same indentation. If False, existing indentation will
270 be strictly increased.
270 be strictly increased.
271
271
272 Returns
272 Returns
273 -------
273 -------
274
274
275 str|unicode : string indented by ntabs and nspaces.
275 str|unicode : string indented by ntabs and nspaces.
276
276
277 """
277 """
278 if instr is None:
278 if instr is None:
279 return
279 return
280 ind = '\t'*ntabs+' '*nspaces
280 ind = '\t'*ntabs+' '*nspaces
281 if flatten:
281 if flatten:
282 pat = re.compile(r'^\s*', re.MULTILINE)
282 pat = re.compile(r'^\s*', re.MULTILINE)
283 else:
283 else:
284 pat = re.compile(r'^', re.MULTILINE)
284 pat = re.compile(r'^', re.MULTILINE)
285 outstr = re.sub(pat, ind, instr)
285 outstr = re.sub(pat, ind, instr)
286 if outstr.endswith(os.linesep+ind):
286 if outstr.endswith(os.linesep+ind):
287 return outstr[:-len(ind)]
287 return outstr[:-len(ind)]
288 else:
288 else:
289 return outstr
289 return outstr
290
290
291
291
292 def list_strings(arg):
292 def list_strings(arg):
293 """Always return a list of strings, given a string or list of strings
293 """Always return a list of strings, given a string or list of strings
294 as input.
294 as input.
295
295
296 Examples
296 Examples
297 --------
297 --------
298 ::
298 ::
299
299
300 In [7]: list_strings('A single string')
300 In [7]: list_strings('A single string')
301 Out[7]: ['A single string']
301 Out[7]: ['A single string']
302
302
303 In [8]: list_strings(['A single string in a list'])
303 In [8]: list_strings(['A single string in a list'])
304 Out[8]: ['A single string in a list']
304 Out[8]: ['A single string in a list']
305
305
306 In [9]: list_strings(['A','list','of','strings'])
306 In [9]: list_strings(['A','list','of','strings'])
307 Out[9]: ['A', 'list', 'of', 'strings']
307 Out[9]: ['A', 'list', 'of', 'strings']
308 """
308 """
309
309
310 if isinstance(arg, str):
310 if isinstance(arg, str):
311 return [arg]
311 return [arg]
312 else:
312 else:
313 return arg
313 return arg
314
314
315
315
316 def marquee(txt='',width=78,mark='*'):
316 def marquee(txt='',width=78,mark='*'):
317 """Return the input string centered in a 'marquee'.
317 """Return the input string centered in a 'marquee'.
318
318
319 Examples
319 Examples
320 --------
320 --------
321 ::
321 ::
322
322
323 In [16]: marquee('A test',40)
323 In [16]: marquee('A test',40)
324 Out[16]: '**************** A test ****************'
324 Out[16]: '**************** A test ****************'
325
325
326 In [17]: marquee('A test',40,'-')
326 In [17]: marquee('A test',40,'-')
327 Out[17]: '---------------- A test ----------------'
327 Out[17]: '---------------- A test ----------------'
328
328
329 In [18]: marquee('A test',40,' ')
329 In [18]: marquee('A test',40,' ')
330 Out[18]: ' A test '
330 Out[18]: ' A test '
331
331
332 """
332 """
333 if not txt:
333 if not txt:
334 return (mark*width)[:width]
334 return (mark*width)[:width]
335 nmark = (width-len(txt)-2)//len(mark)//2
335 nmark = (width-len(txt)-2)//len(mark)//2
336 if nmark < 0: nmark =0
336 if nmark < 0: nmark =0
337 marks = mark*nmark
337 marks = mark*nmark
338 return '%s %s %s' % (marks,txt,marks)
338 return '%s %s %s' % (marks,txt,marks)
339
339
340
340
341 ini_spaces_re = re.compile(r'^(\s+)')
341 ini_spaces_re = re.compile(r'^(\s+)')
342
342
343 def num_ini_spaces(strng):
343 def num_ini_spaces(strng):
344 """Return the number of initial spaces in a string"""
344 """Return the number of initial spaces in a string"""
345
345
346 ini_spaces = ini_spaces_re.match(strng)
346 ini_spaces = ini_spaces_re.match(strng)
347 if ini_spaces:
347 if ini_spaces:
348 return ini_spaces.end()
348 return ini_spaces.end()
349 else:
349 else:
350 return 0
350 return 0
351
351
352
352
353 def format_screen(strng):
353 def format_screen(strng):
354 """Format a string for screen printing.
354 """Format a string for screen printing.
355
355
356 This removes some latex-type format codes."""
356 This removes some latex-type format codes."""
357 # Paragraph continue
357 # Paragraph continue
358 par_re = re.compile(r'\\$',re.MULTILINE)
358 par_re = re.compile(r'\\$',re.MULTILINE)
359 strng = par_re.sub('',strng)
359 strng = par_re.sub('',strng)
360 return strng
360 return strng
361
361
362
362
363 def dedent(text):
363 def dedent(text):
364 """Equivalent of textwrap.dedent that ignores unindented first line.
364 """Equivalent of textwrap.dedent that ignores unindented first line.
365
365
366 This means it will still dedent strings like:
366 This means it will still dedent strings like:
367 '''foo
367 '''foo
368 is a bar
368 is a bar
369 '''
369 '''
370
370
371 For use in wrap_paragraphs.
371 For use in wrap_paragraphs.
372 """
372 """
373
373
374 if text.startswith('\n'):
374 if text.startswith('\n'):
375 # text starts with blank line, don't ignore the first line
375 # text starts with blank line, don't ignore the first line
376 return textwrap.dedent(text)
376 return textwrap.dedent(text)
377
377
378 # split first line
378 # split first line
379 splits = text.split('\n',1)
379 splits = text.split('\n',1)
380 if len(splits) == 1:
380 if len(splits) == 1:
381 # only one line
381 # only one line
382 return textwrap.dedent(text)
382 return textwrap.dedent(text)
383
383
384 first, rest = splits
384 first, rest = splits
385 # dedent everything but the first line
385 # dedent everything but the first line
386 rest = textwrap.dedent(rest)
386 rest = textwrap.dedent(rest)
387 return '\n'.join([first, rest])
387 return '\n'.join([first, rest])
388
388
389
389
390 def wrap_paragraphs(text, ncols=80):
390 def wrap_paragraphs(text, ncols=80):
391 """Wrap multiple paragraphs to fit a specified width.
391 """Wrap multiple paragraphs to fit a specified width.
392
392
393 This is equivalent to textwrap.wrap, but with support for multiple
393 This is equivalent to textwrap.wrap, but with support for multiple
394 paragraphs, as separated by empty lines.
394 paragraphs, as separated by empty lines.
395
395
396 Returns
396 Returns
397 -------
397 -------
398
398
399 list of complete paragraphs, wrapped to fill `ncols` columns.
399 list of complete paragraphs, wrapped to fill `ncols` columns.
400 """
400 """
401 paragraph_re = re.compile(r'\n(\s*\n)+', re.MULTILINE)
401 paragraph_re = re.compile(r'\n(\s*\n)+', re.MULTILINE)
402 text = dedent(text).strip()
402 text = dedent(text).strip()
403 paragraphs = paragraph_re.split(text)[::2] # every other entry is space
403 paragraphs = paragraph_re.split(text)[::2] # every other entry is space
404 out_ps = []
404 out_ps = []
405 indent_re = re.compile(r'\n\s+', re.MULTILINE)
405 indent_re = re.compile(r'\n\s+', re.MULTILINE)
406 for p in paragraphs:
406 for p in paragraphs:
407 # presume indentation that survives dedent is meaningful formatting,
407 # presume indentation that survives dedent is meaningful formatting,
408 # so don't fill unless text is flush.
408 # so don't fill unless text is flush.
409 if indent_re.search(p) is None:
409 if indent_re.search(p) is None:
410 # wrap paragraph
410 # wrap paragraph
411 p = textwrap.fill(p, ncols)
411 p = textwrap.fill(p, ncols)
412 out_ps.append(p)
412 out_ps.append(p)
413 return out_ps
413 return out_ps
414
414
415
415
416 def long_substr(data):
416 def long_substr(data):
417 """Return the longest common substring in a list of strings.
417 """Return the longest common substring in a list of strings.
418
418
419 Credit: http://stackoverflow.com/questions/2892931/longest-common-substring-from-more-than-two-strings-python
419 Credit: http://stackoverflow.com/questions/2892931/longest-common-substring-from-more-than-two-strings-python
420 """
420 """
421 substr = ''
421 substr = ''
422 if len(data) > 1 and len(data[0]) > 0:
422 if len(data) > 1 and len(data[0]) > 0:
423 for i in range(len(data[0])):
423 for i in range(len(data[0])):
424 for j in range(len(data[0])-i+1):
424 for j in range(len(data[0])-i+1):
425 if j > len(substr) and all(data[0][i:i+j] in x for x in data):
425 if j > len(substr) and all(data[0][i:i+j] in x for x in data):
426 substr = data[0][i:i+j]
426 substr = data[0][i:i+j]
427 elif len(data) == 1:
427 elif len(data) == 1:
428 substr = data[0]
428 substr = data[0]
429 return substr
429 return substr
430
430
431
431
432 def strip_email_quotes(text):
432 def strip_email_quotes(text):
433 """Strip leading email quotation characters ('>').
433 """Strip leading email quotation characters ('>').
434
434
435 Removes any combination of leading '>' interspersed with whitespace that
435 Removes any combination of leading '>' interspersed with whitespace that
436 appears *identically* in all lines of the input text.
436 appears *identically* in all lines of the input text.
437
437
438 Parameters
438 Parameters
439 ----------
439 ----------
440 text : str
440 text : str
441
441
442 Examples
442 Examples
443 --------
443 --------
444
444
445 Simple uses::
445 Simple uses::
446
446
447 In [2]: strip_email_quotes('> > text')
447 In [2]: strip_email_quotes('> > text')
448 Out[2]: 'text'
448 Out[2]: 'text'
449
449
450 In [3]: strip_email_quotes('> > text\\n> > more')
450 In [3]: strip_email_quotes('> > text\\n> > more')
451 Out[3]: 'text\\nmore'
451 Out[3]: 'text\\nmore'
452
452
453 Note how only the common prefix that appears in all lines is stripped::
453 Note how only the common prefix that appears in all lines is stripped::
454
454
455 In [4]: strip_email_quotes('> > text\\n> > more\\n> more...')
455 In [4]: strip_email_quotes('> > text\\n> > more\\n> more...')
456 Out[4]: '> text\\n> more\\nmore...'
456 Out[4]: '> text\\n> more\\nmore...'
457
457
458 So if any line has no quote marks ('>') , then none are stripped from any
458 So if any line has no quote marks ('>') , then none are stripped from any
459 of them ::
459 of them ::
460
460
461 In [5]: strip_email_quotes('> > text\\n> > more\\nlast different')
461 In [5]: strip_email_quotes('> > text\\n> > more\\nlast different')
462 Out[5]: '> > text\\n> > more\\nlast different'
462 Out[5]: '> > text\\n> > more\\nlast different'
463 """
463 """
464 lines = text.splitlines()
464 lines = text.splitlines()
465 matches = set()
465 matches = set()
466 for line in lines:
466 for line in lines:
467 prefix = re.match(r'^(\s*>[ >]*)', line)
467 prefix = re.match(r'^(\s*>[ >]*)', line)
468 if prefix:
468 if prefix:
469 matches.add(prefix.group(1))
469 matches.add(prefix.group(1))
470 else:
470 else:
471 break
471 break
472 else:
472 else:
473 prefix = long_substr(list(matches))
473 prefix = long_substr(list(matches))
474 if prefix:
474 if prefix:
475 strip = len(prefix)
475 strip = len(prefix)
476 text = '\n'.join([ ln[strip:] for ln in lines])
476 text = '\n'.join([ ln[strip:] for ln in lines])
477 return text
477 return text
478
478
479 def strip_ansi(source):
479 def strip_ansi(source):
480 """
480 """
481 Remove ansi escape codes from text.
481 Remove ansi escape codes from text.
482
482
483 Parameters
483 Parameters
484 ----------
484 ----------
485 source : str
485 source : str
486 Source to remove the ansi from
486 Source to remove the ansi from
487 """
487 """
488 return re.sub(r'\033\[(\d|;)+?m', '', source)
488 return re.sub(r'\033\[(\d|;)+?m', '', source)
489
489
490
490
491 class EvalFormatter(Formatter):
491 class EvalFormatter(Formatter):
492 """A String Formatter that allows evaluation of simple expressions.
492 """A String Formatter that allows evaluation of simple expressions.
493
493
494 Note that this version interprets a : as specifying a format string (as per
494 Note that this version interprets a : as specifying a format string (as per
495 standard string formatting), so if slicing is required, you must explicitly
495 standard string formatting), so if slicing is required, you must explicitly
496 create a slice.
496 create a slice.
497
497
498 This is to be used in templating cases, such as the parallel batch
498 This is to be used in templating cases, such as the parallel batch
499 script templates, where simple arithmetic on arguments is useful.
499 script templates, where simple arithmetic on arguments is useful.
500
500
501 Examples
501 Examples
502 --------
502 --------
503 ::
503 ::
504
504
505 In [1]: f = EvalFormatter()
505 In [1]: f = EvalFormatter()
506 In [2]: f.format('{n//4}', n=8)
506 In [2]: f.format('{n//4}', n=8)
507 Out[2]: '2'
507 Out[2]: '2'
508
508
509 In [3]: f.format("{greeting[slice(2,4)]}", greeting="Hello")
509 In [3]: f.format("{greeting[slice(2,4)]}", greeting="Hello")
510 Out[3]: 'll'
510 Out[3]: 'll'
511 """
511 """
512 def get_field(self, name, args, kwargs):
512 def get_field(self, name, args, kwargs):
513 v = eval(name, kwargs)
513 v = eval(name, kwargs)
514 return v, name
514 return v, name
515
515
516 #XXX: As of Python 3.4, the format string parsing no longer splits on a colon
516 #XXX: As of Python 3.4, the format string parsing no longer splits on a colon
517 # inside [], so EvalFormatter can handle slicing. Once we only support 3.4 and
517 # inside [], so EvalFormatter can handle slicing. Once we only support 3.4 and
518 # above, it should be possible to remove FullEvalFormatter.
518 # above, it should be possible to remove FullEvalFormatter.
519
519
520 class FullEvalFormatter(Formatter):
520 class FullEvalFormatter(Formatter):
521 """A String Formatter that allows evaluation of simple expressions.
521 """A String Formatter that allows evaluation of simple expressions.
522
522
523 Any time a format key is not found in the kwargs,
523 Any time a format key is not found in the kwargs,
524 it will be tried as an expression in the kwargs namespace.
524 it will be tried as an expression in the kwargs namespace.
525
525
526 Note that this version allows slicing using [1:2], so you cannot specify
526 Note that this version allows slicing using [1:2], so you cannot specify
527 a format string. Use :class:`EvalFormatter` to permit format strings.
527 a format string. Use :class:`EvalFormatter` to permit format strings.
528
528
529 Examples
529 Examples
530 --------
530 --------
531 ::
531 ::
532
532
533 In [1]: f = FullEvalFormatter()
533 In [1]: f = FullEvalFormatter()
534 In [2]: f.format('{n//4}', n=8)
534 In [2]: f.format('{n//4}', n=8)
535 Out[2]: '2'
535 Out[2]: '2'
536
536
537 In [3]: f.format('{list(range(5))[2:4]}')
537 In [3]: f.format('{list(range(5))[2:4]}')
538 Out[3]: '[2, 3]'
538 Out[3]: '[2, 3]'
539
539
540 In [4]: f.format('{3*2}')
540 In [4]: f.format('{3*2}')
541 Out[4]: '6'
541 Out[4]: '6'
542 """
542 """
543 # copied from Formatter._vformat with minor changes to allow eval
543 # copied from Formatter._vformat with minor changes to allow eval
544 # and replace the format_spec code with slicing
544 # and replace the format_spec code with slicing
545 def vformat(self, format_string, args, kwargs):
545 def vformat(self, format_string, args, kwargs):
546 result = []
546 result = []
547 for literal_text, field_name, format_spec, conversion in \
547 for literal_text, field_name, format_spec, conversion in \
548 self.parse(format_string):
548 self.parse(format_string):
549
549
550 # output the literal text
550 # output the literal text
551 if literal_text:
551 if literal_text:
552 result.append(literal_text)
552 result.append(literal_text)
553
553
554 # if there's a field, output it
554 # if there's a field, output it
555 if field_name is not None:
555 if field_name is not None:
556 # this is some markup, find the object and do
556 # this is some markup, find the object and do
557 # the formatting
557 # the formatting
558
558
559 if format_spec:
559 if format_spec:
560 # override format spec, to allow slicing:
560 # override format spec, to allow slicing:
561 field_name = ':'.join([field_name, format_spec])
561 field_name = ':'.join([field_name, format_spec])
562
562
563 # eval the contents of the field for the object
563 # eval the contents of the field for the object
564 # to be formatted
564 # to be formatted
565 obj = eval(field_name, kwargs)
565 obj = eval(field_name, kwargs)
566
566
567 # do any conversion on the resulting object
567 # do any conversion on the resulting object
568 obj = self.convert_field(obj, conversion)
568 obj = self.convert_field(obj, conversion)
569
569
570 # format the object and append to the result
570 # format the object and append to the result
571 result.append(self.format_field(obj, ''))
571 result.append(self.format_field(obj, ''))
572
572
573 return ''.join(py3compat.cast_unicode(s) for s in result)
573 return ''.join(py3compat.cast_unicode(s) for s in result)
574
574
575
575
576 class DollarFormatter(FullEvalFormatter):
576 class DollarFormatter(FullEvalFormatter):
577 """Formatter allowing Itpl style $foo replacement, for names and attribute
577 """Formatter allowing Itpl style $foo replacement, for names and attribute
578 access only. Standard {foo} replacement also works, and allows full
578 access only. Standard {foo} replacement also works, and allows full
579 evaluation of its arguments.
579 evaluation of its arguments.
580
580
581 Examples
581 Examples
582 --------
582 --------
583 ::
583 ::
584
584
585 In [1]: f = DollarFormatter()
585 In [1]: f = DollarFormatter()
586 In [2]: f.format('{n//4}', n=8)
586 In [2]: f.format('{n//4}', n=8)
587 Out[2]: '2'
587 Out[2]: '2'
588
588
589 In [3]: f.format('23 * 76 is $result', result=23*76)
589 In [3]: f.format('23 * 76 is $result', result=23*76)
590 Out[3]: '23 * 76 is 1748'
590 Out[3]: '23 * 76 is 1748'
591
591
592 In [4]: f.format('$a or {b}', a=1, b=2)
592 In [4]: f.format('$a or {b}', a=1, b=2)
593 Out[4]: '1 or 2'
593 Out[4]: '1 or 2'
594 """
594 """
595 _dollar_pattern = re.compile("(.*?)\$(\$?[\w\.]+)")
595 _dollar_pattern_ignore_single_quote = re.compile("(.*?)\$(\$?[\w\.]+)(?=([^']*'[^']*')*[^']*$)")
596 def parse(self, fmt_string):
596 def parse(self, fmt_string):
597 for literal_txt, field_name, format_spec, conversion \
597 for literal_txt, field_name, format_spec, conversion \
598 in Formatter.parse(self, fmt_string):
598 in Formatter.parse(self, fmt_string):
599
599
600 # Find $foo patterns in the literal text.
600 # Find $foo patterns in the literal text.
601 continue_from = 0
601 continue_from = 0
602 txt = ""
602 txt = ""
603 for m in self._dollar_pattern.finditer(literal_txt):
603 for m in self._dollar_pattern_ignore_single_quote.finditer(literal_txt):
604 new_txt, new_field = m.group(1,2)
604 new_txt, new_field = m.group(1,2)
605 # $$foo --> $foo
605 # $$foo --> $foo
606 if new_field.startswith("$"):
606 if new_field.startswith("$"):
607 txt += new_txt + new_field
607 txt += new_txt + new_field
608 else:
608 else:
609 yield (txt + new_txt, new_field, "", None)
609 yield (txt + new_txt, new_field, "", None)
610 txt = ""
610 txt = ""
611 continue_from = m.end()
611 continue_from = m.end()
612
612
613 # Re-yield the {foo} style pattern
613 # Re-yield the {foo} style pattern
614 yield (txt + literal_txt[continue_from:], field_name, format_spec, conversion)
614 yield (txt + literal_txt[continue_from:], field_name, format_spec, conversion)
615
615
616 #-----------------------------------------------------------------------------
616 #-----------------------------------------------------------------------------
617 # Utils to columnize a list of string
617 # Utils to columnize a list of string
618 #-----------------------------------------------------------------------------
618 #-----------------------------------------------------------------------------
619
619
620 def _col_chunks(l, max_rows, row_first=False):
620 def _col_chunks(l, max_rows, row_first=False):
621 """Yield successive max_rows-sized column chunks from l."""
621 """Yield successive max_rows-sized column chunks from l."""
622 if row_first:
622 if row_first:
623 ncols = (len(l) // max_rows) + (len(l) % max_rows > 0)
623 ncols = (len(l) // max_rows) + (len(l) % max_rows > 0)
624 for i in range(ncols):
624 for i in range(ncols):
625 yield [l[j] for j in range(i, len(l), ncols)]
625 yield [l[j] for j in range(i, len(l), ncols)]
626 else:
626 else:
627 for i in range(0, len(l), max_rows):
627 for i in range(0, len(l), max_rows):
628 yield l[i:(i + max_rows)]
628 yield l[i:(i + max_rows)]
629
629
630
630
631 def _find_optimal(rlist, row_first=False, separator_size=2, displaywidth=80):
631 def _find_optimal(rlist, row_first=False, separator_size=2, displaywidth=80):
632 """Calculate optimal info to columnize a list of string"""
632 """Calculate optimal info to columnize a list of string"""
633 for max_rows in range(1, len(rlist) + 1):
633 for max_rows in range(1, len(rlist) + 1):
634 col_widths = list(map(max, _col_chunks(rlist, max_rows, row_first)))
634 col_widths = list(map(max, _col_chunks(rlist, max_rows, row_first)))
635 sumlength = sum(col_widths)
635 sumlength = sum(col_widths)
636 ncols = len(col_widths)
636 ncols = len(col_widths)
637 if sumlength + separator_size * (ncols - 1) <= displaywidth:
637 if sumlength + separator_size * (ncols - 1) <= displaywidth:
638 break
638 break
639 return {'num_columns': ncols,
639 return {'num_columns': ncols,
640 'optimal_separator_width': (displaywidth - sumlength) // (ncols - 1) if (ncols - 1) else 0,
640 'optimal_separator_width': (displaywidth - sumlength) // (ncols - 1) if (ncols - 1) else 0,
641 'max_rows': max_rows,
641 'max_rows': max_rows,
642 'column_widths': col_widths
642 'column_widths': col_widths
643 }
643 }
644
644
645
645
646 def _get_or_default(mylist, i, default=None):
646 def _get_or_default(mylist, i, default=None):
647 """return list item number, or default if don't exist"""
647 """return list item number, or default if don't exist"""
648 if i >= len(mylist):
648 if i >= len(mylist):
649 return default
649 return default
650 else :
650 else :
651 return mylist[i]
651 return mylist[i]
652
652
653
653
654 def compute_item_matrix(items, row_first=False, empty=None, *args, **kwargs) :
654 def compute_item_matrix(items, row_first=False, empty=None, *args, **kwargs) :
655 """Returns a nested list, and info to columnize items
655 """Returns a nested list, and info to columnize items
656
656
657 Parameters
657 Parameters
658 ----------
658 ----------
659
659
660 items
660 items
661 list of strings to columize
661 list of strings to columize
662 row_first : (default False)
662 row_first : (default False)
663 Whether to compute columns for a row-first matrix instead of
663 Whether to compute columns for a row-first matrix instead of
664 column-first (default).
664 column-first (default).
665 empty : (default None)
665 empty : (default None)
666 default value to fill list if needed
666 default value to fill list if needed
667 separator_size : int (default=2)
667 separator_size : int (default=2)
668 How much caracters will be used as a separation between each columns.
668 How much caracters will be used as a separation between each columns.
669 displaywidth : int (default=80)
669 displaywidth : int (default=80)
670 The width of the area onto wich the columns should enter
670 The width of the area onto wich the columns should enter
671
671
672 Returns
672 Returns
673 -------
673 -------
674
674
675 strings_matrix
675 strings_matrix
676
676
677 nested list of string, the outer most list contains as many list as
677 nested list of string, the outer most list contains as many list as
678 rows, the innermost lists have each as many element as colums. If the
678 rows, the innermost lists have each as many element as colums. If the
679 total number of elements in `items` does not equal the product of
679 total number of elements in `items` does not equal the product of
680 rows*columns, the last element of some lists are filled with `None`.
680 rows*columns, the last element of some lists are filled with `None`.
681
681
682 dict_info
682 dict_info
683 some info to make columnize easier:
683 some info to make columnize easier:
684
684
685 num_columns
685 num_columns
686 number of columns
686 number of columns
687 max_rows
687 max_rows
688 maximum number of rows (final number may be less)
688 maximum number of rows (final number may be less)
689 column_widths
689 column_widths
690 list of with of each columns
690 list of with of each columns
691 optimal_separator_width
691 optimal_separator_width
692 best separator width between columns
692 best separator width between columns
693
693
694 Examples
694 Examples
695 --------
695 --------
696 ::
696 ::
697
697
698 In [1]: l = ['aaa','b','cc','d','eeeee','f','g','h','i','j','k','l']
698 In [1]: l = ['aaa','b','cc','d','eeeee','f','g','h','i','j','k','l']
699 In [2]: list, info = compute_item_matrix(l, displaywidth=12)
699 In [2]: list, info = compute_item_matrix(l, displaywidth=12)
700 In [3]: list
700 In [3]: list
701 Out[3]: [['aaa', 'f', 'k'], ['b', 'g', 'l'], ['cc', 'h', None], ['d', 'i', None], ['eeeee', 'j', None]]
701 Out[3]: [['aaa', 'f', 'k'], ['b', 'g', 'l'], ['cc', 'h', None], ['d', 'i', None], ['eeeee', 'j', None]]
702 In [4]: ideal = {'num_columns': 3, 'column_widths': [5, 1, 1], 'optimal_separator_width': 2, 'max_rows': 5}
702 In [4]: ideal = {'num_columns': 3, 'column_widths': [5, 1, 1], 'optimal_separator_width': 2, 'max_rows': 5}
703 In [5]: all((info[k] == ideal[k] for k in ideal.keys()))
703 In [5]: all((info[k] == ideal[k] for k in ideal.keys()))
704 Out[5]: True
704 Out[5]: True
705 """
705 """
706 info = _find_optimal(list(map(len, items)), row_first, *args, **kwargs)
706 info = _find_optimal(list(map(len, items)), row_first, *args, **kwargs)
707 nrow, ncol = info['max_rows'], info['num_columns']
707 nrow, ncol = info['max_rows'], info['num_columns']
708 if row_first:
708 if row_first:
709 return ([[_get_or_default(items, r * ncol + c, default=empty) for c in range(ncol)] for r in range(nrow)], info)
709 return ([[_get_or_default(items, r * ncol + c, default=empty) for c in range(ncol)] for r in range(nrow)], info)
710 else:
710 else:
711 return ([[_get_or_default(items, c * nrow + r, default=empty) for c in range(ncol)] for r in range(nrow)], info)
711 return ([[_get_or_default(items, c * nrow + r, default=empty) for c in range(ncol)] for r in range(nrow)], info)
712
712
713
713
714 def columnize(items, row_first=False, separator=' ', displaywidth=80, spread=False):
714 def columnize(items, row_first=False, separator=' ', displaywidth=80, spread=False):
715 """ Transform a list of strings into a single string with columns.
715 """ Transform a list of strings into a single string with columns.
716
716
717 Parameters
717 Parameters
718 ----------
718 ----------
719 items : sequence of strings
719 items : sequence of strings
720 The strings to process.
720 The strings to process.
721
721
722 row_first : (default False)
722 row_first : (default False)
723 Whether to compute columns for a row-first matrix instead of
723 Whether to compute columns for a row-first matrix instead of
724 column-first (default).
724 column-first (default).
725
725
726 separator : str, optional [default is two spaces]
726 separator : str, optional [default is two spaces]
727 The string that separates columns.
727 The string that separates columns.
728
728
729 displaywidth : int, optional [default is 80]
729 displaywidth : int, optional [default is 80]
730 Width of the display in number of characters.
730 Width of the display in number of characters.
731
731
732 Returns
732 Returns
733 -------
733 -------
734 The formatted string.
734 The formatted string.
735 """
735 """
736 if not items:
736 if not items:
737 return '\n'
737 return '\n'
738 matrix, info = compute_item_matrix(items, row_first=row_first, separator_size=len(separator), displaywidth=displaywidth)
738 matrix, info = compute_item_matrix(items, row_first=row_first, separator_size=len(separator), displaywidth=displaywidth)
739 if spread:
739 if spread:
740 separator = separator.ljust(int(info['optimal_separator_width']))
740 separator = separator.ljust(int(info['optimal_separator_width']))
741 fmatrix = [filter(None, x) for x in matrix]
741 fmatrix = [filter(None, x) for x in matrix]
742 sjoin = lambda x : separator.join([ y.ljust(w, ' ') for y, w in zip(x, info['column_widths'])])
742 sjoin = lambda x : separator.join([ y.ljust(w, ' ') for y, w in zip(x, info['column_widths'])])
743 return '\n'.join(map(sjoin, fmatrix))+'\n'
743 return '\n'.join(map(sjoin, fmatrix))+'\n'
744
744
745
745
746 def get_text_list(list_, last_sep=' and ', sep=", ", wrap_item_with=""):
746 def get_text_list(list_, last_sep=' and ', sep=", ", wrap_item_with=""):
747 """
747 """
748 Return a string with a natural enumeration of items
748 Return a string with a natural enumeration of items
749
749
750 >>> get_text_list(['a', 'b', 'c', 'd'])
750 >>> get_text_list(['a', 'b', 'c', 'd'])
751 'a, b, c and d'
751 'a, b, c and d'
752 >>> get_text_list(['a', 'b', 'c'], ' or ')
752 >>> get_text_list(['a', 'b', 'c'], ' or ')
753 'a, b or c'
753 'a, b or c'
754 >>> get_text_list(['a', 'b', 'c'], ', ')
754 >>> get_text_list(['a', 'b', 'c'], ', ')
755 'a, b, c'
755 'a, b, c'
756 >>> get_text_list(['a', 'b'], ' or ')
756 >>> get_text_list(['a', 'b'], ' or ')
757 'a or b'
757 'a or b'
758 >>> get_text_list(['a'])
758 >>> get_text_list(['a'])
759 'a'
759 'a'
760 >>> get_text_list([])
760 >>> get_text_list([])
761 ''
761 ''
762 >>> get_text_list(['a', 'b'], wrap_item_with="`")
762 >>> get_text_list(['a', 'b'], wrap_item_with="`")
763 '`a` and `b`'
763 '`a` and `b`'
764 >>> get_text_list(['a', 'b', 'c', 'd'], " = ", sep=" + ")
764 >>> get_text_list(['a', 'b', 'c', 'd'], " = ", sep=" + ")
765 'a + b + c = d'
765 'a + b + c = d'
766 """
766 """
767 if len(list_) == 0:
767 if len(list_) == 0:
768 return ''
768 return ''
769 if wrap_item_with:
769 if wrap_item_with:
770 list_ = ['%s%s%s' % (wrap_item_with, item, wrap_item_with) for
770 list_ = ['%s%s%s' % (wrap_item_with, item, wrap_item_with) for
771 item in list_]
771 item in list_]
772 if len(list_) == 1:
772 if len(list_) == 1:
773 return list_[0]
773 return list_[0]
774 return '%s%s%s' % (
774 return '%s%s%s' % (
775 sep.join(i for i in list_[:-1]),
775 sep.join(i for i in list_[:-1]),
776 last_sep, list_[-1])
776 last_sep, list_[-1])
General Comments 0
You need to be logged in to leave comments. Login now