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