Show More
@@ -339,6 +339,15 b' class InteractiveShell(SingletonConfigurable):' | |||
|
339 | 339 | ()) |
|
340 | 340 | |
|
341 | 341 | @property |
|
342 | def input_transformers_cleanup(self): | |
|
343 | return self.input_transformer_manager.cleanup_transforms | |
|
344 | ||
|
345 | input_transformers_post = List([], | |
|
346 | help="A list of string input transformers, to be applied after IPython's " | |
|
347 | "own input transformations." | |
|
348 | ) | |
|
349 | ||
|
350 | @property | |
|
342 | 351 | def input_splitter(self): |
|
343 | 352 | """Make this available for compatibility |
|
344 | 353 | |
@@ -2717,21 +2726,10 b' class InteractiveShell(SingletonConfigurable):' | |||
|
2717 | 2726 | preprocessing_exc_tuple = None |
|
2718 | 2727 | try: |
|
2719 | 2728 | # Static input transformations |
|
2720 |
cell = self. |
|
|
2721 | except SyntaxError: | |
|
2722 | preprocessing_exc_tuple = sys.exc_info() | |
|
2723 | cell = raw_cell # cell has to exist so it can be stored/logged | |
|
2724 | else: | |
|
2725 | if len(cell.splitlines()) == 1: | |
|
2726 | # Dynamic transformations - only applied for single line commands | |
|
2727 | with self.builtin_trap: | |
|
2728 | try: | |
|
2729 | # use prefilter_lines to handle trailing newlines | |
|
2730 | # restore trailing newline for ast.parse | |
|
2731 | cell = self.prefilter_manager.prefilter_lines(cell) + '\n' | |
|
2729 | cell = self.transform_cell(raw_cell) | |
|
2732 | 2730 |
|
|
2733 | # don't allow prefilter errors to crash IPython | |
|
2734 | 2731 |
|
|
2732 | cell = raw_cell # cell has to exist so it can be stored/logged | |
|
2735 | 2733 | |
|
2736 | 2734 | # Store raw and processed history |
|
2737 | 2735 | if store_history: |
@@ -2803,6 +2801,24 b' class InteractiveShell(SingletonConfigurable):' | |||
|
2803 | 2801 | |
|
2804 | 2802 | return result |
|
2805 | 2803 | |
|
2804 | def transform_cell(self, raw_cell): | |
|
2805 | # Static input transformations | |
|
2806 | cell = self.input_transformer_manager.transform_cell(raw_cell) | |
|
2807 | ||
|
2808 | if len(cell.splitlines()) == 1: | |
|
2809 | # Dynamic transformations - only applied for single line commands | |
|
2810 | with self.builtin_trap: | |
|
2811 | # use prefilter_lines to handle trailing newlines | |
|
2812 | # restore trailing newline for ast.parse | |
|
2813 | cell = self.prefilter_manager.prefilter_lines(cell) + '\n' | |
|
2814 | ||
|
2815 | lines = cell.splitlines(keepends=True) | |
|
2816 | for transform in self.input_transformers_post: | |
|
2817 | lines = transform(lines) | |
|
2818 | cell = ''.join(lines) | |
|
2819 | ||
|
2820 | return cell | |
|
2821 | ||
|
2806 | 2822 | def transform_ast(self, node): |
|
2807 | 2823 | """Apply the AST transformations from self.ast_transformers |
|
2808 | 2824 |
@@ -24,34 +24,28 b' end of this stage, it must be valid Python syntax.' | |||
|
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 |
String based transformations are |
|
|
28 | :class:`IPython.core.inputtransformer2.TransformerManager`, which is attached to | |
|
29 | the :class:`~IPython.core.interactiveshell.InteractiveShell` instance as | |
|
30 | ``input_transformer_manager``. This passes the | |
|
31 | data through a series of individual transformers. There are two kinds of | |
|
32 | transformers stored in three groups: | |
|
33 | ||
|
34 | * ``cleanup_transforms`` and ``line_transforms`` are lists of functions. Each | |
|
35 | function is called with a list of input lines (which include trailing | |
|
36 | newlines), and they return a list in the same format. ``cleanup_transforms`` | |
|
37 | are run first; they strip prompts and leading indentation from input. | |
|
38 | The only default transform in ``line_transforms`` processes cell magics. | |
|
39 | * ``token_transformers`` is a list of :class:`IPython.core.inputtransformer2.TokenTransformBase` | |
|
40 | subclasses (not instances). They recognise special syntax like | |
|
41 | ``%line magics`` and ``help?``, and transform them to Python syntax. The | |
|
42 | interface for these is more complex; see below. | |
|
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. | |
|
29 | The transformation function should return output in the same structure. | |
|
30 | ||
|
31 | These transformations are in two groups, accessible as attributes of | |
|
32 | the :class:`~IPython.core.interactiveshell.InteractiveShell` instance. | |
|
33 | Each group is a list of transformation functions. | |
|
34 | ||
|
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 | |
|
37 | stage to parse the input as valid Python code. | |
|
38 | * Then IPython runs its own transformations to handle its special syntax, like | |
|
39 | ``%magics`` and ``!system`` commands. This part does not expose extension | |
|
40 | points. | |
|
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 | |
|
43 | Python code. | |
|
43 | 44 | |
|
44 | 45 | These transformers may raise :exc:`SyntaxError` if the input code is invalid, but |
|
45 | 46 | in most cases it is clearer to pass unrecognised code through unmodified and let |
|
46 | 47 | Python's own parser decide whether it is valid. |
|
47 | 48 | |
|
48 | .. versionchanged:: 2.0 | |
|
49 | ||
|
50 | Added the option to raise :exc:`SyntaxError`. | |
|
51 | ||
|
52 | Line based transformations | |
|
53 | -------------------------- | |
|
54 | ||
|
55 | 49 | For example, imagine we want to obfuscate our code by reversing each line, so |
|
56 | 50 | we'd write ``)5(f =+ a`` instead of ``a += f(5)``. Here's how we could swap it |
|
57 | 51 | back the right way before IPython tries to run it:: |
@@ -66,86 +60,7 b' back the right way before IPython tries to run it::' | |||
|
66 | 60 | To start using this:: |
|
67 | 61 | |
|
68 | 62 | ip = get_ipython() |
|
69 |
ip.input_transformer |
|
|
70 | ||
|
71 | Token based transformations | |
|
72 | --------------------------- | |
|
73 | ||
|
74 | These recognise special syntax like ``%magics`` and ``help?``, and transform it | |
|
75 | into valid Python code. Using tokens makes it easy to avoid transforming similar | |
|
76 | patterns inside comments or strings. | |
|
77 | ||
|
78 | The API for a token-based transformation looks like this:: | |
|
79 | ||
|
80 | .. class:: MyTokenTransformer | |
|
81 | ||
|
82 | .. classmethod:: find(tokens_by_line) | |
|
83 | ||
|
84 | Takes a list of lists of :class:`tokenize.TokenInfo` objects. Each sublist | |
|
85 | is the tokens from one Python line, which may span several physical lines, | |
|
86 | because of line continuations, multiline strings or expressions. If it | |
|
87 | finds a pattern to transform, it returns an instance of the class. | |
|
88 | Otherwise, it returns None. | |
|
89 | ||
|
90 | .. attribute:: start_lineno | |
|
91 | start_col | |
|
92 | priority | |
|
93 | ||
|
94 | These attributes are used to select which transformation to run first. | |
|
95 | ``start_lineno`` is 0-indexed (whereas the locations on | |
|
96 | :class:`~tokenize.TokenInfo` use 1-indexed line numbers). If there are | |
|
97 | multiple matches in the same location, the one with the smaller | |
|
98 | ``priority`` number is used. | |
|
99 | ||
|
100 | .. method:: transform(lines) | |
|
101 | ||
|
102 | This should transform the individual recognised pattern that was | |
|
103 | previously found. As with line-based transforms, it takes a list of | |
|
104 | lines as strings, and returns a similar list. | |
|
105 | ||
|
106 | Because each transformation may affect the parsing of the code after it, | |
|
107 | ``TransformerManager`` takes a careful approach. It calls ``find()`` on all | |
|
108 | available transformers. If any find a match, the transformation which matched | |
|
109 | closest to the start is run. Then it tokenises the transformed code again, | |
|
110 | and starts the process again. This continues until none of the transformers | |
|
111 | return a match. So it's important that the transformation removes the pattern | |
|
112 | which ``find()`` recognises, otherwise it will enter an infinite loop. | |
|
113 | ||
|
114 | For example, here's a transformer which will recognise ``¬`` as a prefix for a | |
|
115 | new kind of special command:: | |
|
116 | ||
|
117 | import tokenize | |
|
118 | from IPython.core.inputtransformer2 import TokenTransformBase | |
|
119 | ||
|
120 | class MySpecialCommand(TokenTransformBase): | |
|
121 | @classmethod | |
|
122 | def find(cls, tokens_by_line): | |
|
123 | """Find the first escaped command (¬foo) in the cell. | |
|
124 | """ | |
|
125 | for line in tokens_by_line: | |
|
126 | ix = 0 | |
|
127 | # Find the first token that's not INDENT/DEDENT | |
|
128 | while line[ix].type in {tokenize.INDENT, tokenize.DEDENT}: | |
|
129 | ix += 1 | |
|
130 | if line[ix].string == '¬': | |
|
131 | return cls(line[ix].start) | |
|
132 | ||
|
133 | def transform(self, lines): | |
|
134 | indent = lines[self.start_line][:self.start_col] | |
|
135 | content = lines[self.start_line][self.start_col+1:] | |
|
136 | ||
|
137 | lines_before = lines[:self.start_line] | |
|
138 | call = "specialcommand(%r)" % content | |
|
139 | new_line = indent + call + '\n' | |
|
140 | lines_after = lines[self.start_line + 1:] | |
|
141 | ||
|
142 | return lines_before + [new_line] + lines_after | |
|
143 | ||
|
144 | And here's how you'd use it:: | |
|
145 | ||
|
146 | ip = get_ipython() | |
|
147 | ip.input_transformer_manager.token_transformers.append(MySpecialCommand) | |
|
148 | ||
|
63 | ip.input_transformers_cleanup.append(reverse_line_chars) | |
|
149 | 64 | |
|
150 | 65 | AST transformations |
|
151 | 66 | =================== |
General Comments 0
You need to be logged in to leave comments.
Login now