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