diff --git a/IPython/core/magic.py b/IPython/core/magic.py index e58bd4e..33daef0 100644 --- a/IPython/core/magic.py +++ b/IPython/core/magic.py @@ -48,6 +48,7 @@ from IPython.core.error import UsageError from IPython.core.fakemodule import FakeModule from IPython.core.profiledir import ProfileDir from IPython.core.macro import Macro +from IPython.core import magic_arguments from IPython.core import page from IPython.core.prefilter import ESC_MAGIC from IPython.lib.pylabtools import mpl_runner @@ -3495,4 +3496,68 @@ Defaulting color scheme to 'NoColor'""" ptformatter.float_precision = s return ptformatter.float_format + + @magic_arguments.magic_arguments() + @magic_arguments.argument( + '-e', '--export', action='store_true', default=False, + help='Export IPython history as a notebook. The filename argument ' + 'is used to specify the notebook name and format. For example ' + 'a filename of notebook.ipynb will result in a notebook name ' + 'of "notebook" and a format of "xml". Likewise using a ".json" ' + 'or ".py" file extension will write the notebook in the json ' + 'or py formats.' + ) + @magic_arguments.argument( + '-f', '--format', + help='Convert an existing IPython notebook to a new format. This option ' + 'specifies the new format and can have the values: xml, json, py. ' + 'The target filename is choosen automatically based on the new ' + 'format. The filename argument gives the name of the source file.' + ) + @magic_arguments.argument( + 'filename', type=unicode, + help='Notebook name or filename' + ) + def magic_notebook(self, s): + """Export and convert IPython notebooks. + + This function can export the current IPython history to a notebook file + or can convert an existing notebook file into a different format. For + example, to export the history to "foo.ipynb" do "%notebook -e foo.ipynb". + To export the history to "foo.py" do "%notebook -e foo.py". To convert + "foo.ipynb" to "foo.json" do "%notebook -f json foo.ipynb". Possible + formats include (xml/ipynb, json, py). + """ + args = magic_arguments.parse_argstring(self.magic_notebook, s) + print args + + from IPython.nbformat import current + if args.export: + fname, name, format = current.parse_filename(args.filename) + cells = [] + hist = list(self.history_manager.get_range()) + for session, prompt_number, input in hist[:-1]: + cells.append(current.new_code_cell(prompt_number=prompt_number, input=input)) + worksheet = current.new_worksheet(cells=cells) + nb = current.new_notebook(name=name,worksheets=[worksheet]) + with open(fname, 'w') as f: + current.write(nb, f, format); + elif args.format is not None: + old_fname, old_name, old_format = current.parse_filename(args.filename) + new_format = args.format + if new_format == u'xml' or new_format == u'ipynb': + new_fname = old_name + u'.ipynb' + new_format = u'xml' + elif new_format == u'py': + new_fname = old_name + u'.py' + elif new_format == u'json': + new_fname = old_name + u'.json' + else: + raise ValueError('Invalid notebook format: %s' % newformat) + with open(old_fname, 'r') as f: + nb = current.read(f, old_format) + with open(new_fname, 'w') as f: + current.write(nb, f, new_format) + + # end Magic diff --git a/IPython/nbformat/current.py b/IPython/nbformat/current.py index 757c20e..412c27e 100644 --- a/IPython/nbformat/current.py +++ b/IPython/nbformat/current.py @@ -7,7 +7,8 @@ from IPython.nbformat import v1 from IPython.nbformat.v2 import ( NotebookNode, - new_code_cell, new_text_cell, new_notebook, new_output, new_worksheet + new_code_cell, new_text_cell, new_notebook, new_output, new_worksheet, + parse_filename ) diff --git a/IPython/nbformat/v2/__init__.py b/IPython/nbformat/v2/__init__.py index 4f71ce5..1c04d3a 100644 --- a/IPython/nbformat/v2/__init__.py +++ b/IPython/nbformat/v2/__init__.py @@ -19,3 +19,39 @@ from .nbpy import to_notebook as to_notebook_py from .convert import convert_to_this_nbformat +def parse_filename(fname): + """Parse a notebook filename. + + This function takes a notebook filename and returns the notebook + format (xml/json/py) and the notebook name. This logic can be + summarized as follows: + + * notebook.ipynb -> (notebook.ipynb, notebook, xml) + * notebook.json -> (notebook.json, notebook, json) + * notebook.py -> (notebook.py, notebook, py) + * notebook -> (notebook.ipynb, notebook, xml) + + Parameters + ---------- + fname : unicode + The notebook filename. The filename can use a specific filename + extention (.ipynb, .json, .py) or none, in which case .ipynb will + be assumed. + + Returns + ------- + (fname, name, format) : (unicode, unicode, unicode) + The filename, notebook name and format. + """ + if fname.endswith(u'.ipynb'): + format = u'xml' + elif fname.endswith(u'.json'): + format = u'json' + elif fname.endswith(u'.py'): + format = u'py' + else: + fname = fname + u'.ipynb' + format = u'xml' + name = fname.split('.')[0] + return fname, name, format + diff --git a/IPython/nbformat/v2/nbpy.py b/IPython/nbformat/v2/nbpy.py index 8bbbc0d..dbb063d 100644 --- a/IPython/nbformat/v2/nbpy.py +++ b/IPython/nbformat/v2/nbpy.py @@ -19,15 +19,19 @@ class PyReader(NotebookReader): cell_lines = [] code_cell = False for line in lines: - if line.startswith(u'# '): + if line.startswith(u'# '): + pass + elif line.startswith(u'# '): if code_cell: raise PyReaderError('Unexpected ') - if cell_lines: - for block in self.split_lines_into_blocks(cell_lines): - cells.append(new_code_cell(input=block)) + # We can't use the ast to split blocks because there can be + # IPython syntax in the files. + # if cell_lines: + # for block in self.split_lines_into_blocks(cell_lines): + # cells.append(new_code_cell(input=block)) cell_lines = [] code_cell = True - if line.startswith(u'# '): + elif line.startswith(u'# '): if not code_cell: raise PyReaderError('Unexpected ') code = u'\n'.join(cell_lines) @@ -37,14 +41,19 @@ class PyReader(NotebookReader): code_cell = False else: cell_lines.append(line) - # For lines we were not able to process, - for block in self.split_lines_into_blocks(cell_lines): - cells.append(new_code_cell(input=block)) + # We can't use the ast to split blocks because there can be + # IPython syntax in the files. + # if cell_lines: + # for block in self.split_lines_into_blocks(cell_lines): + # cells.append(new_code_cell(input=block)) ws = new_worksheet(cells=cells) nb = new_notebook(worksheets=[ws]) return nb def split_lines_into_blocks(self, lines): + if len(lines) == 1: + yield lines[0] + raise StopIteration() import ast source = '\n'.join(lines) code = ast.parse(source)