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