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