##// END OF EJS Templates
fix types in utils/text.py
Matthias Bussonnier -
Show More
@@ -1,264 +1,267 b''
1 1 # encoding: utf-8
2 2 """
3 3 System command aliases.
4 4
5 5 Authors:
6 6
7 7 * Fernando Perez
8 8 * Brian Granger
9 9 """
10 10
11 11 #-----------------------------------------------------------------------------
12 12 # Copyright (C) 2008-2011 The IPython Development Team
13 13 #
14 14 # Distributed under the terms of the BSD License.
15 15 #
16 16 # The full license is in the file COPYING.txt, distributed with this software.
17 17 #-----------------------------------------------------------------------------
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Imports
21 21 #-----------------------------------------------------------------------------
22 22
23 23 import os
24 24 import re
25 25 import sys
26 26
27 27 from traitlets.config.configurable import Configurable
28 28 from .error import UsageError
29 29
30 30 from traitlets import List, Instance
31 31 from logging import error
32 32
33 import typing as t
34
35
33 36 #-----------------------------------------------------------------------------
34 37 # Utilities
35 38 #-----------------------------------------------------------------------------
36 39
37 40 # This is used as the pattern for calls to split_user_input.
38 41 shell_line_split = re.compile(r'^(\s*)()(\S+)(.*$)')
39 42
40 def default_aliases():
43 def default_aliases() -> t.List[t.Tuple[str, str]]:
41 44 """Return list of shell aliases to auto-define.
42 45 """
43 46 # Note: the aliases defined here should be safe to use on a kernel
44 47 # regardless of what frontend it is attached to. Frontends that use a
45 48 # kernel in-process can define additional aliases that will only work in
46 49 # their case. For example, things like 'less' or 'clear' that manipulate
47 50 # the terminal should NOT be declared here, as they will only work if the
48 51 # kernel is running inside a true terminal, and not over the network.
49 52
50 53 if os.name == 'posix':
51 54 default_aliases = [('mkdir', 'mkdir'), ('rmdir', 'rmdir'),
52 55 ('mv', 'mv'), ('rm', 'rm'), ('cp', 'cp'),
53 56 ('cat', 'cat'),
54 57 ]
55 58 # Useful set of ls aliases. The GNU and BSD options are a little
56 59 # different, so we make aliases that provide as similar as possible
57 60 # behavior in ipython, by passing the right flags for each platform
58 61 if sys.platform.startswith('linux'):
59 62 ls_aliases = [('ls', 'ls -F --color'),
60 63 # long ls
61 64 ('ll', 'ls -F -o --color'),
62 65 # ls normal files only
63 66 ('lf', 'ls -F -o --color %l | grep ^-'),
64 67 # ls symbolic links
65 68 ('lk', 'ls -F -o --color %l | grep ^l'),
66 69 # directories or links to directories,
67 70 ('ldir', 'ls -F -o --color %l | grep /$'),
68 71 # things which are executable
69 72 ('lx', 'ls -F -o --color %l | grep ^-..x'),
70 73 ]
71 74 elif sys.platform.startswith('openbsd') or sys.platform.startswith('netbsd'):
72 75 # OpenBSD, NetBSD. The ls implementation on these platforms do not support
73 76 # the -G switch and lack the ability to use colorized output.
74 77 ls_aliases = [('ls', 'ls -F'),
75 78 # long ls
76 79 ('ll', 'ls -F -l'),
77 80 # ls normal files only
78 81 ('lf', 'ls -F -l %l | grep ^-'),
79 82 # ls symbolic links
80 83 ('lk', 'ls -F -l %l | grep ^l'),
81 84 # directories or links to directories,
82 85 ('ldir', 'ls -F -l %l | grep /$'),
83 86 # things which are executable
84 87 ('lx', 'ls -F -l %l | grep ^-..x'),
85 88 ]
86 89 else:
87 90 # BSD, OSX, etc.
88 91 ls_aliases = [('ls', 'ls -F -G'),
89 92 # long ls
90 93 ('ll', 'ls -F -l -G'),
91 94 # ls normal files only
92 95 ('lf', 'ls -F -l -G %l | grep ^-'),
93 96 # ls symbolic links
94 97 ('lk', 'ls -F -l -G %l | grep ^l'),
95 98 # directories or links to directories,
96 99 ('ldir', 'ls -F -G -l %l | grep /$'),
97 100 # things which are executable
98 101 ('lx', 'ls -F -l -G %l | grep ^-..x'),
99 102 ]
100 103 default_aliases = default_aliases + ls_aliases
101 104 elif os.name in ['nt', 'dos']:
102 105 default_aliases = [('ls', 'dir /on'),
103 106 ('ddir', 'dir /ad /on'), ('ldir', 'dir /ad /on'),
104 107 ('mkdir', 'mkdir'), ('rmdir', 'rmdir'),
105 108 ('echo', 'echo'), ('ren', 'ren'), ('copy', 'copy'),
106 109 ]
107 110 else:
108 111 default_aliases = []
109 112
110 113 return default_aliases
111 114
112 115
113 116 class AliasError(Exception):
114 117 pass
115 118
116 119
117 120 class InvalidAliasError(AliasError):
118 121 pass
119 122
120 123 class Alias(object):
121 124 """Callable object storing the details of one alias.
122 125
123 126 Instances are registered as magic functions to allow use of aliases.
124 127 """
125 128
126 129 # Prepare blacklist
127 130 blacklist = {'cd','popd','pushd','dhist','alias','unalias'}
128 131
129 132 def __init__(self, shell, name, cmd):
130 133 self.shell = shell
131 134 self.name = name
132 135 self.cmd = cmd
133 136 self.__doc__ = "Alias for `!{}`".format(cmd)
134 137 self.nargs = self.validate()
135 138
136 139 def validate(self):
137 140 """Validate the alias, and return the number of arguments."""
138 141 if self.name in self.blacklist:
139 142 raise InvalidAliasError("The name %s can't be aliased "
140 143 "because it is a keyword or builtin." % self.name)
141 144 try:
142 145 caller = self.shell.magics_manager.magics['line'][self.name]
143 146 except KeyError:
144 147 pass
145 148 else:
146 149 if not isinstance(caller, Alias):
147 150 raise InvalidAliasError("The name %s can't be aliased "
148 151 "because it is another magic command." % self.name)
149 152
150 153 if not (isinstance(self.cmd, str)):
151 154 raise InvalidAliasError("An alias command must be a string, "
152 155 "got: %r" % self.cmd)
153 156
154 157 nargs = self.cmd.count('%s') - self.cmd.count('%%s')
155 158
156 159 if (nargs > 0) and (self.cmd.find('%l') >= 0):
157 160 raise InvalidAliasError('The %s and %l specifiers are mutually '
158 161 'exclusive in alias definitions.')
159 162
160 163 return nargs
161 164
162 165 def __repr__(self):
163 166 return "<alias {} for {!r}>".format(self.name, self.cmd)
164 167
165 168 def __call__(self, rest=''):
166 169 cmd = self.cmd
167 170 nargs = self.nargs
168 171 # Expand the %l special to be the user's input line
169 172 if cmd.find('%l') >= 0:
170 173 cmd = cmd.replace('%l', rest)
171 174 rest = ''
172 175
173 176 if nargs==0:
174 177 if cmd.find('%%s') >= 1:
175 178 cmd = cmd.replace('%%s', '%s')
176 179 # Simple, argument-less aliases
177 180 cmd = '%s %s' % (cmd, rest)
178 181 else:
179 182 # Handle aliases with positional arguments
180 183 args = rest.split(None, nargs)
181 184 if len(args) < nargs:
182 185 raise UsageError('Alias <%s> requires %s arguments, %s given.' %
183 186 (self.name, nargs, len(args)))
184 187 cmd = '%s %s' % (cmd % tuple(args[:nargs]),' '.join(args[nargs:]))
185 188
186 189 self.shell.system(cmd)
187 190
188 191 #-----------------------------------------------------------------------------
189 192 # Main AliasManager class
190 193 #-----------------------------------------------------------------------------
191 194
192 195 class AliasManager(Configurable):
193 196 default_aliases: List = List(default_aliases()).tag(config=True)
194 197 user_aliases: List = List(default_value=[]).tag(config=True)
195 198 shell = Instance(
196 199 "IPython.core.interactiveshell.InteractiveShellABC", allow_none=True
197 200 )
198 201
199 202 def __init__(self, shell=None, **kwargs):
200 203 super(AliasManager, self).__init__(shell=shell, **kwargs)
201 204 # For convenient access
202 205 if self.shell is not None:
203 206 self.linemagics = self.shell.magics_manager.magics["line"]
204 207 self.init_aliases()
205 208
206 209 def init_aliases(self):
207 210 # Load default & user aliases
208 211 for name, cmd in self.default_aliases + self.user_aliases:
209 212 if (
210 213 cmd.startswith("ls ")
211 214 and self.shell is not None
212 215 and self.shell.colors == "NoColor"
213 216 ):
214 217 cmd = cmd.replace(" --color", "")
215 218 self.soft_define_alias(name, cmd)
216 219
217 220 @property
218 221 def aliases(self):
219 222 return [(n, func.cmd) for (n, func) in self.linemagics.items()
220 223 if isinstance(func, Alias)]
221 224
222 225 def soft_define_alias(self, name, cmd):
223 226 """Define an alias, but don't raise on an AliasError."""
224 227 try:
225 228 self.define_alias(name, cmd)
226 229 except AliasError as e:
227 230 error("Invalid alias: %s" % e)
228 231
229 232 def define_alias(self, name, cmd):
230 233 """Define a new alias after validating it.
231 234
232 235 This will raise an :exc:`AliasError` if there are validation
233 236 problems.
234 237 """
235 238 caller = Alias(shell=self.shell, name=name, cmd=cmd)
236 239 self.shell.magics_manager.register_function(caller, magic_kind='line',
237 240 magic_name=name)
238 241
239 242 def get_alias(self, name):
240 243 """Return an alias, or None if no alias by that name exists."""
241 244 aname = self.linemagics.get(name, None)
242 245 return aname if isinstance(aname, Alias) else None
243 246
244 247 def is_alias(self, name):
245 248 """Return whether or not a given name has been defined as an alias"""
246 249 return self.get_alias(name) is not None
247 250
248 251 def undefine_alias(self, name):
249 252 if self.is_alias(name):
250 253 del self.linemagics[name]
251 254 else:
252 255 raise ValueError('%s is not an alias' % name)
253 256
254 257 def clear_aliases(self):
255 258 for name, _ in self.aliases:
256 259 self.undefine_alias(name)
257 260
258 261 def retrieve_alias(self, name):
259 262 """Retrieve the command to which an alias expands."""
260 263 caller = self.get_alias(name)
261 264 if caller:
262 265 return caller.cmd
263 266 else:
264 267 raise ValueError('%s is not an alias' % name)
@@ -1,1028 +1,1031 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Display formatters.
3 3
4 4 Inheritance diagram:
5 5
6 6 .. inheritance-diagram:: IPython.core.formatters
7 7 :parts: 3
8 8 """
9 9
10 10 # Copyright (c) IPython Development Team.
11 11 # Distributed under the terms of the Modified BSD License.
12 12
13 13 import abc
14 14 import sys
15 15 import traceback
16 16 import warnings
17 17 from io import StringIO
18 18
19 19 from decorator import decorator
20 20
21 21 from traitlets.config.configurable import Configurable
22 22 from .getipython import get_ipython
23 23 from ..utils.sentinel import Sentinel
24 24 from ..utils.dir2 import get_real_method
25 25 from ..lib import pretty
26 26 from traitlets import (
27 27 Bool, Dict, Integer, Unicode, CUnicode, ObjectName, List,
28 28 ForwardDeclaredInstance,
29 29 default, observe,
30 30 )
31 31
32 32 from typing import Any
33 33
34 34
35 35 class DisplayFormatter(Configurable):
36 36
37 37 active_types = List(Unicode(),
38 38 help="""List of currently active mime-types to display.
39 39 You can use this to set a white-list for formats to display.
40 40
41 41 Most users will not need to change this value.
42 42 """).tag(config=True)
43 43
44 44 @default('active_types')
45 45 def _active_types_default(self):
46 46 return self.format_types
47 47
48 48 @observe('active_types')
49 49 def _active_types_changed(self, change):
50 50 for key, formatter in self.formatters.items():
51 51 if key in change['new']:
52 52 formatter.enabled = True
53 53 else:
54 54 formatter.enabled = False
55 55
56 ipython_display_formatter = ForwardDeclaredInstance('FormatterABC')
57 @default('ipython_display_formatter')
56 ipython_display_formatter = ForwardDeclaredInstance("FormatterABC")
57
58 @default("ipython_display_formatter")
58 59 def _default_formatter(self):
59 60 return IPythonDisplayFormatter(parent=self)
60 61
61 mimebundle_formatter = ForwardDeclaredInstance('FormatterABC')
62 @default('mimebundle_formatter')
62 mimebundle_formatter = ForwardDeclaredInstance("FormatterABC")
63
64 @default("mimebundle_formatter")
63 65 def _default_mime_formatter(self):
64 66 return MimeBundleFormatter(parent=self)
65 67
66 68 # A dict of formatter whose keys are format types (MIME types) and whose
67 69 # values are subclasses of BaseFormatter.
68 70 formatters = Dict()
69 @default('formatters')
71
72 @default("formatters")
70 73 def _formatters_default(self):
71 74 """Activate the default formatters."""
72 75 formatter_classes = [
73 76 PlainTextFormatter,
74 77 HTMLFormatter,
75 78 MarkdownFormatter,
76 79 SVGFormatter,
77 80 PNGFormatter,
78 81 PDFFormatter,
79 82 JPEGFormatter,
80 83 LatexFormatter,
81 84 JSONFormatter,
82 85 JavascriptFormatter
83 86 ]
84 87 d = {}
85 88 for cls in formatter_classes:
86 89 f = cls(parent=self)
87 90 d[f.format_type] = f
88 91 return d
89 92
90 93 def format(self, obj, include=None, exclude=None):
91 94 """Return a format data dict for an object.
92 95
93 96 By default all format types will be computed.
94 97
95 98 The following MIME types are usually implemented:
96 99
97 100 * text/plain
98 101 * text/html
99 102 * text/markdown
100 103 * text/latex
101 104 * application/json
102 105 * application/javascript
103 106 * application/pdf
104 107 * image/png
105 108 * image/jpeg
106 109 * image/svg+xml
107 110
108 111 Parameters
109 112 ----------
110 113 obj : object
111 114 The Python object whose format data will be computed.
112 115 include : list, tuple or set; optional
113 116 A list of format type strings (MIME types) to include in the
114 117 format data dict. If this is set *only* the format types included
115 118 in this list will be computed.
116 119 exclude : list, tuple or set; optional
117 120 A list of format type string (MIME types) to exclude in the format
118 121 data dict. If this is set all format types will be computed,
119 122 except for those included in this argument.
120 123 Mimetypes present in exclude will take precedence over the ones in include
121 124
122 125 Returns
123 126 -------
124 127 (format_dict, metadata_dict) : tuple of two dicts
125 128 format_dict is a dictionary of key/value pairs, one of each format that was
126 129 generated for the object. The keys are the format types, which
127 130 will usually be MIME type strings and the values and JSON'able
128 131 data structure containing the raw data for the representation in
129 132 that format.
130 133
131 134 metadata_dict is a dictionary of metadata about each mime-type output.
132 135 Its keys will be a strict subset of the keys in format_dict.
133 136
134 137 Notes
135 138 -----
136 139 If an object implement `_repr_mimebundle_` as well as various
137 140 `_repr_*_`, the data returned by `_repr_mimebundle_` will take
138 141 precedence and the corresponding `_repr_*_` for this mimetype will
139 142 not be called.
140 143
141 144 """
142 145 format_dict = {}
143 146 md_dict = {}
144 147
145 148 if self.ipython_display_formatter(obj):
146 149 # object handled itself, don't proceed
147 150 return {}, {}
148 151
149 152 format_dict, md_dict = self.mimebundle_formatter(obj, include=include, exclude=exclude)
150 153
151 154 if format_dict or md_dict:
152 155 if include:
153 156 format_dict = {k:v for k,v in format_dict.items() if k in include}
154 157 md_dict = {k:v for k,v in md_dict.items() if k in include}
155 158 if exclude:
156 159 format_dict = {k:v for k,v in format_dict.items() if k not in exclude}
157 160 md_dict = {k:v for k,v in md_dict.items() if k not in exclude}
158 161
159 162 for format_type, formatter in self.formatters.items():
160 163 if format_type in format_dict:
161 164 # already got it from mimebundle, maybe don't render again.
162 165 # exception: manually registered per-mime renderer
163 166 # check priority:
164 167 # 1. user-registered per-mime formatter
165 168 # 2. mime-bundle (user-registered or repr method)
166 169 # 3. default per-mime formatter (e.g. repr method)
167 170 try:
168 171 formatter.lookup(obj)
169 172 except KeyError:
170 173 # no special formatter, use mime-bundle-provided value
171 174 continue
172 175 if include and format_type not in include:
173 176 continue
174 177 if exclude and format_type in exclude:
175 178 continue
176 179
177 180 md = None
178 181 try:
179 182 data = formatter(obj)
180 183 except:
181 184 # FIXME: log the exception
182 185 raise
183 186
184 187 # formatters can return raw data or (data, metadata)
185 188 if isinstance(data, tuple) and len(data) == 2:
186 189 data, md = data
187 190
188 191 if data is not None:
189 192 format_dict[format_type] = data
190 193 if md is not None:
191 194 md_dict[format_type] = md
192 195 return format_dict, md_dict
193 196
194 197 @property
195 198 def format_types(self):
196 199 """Return the format types (MIME types) of the active formatters."""
197 200 return list(self.formatters.keys())
198 201
199 202
200 203 #-----------------------------------------------------------------------------
201 204 # Formatters for specific format types (text, html, svg, etc.)
202 205 #-----------------------------------------------------------------------------
203 206
204 207
205 208 def _safe_repr(obj):
206 209 """Try to return a repr of an object
207 210
208 211 always returns a string, at least.
209 212 """
210 213 try:
211 214 return repr(obj)
212 215 except Exception as e:
213 216 return "un-repr-able object (%r)" % e
214 217
215 218
216 219 class FormatterWarning(UserWarning):
217 220 """Warning class for errors in formatters"""
218 221
219 222 @decorator
220 223 def catch_format_error(method, self, *args, **kwargs):
221 224 """show traceback on failed format call"""
222 225 try:
223 226 r = method(self, *args, **kwargs)
224 227 except NotImplementedError:
225 228 # don't warn on NotImplementedErrors
226 229 return self._check_return(None, args[0])
227 230 except Exception:
228 231 exc_info = sys.exc_info()
229 232 ip = get_ipython()
230 233 if ip is not None:
231 234 ip.showtraceback(exc_info)
232 235 else:
233 236 traceback.print_exception(*exc_info)
234 237 return self._check_return(None, args[0])
235 238 return self._check_return(r, args[0])
236 239
237 240
238 241 class FormatterABC(metaclass=abc.ABCMeta):
239 242 """ Abstract base class for Formatters.
240 243
241 244 A formatter is a callable class that is responsible for computing the
242 245 raw format data for a particular format type (MIME type). For example,
243 246 an HTML formatter would have a format type of `text/html` and would return
244 247 the HTML representation of the object when called.
245 248 """
246 249
247 250 # The format type of the data returned, usually a MIME type.
248 251 format_type = 'text/plain'
249 252
250 253 # Is the formatter enabled...
251 254 enabled = True
252 255
253 256 @abc.abstractmethod
254 257 def __call__(self, obj):
255 258 """Return a JSON'able representation of the object.
256 259
257 260 If the object cannot be formatted by this formatter,
258 261 warn and return None.
259 262 """
260 263 return repr(obj)
261 264
262 265
263 266 def _mod_name_key(typ):
264 267 """Return a (__module__, __name__) tuple for a type.
265 268
266 269 Used as key in Formatter.deferred_printers.
267 270 """
268 271 module = getattr(typ, '__module__', None)
269 272 name = getattr(typ, '__name__', None)
270 273 return (module, name)
271 274
272 275
273 276 def _get_type(obj):
274 277 """Return the type of an instance (old and new-style)"""
275 278 return getattr(obj, '__class__', None) or type(obj)
276 279
277 280
278 281 _raise_key_error = Sentinel('_raise_key_error', __name__,
279 282 """
280 283 Special value to raise a KeyError
281 284
282 285 Raise KeyError in `BaseFormatter.pop` if passed as the default value to `pop`
283 286 """)
284 287
285 288
286 289 class BaseFormatter(Configurable):
287 290 """A base formatter class that is configurable.
288 291
289 292 This formatter should usually be used as the base class of all formatters.
290 293 It is a traited :class:`Configurable` class and includes an extensible
291 294 API for users to determine how their objects are formatted. The following
292 295 logic is used to find a function to format an given object.
293 296
294 297 1. The object is introspected to see if it has a method with the name
295 298 :attr:`print_method`. If is does, that object is passed to that method
296 299 for formatting.
297 300 2. If no print method is found, three internal dictionaries are consulted
298 301 to find print method: :attr:`singleton_printers`, :attr:`type_printers`
299 302 and :attr:`deferred_printers`.
300 303
301 304 Users should use these dictionaries to register functions that will be
302 305 used to compute the format data for their objects (if those objects don't
303 306 have the special print methods). The easiest way of using these
304 307 dictionaries is through the :meth:`for_type` and :meth:`for_type_by_name`
305 308 methods.
306 309
307 310 If no function/callable is found to compute the format data, ``None`` is
308 311 returned and this format type is not used.
309 312 """
310 313
311 314 format_type = Unicode("text/plain")
312 315 _return_type: Any = str
313 316
314 317 enabled = Bool(True).tag(config=True)
315 318
316 319 print_method = ObjectName('__repr__')
317 320
318 321 # The singleton printers.
319 322 # Maps the IDs of the builtin singleton objects to the format functions.
320 323 singleton_printers = Dict().tag(config=True)
321 324
322 325 # The type-specific printers.
323 326 # Map type objects to the format functions.
324 327 type_printers = Dict().tag(config=True)
325 328
326 329 # The deferred-import type-specific printers.
327 330 # Map (modulename, classname) pairs to the format functions.
328 331 deferred_printers = Dict().tag(config=True)
329 332
330 333 @catch_format_error
331 334 def __call__(self, obj):
332 335 """Compute the format for an object."""
333 336 if self.enabled:
334 337 # lookup registered printer
335 338 try:
336 339 printer = self.lookup(obj)
337 340 except KeyError:
338 341 pass
339 342 else:
340 343 return printer(obj)
341 344 # Finally look for special method names
342 345 method = get_real_method(obj, self.print_method)
343 346 if method is not None:
344 347 return method()
345 348 return None
346 349 else:
347 350 return None
348 351
349 352 def __contains__(self, typ):
350 353 """map in to lookup_by_type"""
351 354 try:
352 355 self.lookup_by_type(typ)
353 356 except KeyError:
354 357 return False
355 358 else:
356 359 return True
357 360
358 361 def _check_return(self, r, obj):
359 362 """Check that a return value is appropriate
360 363
361 364 Return the value if so, None otherwise, warning if invalid.
362 365 """
363 366 if r is None or isinstance(r, self._return_type) or \
364 367 (isinstance(r, tuple) and r and isinstance(r[0], self._return_type)):
365 368 return r
366 369 else:
367 370 warnings.warn(
368 371 "%s formatter returned invalid type %s (expected %s) for object: %s" % \
369 372 (self.format_type, type(r), self._return_type, _safe_repr(obj)),
370 373 FormatterWarning
371 374 )
372 375
373 376 def lookup(self, obj):
374 377 """Look up the formatter for a given instance.
375 378
376 379 Parameters
377 380 ----------
378 381 obj : object instance
379 382
380 383 Returns
381 384 -------
382 385 f : callable
383 386 The registered formatting callable for the type.
384 387
385 388 Raises
386 389 ------
387 390 KeyError if the type has not been registered.
388 391 """
389 392 # look for singleton first
390 393 obj_id = id(obj)
391 394 if obj_id in self.singleton_printers:
392 395 return self.singleton_printers[obj_id]
393 396 # then lookup by type
394 397 return self.lookup_by_type(_get_type(obj))
395 398
396 399 def lookup_by_type(self, typ):
397 400 """Look up the registered formatter for a type.
398 401
399 402 Parameters
400 403 ----------
401 404 typ : type or '__module__.__name__' string for a type
402 405
403 406 Returns
404 407 -------
405 408 f : callable
406 409 The registered formatting callable for the type.
407 410
408 411 Raises
409 412 ------
410 413 KeyError if the type has not been registered.
411 414 """
412 415 if isinstance(typ, str):
413 416 typ_key = tuple(typ.rsplit('.',1))
414 417 if typ_key not in self.deferred_printers:
415 418 # We may have it cached in the type map. We will have to
416 419 # iterate over all of the types to check.
417 420 for cls in self.type_printers:
418 421 if _mod_name_key(cls) == typ_key:
419 422 return self.type_printers[cls]
420 423 else:
421 424 return self.deferred_printers[typ_key]
422 425 else:
423 426 for cls in pretty._get_mro(typ):
424 427 if cls in self.type_printers or self._in_deferred_types(cls):
425 428 return self.type_printers[cls]
426 429
427 430 # If we have reached here, the lookup failed.
428 431 raise KeyError("No registered printer for {0!r}".format(typ))
429 432
430 433 def for_type(self, typ, func=None):
431 434 """Add a format function for a given type.
432 435
433 436 Parameters
434 437 ----------
435 438 typ : type or '__module__.__name__' string for a type
436 439 The class of the object that will be formatted using `func`.
437 440
438 441 func : callable
439 442 A callable for computing the format data.
440 443 `func` will be called with the object to be formatted,
441 444 and will return the raw data in this formatter's format.
442 445 Subclasses may use a different call signature for the
443 446 `func` argument.
444 447
445 448 If `func` is None or not specified, there will be no change,
446 449 only returning the current value.
447 450
448 451 Returns
449 452 -------
450 453 oldfunc : callable
451 454 The currently registered callable.
452 455 If you are registering a new formatter,
453 456 this will be the previous value (to enable restoring later).
454 457 """
455 458 # if string given, interpret as 'pkg.module.class_name'
456 459 if isinstance(typ, str):
457 460 type_module, type_name = typ.rsplit('.', 1)
458 461 return self.for_type_by_name(type_module, type_name, func)
459 462
460 463 try:
461 464 oldfunc = self.lookup_by_type(typ)
462 465 except KeyError:
463 466 oldfunc = None
464 467
465 468 if func is not None:
466 469 self.type_printers[typ] = func
467 470
468 471 return oldfunc
469 472
470 473 def for_type_by_name(self, type_module, type_name, func=None):
471 474 """Add a format function for a type specified by the full dotted
472 475 module and name of the type, rather than the type of the object.
473 476
474 477 Parameters
475 478 ----------
476 479 type_module : str
477 480 The full dotted name of the module the type is defined in, like
478 481 ``numpy``.
479 482
480 483 type_name : str
481 484 The name of the type (the class name), like ``dtype``
482 485
483 486 func : callable
484 487 A callable for computing the format data.
485 488 `func` will be called with the object to be formatted,
486 489 and will return the raw data in this formatter's format.
487 490 Subclasses may use a different call signature for the
488 491 `func` argument.
489 492
490 493 If `func` is None or unspecified, there will be no change,
491 494 only returning the current value.
492 495
493 496 Returns
494 497 -------
495 498 oldfunc : callable
496 499 The currently registered callable.
497 500 If you are registering a new formatter,
498 501 this will be the previous value (to enable restoring later).
499 502 """
500 503 key = (type_module, type_name)
501 504
502 505 try:
503 506 oldfunc = self.lookup_by_type("%s.%s" % key)
504 507 except KeyError:
505 508 oldfunc = None
506 509
507 510 if func is not None:
508 511 self.deferred_printers[key] = func
509 512 return oldfunc
510 513
511 514 def pop(self, typ, default=_raise_key_error):
512 515 """Pop a formatter for the given type.
513 516
514 517 Parameters
515 518 ----------
516 519 typ : type or '__module__.__name__' string for a type
517 520 default : object
518 521 value to be returned if no formatter is registered for typ.
519 522
520 523 Returns
521 524 -------
522 525 obj : object
523 526 The last registered object for the type.
524 527
525 528 Raises
526 529 ------
527 530 KeyError if the type is not registered and default is not specified.
528 531 """
529 532
530 533 if isinstance(typ, str):
531 534 typ_key = tuple(typ.rsplit('.',1))
532 535 if typ_key not in self.deferred_printers:
533 536 # We may have it cached in the type map. We will have to
534 537 # iterate over all of the types to check.
535 538 for cls in self.type_printers:
536 539 if _mod_name_key(cls) == typ_key:
537 540 old = self.type_printers.pop(cls)
538 541 break
539 542 else:
540 543 old = default
541 544 else:
542 545 old = self.deferred_printers.pop(typ_key)
543 546 else:
544 547 if typ in self.type_printers:
545 548 old = self.type_printers.pop(typ)
546 549 else:
547 550 old = self.deferred_printers.pop(_mod_name_key(typ), default)
548 551 if old is _raise_key_error:
549 552 raise KeyError("No registered value for {0!r}".format(typ))
550 553 return old
551 554
552 555 def _in_deferred_types(self, cls):
553 556 """
554 557 Check if the given class is specified in the deferred type registry.
555 558
556 559 Successful matches will be moved to the regular type registry for future use.
557 560 """
558 561 mod = getattr(cls, '__module__', None)
559 562 name = getattr(cls, '__name__', None)
560 563 key = (mod, name)
561 564 if key in self.deferred_printers:
562 565 # Move the printer over to the regular registry.
563 566 printer = self.deferred_printers.pop(key)
564 567 self.type_printers[cls] = printer
565 568 return True
566 569 return False
567 570
568 571
569 572 class PlainTextFormatter(BaseFormatter):
570 573 """The default pretty-printer.
571 574
572 575 This uses :mod:`IPython.lib.pretty` to compute the format data of
573 576 the object. If the object cannot be pretty printed, :func:`repr` is used.
574 577 See the documentation of :mod:`IPython.lib.pretty` for details on
575 578 how to write pretty printers. Here is a simple example::
576 579
577 580 def dtype_pprinter(obj, p, cycle):
578 581 if cycle:
579 582 return p.text('dtype(...)')
580 583 if hasattr(obj, 'fields'):
581 584 if obj.fields is None:
582 585 p.text(repr(obj))
583 586 else:
584 587 p.begin_group(7, 'dtype([')
585 588 for i, field in enumerate(obj.descr):
586 589 if i > 0:
587 590 p.text(',')
588 591 p.breakable()
589 592 p.pretty(field)
590 593 p.end_group(7, '])')
591 594 """
592 595
593 596 # The format type of data returned.
594 597 format_type = Unicode('text/plain')
595 598
596 599 # This subclass ignores this attribute as it always need to return
597 600 # something.
598 601 enabled = Bool(True).tag(config=False)
599 602
600 603 max_seq_length = Integer(pretty.MAX_SEQ_LENGTH,
601 604 help="""Truncate large collections (lists, dicts, tuples, sets) to this size.
602 605
603 606 Set to 0 to disable truncation.
604 607 """
605 608 ).tag(config=True)
606 609
607 610 # Look for a _repr_pretty_ methods to use for pretty printing.
608 611 print_method = ObjectName('_repr_pretty_')
609 612
610 613 # Whether to pretty-print or not.
611 614 pprint = Bool(True).tag(config=True)
612 615
613 616 # Whether to be verbose or not.
614 617 verbose = Bool(False).tag(config=True)
615 618
616 619 # The maximum width.
617 620 max_width = Integer(79).tag(config=True)
618 621
619 622 # The newline character.
620 623 newline = Unicode('\n').tag(config=True)
621 624
622 625 # format-string for pprinting floats
623 626 float_format = Unicode('%r')
624 627 # setter for float precision, either int or direct format-string
625 628 float_precision = CUnicode('').tag(config=True)
626 629
627 630 @observe('float_precision')
628 631 def _float_precision_changed(self, change):
629 632 """float_precision changed, set float_format accordingly.
630 633
631 634 float_precision can be set by int or str.
632 635 This will set float_format, after interpreting input.
633 636 If numpy has been imported, numpy print precision will also be set.
634 637
635 638 integer `n` sets format to '%.nf', otherwise, format set directly.
636 639
637 640 An empty string returns to defaults (repr for float, 8 for numpy).
638 641
639 642 This parameter can be set via the '%precision' magic.
640 643 """
641 644 new = change['new']
642 645 if '%' in new:
643 646 # got explicit format string
644 647 fmt = new
645 648 try:
646 649 fmt%3.14159
647 650 except Exception as e:
648 651 raise ValueError("Precision must be int or format string, not %r"%new) from e
649 652 elif new:
650 653 # otherwise, should be an int
651 654 try:
652 655 i = int(new)
653 656 assert i >= 0
654 657 except ValueError as e:
655 658 raise ValueError("Precision must be int or format string, not %r"%new) from e
656 659 except AssertionError as e:
657 660 raise ValueError("int precision must be non-negative, not %r"%i) from e
658 661
659 662 fmt = '%%.%if'%i
660 663 if 'numpy' in sys.modules:
661 664 # set numpy precision if it has been imported
662 665 import numpy
663 666 numpy.set_printoptions(precision=i)
664 667 else:
665 668 # default back to repr
666 669 fmt = '%r'
667 670 if 'numpy' in sys.modules:
668 671 import numpy
669 672 # numpy default is 8
670 673 numpy.set_printoptions(precision=8)
671 674 self.float_format = fmt
672 675
673 676 # Use the default pretty printers from IPython.lib.pretty.
674 677 @default('singleton_printers')
675 678 def _singleton_printers_default(self):
676 679 return pretty._singleton_pprinters.copy()
677 680
678 681 @default('type_printers')
679 682 def _type_printers_default(self):
680 683 d = pretty._type_pprinters.copy()
681 684 d[float] = lambda obj,p,cycle: p.text(self.float_format%obj)
682 685 # if NumPy is used, set precision for its float64 type
683 686 if "numpy" in sys.modules:
684 687 import numpy
685 688
686 689 d[numpy.float64] = lambda obj, p, cycle: p.text(self.float_format % obj)
687 690 return d
688 691
689 692 @default('deferred_printers')
690 693 def _deferred_printers_default(self):
691 694 return pretty._deferred_type_pprinters.copy()
692 695
693 696 #### FormatterABC interface ####
694 697
695 698 @catch_format_error
696 699 def __call__(self, obj):
697 700 """Compute the pretty representation of the object."""
698 701 if not self.pprint:
699 702 return repr(obj)
700 703 else:
701 704 stream = StringIO()
702 705 printer = pretty.RepresentationPrinter(stream, self.verbose,
703 706 self.max_width, self.newline,
704 707 max_seq_length=self.max_seq_length,
705 708 singleton_pprinters=self.singleton_printers,
706 709 type_pprinters=self.type_printers,
707 710 deferred_pprinters=self.deferred_printers)
708 711 printer.pretty(obj)
709 712 printer.flush()
710 713 return stream.getvalue()
711 714
712 715
713 716 class HTMLFormatter(BaseFormatter):
714 717 """An HTML formatter.
715 718
716 719 To define the callables that compute the HTML representation of your
717 720 objects, define a :meth:`_repr_html_` method or use the :meth:`for_type`
718 721 or :meth:`for_type_by_name` methods to register functions that handle
719 722 this.
720 723
721 724 The return value of this formatter should be a valid HTML snippet that
722 725 could be injected into an existing DOM. It should *not* include the
723 726 ```<html>`` or ```<body>`` tags.
724 727 """
725 728 format_type = Unicode('text/html')
726 729
727 730 print_method = ObjectName('_repr_html_')
728 731
729 732
730 733 class MarkdownFormatter(BaseFormatter):
731 734 """A Markdown formatter.
732 735
733 736 To define the callables that compute the Markdown representation of your
734 737 objects, define a :meth:`_repr_markdown_` method or use the :meth:`for_type`
735 738 or :meth:`for_type_by_name` methods to register functions that handle
736 739 this.
737 740
738 741 The return value of this formatter should be a valid Markdown.
739 742 """
740 743 format_type = Unicode('text/markdown')
741 744
742 745 print_method = ObjectName('_repr_markdown_')
743 746
744 747 class SVGFormatter(BaseFormatter):
745 748 """An SVG formatter.
746 749
747 750 To define the callables that compute the SVG representation of your
748 751 objects, define a :meth:`_repr_svg_` method or use the :meth:`for_type`
749 752 or :meth:`for_type_by_name` methods to register functions that handle
750 753 this.
751 754
752 755 The return value of this formatter should be valid SVG enclosed in
753 756 ```<svg>``` tags, that could be injected into an existing DOM. It should
754 757 *not* include the ```<html>`` or ```<body>`` tags.
755 758 """
756 759 format_type = Unicode('image/svg+xml')
757 760
758 761 print_method = ObjectName('_repr_svg_')
759 762
760 763
761 764 class PNGFormatter(BaseFormatter):
762 765 """A PNG formatter.
763 766
764 767 To define the callables that compute the PNG representation of your
765 768 objects, define a :meth:`_repr_png_` method or use the :meth:`for_type`
766 769 or :meth:`for_type_by_name` methods to register functions that handle
767 770 this.
768 771
769 772 The return value of this formatter should be raw PNG data, *not*
770 773 base64 encoded.
771 774 """
772 775 format_type = Unicode('image/png')
773 776
774 777 print_method = ObjectName('_repr_png_')
775 778
776 779 _return_type = (bytes, str)
777 780
778 781
779 782 class JPEGFormatter(BaseFormatter):
780 783 """A JPEG formatter.
781 784
782 785 To define the callables that compute the JPEG representation of your
783 786 objects, define a :meth:`_repr_jpeg_` method or use the :meth:`for_type`
784 787 or :meth:`for_type_by_name` methods to register functions that handle
785 788 this.
786 789
787 790 The return value of this formatter should be raw JPEG data, *not*
788 791 base64 encoded.
789 792 """
790 793 format_type = Unicode('image/jpeg')
791 794
792 795 print_method = ObjectName('_repr_jpeg_')
793 796
794 797 _return_type = (bytes, str)
795 798
796 799
797 800 class LatexFormatter(BaseFormatter):
798 801 """A LaTeX formatter.
799 802
800 803 To define the callables that compute the LaTeX representation of your
801 804 objects, define a :meth:`_repr_latex_` method or use the :meth:`for_type`
802 805 or :meth:`for_type_by_name` methods to register functions that handle
803 806 this.
804 807
805 808 The return value of this formatter should be a valid LaTeX equation,
806 809 enclosed in either ```$```, ```$$``` or another LaTeX equation
807 810 environment.
808 811 """
809 812 format_type = Unicode('text/latex')
810 813
811 814 print_method = ObjectName('_repr_latex_')
812 815
813 816
814 817 class JSONFormatter(BaseFormatter):
815 818 """A JSON string formatter.
816 819
817 820 To define the callables that compute the JSONable representation of
818 821 your objects, define a :meth:`_repr_json_` method or use the :meth:`for_type`
819 822 or :meth:`for_type_by_name` methods to register functions that handle
820 823 this.
821 824
822 825 The return value of this formatter should be a JSONable list or dict.
823 826 JSON scalars (None, number, string) are not allowed, only dict or list containers.
824 827 """
825 828 format_type = Unicode('application/json')
826 829 _return_type = (list, dict)
827 830
828 831 print_method = ObjectName('_repr_json_')
829 832
830 833 def _check_return(self, r, obj):
831 834 """Check that a return value is appropriate
832 835
833 836 Return the value if so, None otherwise, warning if invalid.
834 837 """
835 838 if r is None:
836 839 return
837 840 md = None
838 841 if isinstance(r, tuple):
839 842 # unpack data, metadata tuple for type checking on first element
840 843 r, md = r
841 844
842 845 assert not isinstance(
843 846 r, str
844 847 ), "JSON-as-string has been deprecated since IPython < 3"
845 848
846 849 if md is not None:
847 850 # put the tuple back together
848 851 r = (r, md)
849 852 return super(JSONFormatter, self)._check_return(r, obj)
850 853
851 854
852 855 class JavascriptFormatter(BaseFormatter):
853 856 """A Javascript formatter.
854 857
855 858 To define the callables that compute the Javascript representation of
856 859 your objects, define a :meth:`_repr_javascript_` method or use the
857 860 :meth:`for_type` or :meth:`for_type_by_name` methods to register functions
858 861 that handle this.
859 862
860 863 The return value of this formatter should be valid Javascript code and
861 864 should *not* be enclosed in ```<script>``` tags.
862 865 """
863 866 format_type = Unicode('application/javascript')
864 867
865 868 print_method = ObjectName('_repr_javascript_')
866 869
867 870
868 871 class PDFFormatter(BaseFormatter):
869 872 """A PDF formatter.
870 873
871 874 To define the callables that compute the PDF representation of your
872 875 objects, define a :meth:`_repr_pdf_` method or use the :meth:`for_type`
873 876 or :meth:`for_type_by_name` methods to register functions that handle
874 877 this.
875 878
876 879 The return value of this formatter should be raw PDF data, *not*
877 880 base64 encoded.
878 881 """
879 882 format_type = Unicode('application/pdf')
880 883
881 884 print_method = ObjectName('_repr_pdf_')
882 885
883 886 _return_type = (bytes, str)
884 887
885 888 class IPythonDisplayFormatter(BaseFormatter):
886 889 """An escape-hatch Formatter for objects that know how to display themselves.
887 890
888 891 To define the callables that compute the representation of your
889 892 objects, define a :meth:`_ipython_display_` method or use the :meth:`for_type`
890 893 or :meth:`for_type_by_name` methods to register functions that handle
891 894 this. Unlike mime-type displays, this method should not return anything,
892 895 instead calling any appropriate display methods itself.
893 896
894 897 This display formatter has highest priority.
895 898 If it fires, no other display formatter will be called.
896 899
897 900 Prior to IPython 6.1, `_ipython_display_` was the only way to display custom mime-types
898 901 without registering a new Formatter.
899 902
900 903 IPython 6.1 introduces `_repr_mimebundle_` for displaying custom mime-types,
901 904 so `_ipython_display_` should only be used for objects that require unusual
902 905 display patterns, such as multiple display calls.
903 906 """
904 907 print_method = ObjectName('_ipython_display_')
905 908 _return_type = (type(None), bool)
906 909
907 910 @catch_format_error
908 911 def __call__(self, obj):
909 912 """Compute the format for an object."""
910 913 if self.enabled:
911 914 # lookup registered printer
912 915 try:
913 916 printer = self.lookup(obj)
914 917 except KeyError:
915 918 pass
916 919 else:
917 920 printer(obj)
918 921 return True
919 922 # Finally look for special method names
920 923 method = get_real_method(obj, self.print_method)
921 924 if method is not None:
922 925 method()
923 926 return True
924 927
925 928
926 929 class MimeBundleFormatter(BaseFormatter):
927 930 """A Formatter for arbitrary mime-types.
928 931
929 932 Unlike other `_repr_<mimetype>_` methods,
930 933 `_repr_mimebundle_` should return mime-bundle data,
931 934 either the mime-keyed `data` dictionary or the tuple `(data, metadata)`.
932 935 Any mime-type is valid.
933 936
934 937 To define the callables that compute the mime-bundle representation of your
935 938 objects, define a :meth:`_repr_mimebundle_` method or use the :meth:`for_type`
936 939 or :meth:`for_type_by_name` methods to register functions that handle
937 940 this.
938 941
939 942 .. versionadded:: 6.1
940 943 """
941 944 print_method = ObjectName('_repr_mimebundle_')
942 945 _return_type = dict
943 946
944 947 def _check_return(self, r, obj):
945 948 r = super(MimeBundleFormatter, self)._check_return(r, obj)
946 949 # always return (data, metadata):
947 950 if r is None:
948 951 return {}, {}
949 952 if not isinstance(r, tuple):
950 953 return r, {}
951 954 return r
952 955
953 956 @catch_format_error
954 957 def __call__(self, obj, include=None, exclude=None):
955 958 """Compute the format for an object.
956 959
957 960 Identical to parent's method but we pass extra parameters to the method.
958 961
959 962 Unlike other _repr_*_ `_repr_mimebundle_` should allow extra kwargs, in
960 963 particular `include` and `exclude`.
961 964 """
962 965 if self.enabled:
963 966 # lookup registered printer
964 967 try:
965 968 printer = self.lookup(obj)
966 969 except KeyError:
967 970 pass
968 971 else:
969 972 return printer(obj)
970 973 # Finally look for special method names
971 974 method = get_real_method(obj, self.print_method)
972 975
973 976 if method is not None:
974 977 return method(include=include, exclude=exclude)
975 978 return None
976 979 else:
977 980 return None
978 981
979 982
980 983 FormatterABC.register(BaseFormatter)
981 984 FormatterABC.register(PlainTextFormatter)
982 985 FormatterABC.register(HTMLFormatter)
983 986 FormatterABC.register(MarkdownFormatter)
984 987 FormatterABC.register(SVGFormatter)
985 988 FormatterABC.register(PNGFormatter)
986 989 FormatterABC.register(PDFFormatter)
987 990 FormatterABC.register(JPEGFormatter)
988 991 FormatterABC.register(LatexFormatter)
989 992 FormatterABC.register(JSONFormatter)
990 993 FormatterABC.register(JavascriptFormatter)
991 994 FormatterABC.register(IPythonDisplayFormatter)
992 995 FormatterABC.register(MimeBundleFormatter)
993 996
994 997
995 998 def format_display_data(obj, include=None, exclude=None):
996 999 """Return a format data dict for an object.
997 1000
998 1001 By default all format types will be computed.
999 1002
1000 1003 Parameters
1001 1004 ----------
1002 1005 obj : object
1003 1006 The Python object whose format data will be computed.
1004 1007
1005 1008 Returns
1006 1009 -------
1007 1010 format_dict : dict
1008 1011 A dictionary of key/value pairs, one or each format that was
1009 1012 generated for the object. The keys are the format types, which
1010 1013 will usually be MIME type strings and the values and JSON'able
1011 1014 data structure containing the raw data for the representation in
1012 1015 that format.
1013 1016 include : list or tuple, optional
1014 1017 A list of format type strings (MIME types) to include in the
1015 1018 format data dict. If this is set *only* the format types included
1016 1019 in this list will be computed.
1017 1020 exclude : list or tuple, optional
1018 1021 A list of format type string (MIME types) to exclude in the format
1019 1022 data dict. If this is set all format types will be computed,
1020 1023 except for those included in this argument.
1021 1024 """
1022 1025 from .interactiveshell import InteractiveShell
1023 1026
1024 1027 return InteractiveShell.instance().display_formatter.format(
1025 1028 obj,
1026 1029 include,
1027 1030 exclude
1028 1031 )
@@ -1,320 +1,320 b''
1 1 """
2 2 This module contains utility function and classes to inject simple ast
3 3 transformations based on code strings into IPython. While it is already possible
4 4 with ast-transformers it is not easy to directly manipulate ast.
5 5
6 6
7 7 IPython has pre-code and post-code hooks, but are ran from within the IPython
8 8 machinery so may be inappropriate, for example for performance mesurement.
9 9
10 10 This module give you tools to simplify this, and expose 2 classes:
11 11
12 12 - `ReplaceCodeTransformer` which is a simple ast transformer based on code
13 13 template,
14 14
15 15 and for advance case:
16 16
17 17 - `Mangler` which is a simple ast transformer that mangle names in the ast.
18 18
19 19
20 20 Example, let's try to make a simple version of the ``timeit`` magic, that run a
21 21 code snippet 10 times and print the average time taken.
22 22
23 23 Basically we want to run :
24 24
25 25 .. code-block:: python
26 26
27 27 from time import perf_counter
28 28 now = perf_counter()
29 29 for i in range(10):
30 30 __code__ # our code
31 31 print(f"Time taken: {(perf_counter() - now)/10}")
32 32 __ret__ # the result of the last statement
33 33
34 34 Where ``__code__`` is the code snippet we want to run, and ``__ret__`` is the
35 35 result, so that if we for example run `dataframe.head()` IPython still display
36 36 the head of dataframe instead of nothing.
37 37
38 38 Here is a complete example of a file `timit2.py` that define such a magic:
39 39
40 40 .. code-block:: python
41 41
42 42 from IPython.core.magic import (
43 43 Magics,
44 44 magics_class,
45 45 line_cell_magic,
46 46 )
47 47 from IPython.core.magics.ast_mod import ReplaceCodeTransformer
48 48 from textwrap import dedent
49 49 import ast
50 50
51 51 template = template = dedent('''
52 52 from time import perf_counter
53 53 now = perf_counter()
54 54 for i in range(10):
55 55 __code__
56 56 print(f"Time taken: {(perf_counter() - now)/10}")
57 57 __ret__
58 58 '''
59 59 )
60 60
61 61
62 62 @magics_class
63 63 class AstM(Magics):
64 64 @line_cell_magic
65 65 def t2(self, line, cell):
66 66 transformer = ReplaceCodeTransformer.from_string(template)
67 67 transformer.debug = True
68 68 transformer.mangler.debug = True
69 69 new_code = transformer.visit(ast.parse(cell))
70 70 return exec(compile(new_code, "<ast>", "exec"))
71 71
72 72
73 73 def load_ipython_extension(ip):
74 74 ip.register_magics(AstM)
75 75
76 76
77 77
78 78 .. code-block:: python
79 79
80 80 In [1]: %load_ext timit2
81 81
82 82 In [2]: %%t2
83 83 ...: import time
84 84 ...: time.sleep(0.05)
85 85 ...:
86 86 ...:
87 87 Time taken: 0.05435649999999441
88 88
89 89
90 90 If you wish to ran all the code enter in IPython in an ast transformer, you can
91 91 do so as well:
92 92
93 93 .. code-block:: python
94 94
95 95 In [1]: from IPython.core.magics.ast_mod import ReplaceCodeTransformer
96 96 ...:
97 97 ...: template = '''
98 98 ...: from time import perf_counter
99 99 ...: now = perf_counter()
100 100 ...: __code__
101 101 ...: print(f"Code ran in {perf_counter()-now}")
102 102 ...: __ret__'''
103 103 ...:
104 104 ...: get_ipython().ast_transformers.append(ReplaceCodeTransformer.from_string(template))
105 105
106 106 In [2]: 1+1
107 107 Code ran in 3.40410006174352e-05
108 108 Out[2]: 2
109 109
110 110
111 111
112 112 Hygiene and Mangling
113 113 --------------------
114 114
115 115 The ast transformer above is not hygienic, it may not work if the user code use
116 116 the same variable names as the ones used in the template. For example.
117 117
118 118 To help with this by default the `ReplaceCodeTransformer` will mangle all names
119 119 staring with 3 underscores. This is a simple heuristic that should work in most
120 120 case, but can be cumbersome in some case. We provide a `Mangler` class that can
121 121 be overridden to change the mangling heuristic, or simply use the `mangle_all`
122 122 utility function. It will _try_ to mangle all names (except `__ret__` and
123 123 `__code__`), but this include builtins (``print``, ``range``, ``type``) and
124 124 replace those by invalid identifiers py prepending ``mangle-``:
125 125 ``mangle-print``, ``mangle-range``, ``mangle-type`` etc. This is not a problem
126 126 as currently Python AST support invalid identifiers, but it may not be the case
127 127 in the future.
128 128
129 129 You can set `ReplaceCodeTransformer.debug=True` and
130 130 `ReplaceCodeTransformer.mangler.debug=True` to see the code after mangling and
131 131 transforming:
132 132
133 133 .. code-block:: python
134 134
135 135
136 136 In [1]: from IPython.core.magics.ast_mod import ReplaceCodeTransformer, mangle_all
137 137 ...:
138 138 ...: template = '''
139 139 ...: from builtins import type, print
140 140 ...: from time import perf_counter
141 141 ...: now = perf_counter()
142 142 ...: __code__
143 143 ...: print(f"Code ran in {perf_counter()-now}")
144 144 ...: __ret__'''
145 145 ...:
146 146 ...: transformer = ReplaceCodeTransformer.from_string(template, mangling_predicate=mangle_all)
147 147
148 148
149 149 In [2]: transformer.debug = True
150 150 ...: transformer.mangler.debug = True
151 151 ...: get_ipython().ast_transformers.append(transformer)
152 152
153 153 In [3]: 1+1
154 154 Mangling Alias mangle-type
155 155 Mangling Alias mangle-print
156 156 Mangling Alias mangle-perf_counter
157 157 Mangling now
158 158 Mangling perf_counter
159 159 Not mangling __code__
160 160 Mangling print
161 161 Mangling perf_counter
162 162 Mangling now
163 163 Not mangling __ret__
164 164 ---- Transformed code ----
165 165 from builtins import type as mangle-type, print as mangle-print
166 166 from time import perf_counter as mangle-perf_counter
167 167 mangle-now = mangle-perf_counter()
168 168 ret-tmp = 1 + 1
169 169 mangle-print(f'Code ran in {mangle-perf_counter() - mangle-now}')
170 170 ret-tmp
171 171 ---- ---------------- ----
172 172 Code ran in 0.00013654199938173406
173 173 Out[3]: 2
174 174
175 175
176 176 """
177 177
178 178 __skip_doctest__ = True
179 179
180 180
181 from ast import NodeTransformer, Store, Load, Name, Expr, Assign, Module
181 from ast import NodeTransformer, Store, Load, Name, Expr, Assign, Module, Import, ImportFrom
182 182 import ast
183 183 import copy
184 184
185 from typing import Dict, Optional
185 from typing import Dict, Optional, Union
186 186
187 187
188 188 mangle_all = lambda name: False if name in ("__ret__", "__code__") else True
189 189
190 190
191 191 class Mangler(NodeTransformer):
192 192 """
193 193 Mangle given names in and ast tree to make sure they do not conflict with
194 194 user code.
195 195 """
196 196
197 197 enabled: bool = True
198 198 debug: bool = False
199 199
200 200 def log(self, *args, **kwargs):
201 201 if self.debug:
202 202 print(*args, **kwargs)
203 203
204 204 def __init__(self, predicate=None):
205 205 if predicate is None:
206 206 predicate = lambda name: name.startswith("___")
207 207 self.predicate = predicate
208 208
209 209 def visit_Name(self, node):
210 210 if self.predicate(node.id):
211 211 self.log("Mangling", node.id)
212 212 # Once in the ast we do not need
213 213 # names to be valid identifiers.
214 214 node.id = "mangle-" + node.id
215 215 else:
216 216 self.log("Not mangling", node.id)
217 217 return node
218 218
219 219 def visit_FunctionDef(self, node):
220 220 if self.predicate(node.name):
221 221 self.log("Mangling", node.name)
222 222 node.name = "mangle-" + node.name
223 223 else:
224 224 self.log("Not mangling", node.name)
225 225
226 226 for arg in node.args.args:
227 227 if self.predicate(arg.arg):
228 228 self.log("Mangling function arg", arg.arg)
229 229 arg.arg = "mangle-" + arg.arg
230 230 else:
231 231 self.log("Not mangling function arg", arg.arg)
232 232 return self.generic_visit(node)
233 233
234 def visit_ImportFrom(self, node):
234 def visit_ImportFrom(self, node:ImportFrom):
235 235 return self._visit_Import_and_ImportFrom(node)
236 236
237 def visit_Import(self, node):
237 def visit_Import(self, node:Import):
238 238 return self._visit_Import_and_ImportFrom(node)
239 239
240 def _visit_Import_and_ImportFrom(self, node):
240 def _visit_Import_and_ImportFrom(self, node:Union[Import, ImportFrom]):
241 241 for alias in node.names:
242 242 asname = alias.name if alias.asname is None else alias.asname
243 243 if self.predicate(asname):
244 244 new_name: str = "mangle-" + asname
245 245 self.log("Mangling Alias", new_name)
246 246 alias.asname = new_name
247 247 else:
248 248 self.log("Not mangling Alias", alias.asname)
249 249 return node
250 250
251 251
252 252 class ReplaceCodeTransformer(NodeTransformer):
253 253 enabled: bool = True
254 254 debug: bool = False
255 255 mangler: Mangler
256 256
257 257 def __init__(
258 258 self, template: Module, mapping: Optional[Dict] = None, mangling_predicate=None
259 259 ):
260 260 assert isinstance(mapping, (dict, type(None)))
261 261 assert isinstance(mangling_predicate, (type(None), type(lambda: None)))
262 262 assert isinstance(template, ast.Module)
263 263 self.template = template
264 264 self.mangler = Mangler(predicate=mangling_predicate)
265 265 if mapping is None:
266 266 mapping = {}
267 267 self.mapping = mapping
268 268
269 269 @classmethod
270 270 def from_string(
271 271 cls, template: str, mapping: Optional[Dict] = None, mangling_predicate=None
272 272 ):
273 273 return cls(
274 274 ast.parse(template), mapping=mapping, mangling_predicate=mangling_predicate
275 275 )
276 276
277 277 def visit_Module(self, code):
278 278 if not self.enabled:
279 279 return code
280 280 # if not isinstance(code, ast.Module):
281 281 # recursively called...
282 282 # return generic_visit(self, code)
283 283 last = code.body[-1]
284 284 if isinstance(last, Expr):
285 285 code.body.pop()
286 286 code.body.append(Assign([Name("ret-tmp", ctx=Store())], value=last.value))
287 287 ast.fix_missing_locations(code)
288 288 ret = Expr(value=Name("ret-tmp", ctx=Load()))
289 289 ret = ast.fix_missing_locations(ret)
290 290 self.mapping["__ret__"] = ret
291 291 else:
292 292 self.mapping["__ret__"] = ast.parse("None").body[0]
293 293 self.mapping["__code__"] = code.body
294 294 tpl = ast.fix_missing_locations(self.template)
295 295
296 296 tx = copy.deepcopy(tpl)
297 297 tx = self.mangler.visit(tx)
298 298 node = self.generic_visit(tx)
299 299 node_2 = ast.fix_missing_locations(node)
300 300 if self.debug:
301 301 print("---- Transformed code ----")
302 302 print(ast.unparse(node_2))
303 303 print("---- ---------------- ----")
304 304 return node_2
305 305
306 306 # this does not work as the name might be in a list and one might want to extend the list.
307 307 # def visit_Name(self, name):
308 308 # if name.id in self.mapping and name.id == "__ret__":
309 309 # print(name, "in mapping")
310 310 # if isinstance(name.ctx, ast.Store):
311 311 # return Name("tmp", ctx=Store())
312 312 # else:
313 313 # return copy.deepcopy(self.mapping[name.id])
314 314 # return name
315 315
316 316 def visit_Expr(self, expr):
317 317 if isinstance(expr.value, Name) and expr.value.id in self.mapping:
318 318 if self.mapping[expr.value.id] is not None:
319 319 return copy.deepcopy(self.mapping[expr.value.id])
320 320 return self.generic_visit(expr)
@@ -1,799 +1,805 b''
1 # encoding: utf-8
2 1 """
3 2 Utilities for working with strings and text.
4 3
5 4 Inheritance diagram:
6 5
7 6 .. inheritance-diagram:: IPython.utils.text
8 7 :parts: 3
9 8 """
10 9
11 10 import os
12 11 import re
13 12 import string
14 import sys
15 13 import textwrap
16 14 import warnings
17 15 from string import Formatter
18 16 from pathlib import Path
19 17
20 from typing import List, Dict, Tuple
18 from typing import List, Dict, Tuple, Optional, cast
21 19
22 20
23 21 class LSString(str):
24 22 """String derivative with a special access attributes.
25 23
26 24 These are normal strings, but with the special attributes:
27 25
28 26 .l (or .list) : value as list (split on newlines).
29 27 .n (or .nlstr): original value (the string itself).
30 28 .s (or .spstr): value as whitespace-separated string.
31 29 .p (or .paths): list of path objects (requires path.py package)
32 30
33 31 Any values which require transformations are computed only once and
34 32 cached.
35 33
36 34 Such strings are very useful to efficiently interact with the shell, which
37 35 typically only understands whitespace-separated options for commands."""
38 36
39 37 def get_list(self):
40 38 try:
41 39 return self.__list
42 40 except AttributeError:
43 41 self.__list = self.split('\n')
44 42 return self.__list
45 43
46 44 l = list = property(get_list)
47 45
48 46 def get_spstr(self):
49 47 try:
50 48 return self.__spstr
51 49 except AttributeError:
52 50 self.__spstr = self.replace('\n',' ')
53 51 return self.__spstr
54 52
55 53 s = spstr = property(get_spstr)
56 54
57 55 def get_nlstr(self):
58 56 return self
59 57
60 58 n = nlstr = property(get_nlstr)
61 59
62 60 def get_paths(self):
63 61 try:
64 62 return self.__paths
65 63 except AttributeError:
66 64 self.__paths = [Path(p) for p in self.split('\n') if os.path.exists(p)]
67 65 return self.__paths
68 66
69 67 p = paths = property(get_paths)
70 68
71 69 # FIXME: We need to reimplement type specific displayhook and then add this
72 70 # back as a custom printer. This should also be moved outside utils into the
73 71 # core.
74 72
75 73 # def print_lsstring(arg):
76 74 # """ Prettier (non-repr-like) and more informative printer for LSString """
77 75 # print "LSString (.p, .n, .l, .s available). Value:"
78 76 # print arg
79 77 #
80 78 #
81 79 # print_lsstring = result_display.register(LSString)(print_lsstring)
82 80
83 81
84 82 class SList(list):
85 83 """List derivative with a special access attributes.
86 84
87 85 These are normal lists, but with the special attributes:
88 86
89 87 * .l (or .list) : value as list (the list itself).
90 88 * .n (or .nlstr): value as a string, joined on newlines.
91 89 * .s (or .spstr): value as a string, joined on spaces.
92 90 * .p (or .paths): list of path objects (requires path.py package)
93 91
94 92 Any values which require transformations are computed only once and
95 93 cached."""
96 94
97 95 def get_list(self):
98 96 return self
99 97
100 98 l = list = property(get_list)
101 99
102 100 def get_spstr(self):
103 101 try:
104 102 return self.__spstr
105 103 except AttributeError:
106 104 self.__spstr = ' '.join(self)
107 105 return self.__spstr
108 106
109 107 s = spstr = property(get_spstr)
110 108
111 109 def get_nlstr(self):
112 110 try:
113 111 return self.__nlstr
114 112 except AttributeError:
115 113 self.__nlstr = '\n'.join(self)
116 114 return self.__nlstr
117 115
118 116 n = nlstr = property(get_nlstr)
119 117
120 118 def get_paths(self):
121 119 try:
122 120 return self.__paths
123 121 except AttributeError:
124 122 self.__paths = [Path(p) for p in self if os.path.exists(p)]
125 123 return self.__paths
126 124
127 125 p = paths = property(get_paths)
128 126
129 127 def grep(self, pattern, prune = False, field = None):
130 128 """ Return all strings matching 'pattern' (a regex or callable)
131 129
132 130 This is case-insensitive. If prune is true, return all items
133 131 NOT matching the pattern.
134 132
135 133 If field is specified, the match must occur in the specified
136 134 whitespace-separated field.
137 135
138 136 Examples::
139 137
140 138 a.grep( lambda x: x.startswith('C') )
141 139 a.grep('Cha.*log', prune=1)
142 140 a.grep('chm', field=-1)
143 141 """
144 142
145 143 def match_target(s):
146 144 if field is None:
147 145 return s
148 146 parts = s.split()
149 147 try:
150 148 tgt = parts[field]
151 149 return tgt
152 150 except IndexError:
153 151 return ""
154 152
155 153 if isinstance(pattern, str):
156 154 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
157 155 else:
158 156 pred = pattern
159 157 if not prune:
160 158 return SList([el for el in self if pred(match_target(el))])
161 159 else:
162 160 return SList([el for el in self if not pred(match_target(el))])
163 161
164 162 def fields(self, *fields):
165 163 """ Collect whitespace-separated fields from string list
166 164
167 165 Allows quick awk-like usage of string lists.
168 166
169 167 Example data (in var a, created by 'a = !ls -l')::
170 168
171 169 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
172 170 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
173 171
174 172 * ``a.fields(0)`` is ``['-rwxrwxrwx', 'drwxrwxrwx+']``
175 173 * ``a.fields(1,0)`` is ``['1 -rwxrwxrwx', '6 drwxrwxrwx+']``
176 174 (note the joining by space).
177 175 * ``a.fields(-1)`` is ``['ChangeLog', 'IPython']``
178 176
179 177 IndexErrors are ignored.
180 178
181 179 Without args, fields() just split()'s the strings.
182 180 """
183 181 if len(fields) == 0:
184 182 return [el.split() for el in self]
185 183
186 184 res = SList()
187 185 for el in [f.split() for f in self]:
188 186 lineparts = []
189 187
190 188 for fd in fields:
191 189 try:
192 190 lineparts.append(el[fd])
193 191 except IndexError:
194 192 pass
195 193 if lineparts:
196 194 res.append(" ".join(lineparts))
197 195
198 196 return res
199 197
200 198 def sort(self,field= None, nums = False):
201 199 """ sort by specified fields (see fields())
202 200
203 201 Example::
204 202
205 203 a.sort(1, nums = True)
206 204
207 205 Sorts a by second field, in numerical order (so that 21 > 3)
208 206
209 207 """
210 208
211 209 #decorate, sort, undecorate
212 210 if field is not None:
213 211 dsu = [[SList([line]).fields(field), line] for line in self]
214 212 else:
215 213 dsu = [[line, line] for line in self]
216 214 if nums:
217 215 for i in range(len(dsu)):
218 216 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
219 217 try:
220 218 n = int(numstr)
221 219 except ValueError:
222 220 n = 0
223 221 dsu[i][0] = n
224 222
225 223
226 224 dsu.sort()
227 225 return SList([t[1] for t in dsu])
228 226
229 227
230 228 # FIXME: We need to reimplement type specific displayhook and then add this
231 229 # back as a custom printer. This should also be moved outside utils into the
232 230 # core.
233 231
234 232 # def print_slist(arg):
235 233 # """ Prettier (non-repr-like) and more informative printer for SList """
236 234 # print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):"
237 235 # if hasattr(arg, 'hideonce') and arg.hideonce:
238 236 # arg.hideonce = False
239 237 # return
240 238 #
241 239 # nlprint(arg) # This was a nested list printer, now removed.
242 240 #
243 241 # print_slist = result_display.register(SList)(print_slist)
244 242
245 243
246 244 def indent(instr,nspaces=4, ntabs=0, flatten=False):
247 245 """Indent a string a given number of spaces or tabstops.
248 246
249 247 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
250 248
251 249 Parameters
252 250 ----------
253 251 instr : basestring
254 252 The string to be indented.
255 253 nspaces : int (default: 4)
256 254 The number of spaces to be indented.
257 255 ntabs : int (default: 0)
258 256 The number of tabs to be indented.
259 257 flatten : bool (default: False)
260 258 Whether to scrub existing indentation. If True, all lines will be
261 259 aligned to the same indentation. If False, existing indentation will
262 260 be strictly increased.
263 261
264 262 Returns
265 263 -------
266 264 str|unicode : string indented by ntabs and nspaces.
267 265
268 266 """
269 267 if instr is None:
270 268 return
271 269 ind = '\t'*ntabs+' '*nspaces
272 270 if flatten:
273 271 pat = re.compile(r'^\s*', re.MULTILINE)
274 272 else:
275 273 pat = re.compile(r'^', re.MULTILINE)
276 274 outstr = re.sub(pat, ind, instr)
277 275 if outstr.endswith(os.linesep+ind):
278 276 return outstr[:-len(ind)]
279 277 else:
280 278 return outstr
281 279
282 280
283 281 def list_strings(arg):
284 282 """Always return a list of strings, given a string or list of strings
285 283 as input.
286 284
287 285 Examples
288 286 --------
289 287 ::
290 288
291 289 In [7]: list_strings('A single string')
292 290 Out[7]: ['A single string']
293 291
294 292 In [8]: list_strings(['A single string in a list'])
295 293 Out[8]: ['A single string in a list']
296 294
297 295 In [9]: list_strings(['A','list','of','strings'])
298 296 Out[9]: ['A', 'list', 'of', 'strings']
299 297 """
300 298
301 299 if isinstance(arg, str):
302 300 return [arg]
303 301 else:
304 302 return arg
305 303
306 304
307 305 def marquee(txt='',width=78,mark='*'):
308 306 """Return the input string centered in a 'marquee'.
309 307
310 308 Examples
311 309 --------
312 310 ::
313 311
314 312 In [16]: marquee('A test',40)
315 313 Out[16]: '**************** A test ****************'
316 314
317 315 In [17]: marquee('A test',40,'-')
318 316 Out[17]: '---------------- A test ----------------'
319 317
320 318 In [18]: marquee('A test',40,' ')
321 319 Out[18]: ' A test '
322 320
323 321 """
324 322 if not txt:
325 323 return (mark*width)[:width]
326 324 nmark = (width-len(txt)-2)//len(mark)//2
327 325 if nmark < 0: nmark =0
328 326 marks = mark*nmark
329 327 return '%s %s %s' % (marks,txt,marks)
330 328
331 329
332 330 ini_spaces_re = re.compile(r'^(\s+)')
333 331
334 332 def num_ini_spaces(strng):
335 333 """Return the number of initial spaces in a string"""
336 334 warnings.warn(
337 335 "`num_ini_spaces` is Pending Deprecation since IPython 8.17."
338 336 "It is considered fro removal in in future version. "
339 337 "Please open an issue if you believe it should be kept.",
340 338 stacklevel=2,
341 339 category=PendingDeprecationWarning,
342 340 )
343 341 ini_spaces = ini_spaces_re.match(strng)
344 342 if ini_spaces:
345 343 return ini_spaces.end()
346 344 else:
347 345 return 0
348 346
349 347
350 348 def format_screen(strng):
351 349 """Format a string for screen printing.
352 350
353 351 This removes some latex-type format codes."""
354 352 # Paragraph continue
355 353 par_re = re.compile(r'\\$',re.MULTILINE)
356 354 strng = par_re.sub('',strng)
357 355 return strng
358 356
359 357
360 358 def dedent(text: str) -> str:
361 359 """Equivalent of textwrap.dedent that ignores unindented first line.
362 360
363 361 This means it will still dedent strings like:
364 362 '''foo
365 363 is a bar
366 364 '''
367 365
368 366 For use in wrap_paragraphs.
369 367 """
370 368
371 369 if text.startswith('\n'):
372 370 # text starts with blank line, don't ignore the first line
373 371 return textwrap.dedent(text)
374 372
375 373 # split first line
376 374 splits = text.split('\n',1)
377 375 if len(splits) == 1:
378 376 # only one line
379 377 return textwrap.dedent(text)
380 378
381 379 first, rest = splits
382 380 # dedent everything but the first line
383 381 rest = textwrap.dedent(rest)
384 382 return '\n'.join([first, rest])
385 383
386 384
387 385 def wrap_paragraphs(text, ncols=80):
388 386 """Wrap multiple paragraphs to fit a specified width.
389 387
390 388 This is equivalent to textwrap.wrap, but with support for multiple
391 389 paragraphs, as separated by empty lines.
392 390
393 391 Returns
394 392 -------
395 393 list of complete paragraphs, wrapped to fill `ncols` columns.
396 394 """
397 395 warnings.warn(
398 396 "`wrap_paragraphs` is Pending Deprecation since IPython 8.17."
399 397 "It is considered fro removal in in future version. "
400 398 "Please open an issue if you believe it should be kept.",
401 399 stacklevel=2,
402 400 category=PendingDeprecationWarning,
403 401 )
404 402 paragraph_re = re.compile(r'\n(\s*\n)+', re.MULTILINE)
405 403 text = dedent(text).strip()
406 404 paragraphs = paragraph_re.split(text)[::2] # every other entry is space
407 405 out_ps = []
408 406 indent_re = re.compile(r'\n\s+', re.MULTILINE)
409 407 for p in paragraphs:
410 408 # presume indentation that survives dedent is meaningful formatting,
411 409 # so don't fill unless text is flush.
412 410 if indent_re.search(p) is None:
413 411 # wrap paragraph
414 412 p = textwrap.fill(p, ncols)
415 413 out_ps.append(p)
416 414 return out_ps
417 415
418 416
419 417 def strip_email_quotes(text):
420 418 """Strip leading email quotation characters ('>').
421 419
422 420 Removes any combination of leading '>' interspersed with whitespace that
423 421 appears *identically* in all lines of the input text.
424 422
425 423 Parameters
426 424 ----------
427 425 text : str
428 426
429 427 Examples
430 428 --------
431 429
432 430 Simple uses::
433 431
434 432 In [2]: strip_email_quotes('> > text')
435 433 Out[2]: 'text'
436 434
437 435 In [3]: strip_email_quotes('> > text\\n> > more')
438 436 Out[3]: 'text\\nmore'
439 437
440 438 Note how only the common prefix that appears in all lines is stripped::
441 439
442 440 In [4]: strip_email_quotes('> > text\\n> > more\\n> more...')
443 441 Out[4]: '> text\\n> more\\nmore...'
444 442
445 443 So if any line has no quote marks ('>'), then none are stripped from any
446 444 of them ::
447 445
448 446 In [5]: strip_email_quotes('> > text\\n> > more\\nlast different')
449 447 Out[5]: '> > text\\n> > more\\nlast different'
450 448 """
451 449 lines = text.splitlines()
452 450 strip_len = 0
453 451
454 452 for characters in zip(*lines):
455 453 # Check if all characters in this position are the same
456 454 if len(set(characters)) > 1:
457 455 break
458 456 prefix_char = characters[0]
459 457
460 458 if prefix_char in string.whitespace or prefix_char == ">":
461 459 strip_len += 1
462 460 else:
463 461 break
464 462
465 463 text = "\n".join([ln[strip_len:] for ln in lines])
466 464 return text
467 465
468 466
469 467 def strip_ansi(source):
470 468 """
471 469 Remove ansi escape codes from text.
472 470
473 471 Parameters
474 472 ----------
475 473 source : str
476 474 Source to remove the ansi from
477 475 """
478 476 warnings.warn(
479 477 "`strip_ansi` is Pending Deprecation since IPython 8.17."
480 478 "It is considered fro removal in in future version. "
481 479 "Please open an issue if you believe it should be kept.",
482 480 stacklevel=2,
483 481 category=PendingDeprecationWarning,
484 482 )
485 483
486 484 return re.sub(r'\033\[(\d|;)+?m', '', source)
487 485
488 486
489 487 class EvalFormatter(Formatter):
490 488 """A String Formatter that allows evaluation of simple expressions.
491 489
492 490 Note that this version interprets a `:` as specifying a format string (as per
493 491 standard string formatting), so if slicing is required, you must explicitly
494 492 create a slice.
495 493
496 494 This is to be used in templating cases, such as the parallel batch
497 495 script templates, where simple arithmetic on arguments is useful.
498 496
499 497 Examples
500 498 --------
501 499 ::
502 500
503 501 In [1]: f = EvalFormatter()
504 502 In [2]: f.format('{n//4}', n=8)
505 503 Out[2]: '2'
506 504
507 505 In [3]: f.format("{greeting[slice(2,4)]}", greeting="Hello")
508 506 Out[3]: 'll'
509 507 """
510 508 def get_field(self, name, args, kwargs):
511 509 v = eval(name, kwargs)
512 510 return v, name
513 511
514 512 #XXX: As of Python 3.4, the format string parsing no longer splits on a colon
515 513 # inside [], so EvalFormatter can handle slicing. Once we only support 3.4 and
516 514 # above, it should be possible to remove FullEvalFormatter.
517 515
518 516 class FullEvalFormatter(Formatter):
519 517 """A String Formatter that allows evaluation of simple expressions.
520 518
521 519 Any time a format key is not found in the kwargs,
522 520 it will be tried as an expression in the kwargs namespace.
523 521
524 522 Note that this version allows slicing using [1:2], so you cannot specify
525 523 a format string. Use :class:`EvalFormatter` to permit format strings.
526 524
527 525 Examples
528 526 --------
529 527 ::
530 528
531 529 In [1]: f = FullEvalFormatter()
532 530 In [2]: f.format('{n//4}', n=8)
533 531 Out[2]: '2'
534 532
535 533 In [3]: f.format('{list(range(5))[2:4]}')
536 534 Out[3]: '[2, 3]'
537 535
538 536 In [4]: f.format('{3*2}')
539 537 Out[4]: '6'
540 538 """
541 539 # copied from Formatter._vformat with minor changes to allow eval
542 540 # and replace the format_spec code with slicing
543 def vformat(self, format_string:str, args, kwargs)->str:
541 def vformat(self, format_string: str, args, kwargs) -> str:
544 542 result = []
545 for literal_text, field_name, format_spec, conversion in \
546 self.parse(format_string):
547
543 conversion: Optional[str]
544 for literal_text, field_name, format_spec, conversion in self.parse(
545 format_string
546 ):
548 547 # output the literal text
549 548 if literal_text:
550 549 result.append(literal_text)
551 550
552 551 # if there's a field, output it
553 552 if field_name is not None:
554 553 # this is some markup, find the object and do
555 554 # the formatting
556 555
557 556 if format_spec:
558 557 # override format spec, to allow slicing:
559 558 field_name = ':'.join([field_name, format_spec])
560 559
561 560 # eval the contents of the field for the object
562 561 # to be formatted
563 562 obj = eval(field_name, kwargs)
564 563
565 564 # do any conversion on the resulting object
566 obj = self.convert_field(obj, conversion)
565 # type issue in typeshed, fined in https://github.com/python/typeshed/pull/11377
566 obj = self.convert_field(obj, conversion) # type: ignore[arg-type]
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 def columnize(items, row_first=False, separator=" ", displaywidth=80, spread=False):
725 def columnize(
726 items: List[str],
727 row_first: bool = False,
728 separator: str = " ",
729 displaywidth: int = 80,
730 spread: bool = False,
731 ):
726 732 """Transform a list of strings into a single string with columns.
727 733
728 734 Parameters
729 735 ----------
730 736 items : sequence of strings
731 737 The strings to process.
732 738 row_first : (default False)
733 739 Whether to compute columns for a row-first matrix instead of
734 740 column-first (default).
735 741 separator : str, optional [default is two spaces]
736 742 The string that separates columns.
737 743 displaywidth : int, optional [default is 80]
738 744 Width of the display in number of characters.
739 745
740 746 Returns
741 747 -------
742 748 The formatted string.
743 749 """
744 750 warnings.warn(
745 751 "`columnize` is Pending Deprecation since IPython 8.17."
746 "It is considered fro removal in in future version. "
752 "It is considered for removal in future versions. "
747 753 "Please open an issue if you believe it should be kept.",
748 754 stacklevel=2,
749 755 category=PendingDeprecationWarning,
750 756 )
751 757 if not items:
752 758 return "\n"
753 759 matrix: List[List[int]]
754 760 matrix, info = compute_item_matrix(
755 761 items,
756 762 row_first=row_first,
757 763 separator_size=len(separator),
758 764 displaywidth=displaywidth,
759 765 )
760 766 if spread:
761 767 separator = separator.ljust(int(info["optimal_separator_width"]))
762 768 fmatrix: List[filter[int]] = [filter(None, x) for x in matrix]
763 769 sjoin = lambda x: separator.join(
764 [y.ljust(w, " ") for y, w in zip(x, info["column_widths"])]
770 [y.ljust(w, " ") for y, w in zip(x, cast(List[int], info["column_widths"]))]
765 771 )
766 772 return "\n".join(map(sjoin, fmatrix)) + "\n"
767 773
768 774
769 775 def get_text_list(list_, last_sep=' and ', sep=", ", wrap_item_with=""):
770 776 """
771 777 Return a string with a natural enumeration of items
772 778
773 779 >>> get_text_list(['a', 'b', 'c', 'd'])
774 780 'a, b, c and d'
775 781 >>> get_text_list(['a', 'b', 'c'], ' or ')
776 782 'a, b or c'
777 783 >>> get_text_list(['a', 'b', 'c'], ', ')
778 784 'a, b, c'
779 785 >>> get_text_list(['a', 'b'], ' or ')
780 786 'a or b'
781 787 >>> get_text_list(['a'])
782 788 'a'
783 789 >>> get_text_list([])
784 790 ''
785 791 >>> get_text_list(['a', 'b'], wrap_item_with="`")
786 792 '`a` and `b`'
787 793 >>> get_text_list(['a', 'b', 'c', 'd'], " = ", sep=" + ")
788 794 'a + b + c = d'
789 795 """
790 796 if len(list_) == 0:
791 797 return ''
792 798 if wrap_item_with:
793 799 list_ = ['%s%s%s' % (wrap_item_with, item, wrap_item_with) for
794 800 item in list_]
795 801 if len(list_) == 1:
796 802 return list_[0]
797 803 return '%s%s%s' % (
798 804 sep.join(i for i in list_[:-1]),
799 805 last_sep, list_[-1])
General Comments 0
You need to be logged in to leave comments. Login now