##// END OF EJS Templates
improve comment
Srinivas Reddy Thatiparthy -
Show More
@@ -1,221 +1,221 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 import random
17 import random
18 import sys
18 import sys
19
19
20 import nose.tools as nt
20 import nose.tools as nt
21 try:
21 try:
22 from pathlib import Path
22 from pathlib import Path
23 except ImportError:
23 except ImportError:
24 # Python 2 backport
24 # for Python 3.3
25 from pathlib2 import Path
25 from pathlib2 import Path
26
26
27 from IPython.utils import text
27 from IPython.utils import text
28
28
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30 # Globals
30 # Globals
31 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
32
32
33 def test_columnize():
33 def test_columnize():
34 """Basic columnize tests."""
34 """Basic columnize tests."""
35 size = 5
35 size = 5
36 items = [l*size for l in 'abcd']
36 items = [l*size for l in 'abcd']
37
37
38 out = text.columnize(items, displaywidth=80)
38 out = text.columnize(items, displaywidth=80)
39 nt.assert_equal(out, 'aaaaa bbbbb ccccc ddddd\n')
39 nt.assert_equal(out, 'aaaaa bbbbb ccccc ddddd\n')
40 out = text.columnize(items, displaywidth=25)
40 out = text.columnize(items, displaywidth=25)
41 nt.assert_equal(out, 'aaaaa ccccc\nbbbbb ddddd\n')
41 nt.assert_equal(out, 'aaaaa ccccc\nbbbbb ddddd\n')
42 out = text.columnize(items, displaywidth=12)
42 out = text.columnize(items, displaywidth=12)
43 nt.assert_equal(out, 'aaaaa ccccc\nbbbbb ddddd\n')
43 nt.assert_equal(out, 'aaaaa ccccc\nbbbbb ddddd\n')
44 out = text.columnize(items, displaywidth=10)
44 out = text.columnize(items, displaywidth=10)
45 nt.assert_equal(out, 'aaaaa\nbbbbb\nccccc\nddddd\n')
45 nt.assert_equal(out, 'aaaaa\nbbbbb\nccccc\nddddd\n')
46
46
47 out = text.columnize(items, row_first=True, displaywidth=80)
47 out = text.columnize(items, row_first=True, displaywidth=80)
48 nt.assert_equal(out, 'aaaaa bbbbb ccccc ddddd\n')
48 nt.assert_equal(out, 'aaaaa bbbbb ccccc ddddd\n')
49 out = text.columnize(items, row_first=True, displaywidth=25)
49 out = text.columnize(items, row_first=True, displaywidth=25)
50 nt.assert_equal(out, 'aaaaa bbbbb\nccccc ddddd\n')
50 nt.assert_equal(out, 'aaaaa bbbbb\nccccc ddddd\n')
51 out = text.columnize(items, row_first=True, displaywidth=12)
51 out = text.columnize(items, row_first=True, displaywidth=12)
52 nt.assert_equal(out, 'aaaaa bbbbb\nccccc ddddd\n')
52 nt.assert_equal(out, 'aaaaa bbbbb\nccccc ddddd\n')
53 out = text.columnize(items, row_first=True, displaywidth=10)
53 out = text.columnize(items, row_first=True, displaywidth=10)
54 nt.assert_equal(out, 'aaaaa\nbbbbb\nccccc\nddddd\n')
54 nt.assert_equal(out, 'aaaaa\nbbbbb\nccccc\nddddd\n')
55
55
56 out = text.columnize(items, displaywidth=40, spread=True)
56 out = text.columnize(items, displaywidth=40, spread=True)
57 nt.assert_equal(out, 'aaaaa bbbbb ccccc ddddd\n')
57 nt.assert_equal(out, 'aaaaa bbbbb ccccc ddddd\n')
58 out = text.columnize(items, displaywidth=20, spread=True)
58 out = text.columnize(items, displaywidth=20, spread=True)
59 nt.assert_equal(out, 'aaaaa ccccc\nbbbbb ddddd\n')
59 nt.assert_equal(out, 'aaaaa ccccc\nbbbbb ddddd\n')
60 out = text.columnize(items, displaywidth=12, spread=True)
60 out = text.columnize(items, displaywidth=12, spread=True)
61 nt.assert_equal(out, 'aaaaa ccccc\nbbbbb ddddd\n')
61 nt.assert_equal(out, 'aaaaa ccccc\nbbbbb ddddd\n')
62 out = text.columnize(items, displaywidth=10, spread=True)
62 out = text.columnize(items, displaywidth=10, spread=True)
63 nt.assert_equal(out, 'aaaaa\nbbbbb\nccccc\nddddd\n')
63 nt.assert_equal(out, 'aaaaa\nbbbbb\nccccc\nddddd\n')
64
64
65
65
66 def test_columnize_random():
66 def test_columnize_random():
67 """Test with random input to hopfully catch edge case """
67 """Test with random input to hopfully catch edge case """
68 for row_first in [True, False]:
68 for row_first in [True, False]:
69 for nitems in [random.randint(2,70) for i in range(2,20)]:
69 for nitems in [random.randint(2,70) for i in range(2,20)]:
70 displaywidth = random.randint(20,200)
70 displaywidth = random.randint(20,200)
71 rand_len = [random.randint(2,displaywidth) for i in range(nitems)]
71 rand_len = [random.randint(2,displaywidth) for i in range(nitems)]
72 items = ['x'*l for l in rand_len]
72 items = ['x'*l for l in rand_len]
73 out = text.columnize(items, row_first=row_first, displaywidth=displaywidth)
73 out = text.columnize(items, row_first=row_first, displaywidth=displaywidth)
74 longer_line = max([len(x) for x in out.split('\n')])
74 longer_line = max([len(x) for x in out.split('\n')])
75 longer_element = max(rand_len)
75 longer_element = max(rand_len)
76 if longer_line > displaywidth:
76 if longer_line > displaywidth:
77 print("Columnize displayed something lager than displaywidth : %s " % longer_line)
77 print("Columnize displayed something lager than displaywidth : %s " % longer_line)
78 print("longer element : %s " % longer_element)
78 print("longer element : %s " % longer_element)
79 print("displaywidth : %s " % displaywidth)
79 print("displaywidth : %s " % displaywidth)
80 print("number of element : %s " % nitems)
80 print("number of element : %s " % nitems)
81 print("size of each element :\n %s" % rand_len)
81 print("size of each element :\n %s" % rand_len)
82 assert False, "row_first={0}".format(row_first)
82 assert False, "row_first={0}".format(row_first)
83
83
84 def test_columnize_medium():
84 def test_columnize_medium():
85 """Test with inputs than shouldn't be wider than 80"""
85 """Test with inputs than shouldn't be wider than 80"""
86 size = 40
86 size = 40
87 items = [l*size for l in 'abc']
87 items = [l*size for l in 'abc']
88 for row_first in [True, False]:
88 for row_first in [True, False]:
89 out = text.columnize(items, row_first=row_first, displaywidth=80)
89 out = text.columnize(items, row_first=row_first, displaywidth=80)
90 nt.assert_equal(out, '\n'.join(items+['']), "row_first={0}".format(row_first))
90 nt.assert_equal(out, '\n'.join(items+['']), "row_first={0}".format(row_first))
91
91
92 def test_columnize_long():
92 def test_columnize_long():
93 """Test columnize with inputs longer than the display window"""
93 """Test columnize with inputs longer than the display window"""
94 size = 11
94 size = 11
95 items = [l*size for l in 'abc']
95 items = [l*size for l in 'abc']
96 for row_first in [True, False]:
96 for row_first in [True, False]:
97 out = text.columnize(items, row_first=row_first, displaywidth=size-1)
97 out = text.columnize(items, row_first=row_first, displaywidth=size-1)
98 nt.assert_equal(out, '\n'.join(items+['']), "row_first={0}".format(row_first))
98 nt.assert_equal(out, '\n'.join(items+['']), "row_first={0}".format(row_first))
99
99
100 def eval_formatter_check(f):
100 def eval_formatter_check(f):
101 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os, u=u"cafΓ©", b="cafΓ©")
101 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os, u=u"cafΓ©", b="cafΓ©")
102 s = f.format("{n} {n//4} {stuff.split()[0]}", **ns)
102 s = f.format("{n} {n//4} {stuff.split()[0]}", **ns)
103 nt.assert_equal(s, "12 3 hello")
103 nt.assert_equal(s, "12 3 hello")
104 s = f.format(' '.join(['{n//%i}'%i for i in range(1,8)]), **ns)
104 s = f.format(' '.join(['{n//%i}'%i for i in range(1,8)]), **ns)
105 nt.assert_equal(s, "12 6 4 3 2 2 1")
105 nt.assert_equal(s, "12 6 4 3 2 2 1")
106 s = f.format('{[n//i for i in range(1,8)]}', **ns)
106 s = f.format('{[n//i for i in range(1,8)]}', **ns)
107 nt.assert_equal(s, "[12, 6, 4, 3, 2, 2, 1]")
107 nt.assert_equal(s, "[12, 6, 4, 3, 2, 2, 1]")
108 s = f.format("{stuff!s}", **ns)
108 s = f.format("{stuff!s}", **ns)
109 nt.assert_equal(s, ns['stuff'])
109 nt.assert_equal(s, ns['stuff'])
110 s = f.format("{stuff!r}", **ns)
110 s = f.format("{stuff!r}", **ns)
111 nt.assert_equal(s, repr(ns['stuff']))
111 nt.assert_equal(s, repr(ns['stuff']))
112
112
113 # Check with unicode:
113 # Check with unicode:
114 s = f.format("{u}", **ns)
114 s = f.format("{u}", **ns)
115 nt.assert_equal(s, ns['u'])
115 nt.assert_equal(s, ns['u'])
116 # This decodes in a platform dependent manner, but it shouldn't error out
116 # This decodes in a platform dependent manner, but it shouldn't error out
117 s = f.format("{b}", **ns)
117 s = f.format("{b}", **ns)
118
118
119 nt.assert_raises(NameError, f.format, '{dne}', **ns)
119 nt.assert_raises(NameError, f.format, '{dne}', **ns)
120
120
121 def eval_formatter_slicing_check(f):
121 def eval_formatter_slicing_check(f):
122 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
122 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
123 s = f.format(" {stuff.split()[:]} ", **ns)
123 s = f.format(" {stuff.split()[:]} ", **ns)
124 nt.assert_equal(s, " ['hello', 'there'] ")
124 nt.assert_equal(s, " ['hello', 'there'] ")
125 s = f.format(" {stuff.split()[::-1]} ", **ns)
125 s = f.format(" {stuff.split()[::-1]} ", **ns)
126 nt.assert_equal(s, " ['there', 'hello'] ")
126 nt.assert_equal(s, " ['there', 'hello'] ")
127 s = f.format("{stuff[::2]}", **ns)
127 s = f.format("{stuff[::2]}", **ns)
128 nt.assert_equal(s, ns['stuff'][::2])
128 nt.assert_equal(s, ns['stuff'][::2])
129
129
130 nt.assert_raises(SyntaxError, f.format, "{n:x}", **ns)
130 nt.assert_raises(SyntaxError, f.format, "{n:x}", **ns)
131
131
132 def eval_formatter_no_slicing_check(f):
132 def eval_formatter_no_slicing_check(f):
133 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
133 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
134
134
135 s = f.format('{n:x} {pi**2:+f}', **ns)
135 s = f.format('{n:x} {pi**2:+f}', **ns)
136 nt.assert_equal(s, "c +9.869604")
136 nt.assert_equal(s, "c +9.869604")
137
137
138 s = f.format('{stuff[slice(1,4)]}', **ns)
138 s = f.format('{stuff[slice(1,4)]}', **ns)
139 nt.assert_equal(s, 'ell')
139 nt.assert_equal(s, 'ell')
140
140
141 if sys.version_info >= (3, 4):
141 if sys.version_info >= (3, 4):
142 # String formatting has changed in Python 3.4, so this now works.
142 # String formatting has changed in Python 3.4, so this now works.
143 s = f.format("{a[:]}", a=[1, 2])
143 s = f.format("{a[:]}", a=[1, 2])
144 nt.assert_equal(s, "[1, 2]")
144 nt.assert_equal(s, "[1, 2]")
145 else:
145 else:
146 nt.assert_raises(SyntaxError, f.format, "{a[:]}")
146 nt.assert_raises(SyntaxError, f.format, "{a[:]}")
147
147
148 def test_eval_formatter():
148 def test_eval_formatter():
149 f = text.EvalFormatter()
149 f = text.EvalFormatter()
150 eval_formatter_check(f)
150 eval_formatter_check(f)
151 eval_formatter_no_slicing_check(f)
151 eval_formatter_no_slicing_check(f)
152
152
153 def test_full_eval_formatter():
153 def test_full_eval_formatter():
154 f = text.FullEvalFormatter()
154 f = text.FullEvalFormatter()
155 eval_formatter_check(f)
155 eval_formatter_check(f)
156 eval_formatter_slicing_check(f)
156 eval_formatter_slicing_check(f)
157
157
158 def test_dollar_formatter():
158 def test_dollar_formatter():
159 f = text.DollarFormatter()
159 f = text.DollarFormatter()
160 eval_formatter_check(f)
160 eval_formatter_check(f)
161 eval_formatter_slicing_check(f)
161 eval_formatter_slicing_check(f)
162
162
163 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
163 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
164 s = f.format("$n", **ns)
164 s = f.format("$n", **ns)
165 nt.assert_equal(s, "12")
165 nt.assert_equal(s, "12")
166 s = f.format("$n.real", **ns)
166 s = f.format("$n.real", **ns)
167 nt.assert_equal(s, "12")
167 nt.assert_equal(s, "12")
168 s = f.format("$n/{stuff[:5]}", **ns)
168 s = f.format("$n/{stuff[:5]}", **ns)
169 nt.assert_equal(s, "12/hello")
169 nt.assert_equal(s, "12/hello")
170 s = f.format("$n $$HOME", **ns)
170 s = f.format("$n $$HOME", **ns)
171 nt.assert_equal(s, "12 $HOME")
171 nt.assert_equal(s, "12 $HOME")
172 s = f.format("${foo}", foo="HOME")
172 s = f.format("${foo}", foo="HOME")
173 nt.assert_equal(s, "$HOME")
173 nt.assert_equal(s, "$HOME")
174
174
175
175
176 def test_long_substr():
176 def test_long_substr():
177 data = ['hi']
177 data = ['hi']
178 nt.assert_equal(text.long_substr(data), 'hi')
178 nt.assert_equal(text.long_substr(data), 'hi')
179
179
180
180
181 def test_long_substr2():
181 def test_long_substr2():
182 data = ['abc', 'abd', 'abf', 'ab']
182 data = ['abc', 'abd', 'abf', 'ab']
183 nt.assert_equal(text.long_substr(data), 'ab')
183 nt.assert_equal(text.long_substr(data), 'ab')
184
184
185 def test_long_substr_empty():
185 def test_long_substr_empty():
186 data = []
186 data = []
187 nt.assert_equal(text.long_substr(data), '')
187 nt.assert_equal(text.long_substr(data), '')
188
188
189 def test_strip_email():
189 def test_strip_email():
190 src = """\
190 src = """\
191 >> >>> def f(x):
191 >> >>> def f(x):
192 >> ... return x+1
192 >> ... return x+1
193 >> ...
193 >> ...
194 >> >>> zz = f(2.5)"""
194 >> >>> zz = f(2.5)"""
195 cln = """\
195 cln = """\
196 >>> def f(x):
196 >>> def f(x):
197 ... return x+1
197 ... return x+1
198 ...
198 ...
199 >>> zz = f(2.5)"""
199 >>> zz = f(2.5)"""
200 nt.assert_equal(text.strip_email_quotes(src), cln)
200 nt.assert_equal(text.strip_email_quotes(src), cln)
201
201
202
202
203 def test_strip_email2():
203 def test_strip_email2():
204 src = '> > > list()'
204 src = '> > > list()'
205 cln = 'list()'
205 cln = 'list()'
206 nt.assert_equal(text.strip_email_quotes(src), cln)
206 nt.assert_equal(text.strip_email_quotes(src), cln)
207
207
208 def test_LSString():
208 def test_LSString():
209 lss = text.LSString("abc\ndef")
209 lss = text.LSString("abc\ndef")
210 nt.assert_equal(lss.l, ['abc', 'def'])
210 nt.assert_equal(lss.l, ['abc', 'def'])
211 nt.assert_equal(lss.s, 'abc def')
211 nt.assert_equal(lss.s, 'abc def')
212 lss = text.LSString(os.getcwd())
212 lss = text.LSString(os.getcwd())
213 nt.assert_is_instance(lss.p[0], Path)
213 nt.assert_is_instance(lss.p[0], Path)
214
214
215 def test_SList():
215 def test_SList():
216 sl = text.SList(['a 11', 'b 1', 'a 2'])
216 sl = text.SList(['a 11', 'b 1', 'a 2'])
217 nt.assert_equal(sl.n, 'a 11\nb 1\na 2')
217 nt.assert_equal(sl.n, 'a 11\nb 1\na 2')
218 nt.assert_equal(sl.s, 'a 11 b 1 a 2')
218 nt.assert_equal(sl.s, 'a 11 b 1 a 2')
219 nt.assert_equal(sl.grep(lambda x: x.startswith('a')), text.SList(['a 11', 'a 2']))
219 nt.assert_equal(sl.grep(lambda x: x.startswith('a')), text.SList(['a 11', 'a 2']))
220 nt.assert_equal(sl.fields(0), text.SList(['a', 'b', 'a']))
220 nt.assert_equal(sl.fields(0), text.SList(['a', 'b', 'a']))
221 nt.assert_equal(sl.sort(field=1, nums=True), text.SList(['b 1', 'a 2', 'a 11']))
221 nt.assert_equal(sl.sort(field=1, nums=True), text.SList(['b 1', 'a 2', 'a 11']))
@@ -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 # Python 2 backport
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 u''.join(py3compat.cast_unicode(s) for s in result)
573 return u''.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 = 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.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