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