Show More
@@ -191,6 +191,121 b' class SystemAssign:' | |||||
191 |
|
191 | |||
192 | return lines_before + [new_line] + lines_after |
|
192 | return lines_before + [new_line] + lines_after | |
193 |
|
193 | |||
|
194 | # The escape sequences that define the syntax transformations IPython will | |||
|
195 | # apply to user input. These can NOT be just changed here: many regular | |||
|
196 | # expressions and other parts of the code may use their hardcoded values, and | |||
|
197 | # for all intents and purposes they constitute the 'IPython syntax', so they | |||
|
198 | # should be considered fixed. | |||
|
199 | ||||
|
200 | ESC_SHELL = '!' # Send line to underlying system shell | |||
|
201 | ESC_SH_CAP = '!!' # Send line to system shell and capture output | |||
|
202 | ESC_HELP = '?' # Find information about object | |||
|
203 | ESC_HELP2 = '??' # Find extra-detailed information about object | |||
|
204 | ESC_MAGIC = '%' # Call magic function | |||
|
205 | ESC_MAGIC2 = '%%' # Call cell-magic function | |||
|
206 | ESC_QUOTE = ',' # Split args on whitespace, quote each as string and call | |||
|
207 | ESC_QUOTE2 = ';' # Quote all args as a single string, call | |||
|
208 | ESC_PAREN = '/' # Call first argument with rest of line as arguments | |||
|
209 | ||||
|
210 | ESCAPE_SINGLES = {'!', '?', '%', ',', ';', '/'} | |||
|
211 | ESCAPE_DOUBLES = {'!!', '??'} # %% (cell magic) is handled separately | |||
|
212 | ||||
|
213 | def _make_help_call(target, esc, next_input=None): | |||
|
214 | """Prepares a pinfo(2)/psearch call from a target name and the escape | |||
|
215 | (i.e. ? or ??)""" | |||
|
216 | method = 'pinfo2' if esc == '??' \ | |||
|
217 | else 'psearch' if '*' in target \ | |||
|
218 | else 'pinfo' | |||
|
219 | arg = " ".join([method, target]) | |||
|
220 | #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args) | |||
|
221 | t_magic_name, _, t_magic_arg_s = arg.partition(' ') | |||
|
222 | t_magic_name = t_magic_name.lstrip(ESC_MAGIC) | |||
|
223 | if next_input is None: | |||
|
224 | return 'get_ipython().run_line_magic(%r, %r)' % (t_magic_name, t_magic_arg_s) | |||
|
225 | else: | |||
|
226 | return 'get_ipython().set_next_input(%r);get_ipython().run_line_magic(%r, %r)' % \ | |||
|
227 | (next_input, t_magic_name, t_magic_arg_s) | |||
|
228 | ||||
|
229 | def _tr_help(content): | |||
|
230 | "Translate lines escaped with: ?" | |||
|
231 | # A naked help line should just fire the intro help screen | |||
|
232 | if not content: | |||
|
233 | return 'get_ipython().show_usage()' | |||
|
234 | ||||
|
235 | return _make_help_call(content, '?') | |||
|
236 | ||||
|
237 | def _tr_help2(content): | |||
|
238 | "Translate lines escaped with: ??" | |||
|
239 | # A naked help line should just fire the intro help screen | |||
|
240 | if not content: | |||
|
241 | return 'get_ipython().show_usage()' | |||
|
242 | ||||
|
243 | return _make_help_call(content, '??') | |||
|
244 | ||||
|
245 | def _tr_magic(content): | |||
|
246 | "Translate lines escaped with: %" | |||
|
247 | name, _, args = content.partition(' ') | |||
|
248 | return 'get_ipython().run_line_magic(%r, %r)' % (name, args) | |||
|
249 | ||||
|
250 | def _tr_quote(content): | |||
|
251 | "Translate lines escaped with: ," | |||
|
252 | name, _, args = content.partition(' ') | |||
|
253 | return '%s("%s")' % (name, '", "'.join(args.split()) ) | |||
|
254 | ||||
|
255 | def _tr_quote2(content): | |||
|
256 | "Translate lines escaped with: ;" | |||
|
257 | name, _, args = content.partition(' ') | |||
|
258 | return '%s("%s")' % (name, args) | |||
|
259 | ||||
|
260 | def _tr_paren(content): | |||
|
261 | "Translate lines escaped with: /" | |||
|
262 | name, _, args = content.partition(' ') | |||
|
263 | return '%s(%s)' % (name, ", ".join(args.split())) | |||
|
264 | ||||
|
265 | tr = { ESC_SHELL : 'get_ipython().system({!r})'.format, | |||
|
266 | ESC_SH_CAP : 'get_ipython().getoutput({!r})'.format, | |||
|
267 | ESC_HELP : _tr_help, | |||
|
268 | ESC_HELP2 : _tr_help2, | |||
|
269 | ESC_MAGIC : _tr_magic, | |||
|
270 | ESC_QUOTE : _tr_quote, | |||
|
271 | ESC_QUOTE2 : _tr_quote2, | |||
|
272 | ESC_PAREN : _tr_paren } | |||
|
273 | ||||
|
274 | class EscapedCommand: | |||
|
275 | @staticmethod | |||
|
276 | def find(tokens_by_line): | |||
|
277 | """Find the first escaped command (%foo, !foo, etc.) in the cell. | |||
|
278 | ||||
|
279 | Returns (line, column) of the escape if found, or None. *line* is 1-indexed. | |||
|
280 | """ | |||
|
281 | for line in tokens_by_line: | |||
|
282 | ix = 0 | |||
|
283 | while line[ix].type in {tokenize2.INDENT, tokenize2.DEDENT}: | |||
|
284 | ix += 1 | |||
|
285 | if line[ix].string in ESCAPE_SINGLES: | |||
|
286 | return line[ix].start | |||
|
287 | ||||
|
288 | @staticmethod | |||
|
289 | def transform(lines, start): | |||
|
290 | start_line = start[0] - 1 # Shift from 1-index to 0-index | |||
|
291 | start_col = start[1] | |||
|
292 | ||||
|
293 | indent = lines[start_line][:start_col] | |||
|
294 | end_line = find_end_of_continued_line(lines, start_line) | |||
|
295 | line = assemble_continued_line(lines, (start_line, start_col), end_line) | |||
|
296 | ||||
|
297 | if line[:2] in ESCAPE_DOUBLES: | |||
|
298 | escape, content = line[:2], line[2:] | |||
|
299 | else: | |||
|
300 | escape, content = line[:1], line[1:] | |||
|
301 | call = tr[escape](content) | |||
|
302 | ||||
|
303 | lines_before = lines[:start_line] | |||
|
304 | new_line = indent + call + '\n' | |||
|
305 | lines_after = lines[end_line + 1:] | |||
|
306 | ||||
|
307 | return lines_before + [new_line] + lines_after | |||
|
308 | ||||
194 | def make_tokens_by_line(lines): |
|
309 | def make_tokens_by_line(lines): | |
195 | tokens_by_line = [[]] |
|
310 | tokens_by_line = [[]] | |
196 | for token in generate_tokens(iter(lines).__next__): |
|
311 | for token in generate_tokens(iter(lines).__next__): |
@@ -3,6 +3,17 b' import nose.tools as nt' | |||||
3 | from IPython.core import inputtransformer2 as ipt2 |
|
3 | from IPython.core import inputtransformer2 as ipt2 | |
4 | from IPython.core.inputtransformer2 import make_tokens_by_line |
|
4 | from IPython.core.inputtransformer2 import make_tokens_by_line | |
5 |
|
5 | |||
|
6 | MULTILINE_MAGIC = ("""\ | |||
|
7 | a = f() | |||
|
8 | %foo \\ | |||
|
9 | bar | |||
|
10 | g() | |||
|
11 | """.splitlines(keepends=True), """\ | |||
|
12 | a = f() | |||
|
13 | get_ipython().run_line_magic('foo', ' bar') | |||
|
14 | g() | |||
|
15 | """.splitlines(keepends=True)) | |||
|
16 | ||||
6 | MULTILINE_MAGIC_ASSIGN = ("""\ |
|
17 | MULTILINE_MAGIC_ASSIGN = ("""\ | |
7 | a = f() |
|
18 | a = f() | |
8 | b = %foo \\ |
|
19 | b = %foo \\ | |
@@ -58,3 +69,14 b' def test_find_assign_system():' | |||||
58 | def test_transform_assign_system(): |
|
69 | def test_transform_assign_system(): | |
59 | res = ipt2.SystemAssign.transform(MULTILINE_SYSTEM_ASSIGN[0], (2, 4)) |
|
70 | res = ipt2.SystemAssign.transform(MULTILINE_SYSTEM_ASSIGN[0], (2, 4)) | |
60 | nt.assert_equal(res, MULTILINE_SYSTEM_ASSIGN[1]) |
|
71 | nt.assert_equal(res, MULTILINE_SYSTEM_ASSIGN[1]) | |
|
72 | ||||
|
73 | def test_find_magic_escape(): | |||
|
74 | tbl = make_tokens_by_line(MULTILINE_MAGIC[0]) | |||
|
75 | nt.assert_equal(ipt2.EscapedCommand.find(tbl), (2, 0)) | |||
|
76 | ||||
|
77 | tbl = make_tokens_by_line(MULTILINE_MAGIC_ASSIGN[0]) # Shouldn't find a = %foo | |||
|
78 | nt.assert_equal(ipt2.EscapedCommand.find(tbl), None) | |||
|
79 | ||||
|
80 | def test_transform_magic_escape(): | |||
|
81 | res = ipt2.EscapedCommand.transform(MULTILINE_MAGIC[0], (2, 0)) | |||
|
82 | nt.assert_equal(res, MULTILINE_MAGIC[1]) |
General Comments 0
You need to be logged in to leave comments.
Login now