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