##// END OF EJS Templates
Fix docstring leading space....
Matthias Bussonnier -
Show More
@@ -1,140 +1,142 b''
1 1 #-----------------------------------------------------------------------------
2 2 # Copyright (C) 2010-2011, IPython Development Team.
3 3 #
4 4 # Distributed under the terms of the Modified BSD License.
5 5 #
6 6 # The full license is in the file COPYING.txt, distributed with this software.
7 7 #-----------------------------------------------------------------------------
8 8
9 9 import argparse
10 10 import sys
11 11
12 12 from IPython.core.magic_arguments import (argument, argument_group, kwds,
13 13 magic_arguments, parse_argstring, real_name)
14 14
15 LEADING_SPACE = "" if sys.version_info > (3, 13) else " "
16
15 17
16 18 @magic_arguments()
17 19 @argument('-f', '--foo', help="an argument")
18 20 def magic_foo1(self, args):
19 21 """ A docstring.
20 22 """
21 23 return parse_argstring(magic_foo1, args)
22 24
23 25
24 26 @magic_arguments()
25 27 def magic_foo2(self, args):
26 28 """ A docstring.
27 29 """
28 30 return parse_argstring(magic_foo2, args)
29 31
30 32
31 33 @magic_arguments()
32 34 @argument('-f', '--foo', help="an argument")
33 35 @argument_group('Group')
34 36 @argument('-b', '--bar', help="a grouped argument")
35 37 @argument_group('Second Group')
36 38 @argument('-z', '--baz', help="another grouped argument")
37 39 def magic_foo3(self, args):
38 40 """ A docstring.
39 41 """
40 42 return parse_argstring(magic_foo3, args)
41 43
42 44
43 45 @magic_arguments()
44 46 @kwds(argument_default=argparse.SUPPRESS)
45 47 @argument('-f', '--foo', help="an argument")
46 48 def magic_foo4(self, args):
47 49 """ A docstring.
48 50 """
49 51 return parse_argstring(magic_foo4, args)
50 52
51 53
52 54 @magic_arguments('frobnicate')
53 55 @argument('-f', '--foo', help="an argument")
54 56 def magic_foo5(self, args):
55 57 """ A docstring.
56 58 """
57 59 return parse_argstring(magic_foo5, args)
58 60
59 61
60 62 @magic_arguments()
61 63 @argument('-f', '--foo', help="an argument")
62 64 def magic_magic_foo(self, args):
63 65 """ A docstring.
64 66 """
65 67 return parse_argstring(magic_magic_foo, args)
66 68
67 69
68 70 @magic_arguments()
69 71 @argument('-f', '--foo', help="an argument")
70 72 def foo(self, args):
71 73 """ A docstring.
72 74 """
73 75 return parse_argstring(foo, args)
74 76
75 77
76 78 def test_magic_arguments():
77 79 # β€œoptional arguments” was replaced with β€œoptions” in argparse help
78 80 # https://docs.python.org/3/whatsnew/3.10.html#argparse
79 81 # https://bugs.python.org/issue9694
80 82 options = "optional arguments" if sys.version_info < (3, 10) else "options"
81 83
82 84 assert (
83 85 magic_foo1.__doc__
84 == f"::\n\n %foo1 [-f FOO]\n\n A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n"
86 == f"::\n\n %foo1 [-f FOO]\n\n{LEADING_SPACE}A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n"
85 87 )
86 88 assert getattr(magic_foo1, "argcmd_name", None) == None
87 89 assert real_name(magic_foo1) == "foo1"
88 90 assert magic_foo1(None, "") == argparse.Namespace(foo=None)
89 91 assert hasattr(magic_foo1, "has_arguments")
90 92
91 assert magic_foo2.__doc__ == "::\n\n %foo2\n\n A docstring.\n"
93 assert magic_foo2.__doc__ == f"::\n\n %foo2\n\n{LEADING_SPACE}A docstring.\n"
92 94 assert getattr(magic_foo2, "argcmd_name", None) == None
93 95 assert real_name(magic_foo2) == "foo2"
94 96 assert magic_foo2(None, "") == argparse.Namespace()
95 97 assert hasattr(magic_foo2, "has_arguments")
96 98
97 99 assert (
98 100 magic_foo3.__doc__
99 == f"::\n\n %foo3 [-f FOO] [-b BAR] [-z BAZ]\n\n A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n\nGroup:\n -b BAR, --bar BAR a grouped argument\n\nSecond Group:\n -z BAZ, --baz BAZ another grouped argument\n"
101 == f"::\n\n %foo3 [-f FOO] [-b BAR] [-z BAZ]\n\n{LEADING_SPACE}A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n\nGroup:\n -b BAR, --bar BAR a grouped argument\n\nSecond Group:\n -z BAZ, --baz BAZ another grouped argument\n"
100 102 )
101 103 assert getattr(magic_foo3, "argcmd_name", None) == None
102 104 assert real_name(magic_foo3) == "foo3"
103 105 assert magic_foo3(None, "") == argparse.Namespace(bar=None, baz=None, foo=None)
104 106 assert hasattr(magic_foo3, "has_arguments")
105 107
106 108 assert (
107 109 magic_foo4.__doc__
108 == f"::\n\n %foo4 [-f FOO]\n\n A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n"
110 == f"::\n\n %foo4 [-f FOO]\n\n{LEADING_SPACE}A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n"
109 111 )
110 112 assert getattr(magic_foo4, "argcmd_name", None) == None
111 113 assert real_name(magic_foo4) == "foo4"
112 114 assert magic_foo4(None, "") == argparse.Namespace()
113 115 assert hasattr(magic_foo4, "has_arguments")
114 116
115 117 assert (
116 118 magic_foo5.__doc__
117 == f"::\n\n %frobnicate [-f FOO]\n\n A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n"
119 == f"::\n\n %frobnicate [-f FOO]\n\n{LEADING_SPACE}A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n"
118 120 )
119 121 assert getattr(magic_foo5, "argcmd_name", None) == "frobnicate"
120 122 assert real_name(magic_foo5) == "frobnicate"
121 123 assert magic_foo5(None, "") == argparse.Namespace(foo=None)
122 124 assert hasattr(magic_foo5, "has_arguments")
123 125
124 126 assert (
125 127 magic_magic_foo.__doc__
126 == f"::\n\n %magic_foo [-f FOO]\n\n A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n"
128 == f"::\n\n %magic_foo [-f FOO]\n\n{LEADING_SPACE}A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n"
127 129 )
128 130 assert getattr(magic_magic_foo, "argcmd_name", None) == None
129 131 assert real_name(magic_magic_foo) == "magic_foo"
130 132 assert magic_magic_foo(None, "") == argparse.Namespace(foo=None)
131 133 assert hasattr(magic_magic_foo, "has_arguments")
132 134
133 135 assert (
134 136 foo.__doc__
135 == f"::\n\n %foo [-f FOO]\n\n A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n"
137 == f"::\n\n %foo [-f FOO]\n\n{LEADING_SPACE}A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n"
136 138 )
137 139 assert getattr(foo, "argcmd_name", None) == None
138 140 assert real_name(foo) == "foo"
139 141 assert foo(None, "") == argparse.Namespace(foo=None)
140 142 assert hasattr(foo, "has_arguments")
@@ -1,799 +1,799 b''
1 1 # encoding: utf-8
2 2 """
3 3 Utilities for working with strings and text.
4 4
5 5 Inheritance diagram:
6 6
7 7 .. inheritance-diagram:: IPython.utils.text
8 8 :parts: 3
9 9 """
10 10
11 11 import os
12 12 import re
13 13 import string
14 14 import sys
15 15 import textwrap
16 16 import warnings
17 17 from string import Formatter
18 18 from pathlib import Path
19 19
20 from typing import List, Union, Optional, Dict, Tuple
20 from typing import List, Dict, Tuple
21 21
22 22
23 23 class LSString(str):
24 24 """String derivative with a special access attributes.
25 25
26 26 These are normal strings, but with the special attributes:
27 27
28 28 .l (or .list) : value as list (split on newlines).
29 29 .n (or .nlstr): original value (the string itself).
30 30 .s (or .spstr): value as whitespace-separated string.
31 31 .p (or .paths): list of path objects (requires path.py package)
32 32
33 33 Any values which require transformations are computed only once and
34 34 cached.
35 35
36 36 Such strings are very useful to efficiently interact with the shell, which
37 37 typically only understands whitespace-separated options for commands."""
38 38
39 39 def get_list(self):
40 40 try:
41 41 return self.__list
42 42 except AttributeError:
43 43 self.__list = self.split('\n')
44 44 return self.__list
45 45
46 46 l = list = property(get_list)
47 47
48 48 def get_spstr(self):
49 49 try:
50 50 return self.__spstr
51 51 except AttributeError:
52 52 self.__spstr = self.replace('\n',' ')
53 53 return self.__spstr
54 54
55 55 s = spstr = property(get_spstr)
56 56
57 57 def get_nlstr(self):
58 58 return self
59 59
60 60 n = nlstr = property(get_nlstr)
61 61
62 62 def get_paths(self):
63 63 try:
64 64 return self.__paths
65 65 except AttributeError:
66 66 self.__paths = [Path(p) for p in self.split('\n') if os.path.exists(p)]
67 67 return self.__paths
68 68
69 69 p = paths = property(get_paths)
70 70
71 71 # FIXME: We need to reimplement type specific displayhook and then add this
72 72 # back as a custom printer. This should also be moved outside utils into the
73 73 # core.
74 74
75 75 # def print_lsstring(arg):
76 76 # """ Prettier (non-repr-like) and more informative printer for LSString """
77 77 # print "LSString (.p, .n, .l, .s available). Value:"
78 78 # print arg
79 79 #
80 80 #
81 81 # print_lsstring = result_display.register(LSString)(print_lsstring)
82 82
83 83
84 84 class SList(list):
85 85 """List derivative with a special access attributes.
86 86
87 87 These are normal lists, but with the special attributes:
88 88
89 89 * .l (or .list) : value as list (the list itself).
90 90 * .n (or .nlstr): value as a string, joined on newlines.
91 91 * .s (or .spstr): value as a string, joined on spaces.
92 92 * .p (or .paths): list of path objects (requires path.py package)
93 93
94 94 Any values which require transformations are computed only once and
95 95 cached."""
96 96
97 97 def get_list(self):
98 98 return self
99 99
100 100 l = list = property(get_list)
101 101
102 102 def get_spstr(self):
103 103 try:
104 104 return self.__spstr
105 105 except AttributeError:
106 106 self.__spstr = ' '.join(self)
107 107 return self.__spstr
108 108
109 109 s = spstr = property(get_spstr)
110 110
111 111 def get_nlstr(self):
112 112 try:
113 113 return self.__nlstr
114 114 except AttributeError:
115 115 self.__nlstr = '\n'.join(self)
116 116 return self.__nlstr
117 117
118 118 n = nlstr = property(get_nlstr)
119 119
120 120 def get_paths(self):
121 121 try:
122 122 return self.__paths
123 123 except AttributeError:
124 124 self.__paths = [Path(p) for p in self if os.path.exists(p)]
125 125 return self.__paths
126 126
127 127 p = paths = property(get_paths)
128 128
129 129 def grep(self, pattern, prune = False, field = None):
130 130 """ Return all strings matching 'pattern' (a regex or callable)
131 131
132 132 This is case-insensitive. If prune is true, return all items
133 133 NOT matching the pattern.
134 134
135 135 If field is specified, the match must occur in the specified
136 136 whitespace-separated field.
137 137
138 138 Examples::
139 139
140 140 a.grep( lambda x: x.startswith('C') )
141 141 a.grep('Cha.*log', prune=1)
142 142 a.grep('chm', field=-1)
143 143 """
144 144
145 145 def match_target(s):
146 146 if field is None:
147 147 return s
148 148 parts = s.split()
149 149 try:
150 150 tgt = parts[field]
151 151 return tgt
152 152 except IndexError:
153 153 return ""
154 154
155 155 if isinstance(pattern, str):
156 156 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
157 157 else:
158 158 pred = pattern
159 159 if not prune:
160 160 return SList([el for el in self if pred(match_target(el))])
161 161 else:
162 162 return SList([el for el in self if not pred(match_target(el))])
163 163
164 164 def fields(self, *fields):
165 165 """ Collect whitespace-separated fields from string list
166 166
167 167 Allows quick awk-like usage of string lists.
168 168
169 169 Example data (in var a, created by 'a = !ls -l')::
170 170
171 171 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
172 172 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
173 173
174 174 * ``a.fields(0)`` is ``['-rwxrwxrwx', 'drwxrwxrwx+']``
175 175 * ``a.fields(1,0)`` is ``['1 -rwxrwxrwx', '6 drwxrwxrwx+']``
176 176 (note the joining by space).
177 177 * ``a.fields(-1)`` is ``['ChangeLog', 'IPython']``
178 178
179 179 IndexErrors are ignored.
180 180
181 181 Without args, fields() just split()'s the strings.
182 182 """
183 183 if len(fields) == 0:
184 184 return [el.split() for el in self]
185 185
186 186 res = SList()
187 187 for el in [f.split() for f in self]:
188 188 lineparts = []
189 189
190 190 for fd in fields:
191 191 try:
192 192 lineparts.append(el[fd])
193 193 except IndexError:
194 194 pass
195 195 if lineparts:
196 196 res.append(" ".join(lineparts))
197 197
198 198 return res
199 199
200 200 def sort(self,field= None, nums = False):
201 201 """ sort by specified fields (see fields())
202 202
203 203 Example::
204 204
205 205 a.sort(1, nums = True)
206 206
207 207 Sorts a by second field, in numerical order (so that 21 > 3)
208 208
209 209 """
210 210
211 211 #decorate, sort, undecorate
212 212 if field is not None:
213 213 dsu = [[SList([line]).fields(field), line] for line in self]
214 214 else:
215 215 dsu = [[line, line] for line in self]
216 216 if nums:
217 217 for i in range(len(dsu)):
218 218 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
219 219 try:
220 220 n = int(numstr)
221 221 except ValueError:
222 222 n = 0
223 223 dsu[i][0] = n
224 224
225 225
226 226 dsu.sort()
227 227 return SList([t[1] for t in dsu])
228 228
229 229
230 230 # FIXME: We need to reimplement type specific displayhook and then add this
231 231 # back as a custom printer. This should also be moved outside utils into the
232 232 # core.
233 233
234 234 # def print_slist(arg):
235 235 # """ Prettier (non-repr-like) and more informative printer for SList """
236 236 # print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):"
237 237 # if hasattr(arg, 'hideonce') and arg.hideonce:
238 238 # arg.hideonce = False
239 239 # return
240 240 #
241 241 # nlprint(arg) # This was a nested list printer, now removed.
242 242 #
243 243 # print_slist = result_display.register(SList)(print_slist)
244 244
245 245
246 246 def indent(instr,nspaces=4, ntabs=0, flatten=False):
247 247 """Indent a string a given number of spaces or tabstops.
248 248
249 249 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
250 250
251 251 Parameters
252 252 ----------
253 253 instr : basestring
254 254 The string to be indented.
255 255 nspaces : int (default: 4)
256 256 The number of spaces to be indented.
257 257 ntabs : int (default: 0)
258 258 The number of tabs to be indented.
259 259 flatten : bool (default: False)
260 260 Whether to scrub existing indentation. If True, all lines will be
261 261 aligned to the same indentation. If False, existing indentation will
262 262 be strictly increased.
263 263
264 264 Returns
265 265 -------
266 266 str|unicode : string indented by ntabs and nspaces.
267 267
268 268 """
269 269 if instr is None:
270 270 return
271 271 ind = '\t'*ntabs+' '*nspaces
272 272 if flatten:
273 273 pat = re.compile(r'^\s*', re.MULTILINE)
274 274 else:
275 275 pat = re.compile(r'^', re.MULTILINE)
276 276 outstr = re.sub(pat, ind, instr)
277 277 if outstr.endswith(os.linesep+ind):
278 278 return outstr[:-len(ind)]
279 279 else:
280 280 return outstr
281 281
282 282
283 283 def list_strings(arg):
284 284 """Always return a list of strings, given a string or list of strings
285 285 as input.
286 286
287 287 Examples
288 288 --------
289 289 ::
290 290
291 291 In [7]: list_strings('A single string')
292 292 Out[7]: ['A single string']
293 293
294 294 In [8]: list_strings(['A single string in a list'])
295 295 Out[8]: ['A single string in a list']
296 296
297 297 In [9]: list_strings(['A','list','of','strings'])
298 298 Out[9]: ['A', 'list', 'of', 'strings']
299 299 """
300 300
301 301 if isinstance(arg, str):
302 302 return [arg]
303 303 else:
304 304 return arg
305 305
306 306
307 307 def marquee(txt='',width=78,mark='*'):
308 308 """Return the input string centered in a 'marquee'.
309 309
310 310 Examples
311 311 --------
312 312 ::
313 313
314 314 In [16]: marquee('A test',40)
315 315 Out[16]: '**************** A test ****************'
316 316
317 317 In [17]: marquee('A test',40,'-')
318 318 Out[17]: '---------------- A test ----------------'
319 319
320 320 In [18]: marquee('A test',40,' ')
321 321 Out[18]: ' A test '
322 322
323 323 """
324 324 if not txt:
325 325 return (mark*width)[:width]
326 326 nmark = (width-len(txt)-2)//len(mark)//2
327 327 if nmark < 0: nmark =0
328 328 marks = mark*nmark
329 329 return '%s %s %s' % (marks,txt,marks)
330 330
331 331
332 332 ini_spaces_re = re.compile(r'^(\s+)')
333 333
334 334 def num_ini_spaces(strng):
335 335 """Return the number of initial spaces in a string"""
336 336 warnings.warn(
337 337 "`num_ini_spaces` is Pending Deprecation since IPython 8.17."
338 338 "It is considered fro removal in in future version. "
339 339 "Please open an issue if you believe it should be kept.",
340 340 stacklevel=2,
341 341 category=PendingDeprecationWarning,
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 def dedent(text):
360 def dedent(text: str) -> str:
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 list of complete paragraphs, wrapped to fill `ncols` columns.
396 396 """
397 397 warnings.warn(
398 398 "`wrap_paragraphs` is Pending Deprecation since IPython 8.17."
399 399 "It is considered fro removal in in future version. "
400 400 "Please open an issue if you believe it should be kept.",
401 401 stacklevel=2,
402 402 category=PendingDeprecationWarning,
403 403 )
404 404 paragraph_re = re.compile(r'\n(\s*\n)+', re.MULTILINE)
405 405 text = dedent(text).strip()
406 406 paragraphs = paragraph_re.split(text)[::2] # every other entry is space
407 407 out_ps = []
408 408 indent_re = re.compile(r'\n\s+', re.MULTILINE)
409 409 for p in paragraphs:
410 410 # presume indentation that survives dedent is meaningful formatting,
411 411 # so don't fill unless text is flush.
412 412 if indent_re.search(p) is None:
413 413 # wrap paragraph
414 414 p = textwrap.fill(p, ncols)
415 415 out_ps.append(p)
416 416 return out_ps
417 417
418 418
419 419 def strip_email_quotes(text):
420 420 """Strip leading email quotation characters ('>').
421 421
422 422 Removes any combination of leading '>' interspersed with whitespace that
423 423 appears *identically* in all lines of the input text.
424 424
425 425 Parameters
426 426 ----------
427 427 text : str
428 428
429 429 Examples
430 430 --------
431 431
432 432 Simple uses::
433 433
434 434 In [2]: strip_email_quotes('> > text')
435 435 Out[2]: 'text'
436 436
437 437 In [3]: strip_email_quotes('> > text\\n> > more')
438 438 Out[3]: 'text\\nmore'
439 439
440 440 Note how only the common prefix that appears in all lines is stripped::
441 441
442 442 In [4]: strip_email_quotes('> > text\\n> > more\\n> more...')
443 443 Out[4]: '> text\\n> more\\nmore...'
444 444
445 445 So if any line has no quote marks ('>'), then none are stripped from any
446 446 of them ::
447 447
448 448 In [5]: strip_email_quotes('> > text\\n> > more\\nlast different')
449 449 Out[5]: '> > text\\n> > more\\nlast different'
450 450 """
451 451 lines = text.splitlines()
452 452 strip_len = 0
453 453
454 454 for characters in zip(*lines):
455 455 # Check if all characters in this position are the same
456 456 if len(set(characters)) > 1:
457 457 break
458 458 prefix_char = characters[0]
459 459
460 460 if prefix_char in string.whitespace or prefix_char == ">":
461 461 strip_len += 1
462 462 else:
463 463 break
464 464
465 465 text = "\n".join([ln[strip_len:] for ln in lines])
466 466 return text
467 467
468 468
469 469 def strip_ansi(source):
470 470 """
471 471 Remove ansi escape codes from text.
472 472
473 473 Parameters
474 474 ----------
475 475 source : str
476 476 Source to remove the ansi from
477 477 """
478 478 warnings.warn(
479 479 "`strip_ansi` is Pending Deprecation since IPython 8.17."
480 480 "It is considered fro removal in in future version. "
481 481 "Please open an issue if you believe it should be kept.",
482 482 stacklevel=2,
483 483 category=PendingDeprecationWarning,
484 484 )
485 485
486 486 return re.sub(r'\033\[(\d|;)+?m', '', source)
487 487
488 488
489 489 class EvalFormatter(Formatter):
490 490 """A String Formatter that allows evaluation of simple expressions.
491 491
492 492 Note that this version interprets a `:` as specifying a format string (as per
493 493 standard string formatting), so if slicing is required, you must explicitly
494 494 create a slice.
495 495
496 496 This is to be used in templating cases, such as the parallel batch
497 497 script templates, where simple arithmetic on arguments is useful.
498 498
499 499 Examples
500 500 --------
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 #XXX: As of Python 3.4, the format string parsing no longer splits on a colon
515 515 # inside [], so EvalFormatter can handle slicing. Once we only support 3.4 and
516 516 # above, it should be possible to remove FullEvalFormatter.
517 517
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]: '2'
534 534
535 535 In [3]: f.format('{list(range(5))[2:4]}')
536 536 Out[3]: '[2, 3]'
537 537
538 538 In [4]: f.format('{3*2}')
539 539 Out[4]: '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:str, args, kwargs)->str:
544 544 result = []
545 545 for literal_text, field_name, format_spec, conversion in \
546 546 self.parse(format_string):
547 547
548 548 # output the literal text
549 549 if literal_text:
550 550 result.append(literal_text)
551 551
552 552 # if there's a field, output it
553 553 if field_name is not None:
554 554 # this is some markup, find the object and do
555 555 # the formatting
556 556
557 557 if format_spec:
558 558 # override format spec, to allow slicing:
559 559 field_name = ':'.join([field_name, format_spec])
560 560
561 561 # eval the contents of the field for the object
562 562 # to be formatted
563 563 obj = eval(field_name, kwargs)
564 564
565 565 # do any conversion on the resulting object
566 566 obj = self.convert_field(obj, conversion)
567 567
568 568 # format the object and append to the result
569 569 result.append(self.format_field(obj, ''))
570 570
571 571 return ''.join(result)
572 572
573 573
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 ::
582 582
583 583 In [1]: f = DollarFormatter()
584 584 In [2]: f.format('{n//4}', n=8)
585 585 Out[2]: '2'
586 586
587 587 In [3]: f.format('23 * 76 is $result', result=23*76)
588 588 Out[3]: '23 * 76 is 1748'
589 589
590 590 In [4]: f.format('$a or {b}', a=1, b=2)
591 591 Out[4]: '1 or 2'
592 592 """
593 593 _dollar_pattern_ignore_single_quote = re.compile(r"(.*?)\$(\$?[\w\.]+)(?=([^']*'[^']*')*[^']*$)")
594 594 def parse(self, fmt_string):
595 595 for literal_txt, field_name, format_spec, conversion \
596 596 in Formatter.parse(self, fmt_string):
597 597
598 598 # Find $foo patterns in the literal text.
599 599 continue_from = 0
600 600 txt = ""
601 601 for m in self._dollar_pattern_ignore_single_quote.finditer(literal_txt):
602 602 new_txt, new_field = m.group(1,2)
603 603 # $$foo --> $foo
604 604 if new_field.startswith("$"):
605 605 txt += new_txt + new_field
606 606 else:
607 607 yield (txt + new_txt, new_field, "", None)
608 608 txt = ""
609 609 continue_from = m.end()
610 610
611 611 # Re-yield the {foo} style pattern
612 612 yield (txt + literal_txt[continue_from:], field_name, format_spec, conversion)
613 613
614 614 def __repr__(self):
615 615 return "<DollarFormatter>"
616 616
617 617 #-----------------------------------------------------------------------------
618 618 # Utils to columnize a list of string
619 619 #-----------------------------------------------------------------------------
620 620
621 621 def _col_chunks(l, max_rows, row_first=False):
622 622 """Yield successive max_rows-sized column chunks from l."""
623 623 if row_first:
624 624 ncols = (len(l) // max_rows) + (len(l) % max_rows > 0)
625 625 for i in range(ncols):
626 626 yield [l[j] for j in range(i, len(l), ncols)]
627 627 else:
628 628 for i in range(0, len(l), max_rows):
629 629 yield l[i:(i + max_rows)]
630 630
631 631
632 632 def _find_optimal(rlist, row_first: bool, separator_size: int, displaywidth: int):
633 633 """Calculate optimal info to columnize a list of string"""
634 634 for max_rows in range(1, len(rlist) + 1):
635 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 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(
656 656 items, row_first: bool = False, empty=None, *, separator_size=2, displaywidth=80
657 657 ) -> Tuple[List[List[int]], Dict[str, int]]:
658 658 """Returns a nested list, and info to columnize items
659 659
660 660 Parameters
661 661 ----------
662 662 items
663 663 list of strings to columize
664 664 row_first : (default False)
665 665 Whether to compute columns for a row-first matrix instead of
666 666 column-first (default).
667 667 empty : (default None)
668 668 default value to fill list if needed
669 669 separator_size : int (default=2)
670 670 How much characters will be used as a separation between each columns.
671 671 displaywidth : int (default=80)
672 672 The width of the area onto which the columns should enter
673 673
674 674 Returns
675 675 -------
676 676 strings_matrix
677 677 nested list of string, the outer most list contains as many list as
678 678 rows, the innermost lists have each as many element as columns. If the
679 679 total number of elements in `items` does not equal the product of
680 680 rows*columns, the last element of some lists are filled with `None`.
681 681 dict_info
682 682 some info to make columnize easier:
683 683
684 684 num_columns
685 685 number of columns
686 686 max_rows
687 687 maximum number of rows (final number may be less)
688 688 column_widths
689 689 list of with of each columns
690 690 optimal_separator_width
691 691 best separator width between columns
692 692
693 693 Examples
694 694 --------
695 695 ::
696 696
697 697 In [1]: l = ['aaa','b','cc','d','eeeee','f','g','h','i','j','k','l']
698 698 In [2]: list, info = compute_item_matrix(l, displaywidth=12)
699 699 In [3]: list
700 700 Out[3]: [['aaa', 'f', 'k'], ['b', 'g', 'l'], ['cc', 'h', None], ['d', 'i', None], ['eeeee', 'j', None]]
701 701 In [4]: ideal = {'num_columns': 3, 'column_widths': [5, 1, 1], 'optimal_separator_width': 2, 'max_rows': 5}
702 702 In [5]: all((info[k] == ideal[k] for k in ideal.keys()))
703 703 Out[5]: True
704 704 """
705 705 warnings.warn(
706 706 "`compute_item_matrix` is Pending Deprecation since IPython 8.17."
707 707 "It is considered fro removal in in future version. "
708 708 "Please open an issue if you believe it should be kept.",
709 709 stacklevel=2,
710 710 category=PendingDeprecationWarning,
711 711 )
712 712 info = _find_optimal(
713 713 list(map(len, items)),
714 714 row_first,
715 715 separator_size=separator_size,
716 716 displaywidth=displaywidth,
717 717 )
718 718 nrow, ncol = info["max_rows"], info["num_columns"]
719 719 if row_first:
720 720 return ([[_get_or_default(items, r * ncol + c, default=empty) for c in range(ncol)] for r in range(nrow)], info)
721 721 else:
722 722 return ([[_get_or_default(items, c * nrow + r, default=empty) for c in range(ncol)] for r in range(nrow)], info)
723 723
724 724
725 725 def columnize(items, row_first=False, separator=" ", displaywidth=80, spread=False):
726 726 """Transform a list of strings into a single string with columns.
727 727
728 728 Parameters
729 729 ----------
730 730 items : sequence of strings
731 731 The strings to process.
732 732 row_first : (default False)
733 733 Whether to compute columns for a row-first matrix instead of
734 734 column-first (default).
735 735 separator : str, optional [default is two spaces]
736 736 The string that separates columns.
737 737 displaywidth : int, optional [default is 80]
738 738 Width of the display in number of characters.
739 739
740 740 Returns
741 741 -------
742 742 The formatted string.
743 743 """
744 744 warnings.warn(
745 745 "`columnize` is Pending Deprecation since IPython 8.17."
746 746 "It is considered fro removal in in future version. "
747 747 "Please open an issue if you believe it should be kept.",
748 748 stacklevel=2,
749 749 category=PendingDeprecationWarning,
750 750 )
751 751 if not items:
752 752 return "\n"
753 753 matrix: List[List[int]]
754 754 matrix, info = compute_item_matrix(
755 755 items,
756 756 row_first=row_first,
757 757 separator_size=len(separator),
758 758 displaywidth=displaywidth,
759 759 )
760 760 if spread:
761 761 separator = separator.ljust(int(info["optimal_separator_width"]))
762 762 fmatrix: List[filter[int]] = [filter(None, x) for x in matrix]
763 763 sjoin = lambda x: separator.join(
764 764 [y.ljust(w, " ") for y, w in zip(x, info["column_widths"])]
765 765 )
766 766 return "\n".join(map(sjoin, fmatrix)) + "\n"
767 767
768 768
769 769 def get_text_list(list_, last_sep=' and ', sep=", ", wrap_item_with=""):
770 770 """
771 771 Return a string with a natural enumeration of items
772 772
773 773 >>> get_text_list(['a', 'b', 'c', 'd'])
774 774 'a, b, c and d'
775 775 >>> get_text_list(['a', 'b', 'c'], ' or ')
776 776 'a, b or c'
777 777 >>> get_text_list(['a', 'b', 'c'], ', ')
778 778 'a, b, c'
779 779 >>> get_text_list(['a', 'b'], ' or ')
780 780 'a or b'
781 781 >>> get_text_list(['a'])
782 782 'a'
783 783 >>> get_text_list([])
784 784 ''
785 785 >>> get_text_list(['a', 'b'], wrap_item_with="`")
786 786 '`a` and `b`'
787 787 >>> get_text_list(['a', 'b', 'c', 'd'], " = ", sep=" + ")
788 788 'a + b + c = d'
789 789 """
790 790 if len(list_) == 0:
791 791 return ''
792 792 if wrap_item_with:
793 793 list_ = ['%s%s%s' % (wrap_item_with, item, wrap_item_with) for
794 794 item in list_]
795 795 if len(list_) == 1:
796 796 return list_[0]
797 797 return '%s%s%s' % (
798 798 sep.join(i for i in list_[:-1]),
799 799 last_sep, list_[-1])
General Comments 0
You need to be logged in to leave comments. Login now