##// END OF EJS Templates
Merge pull request #13211 from paw-lu/save-docstring...
Matthias Bussonnier -
r26945:2620482a merge
parent child
Show More
@@ -1,754 +1,754
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 [history]
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 The history argument 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
205 If no ranges are specified, saves history of the current session up to
206 this point.
206 this point.
207
207
208 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
209 it asks for confirmation before overwriting existing files.
209 it asks for confirmation before overwriting existing files.
210
210
211 If `-r` option is used, the default extension is `.ipy`.
211 If `-r` option is used, the default extension is `.ipy`.
212 """
212 """
213
213
214 opts,args = self.parse_options(parameter_s,'fra',mode='list')
214 opts,args = self.parse_options(parameter_s,'fra',mode='list')
215 if not args:
215 if not args:
216 raise UsageError('Missing filename.')
216 raise UsageError('Missing filename.')
217 raw = 'r' in opts
217 raw = 'r' in opts
218 force = 'f' in opts
218 force = 'f' in opts
219 append = 'a' in opts
219 append = 'a' in opts
220 mode = 'a' if append else 'w'
220 mode = 'a' if append else 'w'
221 ext = '.ipy' if raw else '.py'
221 ext = '.ipy' if raw else '.py'
222 fname, codefrom = args[0], " ".join(args[1:])
222 fname, codefrom = args[0], " ".join(args[1:])
223 if not fname.endswith(('.py','.ipy')):
223 if not fname.endswith(('.py','.ipy')):
224 fname += ext
224 fname += ext
225 file_exists = os.path.isfile(fname)
225 file_exists = os.path.isfile(fname)
226 if file_exists and not force and not append:
226 if file_exists and not force and not append:
227 try:
227 try:
228 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')
229 except StdinNotImplementedError:
229 except StdinNotImplementedError:
230 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))
231 return
231 return
232 if not overwrite :
232 if not overwrite :
233 print('Operation cancelled.')
233 print('Operation cancelled.')
234 return
234 return
235 try:
235 try:
236 cmds = self.shell.find_user_code(codefrom,raw)
236 cmds = self.shell.find_user_code(codefrom,raw)
237 except (TypeError, ValueError) as e:
237 except (TypeError, ValueError) as e:
238 print(e.args[0])
238 print(e.args[0])
239 return
239 return
240 with io.open(fname, mode, encoding="utf-8") as f:
240 with io.open(fname, mode, encoding="utf-8") as f:
241 if not file_exists or not append:
241 if not file_exists or not append:
242 f.write("# coding: utf-8\n")
242 f.write("# coding: utf-8\n")
243 f.write(cmds)
243 f.write(cmds)
244 # make sure we end on a newline
244 # make sure we end on a newline
245 if not cmds.endswith('\n'):
245 if not cmds.endswith('\n'):
246 f.write('\n')
246 f.write('\n')
247 print('The following commands were written to file `%s`:' % fname)
247 print('The following commands were written to file `%s`:' % fname)
248 print(cmds)
248 print(cmds)
249
249
250 @line_magic
250 @line_magic
251 def pastebin(self, parameter_s=''):
251 def pastebin(self, parameter_s=''):
252 """Upload code to dpaste.com, returning the URL.
252 """Upload code to dpaste.com, returning the URL.
253
253
254 Usage:\\
254 Usage:\\
255 %pastebin [-d "Custom description"][-e 24] 1-7
255 %pastebin [-d "Custom description"][-e 24] 1-7
256
256
257 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
258 string or macro.
258 string or macro.
259
259
260 If no arguments are given, uploads the history of this session up to
260 If no arguments are given, uploads the history of this session up to
261 this point.
261 this point.
262
262
263 Options:
263 Options:
264
264
265 -d: Pass a custom description. The default will say
265 -d: Pass a custom description. The default will say
266 "Pasted from IPython".
266 "Pasted from IPython".
267 -e: Pass number of days for the link to be expired.
267 -e: Pass number of days for the link to be expired.
268 The default will be 7 days.
268 The default will be 7 days.
269 """
269 """
270 opts, args = self.parse_options(parameter_s, "d:e:")
270 opts, args = self.parse_options(parameter_s, "d:e:")
271
271
272 try:
272 try:
273 code = self.shell.find_user_code(args)
273 code = self.shell.find_user_code(args)
274 except (ValueError, TypeError) as e:
274 except (ValueError, TypeError) as e:
275 print(e.args[0])
275 print(e.args[0])
276 return
276 return
277
277
278 expiry_days = 7
278 expiry_days = 7
279 try:
279 try:
280 expiry_days = int(opts.get("e", 7))
280 expiry_days = int(opts.get("e", 7))
281 except ValueError as e:
281 except ValueError as e:
282 print(e.args[0].capitalize())
282 print(e.args[0].capitalize())
283 return
283 return
284 if expiry_days < 1 or expiry_days > 365:
284 if expiry_days < 1 or expiry_days > 365:
285 print("Expiry days should be in range of 1 to 365")
285 print("Expiry days should be in range of 1 to 365")
286 return
286 return
287
287
288 post_data = urlencode(
288 post_data = urlencode(
289 {
289 {
290 "title": opts.get("d", "Pasted from IPython"),
290 "title": opts.get("d", "Pasted from IPython"),
291 "syntax": "python",
291 "syntax": "python",
292 "content": code,
292 "content": code,
293 "expiry_days": expiry_days,
293 "expiry_days": expiry_days,
294 }
294 }
295 ).encode("utf-8")
295 ).encode("utf-8")
296
296
297 request = Request(
297 request = Request(
298 "https://dpaste.com/api/v2/",
298 "https://dpaste.com/api/v2/",
299 headers={"User-Agent": "IPython v{}".format(version)},
299 headers={"User-Agent": "IPython v{}".format(version)},
300 )
300 )
301 response = urlopen(request, post_data)
301 response = urlopen(request, post_data)
302 return response.headers.get('Location')
302 return response.headers.get('Location')
303
303
304 @line_magic
304 @line_magic
305 def loadpy(self, arg_s):
305 def loadpy(self, arg_s):
306 """Alias of `%load`
306 """Alias of `%load`
307
307
308 `%loadpy` has gained some flexibility and dropped the requirement of a `.py`
308 `%loadpy` has gained some flexibility and dropped the requirement of a `.py`
309 extension. So it has been renamed simply into %load. You can look at
309 extension. So it has been renamed simply into %load. You can look at
310 `%load`'s docstring for more info.
310 `%load`'s docstring for more info.
311 """
311 """
312 self.load(arg_s)
312 self.load(arg_s)
313
313
314 @line_magic
314 @line_magic
315 def load(self, arg_s):
315 def load(self, arg_s):
316 """Load code into the current frontend.
316 """Load code into the current frontend.
317
317
318 Usage:\\
318 Usage:\\
319 %load [options] source
319 %load [options] source
320
320
321 where source can be a filename, URL, input history range, macro, or
321 where source can be a filename, URL, input history range, macro, or
322 element in the user namespace
322 element in the user namespace
323
323
324 If no arguments are given, loads the history of this session up to this
324 If no arguments are given, loads the history of this session up to this
325 point.
325 point.
326
326
327 Options:
327 Options:
328
328
329 -r <lines>: Specify lines or ranges of lines to load from the source.
329 -r <lines>: Specify lines or ranges of lines to load from the source.
330 Ranges could be specified as x-y (x..y) or in python-style x:y
330 Ranges could be specified as x-y (x..y) or in python-style x:y
331 (x..(y-1)). Both limits x and y can be left blank (meaning the
331 (x..(y-1)). Both limits x and y can be left blank (meaning the
332 beginning and end of the file, respectively).
332 beginning and end of the file, respectively).
333
333
334 -s <symbols>: Specify function or classes to load from python source.
334 -s <symbols>: Specify function or classes to load from python source.
335
335
336 -y : Don't ask confirmation for loading source above 200 000 characters.
336 -y : Don't ask confirmation for loading source above 200 000 characters.
337
337
338 -n : Include the user's namespace when searching for source code.
338 -n : Include the user's namespace when searching for source code.
339
339
340 This magic command can either take a local filename, a URL, an history
340 This magic command can either take a local filename, a URL, an history
341 range (see %history) or a macro as argument, it will prompt for
341 range (see %history) or a macro as argument, it will prompt for
342 confirmation before loading source with more than 200 000 characters, unless
342 confirmation before loading source with more than 200 000 characters, unless
343 -y flag is passed or if the frontend does not support raw_input::
343 -y flag is passed or if the frontend does not support raw_input::
344
344
345 %load
345 %load
346 %load myscript.py
346 %load myscript.py
347 %load 7-27
347 %load 7-27
348 %load myMacro
348 %load myMacro
349 %load http://www.example.com/myscript.py
349 %load http://www.example.com/myscript.py
350 %load -r 5-10 myscript.py
350 %load -r 5-10 myscript.py
351 %load -r 10-20,30,40: foo.py
351 %load -r 10-20,30,40: foo.py
352 %load -s MyClass,wonder_function myscript.py
352 %load -s MyClass,wonder_function myscript.py
353 %load -n MyClass
353 %load -n MyClass
354 %load -n my_module.wonder_function
354 %load -n my_module.wonder_function
355 """
355 """
356 opts,args = self.parse_options(arg_s,'yns:r:')
356 opts,args = self.parse_options(arg_s,'yns:r:')
357 search_ns = 'n' in opts
357 search_ns = 'n' in opts
358 contents = self.shell.find_user_code(args, search_ns=search_ns)
358 contents = self.shell.find_user_code(args, search_ns=search_ns)
359
359
360 if 's' in opts:
360 if 's' in opts:
361 try:
361 try:
362 blocks, not_found = extract_symbols(contents, opts['s'])
362 blocks, not_found = extract_symbols(contents, opts['s'])
363 except SyntaxError:
363 except SyntaxError:
364 # non python code
364 # non python code
365 error("Unable to parse the input as valid Python code")
365 error("Unable to parse the input as valid Python code")
366 return
366 return
367
367
368 if len(not_found) == 1:
368 if len(not_found) == 1:
369 warn('The symbol `%s` was not found' % not_found[0])
369 warn('The symbol `%s` was not found' % not_found[0])
370 elif len(not_found) > 1:
370 elif len(not_found) > 1:
371 warn('The symbols %s were not found' % get_text_list(not_found,
371 warn('The symbols %s were not found' % get_text_list(not_found,
372 wrap_item_with='`')
372 wrap_item_with='`')
373 )
373 )
374
374
375 contents = '\n'.join(blocks)
375 contents = '\n'.join(blocks)
376
376
377 if 'r' in opts:
377 if 'r' in opts:
378 ranges = opts['r'].replace(',', ' ')
378 ranges = opts['r'].replace(',', ' ')
379 lines = contents.split('\n')
379 lines = contents.split('\n')
380 slices = extract_code_ranges(ranges)
380 slices = extract_code_ranges(ranges)
381 contents = [lines[slice(*slc)] for slc in slices]
381 contents = [lines[slice(*slc)] for slc in slices]
382 contents = '\n'.join(strip_initial_indent(chain.from_iterable(contents)))
382 contents = '\n'.join(strip_initial_indent(chain.from_iterable(contents)))
383
383
384 l = len(contents)
384 l = len(contents)
385
385
386 # 200 000 is ~ 2500 full 80 character lines
386 # 200 000 is ~ 2500 full 80 character lines
387 # so in average, more than 5000 lines
387 # so in average, more than 5000 lines
388 if l > 200000 and 'y' not in opts:
388 if l > 200000 and 'y' not in opts:
389 try:
389 try:
390 ans = self.shell.ask_yes_no(("The text you're trying to load seems pretty big"\
390 ans = self.shell.ask_yes_no(("The text you're trying to load seems pretty big"\
391 " (%d characters). Continue (y/[N]) ?" % l), default='n' )
391 " (%d characters). Continue (y/[N]) ?" % l), default='n' )
392 except StdinNotImplementedError:
392 except StdinNotImplementedError:
393 #assume yes if raw input not implemented
393 #assume yes if raw input not implemented
394 ans = True
394 ans = True
395
395
396 if ans is False :
396 if ans is False :
397 print('Operation cancelled.')
397 print('Operation cancelled.')
398 return
398 return
399
399
400 contents = "# %load {}\n".format(arg_s) + contents
400 contents = "# %load {}\n".format(arg_s) + contents
401
401
402 self.shell.set_next_input(contents, replace=True)
402 self.shell.set_next_input(contents, replace=True)
403
403
404 @staticmethod
404 @staticmethod
405 def _find_edit_target(shell, args, opts, last_call):
405 def _find_edit_target(shell, args, opts, last_call):
406 """Utility method used by magic_edit to find what to edit."""
406 """Utility method used by magic_edit to find what to edit."""
407
407
408 def make_filename(arg):
408 def make_filename(arg):
409 "Make a filename from the given args"
409 "Make a filename from the given args"
410 try:
410 try:
411 filename = get_py_filename(arg)
411 filename = get_py_filename(arg)
412 except IOError:
412 except IOError:
413 # If it ends with .py but doesn't already exist, assume we want
413 # If it ends with .py but doesn't already exist, assume we want
414 # a new file.
414 # a new file.
415 if arg.endswith('.py'):
415 if arg.endswith('.py'):
416 filename = arg
416 filename = arg
417 else:
417 else:
418 filename = None
418 filename = None
419 return filename
419 return filename
420
420
421 # Set a few locals from the options for convenience:
421 # Set a few locals from the options for convenience:
422 opts_prev = 'p' in opts
422 opts_prev = 'p' in opts
423 opts_raw = 'r' in opts
423 opts_raw = 'r' in opts
424
424
425 # custom exceptions
425 # custom exceptions
426 class DataIsObject(Exception): pass
426 class DataIsObject(Exception): pass
427
427
428 # Default line number value
428 # Default line number value
429 lineno = opts.get('n',None)
429 lineno = opts.get('n',None)
430
430
431 if opts_prev:
431 if opts_prev:
432 args = '_%s' % last_call[0]
432 args = '_%s' % last_call[0]
433 if args not in shell.user_ns:
433 if args not in shell.user_ns:
434 args = last_call[1]
434 args = last_call[1]
435
435
436 # by default this is done with temp files, except when the given
436 # by default this is done with temp files, except when the given
437 # arg is a filename
437 # arg is a filename
438 use_temp = True
438 use_temp = True
439
439
440 data = ''
440 data = ''
441
441
442 # First, see if the arguments should be a filename.
442 # First, see if the arguments should be a filename.
443 filename = make_filename(args)
443 filename = make_filename(args)
444 if filename:
444 if filename:
445 use_temp = False
445 use_temp = False
446 elif args:
446 elif args:
447 # Mode where user specifies ranges of lines, like in %macro.
447 # Mode where user specifies ranges of lines, like in %macro.
448 data = shell.extract_input_lines(args, opts_raw)
448 data = shell.extract_input_lines(args, opts_raw)
449 if not data:
449 if not data:
450 try:
450 try:
451 # Load the parameter given as a variable. If not a string,
451 # Load the parameter given as a variable. If not a string,
452 # process it as an object instead (below)
452 # process it as an object instead (below)
453
453
454 #print '*** args',args,'type',type(args) # dbg
454 #print '*** args',args,'type',type(args) # dbg
455 data = eval(args, shell.user_ns)
455 data = eval(args, shell.user_ns)
456 if not isinstance(data, str):
456 if not isinstance(data, str):
457 raise DataIsObject
457 raise DataIsObject
458
458
459 except (NameError,SyntaxError):
459 except (NameError,SyntaxError):
460 # given argument is not a variable, try as a filename
460 # given argument is not a variable, try as a filename
461 filename = make_filename(args)
461 filename = make_filename(args)
462 if filename is None:
462 if filename is None:
463 warn("Argument given (%s) can't be found as a variable "
463 warn("Argument given (%s) can't be found as a variable "
464 "or as a filename." % args)
464 "or as a filename." % args)
465 return (None, None, None)
465 return (None, None, None)
466 use_temp = False
466 use_temp = False
467
467
468 except DataIsObject as e:
468 except DataIsObject as e:
469 # macros have a special edit function
469 # macros have a special edit function
470 if isinstance(data, Macro):
470 if isinstance(data, Macro):
471 raise MacroToEdit(data) from e
471 raise MacroToEdit(data) from e
472
472
473 # For objects, try to edit the file where they are defined
473 # For objects, try to edit the file where they are defined
474 filename = find_file(data)
474 filename = find_file(data)
475 if filename:
475 if filename:
476 if 'fakemodule' in filename.lower() and \
476 if 'fakemodule' in filename.lower() and \
477 inspect.isclass(data):
477 inspect.isclass(data):
478 # class created by %edit? Try to find source
478 # class created by %edit? Try to find source
479 # by looking for method definitions instead, the
479 # by looking for method definitions instead, the
480 # __module__ in those classes is FakeModule.
480 # __module__ in those classes is FakeModule.
481 attrs = [getattr(data, aname) for aname in dir(data)]
481 attrs = [getattr(data, aname) for aname in dir(data)]
482 for attr in attrs:
482 for attr in attrs:
483 if not inspect.ismethod(attr):
483 if not inspect.ismethod(attr):
484 continue
484 continue
485 filename = find_file(attr)
485 filename = find_file(attr)
486 if filename and \
486 if filename and \
487 'fakemodule' not in filename.lower():
487 'fakemodule' not in filename.lower():
488 # change the attribute to be the edit
488 # change the attribute to be the edit
489 # target instead
489 # target instead
490 data = attr
490 data = attr
491 break
491 break
492
492
493 m = ipython_input_pat.match(os.path.basename(filename))
493 m = ipython_input_pat.match(os.path.basename(filename))
494 if m:
494 if m:
495 raise InteractivelyDefined(int(m.groups()[0])) from e
495 raise InteractivelyDefined(int(m.groups()[0])) from e
496
496
497 datafile = 1
497 datafile = 1
498 if filename is None:
498 if filename is None:
499 filename = make_filename(args)
499 filename = make_filename(args)
500 datafile = 1
500 datafile = 1
501 if filename is not None:
501 if filename is not None:
502 # only warn about this if we get a real name
502 # only warn about this if we get a real name
503 warn('Could not find file where `%s` is defined.\n'
503 warn('Could not find file where `%s` is defined.\n'
504 'Opening a file named `%s`' % (args, filename))
504 'Opening a file named `%s`' % (args, filename))
505 # Now, make sure we can actually read the source (if it was
505 # Now, make sure we can actually read the source (if it was
506 # in a temp file it's gone by now).
506 # in a temp file it's gone by now).
507 if datafile:
507 if datafile:
508 if lineno is None:
508 if lineno is None:
509 lineno = find_source_lines(data)
509 lineno = find_source_lines(data)
510 if lineno is None:
510 if lineno is None:
511 filename = make_filename(args)
511 filename = make_filename(args)
512 if filename is None:
512 if filename is None:
513 warn('The file where `%s` was defined '
513 warn('The file where `%s` was defined '
514 'cannot be read or found.' % data)
514 'cannot be read or found.' % data)
515 return (None, None, None)
515 return (None, None, None)
516 use_temp = False
516 use_temp = False
517
517
518 if use_temp:
518 if use_temp:
519 filename = shell.mktempfile(data)
519 filename = shell.mktempfile(data)
520 print('IPython will make a temporary file named:',filename)
520 print('IPython will make a temporary file named:',filename)
521
521
522 # use last_call to remember the state of the previous call, but don't
522 # use last_call to remember the state of the previous call, but don't
523 # let it be clobbered by successive '-p' calls.
523 # let it be clobbered by successive '-p' calls.
524 try:
524 try:
525 last_call[0] = shell.displayhook.prompt_count
525 last_call[0] = shell.displayhook.prompt_count
526 if not opts_prev:
526 if not opts_prev:
527 last_call[1] = args
527 last_call[1] = args
528 except:
528 except:
529 pass
529 pass
530
530
531
531
532 return filename, lineno, use_temp
532 return filename, lineno, use_temp
533
533
534 def _edit_macro(self,mname,macro):
534 def _edit_macro(self,mname,macro):
535 """open an editor with the macro data in a file"""
535 """open an editor with the macro data in a file"""
536 filename = self.shell.mktempfile(macro.value)
536 filename = self.shell.mktempfile(macro.value)
537 self.shell.hooks.editor(filename)
537 self.shell.hooks.editor(filename)
538
538
539 # and make a new macro object, to replace the old one
539 # and make a new macro object, to replace the old one
540 mvalue = Path(filename).read_text()
540 mvalue = Path(filename).read_text()
541 self.shell.user_ns[mname] = Macro(mvalue)
541 self.shell.user_ns[mname] = Macro(mvalue)
542
542
543 @skip_doctest
543 @skip_doctest
544 @line_magic
544 @line_magic
545 def edit(self, parameter_s='',last_call=['','']):
545 def edit(self, parameter_s='',last_call=['','']):
546 """Bring up an editor and execute the resulting code.
546 """Bring up an editor and execute the resulting code.
547
547
548 Usage:
548 Usage:
549 %edit [options] [args]
549 %edit [options] [args]
550
550
551 %edit runs IPython's editor hook. The default version of this hook is
551 %edit runs IPython's editor hook. The default version of this hook is
552 set to call the editor specified by your $EDITOR environment variable.
552 set to call the editor specified by your $EDITOR environment variable.
553 If this isn't found, it will default to vi under Linux/Unix and to
553 If this isn't found, it will default to vi under Linux/Unix and to
554 notepad under Windows. See the end of this docstring for how to change
554 notepad under Windows. See the end of this docstring for how to change
555 the editor hook.
555 the editor hook.
556
556
557 You can also set the value of this editor via the
557 You can also set the value of this editor via the
558 ``TerminalInteractiveShell.editor`` option in your configuration file.
558 ``TerminalInteractiveShell.editor`` option in your configuration file.
559 This is useful if you wish to use a different editor from your typical
559 This is useful if you wish to use a different editor from your typical
560 default with IPython (and for Windows users who typically don't set
560 default with IPython (and for Windows users who typically don't set
561 environment variables).
561 environment variables).
562
562
563 This command allows you to conveniently edit multi-line code right in
563 This command allows you to conveniently edit multi-line code right in
564 your IPython session.
564 your IPython session.
565
565
566 If called without arguments, %edit opens up an empty editor with a
566 If called without arguments, %edit opens up an empty editor with a
567 temporary file and will execute the contents of this file when you
567 temporary file and will execute the contents of this file when you
568 close it (don't forget to save it!).
568 close it (don't forget to save it!).
569
569
570
570
571 Options:
571 Options:
572
572
573 -n <number>: open the editor at a specified line number. By default,
573 -n <number>: open the editor at a specified line number. By default,
574 the IPython editor hook uses the unix syntax 'editor +N filename', but
574 the IPython editor hook uses the unix syntax 'editor +N filename', but
575 you can configure this by providing your own modified hook if your
575 you can configure this by providing your own modified hook if your
576 favorite editor supports line-number specifications with a different
576 favorite editor supports line-number specifications with a different
577 syntax.
577 syntax.
578
578
579 -p: this will call the editor with the same data as the previous time
579 -p: this will call the editor with the same data as the previous time
580 it was used, regardless of how long ago (in your current session) it
580 it was used, regardless of how long ago (in your current session) it
581 was.
581 was.
582
582
583 -r: use 'raw' input. This option only applies to input taken from the
583 -r: use 'raw' input. This option only applies to input taken from the
584 user's history. By default, the 'processed' history is used, so that
584 user's history. By default, the 'processed' history is used, so that
585 magics are loaded in their transformed version to valid Python. If
585 magics are loaded in their transformed version to valid Python. If
586 this option is given, the raw input as typed as the command line is
586 this option is given, the raw input as typed as the command line is
587 used instead. When you exit the editor, it will be executed by
587 used instead. When you exit the editor, it will be executed by
588 IPython's own processor.
588 IPython's own processor.
589
589
590 -x: do not execute the edited code immediately upon exit. This is
590 -x: do not execute the edited code immediately upon exit. This is
591 mainly useful if you are editing programs which need to be called with
591 mainly useful if you are editing programs which need to be called with
592 command line arguments, which you can then do using %run.
592 command line arguments, which you can then do using %run.
593
593
594
594
595 Arguments:
595 Arguments:
596
596
597 If arguments are given, the following possibilities exist:
597 If arguments are given, the following possibilities exist:
598
598
599 - If the argument is a filename, IPython will load that into the
599 - If the argument is a filename, IPython will load that into the
600 editor. It will execute its contents with execfile() when you exit,
600 editor. It will execute its contents with execfile() when you exit,
601 loading any code in the file into your interactive namespace.
601 loading any code in the file into your interactive namespace.
602
602
603 - The arguments are ranges of input history, e.g. "7 ~1/4-6".
603 - The arguments are ranges of input history, e.g. "7 ~1/4-6".
604 The syntax is the same as in the %history magic.
604 The syntax is the same as in the %history magic.
605
605
606 - If the argument is a string variable, its contents are loaded
606 - If the argument is a string variable, its contents are loaded
607 into the editor. You can thus edit any string which contains
607 into the editor. You can thus edit any string which contains
608 python code (including the result of previous edits).
608 python code (including the result of previous edits).
609
609
610 - If the argument is the name of an object (other than a string),
610 - If the argument is the name of an object (other than a string),
611 IPython will try to locate the file where it was defined and open the
611 IPython will try to locate the file where it was defined and open the
612 editor at the point where it is defined. You can use `%edit function`
612 editor at the point where it is defined. You can use `%edit function`
613 to load an editor exactly at the point where 'function' is defined,
613 to load an editor exactly at the point where 'function' is defined,
614 edit it and have the file be executed automatically.
614 edit it and have the file be executed automatically.
615
615
616 - If the object is a macro (see %macro for details), this opens up your
616 - If the object is a macro (see %macro for details), this opens up your
617 specified editor with a temporary file containing the macro's data.
617 specified editor with a temporary file containing the macro's data.
618 Upon exit, the macro is reloaded with the contents of the file.
618 Upon exit, the macro is reloaded with the contents of the file.
619
619
620 Note: opening at an exact line is only supported under Unix, and some