##// END OF EJS Templates
Add DollarFormatter and tests.
Thomas Kluyver -
Show More
@@ -1,87 +1,103 b''
1 1 # encoding: utf-8
2 2 """Tests for IPython.utils.text"""
3 3
4 4 #-----------------------------------------------------------------------------
5 5 # Copyright (C) 2011 The IPython Development Team
6 6 #
7 7 # Distributed under the terms of the BSD License. The full license is in
8 8 # the file COPYING, distributed as part of this software.
9 9 #-----------------------------------------------------------------------------
10 10
11 11 #-----------------------------------------------------------------------------
12 12 # Imports
13 13 #-----------------------------------------------------------------------------
14 14
15 15 import os
16 16 import math
17 17
18 18 import nose.tools as nt
19 19
20 20 from nose import with_setup
21 21
22 22 from IPython.testing import decorators as dec
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_equals(out, 'aaaaa bbbbb ccccc\n')
35 35 out = text.columnize(items, displaywidth=10)
36 36 nt.assert_equals(out, 'aaaaa ccccc\nbbbbb\n')
37 37
38 38
39 39 def test_columnize_long():
40 40 """Test columnize with inputs longer than the display window"""
41 41 text.columnize(['a'*81, 'b'*81], displaywidth=80)
42 42 size = 11
43 43 items = [l*size for l in 'abc']
44 44 out = text.columnize(items, displaywidth=size-1)
45 45 nt.assert_equals(out, '\n'.join(items+['']))
46 46
47 def test_eval_formatter():
48 f = text.EvalFormatter()
47 def eval_formatter_check(f):
49 48 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
50 49 s = f.format("{n} {n//4} {stuff.split()[0]}", **ns)
51 50 nt.assert_equals(s, "12 3 hello")
52 51 s = f.format(' '.join(['{n//%i}'%i for i in range(1,8)]), **ns)
53 52 nt.assert_equals(s, "12 6 4 3 2 2 1")
54 53 s = f.format('{[n//i for i in range(1,8)]}', **ns)
55 54 nt.assert_equals(s, "[12, 6, 4, 3, 2, 2, 1]")
56 55 s = f.format("{stuff!s}", **ns)
57 56 nt.assert_equals(s, ns['stuff'])
58 57 s = f.format("{stuff!r}", **ns)
59 58 nt.assert_equals(s, repr(ns['stuff']))
60 59
61 60 nt.assert_raises(NameError, f.format, '{dne}', **ns)
62 61
63
64 def test_eval_formatter_slicing():
65 f = text.EvalFormatter()
66 f.allow_slicing = True
62 def eval_formatter_slicing_check(f):
67 63 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
68 64 s = f.format(" {stuff.split()[:]} ", **ns)
69 65 nt.assert_equals(s, " ['hello', 'there'] ")
70 66 s = f.format(" {stuff.split()[::-1]} ", **ns)
71 67 nt.assert_equals(s, " ['there', 'hello'] ")
72 68 s = f.format("{stuff[::2]}", **ns)
73 69 nt.assert_equals(s, ns['stuff'][::2])
74 70
75 71 nt.assert_raises(SyntaxError, f.format, "{n:x}", **ns)
76 72
77 73
78 def test_eval_formatter_no_slicing():
79 f = text.EvalFormatter()
80 f.allow_slicing = False
74 def eval_formatter_no_slicing_check(f):
81 75 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
82 76
83 77 s = f.format('{n:x} {pi**2:+f}', **ns)
84 78 nt.assert_equals(s, "c +9.869604")
85 79
86 80 nt.assert_raises(SyntaxError, f.format, "{a[:]}")
87 81
82 def test_eval_formatter():
83 f = text.EvalFormatter()
84 eval_formatter_check(f)
85 eval_formatter_no_slicing_check(f)
86
87 def test_full_eval_formatter():
88 f = text.FullEvalFormatter()
89 eval_formatter_check(f)
90 eval_formatter_slicing_check(f)
91
92 def test_dollar_formatter():
93 f = text.DollarFormatter()
94 eval_formatter_check(f)
95 eval_formatter_slicing_check(f)
96
97 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
98 s = f.format("$n", **ns)
99 nt.assert_equals(s, "12")
100 s = f.format("$n.real", **ns)
101 nt.assert_equals(s, "12")
102 s = f.format("$n/{stuff[:5]}", **ns)
103 nt.assert_equals(s, "12/hello")
@@ -1,753 +1,785 b''
1 1 # encoding: utf-8
2 2 """
3 3 Utilities for working with strings and text.
4 4 """
5 5
6 6 #-----------------------------------------------------------------------------
7 7 # Copyright (C) 2008-2009 The IPython Development Team
8 8 #
9 9 # Distributed under the terms of the BSD License. The full license is in
10 10 # the file COPYING, distributed as part of this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16
17 17 import __main__
18 18
19 19 import locale
20 20 import os
21 21 import re
22 22 import shutil
23 23 import sys
24 24 import textwrap
25 25 from string import Formatter
26 26
27 27 from IPython.external.path import path
28 28 from IPython.utils import py3compat
29 29 from IPython.utils.io import nlprint
30 30 from IPython.utils.data import flatten
31 31
32 32 #-----------------------------------------------------------------------------
33 33 # Code
34 34 #-----------------------------------------------------------------------------
35 35
36 36 # Less conservative replacement for sys.getdefaultencoding, that will try
37 37 # to match the environment.
38 38 # Defined here as central function, so if we find better choices, we
39 39 # won't need to make changes all over IPython.
40 40 def getdefaultencoding():
41 41 """Return IPython's guess for the default encoding for bytes as text.
42 42
43 43 Asks for stdin.encoding first, to match the calling Terminal, but that
44 44 is often None for subprocesses. Fall back on locale.getpreferredencoding()
45 45 which should be a sensible platform default (that respects LANG environment),
46 46 and finally to sys.getdefaultencoding() which is the most conservative option,
47 47 and usually ASCII.
48 48 """
49 49 enc = sys.stdin.encoding
50 50 if not enc or enc=='ascii':
51 51 try:
52 52 # There are reports of getpreferredencoding raising errors
53 53 # in some cases, which may well be fixed, but let's be conservative here.
54 54 enc = locale.getpreferredencoding()
55 55 except Exception:
56 56 pass
57 57 return enc or sys.getdefaultencoding()
58 58
59 59 def unquote_ends(istr):
60 60 """Remove a single pair of quotes from the endpoints of a string."""
61 61
62 62 if not istr:
63 63 return istr
64 64 if (istr[0]=="'" and istr[-1]=="'") or \
65 65 (istr[0]=='"' and istr[-1]=='"'):
66 66 return istr[1:-1]
67 67 else:
68 68 return istr
69 69
70 70
71 71 class LSString(str):
72 72 """String derivative with a special access attributes.
73 73
74 74 These are normal strings, but with the special attributes:
75 75
76 76 .l (or .list) : value as list (split on newlines).
77 77 .n (or .nlstr): original value (the string itself).
78 78 .s (or .spstr): value as whitespace-separated string.
79 79 .p (or .paths): list of path objects
80 80
81 81 Any values which require transformations are computed only once and
82 82 cached.
83 83
84 84 Such strings are very useful to efficiently interact with the shell, which
85 85 typically only understands whitespace-separated options for commands."""
86 86
87 87 def get_list(self):
88 88 try:
89 89 return self.__list
90 90 except AttributeError:
91 91 self.__list = self.split('\n')
92 92 return self.__list
93 93
94 94 l = list = property(get_list)
95 95
96 96 def get_spstr(self):
97 97 try:
98 98 return self.__spstr
99 99 except AttributeError:
100 100 self.__spstr = self.replace('\n',' ')
101 101 return self.__spstr
102 102
103 103 s = spstr = property(get_spstr)
104 104
105 105 def get_nlstr(self):
106 106 return self
107 107
108 108 n = nlstr = property(get_nlstr)
109 109
110 110 def get_paths(self):
111 111 try:
112 112 return self.__paths
113 113 except AttributeError:
114 114 self.__paths = [path(p) for p in self.split('\n') if os.path.exists(p)]
115 115 return self.__paths
116 116
117 117 p = paths = property(get_paths)
118 118
119 119 # FIXME: We need to reimplement type specific displayhook and then add this
120 120 # back as a custom printer. This should also be moved outside utils into the
121 121 # core.
122 122
123 123 # def print_lsstring(arg):
124 124 # """ Prettier (non-repr-like) and more informative printer for LSString """
125 125 # print "LSString (.p, .n, .l, .s available). Value:"
126 126 # print arg
127 127 #
128 128 #
129 129 # print_lsstring = result_display.when_type(LSString)(print_lsstring)
130 130
131 131
132 132 class SList(list):
133 133 """List derivative with a special access attributes.
134 134
135 135 These are normal lists, but with the special attributes:
136 136
137 137 .l (or .list) : value as list (the list itself).
138 138 .n (or .nlstr): value as a string, joined on newlines.
139 139 .s (or .spstr): value as a string, joined on spaces.
140 140 .p (or .paths): list of path objects
141 141
142 142 Any values which require transformations are computed only once and
143 143 cached."""
144 144
145 145 def get_list(self):
146 146 return self
147 147
148 148 l = list = property(get_list)
149 149
150 150 def get_spstr(self):
151 151 try:
152 152 return self.__spstr
153 153 except AttributeError:
154 154 self.__spstr = ' '.join(self)
155 155 return self.__spstr
156 156
157 157 s = spstr = property(get_spstr)
158 158
159 159 def get_nlstr(self):
160 160 try:
161 161 return self.__nlstr
162 162 except AttributeError:
163 163 self.__nlstr = '\n'.join(self)
164 164 return self.__nlstr
165 165
166 166 n = nlstr = property(get_nlstr)
167 167
168 168 def get_paths(self):
169 169 try:
170 170 return self.__paths
171 171 except AttributeError:
172 172 self.__paths = [path(p) for p in self if os.path.exists(p)]
173 173 return self.__paths
174 174
175 175 p = paths = property(get_paths)
176 176
177 177 def grep(self, pattern, prune = False, field = None):
178 178 """ Return all strings matching 'pattern' (a regex or callable)
179 179
180 180 This is case-insensitive. If prune is true, return all items
181 181 NOT matching the pattern.
182 182
183 183 If field is specified, the match must occur in the specified
184 184 whitespace-separated field.
185 185
186 186 Examples::
187 187
188 188 a.grep( lambda x: x.startswith('C') )
189 189 a.grep('Cha.*log', prune=1)
190 190 a.grep('chm', field=-1)
191 191 """
192 192
193 193 def match_target(s):
194 194 if field is None:
195 195 return s
196 196 parts = s.split()
197 197 try:
198 198 tgt = parts[field]
199 199 return tgt
200 200 except IndexError:
201 201 return ""
202 202
203 203 if isinstance(pattern, basestring):
204 204 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
205 205 else:
206 206 pred = pattern
207 207 if not prune:
208 208 return SList([el for el in self if pred(match_target(el))])
209 209 else:
210 210 return SList([el for el in self if not pred(match_target(el))])
211 211
212 212 def fields(self, *fields):
213 213 """ Collect whitespace-separated fields from string list
214 214
215 215 Allows quick awk-like usage of string lists.
216 216
217 217 Example data (in var a, created by 'a = !ls -l')::
218 218 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
219 219 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
220 220
221 221 a.fields(0) is ['-rwxrwxrwx', 'drwxrwxrwx+']
222 222 a.fields(1,0) is ['1 -rwxrwxrwx', '6 drwxrwxrwx+']
223 223 (note the joining by space).
224 224 a.fields(-1) is ['ChangeLog', 'IPython']
225 225
226 226 IndexErrors are ignored.
227 227
228 228 Without args, fields() just split()'s the strings.
229 229 """
230 230 if len(fields) == 0:
231 231 return [el.split() for el in self]
232 232
233 233 res = SList()
234 234 for el in [f.split() for f in self]:
235 235 lineparts = []
236 236
237 237 for fd in fields:
238 238 try:
239 239 lineparts.append(el[fd])
240 240 except IndexError:
241 241 pass
242 242 if lineparts:
243 243 res.append(" ".join(lineparts))
244 244
245 245 return res
246 246
247 247 def sort(self,field= None, nums = False):
248 248 """ sort by specified fields (see fields())
249 249
250 250 Example::
251 251 a.sort(1, nums = True)
252 252
253 253 Sorts a by second field, in numerical order (so that 21 > 3)
254 254
255 255 """
256 256
257 257 #decorate, sort, undecorate
258 258 if field is not None:
259 259 dsu = [[SList([line]).fields(field), line] for line in self]
260 260 else:
261 261 dsu = [[line, line] for line in self]
262 262 if nums:
263 263 for i in range(len(dsu)):
264 264 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
265 265 try:
266 266 n = int(numstr)
267 267 except ValueError:
268 268 n = 0;
269 269 dsu[i][0] = n
270 270
271 271
272 272 dsu.sort()
273 273 return SList([t[1] for t in dsu])
274 274
275 275
276 276 # FIXME: We need to reimplement type specific displayhook and then add this
277 277 # back as a custom printer. This should also be moved outside utils into the
278 278 # core.
279 279
280 280 # def print_slist(arg):
281 281 # """ Prettier (non-repr-like) and more informative printer for SList """
282 282 # print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):"
283 283 # if hasattr(arg, 'hideonce') and arg.hideonce:
284 284 # arg.hideonce = False
285 285 # return
286 286 #
287 287 # nlprint(arg)
288 288 #
289 289 # print_slist = result_display.when_type(SList)(print_slist)
290 290
291 291
292 292 def esc_quotes(strng):
293 293 """Return the input string with single and double quotes escaped out"""
294 294
295 295 return strng.replace('"','\\"').replace("'","\\'")
296 296
297 297
298 298 def make_quoted_expr(s):
299 299 """Return string s in appropriate quotes, using raw string if possible.
300 300
301 301 XXX - example removed because it caused encoding errors in documentation
302 302 generation. We need a new example that doesn't contain invalid chars.
303 303
304 304 Note the use of raw string and padding at the end to allow trailing
305 305 backslash.
306 306 """
307 307
308 308 tail = ''
309 309 tailpadding = ''
310 310 raw = ''
311 311 ucode = '' if py3compat.PY3 else 'u'
312 312 if "\\" in s:
313 313 raw = 'r'
314 314 if s.endswith('\\'):
315 315 tail = '[:-1]'
316 316 tailpadding = '_'
317 317 if '"' not in s:
318 318 quote = '"'
319 319 elif "'" not in s:
320 320 quote = "'"
321 321 elif '"""' not in s and not s.endswith('"'):
322 322 quote = '"""'
323 323 elif "'''" not in s and not s.endswith("'"):
324 324 quote = "'''"
325 325 else:
326 326 # give up, backslash-escaped string will do
327 327 return '"%s"' % esc_quotes(s)
328 328 res = ucode + raw + quote + s + tailpadding + quote + tail
329 329 return res
330 330
331 331
332 332 def qw(words,flat=0,sep=None,maxsplit=-1):
333 333 """Similar to Perl's qw() operator, but with some more options.
334 334
335 335 qw(words,flat=0,sep=' ',maxsplit=-1) -> words.split(sep,maxsplit)
336 336
337 337 words can also be a list itself, and with flat=1, the output will be
338 338 recursively flattened.
339 339
340 340 Examples:
341 341
342 342 >>> qw('1 2')
343 343 ['1', '2']
344 344
345 345 >>> qw(['a b','1 2',['m n','p q']])
346 346 [['a', 'b'], ['1', '2'], [['m', 'n'], ['p', 'q']]]
347 347
348 348 >>> qw(['a b','1 2',['m n','p q']],flat=1)
349 349 ['a', 'b', '1', '2', 'm', 'n', 'p', 'q']
350 350 """
351 351
352 352 if isinstance(words, basestring):
353 353 return [word.strip() for word in words.split(sep,maxsplit)
354 354 if word and not word.isspace() ]
355 355 if flat:
356 356 return flatten(map(qw,words,[1]*len(words)))
357 357 return map(qw,words)
358 358
359 359
360 360 def qwflat(words,sep=None,maxsplit=-1):
361 361 """Calls qw(words) in flat mode. It's just a convenient shorthand."""
362 362 return qw(words,1,sep,maxsplit)
363 363
364 364
365 365 def qw_lol(indata):
366 366 """qw_lol('a b') -> [['a','b']],
367 367 otherwise it's just a call to qw().
368 368
369 369 We need this to make sure the modules_some keys *always* end up as a
370 370 list of lists."""
371 371
372 372 if isinstance(indata, basestring):
373 373 return [qw(indata)]
374 374 else:
375 375 return qw(indata)
376 376
377 377
378 378 def grep(pat,list,case=1):
379 379 """Simple minded grep-like function.
380 380 grep(pat,list) returns occurrences of pat in list, None on failure.
381 381
382 382 It only does simple string matching, with no support for regexps. Use the
383 383 option case=0 for case-insensitive matching."""
384 384
385 385 # This is pretty crude. At least it should implement copying only references
386 386 # to the original data in case it's big. Now it copies the data for output.
387 387 out=[]
388 388 if case:
389 389 for term in list:
390 390 if term.find(pat)>-1: out.append(term)
391 391 else:
392 392 lpat=pat.lower()
393 393 for term in list:
394 394 if term.lower().find(lpat)>-1: out.append(term)
395 395
396 396 if len(out): return out
397 397 else: return None
398 398
399 399
400 400 def dgrep(pat,*opts):
401 401 """Return grep() on dir()+dir(__builtins__).
402 402
403 403 A very common use of grep() when working interactively."""
404 404
405 405 return grep(pat,dir(__main__)+dir(__main__.__builtins__),*opts)
406 406
407 407
408 408 def idgrep(pat):
409 409 """Case-insensitive dgrep()"""
410 410
411 411 return dgrep(pat,0)
412 412
413 413
414 414 def igrep(pat,list):
415 415 """Synonym for case-insensitive grep."""
416 416
417 417 return grep(pat,list,case=0)
418 418
419 419
420 420 def indent(instr,nspaces=4, ntabs=0, flatten=False):
421 421 """Indent a string a given number of spaces or tabstops.
422 422
423 423 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
424 424
425 425 Parameters
426 426 ----------
427 427
428 428 instr : basestring
429 429 The string to be indented.
430 430 nspaces : int (default: 4)
431 431 The number of spaces to be indented.
432 432 ntabs : int (default: 0)
433 433 The number of tabs to be indented.
434 434 flatten : bool (default: False)
435 435 Whether to scrub existing indentation. If True, all lines will be
436 436 aligned to the same indentation. If False, existing indentation will
437 437 be strictly increased.
438 438
439 439 Returns
440 440 -------
441 441
442 442 str|unicode : string indented by ntabs and nspaces.
443 443
444 444 """
445 445 if instr is None:
446 446 return
447 447 ind = '\t'*ntabs+' '*nspaces
448 448 if flatten:
449 449 pat = re.compile(r'^\s*', re.MULTILINE)
450 450 else:
451 451 pat = re.compile(r'^', re.MULTILINE)
452 452 outstr = re.sub(pat, ind, instr)
453 453 if outstr.endswith(os.linesep+ind):
454 454 return outstr[:-len(ind)]
455 455 else:
456 456 return outstr
457 457
458 458 def native_line_ends(filename,backup=1):
459 459 """Convert (in-place) a file to line-ends native to the current OS.
460 460
461 461 If the optional backup argument is given as false, no backup of the
462 462 original file is left. """
463 463
464 464 backup_suffixes = {'posix':'~','dos':'.bak','nt':'.bak','mac':'.bak'}
465 465
466 466 bak_filename = filename + backup_suffixes[os.name]
467 467
468 468 original = open(filename).read()
469 469 shutil.copy2(filename,bak_filename)
470 470 try:
471 471 new = open(filename,'wb')
472 472 new.write(os.linesep.join(original.splitlines()))
473 473 new.write(os.linesep) # ALWAYS put an eol at the end of the file
474 474 new.close()
475 475 except:
476 476 os.rename(bak_filename,filename)
477 477 if not backup:
478 478 try:
479 479 os.remove(bak_filename)
480 480 except:
481 481 pass
482 482
483 483
484 484 def list_strings(arg):
485 485 """Always return a list of strings, given a string or list of strings
486 486 as input.
487 487
488 488 :Examples:
489 489
490 490 In [7]: list_strings('A single string')
491 491 Out[7]: ['A single string']
492 492
493 493 In [8]: list_strings(['A single string in a list'])
494 494 Out[8]: ['A single string in a list']
495 495
496 496 In [9]: list_strings(['A','list','of','strings'])
497 497 Out[9]: ['A', 'list', 'of', 'strings']
498 498 """
499 499
500 500 if isinstance(arg,basestring): return [arg]
501 501 else: return arg
502 502
503 503
504 504 def marquee(txt='',width=78,mark='*'):
505 505 """Return the input string centered in a 'marquee'.
506 506
507 507 :Examples:
508 508
509 509 In [16]: marquee('A test',40)
510 510 Out[16]: '**************** A test ****************'
511 511
512 512 In [17]: marquee('A test',40,'-')
513 513 Out[17]: '---------------- A test ----------------'
514 514
515 515 In [18]: marquee('A test',40,' ')
516 516 Out[18]: ' A test '
517 517
518 518 """
519 519 if not txt:
520 520 return (mark*width)[:width]
521 521 nmark = (width-len(txt)-2)//len(mark)//2
522 522 if nmark < 0: nmark =0
523 523 marks = mark*nmark
524 524 return '%s %s %s' % (marks,txt,marks)
525 525
526 526
527 527 ini_spaces_re = re.compile(r'^(\s+)')
528 528
529 529 def num_ini_spaces(strng):
530 530 """Return the number of initial spaces in a string"""
531 531
532 532 ini_spaces = ini_spaces_re.match(strng)
533 533 if ini_spaces:
534 534 return ini_spaces.end()
535 535 else:
536 536 return 0
537 537
538 538
539 539 def format_screen(strng):
540 540 """Format a string for screen printing.
541 541
542 542 This removes some latex-type format codes."""
543 543 # Paragraph continue
544 544 par_re = re.compile(r'\\$',re.MULTILINE)
545 545 strng = par_re.sub('',strng)
546 546 return strng
547 547
548 548 def dedent(text):
549 549 """Equivalent of textwrap.dedent that ignores unindented first line.
550 550
551 551 This means it will still dedent strings like:
552 552 '''foo
553 553 is a bar
554 554 '''
555 555
556 556 For use in wrap_paragraphs.
557 557 """
558 558
559 559 if text.startswith('\n'):
560 560 # text starts with blank line, don't ignore the first line
561 561 return textwrap.dedent(text)
562 562
563 563 # split first line
564 564 splits = text.split('\n',1)
565 565 if len(splits) == 1:
566 566 # only one line
567 567 return textwrap.dedent(text)
568 568
569 569 first, rest = splits
570 570 # dedent everything but the first line
571 571 rest = textwrap.dedent(rest)
572 572 return '\n'.join([first, rest])
573 573
574 574 def wrap_paragraphs(text, ncols=80):
575 575 """Wrap multiple paragraphs to fit a specified width.
576 576
577 577 This is equivalent to textwrap.wrap, but with support for multiple
578 578 paragraphs, as separated by empty lines.
579 579
580 580 Returns
581 581 -------
582 582
583 583 list of complete paragraphs, wrapped to fill `ncols` columns.
584 584 """
585 585 paragraph_re = re.compile(r'\n(\s*\n)+', re.MULTILINE)
586 586 text = dedent(text).strip()
587 587 paragraphs = paragraph_re.split(text)[::2] # every other entry is space
588 588 out_ps = []
589 589 indent_re = re.compile(r'\n\s+', re.MULTILINE)
590 590 for p in paragraphs:
591 591 # presume indentation that survives dedent is meaningful formatting,
592 592 # so don't fill unless text is flush.
593 593 if indent_re.search(p) is None:
594 594 # wrap paragraph
595 595 p = textwrap.fill(p, ncols)
596 596 out_ps.append(p)
597 597 return out_ps
598 598
599 599
600 600 class EvalFormatter(Formatter):
601 601 """A String Formatter that allows evaluation of simple expressions.
602 602
603 603 Note that this version interprets a : as specifying a format string (as per
604 604 standard string formatting), so if slicing is required, you must explicitly
605 605 create a slice.
606 606
607 607 This is to be used in templating cases, such as the parallel batch
608 608 script templates, where simple arithmetic on arguments is useful.
609 609
610 610 Examples
611 611 --------
612 612
613 613 In [1]: f = EvalFormatter()
614 614 In [2]: f.format('{n//4}', n=8)
615 615 Out [2]: '2'
616 616
617 617 In [3]: f.format("{greeting[slice(2,4)]}", greeting="Hello")
618 618 Out [3]: 'll'
619 619 """
620 620 def get_field(self, name, args, kwargs):
621 621 v = eval(name, kwargs)
622 622 return v, name
623 623
624 624 class FullEvalFormatter(Formatter):
625 625 """A String Formatter that allows evaluation of simple expressions.
626 626
627 627 Any time a format key is not found in the kwargs,
628 628 it will be tried as an expression in the kwargs namespace.
629 629
630 630 Note that this version allows slicing using [1:2], so you cannot specify
631 631 a format string. Use :class:`EvalFormatter` to permit format strings.
632 632
633 633 Examples
634 634 --------
635 635
636 636 In [1]: f = FullEvalFormatter()
637 637 In [2]: f.format('{n//4}', n=8)
638 638 Out[2]: '2'
639 639
640 640 In [3]: f.format('{list(range(5))[2:4]}')
641 641 Out[3]: '[2, 3]'
642 642
643 643 In [4]: f.format('{3*2}')
644 644 Out[4]: '6'
645 645 """
646 646 # copied from Formatter._vformat with minor changes to allow eval
647 647 # and replace the format_spec code with slicing
648 648 def _vformat(self, format_string, args, kwargs, used_args, recursion_depth):
649 649 if recursion_depth < 0:
650 650 raise ValueError('Max string recursion exceeded')
651 651 result = []
652 652 for literal_text, field_name, format_spec, conversion in \
653 653 self.parse(format_string):
654 654
655 655 # output the literal text
656 656 if literal_text:
657 657 result.append(literal_text)
658 658
659 659 # if there's a field, output it
660 660 if field_name is not None:
661 661 # this is some markup, find the object and do
662 662 # the formatting
663 663
664 664 if format_spec:
665 665 # override format spec, to allow slicing:
666 666 field_name = ':'.join([field_name, format_spec])
667 667
668 668 # eval the contents of the field for the object
669 669 # to be formatted
670 670 obj = eval(field_name, kwargs)
671 671
672 672 # do any conversion on the resulting object
673 673 obj = self.convert_field(obj, conversion)
674 674
675 675 # format the object and append to the result
676 676 result.append(self.format_field(obj, ''))
677 677
678 678 return ''.join(result)
679 679
680 class DollarFormatter(FullEvalFormatter):
681 """Formatter allowing Itpl style $foo replacement, for names and attribute
682 access only. Standard {foo} replacement also works, and allows full
683 evaluation of its arguments.
684
685 Examples
686 --------
687 In [1]: f = DollarFormatter()
688 In [2]: f.format('{n//4}', n=8)
689 Out[2]: '2'
690
691 In [3]: f.format('23 * 76 is $result', result=23*76)
692 Out[3]: '23 * 76 is 1748'
693
694 In [4]: f.format('$a or {b}', a=1, b=2)
695 Out[4]: '1 or 2'
696 """
697 _dollar_pattern = re.compile("(.*)\$([\w\.]+)")
698 def parse(self, fmt_string):
699 for literal_txt, field_name, format_spec, conversion \
700 in Formatter.parse(self, fmt_string):
701
702 # Find $foo patterns in the literal text.
703 continue_from = 0
704 for m in self._dollar_pattern.finditer(literal_txt):
705 new_txt, new_field = m.group(1,2)
706 yield (new_txt, new_field, "", "s")
707 continue_from = m.end()
708
709 # Re-yield the {foo} style pattern
710 yield (literal_txt[continue_from:], field_name, format_spec, conversion)
711
680 712
681 713 def columnize(items, separator=' ', displaywidth=80):
682 714 """ Transform a list of strings into a single string with columns.
683 715
684 716 Parameters
685 717 ----------
686 718 items : sequence of strings
687 719 The strings to process.
688 720
689 721 separator : str, optional [default is two spaces]
690 722 The string that separates columns.
691 723
692 724 displaywidth : int, optional [default is 80]
693 725 Width of the display in number of characters.
694 726
695 727 Returns
696 728 -------
697 729 The formatted string.
698 730 """
699 731 # Note: this code is adapted from columnize 0.3.2.
700 732 # See http://code.google.com/p/pycolumnize/
701 733
702 734 # Some degenerate cases.
703 735 size = len(items)
704 736 if size == 0:
705 737 return '\n'
706 738 elif size == 1:
707 739 return '%s\n' % items[0]
708 740
709 741 # Special case: if any item is longer than the maximum width, there's no
710 742 # point in triggering the logic below...
711 743 item_len = map(len, items) # save these, we can reuse them below
712 744 longest = max(item_len)
713 745 if longest >= displaywidth:
714 746 return '\n'.join(items+[''])
715 747
716 748 # Try every row count from 1 upwards
717 749 array_index = lambda nrows, row, col: nrows*col + row
718 750 for nrows in range(1, size):
719 751 ncols = (size + nrows - 1) // nrows
720 752 colwidths = []
721 753 totwidth = -len(separator)
722 754 for col in range(ncols):
723 755 # Get max column width for this column
724 756 colwidth = 0
725 757 for row in range(nrows):
726 758 i = array_index(nrows, row, col)
727 759 if i >= size: break
728 760 x, len_x = items[i], item_len[i]
729 761 colwidth = max(colwidth, len_x)
730 762 colwidths.append(colwidth)
731 763 totwidth += colwidth + len(separator)
732 764 if totwidth > displaywidth:
733 765 break
734 766 if totwidth <= displaywidth:
735 767 break
736 768
737 769 # The smallest number of rows computed and the max widths for each
738 770 # column has been obtained. Now we just have to format each of the rows.
739 771 string = ''
740 772 for row in range(nrows):
741 773 texts = []
742 774 for col in range(ncols):
743 775 i = row + nrows*col
744 776 if i >= size:
745 777 texts.append('')
746 778 else:
747 779 texts.append(items[i])
748 780 while texts and not texts[-1]:
749 781 del texts[-1]
750 782 for col in range(len(texts)):
751 783 texts[col] = texts[col].ljust(colwidths[col])
752 784 string += '%s\n' % separator.join(texts)
753 785 return string
General Comments 0
You need to be logged in to leave comments. Login now