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