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