##// END OF EJS Templates
Simplify some pathlib imports
Thomas Kluyver -
Show More
@@ -1,221 +1,217 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 from pathlib import Path
22 from pathlib import Path
23 except ImportError:
24 # for Python 3.3
25 from pathlib2 import Path
26
22
27 from IPython.utils import text
23 from IPython.utils import text
28
24
29 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
30 # Globals
26 # Globals
31 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
32
28
33 def test_columnize():
29 def test_columnize():
34 """Basic columnize tests."""
30 """Basic columnize tests."""
35 size = 5
31 size = 5
36 items = [l*size for l in 'abcd']
32 items = [l*size for l in 'abcd']
37
33
38 out = text.columnize(items, displaywidth=80)
34 out = text.columnize(items, displaywidth=80)
39 nt.assert_equal(out, 'aaaaa bbbbb ccccc ddddd\n')
35 nt.assert_equal(out, 'aaaaa bbbbb ccccc ddddd\n')
40 out = text.columnize(items, displaywidth=25)
36 out = text.columnize(items, displaywidth=25)
41 nt.assert_equal(out, 'aaaaa ccccc\nbbbbb ddddd\n')
37 nt.assert_equal(out, 'aaaaa ccccc\nbbbbb ddddd\n')
42 out = text.columnize(items, displaywidth=12)
38 out = text.columnize(items, displaywidth=12)
43 nt.assert_equal(out, 'aaaaa ccccc\nbbbbb ddddd\n')
39 nt.assert_equal(out, 'aaaaa ccccc\nbbbbb ddddd\n')
44 out = text.columnize(items, displaywidth=10)
40 out = text.columnize(items, displaywidth=10)
45 nt.assert_equal(out, 'aaaaa\nbbbbb\nccccc\nddddd\n')
41 nt.assert_equal(out, 'aaaaa\nbbbbb\nccccc\nddddd\n')
46
42
47 out = text.columnize(items, row_first=True, displaywidth=80)
43 out = text.columnize(items, row_first=True, displaywidth=80)
48 nt.assert_equal(out, 'aaaaa bbbbb ccccc ddddd\n')
44 nt.assert_equal(out, 'aaaaa bbbbb ccccc ddddd\n')
49 out = text.columnize(items, row_first=True, displaywidth=25)
45 out = text.columnize(items, row_first=True, displaywidth=25)
50 nt.assert_equal(out, 'aaaaa bbbbb\nccccc ddddd\n')
46 nt.assert_equal(out, 'aaaaa bbbbb\nccccc ddddd\n')
51 out = text.columnize(items, row_first=True, displaywidth=12)
47 out = text.columnize(items, row_first=True, displaywidth=12)
52 nt.assert_equal(out, 'aaaaa bbbbb\nccccc ddddd\n')
48 nt.assert_equal(out, 'aaaaa bbbbb\nccccc ddddd\n')
53 out = text.columnize(items, row_first=True, displaywidth=10)
49 out = text.columnize(items, row_first=True, displaywidth=10)
54 nt.assert_equal(out, 'aaaaa\nbbbbb\nccccc\nddddd\n')
50 nt.assert_equal(out, 'aaaaa\nbbbbb\nccccc\nddddd\n')
55
51
56 out = text.columnize(items, displaywidth=40, spread=True)
52 out = text.columnize(items, displaywidth=40, spread=True)
57 nt.assert_equal(out, 'aaaaa bbbbb ccccc ddddd\n')
53 nt.assert_equal(out, 'aaaaa bbbbb ccccc ddddd\n')
58 out = text.columnize(items, displaywidth=20, spread=True)
54 out = text.columnize(items, displaywidth=20, spread=True)
59 nt.assert_equal(out, 'aaaaa ccccc\nbbbbb ddddd\n')
55 nt.assert_equal(out, 'aaaaa ccccc\nbbbbb ddddd\n')
60 out = text.columnize(items, displaywidth=12, spread=True)
56 out = text.columnize(items, displaywidth=12, spread=True)
61 nt.assert_equal(out, 'aaaaa ccccc\nbbbbb ddddd\n')
57 nt.assert_equal(out, 'aaaaa ccccc\nbbbbb ddddd\n')
62 out = text.columnize(items, displaywidth=10, spread=True)
58 out = text.columnize(items, displaywidth=10, spread=True)
63 nt.assert_equal(out, 'aaaaa\nbbbbb\nccccc\nddddd\n')
59 nt.assert_equal(out, 'aaaaa\nbbbbb\nccccc\nddddd\n')
64
60
65
61
66 def test_columnize_random():
62 def test_columnize_random():
67 """Test with random input to hopefully catch edge case """
63 """Test with random input to hopefully catch edge case """
68 for row_first in [True, False]:
64 for row_first in [True, False]:
69 for nitems in [random.randint(2,70) for i in range(2,20)]:
65 for nitems in [random.randint(2,70) for i in range(2,20)]:
70 displaywidth = random.randint(20,200)
66 displaywidth = random.randint(20,200)
71 rand_len = [random.randint(2,displaywidth) for i in range(nitems)]
67 rand_len = [random.randint(2,displaywidth) for i in range(nitems)]
72 items = ['x'*l for l in rand_len]
68 items = ['x'*l for l in rand_len]
73 out = text.columnize(items, row_first=row_first, displaywidth=displaywidth)
69 out = text.columnize(items, row_first=row_first, displaywidth=displaywidth)
74 longer_line = max([len(x) for x in out.split('\n')])
70 longer_line = max([len(x) for x in out.split('\n')])
75 longer_element = max(rand_len)
71 longer_element = max(rand_len)
76 if longer_line > displaywidth:
72 if longer_line > displaywidth:
77 print("Columnize displayed something lager than displaywidth : %s " % longer_line)
73 print("Columnize displayed something lager than displaywidth : %s " % longer_line)
78 print("longer element : %s " % longer_element)
74 print("longer element : %s " % longer_element)
79 print("displaywidth : %s " % displaywidth)
75 print("displaywidth : %s " % displaywidth)
80 print("number of element : %s " % nitems)
76 print("number of element : %s " % nitems)
81 print("size of each element :\n %s" % rand_len)
77 print("size of each element :\n %s" % rand_len)
82 assert False, "row_first={0}".format(row_first)
78 assert False, "row_first={0}".format(row_first)
83
79
84 def test_columnize_medium():
80 def test_columnize_medium():
85 """Test with inputs than shouldn't be wider than 80"""
81 """Test with inputs than shouldn't be wider than 80"""
86 size = 40
82 size = 40
87 items = [l*size for l in 'abc']
83 items = [l*size for l in 'abc']
88 for row_first in [True, False]:
84 for row_first in [True, False]:
89 out = text.columnize(items, row_first=row_first, displaywidth=80)
85 out = text.columnize(items, row_first=row_first, displaywidth=80)
90 nt.assert_equal(out, '\n'.join(items+['']), "row_first={0}".format(row_first))
86 nt.assert_equal(out, '\n'.join(items+['']), "row_first={0}".format(row_first))
91
87
92 def test_columnize_long():
88 def test_columnize_long():
93 """Test columnize with inputs longer than the display window"""
89 """Test columnize with inputs longer than the display window"""
94 size = 11
90 size = 11
95 items = [l*size for l in 'abc']
91 items = [l*size for l in 'abc']
96 for row_first in [True, False]:
92 for row_first in [True, False]:
97 out = text.columnize(items, row_first=row_first, displaywidth=size-1)
93 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))
94 nt.assert_equal(out, '\n'.join(items+['']), "row_first={0}".format(row_first))
99
95
100 def eval_formatter_check(f):
96 def eval_formatter_check(f):
101 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os, u=u"cafΓ©", b="cafΓ©")
97 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)
98 s = f.format("{n} {n//4} {stuff.split()[0]}", **ns)
103 nt.assert_equal(s, "12 3 hello")
99 nt.assert_equal(s, "12 3 hello")
104 s = f.format(' '.join(['{n//%i}'%i for i in range(1,8)]), **ns)
100 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")
101 nt.assert_equal(s, "12 6 4 3 2 2 1")
106 s = f.format('{[n//i for i in range(1,8)]}', **ns)
102 s = f.format('{[n//i for i in range(1,8)]}', **ns)
107 nt.assert_equal(s, "[12, 6, 4, 3, 2, 2, 1]")
103 nt.assert_equal(s, "[12, 6, 4, 3, 2, 2, 1]")
108 s = f.format("{stuff!s}", **ns)
104 s = f.format("{stuff!s}", **ns)
109 nt.assert_equal(s, ns['stuff'])
105 nt.assert_equal(s, ns['stuff'])
110 s = f.format("{stuff!r}", **ns)
106 s = f.format("{stuff!r}", **ns)
111 nt.assert_equal(s, repr(ns['stuff']))
107 nt.assert_equal(s, repr(ns['stuff']))
112
108
113 # Check with unicode:
109 # Check with unicode:
114 s = f.format("{u}", **ns)
110 s = f.format("{u}", **ns)
115 nt.assert_equal(s, ns['u'])
111 nt.assert_equal(s, ns['u'])
116 # This decodes in a platform dependent manner, but it shouldn't error out
112 # This decodes in a platform dependent manner, but it shouldn't error out
117 s = f.format("{b}", **ns)
113 s = f.format("{b}", **ns)
118
114
119 nt.assert_raises(NameError, f.format, '{dne}', **ns)
115 nt.assert_raises(NameError, f.format, '{dne}', **ns)
120
116
121 def eval_formatter_slicing_check(f):
117 def eval_formatter_slicing_check(f):
122 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
118 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
123 s = f.format(" {stuff.split()[:]} ", **ns)
119 s = f.format(" {stuff.split()[:]} ", **ns)
124 nt.assert_equal(s, " ['hello', 'there'] ")
120 nt.assert_equal(s, " ['hello', 'there'] ")
125 s = f.format(" {stuff.split()[::-1]} ", **ns)
121 s = f.format(" {stuff.split()[::-1]} ", **ns)
126 nt.assert_equal(s, " ['there', 'hello'] ")
122 nt.assert_equal(s, " ['there', 'hello'] ")
127 s = f.format("{stuff[::2]}", **ns)
123 s = f.format("{stuff[::2]}", **ns)
128 nt.assert_equal(s, ns['stuff'][::2])
124 nt.assert_equal(s, ns['stuff'][::2])
129
125
130 nt.assert_raises(SyntaxError, f.format, "{n:x}", **ns)
126 nt.assert_raises(SyntaxError, f.format, "{n:x}", **ns)
131
127
132 def eval_formatter_no_slicing_check(f):
128 def eval_formatter_no_slicing_check(f):
133 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
129 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
134
130
135 s = f.format('{n:x} {pi**2:+f}', **ns)
131 s = f.format('{n:x} {pi**2:+f}', **ns)
136 nt.assert_equal(s, "c +9.869604")
132 nt.assert_equal(s, "c +9.869604")
137
133
138 s = f.format('{stuff[slice(1,4)]}', **ns)
134 s = f.format('{stuff[slice(1,4)]}', **ns)
139 nt.assert_equal(s, 'ell')
135 nt.assert_equal(s, 'ell')
140
136
141 if sys.version_info >= (3, 4):
137 if sys.version_info >= (3, 4):
142 # String formatting has changed in Python 3.4, so this now works.
138 # String formatting has changed in Python 3.4, so this now works.
143 s = f.format("{a[:]}", a=[1, 2])
139 s = f.format("{a[:]}", a=[1, 2])
144 nt.assert_equal(s, "[1, 2]")
140 nt.assert_equal(s, "[1, 2]")
145 else:
141 else:
146 nt.assert_raises(SyntaxError, f.format, "{a[:]}")
142 nt.assert_raises(SyntaxError, f.format, "{a[:]}")
147
143
148 def test_eval_formatter():
144 def test_eval_formatter():
149 f = text.EvalFormatter()
145 f = text.EvalFormatter()
150 eval_formatter_check(f)
146 eval_formatter_check(f)
151 eval_formatter_no_slicing_check(f)
147 eval_formatter_no_slicing_check(f)
152
148
153 def test_full_eval_formatter():
149 def test_full_eval_formatter():
154 f = text.FullEvalFormatter()
150 f = text.FullEvalFormatter()
155 eval_formatter_check(f)
151 eval_formatter_check(f)
156 eval_formatter_slicing_check(f)
152 eval_formatter_slicing_check(f)
157
153
158 def test_dollar_formatter():
154 def test_dollar_formatter():
159 f = text.DollarFormatter()
155 f = text.DollarFormatter()
160 eval_formatter_check(f)
156 eval_formatter_check(f)
161 eval_formatter_slicing_check(f)
157 eval_formatter_slicing_check(f)
162
158
163 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
159 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
164 s = f.format("$n", **ns)
160 s = f.format("$n", **ns)
165 nt.assert_equal(s, "12")
161 nt.assert_equal(s, "12")
166 s = f.format("$n.real", **ns)
162 s = f.format("$n.real", **ns)
167 nt.assert_equal(s, "12")
163 nt.assert_equal(s, "12")
168 s = f.format("$n/{stuff[:5]}", **ns)
164 s = f.format("$n/{stuff[:5]}", **ns)
169 nt.assert_equal(s, "12/hello")
165 nt.assert_equal(s, "12/hello")
170 s = f.format("$n $$HOME", **ns)
166 s = f.format("$n $$HOME", **ns)
171 nt.assert_equal(s, "12 $HOME")
167 nt.assert_equal(s, "12 $HOME")
172 s = f.format("${foo}", foo="HOME")
168 s = f.format("${foo}", foo="HOME")
173 nt.assert_equal(s, "$HOME")
169 nt.assert_equal(s, "$HOME")
174
170
175
171
176 def test_long_substr():
172 def test_long_substr():
177 data = ['hi']
173 data = ['hi']
178 nt.assert_equal(text.long_substr(data), 'hi')
174 nt.assert_equal(text.long_substr(data), 'hi')
179
175
180
176
181 def test_long_substr2():
177 def test_long_substr2():
182 data = ['abc', 'abd', 'abf', 'ab']
178 data = ['abc', 'abd', 'abf', 'ab']
183 nt.assert_equal(text.long_substr(data), 'ab')
179 nt.assert_equal(text.long_substr(data), 'ab')
184
180
185 def test_long_substr_empty():
181 def test_long_substr_empty():
186 data = []
182 data = []
187 nt.assert_equal(text.long_substr(data), '')
183 nt.assert_equal(text.long_substr(data), '')
188
184
189 def test_strip_email():
185 def test_strip_email():
190 src = """\
186 src = """\
191 >> >>> def f(x):
187 >> >>> def f(x):
192 >> ... return x+1
188 >> ... return x+1
193 >> ...
189 >> ...
194 >> >>> zz = f(2.5)"""
190 >> >>> zz = f(2.5)"""
195 cln = """\
191 cln = """\
196 >>> def f(x):
192 >>> def f(x):
197 ... return x+1
193 ... return x+1
198 ...
194 ...
199 >>> zz = f(2.5)"""
195 >>> zz = f(2.5)"""
200 nt.assert_equal(text.strip_email_quotes(src), cln)
196 nt.assert_equal(text.strip_email_quotes(src), cln)
201
197
202
198
203 def test_strip_email2():
199 def test_strip_email2():
204 src = '> > > list()'
200 src = '> > > list()'
205 cln = 'list()'
201 cln = 'list()'
206 nt.assert_equal(text.strip_email_quotes(src), cln)
202 nt.assert_equal(text.strip_email_quotes(src), cln)
207
203
208 def test_LSString():
204 def test_LSString():
209 lss = text.LSString("abc\ndef")
205 lss = text.LSString("abc\ndef")
210 nt.assert_equal(lss.l, ['abc', 'def'])
206 nt.assert_equal(lss.l, ['abc', 'def'])
211 nt.assert_equal(lss.s, 'abc def')
207 nt.assert_equal(lss.s, 'abc def')
212 lss = text.LSString(os.getcwd())
208 lss = text.LSString(os.getcwd())
213 nt.assert_is_instance(lss.p[0], Path)
209 nt.assert_is_instance(lss.p[0], Path)
214
210
215 def test_SList():
211 def test_SList():
216 sl = text.SList(['a 11', 'b 1', 'a 2'])
212 sl = text.SList(['a 11', 'b 1', 'a 2'])
217 nt.assert_equal(sl.n, 'a 11\nb 1\na 2')
213 nt.assert_equal(sl.n, 'a 11\nb 1\na 2')
218 nt.assert_equal(sl.s, 'a 11 b 1 a 2')
214 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']))
215 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']))
216 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']))
217 nt.assert_equal(sl.sort(field=1, nums=True), text.SList(['b 1', 'a 2', 'a 11']))
@@ -1,776 +1,772 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Utilities for working with strings and text.
3 Utilities for working with strings and text.
4
4
5 Inheritance diagram:
5 Inheritance diagram:
6
6
7 .. inheritance-diagram:: IPython.utils.text
7 .. inheritance-diagram:: IPython.utils.text
8 :parts: 3
8 :parts: 3
9 """
9 """
10
10
11 import os
11 import os
12 import re
12 import re
13 import sys
13 import sys
14 import textwrap
14 import textwrap
15 from string import Formatter
15 from string import Formatter
16 try:
16 from pathlib import Path
17 from pathlib import Path
18 except ImportError:
19 # for Python 3.3
20 from pathlib2 import Path
21
17
22 from IPython.utils import py3compat
18 from IPython.utils import py3compat
23
19
24 # datetime.strftime date format for ipython
20 # datetime.strftime date format for ipython
25 if sys.platform == 'win32':
21 if sys.platform == 'win32':
26 date_format = "%B %d, %Y"
22 date_format = "%B %d, %Y"
27 else:
23 else:
28 date_format = "%B %-d, %Y"
24 date_format = "%B %-d, %Y"
29
25
30 class LSString(str):
26 class LSString(str):
31 """String derivative with a special access attributes.
27 """String derivative with a special access attributes.
32
28
33 These are normal strings, but with the special attributes:
29 These are normal strings, but with the special attributes:
34
30
35 .l (or .list) : value as list (split on newlines).
31 .l (or .list) : value as list (split on newlines).
36 .n (or .nlstr): original value (the string itself).
32 .n (or .nlstr): original value (the string itself).
37 .s (or .spstr): value as whitespace-separated string.
33 .s (or .spstr): value as whitespace-separated string.
38 .p (or .paths): list of path objects (requires path.py package)
34 .p (or .paths): list of path objects (requires path.py package)
39
35
40 Any values which require transformations are computed only once and
36 Any values which require transformations are computed only once and
41 cached.
37 cached.
42
38
43 Such strings are very useful to efficiently interact with the shell, which
39 Such strings are very useful to efficiently interact with the shell, which
44 typically only understands whitespace-separated options for commands."""
40 typically only understands whitespace-separated options for commands."""
45
41
46 def get_list(self):
42 def get_list(self):
47 try:
43 try:
48 return self.__list
44 return self.__list
49 except AttributeError:
45 except AttributeError:
50 self.__list = self.split('\n')
46 self.__list = self.split('\n')
51 return self.__list
47 return self.__list
52
48
53 l = list = property(get_list)
49 l = list = property(get_list)
54
50
55 def get_spstr(self):
51 def get_spstr(self):
56 try:
52 try:
57 return self.__spstr
53 return self.__spstr
58 except AttributeError:
54 except AttributeError:
59 self.__spstr = self.replace('\n',' ')
55 self.__spstr = self.replace('\n',' ')
60 return self.__spstr
56 return self.__spstr
61
57
62 s = spstr = property(get_spstr)
58 s = spstr = property(get_spstr)
63
59
64 def get_nlstr(self):
60 def get_nlstr(self):
65 return self
61 return self
66
62
67 n = nlstr = property(get_nlstr)
63 n = nlstr = property(get_nlstr)
68
64
69 def get_paths(self):
65 def get_paths(self):
70 try:
66 try:
71 return self.__paths
67 return self.__paths
72 except AttributeError:
68 except AttributeError:
73 self.__paths = [Path(p) for p in self.split('\n') if os.path.exists(p)]
69 self.__paths = [Path(p) for p in self.split('\n') if os.path.exists(p)]
74 return self.__paths
70 return self.__paths
75
71
76 p = paths = property(get_paths)
72 p = paths = property(get_paths)
77
73
78 # FIXME: We need to reimplement type specific displayhook and then add this
74 # FIXME: We need to reimplement type specific displayhook and then add this
79 # back as a custom printer. This should also be moved outside utils into the
75 # back as a custom printer. This should also be moved outside utils into the
80 # core.
76 # core.
81
77
82 # def print_lsstring(arg):
78 # def print_lsstring(arg):
83 # """ Prettier (non-repr-like) and more informative printer for LSString """
79 # """ Prettier (non-repr-like) and more informative printer for LSString """
84 # print "LSString (.p, .n, .l, .s available). Value:"
80 # print "LSString (.p, .n, .l, .s available). Value:"
85 # print arg
81 # print arg
86 #
82 #
87 #
83 #
88 # print_lsstring = result_display.when_type(LSString)(print_lsstring)
84 # print_lsstring = result_display.when_type(LSString)(print_lsstring)
89
85
90
86
91 class SList(list):
87 class SList(list):
92 """List derivative with a special access attributes.
88 """List derivative with a special access attributes.
93
89
94 These are normal lists, but with the special attributes:
90 These are normal lists, but with the special attributes:
95
91
96 * .l (or .list) : value as list (the list itself).
92 * .l (or .list) : value as list (the list itself).
97 * .n (or .nlstr): value as a string, joined on newlines.
93 * .n (or .nlstr): value as a string, joined on newlines.
98 * .s (or .spstr): value as a string, joined on spaces.
94 * .s (or .spstr): value as a string, joined on spaces.
99 * .p (or .paths): list of path objects (requires path.py package)
95 * .p (or .paths): list of path objects (requires path.py package)
100
96
101 Any values which require transformations are computed only once and
97 Any values which require transformations are computed only once and
102 cached."""
98 cached."""
103
99
104 def get_list(self):
100 def get_list(self):
105 return self
101 return self
106
102
107 l = list = property(get_list)
103 l = list = property(get_list)
108
104
109 def get_spstr(self):
105 def get_spstr(self):
110 try:
106 try:
111 return self.__spstr
107 return self.__spstr
112 except AttributeError:
108 except AttributeError:
113 self.__spstr = ' '.join(self)
109 self.__spstr = ' '.join(self)
114 return self.__spstr
110 return self.__spstr
115
111
116 s = spstr = property(get_spstr)
112 s = spstr = property(get_spstr)
117
113
118 def get_nlstr(self):
114 def get_nlstr(self):
119 try:
115 try:
120 return self.__nlstr
116 return self.__nlstr
121 except AttributeError:
117 except AttributeError:
122 self.__nlstr = '\n'.join(self)
118 self.__nlstr = '\n'.join(self)
123 return self.__nlstr
119 return self.__nlstr
124
120
125 n = nlstr = property(get_nlstr)
121 n = nlstr = property(get_nlstr)
126
122
127 def get_paths(self):
123 def get_paths(self):
128 try:
124 try:
129 return self.__paths
125 return self.__paths
130 except AttributeError:
126 except AttributeError:
131 self.__paths = [Path(p) for p in self if os.path.exists(p)]
127 self.__paths = [Path(p) for p in self if os.path.exists(p)]
132 return self.__paths
128 return self.__paths
133
129
134 p = paths = property(get_paths)
130 p = paths = property(get_paths)
135
131
136 def grep(self, pattern, prune = False, field = None):
132 def grep(self, pattern, prune = False, field = None):
137 """ Return all strings matching 'pattern' (a regex or callable)
133 """ Return all strings matching 'pattern' (a regex or callable)
138
134
139 This is case-insensitive. If prune is true, return all items
135 This is case-insensitive. If prune is true, return all items
140 NOT matching the pattern.
136 NOT matching the pattern.
141
137
142 If field is specified, the match must occur in the specified
138 If field is specified, the match must occur in the specified
143 whitespace-separated field.
139 whitespace-separated field.
144
140
145 Examples::
141 Examples::
146
142
147 a.grep( lambda x: x.startswith('C') )
143 a.grep( lambda x: x.startswith('C') )
148 a.grep('Cha.*log', prune=1)
144 a.grep('Cha.*log', prune=1)
149 a.grep('chm', field=-1)
145 a.grep('chm', field=-1)
150 """
146 """
151
147
152 def match_target(s):
148 def match_target(s):
153 if field is None:
149 if field is None:
154 return s
150 return s
155 parts = s.split()
151 parts = s.split()
156 try:
152 try:
157 tgt = parts[field]
153 tgt = parts[field]
158 return tgt
154 return tgt
159 except IndexError:
155 except IndexError:
160 return ""
156 return ""
161
157
162 if isinstance(pattern, str):
158 if isinstance(pattern, str):
163 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
159 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
164 else:
160 else:
165 pred = pattern
161 pred = pattern
166 if not prune:
162 if not prune:
167 return SList([el for el in self if pred(match_target(el))])
163 return SList([el for el in self if pred(match_target(el))])
168 else:
164 else:
169 return SList([el for el in self if not pred(match_target(el))])
165 return SList([el for el in self if not pred(match_target(el))])
170
166
171 def fields(self, *fields):
167 def fields(self, *fields):
172 """ Collect whitespace-separated fields from string list
168 """ Collect whitespace-separated fields from string list
173
169
174 Allows quick awk-like usage of string lists.
170 Allows quick awk-like usage of string lists.
175
171
176 Example data (in var a, created by 'a = !ls -l')::
172 Example data (in var a, created by 'a = !ls -l')::
177
173
178 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
174 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
179 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
175 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
180
176
181 * ``a.fields(0)`` is ``['-rwxrwxrwx', 'drwxrwxrwx+']``
177 * ``a.fields(0)`` is ``['-rwxrwxrwx', 'drwxrwxrwx+']``
182 * ``a.fields(1,0)`` is ``['1 -rwxrwxrwx', '6 drwxrwxrwx+']``
178 * ``a.fields(1,0)`` is ``['1 -rwxrwxrwx', '6 drwxrwxrwx+']``
183 (note the joining by space).
179 (note the joining by space).
184 * ``a.fields(-1)`` is ``['ChangeLog', 'IPython']``
180 * ``a.fields(-1)`` is ``['ChangeLog', 'IPython']``
185
181
186 IndexErrors are ignored.
182 IndexErrors are ignored.
187
183
188 Without args, fields() just split()'s the strings.
184 Without args, fields() just split()'s the strings.
189 """
185 """
190 if len(fields) == 0:
186 if len(fields) == 0:
191 return [el.split() for el in self]
187 return [el.split() for el in self]
192
188
193 res = SList()
189 res = SList()
194 for el in [f.split() for f in self]:
190 for el in [f.split() for f in self]:
195 lineparts = []
191 lineparts = []
196
192
197 for fd in fields:
193 for fd in fields:
198 try:
194 try:
199 lineparts.append(el[fd])
195 lineparts.append(el[fd])
200 except IndexError:
196 except IndexError:
201 pass
197 pass
202 if lineparts:
198 if lineparts:
203 res.append(" ".join(lineparts))
199 res.append(" ".join(lineparts))
204
200
205 return res
201 return res
206
202
207 def sort(self,field= None, nums = False):
203 def sort(self,field= None, nums = False):
208 """ sort by specified fields (see fields())
204 """ sort by specified fields (see fields())
209
205
210 Example::
206 Example::
211
207
212 a.sort(1, nums = True)
208 a.sort(1, nums = True)
213
209
214 Sorts a by second field, in numerical order (so that 21 > 3)
210 Sorts a by second field, in numerical order (so that 21 > 3)
215
211
216 """
212 """
217
213
218 #decorate, sort, undecorate
214 #decorate, sort, undecorate
219 if field is not None:
215 if field is not None:
220 dsu = [[SList([line]).fields(field), line] for line in self]
216 dsu = [[SList([line]).fields(field), line] for line in self]
221 else:
217 else:
222 dsu = [[line, line] for line in self]
218 dsu = [[line, line] for line in self]
223 if nums:
219 if nums:
224 for i in range(len(dsu)):
220 for i in range(len(dsu)):
225 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
221 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
226 try:
222 try:
227 n = int(numstr)
223 n = int(numstr)
228 except ValueError:
224 except ValueError:
229 n = 0
225 n = 0
230 dsu[i][0] = n
226 dsu[i][0] = n
231
227
232
228
233 dsu.sort()
229 dsu.sort()
234 return SList([t[1] for t in dsu])
230 return SList([t[1] for t in dsu])
235
231
236
232
237 # FIXME: We need to reimplement type specific displayhook and then add this
233 # FIXME: We need to reimplement type specific displayhook and then add this
238 # back as a custom printer. This should also be moved outside utils into the
234 # back as a custom printer. This should also be moved outside utils into the
239 # core.
235 # core.
240
236
241 # def print_slist(arg):
237 # def print_slist(arg):
242 # """ Prettier (non-repr-like) and more informative printer for SList """
238 # """ Prettier (non-repr-like) and more informative printer for SList """
243 # print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):"
239 # print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):"
244 # if hasattr(arg, 'hideonce') and arg.hideonce:
240 # if hasattr(arg, 'hideonce') and arg.hideonce:
245 # arg.hideonce = False
241 # arg.hideonce = False
246 # return
242 # return
247 #
243 #
248 # nlprint(arg) # This was a nested list printer, now removed.
244 # nlprint(arg) # This was a nested list printer, now removed.
249 #
245 #
250 # print_slist = result_display.when_type(SList)(print_slist)
246 # print_slist = result_display.when_type(SList)(print_slist)
251
247
252
248
253 def indent(instr,nspaces=4, ntabs=0, flatten=False):
249 def indent(instr,nspaces=4, ntabs=0, flatten=False):
254 """Indent a string a given number of spaces or tabstops.
250 """Indent a string a given number of spaces or tabstops.
255
251
256 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
252 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
257
253
258 Parameters
254 Parameters
259 ----------
255 ----------
260
256
261 instr : basestring
257 instr : basestring
262 The string to be indented.
258 The string to be indented.
263 nspaces : int (default: 4)
259 nspaces : int (default: 4)
264 The number of spaces to be indented.
260 The number of spaces to be indented.
265 ntabs : int (default: 0)
261 ntabs : int (default: 0)
266 The number of tabs to be indented.
262 The number of tabs to be indented.
267 flatten : bool (default: False)
263 flatten : bool (default: False)
268 Whether to scrub existing indentation. If True, all lines will be
264 Whether to scrub existing indentation. If True, all lines will be
269 aligned to the same indentation. If False, existing indentation will
265 aligned to the same indentation. If False, existing indentation will
270 be strictly increased.
266 be strictly increased.
271
267
272 Returns
268 Returns
273 -------
269 -------
274
270
275 str|unicode : string indented by ntabs and nspaces.
271 str|unicode : string indented by ntabs and nspaces.
276
272
277 """
273 """
278 if instr is None:
274 if instr is None:
279 return
275 return
280 ind = '\t'*ntabs+' '*nspaces
276 ind = '\t'*ntabs+' '*nspaces
281 if flatten:
277 if flatten:
282 pat = re.compile(r'^\s*', re.MULTILINE)
278 pat = re.compile(r'^\s*', re.MULTILINE)
283 else:
279 else:
284 pat = re.compile(r'^', re.MULTILINE)
280 pat = re.compile(r'^', re.MULTILINE)
285 outstr = re.sub(pat, ind, instr)
281 outstr = re.sub(pat, ind, instr)
286 if outstr.endswith(os.linesep+ind):
282 if outstr.endswith(os.linesep+ind):
287 return outstr[:-len(ind)]
283 return outstr[:-len(ind)]
288 else:
284 else:
289 return outstr
285 return outstr
290
286
291
287
292 def list_strings(arg):
288 def list_strings(arg):
293 """Always return a list of strings, given a string or list of strings
289 """Always return a list of strings, given a string or list of strings
294 as input.
290 as input.
295
291
296 Examples
292 Examples
297 --------
293 --------
298 ::
294 ::
299
295
300 In [7]: list_strings('A single string')
296 In [7]: list_strings('A single string')
301 Out[7]: ['A single string']
297 Out[7]: ['A single string']
302
298
303 In [8]: list_strings(['A single string in a list'])
299 In [8]: list_strings(['A single string in a list'])
304 Out[8]: ['A single string in a list']
300 Out[8]: ['A single string in a list']
305
301
306 In [9]: list_strings(['A','list','of','strings'])
302 In [9]: list_strings(['A','list','of','strings'])
307 Out[9]: ['A', 'list', 'of', 'strings']
303 Out[9]: ['A', 'list', 'of', 'strings']
308 """
304 """
309
305
310 if isinstance(arg, str):
306 if isinstance(arg, str):
311 return [arg]
307 return [arg]
312 else:
308 else:
313 return arg
309 return arg
314
310
315
311
316 def marquee(txt='',width=78,mark='*'):
312 def marquee(txt='',width=78,mark='*'):
317 """Return the input string centered in a 'marquee'.
313 """Return the input string centered in a 'marquee'.
318
314
319 Examples
315 Examples
320 --------
316 --------
321 ::
317 ::
322
318
323 In [16]: marquee('A test',40)
319 In [16]: marquee('A test',40)
324 Out[16]: '**************** A test ****************'
320 Out[16]: '**************** A test ****************'
325
321
326 In [17]: marquee('A test',40,'-')
322 In [17]: marquee('A test',40,'-')
327 Out[17]: '---------------- A test ----------------'
323 Out[17]: '---------------- A test ----------------'
328
324
329 In [18]: marquee('A test',40,' ')
325 In [18]: marquee('A test',40,' ')
330 Out[18]: ' A test '
326 Out[18]: ' A test '
331
327
332 """
328 """
333 if not txt:
329 if not txt:
334 return (mark*width)[:width]
330 return (mark*width)[:width]
335 nmark = (width-len(txt)-2)//len(mark)//2
331 nmark = (width-len(txt)-2)//len(mark)//2
336 if nmark < 0: nmark =0
332 if nmark < 0: nmark =0
337 marks = mark*nmark
333 marks = mark*nmark
338 return '%s %s %s' % (marks,txt,marks)
334 return '%s %s %s' % (marks,txt,marks)
339
335
340
336
341 ini_spaces_re = re.compile(r'^(\s+)')
337 ini_spaces_re = re.compile(r'^(\s+)')
342
338
343 def num_ini_spaces(strng):
339 def num_ini_spaces(strng):
344 """Return the number of initial spaces in a string"""
340 """Return the number of initial spaces in a string"""
345
341
346 ini_spaces = ini_spaces_re.match(strng)
342 ini_spaces = ini_spaces_re.match(strng)
347 if ini_spaces:
343 if ini_spaces:
348 return ini_spaces.end()
344 return ini_spaces.end()
349 else:
345 else:
350 return 0
346 return 0
351
347
352
348
353 def format_screen(strng):
349 def format_screen(strng):
354 """Format a string for screen printing.
350 """Format a string for screen printing.
355
351
356 This removes some latex-type format codes."""
352 This removes some latex-type format codes."""
357 # Paragraph continue
353 # Paragraph continue
358 par_re = re.compile(r'\\$',re.MULTILINE)
354 par_re = re.compile(r'\\$',re.MULTILINE)
359 strng = par_re.sub('',strng)
355 strng = par_re.sub('',strng)
360 return strng
356 return strng
361
357
362
358
363 def dedent(text):
359 def dedent(text):
364 """Equivalent of textwrap.dedent that ignores unindented first line.
360 """Equivalent of textwrap.dedent that ignores unindented first line.
365
361
366 This means it will still dedent strings like:
362 This means it will still dedent strings like:
367 '''foo
363 '''foo
368 is a bar
364 is a bar
369 '''
365 '''
370
366
371 For use in wrap_paragraphs.
367 For use in wrap_paragraphs.
372 """
368 """
373
369
374 if text.startswith('\n'):
370 if text.startswith('\n'):
375 # text starts with blank line, don't ignore the first line
371 # text starts with blank line, don't ignore the first line
376 return textwrap.dedent(text)
372 return textwrap.dedent(text)
377
373
378 # split first line
374 # split first line
379 splits = text.split('\n',1)
375 splits = text.split('\n',1)
380 if len(splits) == 1:
376 if len(splits) == 1:
381 # only one line
377 # only one line
382 return textwrap.dedent(text)
378 return textwrap.dedent(text)
383
379
384 first, rest = splits
380 first, rest = splits
385 # dedent everything but the first line
381 # dedent everything but the first line
386 rest = textwrap.dedent(rest)
382 rest = textwrap.dedent(rest)
387 return '\n'.join([first, rest])
383 return '\n'.join([first, rest])
388
384
389
385
390 def wrap_paragraphs(text, ncols=80):
386 def wrap_paragraphs(text, ncols=80):
391 """Wrap multiple paragraphs to fit a specified width.
387 """Wrap multiple paragraphs to fit a specified width.
392
388
393 This is equivalent to textwrap.wrap, but with support for multiple
389 This is equivalent to textwrap.wrap, but with support for multiple
394 paragraphs, as separated by empty lines.
390 paragraphs, as separated by empty lines.
395
391
396 Returns
392 Returns
397 -------
393 -------
398
394
399 list of complete paragraphs, wrapped to fill `ncols` columns.
395 list of complete paragraphs, wrapped to fill `ncols` columns.
400 """
396 """
401 paragraph_re = re.compile(r'\n(\s*\n)+', re.MULTILINE)
397 paragraph_re = re.compile(r'\n(\s*\n)+', re.MULTILINE)
402 text = dedent(text).strip()
398 text = dedent(text).strip()
403 paragraphs = paragraph_re.split(text)[::2] # every other entry is space
399 paragraphs = paragraph_re.split(text)[::2] # every other entry is space
404 out_ps = []
400 out_ps = []
405 indent_re = re.compile(r'\n\s+', re.MULTILINE)
401 indent_re = re.compile(r'\n\s+', re.MULTILINE)
406 for p in paragraphs:
402 for p in paragraphs:
407 # presume indentation that survives dedent is meaningful formatting,
403 # presume indentation that survives dedent is meaningful formatting,
408 # so don't fill unless text is flush.
404 # so don't fill unless text is flush.
409 if indent_re.search(p) is None:
405 if indent_re.search(p) is None:
410 # wrap paragraph
406 # wrap paragraph
411 p = textwrap.fill(p, ncols)
407 p = textwrap.fill(p, ncols)
412 out_ps.append(p)
408 out_ps.append(p)
413 return out_ps
409 return out_ps
414
410
415
411
416 def long_substr(data):
412 def long_substr(data):
417 """Return the longest common substring in a list of strings.
413 """Return the longest common substring in a list of strings.
418
414
419 Credit: http://stackoverflow.com/questions/2892931/longest-common-substring-from-more-than-two-strings-python
415 Credit: http://stackoverflow.com/questions/2892931/longest-common-substring-from-more-than-two-strings-python
420 """
416 """
421 substr = ''
417 substr = ''
422 if len(data) > 1 and len(data[0]) > 0:
418 if len(data) > 1 and len(data[0]) > 0:
423 for i in range(len(data[0])):
419 for i in range(len(data[0])):
424 for j in range(len(data[0])-i+1):
420 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):
421 if j > len(substr) and all(data[0][i:i+j] in x for x in data):
426 substr = data[0][i:i+j]
422 substr = data[0][i:i+j]
427 elif len(data) == 1:
423 elif len(data) == 1:
428 substr = data[0]
424 substr = data[0]
429 return substr
425 return substr
430
426
431
427
432 def strip_email_quotes(text):
428 def strip_email_quotes(text):
433 """Strip leading email quotation characters ('>').
429 """Strip leading email quotation characters ('>').
434
430
435 Removes any combination of leading '>' interspersed with whitespace that
431 Removes any combination of leading '>' interspersed with whitespace that
436 appears *identically* in all lines of the input text.
432 appears *identically* in all lines of the input text.
437
433
438 Parameters
434 Parameters
439 ----------
435 ----------
440 text : str
436 text : str
441
437
442 Examples
438 Examples
443 --------
439 --------
444
440
445 Simple uses::
441 Simple uses::
446
442
447 In [2]: strip_email_quotes('> > text')
443 In [2]: strip_email_quotes('> > text')
448 Out[2]: 'text'
444 Out[2]: 'text'
449
445
450 In [3]: strip_email_quotes('> > text\\n> > more')
446 In [3]: strip_email_quotes('> > text\\n> > more')
451 Out[3]: 'text\\nmore'
447 Out[3]: 'text\\nmore'
452
448
453 Note how only the common prefix that appears in all lines is stripped::
449 Note how only the common prefix that appears in all lines is stripped::
454
450
455 In [4]: strip_email_quotes('> > text\\n> > more\\n> more...')
451 In [4]: strip_email_quotes('> > text\\n> > more\\n> more...')
456 Out[4]: '> text\\n> more\\nmore...'
452 Out[4]: '> text\\n> more\\nmore...'
457
453
458 So if any line has no quote marks ('>') , then none are stripped from any
454 So if any line has no quote marks ('>') , then none are stripped from any
459 of them ::
455 of them ::
460
456
461 In [5]: strip_email_quotes('> > text\\n> > more\\nlast different')
457 In [5]: strip_email_quotes('> > text\\n> > more\\nlast different')
462 Out[5]: '> > text\\n> > more\\nlast different'
458 Out[5]: '> > text\\n> > more\\nlast different'
463 """
459 """
464 lines = text.splitlines()
460 lines = text.splitlines()
465 matches = set()
461 matches = set()
466 for line in lines:
462 for line in lines:
467 prefix = re.match(r'^(\s*>[ >]*)', line)
463 prefix = re.match(r'^(\s*>[ >]*)', line)
468 if prefix:
464 if prefix:
469 matches.add(prefix.group(1))
465 matches.add(prefix.group(1))
470 else:
466 else:
471 break
467 break
472 else:
468 else:
473 prefix = long_substr(list(matches))
469 prefix = long_substr(list(matches))
474 if prefix:
470 if prefix:
475 strip = len(prefix)
471 strip = len(prefix)
476 text = '\n'.join([ ln[strip:] for ln in lines])
472 text = '\n'.join([ ln[strip:] for ln in lines])
477 return text
473 return text
478
474
479 def strip_ansi(source):
475 def strip_ansi(source):
480 """
476 """
481 Remove ansi escape codes from text.
477 Remove ansi escape codes from text.
482
478
483 Parameters
479 Parameters
484 ----------
480 ----------
485 source : str
481 source : str
486 Source to remove the ansi from
482 Source to remove the ansi from
487 """
483 """
488 return re.sub(r'\033\[(\d|;)+?m', '', source)
484 return re.sub(r'\033\[(\d|;)+?m', '', source)
489
485
490
486
491 class EvalFormatter(Formatter):
487 class EvalFormatter(Formatter):
492 """A String Formatter that allows evaluation of simple expressions.
488 """A String Formatter that allows evaluation of simple expressions.
493
489
494 Note that this version interprets a : as specifying a format string (as per
490 Note that this version interprets a : as specifying a format string (as per
495 standard string formatting), so if slicing is required, you must explicitly
491 standard string formatting), so if slicing is required, you must explicitly
496 create a slice.
492 create a slice.
497
493
498 This is to be used in templating cases, such as the parallel batch
494 This is to be used in templating cases, such as the parallel batch
499 script templates, where simple arithmetic on arguments is useful.
495 script templates, where simple arithmetic on arguments is useful.
500
496
501 Examples
497 Examples
502 --------
498 --------
503 ::
499 ::
504
500
505 In [1]: f = EvalFormatter()
501 In [1]: f = EvalFormatter()
506 In [2]: f.format('{n//4}', n=8)
502 In [2]: f.format('{n//4}', n=8)
507 Out[2]: '2'
503 Out[2]: '2'
508
504
509 In [3]: f.format("{greeting[slice(2,4)]}", greeting="Hello")
505 In [3]: f.format("{greeting[slice(2,4)]}", greeting="Hello")
510 Out[3]: 'll'
506 Out[3]: 'll'
511 """
507 """
512 def get_field(self, name, args, kwargs):
508 def get_field(self, name, args, kwargs):
513 v = eval(name, kwargs)
509 v = eval(name, kwargs)
514 return v, name
510 return v, name
515
511
516 #XXX: As of Python 3.4, the format string parsing no longer splits on a colon
512 #XXX: As of Python 3.4, the format string parsing no longer splits on a colon
517 # inside [], so EvalFormatter can handle slicing. Once we only support 3.4 and
513 # inside [], so EvalFormatter can handle slicing. Once we only support 3.4 and
518 # above, it should be possible to remove FullEvalFormatter.
514 # above, it should be possible to remove FullEvalFormatter.
519
515
520 class FullEvalFormatter(Formatter):
516 class FullEvalFormatter(Formatter):
521 """A String Formatter that allows evaluation of simple expressions.
517 """A String Formatter that allows evaluation of simple expressions.
522
518
523 Any time a format key is not found in the kwargs,
519 Any time a format key is not found in the kwargs,
524 it will be tried as an expression in the kwargs namespace.
520 it will be tried as an expression in the kwargs namespace.
525
521
526 Note that this version allows slicing using [1:2], so you cannot specify
522 Note that this version allows slicing using [1:2], so you cannot specify
527 a format string. Use :class:`EvalFormatter` to permit format strings.
523 a format string. Use :class:`EvalFormatter` to permit format strings.
528
524
529 Examples
525 Examples
530 --------
526 --------
531 ::
527 ::
532
528
533 In [1]: f = FullEvalFormatter()
529 In [1]: f = FullEvalFormatter()
534 In [2]: f.format('{n//4}', n=8)
530 In [2]: f.format('{n//4}', n=8)
535 Out[2]: '2'
531 Out[2]: '2'
536
532
537 In [3]: f.format('{list(range(5))[2:4]}')
533 In [3]: f.format('{list(range(5))[2:4]}')
538 Out[3]: '[2, 3]'
534 Out[3]: '[2, 3]'
539
535
540 In [4]: f.format('{3*2}')
536 In [4]: f.format('{3*2}')
541 Out[4]: '6'
537 Out[4]: '6'
542 """
538 """
543 # copied from Formatter._vformat with minor changes to allow eval
539 # copied from Formatter._vformat with minor changes to allow eval
544 # and replace the format_spec code with slicing
540 # and replace the format_spec code with slicing
545 def vformat(self, format_string, args, kwargs):
541 def vformat(self, format_string, args, kwargs):
546 result = []
542 result = []
547 for literal_text, field_name, format_spec, conversion in \
543 for literal_text, field_name, format_spec, conversion in \
548 self.parse(format_string):
544 self.parse(format_string):
549
545
550 # output the literal text
546 # output the literal text
551 if literal_text:
547 if literal_text:
552 result.append(literal_text)
548 result.append(literal_text)
553
549
554 # if there's a field, output it
550 # if there's a field, output it
555 if field_name is not None:
551 if field_name is not None:
556 # this is some markup, find the object and do
552 # this is some markup, find the object and do
557 # the formatting
553 # the formatting
558
554
559 if format_spec:
555 if format_spec:
560 # override format spec, to allow slicing:
556 # override format spec, to allow slicing:
561 field_name = ':'.join([field_name, format_spec])
557 field_name = ':'.join([field_name, format_spec])
562
558
563 # eval the contents of the field for the object
559 # eval the contents of the field for the object
564 # to be formatted
560 # to be formatted
565 obj = eval(field_name, kwargs)
561 obj = eval(field_name, kwargs)
566
562
567 # do any conversion on the resulting object
563 # do any conversion on the resulting object
568 obj = self.convert_field(obj, conversion)
564 obj = self.convert_field(obj, conversion)
569
565
570 # format the object and append to the result
566 # format the object and append to the result
571 result.append(self.format_field(obj, ''))
567 result.append(self.format_field(obj, ''))
572
568
573 return ''.join(py3compat.cast_unicode(s) for s in result)
569 return ''.join(py3compat.cast_unicode(s) for s in result)
574
570
575
571
576 class DollarFormatter(FullEvalFormatter):
572 class DollarFormatter(FullEvalFormatter):
577 """Formatter allowing Itpl style $foo replacement, for names and attribute
573 """Formatter allowing Itpl style $foo replacement, for names and attribute
578 access only. Standard {foo} replacement also works, and allows full
574 access only. Standard {foo} replacement also works, and allows full
579 evaluation of its arguments.
575 evaluation of its arguments.
580
576
581 Examples
577 Examples
582 --------
578 --------
583 ::
579 ::
584
580
585 In [1]: f = DollarFormatter()
581 In [1]: f = DollarFormatter()
586 In [2]: f.format('{n//4}', n=8)
582 In [2]: f.format('{n//4}', n=8)
587 Out[2]: '2'
583 Out[2]: '2'
588
584
589 In [3]: f.format('23 * 76 is $result', result=23*76)
585 In [3]: f.format('23 * 76 is $result', result=23*76)
590 Out[3]: '23 * 76 is 1748'
586 Out[3]: '23 * 76 is 1748'
591
587
592 In [4]: f.format('$a or {b}', a=1, b=2)
588 In [4]: f.format('$a or {b}', a=1, b=2)
593 Out[4]: '1 or 2'
589 Out[4]: '1 or 2'
594 """
590 """
595 _dollar_pattern_ignore_single_quote = re.compile("(.*?)\$(\$?[\w\.]+)(?=([^']*'[^']*')*[^']*$)")
591 _dollar_pattern_ignore_single_quote = re.compile("(.*?)\$(\$?[\w\.]+)(?=([^']*'[^']*')*[^']*$)")
596 def parse(self, fmt_string):
592 def parse(self, fmt_string):
597 for literal_txt, field_name, format_spec, conversion \
593 for literal_txt, field_name, format_spec, conversion \
598 in Formatter.parse(self, fmt_string):
594 in Formatter.parse(self, fmt_string):
599
595
600 # Find $foo patterns in the literal text.
596 # Find $foo patterns in the literal text.
601 continue_from = 0
597 continue_from = 0
602 txt = ""
598 txt = ""
603 for m in self._dollar_pattern_ignore_single_quote.finditer(literal_txt):
599 for m in self._dollar_pattern_ignore_single_quote.finditer(literal_txt):
604 new_txt, new_field = m.group(1,2)
600 new_txt, new_field = m.group(1,2)
605 # $$foo --> $foo
601 # $$foo --> $foo
606 if new_field.startswith("$"):
602 if new_field.startswith("$"):
607 txt += new_txt + new_field
603 txt += new_txt + new_field
608 else:
604 else:
609 yield (txt + new_txt, new_field, "", None)
605 yield (txt + new_txt, new_field, "", None)
610 txt = ""
606 txt = ""
611 continue_from = m.end()
607 continue_from = m.end()
612
608
613 # Re-yield the {foo} style pattern
609 # Re-yield the {foo} style pattern
614 yield (txt + literal_txt[continue_from:], field_name, format_spec, conversion)
610 yield (txt + literal_txt[continue_from:], field_name, format_spec, conversion)
615
611
616 #-----------------------------------------------------------------------------
612 #-----------------------------------------------------------------------------
617 # Utils to columnize a list of string
613 # Utils to columnize a list of string
618 #-----------------------------------------------------------------------------
614 #-----------------------------------------------------------------------------
619
615
620 def _col_chunks(l, max_rows, row_first=False):
616 def _col_chunks(l, max_rows, row_first=False):
621 """Yield successive max_rows-sized column chunks from l."""
617 """Yield successive max_rows-sized column chunks from l."""
622 if row_first:
618 if row_first:
623 ncols = (len(l) // max_rows) + (len(l) % max_rows > 0)
619 ncols = (len(l) // max_rows) + (len(l) % max_rows > 0)
624 for i in range(ncols):
620 for i in range(ncols):
625 yield [l[j] for j in range(i, len(l), ncols)]
621 yield [l[j] for j in range(i, len(l), ncols)]
626 else:
622 else:
627 for i in range(0, len(l), max_rows):
623 for i in range(0, len(l), max_rows):
628 yield l[i:(i + max_rows)]
624 yield l[i:(i + max_rows)]
629
625
630
626
631 def _find_optimal(rlist, row_first=False, separator_size=2, displaywidth=80):
627 def _find_optimal(rlist, row_first=False, separator_size=2, displaywidth=80):
632 """Calculate optimal info to columnize a list of string"""
628 """Calculate optimal info to columnize a list of string"""
633 for max_rows in range(1, len(rlist) + 1):
629 for max_rows in range(1, len(rlist) + 1):
634 col_widths = list(map(max, _col_chunks(rlist, max_rows, row_first)))
630 col_widths = list(map(max, _col_chunks(rlist, max_rows, row_first)))
635 sumlength = sum(col_widths)
631 sumlength = sum(col_widths)
636 ncols = len(col_widths)
632 ncols = len(col_widths)
637 if sumlength + separator_size * (ncols - 1) <= displaywidth:
633 if sumlength + separator_size * (ncols - 1) <= displaywidth:
638 break
634 break
639 return {'num_columns': ncols,
635 return {'num_columns': ncols,
640 'optimal_separator_width': (displaywidth - sumlength) // (ncols - 1) if (ncols - 1) else 0,
636 'optimal_separator_width': (displaywidth - sumlength) // (ncols - 1) if (ncols - 1) else 0,
641 'max_rows': max_rows,
637 'max_rows': max_rows,
642 'column_widths': col_widths
638 'column_widths': col_widths
643 }
639 }
644
640
645
641
646 def _get_or_default(mylist, i, default=None):
642 def _get_or_default(mylist, i, default=None):
647 """return list item number, or default if don't exist"""
643 """return list item number, or default if don't exist"""
648 if i >= len(mylist):
644 if i >= len(mylist):
649 return default
645 return default
650 else :
646 else :
651 return mylist[i]
647 return mylist[i]
652
648
653
649
654 def compute_item_matrix(items, row_first=False, empty=None, *args, **kwargs) :
650 def compute_item_matrix(items, row_first=False, empty=None, *args, **kwargs) :
655 """Returns a nested list, and info to columnize items
651 """Returns a nested list, and info to columnize items
656
652
657 Parameters
653 Parameters
658 ----------
654 ----------
659
655
660 items
656 items
661 list of strings to columize
657 list of strings to columize
662 row_first : (default False)
658 row_first : (default False)
663 Whether to compute columns for a row-first matrix instead of
659 Whether to compute columns for a row-first matrix instead of
664 column-first (default).
660 column-first (default).
665 empty : (default None)
661 empty : (default None)
666 default value to fill list if needed
662 default value to fill list if needed
667 separator_size : int (default=2)
663 separator_size : int (default=2)
668 How much caracters will be used as a separation between each columns.
664 How much caracters will be used as a separation between each columns.
669 displaywidth : int (default=80)
665 displaywidth : int (default=80)
670 The width of the area onto which the columns should enter
666 The width of the area onto which the columns should enter
671
667
672 Returns
668 Returns
673 -------
669 -------
674
670
675 strings_matrix
671 strings_matrix
676
672
677 nested list of string, the outer most list contains as many list as
673 nested list of string, the outer most list contains as many list as
678 rows, the innermost lists have each as many element as columns. If the
674 rows, the innermost lists have each as many element as columns. If the
679 total number of elements in `items` does not equal the product of
675 total number of elements in `items` does not equal the product of
680 rows*columns, the last element of some lists are filled with `None`.
676 rows*columns, the last element of some lists are filled with `None`.
681
677
682 dict_info
678 dict_info
683 some info to make columnize easier:
679 some info to make columnize easier:
684
680
685 num_columns
681 num_columns
686 number of columns
682 number of columns
687 max_rows
683 max_rows
688 maximum number of rows (final number may be less)
684 maximum number of rows (final number may be less)
689 column_widths
685 column_widths
690 list of with of each columns
686 list of with of each columns
691 optimal_separator_width
687 optimal_separator_width
692 best separator width between columns
688 best separator width between columns
693
689
694 Examples
690 Examples
695 --------
691 --------
696 ::
692 ::
697
693
698 In [1]: l = ['aaa','b','cc','d','eeeee','f','g','h','i','j','k','l']
694 In [1]: l = ['aaa','b','cc','d','eeeee','f','g','h','i','j','k','l']
699 In [2]: list, info = compute_item_matrix(l, displaywidth=12)
695 In [2]: list, info = compute_item_matrix(l, displaywidth=12)
700 In [3]: list
696 In [3]: list
701 Out[3]: [['aaa', 'f', 'k'], ['b', 'g', 'l'], ['cc', 'h', None], ['d', 'i', None], ['eeeee', 'j', None]]
697 Out[3]: [['aaa', 'f', 'k'], ['b', 'g', 'l'], ['cc', 'h', None], ['d', 'i', None], ['eeeee', 'j', None]]
702 In [4]: ideal = {'num_columns': 3, 'column_widths': [5, 1, 1], 'optimal_separator_width': 2, 'max_rows': 5}
698 In [4]: ideal = {'num_columns': 3, 'column_widths': [5, 1, 1], 'optimal_separator_width': 2, 'max_rows': 5}
703 In [5]: all((info[k] == ideal[k] for k in ideal.keys()))
699 In [5]: all((info[k] == ideal[k] for k in ideal.keys()))
704 Out[5]: True
700 Out[5]: True
705 """
701 """
706 info = _find_optimal(list(map(len, items)), row_first, *args, **kwargs)
702 info = _find_optimal(list(map(len, items)), row_first, *args, **kwargs)
707 nrow, ncol = info['max_rows'], info['num_columns']
703 nrow, ncol = info['max_rows'], info['num_columns']
708 if row_first:
704 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)
705 return ([[_get_or_default(items, r * ncol + c, default=empty) for c in range(ncol)] for r in range(nrow)], info)
710 else:
706 else:
711 return ([[_get_or_default(items, c * nrow + r, default=empty) for c in range(ncol)] for r in range(nrow)], info)
707 return ([[_get_or_default(items, c * nrow + r, default=empty) for c in range(ncol)] for r in range(nrow)], info)
712
708
713
709
714 def columnize(items, row_first=False, separator=' ', displaywidth=80, spread=False):
710 def columnize(items, row_first=False, separator=' ', displaywidth=80, spread=False):
715 """ Transform a list of strings into a single string with columns.
711 """ Transform a list of strings into a single string with columns.
716
712
717 Parameters
713 Parameters
718 ----------
714 ----------
719 items : sequence of strings
715 items : sequence of strings
720 The strings to process.
716 The strings to process.
721
717
722 row_first : (default False)
718 row_first : (default False)
723 Whether to compute columns for a row-first matrix instead of
719 Whether to compute columns for a row-first matrix instead of
724 column-first (default).
720 column-first (default).
725
721
726 separator : str, optional [default is two spaces]
722 separator : str, optional [default is two spaces]
727 The string that separates columns.
723 The string that separates columns.
728
724
729 displaywidth : int, optional [default is 80]
725 displaywidth : int, optional [default is 80]
730 Width of the display in number of characters.
726 Width of the display in number of characters.
731
727
732 Returns
728 Returns
733 -------
729 -------
734 The formatted string.
730 The formatted string.
735 """
731 """
736 if not items:
732 if not items:
737 return '\n'
733 return '\n'
738 matrix, info = compute_item_matrix(items, row_first=row_first, separator_size=len(separator), displaywidth=displaywidth)
734 matrix, info = compute_item_matrix(items, row_first=row_first, separator_size=len(separator), displaywidth=displaywidth)
739 if spread:
735 if spread:
740 separator = separator.ljust(int(info['optimal_separator_width']))
736 separator = separator.ljust(int(info['optimal_separator_width']))
741 fmatrix = [filter(None, x) for x in matrix]
737 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'])])
738 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'
739 return '\n'.join(map(sjoin, fmatrix))+'\n'
744
740
745
741
746 def get_text_list(list_, last_sep=' and ', sep=", ", wrap_item_with=""):
742 def get_text_list(list_, last_sep=' and ', sep=", ", wrap_item_with=""):
747 """
743 """
748 Return a string with a natural enumeration of items
744 Return a string with a natural enumeration of items
749
745
750 >>> get_text_list(['a', 'b', 'c', 'd'])
746 >>> get_text_list(['a', 'b', 'c', 'd'])
751 'a, b, c and d'
747 'a, b, c and d'
752 >>> get_text_list(['a', 'b', 'c'], ' or ')
748 >>> get_text_list(['a', 'b', 'c'], ' or ')
753 'a, b or c'
749 'a, b or c'
754 >>> get_text_list(['a', 'b', 'c'], ', ')
750 >>> get_text_list(['a', 'b', 'c'], ', ')
755 'a, b, c'
751 'a, b, c'
756 >>> get_text_list(['a', 'b'], ' or ')
752 >>> get_text_list(['a', 'b'], ' or ')
757 'a or b'
753 'a or b'
758 >>> get_text_list(['a'])
754 >>> get_text_list(['a'])
759 'a'
755 'a'
760 >>> get_text_list([])
756 >>> get_text_list([])
761 ''
757 ''
762 >>> get_text_list(['a', 'b'], wrap_item_with="`")
758 >>> get_text_list(['a', 'b'], wrap_item_with="`")
763 '`a` and `b`'
759 '`a` and `b`'
764 >>> get_text_list(['a', 'b', 'c', 'd'], " = ", sep=" + ")
760 >>> get_text_list(['a', 'b', 'c', 'd'], " = ", sep=" + ")
765 'a + b + c = d'
761 'a + b + c = d'
766 """
762 """
767 if len(list_) == 0:
763 if len(list_) == 0:
768 return ''
764 return ''
769 if wrap_item_with:
765 if wrap_item_with:
770 list_ = ['%s%s%s' % (wrap_item_with, item, wrap_item_with) for
766 list_ = ['%s%s%s' % (wrap_item_with, item, wrap_item_with) for
771 item in list_]
767 item in list_]
772 if len(list_) == 1:
768 if len(list_) == 1:
773 return list_[0]
769 return list_[0]
774 return '%s%s%s' % (
770 return '%s%s%s' % (
775 sep.join(i for i in list_[:-1]),
771 sep.join(i for i in list_[:-1]),
776 last_sep, list_[-1])
772 last_sep, list_[-1])
General Comments 0
You need to be logged in to leave comments. Login now