diff --git a/IPython/core/magics/code.py b/IPython/core/magics/code.py index 2f8c4a9..57aa162 100644 --- a/IPython/core/magics/code.py +++ b/IPython/core/magics/code.py @@ -61,6 +61,8 @@ class CodeMagics(Magics): -f: force overwrite. If file exists, %save will prompt for overwrite unless -f is given. + -a: append to the file instead of overwriting it. + This function uses the same syntax as %history for input ranges, then saves the lines to the filename you specify. @@ -70,14 +72,17 @@ class CodeMagics(Magics): If `-r` option is used, the default extension is `.ipy`. """ - opts,args = self.parse_options(parameter_s,'fr',mode='list') + opts,args = self.parse_options(parameter_s,'fra',mode='list') raw = 'r' in opts force = 'f' in opts + append = 'a' in opts + mode = 'a' if append else 'w' ext = u'.ipy' if raw else u'.py' fname, codefrom = unquote_filename(args[0]), " ".join(args[1:]) if not fname.endswith((u'.py',u'.ipy')): fname += ext - if os.path.isfile(fname) and not force: + file_exists = os.path.isfile(fname) + if file_exists and not force and not append: try: overwrite = self.shell.ask_yes_no('File `%s` exists. Overwrite (y/[N])? ' % fname, default='n') except StdinNotImplementedError: @@ -91,9 +96,14 @@ class CodeMagics(Magics): except (TypeError, ValueError) as e: print e.args[0] return - with io.open(fname,'w', encoding="utf-8") as f: - f.write(u"# coding: utf-8\n") - f.write(py3compat.cast_unicode(cmds)) + out = py3compat.cast_unicode(cmds) + with io.open(fname, mode, encoding="utf-8") as f: + if not file_exists or not append: + f.write(u"# coding: utf-8\n") + f.write(out) + # make sure we end on a newline + if not out.endswith(u'\n'): + f.write(u'\n') print 'The following commands were written to file `%s`:' % fname print cmds diff --git a/IPython/core/tests/test_history.py b/IPython/core/tests/test_history.py index bef2d42..6b5c218 100644 --- a/IPython/core/tests/test_history.py +++ b/IPython/core/tests/test_history.py @@ -95,7 +95,7 @@ def test_history(): ip.magic("save " + testfilename + " ~1/1-3") with py3compat.open(testfilename, encoding='utf-8') as testfile: nt.assert_equal(testfile.read(), - u"# coding: utf-8\n" + u"\n".join(hist)) + u"# coding: utf-8\n" + u"\n".join(hist)+u"\n") # Duplicate line numbers - check that it doesn't crash, and # gets a new session diff --git a/IPython/core/tests/test_magic.py b/IPython/core/tests/test_magic.py index b74236d..16b0ec6 100644 --- a/IPython/core/tests/test_magic.py +++ b/IPython/core/tests/test_magic.py @@ -742,3 +742,23 @@ def test_alias_magic(): ip.run_line_magic('alias_magic', '--line env_alias env') nt.assert_equal(ip.run_line_magic('env', ''), ip.run_line_magic('env_alias', '')) + +def test_save(): + """Test %save.""" + ip = get_ipython() + ip.history_manager.reset() # Clear any existing history. + cmds = [u"a=1", u"def b():\n return a**2", u"print(a, b())"] + for i, cmd in enumerate(cmds, start=1): + ip.history_manager.store_inputs(i, cmd) + with TemporaryDirectory() as tmpdir: + file = os.path.join(tmpdir, "testsave.py") + ip.run_line_magic("save", "%s 1-10" % file) + with open(file) as f: + content = f.read() + nt.assert_equal(content.count(cmds[0]), 1) + nt.assert_true('coding: utf-8' in content) + ip.run_line_magic("save", "-a %s 1-10" % file) + with open(file) as f: + content = f.read() + nt.assert_equal(content.count(cmds[0]), 2) + nt.assert_true('coding: utf-8' in content)