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