##// END OF EJS Templates
Apply suggestions from code review...
Matthias Bussonnier -
Show More
@@ -1,734 +1,732 b''
1 """Implementation of code management magic functions.
1 """Implementation of code management magic functions.
2 """
2 """
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (c) 2012 The IPython Development Team.
4 # Copyright (c) 2012 The IPython Development Team.
5 #
5 #
6 # Distributed under the terms of the Modified BSD License.
6 # Distributed under the terms of the Modified BSD License.
7 #
7 #
8 # The full license is in the file COPYING.txt, distributed with this software.
8 # The full license is in the file COPYING.txt, distributed with this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 # Stdlib
15 # Stdlib
16 import inspect
16 import inspect
17 import io
17 import io
18 import os
18 import os
19 import re
19 import re
20 import sys
20 import sys
21 import ast
21 import ast
22 from itertools import chain
22 from itertools import chain
23 from urllib.request import urlopen
23 from urllib.request import urlopen
24 from urllib.parse import urlencode
24 from urllib.parse import urlencode
25 from pathlib import Path
25 from pathlib import Path
26
26
27 # Our own packages
27 # Our own packages
28 from IPython.core.error import TryNext, StdinNotImplementedError, UsageError
28 from IPython.core.error import TryNext, StdinNotImplementedError, UsageError
29 from IPython.core.macro import Macro
29 from IPython.core.macro import Macro
30 from IPython.core.magic import Magics, magics_class, line_magic
30 from IPython.core.magic import Magics, magics_class, line_magic
31 from IPython.core.oinspect import find_file, find_source_lines
31 from IPython.core.oinspect import find_file, find_source_lines
32 from IPython.testing.skipdoctest import skip_doctest
32 from IPython.testing.skipdoctest import skip_doctest
33 from IPython.utils.contexts import preserve_keys
33 from IPython.utils.contexts import preserve_keys
34 from IPython.utils.path import get_py_filename
34 from IPython.utils.path import get_py_filename
35 from warnings import warn
35 from warnings import warn
36 from logging import error
36 from logging import error
37 from IPython.utils.text import get_text_list
37 from IPython.utils.text import get_text_list
38
38
39 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
40 # Magic implementation classes
40 # Magic implementation classes
41 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42
42
43 # Used for exception handling in magic_edit
43 # Used for exception handling in magic_edit
44 class MacroToEdit(ValueError): pass
44 class MacroToEdit(ValueError): pass
45
45
46 ipython_input_pat = re.compile(r"<ipython\-input\-(\d+)-[a-z\d]+>$")
46 ipython_input_pat = re.compile(r"<ipython\-input\-(\d+)-[a-z\d]+>$")
47
47
48 # To match, e.g. 8-10 1:5 :10 3-
48 # To match, e.g. 8-10 1:5 :10 3-
49 range_re = re.compile(r"""
49 range_re = re.compile(r"""
50 (?P<start>\d+)?
50 (?P<start>\d+)?
51 ((?P<sep>[\-:])
51 ((?P<sep>[\-:])
52 (?P<end>\d+)?)?
52 (?P<end>\d+)?)?
53 $""", re.VERBOSE)
53 $""", re.VERBOSE)
54
54
55
55
56 def extract_code_ranges(ranges_str):
56 def extract_code_ranges(ranges_str):
57 """Turn a string of range for %%load into 2-tuples of (start, stop)
57 """Turn a string of range for %%load into 2-tuples of (start, stop)
58 ready to use as a slice of the content split by lines.
58 ready to use as a slice of the content split by lines.
59
59
60 Examples
60 Examples
61 --------
61 --------
62 list(extract_input_ranges("5-10 2"))
62 list(extract_input_ranges("5-10 2"))
63 [(4, 10), (1, 2)]
63 [(4, 10), (1, 2)]
64 """
64 """
65 for range_str in ranges_str.split():
65 for range_str in ranges_str.split():
66 rmatch = range_re.match(range_str)
66 rmatch = range_re.match(range_str)
67 if not rmatch:
67 if not rmatch:
68 continue
68 continue
69 sep = rmatch.group("sep")
69 sep = rmatch.group("sep")
70 start = rmatch.group("start")
70 start = rmatch.group("start")
71 end = rmatch.group("end")
71 end = rmatch.group("end")
72
72
73 if sep == '-':
73 if sep == '-':
74 start = int(start) - 1 if start else None
74 start = int(start) - 1 if start else None
75 end = int(end) if end else None
75 end = int(end) if end else None
76 elif sep == ':':
76 elif sep == ':':
77 start = int(start) - 1 if start else None
77 start = int(start) - 1 if start else None
78 end = int(end) - 1 if end else None
78 end = int(end) - 1 if end else None
79 else:
79 else:
80 end = int(start)
80 end = int(start)
81 start = int(start) - 1
81 start = int(start) - 1
82 yield (start, end)
82 yield (start, end)
83
83
84
84
85 def extract_symbols(code, symbols):
85 def extract_symbols(code, symbols):
86 """
86 """
87 Return a tuple (blocks, not_found)
87 Return a tuple (blocks, not_found)
88 where ``blocks`` is a list of code fragments
88 where ``blocks`` is a list of code fragments
89 for each symbol parsed from code, and ``not_found`` are
89 for each symbol parsed from code, and ``not_found`` are
90 symbols not found in the code.
90 symbols not found in the code.
91
91
92 For example::
92 For example::
93
93
94 In [1]: code = '''a = 10
94 In [1]: code = '''a = 10
95 ...: def b(): return 42
95 ...: def b(): return 42
96 ...: class A: pass'''
96 ...: class A: pass'''
97
97
98 In [2]: extract_symbols(code, 'A,b,z')
98 In [2]: extract_symbols(code, 'A,b,z')
99 Out[2]: (['class A: pass\\n', 'def b(): return 42\\n'], ['z'])
99 Out[2]: (['class A: pass\\n', 'def b(): return 42\\n'], ['z'])
100 """
100 """
101 symbols = symbols.split(',')
101 symbols = symbols.split(',')
102
102
103 # this will raise SyntaxError if code isn't valid Python
103 # this will raise SyntaxError if code isn't valid Python
104 py_code = ast.parse(code)
104 py_code = ast.parse(code)
105
105
106 marks = [(getattr(s, 'name', None), s.lineno) for s in py_code.body]
106 marks = [(getattr(s, 'name', None), s.lineno) for s in py_code.body]
107 code = code.split('\n')
107 code = code.split('\n')
108
108
109 symbols_lines = {}
109 symbols_lines = {}
110
110
111 # we already know the start_lineno of each symbol (marks).
111 # we already know the start_lineno of each symbol (marks).
112 # To find each end_lineno, we traverse in reverse order until each
112 # To find each end_lineno, we traverse in reverse order until each
113 # non-blank line
113 # non-blank line
114 end = len(code)
114 end = len(code)
115 for name, start in reversed(marks):
115 for name, start in reversed(marks):
116 while not code[end - 1].strip():
116 while not code[end - 1].strip():
117 end -= 1
117 end -= 1
118 if name:
118 if name:
119 symbols_lines[name] = (start - 1, end)
119 symbols_lines[name] = (start - 1, end)
120 end = start - 1
120 end = start - 1
121
121
122 # Now symbols_lines is a map
122 # Now symbols_lines is a map
123 # {'symbol_name': (start_lineno, end_lineno), ...}
123 # {'symbol_name': (start_lineno, end_lineno), ...}
124
124
125 # fill a list with chunks of codes for each requested symbol
125 # fill a list with chunks of codes for each requested symbol
126 blocks = []
126 blocks = []
127 not_found = []
127 not_found = []
128 for symbol in symbols:
128 for symbol in symbols:
129 if symbol in symbols_lines:
129 if symbol in symbols_lines:
130 start, end = symbols_lines[symbol]
130 start, end = symbols_lines[symbol]
131 blocks.append('\n'.join(code[start:end]) + '\n')
131 blocks.append('\n'.join(code[start:end]) + '\n')
132 else:
132 else:
133 not_found.append(symbol)
133 not_found.append(symbol)
134
134
135 return blocks, not_found
135 return blocks, not_found
136
136
137 def strip_initial_indent(lines):
137 def strip_initial_indent(lines):
138 """For %load, strip indent from lines until finding an unindented line.
138 """For %load, strip indent from lines until finding an unindented line.
139
139
140 https://github.com/ipython/ipython/issues/9775
140 https://github.com/ipython/ipython/issues/9775
141 """
141 """
142 indent_re = re.compile(r'\s+')
142 indent_re = re.compile(r'\s+')
143
143
144 it = iter(lines)
144 it = iter(lines)
145 first_line = next(it)
145 first_line = next(it)
146 indent_match = indent_re.match(first_line)
146 indent_match = indent_re.match(first_line)
147
147
148 if indent_match:
148 if indent_match:
149 # First line was indented
149 # First line was indented
150 indent = indent_match.group()
150 indent = indent_match.group()
151 yield first_line[len(indent):]
151 yield first_line[len(indent):]
152
152
153 for line in it:
153 for line in it:
154 if line.startswith(indent):
154 if line.startswith(indent):
155 yield line[len(indent):]
155 yield line[len(indent):]
156 else:
156 else:
157 # Less indented than the first line - stop dedenting
157 # Less indented than the first line - stop dedenting
158 yield line
158 yield line
159 break
159 break
160 else:
160 else:
161 yield first_line
161 yield first_line
162
162
163 # Pass the remaining lines through without dedenting
163 # Pass the remaining lines through without dedenting
164 for line in it:
164 for line in it:
165 yield line
165 yield line
166
166
167
167
168 class InteractivelyDefined(Exception):
168 class InteractivelyDefined(Exception):
169 """Exception for interactively defined variable in magic_edit"""
169 """Exception for interactively defined variable in magic_edit"""
170 def __init__(self, index):
170 def __init__(self, index):
171 self.index = index
171 self.index = index
172
172
173
173
174 @magics_class
174 @magics_class
175 class CodeMagics(Magics):
175 class CodeMagics(Magics):
176 """Magics related to code management (loading, saving, editing, ...)."""
176 """Magics related to code management (loading, saving, editing, ...)."""
177
177
178 def __init__(self, *args, **kwargs):
178 def __init__(self, *args, **kwargs):
179 self._knowntemps = set()
179 self._knowntemps = set()
180 super(CodeMagics, self).__init__(*args, **kwargs)
180 super(CodeMagics, self).__init__(*args, **kwargs)
181
181
182 @line_magic
182 @line_magic
183 def save(self, parameter_s=''):
183 def save(self, parameter_s=''):
184 """Save a set of lines or a macro to a given filename.
184 """Save a set of lines or a macro to a given filename.
185
185
186 Usage:\\
186 Usage:\\
187 %save [options] filename n1-n2 n3-n4 ... n5 .. n6 ...
187 %save [options] filename n1-n2 n3-n4 ... n5 .. n6 ...
188
188
189 Options:
189 Options:
190
190
191 -r: use 'raw' input. By default, the 'processed' history is used,
191 -r: use 'raw' input. By default, the 'processed' history is used,
192 so that magics are loaded in their transformed version to valid
192 so that magics are loaded in their transformed version to valid
193 Python. If this option is given, the raw input as typed as the
193 Python. If this option is given, the raw input as typed as the
194 command line is used instead.
194 command line is used instead.
195
195
196 -f: force overwrite. If file exists, %save will prompt for overwrite
196 -f: force overwrite. If file exists, %save will prompt for overwrite
197 unless -f is given.
197 unless -f is given.
198
198
199 -a: append to the file instead of overwriting it.
199 -a: append to the file instead of overwriting it.
200
200
201 This function uses the same syntax as %history for input ranges,
201 This function uses the same syntax as %history for input ranges,
202 then saves the lines to the filename you specify.
202 then saves the lines to the filename you specify.
203
203
204 It adds a '.py' extension to the file if you don't do so yourself, and
204 It adds a '.py' extension to the file if you don't do so yourself, and
205 it asks for confirmation before overwriting existing files.
205 it asks for confirmation before overwriting existing files.
206
206
207 If `-r` option is used, the default extension is `.ipy`.
207 If `-r` option is used, the default extension is `.ipy`.
208 """
208 """
209
209
210 opts,args = self.parse_options(parameter_s,'fra',mode='list')
210 opts,args = self.parse_options(parameter_s,'fra',mode='list')
211 if not args:
211 if not args:
212 raise UsageError('Missing filename.')
212 raise UsageError('Missing filename.')
213 raw = 'r' in opts
213 raw = 'r' in opts
214 force = 'f' in opts
214 force = 'f' in opts
215 append = 'a' in opts
215 append = 'a' in opts
216 mode = 'a' if append else 'w'
216 mode = 'a' if append else 'w'
217 ext = '.ipy' if raw else '.py'
217 ext = '.ipy' if raw else '.py'
218 fname, codefrom = args[0], " ".join(args[1:])
218 fname, codefrom = args[0], " ".join(args[1:])
219 if not fname.endswith(('.py','.ipy')):
219 if not fname.endswith(('.py','.ipy')):
220 fname += ext
220 fname += ext
221 file_exists = os.path.isfile(fname)
221 file_exists = os.path.isfile(fname)
222 if file_exists and not force and not append:
222 if file_exists and not force and not append:
223 try:
223 try:
224 overwrite = self.shell.ask_yes_no('File `%s` exists. Overwrite (y/[N])? ' % fname, default='n')
224 overwrite = self.shell.ask_yes_no('File `%s` exists. Overwrite (y/[N])? ' % fname, default='n')
225 except StdinNotImplementedError:
225 except StdinNotImplementedError:
226 print("File `%s` exists. Use `%%save -f %s` to force overwrite" % (fname, parameter_s))
226 print("File `%s` exists. Use `%%save -f %s` to force overwrite" % (fname, parameter_s))
227 return
227 return
228 if not overwrite :
228 if not overwrite :
229 print('Operation cancelled.')
229 print('Operation cancelled.')
230 return
230 return
231 try:
231 try:
232 cmds = self.shell.find_user_code(codefrom,raw)
232 cmds = self.shell.find_user_code(codefrom,raw)
233 except (TypeError, ValueError) as e:
233 except (TypeError, ValueError) as e:
234 print(e.args[0])
234 print(e.args[0])
235 return
235 return
236 with io.open(fname, mode, encoding="utf-8") as f:
236 with io.open(fname, mode, encoding="utf-8") as f:
237 if not file_exists or not append:
237 if not file_exists or not append:
238 f.write("# coding: utf-8\n")
238 f.write("# coding: utf-8\n")
239 f.write(cmds)
239 f.write(cmds)
240 # make sure we end on a newline
240 # make sure we end on a newline
241 if not cmds.endswith('\n'):
241 if not cmds.endswith('\n'):
242 f.write('\n')
242 f.write('\n')
243 print('The following commands were written to file `%s`:' % fname)
243 print('The following commands were written to file `%s`:' % fname)
244 print(cmds)
244 print(cmds)
245
245
246 @line_magic
246 @line_magic
247 def pastebin(self, parameter_s=''):
247 def pastebin(self, parameter_s=''):
248 """Upload code to dpaste's paste bin, returning the URL.
248 """Upload code to dpaste's paste bin, returning the URL.
249
249
250 Usage:\\
250 Usage:\\
251 %pastebin [-d "Custom description"] 1-7
251 %pastebin [-d "Custom description"] 1-7
252
252
253 The argument can be an input history range, a filename, or the name of a
253 The argument can be an input history range, a filename, or the name of a
254 string or macro.
254 string or macro.
255
255
256 Options:
256 Options:
257
257
258 -d: Pass a custom description for the gist. The default will say
258 -d: Pass a custom description for the gist. The default will say
259 "Pasted from IPython".
259 "Pasted from IPython".
260 """
260 """
261 opts, args = self.parse_options(parameter_s, 'd:')
261 opts, args = self.parse_options(parameter_s, 'd:')
262
262
263 try:
263 try:
264 code = self.shell.find_user_code(args)
264 code = self.shell.find_user_code(args)
265 except (ValueError, TypeError) as e:
265 except (ValueError, TypeError) as e:
266 print(e.args[0])
266 print(e.args[0])
267 return
267 return
268
268
269 post_data = urlencode({
269 post_data = urlencode({
270 "title": opts.get('d', "Pasted from IPython"),
270 "title": opts.get('d', "Pasted from IPython"),
271 "syntax": "python3",
271 "syntax": "python3",
272 "content": code
272 "content": code
273 }).encode('utf-8')
273 }).encode('utf-8')
274
274
275 response = urlopen("http://dpaste.com/api/v2/", post_data)
275 response = urlopen("http://dpaste.com/api/v2/", post_data)
276 return response.headers.get('Location')
276 return response.headers.get('Location')
277
277
278 @line_magic
278 @line_magic
279 def loadpy(self, arg_s):
279 def loadpy(self, arg_s):
280 """Alias of `%load`
280 """Alias of `%load`
281
281
282 `%loadpy` has gained some flexibility and dropped the requirement of a `.py`
282 `%loadpy` has gained some flexibility and dropped the requirement of a `.py`
283 extension. So it has been renamed simply into %load. You can look at
283 extension. So it has been renamed simply into %load. You can look at
284 `%load`'s docstring for more info.
284 `%load`'s docstring for more info.
285 """
285 """
286 self.load(arg_s)
286 self.load(arg_s)
287
287
288 @line_magic
288 @line_magic
289 def load(self, arg_s):
289 def load(self, arg_s):
290 """Load code into the current frontend.
290 """Load code into the current frontend.
291
291
292 Usage:\\
292 Usage:\\
293 %load [options] source
293 %load [options] source
294
294
295 where source can be a filename, URL, input history range, macro, or
295 where source can be a filename, URL, input history range, macro, or
296 element in the user namespace
296 element in the user namespace
297
297
298 Options:
298 Options:
299
299
300 -r <lines>: Specify lines or ranges of lines to load from the source.
300 -r <lines>: Specify lines or ranges of lines to load from the source.
301 Ranges could be specified as x-y (x..y) or in python-style x:y
301 Ranges could be specified as x-y (x..y) or in python-style x:y
302 (x..(y-1)). Both limits x and y can be left blank (meaning the
302 (x..(y-1)). Both limits x and y can be left blank (meaning the
303 beginning and end of the file, respectively).
303 beginning and end of the file, respectively).
304
304
305 -s <symbols>: Specify function or classes to load from python source.
305 -s <symbols>: Specify function or classes to load from python source.
306
306
307 -y : Don't ask confirmation for loading source above 200 000 characters.
307 -y : Don't ask confirmation for loading source above 200 000 characters.
308
308
309 -n : Include the user's namespace when searching for source code.
309 -n : Include the user's namespace when searching for source code.
310
310
311 This magic command can either take a local filename, a URL, an history
311 This magic command can either take a local filename, a URL, an history
312 range (see %history) or a macro as argument, it will prompt for
312 range (see %history) or a macro as argument, it will prompt for
313 confirmation before loading source with more than 200 000 characters, unless
313 confirmation before loading source with more than 200 000 characters, unless
314 -y flag is passed or if the frontend does not support raw_input::
314 -y flag is passed or if the frontend does not support raw_input::
315
315
316 %load myscript.py
316 %load myscript.py
317 %load 7-27
317 %load 7-27
318 %load myMacro
318 %load myMacro
319 %load http://www.example.com/myscript.py
319 %load http://www.example.com/myscript.py
320 %load -r 5-10 myscript.py
320 %load -r 5-10 myscript.py
321 %load -r 10-20,30,40: foo.py
321 %load -r 10-20,30,40: foo.py
322 %load -s MyClass,wonder_function myscript.py
322 %load -s MyClass,wonder_function myscript.py
323 %load -n MyClass
323 %load -n MyClass
324 %load -n my_module.wonder_function
324 %load -n my_module.wonder_function
325 """
325 """
326 opts,args = self.parse_options(arg_s,'yns:r:')
326 opts,args = self.parse_options(arg_s,'yns:r:')
327
327
328 if not args:
328 if not args:
329 raise UsageError('Missing filename, URL, input history range, '
329 raise UsageError('Missing filename, URL, input history range, '
330 'macro, or element in the user namespace.')
330 'macro, or element in the user namespace.')
331
331
332 search_ns = 'n' in opts
332 search_ns = 'n' in opts
333
333
334 contents = self.shell.find_user_code(args, search_ns=search_ns)
334 contents = self.shell.find_user_code(args, search_ns=search_ns)
335
335
336 if 's' in opts:
336 if 's' in opts:
337 try:
337 try:
338 blocks, not_found = extract_symbols(contents, opts['s'])
338 blocks, not_found = extract_symbols(contents, opts['s'])
339 except SyntaxError:
339 except SyntaxError:
340 # non python code
340 # non python code
341 error("Unable to parse the input as valid Python code")
341 error("Unable to parse the input as valid Python code")
342 return
342 return
343
343
344 if len(not_found) == 1:
344 if len(not_found) == 1:
345 warn('The symbol `%s` was not found' % not_found[0])
345 warn('The symbol `%s` was not found' % not_found[0])
346 elif len(not_found) > 1:
346 elif len(not_found) > 1:
347 warn('The symbols %s were not found' % get_text_list(not_found,
347 warn('The symbols %s were not found' % get_text_list(not_found,
348 wrap_item_with='`')
348 wrap_item_with='`')
349 )
349 )
350
350
351 contents = '\n'.join(blocks)
351 contents = '\n'.join(blocks)
352
352
353 if 'r' in opts:
353 if 'r' in opts:
354 ranges = opts['r'].replace(',', ' ')
354 ranges = opts['r'].replace(',', ' ')
355 lines = contents.split('\n')
355 lines = contents.split('\n')
356 slices = extract_code_ranges(ranges)
356 slices = extract_code_ranges(ranges)
357 contents = [lines[slice(*slc)] for slc in slices]
357 contents = [lines[slice(*slc)] for slc in slices]
358 contents = '\n'.join(strip_initial_indent(chain.from_iterable(contents)))
358 contents = '\n'.join(strip_initial_indent(chain.from_iterable(contents)))
359
359
360 l = len(contents)
360 l = len(contents)
361
361
362 # 200 000 is ~ 2500 full 80 character lines
362 # 200 000 is ~ 2500 full 80 character lines
363 # so in average, more than 5000 lines
363 # so in average, more than 5000 lines
364 if l > 200000 and 'y' not in opts:
364 if l > 200000 and 'y' not in opts:
365 try:
365 try:
366 ans = self.shell.ask_yes_no(("The text you're trying to load seems pretty big"\
366 ans = self.shell.ask_yes_no(("The text you're trying to load seems pretty big"\
367 " (%d characters). Continue (y/[N]) ?" % l), default='n' )
367 " (%d characters). Continue (y/[N]) ?" % l), default='n' )
368 except StdinNotImplementedError:
368 except StdinNotImplementedError:
369 #assume yes if raw input not implemented
369 #assume yes if raw input not implemented
370 ans = True
370 ans = True
371
371
372 if ans is False :
372 if ans is False :
373 print('Operation cancelled.')
373 print('Operation cancelled.')
374 return
374 return
375
375
376 contents = "# %load {}\n".format(arg_s) + contents
376 contents = "# %load {}\n".format(arg_s) + contents
377
377
378 self.shell.set_next_input(contents, replace=True)
378 self.shell.set_next_input(contents, replace=True)
379
379
380 @staticmethod
380 @staticmethod
381 def _find_edit_target(shell, args, opts, last_call):
381 def _find_edit_target(shell, args, opts, last_call):
382 """Utility method used by magic_edit to find what to edit."""
382 """Utility method used by magic_edit to find what to edit."""
383
383
384 def make_filename(arg):
384 def make_filename(arg):
385 "Make a filename from the given args"
385 "Make a filename from the given args"
386 try:
386 try:
387 filename = get_py_filename(arg)
387 filename = get_py_filename(arg)
388 except IOError:
388 except IOError:
389 # If it ends with .py but doesn't already exist, assume we want
389 # If it ends with .py but doesn't already exist, assume we want
390 # a new file.
390 # a new file.
391 if arg.endswith('.py'):
391 if arg.endswith('.py'):
392 filename = arg
392 filename = arg
393 else:
393 else:
394 filename = None
394 filename = None
395 return filename
395 return filename
396
396
397 # Set a few locals from the options for convenience:
397 # Set a few locals from the options for convenience:
398 opts_prev = 'p' in opts
398 opts_prev = 'p' in opts
399 opts_raw = 'r' in opts
399 opts_raw = 'r' in opts
400
400
401 # custom exceptions
401 # custom exceptions
402 class DataIsObject(Exception): pass
402 class DataIsObject(Exception): pass
403
403
404 # Default line number value
404 # Default line number value
405 lineno = opts.get('n',None)
405 lineno = opts.get('n',None)
406
406
407 if opts_prev:
407 if opts_prev:
408 args = '_%s' % last_call[0]
408 args = '_%s' % last_call[0]
409 if args not in shell.user_ns:
409 if args not in shell.user_ns:
410 args = last_call[1]
410 args = last_call[1]
411
411
412 # by default this is done with temp files, except when the given
412 # by default this is done with temp files, except when the given
413 # arg is a filename
413 # arg is a filename
414 use_temp = True
414 use_temp = True
415
415
416 data = ''
416 data = ''
417
417
418 # First, see if the arguments should be a filename.
418 # First, see if the arguments should be a filename.
419 filename = make_filename(args)
419 filename = make_filename(args)
420 if filename:
420 if filename:
421 use_temp = False
421 use_temp = False
422 elif args:
422 elif args:
423 # Mode where user specifies ranges of lines, like in %macro.
423 # Mode where user specifies ranges of lines, like in %macro.
424 data = shell.extract_input_lines(args, opts_raw)
424 data = shell.extract_input_lines(args, opts_raw)
425 if not data:
425 if not data:
426 try:
426 try:
427 # Load the parameter given as a variable. If not a string,
427 # Load the parameter given as a variable. If not a string,
428 # process it as an object instead (below)
428 # process it as an object instead (below)
429
429
430 #print '*** args',args,'type',type(args) # dbg
430 #print '*** args',args,'type',type(args) # dbg
431 data = eval(args, shell.user_ns)
431 data = eval(args, shell.user_ns)
432 if not isinstance(data, str):
432 if not isinstance(data, str):
433 raise DataIsObject
433 raise DataIsObject
434
434
435 except (NameError,SyntaxError):
435 except (NameError,SyntaxError):
436 # given argument is not a variable, try as a filename
436 # given argument is not a variable, try as a filename
437 filename = make_filename(args)
437 filename = make_filename(args)
438 if filename is None:
438 if filename is None:
439 warn("Argument given (%s) can't be found as a variable "
439 warn("Argument given (%s) can't be found as a variable "
440 "or as a filename." % args)
440 "or as a filename." % args)
441 return (None, None, None)
441 return (None, None, None)
442 use_temp = False
442 use_temp = False
443
443
444 except DataIsObject as e:
444 except DataIsObject as e:
445 # macros have a special edit function
445 # macros have a special edit function
446 if isinstance(data, Macro):
446 if isinstance(data, Macro):
447 raise MacroToEdit(data) from e
447 raise MacroToEdit(data) from e
448
448
449 # For objects, try to edit the file where they are defined
449 # For objects, try to edit the file where they are defined
450 filename = find_file(data)
450 filename = find_file(data)
451 if filename:
451 if filename:
452 if 'fakemodule' in filename.lower() and \
452 if 'fakemodule' in filename.lower() and \
453 inspect.isclass(data):
453 inspect.isclass(data):
454 # class created by %edit? Try to find source
454 # class created by %edit? Try to find source
455 # by looking for method definitions instead, the
455 # by looking for method definitions instead, the
456 # __module__ in those classes is FakeModule.
456 # __module__ in those classes is FakeModule.
457 attrs = [getattr(data, aname) for aname in dir(data)]
457 attrs = [getattr(data, aname) for aname in dir(data)]
458 for attr in attrs:
458 for attr in attrs:
459 if not inspect.ismethod(attr):
459 if not inspect.ismethod(attr):
460 continue
460 continue
461 filename = find_file(attr)
461 filename = find_file(attr)
462 if filename and \
462 if filename and \
463 'fakemodule' not in filename.lower():
463 'fakemodule' not in filename.lower():
464 # change the attribute to be the edit
464 # change the attribute to be the edit
465 # target instead
465 # target instead
466 data = attr
466 data = attr
467 break
467 break
468
468
469 m = ipython_input_pat.match(os.path.basename(filename))
469 m = ipython_input_pat.match(os.path.basename(filename))
470 if m:
470 if m:
471 raise InteractivelyDefined(int(m.groups()[0])) from e
471 raise InteractivelyDefined(int(m.groups()[0])) from e
472
472
473 datafile = 1
473 datafile = 1
474 if filename is None:
474 if filename is None:
475 filename = make_filename(args)
475 filename = make_filename(args)
476 datafile = 1
476 datafile = 1
477 if filename is not None:
477 if filename is not None:
478 # only warn about this if we get a real name
478 # only warn about this if we get a real name
479 warn('Could not find file where `%s` is defined.\n'
479 warn('Could not find file where `%s` is defined.\n'
480 'Opening a file named `%s`' % (args, filename))
480 'Opening a file named `%s`' % (args, filename))
481 # Now, make sure we can actually read the source (if it was
481 # Now, make sure we can actually read the source (if it was
482 # in a temp file it's gone by now).
482 # in a temp file it's gone by now).
483 if datafile:
483 if datafile:
484 if lineno is None:
484 if lineno is None:
485 lineno = find_source_lines(data)
485 lineno = find_source_lines(data)
486 if lineno is None:
486 if lineno is None:
487 filename = make_filename(args)
487 filename = make_filename(args)
488 if filename is None:
488 if filename is None:
489 warn('The file where `%s` was defined '
489 warn('The file where `%s` was defined '
490 'cannot be read or found.' % data)
490 'cannot be read or found.' % data)
491 return (None, None, None)
491 return (None, None, None)
492 use_temp = False
492 use_temp = False
493
493
494 if use_temp:
494 if use_temp:
495 filename = shell.mktempfile(data)
495 filename = shell.mktempfile(data)
496 print('IPython will make a temporary file named:',filename)
496 print('IPython will make a temporary file named:',filename)
497
497
498 # use last_call to remember the state of the previous call, but don't
498 # use last_call to remember the state of the previous call, but don't
499 # let it be clobbered by successive '-p' calls.
499 # let it be clobbered by successive '-p' calls.
500 try:
500 try:
501 last_call[0] = shell.displayhook.prompt_count
501 last_call[0] = shell.displayhook.prompt_count
502 if not opts_prev:
502 if not opts_prev:
503 last_call[1] = args
503 last_call[1] = args
504 except:
504 except:
505 pass
505 pass
506
506
507
507
508 return filename, lineno, use_temp
508 return filename, lineno, use_temp
509
509
510 def _edit_macro(self,mname,macro):
510 def _edit_macro(self,mname,macro):
511 """open an editor with the macro data in a file"""
511 """open an editor with the macro data in a file"""
512 filename = self.shell.mktempfile(macro.value)
512 filename = self.shell.mktempfile(macro.value)
513 self.shell.hooks.editor(filename)
513 self.shell.hooks.editor(filename)
514
514
515 # and make a new macro object, to replace the old one
515 # and make a new macro object, to replace the old one
516 with Path(filename).open() as mfile:
516 mvalue = Path(filename).read_text()
517 mvalue = mfile.read()
518 self.shell.user_ns[mname] = Macro(mvalue)
517 self.shell.user_ns[mname] = Macro(mvalue)
519
518
520 @skip_doctest
519 @skip_doctest
521 @line_magic
520 @line_magic
522 def edit(self, parameter_s='',last_call=['','']):
521 def edit(self, parameter_s='',last_call=['','']):
523 """Bring up an editor and execute the resulting code.
522 """Bring up an editor and execute the resulting code.
524
523
525 Usage:
524 Usage:
526 %edit [options] [args]
525 %edit [options] [args]
527
526
528 %edit runs IPython's editor hook. The default version of this hook is
527 %edit runs IPython's editor hook. The default version of this hook is
529 set to call the editor specified by your $EDITOR environment variable.
528 set to call the editor specified by your $EDITOR environment variable.
530 If this isn't found, it will default to vi under Linux/Unix and to
529 If this isn't found, it will default to vi under Linux/Unix and to
531 notepad under Windows. See the end of this docstring for how to change
530 notepad under Windows. See the end of this docstring for how to change
532 the editor hook.
531 the editor hook.
533
532
534 You can also set the value of this editor via the
533 You can also set the value of this editor via the
535 ``TerminalInteractiveShell.editor`` option in your configuration file.
534 ``TerminalInteractiveShell.editor`` option in your configuration file.
536 This is useful if you wish to use a different editor from your typical
535 This is useful if you wish to use a different editor from your typical
537 default with IPython (and for Windows users who typically don't set
536 default with IPython (and for Windows users who typically don't set
538 environment variables).
537 environment variables).
539
538
540 This command allows you to conveniently edit multi-line code right in
539 This command allows you to conveniently edit multi-line code right in
541 your IPython session.
540 your IPython session.
542
541
543 If called without arguments, %edit opens up an empty editor with a
542 If called without arguments, %edit opens up an empty editor with a
544 temporary file and will execute the contents of this file when you
543 temporary file and will execute the contents of this file when you
545 close it (don't forget to save it!).
544 close it (don't forget to save it!).
546
545
547
546
548 Options:
547 Options:
549
548
550 -n <number>: open the editor at a specified line number. By default,
549 -n <number>: open the editor at a specified line number. By default,
551 the IPython editor hook uses the unix syntax 'editor +N filename', but
550 the IPython editor hook uses the unix syntax 'editor +N filename', but
552 you can configure this by providing your own modified hook if your
551 you can configure this by providing your own modified hook if your
553 favorite editor supports line-number specifications with a different
552 favorite editor supports line-number specifications with a different
554 syntax.
553 syntax.
555
554
556 -p: this will call the editor with the same data as the previous time
555 -p: this will call the editor with the same data as the previous time
557 it was used, regardless of how long ago (in your current session) it
556 it was used, regardless of how long ago (in your current session) it
558 was.
557 was.
559
558
560 -r: use 'raw' input. This option only applies to input taken from the
559 -r: use 'raw' input. This option only applies to input taken from the
561 user's history. By default, the 'processed' history is used, so that
560 user's history. By default, the 'processed' history is used, so that
562 magics are loaded in their transformed version to valid Python. If
561 magics are loaded in their transformed version to valid Python. If
563 this option is given, the raw input as typed as the command line is
562 this option is given, the raw input as typed as the command line is
564 used instead. When you exit the editor, it will be executed by
563 used instead. When you exit the editor, it will be executed by
565 IPython's own processor.
564 IPython's own processor.
566
565
567 -x: do not execute the edited code immediately upon exit. This is
566 -x: do not execute the edited code immediately upon exit. This is
568 mainly useful if you are editing programs which need to be called with
567 mainly useful if you are editing programs which need to be called with
569 command line arguments, which you can then do using %run.
568 command line arguments, which you can then do using %run.
570
569
571
570
572 Arguments:
571 Arguments:
573
572
574 If arguments are given, the following possibilities exist:
573 If arguments are given, the following possibilities exist:
575
574
576 - If the argument is a filename, IPython will load that into the
575 - If the argument is a filename, IPython will load that into the
577 editor. It will execute its contents with execfile() when you exit,
576 editor. It will execute its contents with execfile() when you exit,
578 loading any code in the file into your interactive namespace.
577 loading any code in the file into your interactive namespace.
579
578
580 - The arguments are ranges of input history, e.g. "7 ~1/4-6".
579 - The arguments are ranges of input history, e.g. "7 ~1/4-6".
581 The syntax is the same as in the %history magic.
580 The syntax is the same as in the %history magic.
582
581
583 - If the argument is a string variable, its contents are loaded
582 - If the argument is a string variable, its contents are loaded
584 into the editor. You can thus edit any string which contains
583 into the editor. You can thus edit any string which contains
585 python code (including the result of previous edits).
584 python code (including the result of previous edits).
586
585
587 - If the argument is the name of an object (other than a string),
586 - If the argument is the name of an object (other than a string),
588 IPython will try to locate the file where it was defined and open the
587 IPython will try to locate the file where it was defined and open the
589 editor at the point where it is defined. You can use `%edit function`
588 editor at the point where it is defined. You can use `%edit function`
590 to load an editor exactly at the point where 'function' is defined,
589 to load an editor exactly at the point where 'function' is defined,
591 edit it and have the file be executed automatically.
590 edit it and have the file be executed automatically.
592
591
593 - If the object is a macro (see %macro for details), this opens up your
592 - If the object is a macro (see %macro for details), this opens up your
594 specified editor with a temporary file containing the macro's data.
593 specified editor with a temporary file containing the macro's data.
595 Upon exit, the macro is reloaded with the contents of the file.
594 Upon exit, the macro is reloaded with the contents of the file.
596
595
597 Note: opening at an exact line is only supported under Unix, and some
596 Note: opening at an exact line is only supported under Unix, and some
598 editors (like kedit and gedit up to Gnome 2.8) do not understand the
597 editors (like kedit and gedit up to Gnome 2.8) do not understand the
599 '+NUMBER' parameter necessary for this feature. Good editors like
598 '+NUMBER' parameter necessary for this feature. Good editors like
600 (X)Emacs, vi, jed, pico and joe all do.
599 (X)Emacs, vi, jed, pico and joe all do.
601
600
602 After executing your code, %edit will return as output the code you
601 After executing your code, %edit will return as output the code you
603 typed in the editor (except when it was an existing file). This way
602 typed in the editor (except when it was an existing file). This way
604 you can reload the code in further invocations of %edit as a variable,
603 you can reload the code in further invocations of %edit as a variable,
605 via _<NUMBER> or Out[<NUMBER>], where <NUMBER> is the prompt number of
604 via _<NUMBER> or Out[<NUMBER>], where <NUMBER> is the prompt number of
606 the output.
605 the output.
607
606
608 Note that %edit is also available through the alias %ed.
607 Note that %edit is also available through the alias %ed.
609
608
610 This is an example of creating a simple function inside the editor and
609 This is an example of creating a simple function inside the editor and
611 then modifying it. First, start up the editor::
610 then modifying it. First, start up the editor::
612
611
613 In [1]: edit
612 In [1]: edit
614 Editing... done. Executing edited code...
613 Editing... done. Executing edited code...
615 Out[1]: 'def foo():\\n print "foo() was defined in an editing
614 Out[1]: 'def foo():\\n print "foo() was defined in an editing
616 session"\\n'
615 session"\\n'
617
616
618 We can then call the function foo()::
617 We can then call the function foo()::
619
618
620 In [2]: foo()
619 In [2]: foo()
621 foo() was defined in an editing session
620 foo() was defined in an editing session
622
621
623 Now we edit foo. IPython automatically loads the editor with the
622 Now we edit foo. IPython automatically loads the editor with the
624 (temporary) file where foo() was previously defined::
623 (temporary) file where foo() was previously defined::
625
624
626 In [3]: edit foo
625 In [3]: edit foo
627 Editing... done. Executing edited code...
626 Editing... done. Executing edited code...
628
627
629 And if we call foo() again we get the modified version::
628 And if we call foo() again we get the modified version::
630
629
631 In [4]: foo()
630 In [4]: foo()
632 foo() has now been changed!
631 foo() has now been changed!
633
632
634 Here is an example of how to edit a code snippet successive
633 Here is an example of how to edit a code snippet successive
635 times. First we call the editor::
634 times. First we call the editor::
636
635
637 In [5]: edit
636 In [5]: edit
638 Editing... done. Executing edited code...
637 Editing... done. Executing edited code...
639 hello
638 hello
640 Out[5]: "print 'hello'\\n"
639 Out[5]: "print 'hello'\\n"
641
640
642 Now we call it again with the previous output (stored in _)::
641 Now we call it again with the previous output (stored in _)::
643
642
644 In [6]: edit _
643 In [6]: edit _
645 Editing... done. Executing edited code...
644 Editing... done. Executing edited code...
646 hello world
645 hello world
647 Out[6]: "print 'hello world'\\n"
646 Out[6]: "print 'hello world'\\n"
648
647
649 Now we call it with the output #8 (stored in _8, also as Out[8])::
648 Now we call it with the output #8 (stored in _8, also as Out[8])::
650
649
651 In [7]: edit _8
650 In [7]: edit _8
652 Editing... done. Executing edited code...
651 Editing... done. Executing edited code...
653 hello again
652 hello again
654 Out[7]: "print 'hello again'\\n"
653 Out[7]: "print 'hello again'\\n"
655
654
656
655
657 Changing the default editor hook:
656 Changing the default editor hook:
658
657
659 If you wish to write your own editor hook, you can put it in a
658 If you wish to write your own editor hook, you can put it in a
660 configuration file which you load at startup time. The default hook
659 configuration file which you load at startup time. The default hook
661 is defined in the IPython.core.hooks module, and you can use that as a
660 is defined in the IPython.core.hooks module, and you can use that as a
662 starting example for further modifications. That file also has
661 starting example for further modifications. That file also has
663 general instructions on how to set a new hook for use once you've
662 general instructions on how to set a new hook for use once you've
664 defined it."""
663 defined it."""
665 opts,args = self.parse_options(parameter_s,'prxn:')
664 opts,args = self.parse_options(parameter_s,'prxn:')
666
665
667 try:
666 try:
668 filename, lineno, is_temp = self._find_edit_target(self.shell,
667 filename, lineno, is_temp = self._find_edit_target(self.shell,
669 args, opts, last_call)
668 args, opts, last_call)
670 except MacroToEdit as e:
669 except MacroToEdit as e:
671 self._edit_macro(args, e.args[0])
670 self._edit_macro(args, e.args[0])
672 return
671 return
673 except InteractivelyDefined as e:
672 except InteractivelyDefined as e:
674 print("Editing In[%i]" % e.index)
673 print("Editing In[%i]" % e.index)
675 args = str(e.index)
674 args = str(e.index)
676 filename, lineno, is_temp = self._find_edit_target(self.shell,
675 filename, lineno, is_temp = self._find_edit_target(self.shell,
677 args, opts, last_call)
676 args, opts, last_call)
678 if filename is None:
677 if filename is None:
679 # nothing was found, warnings have already been issued,
678 # nothing was found, warnings have already been issued,
680 # just give up.
679 # just give up.
681 return
680 return
682
681
683 if is_temp:
682 if is_temp:
684 self._knowntemps.add(filename)
683 self._knowntemps.add(filename)
685 elif (filename in self._knowntemps):
684 elif (filename in self._knowntemps):
686 is_temp = True
685 is_temp = True
687
686
688
687
689 # do actual editing here
688 # do actual editing here
690 print('Editing...', end=' ')
689 print('Editing...', end=' ')
691 sys.stdout.flush()
690 sys.stdout.flush()
692 filepath = Path(filename)
691 filepath = Path(filename)
693 try:
692 try:
694 # Quote filenames that may have spaces in them when opening
693 # Quote filenames that may have spaces in them when opening
695 # the editor
694 # the editor
696 quoted = filename = str(filepath.absolute())
695 quoted = filename = str(filepath.absolute())
697 if ' ' in quoted:
696 if ' ' in quoted:
698 quoted = "'%s'" % quoted
697 quoted = "'%s'" % quoted
699 self.shell.hooks.editor(quoted,lineno)
698 self.shell.hooks.editor(quoted,lineno)
700 except TryNext:
699 except TryNext:
701 warn('Could not open editor')
700 warn('Could not open editor')
702 return
701 return
703
702
704 # XXX TODO: should this be generalized for all string vars?
703 # XXX TODO: should this be generalized for all string vars?
705 # For now, this is special-cased to blocks created by cpaste
704 # For now, this is special-cased to blocks created by cpaste
706 if args.strip() == 'pasted_block':
705 if args.strip() == 'pasted_block':
707 with filepath.open('r') as f:
706 self.shell.user_ns['pasted_block'] = filepath.read_text()
708 self.shell.user_ns['pasted_block'] = f.read()
709
707
710 if 'x' in opts: # -x prevents actual execution
708 if 'x' in opts: # -x prevents actual execution
711 print()
709 print()
712 else:
710 else:
713 print('done. Executing edited code...')
711 print('done. Executing edited code...')
714 with preserve_keys(self.shell.user_ns, '__file__'):
712 with preserve_keys(self.shell.user_ns, '__file__'):
715 if not is_temp:
713 if not is_temp:
716 self.shell.user_ns['__file__'] = filename
714 self.shell.user_ns['__file__'] = filename
717 if 'r' in opts: # Untranslated IPython code
715 if 'r' in opts: # Untranslated IPython code
718 with filepath.open('r') as f:
716 with filepath.open('r') as f:
719 source = f.read()
717 source = f.read()
720 self.shell.run_cell(source, store_history=False)
718 self.shell.run_cell(source, store_history=False)
721 else:
719 else:
722 self.shell.safe_execfile(filename, self.shell.user_ns,
720 self.shell.safe_execfile(filename, self.shell.user_ns,
723 self.shell.user_ns)
721 self.shell.user_ns)
724
722
725 if is_temp:
723 if is_temp:
726 try:
724 try:
727 with filepath.open() as f:
725 with filepath.open() as f:
728 return f.read()
726 return f.read()
729 except IOError as msg:
727 except IOError as msg:
730 if Path(msg.filename) == filepath:
728 if Path(msg.filename) == filepath:
731 warn('File not found. Did you forget to save?')
729 warn('File not found. Did you forget to save?')
732 return
730 return
733 else:
731 else:
734 self.shell.showtraceback()
732 self.shell.showtraceback()
General Comments 0
You need to be logged in to leave comments. Login now