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