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( |
|
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( |
|
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 f |
|
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