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