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