##// END OF EJS Templates
Row-first option for columnize
naught101 -
Show More
@@ -1,765 +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 _chunks(l, n):
622 """Yield successive n-sized chunks from l."""
623 for i in py3compat.xrange(0, len(l), n):
624 yield l[i:i+n]
621 def _col_chunks(l, nrows, row_first=False):
622 """Yield successive nrows-sized column chunks from l."""
623 if row_first:
624 ncols = (len(l) // nrows) + (len(l) % nrows > 0)
625 for i in py3compat.xrange(ncols):
626 yield [l[j] for j in py3compat.xrange(i, len(l), nrows)]
627 else:
628 for i in py3compat.xrange(0, len(l), nrows):
629 yield l[i:(i + nrows)]
625 630
626 631
627 def _find_optimal(rlist , separator_size=2 , displaywidth=80):
632 def _find_optimal(rlist, row_first=False, separator_size=2, displaywidth=80):
628 633 """Calculate optimal info to columnize a list of string"""
629 for nrow in range(1, len(rlist)+1) :
630 chk = list(map(max,_chunks(rlist, nrow)))
631 sumlength = sum(chk)
632 ncols = len(chk)
633 if sumlength+separator_size*(ncols-1) <= displaywidth :
634 break;
635 return {'columns_numbers' : ncols,
636 'optimal_separator_width':(displaywidth - sumlength)/(ncols-1) if (ncols -1) else 0,
637 'rows_numbers' : nrow,
638 'columns_width' : chk
639 }
634 for nrow in range(1, len(rlist) + 1):
635 col_widths = list(map(max, _col_chunks(rlist, nrow, row_first)))
636 sumlength = sum(col_widths)
637 ncols = len(col_widths)
638 if sumlength + separator_size * (ncols - 1) <= displaywidth:
639 break
640 return {'num_columns': ncols,
641 'optimal_separator_width': (displaywidth - sumlength) / (ncols - 1) if (ncols - 1) else 0,
642 'num_rows': nrow,
643 'column_widths': col_widths
644 }
640 645
641 646
642 647 def _get_or_default(mylist, i, default=None):
643 648 """return list item number, or default if don't exist"""
644 649 if i >= len(mylist):
645 650 return default
646 651 else :
647 652 return mylist[i]
648 653
649 654
650 def compute_item_matrix(items, empty=None, *args, **kwargs) :
655 def compute_item_matrix(items, row_first=False, empty=None, *args, **kwargs) :
651 656 """Returns a nested list, and info to columnize items
652 657
653 658 Parameters
654 659 ----------
655 660
656 661 items
657 662 list of strings to columize
663 row_first : (default False)
664 Whether to to compute columns for a row-first matrix instead ofr
665 column-first (default).
658 666 empty : (default None)
659 667 default value to fill list if needed
660 668 separator_size : int (default=2)
661 669 How much caracters will be used as a separation between each columns.
662 670 displaywidth : int (default=80)
663 671 The width of the area onto wich the columns should enter
664 672
665 673 Returns
666 674 -------
667 675
668 676 strings_matrix
669 677
670 678 nested list of string, the outer most list contains as many list as
671 679 rows, the innermost lists have each as many element as colums. If the
672 680 total number of elements in `items` does not equal the product of
673 681 rows*columns, the last element of some lists are filled with `None`.
674 682
675 683 dict_info
676 684 some info to make columnize easier:
677 685
678 columns_numbers
686 num_columns
679 687 number of columns
680 rows_numbers
688 num_rows
681 689 number of rows
682 columns_width
690 column_widths
683 691 list of with of each columns
684 692 optimal_separator_width
685 693 best separator width between columns
686 694
687 695 Examples
688 696 --------
689 697 ::
690 698
691 699 In [1]: l = ['aaa','b','cc','d','eeeee','f','g','h','i','j','k','l']
692 ...: compute_item_matrix(l,displaywidth=12)
700 ...: compute_item_matrix(l, displaywidth=12)
693 701 Out[1]:
694 702 ([['aaa', 'f', 'k'],
695 703 ['b', 'g', 'l'],
696 704 ['cc', 'h', None],
697 705 ['d', 'i', None],
698 706 ['eeeee', 'j', None]],
699 {'columns_numbers': 3,
700 'columns_width': [5, 1, 1],
707 {'num_columns': 3,
708 'column_widths': [5, 1, 1],
701 709 'optimal_separator_width': 2,
702 'rows_numbers': 5})
710 'num_rows': 5})
703 711 """
704 info = _find_optimal(list(map(len, items)), *args, **kwargs)
705 nrow, ncol = info['rows_numbers'], info['columns_numbers']
706 return ([[ _get_or_default(items, c*nrow+i, default=empty) for c in range(ncol) ] for i in range(nrow) ], info)
712 info = _find_optimal(list(map(len, items)), row_first, *args, **kwargs)
713 nrow, ncol = info['num_rows'], info['num_columns']
714 if row_first:
715 return ([[_get_or_default(items, c * nrow + r, default=empty) for r in range(nrow)] for c in range(ncol)], info)
716 else:
717 return ([[_get_or_default(items, c * nrow + r, default=empty) for c in range(ncol)] for r in range(nrow)], info)
707 718
708 719
709 def columnize(items, separator=' ', displaywidth=80):
720 def columnize(items, row_first=False, separator=' ', displaywidth=80):
710 721 """ Transform a list of strings into a single string with columns.
711 722
712 723 Parameters
713 724 ----------
714 725 items : sequence of strings
715 726 The strings to process.
716 727
728 row_first : (default False)
729 Whether to to compute columns for a row-first matrix instead ofr
730 column-first (default).
731
717 732 separator : str, optional [default is two spaces]
718 733 The string that separates columns.
719 734
720 735 displaywidth : int, optional [default is 80]
721 736 Width of the display in number of characters.
722 737
723 738 Returns
724 739 -------
725 740 The formatted string.
726 741 """
727 if not items :
742 if not items:
728 743 return '\n'
729 matrix, info = compute_item_matrix(items, separator_size=len(separator), displaywidth=displaywidth)
744 matrix, info = compute_item_matrix(items, row_first=row_first, separator_size=len(separator), displaywidth=displaywidth)
730 745 fmatrix = [filter(None, x) for x in matrix]
731 sjoin = lambda x : separator.join([ y.ljust(w, ' ') for y, w in zip(x, info['columns_width'])])
746 sjoin = lambda x : separator.join([ y.ljust(w, ' ') for y, w in zip(x, info['column_widths'])])
732 747 return '\n'.join(map(sjoin, fmatrix))+'\n'
733 748
734 749
735 750 def get_text_list(list_, last_sep=' and ', sep=", ", wrap_item_with=""):
736 751 """
737 752 Return a string with a natural enumeration of items
738 753
739 754 >>> get_text_list(['a', 'b', 'c', 'd'])
740 755 'a, b, c and d'
741 756 >>> get_text_list(['a', 'b', 'c'], ' or ')
742 757 'a, b or c'
743 758 >>> get_text_list(['a', 'b', 'c'], ', ')
744 759 'a, b, c'
745 760 >>> get_text_list(['a', 'b'], ' or ')
746 761 'a or b'
747 762 >>> get_text_list(['a'])
748 763 'a'
749 764 >>> get_text_list([])
750 765 ''
751 766 >>> get_text_list(['a', 'b'], wrap_item_with="`")
752 767 '`a` and `b`'
753 768 >>> get_text_list(['a', 'b', 'c', 'd'], " = ", sep=" + ")
754 769 'a + b + c = d'
755 770 """
756 771 if len(list_) == 0:
757 772 return ''
758 773 if wrap_item_with:
759 774 list_ = ['%s%s%s' % (wrap_item_with, item, wrap_item_with) for
760 775 item in list_]
761 776 if len(list_) == 1:
762 777 return list_[0]
763 778 return '%s%s%s' % (
764 779 sep.join(i for i in list_[:-1]),
765 780 last_sep, list_[-1])
General Comments 0
You need to be logged in to leave comments. Login now