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