Repository: ipython/ipython

Issue:
`%save` does not understand `~/`
E.g.:

```
In [62]: %save ~/remove_stale_branches.py 51
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
<ipython-input-62-7439301d8c47> in <module>
----> 1 get_ipython().run_line_magic('save', '~/remove_stale_branches.py 51')

~/.pyenv/versions/3.9.1/lib/python3.9/site-packages/IPython/core/interactiveshell.py in InteractiveShell.run_line_magic(self, magic_name, line, _stack_depth)
   2363     kwargs['local_ns'] = self.get_local_scope(stack_depth)
   2364 with self.builtin_trap:
-> 2365     result = fn(*args, **kwargs)
   2366 return result

<decorator-gen-36> in save(self, parameter_s)

~/.pyenv/versions/3.9.1/lib/python3.9/site-packages/IPython/core/magic.py in _method_magic_marker.<locals>.magic_deco.<locals>.<lambda>(f, *a, **k)
    186 def magic_deco(arg):
--> 187     call = lambda f, *a, **k: f(*a, **k)
    189     if callable(arg):
    190         # "Naked" decorator call (just @foo, no args)
    191         func = arg

~/.pyenv/versions/3.9.1/lib/python3.9/site-packages/IPython/core/magics/code.py in CodeMagics.save(self, parameter_s)
    235     print(e.args[0])
    236     return
--> 237 with io.open(fname, mode, encoding="utf-8") as f:
    238     if not file_exists or not append:
    239         f.write("# coding: utf-8\n")

FileNotFoundError: [Errno 2] No such file or directory: '~/remove_stale_branches.py'
```

- [ ] Investigate where expansion should be made
- [ ] Add tests
- [ ] Check what other magics might suffer from this issue
- [ ] Fix them too


Discussion:
HI! Would it be ok if I started working on this one?

Resolving patch:
diff --git a/IPython/core/magics/basic.py b/IPython/core/magics/basic.py
--- a/IPython/core/magics/basic.py
+++ b/IPython/core/magics/basic.py
@@ -4,6 +4,7 @@
 import argparse
 from logging import error
 import io
+import os
 from pprint import pformat
 import sys
 from warnings import warn
@@ -572,6 +573,7 @@ def notebook(self, s):
         For example, to export the history to "foo.ipynb" do "%notebook foo.ipynb".
         """
         args = magic_arguments.parse_argstring(self.notebook, s)
+        outfname = os.path.expanduser(args.filename)
 
         from nbformat import write, v4
 
@@ -585,7 +587,7 @@ def notebook(self, s):
                 source=source
             ))
         nb = v4.new_notebook(cells=cells)
-        with io.open(args.filename, 'w', encoding='utf-8') as f:
+        with io.open(outfname, "w", encoding="utf-8") as f:
             write(nb, f, version=4)
 
 @magics_class
diff --git a/IPython/core/magics/code.py b/IPython/core/magics/code.py
--- a/IPython/core/magics/code.py
+++ b/IPython/core/magics/code.py
@@ -222,6 +222,7 @@ def save(self, parameter_s=''):
         fname, codefrom = args[0], " ".join(args[1:])
         if not fname.endswith(('.py','.ipy')):
             fname += ext
+        fname = os.path.expanduser(fname)
         file_exists = os.path.isfile(fname)
         if file_exists and not force and not append:
             try:
diff --git a/IPython/core/magics/history.py b/IPython/core/magics/history.py
--- a/IPython/core/magics/history.py
+++ b/IPython/core/magics/history.py
@@ -151,6 +151,7 @@ def _format_lineno(session, line):
             # We don't want to close stdout at the end!
             close_at_end = False
         else:
+            outfname = os.path.expanduser(outfname)
             if os.path.exists(outfname):
                 try:
                     ans = io.ask_yes_no("File %r exists. Overwrite?" % outfname)


Test patch: