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