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