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