##// END OF EJS Templates
rename num_rows etc to max_rows for clarity...
naught101 -
Show More
@@ -1,780 +1,780 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 543 def _vformat(self, format_string, args, kwargs, used_args, recursion_depth):
544 544 if recursion_depth < 0:
545 545 raise ValueError('Max string recursion exceeded')
546 546 result = []
547 547 for literal_text, field_name, format_spec, conversion in \
548 548 self.parse(format_string):
549 549
550 550 # output the literal text
551 551 if literal_text:
552 552 result.append(literal_text)
553 553
554 554 # if there's a field, output it
555 555 if field_name is not None:
556 556 # this is some markup, find the object and do
557 557 # the formatting
558 558
559 559 if format_spec:
560 560 # override format spec, to allow slicing:
561 561 field_name = ':'.join([field_name, format_spec])
562 562
563 563 # eval the contents of the field for the object
564 564 # to be formatted
565 565 obj = eval(field_name, kwargs)
566 566
567 567 # do any conversion on the resulting object
568 568 obj = self.convert_field(obj, conversion)
569 569
570 570 # format the object and append to the result
571 571 result.append(self.format_field(obj, ''))
572 572
573 573 return u''.join(py3compat.cast_unicode(s) for s in result)
574 574
575 575
576 576 @skip_doctest_py3
577 577 class DollarFormatter(FullEvalFormatter):
578 578 """Formatter allowing Itpl style $foo replacement, for names and attribute
579 579 access only. Standard {foo} replacement also works, and allows full
580 580 evaluation of its arguments.
581 581
582 582 Examples
583 583 --------
584 584 ::
585 585
586 586 In [1]: f = DollarFormatter()
587 587 In [2]: f.format('{n//4}', n=8)
588 588 Out[2]: u'2'
589 589
590 590 In [3]: f.format('23 * 76 is $result', result=23*76)
591 591 Out[3]: u'23 * 76 is 1748'
592 592
593 593 In [4]: f.format('$a or {b}', a=1, b=2)
594 594 Out[4]: u'1 or 2'
595 595 """
596 596 _dollar_pattern = re.compile("(.*?)\$(\$?[\w\.]+)")
597 597 def parse(self, fmt_string):
598 598 for literal_txt, field_name, format_spec, conversion \
599 599 in Formatter.parse(self, fmt_string):
600 600
601 601 # Find $foo patterns in the literal text.
602 602 continue_from = 0
603 603 txt = ""
604 604 for m in self._dollar_pattern.finditer(literal_txt):
605 605 new_txt, new_field = m.group(1,2)
606 606 # $$foo --> $foo
607 607 if new_field.startswith("$"):
608 608 txt += new_txt + new_field
609 609 else:
610 610 yield (txt + new_txt, new_field, "", None)
611 611 txt = ""
612 612 continue_from = m.end()
613 613
614 614 # Re-yield the {foo} style pattern
615 615 yield (txt + literal_txt[continue_from:], field_name, format_spec, conversion)
616 616
617 617 #-----------------------------------------------------------------------------
618 618 # Utils to columnize a list of string
619 619 #-----------------------------------------------------------------------------
620 620
621 def _col_chunks(l, nrows, row_first=False):
622 """Yield successive nrows-sized column chunks from l."""
621 def _col_chunks(l, max_rows, row_first=False):
622 """Yield successive max_rows-sized column chunks from l."""
623 623 if row_first:
624 ncols = (len(l) // nrows) + (len(l) % nrows > 0)
624 ncols = (len(l) // max_rows) + (len(l) % max_rows > 0)
625 625 for i in py3compat.xrange(ncols):
626 626 yield [l[j] for j in py3compat.xrange(i, len(l), ncols)]
627 627 else:
628 for i in py3compat.xrange(0, len(l), nrows):
629 yield l[i:(i + nrows)]
628 for i in py3compat.xrange(0, len(l), max_rows):
629 yield l[i:(i + max_rows)]
630 630
631 631
632 632 def _find_optimal(rlist, row_first=False, separator_size=2, displaywidth=80):
633 633 """Calculate optimal info to columnize a list of string"""
634 for nrow in range(1, len(rlist) + 1):
635 col_widths = list(map(max, _col_chunks(rlist, nrow, row_first)))
634 for max_rows in range(1, len(rlist) + 1):
635 col_widths = list(map(max, _col_chunks(rlist, max_rows, row_first)))
636 636 sumlength = sum(col_widths)
637 637 ncols = len(col_widths)
638 638 if sumlength + separator_size * (ncols - 1) <= displaywidth:
639 639 break
640 640 return {'num_columns': ncols,
641 641 'optimal_separator_width': (displaywidth - sumlength) / (ncols - 1) if (ncols - 1) else 0,
642 'num_rows': nrow,
642 'max_rows': max_rows,
643 643 'column_widths': col_widths
644 644 }
645 645
646 646
647 647 def _get_or_default(mylist, i, default=None):
648 648 """return list item number, or default if don't exist"""
649 649 if i >= len(mylist):
650 650 return default
651 651 else :
652 652 return mylist[i]
653 653
654 654
655 655 def compute_item_matrix(items, row_first=False, empty=None, *args, **kwargs) :
656 656 """Returns a nested list, and info to columnize items
657 657
658 658 Parameters
659 659 ----------
660 660
661 661 items
662 662 list of strings to columize
663 663 row_first : (default False)
664 664 Whether to compute columns for a row-first matrix instead of
665 665 column-first (default).
666 666 empty : (default None)
667 667 default value to fill list if needed
668 668 separator_size : int (default=2)
669 669 How much caracters will be used as a separation between each columns.
670 670 displaywidth : int (default=80)
671 671 The width of the area onto wich the columns should enter
672 672
673 673 Returns
674 674 -------
675 675
676 676 strings_matrix
677 677
678 678 nested list of string, the outer most list contains as many list as
679 679 rows, the innermost lists have each as many element as colums. If the
680 680 total number of elements in `items` does not equal the product of
681 681 rows*columns, the last element of some lists are filled with `None`.
682 682
683 683 dict_info
684 684 some info to make columnize easier:
685 685
686 686 num_columns
687 687 number of columns
688 num_rows
689 number of rows
688 max_rows
689 maximum number of rows (final number may be less)
690 690 column_widths
691 691 list of with of each columns
692 692 optimal_separator_width
693 693 best separator width between columns
694 694
695 695 Examples
696 696 --------
697 697 ::
698 698
699 699 In [1]: l = ['aaa','b','cc','d','eeeee','f','g','h','i','j','k','l']
700 700 ...: compute_item_matrix(l, displaywidth=12)
701 701 Out[1]:
702 702 ([['aaa', 'f', 'k'],
703 703 ['b', 'g', 'l'],
704 704 ['cc', 'h', None],
705 705 ['d', 'i', None],
706 706 ['eeeee', 'j', None]],
707 707 {'num_columns': 3,
708 708 'column_widths': [5, 1, 1],
709 709 'optimal_separator_width': 2,
710 'num_rows': 5})
710 'max_rows': 5})
711 711 """
712 712 info = _find_optimal(list(map(len, items)), row_first, *args, **kwargs)
713 nrow, ncol = info['num_rows'], info['num_columns']
713 nrow, ncol = info['max_rows'], info['num_columns']
714 714 if row_first:
715 715 return ([[_get_or_default(items, r * ncol + c, default=empty) for c in range(ncol)] for r in range(nrow)], info)
716 716 else:
717 717 return ([[_get_or_default(items, c * nrow + r, default=empty) for c in range(ncol)] for r in range(nrow)], info)
718 718
719 719
720 720 def columnize(items, row_first=False, separator=' ', displaywidth=80):
721 721 """ Transform a list of strings into a single string with columns.
722 722
723 723 Parameters
724 724 ----------
725 725 items : sequence of strings
726 726 The strings to process.
727 727
728 728 row_first : (default False)
729 729 Whether to compute columns for a row-first matrix instead of
730 730 column-first (default).
731 731
732 732 separator : str, optional [default is two spaces]
733 733 The string that separates columns.
734 734
735 735 displaywidth : int, optional [default is 80]
736 736 Width of the display in number of characters.
737 737
738 738 Returns
739 739 -------
740 740 The formatted string.
741 741 """
742 742 if not items:
743 743 return '\n'
744 744 matrix, info = compute_item_matrix(items, row_first=row_first, separator_size=len(separator), displaywidth=displaywidth)
745 745 fmatrix = [filter(None, x) for x in matrix]
746 746 sjoin = lambda x : separator.join([ y.ljust(w, ' ') for y, w in zip(x, info['column_widths'])])
747 747 return '\n'.join(map(sjoin, fmatrix))+'\n'
748 748
749 749
750 750 def get_text_list(list_, last_sep=' and ', sep=", ", wrap_item_with=""):
751 751 """
752 752 Return a string with a natural enumeration of items
753 753
754 754 >>> get_text_list(['a', 'b', 'c', 'd'])
755 755 'a, b, c and d'
756 756 >>> get_text_list(['a', 'b', 'c'], ' or ')
757 757 'a, b or c'
758 758 >>> get_text_list(['a', 'b', 'c'], ', ')
759 759 'a, b, c'
760 760 >>> get_text_list(['a', 'b'], ' or ')
761 761 'a or b'
762 762 >>> get_text_list(['a'])
763 763 'a'
764 764 >>> get_text_list([])
765 765 ''
766 766 >>> get_text_list(['a', 'b'], wrap_item_with="`")
767 767 '`a` and `b`'
768 768 >>> get_text_list(['a', 'b', 'c', 'd'], " = ", sep=" + ")
769 769 'a + b + c = d'
770 770 """
771 771 if len(list_) == 0:
772 772 return ''
773 773 if wrap_item_with:
774 774 list_ = ['%s%s%s' % (wrap_item_with, item, wrap_item_with) for
775 775 item in list_]
776 776 if len(list_) == 1:
777 777 return list_[0]
778 778 return '%s%s%s' % (
779 779 sep.join(i for i in list_[:-1]),
780 780 last_sep, list_[-1])
General Comments 0
You need to be logged in to leave comments. Login now