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