##// END OF EJS Templates
fix `%magic` output...
MinRK -
Show More
@@ -1,648 +1,648
1 1 """Implementation of basic 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 from __future__ import print_function
15 15
16 16 # Stdlib
17 17 import io
18 18 import json
19 19 import sys
20 20 from pprint import pformat
21 21
22 22 # Our own packages
23 23 from IPython.core import magic_arguments
24 24 from IPython.core.error import UsageError
25 25 from IPython.core.magic import Magics, magics_class, line_magic, magic_escapes
26 26 from IPython.utils.text import format_screen, dedent, indent
27 27 from IPython.core import magic_arguments, page
28 28 from IPython.testing.skipdoctest import skip_doctest
29 29 from IPython.utils.ipstruct import Struct
30 30 from IPython.utils.path import unquote_filename
31 31 from IPython.utils.warn import warn, error
32 32
33 33 #-----------------------------------------------------------------------------
34 34 # Magics class implementation
35 35 #-----------------------------------------------------------------------------
36 36
37 37 class MagicsDisplay(object):
38 38 def __init__(self, magics_manager):
39 39 self.magics_manager = magics_manager
40 40
41 41 def _lsmagic(self):
42 42 """The main implementation of the %lsmagic"""
43 43 mesc = magic_escapes['line']
44 44 cesc = magic_escapes['cell']
45 45 mman = self.magics_manager
46 46 magics = mman.lsmagic()
47 47 out = ['Available line magics:',
48 48 mesc + (' '+mesc).join(sorted(magics['line'])),
49 49 '',
50 50 'Available cell magics:',
51 51 cesc + (' '+cesc).join(sorted(magics['cell'])),
52 52 '',
53 53 mman.auto_status()]
54 54 return '\n'.join(out)
55 55
56 56 def _repr_pretty_(self, p, cycle):
57 57 p.text(self._lsmagic())
58 58
59 59 def __str__(self):
60 60 return self._lsmagic()
61 61
62 62 def _jsonable(self):
63 63 """turn magics dict into jsonable dict of the same structure
64 64
65 65 replaces object instances with their class names as strings
66 66 """
67 67 magic_dict = {}
68 68 mman = self.magics_manager
69 69 magics = mman.lsmagic()
70 70 for key, subdict in magics.items():
71 71 d = {}
72 72 magic_dict[key] = d
73 73 for name, obj in subdict.items():
74 74 try:
75 75 classname = obj.im_class.__name__
76 76 except AttributeError:
77 77 classname = 'Other'
78 78
79 79 d[name] = classname
80 80 return magic_dict
81 81
82 82 def _repr_json_(self):
83 83 return json.dumps(self._jsonable())
84 84
85 85
86 86 @magics_class
87 87 class BasicMagics(Magics):
88 88 """Magics that provide central IPython functionality.
89 89
90 90 These are various magics that don't fit into specific categories but that
91 91 are all part of the base 'IPython experience'."""
92 92
93 93 @magic_arguments.magic_arguments()
94 94 @magic_arguments.argument(
95 95 '-l', '--line', action='store_true',
96 96 help="""Create a line magic alias."""
97 97 )
98 98 @magic_arguments.argument(
99 99 '-c', '--cell', action='store_true',
100 100 help="""Create a cell magic alias."""
101 101 )
102 102 @magic_arguments.argument(
103 103 'name',
104 104 help="""Name of the magic to be created."""
105 105 )
106 106 @magic_arguments.argument(
107 107 'target',
108 108 help="""Name of the existing line or cell magic."""
109 109 )
110 110 @line_magic
111 111 def alias_magic(self, line=''):
112 112 """Create an alias for an existing line or cell magic.
113 113
114 114 Examples
115 115 --------
116 116 ::
117 117 In [1]: %alias_magic t timeit
118 118 Created `%t` as an alias for `%timeit`.
119 119 Created `%%t` as an alias for `%%timeit`.
120 120
121 121 In [2]: %t -n1 pass
122 122 1 loops, best of 3: 954 ns per loop
123 123
124 124 In [3]: %%t -n1
125 125 ...: pass
126 126 ...:
127 127 1 loops, best of 3: 954 ns per loop
128 128
129 129 In [4]: %alias_magic --cell whereami pwd
130 130 UsageError: Cell magic function `%%pwd` not found.
131 131 In [5]: %alias_magic --line whereami pwd
132 132 Created `%whereami` as an alias for `%pwd`.
133 133
134 134 In [6]: %whereami
135 135 Out[6]: u'/home/testuser'
136 136 """
137 137 args = magic_arguments.parse_argstring(self.alias_magic, line)
138 138 shell = self.shell
139 139 mman = self.shell.magics_manager
140 140 escs = ''.join(magic_escapes.values())
141 141
142 142 target = args.target.lstrip(escs)
143 143 name = args.name.lstrip(escs)
144 144
145 145 # Find the requested magics.
146 146 m_line = shell.find_magic(target, 'line')
147 147 m_cell = shell.find_magic(target, 'cell')
148 148 if args.line and m_line is None:
149 149 raise UsageError('Line magic function `%s%s` not found.' %
150 150 (magic_escapes['line'], target))
151 151 if args.cell and m_cell is None:
152 152 raise UsageError('Cell magic function `%s%s` not found.' %
153 153 (magic_escapes['cell'], target))
154 154
155 155 # If --line and --cell are not specified, default to the ones
156 156 # that are available.
157 157 if not args.line and not args.cell:
158 158 if not m_line and not m_cell:
159 159 raise UsageError(
160 160 'No line or cell magic with name `%s` found.' % target
161 161 )
162 162 args.line = bool(m_line)
163 163 args.cell = bool(m_cell)
164 164
165 165 if args.line:
166 166 mman.register_alias(name, target, 'line')
167 167 print('Created `%s%s` as an alias for `%s%s`.' % (
168 168 magic_escapes['line'], name,
169 169 magic_escapes['line'], target))
170 170
171 171 if args.cell:
172 172 mman.register_alias(name, target, 'cell')
173 173 print('Created `%s%s` as an alias for `%s%s`.' % (
174 174 magic_escapes['cell'], name,
175 175 magic_escapes['cell'], target))
176 176
177 177 @line_magic
178 178 def lsmagic(self, parameter_s=''):
179 179 """List currently available magic functions."""
180 180 return MagicsDisplay(self.shell.magics_manager)
181 181
182 182 def _magic_docs(self, brief=False, rest=False):
183 183 """Return docstrings from magic functions."""
184 184 mman = self.shell.magics_manager
185 185 docs = mman.lsmagic_docs(brief, missing='No documentation')
186 186
187 187 if rest:
188 188 format_string = '**%s%s**::\n\n%s\n\n'
189 189 else:
190 190 format_string = '%s%s:\n%s\n'
191 191
192 192 return ''.join(
193 193 [format_string % (magic_escapes['line'], fname,
194 194 indent(dedent(fndoc)))
195 195 for fname, fndoc in sorted(docs['line'].items())]
196 196 +
197 197 [format_string % (magic_escapes['cell'], fname,
198 198 indent(dedent(fndoc)))
199 199 for fname, fndoc in sorted(docs['cell'].items())]
200 200 )
201 201
202 202 @line_magic
203 203 def magic(self, parameter_s=''):
204 204 """Print information about the magic function system.
205 205
206 206 Supported formats: -latex, -brief, -rest
207 207 """
208 208
209 209 mode = ''
210 210 try:
211 211 mode = parameter_s.split()[0][1:]
212 212 if mode == 'rest':
213 213 rest_docs = []
214 214 except IndexError:
215 215 pass
216 216
217 217 brief = (mode == 'brief')
218 218 rest = (mode == 'rest')
219 219 magic_docs = self._magic_docs(brief, rest)
220 220
221 221 if mode == 'latex':
222 222 print(self.format_latex(magic_docs))
223 223 return
224 224 else:
225 225 magic_docs = format_screen(magic_docs)
226 226
227 227 out = ["""
228 228 IPython's 'magic' functions
229 229 ===========================
230 230
231 231 The magic function system provides a series of functions which allow you to
232 232 control the behavior of IPython itself, plus a lot of system-type
233 233 features. There are two kinds of magics, line-oriented and cell-oriented.
234 234
235 235 Line magics are prefixed with the % character and work much like OS
236 236 command-line calls: they get as an argument the rest of the line, where
237 237 arguments are passed without parentheses or quotes. For example, this will
238 238 time the given statement::
239 239
240 240 %timeit range(1000)
241 241
242 242 Cell magics are prefixed with a double %%, and they are functions that get as
243 243 an argument not only the rest of the line, but also the lines below it in a
244 244 separate argument. These magics are called with two arguments: the rest of the
245 245 call line and the body of the cell, consisting of the lines below the first.
246 246 For example::
247 247
248 248 %%timeit x = numpy.random.randn((100, 100))
249 249 numpy.linalg.svd(x)
250 250
251 251 will time the execution of the numpy svd routine, running the assignment of x
252 252 as part of the setup phase, which is not timed.
253 253
254 254 In a line-oriented client (the terminal or Qt console IPython), starting a new
255 255 input with %% will automatically enter cell mode, and IPython will continue
256 256 reading input until a blank line is given. In the notebook, simply type the
257 257 whole cell as one entity, but keep in mind that the %% escape can only be at
258 258 the very start of the cell.
259 259
260 260 NOTE: If you have 'automagic' enabled (via the command line option or with the
261 261 %automagic function), you don't need to type in the % explicitly for line
262 262 magics; cell magics always require an explicit '%%' escape. By default,
263 263 IPython ships with automagic on, so you should only rarely need the % escape.
264 264
265 265 Example: typing '%cd mydir' (without the quotes) changes you working directory
266 266 to 'mydir', if it exists.
267 267
268 268 For a list of the available magic functions, use %lsmagic. For a description
269 269 of any of them, type %magic_name?, e.g. '%cd?'.
270 270
271 271 Currently the magic system has the following functions:""",
272 272 magic_docs,
273 273 "Summary of magic functions (from %slsmagic):" % magic_escapes['line'],
274 self._lsmagic(),
274 str(self.lsmagic()),
275 275 ]
276 276 page.page('\n'.join(out))
277 277
278 278
279 279 @line_magic
280 280 def page(self, parameter_s=''):
281 281 """Pretty print the object and display it through a pager.
282 282
283 283 %page [options] OBJECT
284 284
285 285 If no object is given, use _ (last output).
286 286
287 287 Options:
288 288
289 289 -r: page str(object), don't pretty-print it."""
290 290
291 291 # After a function contributed by Olivier Aubert, slightly modified.
292 292
293 293 # Process options/args
294 294 opts, args = self.parse_options(parameter_s, 'r')
295 295 raw = 'r' in opts
296 296
297 297 oname = args and args or '_'
298 298 info = self.shell._ofind(oname)
299 299 if info['found']:
300 300 txt = (raw and str or pformat)( info['obj'] )
301 301 page.page(txt)
302 302 else:
303 303 print('Object `%s` not found' % oname)
304 304
305 305 @line_magic
306 306 def profile(self, parameter_s=''):
307 307 """Print your currently active IPython profile."""
308 308 from IPython.core.application import BaseIPythonApplication
309 309 if BaseIPythonApplication.initialized():
310 310 print(BaseIPythonApplication.instance().profile)
311 311 else:
312 312 error("profile is an application-level value, but you don't appear to be in an IPython application")
313 313
314 314 @line_magic
315 315 def pprint(self, parameter_s=''):
316 316 """Toggle pretty printing on/off."""
317 317 ptformatter = self.shell.display_formatter.formatters['text/plain']
318 318 ptformatter.pprint = bool(1 - ptformatter.pprint)
319 319 print('Pretty printing has been turned',
320 320 ['OFF','ON'][ptformatter.pprint])
321 321
322 322 @line_magic
323 323 def colors(self, parameter_s=''):
324 324 """Switch color scheme for prompts, info system and exception handlers.
325 325
326 326 Currently implemented schemes: NoColor, Linux, LightBG.
327 327
328 328 Color scheme names are not case-sensitive.
329 329
330 330 Examples
331 331 --------
332 332 To get a plain black and white terminal::
333 333
334 334 %colors nocolor
335 335 """
336 336 def color_switch_err(name):
337 337 warn('Error changing %s color schemes.\n%s' %
338 338 (name, sys.exc_info()[1]))
339 339
340 340
341 341 new_scheme = parameter_s.strip()
342 342 if not new_scheme:
343 343 raise UsageError(
344 344 "%colors: you must specify a color scheme. See '%colors?'")
345 345 return
346 346 # local shortcut
347 347 shell = self.shell
348 348
349 349 import IPython.utils.rlineimpl as readline
350 350
351 351 if not shell.colors_force and \
352 352 not readline.have_readline and \
353 353 (sys.platform == "win32" or sys.platform == "cli"):
354 354 msg = """\
355 355 Proper color support under MS Windows requires the pyreadline library.
356 356 You can find it at:
357 357 http://ipython.org/pyreadline.html
358 358 Gary's readline needs the ctypes module, from:
359 359 http://starship.python.net/crew/theller/ctypes
360 360 (Note that ctypes is already part of Python versions 2.5 and newer).
361 361
362 362 Defaulting color scheme to 'NoColor'"""
363 363 new_scheme = 'NoColor'
364 364 warn(msg)
365 365
366 366 # readline option is 0
367 367 if not shell.colors_force and not shell.has_readline:
368 368 new_scheme = 'NoColor'
369 369
370 370 # Set prompt colors
371 371 try:
372 372 shell.prompt_manager.color_scheme = new_scheme
373 373 except:
374 374 color_switch_err('prompt')
375 375 else:
376 376 shell.colors = \
377 377 shell.prompt_manager.color_scheme_table.active_scheme_name
378 378 # Set exception colors
379 379 try:
380 380 shell.InteractiveTB.set_colors(scheme = new_scheme)
381 381 shell.SyntaxTB.set_colors(scheme = new_scheme)
382 382 except:
383 383 color_switch_err('exception')
384 384
385 385 # Set info (for 'object?') colors
386 386 if shell.color_info:
387 387 try:
388 388 shell.inspector.set_active_scheme(new_scheme)
389 389 except:
390 390 color_switch_err('object inspector')
391 391 else:
392 392 shell.inspector.set_active_scheme('NoColor')
393 393
394 394 @line_magic
395 395 def xmode(self, parameter_s=''):
396 396 """Switch modes for the exception handlers.
397 397
398 398 Valid modes: Plain, Context and Verbose.
399 399
400 400 If called without arguments, acts as a toggle."""
401 401
402 402 def xmode_switch_err(name):
403 403 warn('Error changing %s exception modes.\n%s' %
404 404 (name,sys.exc_info()[1]))
405 405
406 406 shell = self.shell
407 407 new_mode = parameter_s.strip().capitalize()
408 408 try:
409 409 shell.InteractiveTB.set_mode(mode=new_mode)
410 410 print('Exception reporting mode:',shell.InteractiveTB.mode)
411 411 except:
412 412 xmode_switch_err('user')
413 413
414 414 @line_magic
415 415 def quickref(self,arg):
416 416 """ Show a quick reference sheet """
417 417 from IPython.core.usage import quick_reference
418 418 qr = quick_reference + self._magic_docs(brief=True)
419 419 page.page(qr)
420 420
421 421 @line_magic
422 422 def doctest_mode(self, parameter_s=''):
423 423 """Toggle doctest mode on and off.
424 424
425 425 This mode is intended to make IPython behave as much as possible like a
426 426 plain Python shell, from the perspective of how its prompts, exceptions
427 427 and output look. This makes it easy to copy and paste parts of a
428 428 session into doctests. It does so by:
429 429
430 430 - Changing the prompts to the classic ``>>>`` ones.
431 431 - Changing the exception reporting mode to 'Plain'.
432 432 - Disabling pretty-printing of output.
433 433
434 434 Note that IPython also supports the pasting of code snippets that have
435 435 leading '>>>' and '...' prompts in them. This means that you can paste
436 436 doctests from files or docstrings (even if they have leading
437 437 whitespace), and the code will execute correctly. You can then use
438 438 '%history -t' to see the translated history; this will give you the
439 439 input after removal of all the leading prompts and whitespace, which
440 440 can be pasted back into an editor.
441 441
442 442 With these features, you can switch into this mode easily whenever you
443 443 need to do testing and changes to doctests, without having to leave
444 444 your existing IPython session.
445 445 """
446 446
447 447 # Shorthands
448 448 shell = self.shell
449 449 pm = shell.prompt_manager
450 450 meta = shell.meta
451 451 disp_formatter = self.shell.display_formatter
452 452 ptformatter = disp_formatter.formatters['text/plain']
453 453 # dstore is a data store kept in the instance metadata bag to track any
454 454 # changes we make, so we can undo them later.
455 455 dstore = meta.setdefault('doctest_mode',Struct())
456 456 save_dstore = dstore.setdefault
457 457
458 458 # save a few values we'll need to recover later
459 459 mode = save_dstore('mode',False)
460 460 save_dstore('rc_pprint',ptformatter.pprint)
461 461 save_dstore('xmode',shell.InteractiveTB.mode)
462 462 save_dstore('rc_separate_out',shell.separate_out)
463 463 save_dstore('rc_separate_out2',shell.separate_out2)
464 464 save_dstore('rc_prompts_pad_left',pm.justify)
465 465 save_dstore('rc_separate_in',shell.separate_in)
466 466 save_dstore('rc_active_types',disp_formatter.active_types)
467 467 save_dstore('prompt_templates',(pm.in_template, pm.in2_template, pm.out_template))
468 468
469 469 if mode == False:
470 470 # turn on
471 471 pm.in_template = '>>> '
472 472 pm.in2_template = '... '
473 473 pm.out_template = ''
474 474
475 475 # Prompt separators like plain python
476 476 shell.separate_in = ''
477 477 shell.separate_out = ''
478 478 shell.separate_out2 = ''
479 479
480 480 pm.justify = False
481 481
482 482 ptformatter.pprint = False
483 483 disp_formatter.active_types = ['text/plain']
484 484
485 485 shell.magic('xmode Plain')
486 486 else:
487 487 # turn off
488 488 pm.in_template, pm.in2_template, pm.out_template = dstore.prompt_templates
489 489
490 490 shell.separate_in = dstore.rc_separate_in
491 491
492 492 shell.separate_out = dstore.rc_separate_out
493 493 shell.separate_out2 = dstore.rc_separate_out2
494 494
495 495 pm.justify = dstore.rc_prompts_pad_left
496 496
497 497 ptformatter.pprint = dstore.rc_pprint
498 498 disp_formatter.active_types = dstore.rc_active_types
499 499
500 500 shell.magic('xmode ' + dstore.xmode)
501 501
502 502 # Store new mode and inform
503 503 dstore.mode = bool(1-int(mode))
504 504 mode_label = ['OFF','ON'][dstore.mode]
505 505 print('Doctest mode is:', mode_label)
506 506
507 507 @line_magic
508 508 def gui(self, parameter_s=''):
509 509 """Enable or disable IPython GUI event loop integration.
510 510
511 511 %gui [GUINAME]
512 512
513 513 This magic replaces IPython's threaded shells that were activated
514 514 using the (pylab/wthread/etc.) command line flags. GUI toolkits
515 515 can now be enabled at runtime and keyboard
516 516 interrupts should work without any problems. The following toolkits
517 517 are supported: wxPython, PyQt4, PyGTK, Tk and Cocoa (OSX)::
518 518
519 519 %gui wx # enable wxPython event loop integration
520 520 %gui qt4|qt # enable PyQt4 event loop integration
521 521 %gui gtk # enable PyGTK event loop integration
522 522 %gui gtk3 # enable Gtk3 event loop integration
523 523 %gui tk # enable Tk event loop integration
524 524 %gui osx # enable Cocoa event loop integration
525 525 # (requires %matplotlib 1.1)
526 526 %gui # disable all event loop integration
527 527
528 528 WARNING: after any of these has been called you can simply create
529 529 an application object, but DO NOT start the event loop yourself, as
530 530 we have already handled that.
531 531 """
532 532 opts, arg = self.parse_options(parameter_s, '')
533 533 if arg=='': arg = None
534 534 try:
535 535 return self.shell.enable_gui(arg)
536 536 except Exception as e:
537 537 # print simple error message, rather than traceback if we can't
538 538 # hook up the GUI
539 539 error(str(e))
540 540
541 541 @skip_doctest
542 542 @line_magic
543 543 def precision(self, s=''):
544 544 """Set floating point precision for pretty printing.
545 545
546 546 Can set either integer precision or a format string.
547 547
548 548 If numpy has been imported and precision is an int,
549 549 numpy display precision will also be set, via ``numpy.set_printoptions``.
550 550
551 551 If no argument is given, defaults will be restored.
552 552
553 553 Examples
554 554 --------
555 555 ::
556 556
557 557 In [1]: from math import pi
558 558
559 559 In [2]: %precision 3
560 560 Out[2]: u'%.3f'
561 561
562 562 In [3]: pi
563 563 Out[3]: 3.142
564 564
565 565 In [4]: %precision %i
566 566 Out[4]: u'%i'
567 567
568 568 In [5]: pi
569 569 Out[5]: 3
570 570
571 571 In [6]: %precision %e
572 572 Out[6]: u'%e'
573 573
574 574 In [7]: pi**10
575 575 Out[7]: 9.364805e+04
576 576
577 577 In [8]: %precision
578 578 Out[8]: u'%r'
579 579
580 580 In [9]: pi**10
581 581 Out[9]: 93648.047476082982
582 582 """
583 583 ptformatter = self.shell.display_formatter.formatters['text/plain']
584 584 ptformatter.float_precision = s
585 585 return ptformatter.float_format
586 586
587 587 @magic_arguments.magic_arguments()
588 588 @magic_arguments.argument(
589 589 '-e', '--export', action='store_true', default=False,
590 590 help='Export IPython history as a notebook. The filename argument '
591 591 'is used to specify the notebook name and format. For example '
592 592 'a filename of notebook.ipynb will result in a notebook name '
593 593 'of "notebook" and a format of "json". Likewise using a ".py" '
594 594 'file extension will write the notebook as a Python script'
595 595 )
596 596 @magic_arguments.argument(
597 597 '-f', '--format',
598 598 help='Convert an existing IPython notebook to a new format. This option '
599 599 'specifies the new format and can have the values: json, py. '
600 600 'The target filename is chosen automatically based on the new '
601 601 'format. The filename argument gives the name of the source file.'
602 602 )
603 603 @magic_arguments.argument(
604 604 'filename', type=unicode,
605 605 help='Notebook name or filename'
606 606 )
607 607 @line_magic
608 608 def notebook(self, s):
609 609 """Export and convert IPython notebooks.
610 610
611 611 This function can export the current IPython history to a notebook file
612 612 or can convert an existing notebook file into a different format. For
613 613 example, to export the history to "foo.ipynb" do "%notebook -e foo.ipynb".
614 614 To export the history to "foo.py" do "%notebook -e foo.py". To convert
615 615 "foo.ipynb" to "foo.json" do "%notebook -f json foo.ipynb". Possible
616 616 formats include (json/ipynb, py).
617 617 """
618 618 args = magic_arguments.parse_argstring(self.notebook, s)
619 619
620 620 from IPython.nbformat import current
621 621 args.filename = unquote_filename(args.filename)
622 622 if args.export:
623 623 fname, name, format = current.parse_filename(args.filename)
624 624 cells = []
625 625 hist = list(self.shell.history_manager.get_range())
626 626 for session, prompt_number, input in hist[:-1]:
627 627 cells.append(current.new_code_cell(prompt_number=prompt_number,
628 628 input=input))
629 629 worksheet = current.new_worksheet(cells=cells)
630 630 nb = current.new_notebook(name=name,worksheets=[worksheet])
631 631 with io.open(fname, 'w', encoding='utf-8') as f:
632 632 current.write(nb, f, format);
633 633 elif args.format is not None:
634 634 old_fname, old_name, old_format = current.parse_filename(args.filename)
635 635 new_format = args.format
636 636 if new_format == u'xml':
637 637 raise ValueError('Notebooks cannot be written as xml.')
638 638 elif new_format == u'ipynb' or new_format == u'json':
639 639 new_fname = old_name + u'.ipynb'
640 640 new_format = u'json'
641 641 elif new_format == u'py':
642 642 new_fname = old_name + u'.py'
643 643 else:
644 644 raise ValueError('Invalid notebook format: %s' % new_format)
645 645 with io.open(old_fname, 'r', encoding='utf-8') as f:
646 646 nb = current.read(f, old_format)
647 647 with io.open(new_fname, 'w', encoding='utf-8') as f:
648 648 current.write(nb, f, new_format)
General Comments 0
You need to be logged in to leave comments. Login now