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