##// END OF EJS Templates
Merge branch 'dollar-escape'
Thomas Kluyver -
r5376:16b9fed8 merge
parent child Browse files
Show More
@@ -1,237 +1,240 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 Authors
8 Authors
9 -------
9 -------
10 * Fernando Perez
10 * Fernando Perez
11 """
11 """
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Copyright (C) 2011 The IPython Development Team
13 # Copyright (C) 2011 The IPython Development Team
14 #
14 #
15 # Distributed under the terms of the BSD License. The full license is in
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
16 # the file COPYING, distributed as part of this software.
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Imports
20 # Imports
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22 # stdlib
22 # stdlib
23 import os
23 import os
24 import shutil
24 import shutil
25 import tempfile
25 import tempfile
26 import unittest
26 import unittest
27 from os.path import join
27 from os.path import join
28 from StringIO import StringIO
28 from StringIO import StringIO
29
29
30 from IPython.testing import decorators as dec
30 from IPython.testing import decorators as dec
31 from IPython.utils import io
31 from IPython.utils import io
32
32
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34 # Tests
34 # Tests
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36
36
37 class InteractiveShellTestCase(unittest.TestCase):
37 class InteractiveShellTestCase(unittest.TestCase):
38 def test_naked_string_cells(self):
38 def test_naked_string_cells(self):
39 """Test that cells with only naked strings are fully executed"""
39 """Test that cells with only naked strings are fully executed"""
40 ip = get_ipython()
40 ip = get_ipython()
41 # First, single-line inputs
41 # First, single-line inputs
42 ip.run_cell('"a"\n')
42 ip.run_cell('"a"\n')
43 self.assertEquals(ip.user_ns['_'], 'a')
43 self.assertEquals(ip.user_ns['_'], 'a')
44 # And also multi-line cells
44 # And also multi-line cells
45 ip.run_cell('"""a\nb"""\n')
45 ip.run_cell('"""a\nb"""\n')
46 self.assertEquals(ip.user_ns['_'], 'a\nb')
46 self.assertEquals(ip.user_ns['_'], 'a\nb')
47
47
48 def test_run_empty_cell(self):
48 def test_run_empty_cell(self):
49 """Just make sure we don't get a horrible error with a blank
49 """Just make sure we don't get a horrible error with a blank
50 cell of input. Yes, I did overlook that."""
50 cell of input. Yes, I did overlook that."""
51 ip = get_ipython()
51 ip = get_ipython()
52 old_xc = ip.execution_count
52 old_xc = ip.execution_count
53 ip.run_cell('')
53 ip.run_cell('')
54 self.assertEquals(ip.execution_count, old_xc)
54 self.assertEquals(ip.execution_count, old_xc)
55
55
56 def test_run_cell_multiline(self):
56 def test_run_cell_multiline(self):
57 """Multi-block, multi-line cells must execute correctly.
57 """Multi-block, multi-line cells must execute correctly.
58 """
58 """
59 ip = get_ipython()
59 ip = get_ipython()
60 src = '\n'.join(["x=1",
60 src = '\n'.join(["x=1",
61 "y=2",
61 "y=2",
62 "if 1:",
62 "if 1:",
63 " x += 1",
63 " x += 1",
64 " y += 1",])
64 " y += 1",])
65 ip.run_cell(src)
65 ip.run_cell(src)
66 self.assertEquals(ip.user_ns['x'], 2)
66 self.assertEquals(ip.user_ns['x'], 2)
67 self.assertEquals(ip.user_ns['y'], 3)
67 self.assertEquals(ip.user_ns['y'], 3)
68
68
69 def test_multiline_string_cells(self):
69 def test_multiline_string_cells(self):
70 "Code sprinkled with multiline strings should execute (GH-306)"
70 "Code sprinkled with multiline strings should execute (GH-306)"
71 ip = get_ipython()
71 ip = get_ipython()
72 ip.run_cell('tmp=0')
72 ip.run_cell('tmp=0')
73 self.assertEquals(ip.user_ns['tmp'], 0)
73 self.assertEquals(ip.user_ns['tmp'], 0)
74 ip.run_cell('tmp=1;"""a\nb"""\n')
74 ip.run_cell('tmp=1;"""a\nb"""\n')
75 self.assertEquals(ip.user_ns['tmp'], 1)
75 self.assertEquals(ip.user_ns['tmp'], 1)
76
76
77 def test_dont_cache_with_semicolon(self):
77 def test_dont_cache_with_semicolon(self):
78 "Ending a line with semicolon should not cache the returned object (GH-307)"
78 "Ending a line with semicolon should not cache the returned object (GH-307)"
79 ip = get_ipython()
79 ip = get_ipython()
80 oldlen = len(ip.user_ns['Out'])
80 oldlen = len(ip.user_ns['Out'])
81 a = ip.run_cell('1;', store_history=True)
81 a = ip.run_cell('1;', store_history=True)
82 newlen = len(ip.user_ns['Out'])
82 newlen = len(ip.user_ns['Out'])
83 self.assertEquals(oldlen, newlen)
83 self.assertEquals(oldlen, newlen)
84 #also test the default caching behavior
84 #also test the default caching behavior
85 ip.run_cell('1', store_history=True)
85 ip.run_cell('1', store_history=True)
86 newlen = len(ip.user_ns['Out'])
86 newlen = len(ip.user_ns['Out'])
87 self.assertEquals(oldlen+1, newlen)
87 self.assertEquals(oldlen+1, newlen)
88
88
89 def test_In_variable(self):
89 def test_In_variable(self):
90 "Verify that In variable grows with user input (GH-284)"
90 "Verify that In variable grows with user input (GH-284)"
91 ip = get_ipython()
91 ip = get_ipython()
92 oldlen = len(ip.user_ns['In'])
92 oldlen = len(ip.user_ns['In'])
93 ip.run_cell('1;', store_history=True)
93 ip.run_cell('1;', store_history=True)
94 newlen = len(ip.user_ns['In'])
94 newlen = len(ip.user_ns['In'])
95 self.assertEquals(oldlen+1, newlen)
95 self.assertEquals(oldlen+1, newlen)
96 self.assertEquals(ip.user_ns['In'][-1],'1;')
96 self.assertEquals(ip.user_ns['In'][-1],'1;')
97
97
98 def test_magic_names_in_string(self):
98 def test_magic_names_in_string(self):
99 ip = get_ipython()
99 ip = get_ipython()
100 ip.run_cell('a = """\n%exit\n"""')
100 ip.run_cell('a = """\n%exit\n"""')
101 self.assertEquals(ip.user_ns['a'], '\n%exit\n')
101 self.assertEquals(ip.user_ns['a'], '\n%exit\n')
102
102
103 def test_alias_crash(self):
103 def test_alias_crash(self):
104 """Errors in prefilter can't crash IPython"""
104 """Errors in prefilter can't crash IPython"""
105 ip = get_ipython()
105 ip = get_ipython()
106 ip.run_cell('%alias parts echo first %s second %s')
106 ip.run_cell('%alias parts echo first %s second %s')
107 # capture stderr:
107 # capture stderr:
108 save_err = io.stderr
108 save_err = io.stderr
109 io.stderr = StringIO()
109 io.stderr = StringIO()
110 ip.run_cell('parts 1')
110 ip.run_cell('parts 1')
111 err = io.stderr.getvalue()
111 err = io.stderr.getvalue()
112 io.stderr = save_err
112 io.stderr = save_err
113 self.assertEquals(err.split(':')[0], 'ERROR')
113 self.assertEquals(err.split(':')[0], 'ERROR')
114
114
115 def test_trailing_newline(self):
115 def test_trailing_newline(self):
116 """test that running !(command) does not raise a SyntaxError"""
116 """test that running !(command) does not raise a SyntaxError"""
117 ip = get_ipython()
117 ip = get_ipython()
118 ip.run_cell('!(true)\n', False)
118 ip.run_cell('!(true)\n', False)
119 ip.run_cell('!(true)\n\n\n', False)
119 ip.run_cell('!(true)\n\n\n', False)
120
120
121 def test_gh_597(self):
121 def test_gh_597(self):
122 """Pretty-printing lists of objects with non-ascii reprs may cause
122 """Pretty-printing lists of objects with non-ascii reprs may cause
123 problems."""
123 problems."""
124 class Spam(object):
124 class Spam(object):
125 def __repr__(self):
125 def __repr__(self):
126 return "\xe9"*50
126 return "\xe9"*50
127 import IPython.core.formatters
127 import IPython.core.formatters
128 f = IPython.core.formatters.PlainTextFormatter()
128 f = IPython.core.formatters.PlainTextFormatter()
129 f([Spam(),Spam()])
129 f([Spam(),Spam()])
130
130
131 def test_future_flags(self):
131 def test_future_flags(self):
132 """Check that future flags are used for parsing code (gh-777)"""
132 """Check that future flags are used for parsing code (gh-777)"""
133 ip = get_ipython()
133 ip = get_ipython()
134 ip.run_cell('from __future__ import print_function')
134 ip.run_cell('from __future__ import print_function')
135 try:
135 try:
136 ip.run_cell('prfunc_return_val = print(1,2, sep=" ")')
136 ip.run_cell('prfunc_return_val = print(1,2, sep=" ")')
137 assert 'prfunc_return_val' in ip.user_ns
137 assert 'prfunc_return_val' in ip.user_ns
138 finally:
138 finally:
139 # Reset compiler flags so we don't mess up other tests.
139 # Reset compiler flags so we don't mess up other tests.
140 ip.compile.reset_compiler_flags()
140 ip.compile.reset_compiler_flags()
141
141
142 def test_future_unicode(self):
142 def test_future_unicode(self):
143 """Check that unicode_literals is imported from __future__ (gh #786)"""
143 """Check that unicode_literals is imported from __future__ (gh #786)"""
144 ip = get_ipython()
144 ip = get_ipython()
145 try:
145 try:
146 ip.run_cell(u'byte_str = "a"')
146 ip.run_cell(u'byte_str = "a"')
147 assert isinstance(ip.user_ns['byte_str'], str) # string literals are byte strings by default
147 assert isinstance(ip.user_ns['byte_str'], str) # string literals are byte strings by default
148 ip.run_cell('from __future__ import unicode_literals')
148 ip.run_cell('from __future__ import unicode_literals')
149 ip.run_cell(u'unicode_str = "a"')
149 ip.run_cell(u'unicode_str = "a"')
150 assert isinstance(ip.user_ns['unicode_str'], unicode) # strings literals are now unicode
150 assert isinstance(ip.user_ns['unicode_str'], unicode) # strings literals are now unicode
151 finally:
151 finally:
152 # Reset compiler flags so we don't mess up other tests.
152 # Reset compiler flags so we don't mess up other tests.
153 ip.compile.reset_compiler_flags()
153 ip.compile.reset_compiler_flags()
154
154
155 def test_bad_custom_tb(self):
155 def test_bad_custom_tb(self):
156 """Check that InteractiveShell is protected from bad custom exception handlers"""
156 """Check that InteractiveShell is protected from bad custom exception handlers"""
157 ip = get_ipython()
157 ip = get_ipython()
158 from IPython.utils import io
158 from IPython.utils import io
159 save_stderr = io.stderr
159 save_stderr = io.stderr
160 try:
160 try:
161 # capture stderr
161 # capture stderr
162 io.stderr = StringIO()
162 io.stderr = StringIO()
163 ip.set_custom_exc((IOError,), lambda etype,value,tb: 1/0)
163 ip.set_custom_exc((IOError,), lambda etype,value,tb: 1/0)
164 self.assertEquals(ip.custom_exceptions, (IOError,))
164 self.assertEquals(ip.custom_exceptions, (IOError,))
165 ip.run_cell(u'raise IOError("foo")')
165 ip.run_cell(u'raise IOError("foo")')
166 self.assertEquals(ip.custom_exceptions, ())
166 self.assertEquals(ip.custom_exceptions, ())
167 self.assertTrue("Custom TB Handler failed" in io.stderr.getvalue())
167 self.assertTrue("Custom TB Handler failed" in io.stderr.getvalue())
168 finally:
168 finally:
169 io.stderr = save_stderr
169 io.stderr = save_stderr
170
170
171 def test_bad_custom_tb_return(self):
171 def test_bad_custom_tb_return(self):
172 """Check that InteractiveShell is protected from bad return types in custom exception handlers"""
172 """Check that InteractiveShell is protected from bad return types in custom exception handlers"""
173 ip = get_ipython()
173 ip = get_ipython()
174 from IPython.utils import io
174 from IPython.utils import io
175 save_stderr = io.stderr
175 save_stderr = io.stderr
176 try:
176 try:
177 # capture stderr
177 # capture stderr
178 io.stderr = StringIO()
178 io.stderr = StringIO()
179 ip.set_custom_exc((NameError,),lambda etype,value,tb, tb_offset=None: 1)
179 ip.set_custom_exc((NameError,),lambda etype,value,tb, tb_offset=None: 1)
180 self.assertEquals(ip.custom_exceptions, (NameError,))
180 self.assertEquals(ip.custom_exceptions, (NameError,))
181 ip.run_cell(u'a=abracadabra')
181 ip.run_cell(u'a=abracadabra')
182 self.assertEquals(ip.custom_exceptions, ())
182 self.assertEquals(ip.custom_exceptions, ())
183 self.assertTrue("Custom TB Handler failed" in io.stderr.getvalue())
183 self.assertTrue("Custom TB Handler failed" in io.stderr.getvalue())
184 finally:
184 finally:
185 io.stderr = save_stderr
185 io.stderr = save_stderr
186
186
187 def test_drop_by_id(self):
187 def test_drop_by_id(self):
188 ip = get_ipython()
188 ip = get_ipython()
189 myvars = {"a":object(), "b":object(), "c": object()}
189 myvars = {"a":object(), "b":object(), "c": object()}
190 ip.push(myvars, interactive=False)
190 ip.push(myvars, interactive=False)
191 for name in myvars:
191 for name in myvars:
192 assert name in ip.user_ns, name
192 assert name in ip.user_ns, name
193 assert name in ip.user_ns_hidden, name
193 assert name in ip.user_ns_hidden, name
194 ip.user_ns['b'] = 12
194 ip.user_ns['b'] = 12
195 ip.drop_by_id(myvars)
195 ip.drop_by_id(myvars)
196 for name in ["a", "c"]:
196 for name in ["a", "c"]:
197 assert name not in ip.user_ns, name
197 assert name not in ip.user_ns, name
198 assert name not in ip.user_ns_hidden, name
198 assert name not in ip.user_ns_hidden, name
199 assert ip.user_ns['b'] == 12
199 assert ip.user_ns['b'] == 12
200 ip.reset()
200 ip.reset()
201
201
202 def test_var_expand(self):
202 def test_var_expand(self):
203 ip = get_ipython()
203 ip = get_ipython()
204 ip.user_ns['f'] = 'Ca\xc3\xb1o'
204 ip.user_ns['f'] = u'Ca\xf1o'
205 self.assertEqual(ip.var_expand(u'echo $f'), u'echo Ca\xf1o')
206
207 ip.user_ns['f'] = b'Ca\xc3\xb1o'
205 # This should not raise any exception:
208 # This should not raise any exception:
206 ip.var_expand(u'echo $f')
209 ip.var_expand(u'echo $f')
207
210
208
211
209 class TestSafeExecfileNonAsciiPath(unittest.TestCase):
212 class TestSafeExecfileNonAsciiPath(unittest.TestCase):
210
213
211 def setUp(self):
214 def setUp(self):
212 self.BASETESTDIR = tempfile.mkdtemp()
215 self.BASETESTDIR = tempfile.mkdtemp()
213 self.TESTDIR = join(self.BASETESTDIR, u"Γ₯Àâ")
216 self.TESTDIR = join(self.BASETESTDIR, u"Γ₯Àâ")
214 os.mkdir(self.TESTDIR)
217 os.mkdir(self.TESTDIR)
215 with open(join(self.TESTDIR, u"Γ₯Àâtestscript.py"), "w") as sfile:
218 with open(join(self.TESTDIR, u"Γ₯Àâtestscript.py"), "w") as sfile:
216 sfile.write("pass\n")
219 sfile.write("pass\n")
217 self.oldpath = os.getcwdu()
220 self.oldpath = os.getcwdu()
218 os.chdir(self.TESTDIR)
221 os.chdir(self.TESTDIR)
219 self.fname = u"Γ₯Àâtestscript.py"
222 self.fname = u"Γ₯Àâtestscript.py"
220
223
221
224
222 def tearDown(self):
225 def tearDown(self):
223 os.chdir(self.oldpath)
226 os.chdir(self.oldpath)
224 shutil.rmtree(self.BASETESTDIR)
227 shutil.rmtree(self.BASETESTDIR)
225
228
226 def test_1(self):
229 def test_1(self):
227 """Test safe_execfile with non-ascii path
230 """Test safe_execfile with non-ascii path
228 """
231 """
229 _ip.shell.safe_execfile(self.fname, {}, raise_exceptions=True)
232 _ip.shell.safe_execfile(self.fname, {}, raise_exceptions=True)
230
233
231
234
232 class TestSystemRaw(unittest.TestCase):
235 class TestSystemRaw(unittest.TestCase):
233 def test_1(self):
236 def test_1(self):
234 """Test system_raw with non-ascii cmd
237 """Test system_raw with non-ascii cmd
235 """
238 """
236 cmd = ur'''python -c "'Γ₯Àâ'" '''
239 cmd = ur'''python -c "'Γ₯Àâ'" '''
237 _ip.shell.system_raw(cmd)
240 _ip.shell.system_raw(cmd)
@@ -1,109 +1,113 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """Tests for IPython.utils.text"""
2 """Tests for IPython.utils.text"""
3
3
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2011 The IPython Development Team
5 # Copyright (C) 2011 The IPython Development Team
6 #
6 #
7 # Distributed under the terms of the BSD License. The full license is in
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 import os
15 import os
16 import math
16 import math
17
17
18 import nose.tools as nt
18 import nose.tools as nt
19
19
20 from nose import with_setup
20 from nose import with_setup
21
21
22 from IPython.testing import decorators as dec
22 from IPython.testing import decorators as dec
23 from IPython.utils import text
23 from IPython.utils import text
24
24
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26 # Globals
26 # Globals
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28
28
29 def test_columnize():
29 def test_columnize():
30 """Basic columnize tests."""
30 """Basic columnize tests."""
31 size = 5
31 size = 5
32 items = [l*size for l in 'abc']
32 items = [l*size for l in 'abc']
33 out = text.columnize(items, displaywidth=80)
33 out = text.columnize(items, displaywidth=80)
34 nt.assert_equals(out, 'aaaaa bbbbb ccccc\n')
34 nt.assert_equals(out, 'aaaaa bbbbb ccccc\n')
35 out = text.columnize(items, displaywidth=10)
35 out = text.columnize(items, displaywidth=10)
36 nt.assert_equals(out, 'aaaaa ccccc\nbbbbb\n')
36 nt.assert_equals(out, 'aaaaa ccccc\nbbbbb\n')
37
37
38
38
39 def test_columnize_long():
39 def test_columnize_long():
40 """Test columnize with inputs longer than the display window"""
40 """Test columnize with inputs longer than the display window"""
41 text.columnize(['a'*81, 'b'*81], displaywidth=80)
41 text.columnize(['a'*81, 'b'*81], displaywidth=80)
42 size = 11
42 size = 11
43 items = [l*size for l in 'abc']
43 items = [l*size for l in 'abc']
44 out = text.columnize(items, displaywidth=size-1)
44 out = text.columnize(items, displaywidth=size-1)
45 nt.assert_equals(out, '\n'.join(items+['']))
45 nt.assert_equals(out, '\n'.join(items+['']))
46
46
47 def eval_formatter_check(f):
47 def eval_formatter_check(f):
48 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os, u=u"cafΓ©", b="cafΓ©")
48 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os, u=u"cafΓ©", b="cafΓ©")
49 s = f.format("{n} {n//4} {stuff.split()[0]}", **ns)
49 s = f.format("{n} {n//4} {stuff.split()[0]}", **ns)
50 nt.assert_equals(s, "12 3 hello")
50 nt.assert_equals(s, "12 3 hello")
51 s = f.format(' '.join(['{n//%i}'%i for i in range(1,8)]), **ns)
51 s = f.format(' '.join(['{n//%i}'%i for i in range(1,8)]), **ns)
52 nt.assert_equals(s, "12 6 4 3 2 2 1")
52 nt.assert_equals(s, "12 6 4 3 2 2 1")
53 s = f.format('{[n//i for i in range(1,8)]}', **ns)
53 s = f.format('{[n//i for i in range(1,8)]}', **ns)
54 nt.assert_equals(s, "[12, 6, 4, 3, 2, 2, 1]")
54 nt.assert_equals(s, "[12, 6, 4, 3, 2, 2, 1]")
55 s = f.format("{stuff!s}", **ns)
55 s = f.format("{stuff!s}", **ns)
56 nt.assert_equals(s, ns['stuff'])
56 nt.assert_equals(s, ns['stuff'])
57 s = f.format("{stuff!r}", **ns)
57 s = f.format("{stuff!r}", **ns)
58 nt.assert_equals(s, repr(ns['stuff']))
58 nt.assert_equals(s, repr(ns['stuff']))
59
59
60 # Check with unicode:
60 # Check with unicode:
61 s = f.format("{u}", **ns)
61 s = f.format("{u}", **ns)
62 nt.assert_equals(s, ns['u'])
62 nt.assert_equals(s, ns['u'])
63 # This decodes in a platform dependent manner, but it shouldn't error out
63 # This decodes in a platform dependent manner, but it shouldn't error out
64 s = f.format("{b}", **ns)
64 s = f.format("{b}", **ns)
65
65
66 nt.assert_raises(NameError, f.format, '{dne}', **ns)
66 nt.assert_raises(NameError, f.format, '{dne}', **ns)
67
67
68 def eval_formatter_slicing_check(f):
68 def eval_formatter_slicing_check(f):
69 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
69 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
70 s = f.format(" {stuff.split()[:]} ", **ns)
70 s = f.format(" {stuff.split()[:]} ", **ns)
71 nt.assert_equals(s, " ['hello', 'there'] ")
71 nt.assert_equals(s, " ['hello', 'there'] ")
72 s = f.format(" {stuff.split()[::-1]} ", **ns)
72 s = f.format(" {stuff.split()[::-1]} ", **ns)
73 nt.assert_equals(s, " ['there', 'hello'] ")
73 nt.assert_equals(s, " ['there', 'hello'] ")
74 s = f.format("{stuff[::2]}", **ns)
74 s = f.format("{stuff[::2]}", **ns)
75 nt.assert_equals(s, ns['stuff'][::2])
75 nt.assert_equals(s, ns['stuff'][::2])
76
76
77 nt.assert_raises(SyntaxError, f.format, "{n:x}", **ns)
77 nt.assert_raises(SyntaxError, f.format, "{n:x}", **ns)
78
78
79
79
80 def eval_formatter_no_slicing_check(f):
80 def eval_formatter_no_slicing_check(f):
81 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
81 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
82
82
83 s = f.format('{n:x} {pi**2:+f}', **ns)
83 s = f.format('{n:x} {pi**2:+f}', **ns)
84 nt.assert_equals(s, "c +9.869604")
84 nt.assert_equals(s, "c +9.869604")
85
85
86 nt.assert_raises(SyntaxError, f.format, "{a[:]}")
86 nt.assert_raises(SyntaxError, f.format, "{a[:]}")
87
87
88 def test_eval_formatter():
88 def test_eval_formatter():
89 f = text.EvalFormatter()
89 f = text.EvalFormatter()
90 eval_formatter_check(f)
90 eval_formatter_check(f)
91 eval_formatter_no_slicing_check(f)
91 eval_formatter_no_slicing_check(f)
92
92
93 def test_full_eval_formatter():
93 def test_full_eval_formatter():
94 f = text.FullEvalFormatter()
94 f = text.FullEvalFormatter()
95 eval_formatter_check(f)
95 eval_formatter_check(f)
96 eval_formatter_slicing_check(f)
96 eval_formatter_slicing_check(f)
97
97
98 def test_dollar_formatter():
98 def test_dollar_formatter():
99 f = text.DollarFormatter()
99 f = text.DollarFormatter()
100 eval_formatter_check(f)
100 eval_formatter_check(f)
101 eval_formatter_slicing_check(f)
101 eval_formatter_slicing_check(f)
102
102
103 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
103 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
104 s = f.format("$n", **ns)
104 s = f.format("$n", **ns)
105 nt.assert_equals(s, "12")
105 nt.assert_equals(s, "12")
106 s = f.format("$n.real", **ns)
106 s = f.format("$n.real", **ns)
107 nt.assert_equals(s, "12")
107 nt.assert_equals(s, "12")
108 s = f.format("$n/{stuff[:5]}", **ns)
108 s = f.format("$n/{stuff[:5]}", **ns)
109 nt.assert_equals(s, "12/hello")
109 nt.assert_equals(s, "12/hello")
110 s = f.format("$n $$HOME", **ns)
111 nt.assert_equals(s, "12 $HOME")
112 s = f.format("${foo}", foo="HOME")
113 nt.assert_equals(s, "$HOME")
@@ -1,754 +1,760 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
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2008-2009 The IPython Development Team
7 # Copyright (C) 2008-2009 The IPython Development Team
8 #
8 #
9 # Distributed under the terms of the BSD License. The full license is in
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
10 # the file COPYING, distributed as part of this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 import __main__
17 import __main__
18
18
19 import locale
19 import locale
20 import os
20 import os
21 import re
21 import re
22 import shutil
22 import shutil
23 import sys
23 import sys
24 import textwrap
24 import textwrap
25 from string import Formatter
25 from string import Formatter
26
26
27 from IPython.external.path import path
27 from IPython.external.path import path
28 from IPython.testing.skipdoctest import skip_doctest_py3
28 from IPython.testing.skipdoctest import skip_doctest_py3
29 from IPython.utils import py3compat
29 from IPython.utils import py3compat
30 from IPython.utils.io import nlprint
30 from IPython.utils.io import nlprint
31 from IPython.utils.data import flatten
31 from IPython.utils.data import flatten
32
32
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34 # Code
34 # Code
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36
36
37 # Less conservative replacement for sys.getdefaultencoding, that will try
37 # Less conservative replacement for sys.getdefaultencoding, that will try
38 # to match the environment.
38 # to match the environment.
39 # Defined here as central function, so if we find better choices, we
39 # Defined here as central function, so if we find better choices, we
40 # won't need to make changes all over IPython.
40 # won't need to make changes all over IPython.
41 def getdefaultencoding():
41 def getdefaultencoding():
42 """Return IPython's guess for the default encoding for bytes as text.
42 """Return IPython's guess for the default encoding for bytes as text.
43
43
44 Asks for stdin.encoding first, to match the calling Terminal, but that
44 Asks for stdin.encoding first, to match the calling Terminal, but that
45 is often None for subprocesses. Fall back on locale.getpreferredencoding()
45 is often None for subprocesses. Fall back on locale.getpreferredencoding()
46 which should be a sensible platform default (that respects LANG environment),
46 which should be a sensible platform default (that respects LANG environment),
47 and finally to sys.getdefaultencoding() which is the most conservative option,
47 and finally to sys.getdefaultencoding() which is the most conservative option,
48 and usually ASCII.
48 and usually ASCII.
49 """
49 """
50 enc = sys.stdin.encoding
50 enc = sys.stdin.encoding
51 if not enc or enc=='ascii':
51 if not enc or enc=='ascii':
52 try:
52 try:
53 # There are reports of getpreferredencoding raising errors
53 # There are reports of getpreferredencoding raising errors
54 # in some cases, which may well be fixed, but let's be conservative here.
54 # in some cases, which may well be fixed, but let's be conservative here.
55 enc = locale.getpreferredencoding()
55 enc = locale.getpreferredencoding()
56 except Exception:
56 except Exception:
57 pass
57 pass
58 return enc or sys.getdefaultencoding()
58 return enc or sys.getdefaultencoding()
59
59
60 def unquote_ends(istr):
60 def unquote_ends(istr):
61 """Remove a single pair of quotes from the endpoints of a string."""
61 """Remove a single pair of quotes from the endpoints of a string."""
62
62
63 if not istr:
63 if not istr:
64 return istr
64 return istr
65 if (istr[0]=="'" and istr[-1]=="'") or \
65 if (istr[0]=="'" and istr[-1]=="'") or \
66 (istr[0]=='"' and istr[-1]=='"'):
66 (istr[0]=='"' and istr[-1]=='"'):
67 return istr[1:-1]
67 return istr[1:-1]
68 else:
68 else:
69 return istr
69 return istr
70
70
71
71
72 class LSString(str):
72 class LSString(str):
73 """String derivative with a special access attributes.
73 """String derivative with a special access attributes.
74
74
75 These are normal strings, but with the special attributes:
75 These are normal strings, but with the special attributes:
76
76
77 .l (or .list) : value as list (split on newlines).
77 .l (or .list) : value as list (split on newlines).
78 .n (or .nlstr): original value (the string itself).
78 .n (or .nlstr): original value (the string itself).
79 .s (or .spstr): value as whitespace-separated string.
79 .s (or .spstr): value as whitespace-separated string.
80 .p (or .paths): list of path objects
80 .p (or .paths): list of path objects
81
81
82 Any values which require transformations are computed only once and
82 Any values which require transformations are computed only once and
83 cached.
83 cached.
84
84
85 Such strings are very useful to efficiently interact with the shell, which
85 Such strings are very useful to efficiently interact with the shell, which
86 typically only understands whitespace-separated options for commands."""
86 typically only understands whitespace-separated options for commands."""
87
87
88 def get_list(self):
88 def get_list(self):
89 try:
89 try:
90 return self.__list
90 return self.__list
91 except AttributeError:
91 except AttributeError:
92 self.__list = self.split('\n')
92 self.__list = self.split('\n')
93 return self.__list
93 return self.__list
94
94
95 l = list = property(get_list)
95 l = list = property(get_list)
96
96
97 def get_spstr(self):
97 def get_spstr(self):
98 try:
98 try:
99 return self.__spstr
99 return self.__spstr
100 except AttributeError:
100 except AttributeError:
101 self.__spstr = self.replace('\n',' ')
101 self.__spstr = self.replace('\n',' ')
102 return self.__spstr
102 return self.__spstr
103
103
104 s = spstr = property(get_spstr)
104 s = spstr = property(get_spstr)
105
105
106 def get_nlstr(self):
106 def get_nlstr(self):
107 return self
107 return self
108
108
109 n = nlstr = property(get_nlstr)
109 n = nlstr = property(get_nlstr)
110
110
111 def get_paths(self):
111 def get_paths(self):
112 try:
112 try:
113 return self.__paths
113 return self.__paths
114 except AttributeError:
114 except AttributeError:
115 self.__paths = [path(p) for p in self.split('\n') if os.path.exists(p)]
115 self.__paths = [path(p) for p in self.split('\n') if os.path.exists(p)]
116 return self.__paths
116 return self.__paths
117
117
118 p = paths = property(get_paths)
118 p = paths = property(get_paths)
119
119
120 # FIXME: We need to reimplement type specific displayhook and then add this
120 # FIXME: We need to reimplement type specific displayhook and then add this
121 # back as a custom printer. This should also be moved outside utils into the
121 # back as a custom printer. This should also be moved outside utils into the
122 # core.
122 # core.
123
123
124 # def print_lsstring(arg):
124 # def print_lsstring(arg):
125 # """ Prettier (non-repr-like) and more informative printer for LSString """
125 # """ Prettier (non-repr-like) and more informative printer for LSString """
126 # print "LSString (.p, .n, .l, .s available). Value:"
126 # print "LSString (.p, .n, .l, .s available). Value:"
127 # print arg
127 # print arg
128 #
128 #
129 #
129 #
130 # print_lsstring = result_display.when_type(LSString)(print_lsstring)
130 # print_lsstring = result_display.when_type(LSString)(print_lsstring)
131
131
132
132
133 class SList(list):
133 class SList(list):
134 """List derivative with a special access attributes.
134 """List derivative with a special access attributes.
135
135
136 These are normal lists, but with the special attributes:
136 These are normal lists, but with the special attributes:
137
137
138 .l (or .list) : value as list (the list itself).
138 .l (or .list) : value as list (the list itself).
139 .n (or .nlstr): value as a string, joined on newlines.
139 .n (or .nlstr): value as a string, joined on newlines.
140 .s (or .spstr): value as a string, joined on spaces.
140 .s (or .spstr): value as a string, joined on spaces.
141 .p (or .paths): list of path objects
141 .p (or .paths): list of path objects
142
142
143 Any values which require transformations are computed only once and
143 Any values which require transformations are computed only once and
144 cached."""
144 cached."""
145
145
146 def get_list(self):
146 def get_list(self):
147 return self
147 return self
148
148
149 l = list = property(get_list)
149 l = list = property(get_list)
150
150
151 def get_spstr(self):
151 def get_spstr(self):
152 try:
152 try:
153 return self.__spstr
153 return self.__spstr
154 except AttributeError:
154 except AttributeError:
155 self.__spstr = ' '.join(self)
155 self.__spstr = ' '.join(self)
156 return self.__spstr
156 return self.__spstr
157
157
158 s = spstr = property(get_spstr)
158 s = spstr = property(get_spstr)
159
159
160 def get_nlstr(self):
160 def get_nlstr(self):
161 try:
161 try:
162 return self.__nlstr
162 return self.__nlstr
163 except AttributeError:
163 except AttributeError:
164 self.__nlstr = '\n'.join(self)
164 self.__nlstr = '\n'.join(self)
165 return self.__nlstr
165 return self.__nlstr
166
166
167 n = nlstr = property(get_nlstr)
167 n = nlstr = property(get_nlstr)
168
168
169 def get_paths(self):
169 def get_paths(self):
170 try:
170 try:
171 return self.__paths
171 return self.__paths
172 except AttributeError:
172 except AttributeError:
173 self.__paths = [path(p) for p in self if os.path.exists(p)]
173 self.__paths = [path(p) for p in self if os.path.exists(p)]
174 return self.__paths
174 return self.__paths
175
175
176 p = paths = property(get_paths)
176 p = paths = property(get_paths)
177
177
178 def grep(self, pattern, prune = False, field = None):
178 def grep(self, pattern, prune = False, field = None):
179 """ Return all strings matching 'pattern' (a regex or callable)
179 """ Return all strings matching 'pattern' (a regex or callable)
180
180
181 This is case-insensitive. If prune is true, return all items
181 This is case-insensitive. If prune is true, return all items
182 NOT matching the pattern.
182 NOT matching the pattern.
183
183
184 If field is specified, the match must occur in the specified
184 If field is specified, the match must occur in the specified
185 whitespace-separated field.
185 whitespace-separated field.
186
186
187 Examples::
187 Examples::
188
188
189 a.grep( lambda x: x.startswith('C') )
189 a.grep( lambda x: x.startswith('C') )
190 a.grep('Cha.*log', prune=1)
190 a.grep('Cha.*log', prune=1)
191 a.grep('chm', field=-1)
191 a.grep('chm', field=-1)
192 """
192 """
193
193
194 def match_target(s):
194 def match_target(s):
195 if field is None:
195 if field is None:
196 return s
196 return s
197 parts = s.split()
197 parts = s.split()
198 try:
198 try:
199 tgt = parts[field]
199 tgt = parts[field]
200 return tgt
200 return tgt
201 except IndexError:
201 except IndexError:
202 return ""
202 return ""
203
203
204 if isinstance(pattern, basestring):
204 if isinstance(pattern, basestring):
205 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
205 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
206 else:
206 else:
207 pred = pattern
207 pred = pattern
208 if not prune:
208 if not prune:
209 return SList([el for el in self if pred(match_target(el))])
209 return SList([el for el in self if pred(match_target(el))])
210 else:
210 else:
211 return SList([el for el in self if not pred(match_target(el))])
211 return SList([el for el in self if not pred(match_target(el))])
212
212
213 def fields(self, *fields):
213 def fields(self, *fields):
214 """ Collect whitespace-separated fields from string list
214 """ Collect whitespace-separated fields from string list
215
215
216 Allows quick awk-like usage of string lists.
216 Allows quick awk-like usage of string lists.
217
217
218 Example data (in var a, created by 'a = !ls -l')::
218 Example data (in var a, created by 'a = !ls -l')::
219 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
219 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
220 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
220 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
221
221
222 a.fields(0) is ['-rwxrwxrwx', 'drwxrwxrwx+']
222 a.fields(0) is ['-rwxrwxrwx', 'drwxrwxrwx+']
223 a.fields(1,0) is ['1 -rwxrwxrwx', '6 drwxrwxrwx+']
223 a.fields(1,0) is ['1 -rwxrwxrwx', '6 drwxrwxrwx+']
224 (note the joining by space).
224 (note the joining by space).
225 a.fields(-1) is ['ChangeLog', 'IPython']
225 a.fields(-1) is ['ChangeLog', 'IPython']
226
226
227 IndexErrors are ignored.
227 IndexErrors are ignored.
228
228
229 Without args, fields() just split()'s the strings.
229 Without args, fields() just split()'s the strings.
230 """
230 """
231 if len(fields) == 0:
231 if len(fields) == 0:
232 return [el.split() for el in self]
232 return [el.split() for el in self]
233
233
234 res = SList()
234 res = SList()
235 for el in [f.split() for f in self]:
235 for el in [f.split() for f in self]:
236 lineparts = []
236 lineparts = []
237
237
238 for fd in fields:
238 for fd in fields:
239 try:
239 try:
240 lineparts.append(el[fd])
240 lineparts.append(el[fd])
241 except IndexError:
241 except IndexError:
242 pass
242 pass
243 if lineparts:
243 if lineparts:
244 res.append(" ".join(lineparts))
244 res.append(" ".join(lineparts))
245
245
246 return res
246 return res
247
247
248 def sort(self,field= None, nums = False):
248 def sort(self,field= None, nums = False):
249 """ sort by specified fields (see fields())
249 """ sort by specified fields (see fields())
250
250
251 Example::
251 Example::
252 a.sort(1, nums = True)
252 a.sort(1, nums = True)
253
253
254 Sorts a by second field, in numerical order (so that 21 > 3)
254 Sorts a by second field, in numerical order (so that 21 > 3)
255
255
256 """
256 """
257
257
258 #decorate, sort, undecorate
258 #decorate, sort, undecorate
259 if field is not None:
259 if field is not None:
260 dsu = [[SList([line]).fields(field), line] for line in self]
260 dsu = [[SList([line]).fields(field), line] for line in self]
261 else:
261 else:
262 dsu = [[line, line] for line in self]
262 dsu = [[line, line] for line in self]
263 if nums:
263 if nums:
264 for i in range(len(dsu)):
264 for i in range(len(dsu)):
265 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
265 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
266 try:
266 try:
267 n = int(numstr)
267 n = int(numstr)
268 except ValueError:
268 except ValueError:
269 n = 0;
269 n = 0;
270 dsu[i][0] = n
270 dsu[i][0] = n
271
271
272
272
273 dsu.sort()
273 dsu.sort()
274 return SList([t[1] for t in dsu])
274 return SList([t[1] for t in dsu])
275
275
276
276
277 # FIXME: We need to reimplement type specific displayhook and then add this
277 # FIXME: We need to reimplement type specific displayhook and then add this
278 # back as a custom printer. This should also be moved outside utils into the
278 # back as a custom printer. This should also be moved outside utils into the
279 # core.
279 # core.
280
280
281 # def print_slist(arg):
281 # def print_slist(arg):
282 # """ Prettier (non-repr-like) and more informative printer for SList """
282 # """ Prettier (non-repr-like) and more informative printer for SList """
283 # print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):"
283 # print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):"
284 # if hasattr(arg, 'hideonce') and arg.hideonce:
284 # if hasattr(arg, 'hideonce') and arg.hideonce:
285 # arg.hideonce = False
285 # arg.hideonce = False
286 # return
286 # return
287 #
287 #
288 # nlprint(arg)
288 # nlprint(arg)
289 #
289 #
290 # print_slist = result_display.when_type(SList)(print_slist)
290 # print_slist = result_display.when_type(SList)(print_slist)
291
291
292
292
293 def esc_quotes(strng):
293 def esc_quotes(strng):
294 """Return the input string with single and double quotes escaped out"""
294 """Return the input string with single and double quotes escaped out"""
295
295
296 return strng.replace('"','\\"').replace("'","\\'")
296 return strng.replace('"','\\"').replace("'","\\'")
297
297
298
298
299 def qw(words,flat=0,sep=None,maxsplit=-1):
299 def qw(words,flat=0,sep=None,maxsplit=-1):
300 """Similar to Perl's qw() operator, but with some more options.
300 """Similar to Perl's qw() operator, but with some more options.
301
301
302 qw(words,flat=0,sep=' ',maxsplit=-1) -> words.split(sep,maxsplit)
302 qw(words,flat=0,sep=' ',maxsplit=-1) -> words.split(sep,maxsplit)
303
303
304 words can also be a list itself, and with flat=1, the output will be
304 words can also be a list itself, and with flat=1, the output will be
305 recursively flattened.
305 recursively flattened.
306
306
307 Examples:
307 Examples:
308
308
309 >>> qw('1 2')
309 >>> qw('1 2')
310 ['1', '2']
310 ['1', '2']
311
311
312 >>> qw(['a b','1 2',['m n','p q']])
312 >>> qw(['a b','1 2',['m n','p q']])
313 [['a', 'b'], ['1', '2'], [['m', 'n'], ['p', 'q']]]
313 [['a', 'b'], ['1', '2'], [['m', 'n'], ['p', 'q']]]
314
314
315 >>> qw(['a b','1 2',['m n','p q']],flat=1)
315 >>> qw(['a b','1 2',['m n','p q']],flat=1)
316 ['a', 'b', '1', '2', 'm', 'n', 'p', 'q']
316 ['a', 'b', '1', '2', 'm', 'n', 'p', 'q']
317 """
317 """
318
318
319 if isinstance(words, basestring):
319 if isinstance(words, basestring):
320 return [word.strip() for word in words.split(sep,maxsplit)
320 return [word.strip() for word in words.split(sep,maxsplit)
321 if word and not word.isspace() ]
321 if word and not word.isspace() ]
322 if flat:
322 if flat:
323 return flatten(map(qw,words,[1]*len(words)))
323 return flatten(map(qw,words,[1]*len(words)))
324 return map(qw,words)
324 return map(qw,words)
325
325
326
326
327 def qwflat(words,sep=None,maxsplit=-1):
327 def qwflat(words,sep=None,maxsplit=-1):
328 """Calls qw(words) in flat mode. It's just a convenient shorthand."""
328 """Calls qw(words) in flat mode. It's just a convenient shorthand."""
329 return qw(words,1,sep,maxsplit)
329 return qw(words,1,sep,maxsplit)
330
330
331
331
332 def qw_lol(indata):
332 def qw_lol(indata):
333 """qw_lol('a b') -> [['a','b']],
333 """qw_lol('a b') -> [['a','b']],
334 otherwise it's just a call to qw().
334 otherwise it's just a call to qw().
335
335
336 We need this to make sure the modules_some keys *always* end up as a
336 We need this to make sure the modules_some keys *always* end up as a
337 list of lists."""
337 list of lists."""
338
338
339 if isinstance(indata, basestring):
339 if isinstance(indata, basestring):
340 return [qw(indata)]
340 return [qw(indata)]
341 else:
341 else:
342 return qw(indata)
342 return qw(indata)
343
343
344
344
345 def grep(pat,list,case=1):
345 def grep(pat,list,case=1):
346 """Simple minded grep-like function.
346 """Simple minded grep-like function.
347 grep(pat,list) returns occurrences of pat in list, None on failure.
347 grep(pat,list) returns occurrences of pat in list, None on failure.
348
348
349 It only does simple string matching, with no support for regexps. Use the
349 It only does simple string matching, with no support for regexps. Use the
350 option case=0 for case-insensitive matching."""
350 option case=0 for case-insensitive matching."""
351
351
352 # This is pretty crude. At least it should implement copying only references
352 # This is pretty crude. At least it should implement copying only references
353 # to the original data in case it's big. Now it copies the data for output.
353 # to the original data in case it's big. Now it copies the data for output.
354 out=[]
354 out=[]
355 if case:
355 if case:
356 for term in list:
356 for term in list:
357 if term.find(pat)>-1: out.append(term)
357 if term.find(pat)>-1: out.append(term)
358 else:
358 else:
359 lpat=pat.lower()
359 lpat=pat.lower()
360 for term in list:
360 for term in list:
361 if term.lower().find(lpat)>-1: out.append(term)
361 if term.lower().find(lpat)>-1: out.append(term)
362
362
363 if len(out): return out
363 if len(out): return out
364 else: return None
364 else: return None
365
365
366
366
367 def dgrep(pat,*opts):
367 def dgrep(pat,*opts):
368 """Return grep() on dir()+dir(__builtins__).
368 """Return grep() on dir()+dir(__builtins__).
369
369
370 A very common use of grep() when working interactively."""
370 A very common use of grep() when working interactively."""
371
371
372 return grep(pat,dir(__main__)+dir(__main__.__builtins__),*opts)
372 return grep(pat,dir(__main__)+dir(__main__.__builtins__),*opts)
373
373
374
374
375 def idgrep(pat):
375 def idgrep(pat):
376 """Case-insensitive dgrep()"""
376 """Case-insensitive dgrep()"""
377
377
378 return dgrep(pat,0)
378 return dgrep(pat,0)
379
379
380
380
381 def igrep(pat,list):
381 def igrep(pat,list):
382 """Synonym for case-insensitive grep."""
382 """Synonym for case-insensitive grep."""
383
383
384 return grep(pat,list,case=0)
384 return grep(pat,list,case=0)
385
385
386
386
387 def indent(instr,nspaces=4, ntabs=0, flatten=False):
387 def indent(instr,nspaces=4, ntabs=0, flatten=False):
388 """Indent a string a given number of spaces or tabstops.
388 """Indent a string a given number of spaces or tabstops.
389
389
390 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
390 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
391
391
392 Parameters
392 Parameters
393 ----------
393 ----------
394
394
395 instr : basestring
395 instr : basestring
396 The string to be indented.
396 The string to be indented.
397 nspaces : int (default: 4)
397 nspaces : int (default: 4)
398 The number of spaces to be indented.
398 The number of spaces to be indented.
399 ntabs : int (default: 0)
399 ntabs : int (default: 0)
400 The number of tabs to be indented.
400 The number of tabs to be indented.
401 flatten : bool (default: False)
401 flatten : bool (default: False)
402 Whether to scrub existing indentation. If True, all lines will be
402 Whether to scrub existing indentation. If True, all lines will be
403 aligned to the same indentation. If False, existing indentation will
403 aligned to the same indentation. If False, existing indentation will
404 be strictly increased.
404 be strictly increased.
405
405
406 Returns
406 Returns
407 -------
407 -------
408
408
409 str|unicode : string indented by ntabs and nspaces.
409 str|unicode : string indented by ntabs and nspaces.
410
410
411 """
411 """
412 if instr is None:
412 if instr is None:
413 return
413 return
414 ind = '\t'*ntabs+' '*nspaces
414 ind = '\t'*ntabs+' '*nspaces
415 if flatten:
415 if flatten:
416 pat = re.compile(r'^\s*', re.MULTILINE)
416 pat = re.compile(r'^\s*', re.MULTILINE)
417 else:
417 else:
418 pat = re.compile(r'^', re.MULTILINE)
418 pat = re.compile(r'^', re.MULTILINE)
419 outstr = re.sub(pat, ind, instr)
419 outstr = re.sub(pat, ind, instr)
420 if outstr.endswith(os.linesep+ind):
420 if outstr.endswith(os.linesep+ind):
421 return outstr[:-len(ind)]
421 return outstr[:-len(ind)]
422 else:
422 else:
423 return outstr
423 return outstr
424
424
425 def native_line_ends(filename,backup=1):
425 def native_line_ends(filename,backup=1):
426 """Convert (in-place) a file to line-ends native to the current OS.
426 """Convert (in-place) a file to line-ends native to the current OS.
427
427
428 If the optional backup argument is given as false, no backup of the
428 If the optional backup argument is given as false, no backup of the
429 original file is left. """
429 original file is left. """
430
430
431 backup_suffixes = {'posix':'~','dos':'.bak','nt':'.bak','mac':'.bak'}
431 backup_suffixes = {'posix':'~','dos':'.bak','nt':'.bak','mac':'.bak'}
432
432
433 bak_filename = filename + backup_suffixes[os.name]
433 bak_filename = filename + backup_suffixes[os.name]
434
434
435 original = open(filename).read()
435 original = open(filename).read()
436 shutil.copy2(filename,bak_filename)
436 shutil.copy2(filename,bak_filename)
437 try:
437 try:
438 new = open(filename,'wb')
438 new = open(filename,'wb')
439 new.write(os.linesep.join(original.splitlines()))
439 new.write(os.linesep.join(original.splitlines()))
440 new.write(os.linesep) # ALWAYS put an eol at the end of the file
440 new.write(os.linesep) # ALWAYS put an eol at the end of the file
441 new.close()
441 new.close()
442 except:
442 except:
443 os.rename(bak_filename,filename)
443 os.rename(bak_filename,filename)
444 if not backup:
444 if not backup:
445 try:
445 try:
446 os.remove(bak_filename)
446 os.remove(bak_filename)
447 except:
447 except:
448 pass
448 pass
449
449
450
450
451 def list_strings(arg):
451 def list_strings(arg):
452 """Always return a list of strings, given a string or list of strings
452 """Always return a list of strings, given a string or list of strings
453 as input.
453 as input.
454
454
455 :Examples:
455 :Examples:
456
456
457 In [7]: list_strings('A single string')
457 In [7]: list_strings('A single string')
458 Out[7]: ['A single string']
458 Out[7]: ['A single string']
459
459
460 In [8]: list_strings(['A single string in a list'])
460 In [8]: list_strings(['A single string in a list'])
461 Out[8]: ['A single string in a list']
461 Out[8]: ['A single string in a list']
462
462
463 In [9]: list_strings(['A','list','of','strings'])
463 In [9]: list_strings(['A','list','of','strings'])
464 Out[9]: ['A', 'list', 'of', 'strings']
464 Out[9]: ['A', 'list', 'of', 'strings']
465 """
465 """
466
466
467 if isinstance(arg,basestring): return [arg]
467 if isinstance(arg,basestring): return [arg]
468 else: return arg
468 else: return arg
469
469
470
470
471 def marquee(txt='',width=78,mark='*'):
471 def marquee(txt='',width=78,mark='*'):
472 """Return the input string centered in a 'marquee'.
472 """Return the input string centered in a 'marquee'.
473
473
474 :Examples:
474 :Examples:
475
475
476 In [16]: marquee('A test',40)
476 In [16]: marquee('A test',40)
477 Out[16]: '**************** A test ****************'
477 Out[16]: '**************** A test ****************'
478
478
479 In [17]: marquee('A test',40,'-')
479 In [17]: marquee('A test',40,'-')
480 Out[17]: '---------------- A test ----------------'
480 Out[17]: '---------------- A test ----------------'
481
481
482 In [18]: marquee('A test',40,' ')
482 In [18]: marquee('A test',40,' ')
483 Out[18]: ' A test '
483 Out[18]: ' A test '
484
484
485 """
485 """
486 if not txt:
486 if not txt:
487 return (mark*width)[:width]
487 return (mark*width)[:width]
488 nmark = (width-len(txt)-2)//len(mark)//2
488 nmark = (width-len(txt)-2)//len(mark)//2
489 if nmark < 0: nmark =0
489 if nmark < 0: nmark =0
490 marks = mark*nmark
490 marks = mark*nmark
491 return '%s %s %s' % (marks,txt,marks)
491 return '%s %s %s' % (marks,txt,marks)
492
492
493
493
494 ini_spaces_re = re.compile(r'^(\s+)')
494 ini_spaces_re = re.compile(r'^(\s+)')
495
495
496 def num_ini_spaces(strng):
496 def num_ini_spaces(strng):
497 """Return the number of initial spaces in a string"""
497 """Return the number of initial spaces in a string"""
498
498
499 ini_spaces = ini_spaces_re.match(strng)
499 ini_spaces = ini_spaces_re.match(strng)
500 if ini_spaces:
500 if ini_spaces:
501 return ini_spaces.end()
501 return ini_spaces.end()
502 else:
502 else:
503 return 0
503 return 0
504
504
505
505
506 def format_screen(strng):
506 def format_screen(strng):
507 """Format a string for screen printing.
507 """Format a string for screen printing.
508
508
509 This removes some latex-type format codes."""
509 This removes some latex-type format codes."""
510 # Paragraph continue
510 # Paragraph continue
511 par_re = re.compile(r'\\$',re.MULTILINE)
511 par_re = re.compile(r'\\$',re.MULTILINE)
512 strng = par_re.sub('',strng)
512 strng = par_re.sub('',strng)
513 return strng
513 return strng
514
514
515 def dedent(text):
515 def dedent(text):
516 """Equivalent of textwrap.dedent that ignores unindented first line.
516 """Equivalent of textwrap.dedent that ignores unindented first line.
517
517
518 This means it will still dedent strings like:
518 This means it will still dedent strings like:
519 '''foo
519 '''foo
520 is a bar
520 is a bar
521 '''
521 '''
522
522
523 For use in wrap_paragraphs.
523 For use in wrap_paragraphs.
524 """
524 """
525
525
526 if text.startswith('\n'):
526 if text.startswith('\n'):
527 # text starts with blank line, don't ignore the first line
527 # text starts with blank line, don't ignore the first line
528 return textwrap.dedent(text)
528 return textwrap.dedent(text)
529
529
530 # split first line
530 # split first line
531 splits = text.split('\n',1)
531 splits = text.split('\n',1)
532 if len(splits) == 1:
532 if len(splits) == 1:
533 # only one line
533 # only one line
534 return textwrap.dedent(text)
534 return textwrap.dedent(text)
535
535
536 first, rest = splits
536 first, rest = splits
537 # dedent everything but the first line
537 # dedent everything but the first line
538 rest = textwrap.dedent(rest)
538 rest = textwrap.dedent(rest)
539 return '\n'.join([first, rest])
539 return '\n'.join([first, rest])
540
540
541 def wrap_paragraphs(text, ncols=80):
541 def wrap_paragraphs(text, ncols=80):
542 """Wrap multiple paragraphs to fit a specified width.
542 """Wrap multiple paragraphs to fit a specified width.
543
543
544 This is equivalent to textwrap.wrap, but with support for multiple
544 This is equivalent to textwrap.wrap, but with support for multiple
545 paragraphs, as separated by empty lines.
545 paragraphs, as separated by empty lines.
546
546
547 Returns
547 Returns
548 -------
548 -------
549
549
550 list of complete paragraphs, wrapped to fill `ncols` columns.
550 list of complete paragraphs, wrapped to fill `ncols` columns.
551 """
551 """
552 paragraph_re = re.compile(r'\n(\s*\n)+', re.MULTILINE)
552 paragraph_re = re.compile(r'\n(\s*\n)+', re.MULTILINE)
553 text = dedent(text).strip()
553 text = dedent(text).strip()
554 paragraphs = paragraph_re.split(text)[::2] # every other entry is space
554 paragraphs = paragraph_re.split(text)[::2] # every other entry is space
555 out_ps = []
555 out_ps = []
556 indent_re = re.compile(r'\n\s+', re.MULTILINE)
556 indent_re = re.compile(r'\n\s+', re.MULTILINE)
557 for p in paragraphs:
557 for p in paragraphs:
558 # presume indentation that survives dedent is meaningful formatting,
558 # presume indentation that survives dedent is meaningful formatting,
559 # so don't fill unless text is flush.
559 # so don't fill unless text is flush.
560 if indent_re.search(p) is None:
560 if indent_re.search(p) is None:
561 # wrap paragraph
561 # wrap paragraph
562 p = textwrap.fill(p, ncols)
562 p = textwrap.fill(p, ncols)
563 out_ps.append(p)
563 out_ps.append(p)
564 return out_ps
564 return out_ps
565
565
566
566
567 class EvalFormatter(Formatter):
567 class EvalFormatter(Formatter):
568 """A String Formatter that allows evaluation of simple expressions.
568 """A String Formatter that allows evaluation of simple expressions.
569
569
570 Note that this version interprets a : as specifying a format string (as per
570 Note that this version interprets a : as specifying a format string (as per
571 standard string formatting), so if slicing is required, you must explicitly
571 standard string formatting), so if slicing is required, you must explicitly
572 create a slice.
572 create a slice.
573
573
574 This is to be used in templating cases, such as the parallel batch
574 This is to be used in templating cases, such as the parallel batch
575 script templates, where simple arithmetic on arguments is useful.
575 script templates, where simple arithmetic on arguments is useful.
576
576
577 Examples
577 Examples
578 --------
578 --------
579
579
580 In [1]: f = EvalFormatter()
580 In [1]: f = EvalFormatter()
581 In [2]: f.format('{n//4}', n=8)
581 In [2]: f.format('{n//4}', n=8)
582 Out [2]: '2'
582 Out [2]: '2'
583
583
584 In [3]: f.format("{greeting[slice(2,4)]}", greeting="Hello")
584 In [3]: f.format("{greeting[slice(2,4)]}", greeting="Hello")
585 Out [3]: 'll'
585 Out [3]: 'll'
586 """
586 """
587 def get_field(self, name, args, kwargs):
587 def get_field(self, name, args, kwargs):
588 v = eval(name, kwargs)
588 v = eval(name, kwargs)
589 return v, name
589 return v, name
590
590
591 @skip_doctest_py3
591 @skip_doctest_py3
592 class FullEvalFormatter(Formatter):
592 class FullEvalFormatter(Formatter):
593 """A String Formatter that allows evaluation of simple expressions.
593 """A String Formatter that allows evaluation of simple expressions.
594
594
595 Any time a format key is not found in the kwargs,
595 Any time a format key is not found in the kwargs,
596 it will be tried as an expression in the kwargs namespace.
596 it will be tried as an expression in the kwargs namespace.
597
597
598 Note that this version allows slicing using [1:2], so you cannot specify
598 Note that this version allows slicing using [1:2], so you cannot specify
599 a format string. Use :class:`EvalFormatter` to permit format strings.
599 a format string. Use :class:`EvalFormatter` to permit format strings.
600
600
601 Examples
601 Examples
602 --------
602 --------
603
603
604 In [1]: f = FullEvalFormatter()
604 In [1]: f = FullEvalFormatter()
605 In [2]: f.format('{n//4}', n=8)
605 In [2]: f.format('{n//4}', n=8)
606 Out[2]: u'2'
606 Out[2]: u'2'
607
607
608 In [3]: f.format('{list(range(5))[2:4]}')
608 In [3]: f.format('{list(range(5))[2:4]}')
609 Out[3]: u'[2, 3]'
609 Out[3]: u'[2, 3]'
610
610
611 In [4]: f.format('{3*2}')
611 In [4]: f.format('{3*2}')
612 Out[4]: u'6'
612 Out[4]: u'6'
613 """
613 """
614 # copied from Formatter._vformat with minor changes to allow eval
614 # copied from Formatter._vformat with minor changes to allow eval
615 # and replace the format_spec code with slicing
615 # and replace the format_spec code with slicing
616 def _vformat(self, format_string, args, kwargs, used_args, recursion_depth):
616 def _vformat(self, format_string, args, kwargs, used_args, recursion_depth):
617 if recursion_depth < 0:
617 if recursion_depth < 0:
618 raise ValueError('Max string recursion exceeded')
618 raise ValueError('Max string recursion exceeded')
619 result = []
619 result = []
620 for literal_text, field_name, format_spec, conversion in \
620 for literal_text, field_name, format_spec, conversion in \
621 self.parse(format_string):
621 self.parse(format_string):
622
622
623 # output the literal text
623 # output the literal text
624 if literal_text:
624 if literal_text:
625 result.append(literal_text)
625 result.append(literal_text)
626
626
627 # if there's a field, output it
627 # if there's a field, output it
628 if field_name is not None:
628 if field_name is not None:
629 # this is some markup, find the object and do
629 # this is some markup, find the object and do
630 # the formatting
630 # the formatting
631
631
632 if format_spec:
632 if format_spec:
633 # override format spec, to allow slicing:
633 # override format spec, to allow slicing:
634 field_name = ':'.join([field_name, format_spec])
634 field_name = ':'.join([field_name, format_spec])
635
635
636 # eval the contents of the field for the object
636 # eval the contents of the field for the object
637 # to be formatted
637 # to be formatted
638 obj = eval(field_name, kwargs)
638 obj = eval(field_name, kwargs)
639
639
640 # do any conversion on the resulting object
640 # do any conversion on the resulting object
641 obj = self.convert_field(obj, conversion)
641 obj = self.convert_field(obj, conversion)
642
642
643 # format the object and append to the result
643 # format the object and append to the result
644 result.append(self.format_field(obj, ''))
644 result.append(self.format_field(obj, ''))
645
645
646 return u''.join(py3compat.cast_unicode(s) for s in result)
646 return u''.join(py3compat.cast_unicode(s) for s in result)
647
647
648 @skip_doctest_py3
648 @skip_doctest_py3
649 class DollarFormatter(FullEvalFormatter):
649 class DollarFormatter(FullEvalFormatter):
650 """Formatter allowing Itpl style $foo replacement, for names and attribute
650 """Formatter allowing Itpl style $foo replacement, for names and attribute
651 access only. Standard {foo} replacement also works, and allows full
651 access only. Standard {foo} replacement also works, and allows full
652 evaluation of its arguments.
652 evaluation of its arguments.
653
653
654 Examples
654 Examples
655 --------
655 --------
656 In [1]: f = DollarFormatter()
656 In [1]: f = DollarFormatter()
657 In [2]: f.format('{n//4}', n=8)
657 In [2]: f.format('{n//4}', n=8)
658 Out[2]: u'2'
658 Out[2]: u'2'
659
659
660 In [3]: f.format('23 * 76 is $result', result=23*76)
660 In [3]: f.format('23 * 76 is $result', result=23*76)
661 Out[3]: u'23 * 76 is 1748'
661 Out[3]: u'23 * 76 is 1748'
662
662
663 In [4]: f.format('$a or {b}', a=1, b=2)
663 In [4]: f.format('$a or {b}', a=1, b=2)
664 Out[4]: u'1 or 2'
664 Out[4]: u'1 or 2'
665 """
665 """
666 _dollar_pattern = re.compile("(.*)\$([\w\.]+)")
666 _dollar_pattern = re.compile("(.*?)\$(\$?[\w\.]+)")
667 def parse(self, fmt_string):
667 def parse(self, fmt_string):
668 for literal_txt, field_name, format_spec, conversion \
668 for literal_txt, field_name, format_spec, conversion \
669 in Formatter.parse(self, fmt_string):
669 in Formatter.parse(self, fmt_string):
670
670
671 # Find $foo patterns in the literal text.
671 # Find $foo patterns in the literal text.
672 continue_from = 0
672 continue_from = 0
673 txt = ""
673 for m in self._dollar_pattern.finditer(literal_txt):
674 for m in self._dollar_pattern.finditer(literal_txt):
674 new_txt, new_field = m.group(1,2)
675 new_txt, new_field = m.group(1,2)
675 yield (new_txt, new_field, "", None)
676 # $$foo --> $foo
677 if new_field.startswith("$"):
678 txt += new_txt + new_field
679 else:
680 yield (txt + new_txt, new_field, "", None)
681 txt = ""
676 continue_from = m.end()
682 continue_from = m.end()
677
683
678 # Re-yield the {foo} style pattern
684 # Re-yield the {foo} style pattern
679 yield (literal_txt[continue_from:], field_name, format_spec, conversion)
685 yield (txt + literal_txt[continue_from:], field_name, format_spec, conversion)
680
686
681
687
682 def columnize(items, separator=' ', displaywidth=80):
688 def columnize(items, separator=' ', displaywidth=80):
683 """ Transform a list of strings into a single string with columns.
689 """ Transform a list of strings into a single string with columns.
684
690
685 Parameters
691 Parameters
686 ----------
692 ----------
687 items : sequence of strings
693 items : sequence of strings
688 The strings to process.
694 The strings to process.
689
695
690 separator : str, optional [default is two spaces]
696 separator : str, optional [default is two spaces]
691 The string that separates columns.
697 The string that separates columns.
692
698
693 displaywidth : int, optional [default is 80]
699 displaywidth : int, optional [default is 80]
694 Width of the display in number of characters.
700 Width of the display in number of characters.
695
701
696 Returns
702 Returns
697 -------
703 -------
698 The formatted string.
704 The formatted string.
699 """
705 """
700 # Note: this code is adapted from columnize 0.3.2.
706 # Note: this code is adapted from columnize 0.3.2.
701 # See http://code.google.com/p/pycolumnize/
707 # See http://code.google.com/p/pycolumnize/
702
708
703 # Some degenerate cases.
709 # Some degenerate cases.
704 size = len(items)
710 size = len(items)
705 if size == 0:
711 if size == 0:
706 return '\n'
712 return '\n'
707 elif size == 1:
713 elif size == 1:
708 return '%s\n' % items[0]
714 return '%s\n' % items[0]
709
715
710 # Special case: if any item is longer than the maximum width, there's no
716 # Special case: if any item is longer than the maximum width, there's no
711 # point in triggering the logic below...
717 # point in triggering the logic below...
712 item_len = map(len, items) # save these, we can reuse them below
718 item_len = map(len, items) # save these, we can reuse them below
713 longest = max(item_len)
719 longest = max(item_len)
714 if longest >= displaywidth:
720 if longest >= displaywidth:
715 return '\n'.join(items+[''])
721 return '\n'.join(items+[''])
716
722
717 # Try every row count from 1 upwards
723 # Try every row count from 1 upwards
718 array_index = lambda nrows, row, col: nrows*col + row
724 array_index = lambda nrows, row, col: nrows*col + row
719 for nrows in range(1, size):
725 for nrows in range(1, size):
720 ncols = (size + nrows - 1) // nrows
726 ncols = (size + nrows - 1) // nrows
721 colwidths = []
727 colwidths = []
722 totwidth = -len(separator)
728 totwidth = -len(separator)
723 for col in range(ncols):
729 for col in range(ncols):
724 # Get max column width for this column
730 # Get max column width for this column
725 colwidth = 0
731 colwidth = 0
726 for row in range(nrows):
732 for row in range(nrows):
727 i = array_index(nrows, row, col)
733 i = array_index(nrows, row, col)
728 if i >= size: break
734 if i >= size: break
729 x, len_x = items[i], item_len[i]
735 x, len_x = items[i], item_len[i]
730 colwidth = max(colwidth, len_x)
736 colwidth = max(colwidth, len_x)
731 colwidths.append(colwidth)
737 colwidths.append(colwidth)
732 totwidth += colwidth + len(separator)
738 totwidth += colwidth + len(separator)
733 if totwidth > displaywidth:
739 if totwidth > displaywidth:
734 break
740 break
735 if totwidth <= displaywidth:
741 if totwidth <= displaywidth:
736 break
742 break
737
743
738 # The smallest number of rows computed and the max widths for each
744 # The smallest number of rows computed and the max widths for each
739 # column has been obtained. Now we just have to format each of the rows.
745 # column has been obtained. Now we just have to format each of the rows.
740 string = ''
746 string = ''
741 for row in range(nrows):
747 for row in range(nrows):
742 texts = []
748 texts = []
743 for col in range(ncols):
749 for col in range(ncols):
744 i = row + nrows*col
750 i = row + nrows*col
745 if i >= size:
751 if i >= size:
746 texts.append('')
752 texts.append('')
747 else:
753 else:
748 texts.append(items[i])
754 texts.append(items[i])
749 while texts and not texts[-1]:
755 while texts and not texts[-1]:
750 del texts[-1]
756 del texts[-1]
751 for col in range(len(texts)):
757 for col in range(len(texts)):
752 texts[col] = texts[col].ljust(colwidths[col])
758 texts[col] = texts[col].ljust(colwidths[col])
753 string += '%s\n' % separator.join(texts)
759 string += '%s\n' % separator.join(texts)
754 return string
760 return string
General Comments 0
You need to be logged in to leave comments. Login now