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