##// END OF EJS Templates
Add docstrings in IPython.core.inputtransformer
Thomas Kluyver -
Show More
@@ -1,336 +1,382 b''
1 1 import abc
2 2 import re
3 3 from StringIO import StringIO
4 4 import tokenize
5 5
6 6 from IPython.core.splitinput import split_user_input, LineInfo
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Globals
10 10 #-----------------------------------------------------------------------------
11 11
12 12 # The escape sequences that define the syntax transformations IPython will
13 13 # apply to user input. These can NOT be just changed here: many regular
14 14 # expressions and other parts of the code may use their hardcoded values, and
15 15 # for all intents and purposes they constitute the 'IPython syntax', so they
16 16 # should be considered fixed.
17 17
18 18 ESC_SHELL = '!' # Send line to underlying system shell
19 19 ESC_SH_CAP = '!!' # Send line to system shell and capture output
20 20 ESC_HELP = '?' # Find information about object
21 21 ESC_HELP2 = '??' # Find extra-detailed information about object
22 22 ESC_MAGIC = '%' # Call magic function
23 23 ESC_MAGIC2 = '%%' # Call cell-magic function
24 24 ESC_QUOTE = ',' # Split args on whitespace, quote each as string and call
25 25 ESC_QUOTE2 = ';' # Quote all args as a single string, call
26 26 ESC_PAREN = '/' # Call first argument with rest of line as arguments
27 27
28 28 ESC_SEQUENCES = [ESC_SHELL, ESC_SH_CAP, ESC_HELP ,\
29 29 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,\
30 30 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN ]
31 31
32 32
33 33 class InputTransformer(object):
34 """Abstract base class for line-based input transformers."""
34 35 __metaclass__ = abc.ABCMeta
35 36
36 37 @abc.abstractmethod
37 38 def push(self, line):
39 """Send a line of input to the transformer, returning the transformed
40 input or None if the transformer is waiting for more input.
41
42 Must be overridden by subclasses.
43 """
38 44 pass
39 45
40 46 @abc.abstractmethod
41 47 def reset(self):
48 """Return, transformed any lines that the transformer has accumulated,
49 and reset its internal state.
50
51 Must be overridden by subclasses.
52 """
42 53 pass
43 54
55 # Set this to True to allow the transformer to act on lines inside strings.
44 56 look_in_string = False
45 57
46 58 def stateless_input_transformer(func):
47 59 class StatelessInputTransformer(InputTransformer):
48 60 """Decorator for a stateless input transformer implemented as a function."""
49 61 def __init__(self):
50 62 self.func = func
51 63
52 64 def push(self, line):
65 """Send a line of input to the transformer, returning the
66 transformed input."""
53 67 return self.func(line)
54 68
55 69 def reset(self):
70 """No-op - exists for compatibility."""
56 71 pass
57 72
58 73 return StatelessInputTransformer
59 74
60 75 def coroutine_input_transformer(coro):
61 76 class CoroutineInputTransformer(InputTransformer):
77 """Wrapper for input transformers based on coroutines."""
62 78 def __init__(self):
63 79 # Prime it
64 80 self.coro = coro()
65 81 next(self.coro)
66 82
67 83 def push(self, line):
84 """Send a line of input to the transformer, returning the
85 transformed input or None if the transformer is waiting for more
86 input.
87 """
68 88 return self.coro.send(line)
69 89
70 90 def reset(self):
91 """Return, transformed any lines that the transformer has
92 accumulated, and reset its internal state.
93 """
71 94 return self.coro.send(None)
72 95
73 96 return CoroutineInputTransformer
74 97
75 98
76 99 # Utilities
77 100 def _make_help_call(target, esc, lspace, next_input=None):
78 101 """Prepares a pinfo(2)/psearch call from a target name and the escape
79 102 (i.e. ? or ??)"""
80 103 method = 'pinfo2' if esc == '??' \
81 104 else 'psearch' if '*' in target \
82 105 else 'pinfo'
83 106 arg = " ".join([method, target])
84 107 if next_input is None:
85 108 return '%sget_ipython().magic(%r)' % (lspace, arg)
86 109 else:
87 110 return '%sget_ipython().set_next_input(%r);get_ipython().magic(%r)' % \
88 111 (lspace, next_input, arg)
89 112
90 113 @coroutine_input_transformer
91 114 def escaped_transformer():
92 """Translate lines beginning with one of IPython's escape characters."""
115 """Translate lines beginning with one of IPython's escape characters.
116
117 This is stateful to allow magic commands etc. to be continued over several
118 lines using explicit line continuations (\ at the end of a line).
119 """
93 120
94 121 # These define the transformations for the different escape characters.
95 122 def _tr_system(line_info):
96 123 "Translate lines escaped with: !"
97 124 cmd = line_info.line.lstrip().lstrip(ESC_SHELL)
98 125 return '%sget_ipython().system(%r)' % (line_info.pre, cmd)
99 126
100 127 def _tr_system2(line_info):
101 128 "Translate lines escaped with: !!"
102 129 cmd = line_info.line.lstrip()[2:]
103 130 return '%sget_ipython().getoutput(%r)' % (line_info.pre, cmd)
104 131
105 132 def _tr_help(line_info):
106 133 "Translate lines escaped with: ?/??"
107 134 # A naked help line should just fire the intro help screen
108 135 if not line_info.line[1:]:
109 136 return 'get_ipython().show_usage()'
110 137
111 138 return _make_help_call(line_info.ifun, line_info.esc, line_info.pre)
112 139
113 140 def _tr_magic(line_info):
114 141 "Translate lines escaped with: %"
115 142 tpl = '%sget_ipython().magic(%r)'
116 143 cmd = ' '.join([line_info.ifun, line_info.the_rest]).strip()
117 144 return tpl % (line_info.pre, cmd)
118 145
119 146 def _tr_quote(line_info):
120 147 "Translate lines escaped with: ,"
121 148 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
122 149 '", "'.join(line_info.the_rest.split()) )
123 150
124 151 def _tr_quote2(line_info):
125 152 "Translate lines escaped with: ;"
126 153 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
127 154 line_info.the_rest)
128 155
129 156 def _tr_paren(line_info):
130 157 "Translate lines escaped with: /"
131 158 return '%s%s(%s)' % (line_info.pre, line_info.ifun,
132 159 ", ".join(line_info.the_rest.split()))
133 160
134 161 tr = { ESC_SHELL : _tr_system,
135 162 ESC_SH_CAP : _tr_system2,
136 163 ESC_HELP : _tr_help,
137 164 ESC_HELP2 : _tr_help,
138 165 ESC_MAGIC : _tr_magic,
139 166 ESC_QUOTE : _tr_quote,
140 167 ESC_QUOTE2 : _tr_quote2,
141 168 ESC_PAREN : _tr_paren }
142 169
143 170 line = ''
144 171 while True:
145 172 line = (yield line)
146 173 if not line or line.isspace():
147 174 continue
148 175 lineinf = LineInfo(line)
149 176 if lineinf.esc not in tr:
150 177 continue
151 178
152 179 parts = []
153 180 while line is not None:
154 181 parts.append(line.rstrip('\\'))
155 182 if not line.endswith('\\'):
156 183 break
157 184 line = (yield None)
158 185
159 186 # Output
160 187 lineinf = LineInfo(' '.join(parts))
161 188 line = tr[lineinf.esc](lineinf)
162 189
163 190 _initial_space_re = re.compile(r'\s*')
164 191
165 192 _help_end_re = re.compile(r"""(%{0,2}
166 193 [a-zA-Z_*][\w*]* # Variable name
167 194 (\.[a-zA-Z_*][\w*]*)* # .etc.etc
168 195 )
169 196 (\?\??)$ # ? or ??""",
170 197 re.VERBOSE)
171 198
172 199 def has_comment(src):
173 200 """Indicate whether an input line has (i.e. ends in, or is) a comment.
174 201
175 202 This uses tokenize, so it can distinguish comments from # inside strings.
176 203
177 204 Parameters
178 205 ----------
179 206 src : string
180 207 A single line input string.
181 208
182 209 Returns
183 210 -------
184 211 Boolean: True if source has a comment.
185 212 """
186 213 readline = StringIO(src).readline
187 214 toktypes = set()
188 215 try:
189 216 for t in tokenize.generate_tokens(readline):
190 217 toktypes.add(t[0])
191 218 except tokenize.TokenError:
192 219 pass
193 220 return(tokenize.COMMENT in toktypes)
194 221
195 222
196 223 @stateless_input_transformer
197 224 def help_end(line):
198 225 """Translate lines with ?/?? at the end"""
199 226 m = _help_end_re.search(line)
200 227 if m is None or has_comment(line):
201 228 return line
202 229 target = m.group(1)
203 230 esc = m.group(3)
204 231 lspace = _initial_space_re.match(line).group(0)
205 232
206 233 # If we're mid-command, put it back on the next prompt for the user.
207 234 next_input = line.rstrip('?') if line.strip() != m.group(0) else None
208 235
209 236 return _make_help_call(target, esc, lspace, next_input)
210 237
211 238
212 239 @coroutine_input_transformer
213 240 def cellmagic():
241 """Captures & transforms cell magics.
242
243 After a cell magic is started, this stores up any lines it gets until it is
244 reset (sent None).
245 """
214 246 tpl = 'get_ipython().run_cell_magic(%r, %r, %r)'
215 247 cellmagic_help_re = re.compile('%%\w+\?')
216 248 line = ''
217 249 while True:
218 250 line = (yield line)
219 251 if (not line) or (not line.startswith(ESC_MAGIC2)):
220 252 continue
221 253
222 254 if cellmagic_help_re.match(line):
223 255 # This case will be handled by help_end
224 256 continue
225 257
226 258 first = line
227 259 body = []
228 260 line = (yield None)
229 261 while (line is not None) and (line.strip() != ''):
230 262 body.append(line)
231 263 line = (yield None)
232 264
233 265 # Output
234 266 magic_name, _, first = first.partition(' ')
235 267 magic_name = magic_name.lstrip(ESC_MAGIC2)
236 268 line = tpl % (magic_name, first, u'\n'.join(body))
237 269
238 270
239 271 def _strip_prompts(prompt1_re, prompt2_re):
240 272 """Remove matching input prompts from a block of input."""
241 273 line = ''
242 274 while True:
243 275 line = (yield line)
244 276
245 277 if line is None:
246 278 continue
247 279
248 280 m = prompt1_re.match(line)
249 281 if m:
250 282 while m:
251 283 line = (yield line[len(m.group(0)):])
252 284 if line is None:
253 285 break
254 286 m = prompt2_re.match(line)
255 287 else:
256 288 # Prompts not in input - wait for reset
257 289 while line is not None:
258 290 line = (yield line)
259 291
260 292 @coroutine_input_transformer
261 293 def classic_prompt():
294 """Strip the >>>/... prompts of the Python interactive shell."""
262 295 prompt1_re = re.compile(r'^(>>> )')
263 296 prompt2_re = re.compile(r'^(>>> |^\.\.\. )')
264 297 return _strip_prompts(prompt1_re, prompt2_re)
265 298
266 299 classic_prompt.look_in_string = True
267 300
268 301 @coroutine_input_transformer
269 302 def ipy_prompt():
303 """Strip IPython's In [1]:/...: prompts."""
270 304 prompt1_re = re.compile(r'^In \[\d+\]: ')
271 305 prompt2_re = re.compile(r'^(In \[\d+\]: |^\ \ \ \.\.\.+: )')
272 306 return _strip_prompts(prompt1_re, prompt2_re)
273 307
274 308 ipy_prompt.look_in_string = True
275 309
276 310
277 311 @coroutine_input_transformer
278 312 def leading_indent():
313 """Remove leading indentation.
314
315 If the first line starts with a spaces or tabs, the same whitespace will be
316 removed from each following line until it is reset.
317 """
279 318 space_re = re.compile(r'^[ \t]+')
280 319 line = ''
281 320 while True:
282 321 line = (yield line)
283 322
284 323 if line is None:
285 324 continue
286 325
287 326 m = space_re.match(line)
288 327 if m:
289 328 space = m.group(0)
290 329 while line is not None:
291 330 if line.startswith(space):
292 331 line = line[len(space):]
293 332 line = (yield line)
294 333 else:
295 334 # No leading spaces - wait for reset
296 335 while line is not None:
297 336 line = (yield line)
298 337
299 338 leading_indent.look_in_string = True
300 339
301 340
302 341 def _special_assignment(assignment_re, template):
342 """Transform assignment from system & magic commands.
343
344 This is stateful so that it can handle magic commands continued on several
345 lines.
346 """
303 347 line = ''
304 348 while True:
305 349 line = (yield line)
306 350 if not line or line.isspace():
307 351 continue
308 352
309 353 m = assignment_re.match(line)
310 354 if not m:
311 355 continue
312 356
313 357 parts = []
314 358 while line is not None:
315 359 parts.append(line.rstrip('\\'))
316 360 if not line.endswith('\\'):
317 361 break
318 362 line = (yield None)
319 363
320 364 # Output
321 365 whole = assignment_re.match(' '.join(parts))
322 366 line = template % (whole.group('lhs'), whole.group('cmd'))
323 367
324 368 @coroutine_input_transformer
325 369 def assign_from_system():
370 """Transform assignment from system commands (e.g. files = !ls)"""
326 371 assignment_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
327 372 r'\s*=\s*!\s*(?P<cmd>.*)')
328 373 template = '%s = get_ipython().getoutput(%r)'
329 374 return _special_assignment(assignment_re, template)
330 375
331 376 @coroutine_input_transformer
332 377 def assign_from_magic():
378 """Transform assignment from magic commands (e.g. a = %who_ls)"""
333 379 assignment_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
334 380 r'\s*=\s*%\s*(?P<cmd>.*)')
335 381 template = '%s = get_ipython().magic(%r)'
336 382 return _special_assignment(assignment_re, template)
General Comments 0
You need to be logged in to leave comments. Login now