Show More
@@ -339,6 +339,15 b' class InteractiveShell(SingletonConfigurable):' | |||||
339 | ()) |
|
339 | ()) | |
340 |
|
340 | |||
341 | @property |
|
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 | def input_splitter(self): |
|
351 | def input_splitter(self): | |
343 | """Make this available for compatibility |
|
352 | """Make this available for compatibility | |
344 |
|
353 | |||
@@ -2717,21 +2726,10 b' class InteractiveShell(SingletonConfigurable):' | |||||
2717 | preprocessing_exc_tuple = None |
|
2726 | preprocessing_exc_tuple = None | |
2718 | try: |
|
2727 | try: | |
2719 | # Static input transformations |
|
2728 | # Static input transformations | |
2720 |
cell = self. |
|
2729 | cell = self.transform_cell(raw_cell) | |
2721 |
except |
|
2730 | except Exception: | |
2722 | preprocessing_exc_tuple = sys.exc_info() |
|
2731 | preprocessing_exc_tuple = sys.exc_info() | |
2723 | cell = raw_cell # cell has to exist so it can be stored/logged |
|
2732 | 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' |
|
|||
2732 | except Exception: |
|
|||
2733 | # don't allow prefilter errors to crash IPython |
|
|||
2734 | preprocessing_exc_tuple = sys.exc_info() |
|
|||
2735 |
|
2733 | |||
2736 | # Store raw and processed history |
|
2734 | # Store raw and processed history | |
2737 | if store_history: |
|
2735 | if store_history: | |
@@ -2802,6 +2800,24 b' class InteractiveShell(SingletonConfigurable):' | |||||
2802 | self.execution_count += 1 |
|
2800 | self.execution_count += 1 | |
2803 |
|
2801 | |||
2804 | return result |
|
2802 | return result | |
|
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 | |||
2805 |
|
2821 | |||
2806 | def transform_ast(self, node): |
|
2822 | def transform_ast(self, node): | |
2807 | """Apply the AST transformations from self.ast_transformers |
|
2823 | """Apply the AST transformations from self.ast_transformers |
@@ -24,34 +24,28 b' end of this stage, it must be valid Python syntax.' | |||||
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 |
|
27 | String based transformations are functions which accept a list of strings: | |
28 | :class:`IPython.core.inputtransformer2.TransformerManager`, which is attached to |
|
28 | each string is a single line of the input cell, including its line ending. | |
29 | the :class:`~IPython.core.interactiveshell.InteractiveShell` instance as |
|
29 | The transformation function should return output in the same structure. | |
30 | ``input_transformer_manager``. This passes the |
|
30 | ||
31 | data through a series of individual transformers. There are two kinds of |
|
31 | These transformations are in two groups, accessible as attributes of | |
32 | transformers stored in three groups: |
|
32 | the :class:`~IPython.core.interactiveshell.InteractiveShell` instance. | |
33 |
|
33 | Each group is a list of transformation functions. | ||
34 | * ``cleanup_transforms`` and ``line_transforms`` are lists of functions. Each |
|
34 | ||
35 | function is called with a list of input lines (which include trailing |
|
35 | * ``input_transformers_cleanup`` run first on input, to do things like stripping | |
36 | newlines), and they return a list in the same format. ``cleanup_transforms`` |
|
36 | prompts and leading indents from copied code. It may not be possible at this | |
37 | are run first; they strip prompts and leading indentation from input. |
|
37 | stage to parse the input as valid Python code. | |
38 | The only default transform in ``line_transforms`` processes cell magics. |
|
38 | * Then IPython runs its own transformations to handle its special syntax, like | |
39 | * ``token_transformers`` is a list of :class:`IPython.core.inputtransformer2.TokenTransformBase` |
|
39 | ``%magics`` and ``!system`` commands. This part does not expose extension | |
40 | subclasses (not instances). They recognise special syntax like |
|
40 | points. | |
41 | ``%line magics`` and ``help?``, and transform them to Python syntax. The |
|
41 | * ``input_transformers_post`` run as the last step, to do things like converting | |
42 | interface for these is more complex; see below. |
|
42 | float literals into decimal objects. These may attempt to parse the input as | |
|
43 | Python code. | |||
43 |
|
44 | |||
44 | 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 | |
45 | 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 | |
46 | Python's own parser decide whether it is valid. |
|
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 | 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 | |
56 | 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 | |
57 | back the right way before IPython tries to run it:: |
|
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 | To start using this:: |
|
60 | To start using this:: | |
67 |
|
61 | |||
68 | ip = get_ipython() |
|
62 | ip = get_ipython() | |
69 |
ip.input_transformer |
|
63 | ip.input_transformers_cleanup.append(reverse_line_chars) | |
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 |
|
||||
149 |
|
64 | |||
150 | AST transformations |
|
65 | AST transformations | |
151 | =================== |
|
66 | =================== |
General Comments 0
You need to be logged in to leave comments.
Login now