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