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