##// END OF EJS Templates
Backport PR #14601 on branch 8.x (Deprecate inputtransformer since 7.0) (#14603)...
M Bussonnier -
r28994:c696eefa merge 8.x
parent child Browse files
Show More
@@ -1,544 +1,577
1 """DEPRECATED: Input transformer classes to support IPython special syntax.
1 """DEPRECATED: Input transformer classes to support IPython special syntax.
2
2
3 This module was deprecated in IPython 7.0, in favour of inputtransformer2.
3 This module was deprecated in IPython 7.0, in favour of inputtransformer2.
4
4
5 This includes the machinery to recognise and transform ``%magic`` commands,
5 This includes the machinery to recognise and transform ``%magic`` commands,
6 ``!system`` commands, ``help?`` querying, prompt stripping, and so forth.
6 ``!system`` commands, ``help?`` querying, prompt stripping, and so forth.
7 """
7 """
8 import abc
8 import abc
9 import functools
9 import functools
10 import re
10 import re
11 import tokenize
11 import tokenize
12 import warnings
12 from tokenize import untokenize, TokenError
13 from tokenize import untokenize, TokenError
13 from io import StringIO
14 from io import StringIO
14
15
15 from IPython.core.splitinput import LineInfo
16 from IPython.core.splitinput import LineInfo
16 from IPython.utils import tokenutil
17 from IPython.utils import tokenutil
17
18
18 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
19 # Globals
20 # Globals
20 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
21
22
22 # The escape sequences that define the syntax transformations IPython will
23 # The escape sequences that define the syntax transformations IPython will
23 # apply to user input. These can NOT be just changed here: many regular
24 # apply to user input. These can NOT be just changed here: many regular
24 # expressions and other parts of the code may use their hardcoded values, and
25 # expressions and other parts of the code may use their hardcoded values, and
25 # for all intents and purposes they constitute the 'IPython syntax', so they
26 # for all intents and purposes they constitute the 'IPython syntax', so they
26 # should be considered fixed.
27 # should be considered fixed.
27
28
28 ESC_SHELL = '!' # Send line to underlying system shell
29 ESC_SHELL = '!' # Send line to underlying system shell
29 ESC_SH_CAP = '!!' # Send line to system shell and capture output
30 ESC_SH_CAP = '!!' # Send line to system shell and capture output
30 ESC_HELP = '?' # Find information about object
31 ESC_HELP = '?' # Find information about object
31 ESC_HELP2 = '??' # Find extra-detailed information about object
32 ESC_HELP2 = '??' # Find extra-detailed information about object
32 ESC_MAGIC = '%' # Call magic function
33 ESC_MAGIC = '%' # Call magic function
33 ESC_MAGIC2 = '%%' # Call cell-magic function
34 ESC_MAGIC2 = '%%' # Call cell-magic function
34 ESC_QUOTE = ',' # Split args on whitespace, quote each as string and call
35 ESC_QUOTE = ',' # Split args on whitespace, quote each as string and call
35 ESC_QUOTE2 = ';' # Quote all args as a single string, call
36 ESC_QUOTE2 = ';' # Quote all args as a single string, call
36 ESC_PAREN = '/' # Call first argument with rest of line as arguments
37 ESC_PAREN = '/' # Call first argument with rest of line as arguments
37
38
38 ESC_SEQUENCES = [ESC_SHELL, ESC_SH_CAP, ESC_HELP ,\
39 ESC_SEQUENCES = [ESC_SHELL, ESC_SH_CAP, ESC_HELP ,\
39 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,\
40 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,\
40 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN ]
41 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN ]
41
42
42
43
43 class InputTransformer(metaclass=abc.ABCMeta):
44 class InputTransformer(metaclass=abc.ABCMeta):
44 """Abstract base class for line-based input transformers."""
45 """Abstract base class for line-based input transformers."""
45
46
47 def __init__(self):
48 warnings.warn(
49 "`InputTransformer` has been deprecated since IPython 7.0"
50 " and emit a warnig since IPython 8.31, it"
51 " will be removed in the future",
52 DeprecationWarning,
53 stacklevel=2,
54 )
55
46 @abc.abstractmethod
56 @abc.abstractmethod
47 def push(self, line):
57 def push(self, line):
48 """Send a line of input to the transformer, returning the transformed
58 """Send a line of input to the transformer, returning the transformed
49 input or None if the transformer is waiting for more input.
59 input or None if the transformer is waiting for more input.
50
60
51 Must be overridden by subclasses.
61 Must be overridden by subclasses.
52
62
53 Implementations may raise ``SyntaxError`` if the input is invalid. No
63 Implementations may raise ``SyntaxError`` if the input is invalid. No
54 other exceptions may be raised.
64 other exceptions may be raised.
55 """
65 """
56 pass
66 pass
57
67
58 @abc.abstractmethod
68 @abc.abstractmethod
59 def reset(self):
69 def reset(self):
60 """Return, transformed any lines that the transformer has accumulated,
70 """Return, transformed any lines that the transformer has accumulated,
61 and reset its internal state.
71 and reset its internal state.
62
72
63 Must be overridden by subclasses.
73 Must be overridden by subclasses.
64 """
74 """
65 pass
75 pass
66
76
67 @classmethod
77 @classmethod
68 def wrap(cls, func):
78 def wrap(cls, func):
69 """Can be used by subclasses as a decorator, to return a factory that
79 """Can be used by subclasses as a decorator, to return a factory that
70 will allow instantiation with the decorated object.
80 will allow instantiation with the decorated object.
71 """
81 """
72 @functools.wraps(func)
82 @functools.wraps(func)
73 def transformer_factory(**kwargs):
83 def transformer_factory(**kwargs):
74 return cls(func, **kwargs) # type: ignore [call-arg]
84 return cls(func, **kwargs) # type: ignore [call-arg]
75
85
76 return transformer_factory
86 return transformer_factory
77
87
78 class StatelessInputTransformer(InputTransformer):
88 class StatelessInputTransformer(InputTransformer):
79 """Wrapper for a stateless input transformer implemented as a function."""
89 """Wrapper for a stateless input transformer implemented as a function."""
80 def __init__(self, func):
90 def __init__(self, func):
91 super().__init__()
92 warnings.warn(
93 "`StatelessInputTransformer` has been deprecated since IPython 7.0"
94 " and emit a warnig since IPython 8.31, it"
95 " will be removed in the future",
96 DeprecationWarning,
97 stacklevel=2,
98 )
81 self.func = func
99 self.func = func
82
100
83 def __repr__(self):
101 def __repr__(self):
84 return "StatelessInputTransformer(func={0!r})".format(self.func)
102 return "StatelessInputTransformer(func={0!r})".format(self.func)
85
103
86 def push(self, line):
104 def push(self, line):
87 """Send a line of input to the transformer, returning the
105 """Send a line of input to the transformer, returning the
88 transformed input."""
106 transformed input."""
89 return self.func(line)
107 return self.func(line)
90
108
91 def reset(self):
109 def reset(self):
92 """No-op - exists for compatibility."""
110 """No-op - exists for compatibility."""
93 pass
111 pass
94
112
95 class CoroutineInputTransformer(InputTransformer):
113 class CoroutineInputTransformer(InputTransformer):
96 """Wrapper for an input transformer implemented as a coroutine."""
114 """Wrapper for an input transformer implemented as a coroutine."""
97 def __init__(self, coro, **kwargs):
115 def __init__(self, coro, **kwargs):
98 # Prime it
116 # Prime it
117 super().__init__()
118 warnings.warn(
119 "`CoroutineInputTransformer` has been deprecated since IPython 7.0"
120 " and emit a warnig since IPython 8.31, it"
121 " will be removed in the future",
122 DeprecationWarning,
123 stacklevel=2,
124 )
99 self.coro = coro(**kwargs)
125 self.coro = coro(**kwargs)
100 next(self.coro)
126 next(self.coro)
101
127
102 def __repr__(self):
128 def __repr__(self):
103 return "CoroutineInputTransformer(coro={0!r})".format(self.coro)
129 return "CoroutineInputTransformer(coro={0!r})".format(self.coro)
104
130
105 def push(self, line):
131 def push(self, line):
106 """Send a line of input to the transformer, returning the
132 """Send a line of input to the transformer, returning the
107 transformed input or None if the transformer is waiting for more
133 transformed input or None if the transformer is waiting for more
108 input.
134 input.
109 """
135 """
110 return self.coro.send(line)
136 return self.coro.send(line)
111
137
112 def reset(self):
138 def reset(self):
113 """Return, transformed any lines that the transformer has
139 """Return, transformed any lines that the transformer has
114 accumulated, and reset its internal state.
140 accumulated, and reset its internal state.
115 """
141 """
116 return self.coro.send(None)
142 return self.coro.send(None)
117
143
118 class TokenInputTransformer(InputTransformer):
144 class TokenInputTransformer(InputTransformer):
119 """Wrapper for a token-based input transformer.
145 """Wrapper for a token-based input transformer.
120
146
121 func should accept a list of tokens (5-tuples, see tokenize docs), and
147 func should accept a list of tokens (5-tuples, see tokenize docs), and
122 return an iterable which can be passed to tokenize.untokenize().
148 return an iterable which can be passed to tokenize.untokenize().
123 """
149 """
124 def __init__(self, func):
150 def __init__(self, func):
151 warnings.warn(
152 "`CoroutineInputTransformer` has been deprecated since IPython 7.0"
153 " and emit a warnig since IPython 8.31, it"
154 " will be removed in the future",
155 DeprecationWarning,
156 stacklevel=2,
157 )
125 self.func = func
158 self.func = func
126 self.buf = []
159 self.buf = []
127 self.reset_tokenizer()
160 self.reset_tokenizer()
128
161
129 def reset_tokenizer(self):
162 def reset_tokenizer(self):
130 it = iter(self.buf)
163 it = iter(self.buf)
131 self.tokenizer = tokenutil.generate_tokens_catch_errors(it.__next__)
164 self.tokenizer = tokenutil.generate_tokens_catch_errors(it.__next__)
132
165
133 def push(self, line):
166 def push(self, line):
134 self.buf.append(line + '\n')
167 self.buf.append(line + '\n')
135 if all(l.isspace() for l in self.buf):
168 if all(l.isspace() for l in self.buf):
136 return self.reset()
169 return self.reset()
137
170
138 tokens = []
171 tokens = []
139 stop_at_NL = False
172 stop_at_NL = False
140 try:
173 try:
141 for intok in self.tokenizer:
174 for intok in self.tokenizer:
142 tokens.append(intok)
175 tokens.append(intok)
143 t = intok[0]
176 t = intok[0]
144 if t == tokenize.NEWLINE or (stop_at_NL and t == tokenize.NL):
177 if t == tokenize.NEWLINE or (stop_at_NL and t == tokenize.NL):
145 # Stop before we try to pull a line we don't have yet
178 # Stop before we try to pull a line we don't have yet
146 break
179 break
147 elif t == tokenize.ERRORTOKEN:
180 elif t == tokenize.ERRORTOKEN:
148 stop_at_NL = True
181 stop_at_NL = True
149 except TokenError:
182 except TokenError:
150 # Multi-line statement - stop and try again with the next line
183 # Multi-line statement - stop and try again with the next line
151 self.reset_tokenizer()
184 self.reset_tokenizer()
152 return None
185 return None
153
186
154 return self.output(tokens)
187 return self.output(tokens)
155
188
156 def output(self, tokens):
189 def output(self, tokens):
157 self.buf.clear()
190 self.buf.clear()
158 self.reset_tokenizer()
191 self.reset_tokenizer()
159 return untokenize(self.func(tokens)).rstrip('\n')
192 return untokenize(self.func(tokens)).rstrip('\n')
160
193
161 def reset(self):
194 def reset(self):
162 l = ''.join(self.buf)
195 l = ''.join(self.buf)
163 self.buf.clear()
196 self.buf.clear()
164 self.reset_tokenizer()
197 self.reset_tokenizer()
165 if l:
198 if l:
166 return l.rstrip('\n')
199 return l.rstrip('\n')
167
200
168 class assemble_python_lines(TokenInputTransformer):
201 class assemble_python_lines(TokenInputTransformer):
169 def __init__(self):
202 def __init__(self):
170 super(assemble_python_lines, self).__init__(None)
203 super().__init__(None)
171
204
172 def output(self, tokens):
205 def output(self, tokens):
173 return self.reset()
206 return self.reset()
174
207
175 @CoroutineInputTransformer.wrap
208 @CoroutineInputTransformer.wrap
176 def assemble_logical_lines():
209 def assemble_logical_lines():
177 r"""Join lines following explicit line continuations (\)"""
210 r"""Join lines following explicit line continuations (\)"""
178 line = ''
211 line = ''
179 while True:
212 while True:
180 line = (yield line)
213 line = (yield line)
181 if not line or line.isspace():
214 if not line or line.isspace():
182 continue
215 continue
183
216
184 parts = []
217 parts = []
185 while line is not None:
218 while line is not None:
186 if line.endswith('\\') and (not has_comment(line)):
219 if line.endswith('\\') and (not has_comment(line)):
187 parts.append(line[:-1])
220 parts.append(line[:-1])
188 line = (yield None) # Get another line
221 line = (yield None) # Get another line
189 else:
222 else:
190 parts.append(line)
223 parts.append(line)
191 break
224 break
192
225
193 # Output
226 # Output
194 line = ''.join(parts)
227 line = ''.join(parts)
195
228
196 # Utilities
229 # Utilities
197 def _make_help_call(target: str, esc: str, lspace: str) -> str:
230 def _make_help_call(target: str, esc: str, lspace: str) -> str:
198 """Prepares a pinfo(2)/psearch call from a target name and the escape
231 """Prepares a pinfo(2)/psearch call from a target name and the escape
199 (i.e. ? or ??)"""
232 (i.e. ? or ??)"""
200 method = 'pinfo2' if esc == '??' \
233 method = 'pinfo2' if esc == '??' \
201 else 'psearch' if '*' in target \
234 else 'psearch' if '*' in target \
202 else 'pinfo'
235 else 'pinfo'
203 arg = " ".join([method, target])
236 arg = " ".join([method, target])
204 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
237 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
205 t_magic_name, _, t_magic_arg_s = arg.partition(' ')
238 t_magic_name, _, t_magic_arg_s = arg.partition(' ')
206 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
239 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
207 return "%sget_ipython().run_line_magic(%r, %r)" % (
240 return "%sget_ipython().run_line_magic(%r, %r)" % (
208 lspace,
241 lspace,
209 t_magic_name,
242 t_magic_name,
210 t_magic_arg_s,
243 t_magic_arg_s,
211 )
244 )
212
245
213
246
214 # These define the transformations for the different escape characters.
247 # These define the transformations for the different escape characters.
215 def _tr_system(line_info: LineInfo):
248 def _tr_system(line_info: LineInfo):
216 "Translate lines escaped with: !"
249 "Translate lines escaped with: !"
217 cmd = line_info.line.lstrip().lstrip(ESC_SHELL)
250 cmd = line_info.line.lstrip().lstrip(ESC_SHELL)
218 return '%sget_ipython().system(%r)' % (line_info.pre, cmd)
251 return '%sget_ipython().system(%r)' % (line_info.pre, cmd)
219
252
220
253
221 def _tr_system2(line_info: LineInfo):
254 def _tr_system2(line_info: LineInfo):
222 "Translate lines escaped with: !!"
255 "Translate lines escaped with: !!"
223 cmd = line_info.line.lstrip()[2:]
256 cmd = line_info.line.lstrip()[2:]
224 return '%sget_ipython().getoutput(%r)' % (line_info.pre, cmd)
257 return '%sget_ipython().getoutput(%r)' % (line_info.pre, cmd)
225
258
226
259
227 def _tr_help(line_info: LineInfo):
260 def _tr_help(line_info: LineInfo):
228 "Translate lines escaped with: ?/??"
261 "Translate lines escaped with: ?/??"
229 # A naked help line should just fire the intro help screen
262 # A naked help line should just fire the intro help screen
230 if not line_info.line[1:]:
263 if not line_info.line[1:]:
231 return 'get_ipython().show_usage()'
264 return 'get_ipython().show_usage()'
232
265
233 return _make_help_call(line_info.ifun, line_info.esc, line_info.pre)
266 return _make_help_call(line_info.ifun, line_info.esc, line_info.pre)
234
267
235
268
236 def _tr_magic(line_info: LineInfo):
269 def _tr_magic(line_info: LineInfo):
237 "Translate lines escaped with: %"
270 "Translate lines escaped with: %"
238 tpl = '%sget_ipython().run_line_magic(%r, %r)'
271 tpl = '%sget_ipython().run_line_magic(%r, %r)'
239 if line_info.line.startswith(ESC_MAGIC2):
272 if line_info.line.startswith(ESC_MAGIC2):
240 return line_info.line
273 return line_info.line
241 cmd = ' '.join([line_info.ifun, line_info.the_rest]).strip()
274 cmd = ' '.join([line_info.ifun, line_info.the_rest]).strip()
242 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
275 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
243 t_magic_name, _, t_magic_arg_s = cmd.partition(' ')
276 t_magic_name, _, t_magic_arg_s = cmd.partition(' ')
244 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
277 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
245 return tpl % (line_info.pre, t_magic_name, t_magic_arg_s)
278 return tpl % (line_info.pre, t_magic_name, t_magic_arg_s)
246
279
247
280
248 def _tr_quote(line_info: LineInfo):
281 def _tr_quote(line_info: LineInfo):
249 "Translate lines escaped with: ,"
282 "Translate lines escaped with: ,"
250 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
283 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
251 '", "'.join(line_info.the_rest.split()) )
284 '", "'.join(line_info.the_rest.split()) )
252
285
253
286
254 def _tr_quote2(line_info: LineInfo):
287 def _tr_quote2(line_info: LineInfo):
255 "Translate lines escaped with: ;"
288 "Translate lines escaped with: ;"
256 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
289 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
257 line_info.the_rest)
290 line_info.the_rest)
258
291
259
292
260 def _tr_paren(line_info: LineInfo):
293 def _tr_paren(line_info: LineInfo):
261 "Translate lines escaped with: /"
294 "Translate lines escaped with: /"
262 return '%s%s(%s)' % (line_info.pre, line_info.ifun,
295 return '%s%s(%s)' % (line_info.pre, line_info.ifun,
263 ", ".join(line_info.the_rest.split()))
296 ", ".join(line_info.the_rest.split()))
264
297
265 tr = { ESC_SHELL : _tr_system,
298 tr = { ESC_SHELL : _tr_system,
266 ESC_SH_CAP : _tr_system2,
299 ESC_SH_CAP : _tr_system2,
267 ESC_HELP : _tr_help,
300 ESC_HELP : _tr_help,
268 ESC_HELP2 : _tr_help,
301 ESC_HELP2 : _tr_help,
269 ESC_MAGIC : _tr_magic,
302 ESC_MAGIC : _tr_magic,
270 ESC_QUOTE : _tr_quote,
303 ESC_QUOTE : _tr_quote,
271 ESC_QUOTE2 : _tr_quote2,
304 ESC_QUOTE2 : _tr_quote2,
272 ESC_PAREN : _tr_paren }
305 ESC_PAREN : _tr_paren }
273
306
274 @StatelessInputTransformer.wrap
307 @StatelessInputTransformer.wrap
275 def escaped_commands(line: str):
308 def escaped_commands(line: str):
276 """Transform escaped commands - %magic, !system, ?help + various autocalls."""
309 """Transform escaped commands - %magic, !system, ?help + various autocalls."""
277 if not line or line.isspace():
310 if not line or line.isspace():
278 return line
311 return line
279 lineinf = LineInfo(line)
312 lineinf = LineInfo(line)
280 if lineinf.esc not in tr:
313 if lineinf.esc not in tr:
281 return line
314 return line
282
315
283 return tr[lineinf.esc](lineinf)
316 return tr[lineinf.esc](lineinf)
284
317
285 _initial_space_re = re.compile(r'\s*')
318 _initial_space_re = re.compile(r'\s*')
286
319
287 _help_end_re = re.compile(r"""(%{0,2}
320 _help_end_re = re.compile(r"""(%{0,2}
288 (?!\d)[\w*]+ # Variable name
321 (?!\d)[\w*]+ # Variable name
289 (\.(?!\d)[\w*]+)* # .etc.etc
322 (\.(?!\d)[\w*]+)* # .etc.etc
290 )
323 )
291 (\?\??)$ # ? or ??
324 (\?\??)$ # ? or ??
292 """,
325 """,
293 re.VERBOSE)
326 re.VERBOSE)
294
327
295 # Extra pseudotokens for multiline strings and data structures
328 # Extra pseudotokens for multiline strings and data structures
296 _MULTILINE_STRING = object()
329 _MULTILINE_STRING = object()
297 _MULTILINE_STRUCTURE = object()
330 _MULTILINE_STRUCTURE = object()
298
331
299 def _line_tokens(line):
332 def _line_tokens(line):
300 """Helper for has_comment and ends_in_comment_or_string."""
333 """Helper for has_comment and ends_in_comment_or_string."""
301 readline = StringIO(line).readline
334 readline = StringIO(line).readline
302 toktypes = set()
335 toktypes = set()
303 try:
336 try:
304 for t in tokenutil.generate_tokens_catch_errors(readline):
337 for t in tokenutil.generate_tokens_catch_errors(readline):
305 toktypes.add(t[0])
338 toktypes.add(t[0])
306 except TokenError as e:
339 except TokenError as e:
307 # There are only two cases where a TokenError is raised.
340 # There are only two cases where a TokenError is raised.
308 if 'multi-line string' in e.args[0]:
341 if 'multi-line string' in e.args[0]:
309 toktypes.add(_MULTILINE_STRING)
342 toktypes.add(_MULTILINE_STRING)
310 else:
343 else:
311 toktypes.add(_MULTILINE_STRUCTURE)
344 toktypes.add(_MULTILINE_STRUCTURE)
312 return toktypes
345 return toktypes
313
346
314 def has_comment(src):
347 def has_comment(src):
315 """Indicate whether an input line has (i.e. ends in, or is) a comment.
348 """Indicate whether an input line has (i.e. ends in, or is) a comment.
316
349
317 This uses tokenize, so it can distinguish comments from # inside strings.
350 This uses tokenize, so it can distinguish comments from # inside strings.
318
351
319 Parameters
352 Parameters
320 ----------
353 ----------
321 src : string
354 src : string
322 A single line input string.
355 A single line input string.
323
356
324 Returns
357 Returns
325 -------
358 -------
326 comment : bool
359 comment : bool
327 True if source has a comment.
360 True if source has a comment.
328 """
361 """
329 return (tokenize.COMMENT in _line_tokens(src))
362 return (tokenize.COMMENT in _line_tokens(src))
330
363
331 def ends_in_comment_or_string(src):
364 def ends_in_comment_or_string(src):
332 """Indicates whether or not an input line ends in a comment or within
365 """Indicates whether or not an input line ends in a comment or within
333 a multiline string.
366 a multiline string.
334
367
335 Parameters
368 Parameters
336 ----------
369 ----------
337 src : string
370 src : string
338 A single line input string.
371 A single line input string.
339
372
340 Returns
373 Returns
341 -------
374 -------
342 comment : bool
375 comment : bool
343 True if source ends in a comment or multiline string.
376 True if source ends in a comment or multiline string.
344 """
377 """
345 toktypes = _line_tokens(src)
378 toktypes = _line_tokens(src)
346 return (tokenize.COMMENT in toktypes) or (_MULTILINE_STRING in toktypes)
379 return (tokenize.COMMENT in toktypes) or (_MULTILINE_STRING in toktypes)
347
380
348
381
349 @StatelessInputTransformer.wrap
382 @StatelessInputTransformer.wrap
350 def help_end(line: str):
383 def help_end(line: str):
351 """Translate lines with ?/?? at the end"""
384 """Translate lines with ?/?? at the end"""
352 m = _help_end_re.search(line)
385 m = _help_end_re.search(line)
353 if m is None or ends_in_comment_or_string(line):
386 if m is None or ends_in_comment_or_string(line):
354 return line
387 return line
355 target = m.group(1)
388 target = m.group(1)
356 esc = m.group(3)
389 esc = m.group(3)
357 match = _initial_space_re.match(line)
390 match = _initial_space_re.match(line)
358 assert match is not None
391 assert match is not None
359 lspace = match.group(0)
392 lspace = match.group(0)
360
393
361 return _make_help_call(target, esc, lspace)
394 return _make_help_call(target, esc, lspace)
362
395
363
396
364 @CoroutineInputTransformer.wrap
397 @CoroutineInputTransformer.wrap
365 def cellmagic(end_on_blank_line: bool = False):
398 def cellmagic(end_on_blank_line: bool = False):
366 """Captures & transforms cell magics.
399 """Captures & transforms cell magics.
367
400
368 After a cell magic is started, this stores up any lines it gets until it is
401 After a cell magic is started, this stores up any lines it gets until it is
369 reset (sent None).
402 reset (sent None).
370 """
403 """
371 tpl = 'get_ipython().run_cell_magic(%r, %r, %r)'
404 tpl = 'get_ipython().run_cell_magic(%r, %r, %r)'
372 cellmagic_help_re = re.compile(r'%%\w+\?')
405 cellmagic_help_re = re.compile(r'%%\w+\?')
373 line = ''
406 line = ''
374 while True:
407 while True:
375 line = (yield line)
408 line = (yield line)
376 # consume leading empty lines
409 # consume leading empty lines
377 while not line:
410 while not line:
378 line = (yield line)
411 line = (yield line)
379
412
380 if not line.startswith(ESC_MAGIC2):
413 if not line.startswith(ESC_MAGIC2):
381 # This isn't a cell magic, idle waiting for reset then start over
414 # This isn't a cell magic, idle waiting for reset then start over
382 while line is not None:
415 while line is not None:
383 line = (yield line)
416 line = (yield line)
384 continue
417 continue
385
418
386 if cellmagic_help_re.match(line):
419 if cellmagic_help_re.match(line):
387 # This case will be handled by help_end
420 # This case will be handled by help_end
388 continue
421 continue
389
422
390 first = line
423 first = line
391 body = []
424 body = []
392 line = (yield None)
425 line = (yield None)
393 while (line is not None) and \
426 while (line is not None) and \
394 ((line.strip() != '') or not end_on_blank_line):
427 ((line.strip() != '') or not end_on_blank_line):
395 body.append(line)
428 body.append(line)
396 line = (yield None)
429 line = (yield None)
397
430
398 # Output
431 # Output
399 magic_name, _, first = first.partition(' ')
432 magic_name, _, first = first.partition(' ')
400 magic_name = magic_name.lstrip(ESC_MAGIC2)
433 magic_name = magic_name.lstrip(ESC_MAGIC2)
401 line = tpl % (magic_name, first, u'\n'.join(body))
434 line = tpl % (magic_name, first, u'\n'.join(body))
402
435
403
436
404 def _strip_prompts(prompt_re, initial_re=None, turnoff_re=None):
437 def _strip_prompts(prompt_re, initial_re=None, turnoff_re=None):
405 """Remove matching input prompts from a block of input.
438 """Remove matching input prompts from a block of input.
406
439
407 Parameters
440 Parameters
408 ----------
441 ----------
409 prompt_re : regular expression
442 prompt_re : regular expression
410 A regular expression matching any input prompt (including continuation)
443 A regular expression matching any input prompt (including continuation)
411 initial_re : regular expression, optional
444 initial_re : regular expression, optional
412 A regular expression matching only the initial prompt, but not continuation.
445 A regular expression matching only the initial prompt, but not continuation.
413 If no initial expression is given, prompt_re will be used everywhere.
446 If no initial expression is given, prompt_re will be used everywhere.
414 Used mainly for plain Python prompts, where the continuation prompt
447 Used mainly for plain Python prompts, where the continuation prompt
415 ``...`` is a valid Python expression in Python 3, so shouldn't be stripped.
448 ``...`` is a valid Python expression in Python 3, so shouldn't be stripped.
416
449
417 Notes
450 Notes
418 -----
451 -----
419 If `initial_re` and `prompt_re differ`,
452 If `initial_re` and `prompt_re differ`,
420 only `initial_re` will be tested against the first line.
453 only `initial_re` will be tested against the first line.
421 If any prompt is found on the first two lines,
454 If any prompt is found on the first two lines,
422 prompts will be stripped from the rest of the block.
455 prompts will be stripped from the rest of the block.
423 """
456 """
424 if initial_re is None:
457 if initial_re is None:
425 initial_re = prompt_re
458 initial_re = prompt_re
426 line = ''
459 line = ''
427 while True:
460 while True:
428 line = (yield line)
461 line = (yield line)
429
462
430 # First line of cell
463 # First line of cell
431 if line is None:
464 if line is None:
432 continue
465 continue
433 out, n1 = initial_re.subn('', line, count=1)
466 out, n1 = initial_re.subn('', line, count=1)
434 if turnoff_re and not n1:
467 if turnoff_re and not n1:
435 if turnoff_re.match(line):
468 if turnoff_re.match(line):
436 # We're in e.g. a cell magic; disable this transformer for
469 # We're in e.g. a cell magic; disable this transformer for
437 # the rest of the cell.
470 # the rest of the cell.
438 while line is not None:
471 while line is not None:
439 line = (yield line)
472 line = (yield line)
440 continue
473 continue
441
474
442 line = (yield out)
475 line = (yield out)
443
476
444 if line is None:
477 if line is None:
445 continue
478 continue
446 # check for any prompt on the second line of the cell,
479 # check for any prompt on the second line of the cell,
447 # because people often copy from just after the first prompt,
480 # because people often copy from just after the first prompt,
448 # so we might not see it in the first line.
481 # so we might not see it in the first line.
449 out, n2 = prompt_re.subn('', line, count=1)
482 out, n2 = prompt_re.subn('', line, count=1)
450 line = (yield out)
483 line = (yield out)
451
484
452 if n1 or n2:
485 if n1 or n2:
453 # Found a prompt in the first two lines - check for it in
486 # Found a prompt in the first two lines - check for it in
454 # the rest of the cell as well.
487 # the rest of the cell as well.
455 while line is not None:
488 while line is not None:
456 line = (yield prompt_re.sub('', line, count=1))
489 line = (yield prompt_re.sub('', line, count=1))
457
490
458 else:
491 else:
459 # Prompts not in input - wait for reset
492 # Prompts not in input - wait for reset
460 while line is not None:
493 while line is not None:
461 line = (yield line)
494 line = (yield line)
462
495
463 @CoroutineInputTransformer.wrap
496 @CoroutineInputTransformer.wrap
464 def classic_prompt():
497 def classic_prompt():
465 """Strip the >>>/... prompts of the Python interactive shell."""
498 """Strip the >>>/... prompts of the Python interactive shell."""
466 # FIXME: non-capturing version (?:...) usable?
499 # FIXME: non-capturing version (?:...) usable?
467 prompt_re = re.compile(r'^(>>>|\.\.\.)( |$)')
500 prompt_re = re.compile(r'^(>>>|\.\.\.)( |$)')
468 initial_re = re.compile(r'^>>>( |$)')
501 initial_re = re.compile(r'^>>>( |$)')
469 # Any %magic/!system is IPython syntax, so we needn't look for >>> prompts
502 # Any %magic/!system is IPython syntax, so we needn't look for >>> prompts
470 turnoff_re = re.compile(r'^[%!]')
503 turnoff_re = re.compile(r'^[%!]')
471 return _strip_prompts(prompt_re, initial_re, turnoff_re)
504 return _strip_prompts(prompt_re, initial_re, turnoff_re)
472
505
473 @CoroutineInputTransformer.wrap
506 @CoroutineInputTransformer.wrap
474 def ipy_prompt():
507 def ipy_prompt():
475 """Strip IPython's In [1]:/...: prompts."""
508 """Strip IPython's In [1]:/...: prompts."""
476 # FIXME: non-capturing version (?:...) usable?
509 # FIXME: non-capturing version (?:...) usable?
477 prompt_re = re.compile(r'^(In \[\d+\]: |\s*\.{3,}: ?)')
510 prompt_re = re.compile(r'^(In \[\d+\]: |\s*\.{3,}: ?)')
478 # Disable prompt stripping inside cell magics
511 # Disable prompt stripping inside cell magics
479 turnoff_re = re.compile(r'^%%')
512 turnoff_re = re.compile(r'^%%')
480 return _strip_prompts(prompt_re, turnoff_re=turnoff_re)
513 return _strip_prompts(prompt_re, turnoff_re=turnoff_re)
481
514
482
515
483 @CoroutineInputTransformer.wrap
516 @CoroutineInputTransformer.wrap
484 def leading_indent():
517 def leading_indent():
485 """Remove leading indentation.
518 """Remove leading indentation.
486
519
487 If the first line starts with a spaces or tabs, the same whitespace will be
520 If the first line starts with a spaces or tabs, the same whitespace will be
488 removed from each following line until it is reset.
521 removed from each following line until it is reset.
489 """
522 """
490 space_re = re.compile(r'^[ \t]+')
523 space_re = re.compile(r'^[ \t]+')
491 line = ''
524 line = ''
492 while True:
525 while True:
493 line = (yield line)
526 line = (yield line)
494
527
495 if line is None:
528 if line is None:
496 continue
529 continue
497
530
498 m = space_re.match(line)
531 m = space_re.match(line)
499 if m:
532 if m:
500 space = m.group(0)
533 space = m.group(0)
501 while line is not None:
534 while line is not None:
502 if line.startswith(space):
535 if line.startswith(space):
503 line = line[len(space):]
536 line = line[len(space):]
504 line = (yield line)
537 line = (yield line)
505 else:
538 else:
506 # No leading spaces - wait for reset
539 # No leading spaces - wait for reset
507 while line is not None:
540 while line is not None:
508 line = (yield line)
541 line = (yield line)
509
542
510
543
511 _assign_pat = \
544 _assign_pat = \
512 r'''(?P<lhs>(\s*)
545 r'''(?P<lhs>(\s*)
513 ([\w\.]+) # Initial identifier
546 ([\w\.]+) # Initial identifier
514 (\s*,\s*
547 (\s*,\s*
515 \*?[\w\.]+)* # Further identifiers for unpacking
548 \*?[\w\.]+)* # Further identifiers for unpacking
516 \s*?,? # Trailing comma
549 \s*?,? # Trailing comma
517 )
550 )
518 \s*=\s*
551 \s*=\s*
519 '''
552 '''
520
553
521 assign_system_re = re.compile(r'{}!\s*(?P<cmd>.*)'.format(_assign_pat), re.VERBOSE)
554 assign_system_re = re.compile(r'{}!\s*(?P<cmd>.*)'.format(_assign_pat), re.VERBOSE)
522 assign_system_template = '%s = get_ipython().getoutput(%r)'
555 assign_system_template = '%s = get_ipython().getoutput(%r)'
523 @StatelessInputTransformer.wrap
556 @StatelessInputTransformer.wrap
524 def assign_from_system(line):
557 def assign_from_system(line):
525 """Transform assignment from system commands (e.g. files = !ls)"""
558 """Transform assignment from system commands (e.g. files = !ls)"""
526 m = assign_system_re.match(line)
559 m = assign_system_re.match(line)
527 if m is None:
560 if m is None:
528 return line
561 return line
529
562
530 return assign_system_template % m.group('lhs', 'cmd')
563 return assign_system_template % m.group('lhs', 'cmd')
531
564
532 assign_magic_re = re.compile(r'{}%\s*(?P<cmd>.*)'.format(_assign_pat), re.VERBOSE)
565 assign_magic_re = re.compile(r'{}%\s*(?P<cmd>.*)'.format(_assign_pat), re.VERBOSE)
533 assign_magic_template = '%s = get_ipython().run_line_magic(%r, %r)'
566 assign_magic_template = '%s = get_ipython().run_line_magic(%r, %r)'
534 @StatelessInputTransformer.wrap
567 @StatelessInputTransformer.wrap
535 def assign_from_magic(line):
568 def assign_from_magic(line):
536 """Transform assignment from magic commands (e.g. a = %who_ls)"""
569 """Transform assignment from magic commands (e.g. a = %who_ls)"""
537 m = assign_magic_re.match(line)
570 m = assign_magic_re.match(line)
538 if m is None:
571 if m is None:
539 return line
572 return line
540 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
573 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
541 m_lhs, m_cmd = m.group('lhs', 'cmd')
574 m_lhs, m_cmd = m.group('lhs', 'cmd')
542 t_magic_name, _, t_magic_arg_s = m_cmd.partition(' ')
575 t_magic_name, _, t_magic_arg_s = m_cmd.partition(' ')
543 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
576 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
544 return assign_magic_template % (m_lhs, t_magic_name, t_magic_arg_s)
577 return assign_magic_template % (m_lhs, t_magic_name, t_magic_arg_s)
@@ -1,90 +1,90
1
1
2 ===========================
2 ===========================
3 Custom input transformation
3 Custom input transformation
4 ===========================
4 ===========================
5
5
6 IPython extends Python syntax to allow things like magic commands, and help with
6 IPython extends Python syntax to allow things like magic commands, and help with
7 the ``?`` syntax. There are several ways to customise how the user's input is
7 the ``?`` syntax. There are several ways to customise how the user's input is
8 processed into Python code to be executed.
8 processed into Python code to be executed.
9
9
10 These hooks are mainly for other projects using IPython as the core of their
10 These hooks are mainly for other projects using IPython as the core of their
11 interactive interface. Using them carelessly can easily break IPython!
11 interactive interface. Using them carelessly can easily break IPython!
12
12
13 String based transformations
13 String based transformations
14 ============================
14 ============================
15
15
16 .. currentmodule:: IPython.core.inputtransforms
16 .. currentmodule:: IPython.core.inputtransformers2
17
17
18 When the user enters code, it is first processed as a string. By the
18 When the user enters code, it is first processed as a string. By the
19 end of this stage, it must be valid Python syntax.
19 end of this stage, it must be valid Python syntax.
20
20
21 .. versionchanged:: 7.0
21 .. versionchanged:: 7.0
22
22
23 The API for string and token-based transformations has been completely
23 The API for string and token-based transformations has been completely
24 redesigned. Any third party code extending input transformation will need to
24 redesigned. Any third party code extending input transformation will need to
25 be rewritten. The new API is, hopefully, simpler.
25 be rewritten. The new API is, hopefully, simpler.
26
26
27 String based transformations are functions which accept a list of strings:
27 String based transformations are functions which accept a list of strings:
28 each string is a single line of the input cell, including its line ending.
28 each string is a single line of the input cell, including its line ending.
29 The transformation function should return output in the same structure.
29 The transformation function should return output in the same structure.
30
30
31 These transformations are in two groups, accessible as attributes of
31 These transformations are in two groups, accessible as attributes of
32 the :class:`~IPython.core.interactiveshell.InteractiveShell` instance.
32 the :class:`~IPython.core.interactiveshell.InteractiveShell` instance.
33 Each group is a list of transformation functions.
33 Each group is a list of transformation functions.
34
34
35 * ``input_transformers_cleanup`` run first on input, to do things like stripping
35 * ``input_transformers_cleanup`` run first on input, to do things like stripping
36 prompts and leading indents from copied code. It may not be possible at this
36 prompts and leading indents from copied code. It may not be possible at this
37 stage to parse the input as valid Python code.
37 stage to parse the input as valid Python code.
38 * Then IPython runs its own transformations to handle its special syntax, like
38 * Then IPython runs its own transformations to handle its special syntax, like
39 ``%magics`` and ``!system`` commands. This part does not expose extension
39 ``%magics`` and ``!system`` commands. This part does not expose extension
40 points.
40 points.
41 * ``input_transformers_post`` run as the last step, to do things like converting
41 * ``input_transformers_post`` run as the last step, to do things like converting
42 float literals into decimal objects. These may attempt to parse the input as
42 float literals into decimal objects. These may attempt to parse the input as
43 Python code.
43 Python code.
44
44
45 These transformers may raise :exc:`SyntaxError` if the input code is invalid, but
45 These transformers may raise :exc:`SyntaxError` if the input code is invalid, but
46 in most cases it is clearer to pass unrecognised code through unmodified and let
46 in most cases it is clearer to pass unrecognised code through unmodified and let
47 Python's own parser decide whether it is valid.
47 Python's own parser decide whether it is valid.
48
48
49 For example, imagine we want to obfuscate our code by reversing each line, so
49 For example, imagine we want to obfuscate our code by reversing each line, so
50 we'd write ``)5(f =+ a`` instead of ``a += f(5)``. Here's how we could swap it
50 we'd write ``)5(f =+ a`` instead of ``a += f(5)``. Here's how we could swap it
51 back the right way before IPython tries to run it::
51 back the right way before IPython tries to run it::
52
52
53 def reverse_line_chars(lines):
53 def reverse_line_chars(lines):
54 new_lines = []
54 new_lines = []
55 for line in lines:
55 for line in lines:
56 chars = line[:-1] # the newline needs to stay at the end
56 chars = line[:-1] # the newline needs to stay at the end
57 new_lines.append(chars[::-1] + '\n')
57 new_lines.append(chars[::-1] + '\n')
58 return new_lines
58 return new_lines
59
59
60 To start using this::
60 To start using this::
61
61
62 ip = get_ipython()
62 ip = get_ipython()
63 ip.input_transformers_cleanup.append(reverse_line_chars)
63 ip.input_transformers_cleanup.append(reverse_line_chars)
64
64
65 .. versionadded:: 7.17
65 .. versionadded:: 7.17
66
66
67 input_transformers can now have an attribute ``has_side_effects`` set to
67 input_transformers can now have an attribute ``has_side_effects`` set to
68 `True`, which will prevent the transformers from being ran when IPython is
68 `True`, which will prevent the transformers from being ran when IPython is
69 trying to guess whether the user input is complete.
69 trying to guess whether the user input is complete.
70
70
71
71
72
72
73 AST transformations
73 AST transformations
74 ===================
74 ===================
75
75
76 After the code has been parsed as Python syntax, you can use Python's powerful
76 After the code has been parsed as Python syntax, you can use Python's powerful
77 *Abstract Syntax Tree* tools to modify it. Subclass :class:`ast.NodeTransformer`,
77 *Abstract Syntax Tree* tools to modify it. Subclass :class:`ast.NodeTransformer`,
78 and add an instance to ``shell.ast_transformers``.
78 and add an instance to ``shell.ast_transformers``.
79
79
80 This example wraps integer literals in an ``Integer`` class, which is useful for
80 This example wraps integer literals in an ``Integer`` class, which is useful for
81 mathematical frameworks that want to handle e.g. ``1/3`` as a precise fraction::
81 mathematical frameworks that want to handle e.g. ``1/3`` as a precise fraction::
82
82
83
83
84 class IntegerWrapper(ast.NodeTransformer):
84 class IntegerWrapper(ast.NodeTransformer):
85 """Wraps all integers in a call to Integer()"""
85 """Wraps all integers in a call to Integer()"""
86 def visit_Num(self, node):
86 def visit_Num(self, node):
87 if isinstance(node.value, int):
87 if isinstance(node.value, int):
88 return ast.Call(func=ast.Name(id='Integer', ctx=ast.Load()),
88 return ast.Call(func=ast.Name(id='Integer', ctx=ast.Load()),
89 args=[node], keywords=[])
89 args=[node], keywords=[])
90 return node
90 return node
General Comments 0
You need to be logged in to leave comments. Login now