Repository: ipython/ipython

Issue:
In %magic help, remove duplicate aliases
Certain magics are actually just aliases of another, like %hist and %history.  In %magic, we show the full help for both, which can be confusing.  We should instead indicate which one is an alias and refer the reader to the original for details.



Discussion:
I agree this would be a nice improvement.  In terms of implementation, I think it'd be easiest to add an attribute to the function which stored the name of the alias:

```
    # For a long time we've had %hist as well as %history
    @line_magic
    def hist(self, arg):
        return self.history(arg)

    hist.__doc__ = history.__doc__
    hist._magic_alias = 'history'
```

Then `%magic` can just check for `fn._magic_alias` and print an abbreviated description:

```
%history:
    Alias for `%hist`.
```

We could optionally provide a function which did this wrapping automatically:

```
    hist = line_magic_alias('hist', history)
```

@bfroehle, +1


Resolving patch:
diff --git a/IPython/core/magic.py b/IPython/core/magic.py
--- a/IPython/core/magic.py
+++ b/IPython/core/magic.py
@@ -25,7 +25,7 @@
 from IPython.config.configurable import Configurable
 from IPython.core import oinspect
 from IPython.core.error import UsageError
-from IPython.core.inputsplitter import ESC_MAGIC
+from IPython.core.inputsplitter import ESC_MAGIC, ESC_MAGIC2
 from IPython.external.decorator import decorator
 from IPython.utils.ipstruct import Struct
 from IPython.utils.process import arg_split
@@ -47,6 +47,7 @@
 
 magic_kinds = ('line', 'cell')
 magic_spec = ('line', 'cell', 'line_cell')
+magic_escapes = dict(line=ESC_MAGIC, cell=ESC_MAGIC2)
 
 #-----------------------------------------------------------------------------
 # Utility classes and functions
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
@@ -19,9 +19,9 @@
 from pprint import pformat
 
 # Our own packages
+from IPython.core import magic_arguments
 from IPython.core.error import UsageError
-from IPython.core.inputsplitter import ESC_MAGIC
-from IPython.core.magic import Magics, magics_class, line_magic
+from IPython.core.magic import Magics, magics_class, line_magic, magic_escapes
 from IPython.utils.text import format_screen
 from IPython.core import magic_arguments, page
 from IPython.testing.skipdoctest import skip_doctest
@@ -40,9 +40,91 @@ class BasicMagics(Magics):
     These are various magics that don't fit into specific categories but that
     are all part of the base 'IPython experience'."""
 
+    @magic_arguments.magic_arguments()
+    @magic_arguments.argument(
+        '-l', '--line', action='store_true',
+        help="""Create a line magic alias."""
+    )
+    @magic_arguments.argument(
+        '-c', '--cell', action='store_true',
+        help="""Create a cell magic alias."""
+    )
+    @magic_arguments.argument(
+        'name',
+        help="""Name of the magic to be created."""
+    )
+    @magic_arguments.argument(
+        'target',
+        help="""Name of the existing line or cell magic."""
+    )
+    @line_magic
+    def alias_magic(self, line=''):
+        """Create an alias for an existing line or cell magic.
+
+        Examples
+        --------
+        ::
+          In [1]: %alias_magic t timeit
+
+          In [2]: %t -n1 pass
+          1 loops, best of 3: 954 ns per loop
+
+          In [3]: %%t -n1
+             ...: pass
+             ...:
+          1 loops, best of 3: 954 ns per loop
+
+          In [4]: %alias_magic --cell whereami pwd
+          UsageError: Cell magic function `%%pwd` not found.
+          In [5]: %alias_magic --line whereami pwd
+
+          In [6]: %whereami
+          Out[6]: u'/home/testuser'
+        """
+        args = magic_arguments.parse_argstring(self.alias_magic, line)
+        shell = self.shell
+        escs = ''.join(magic_escapes.values())
+
+        target = args.target.lstrip(escs)
+        name = args.name.lstrip(escs)
+
+        # Find the requested magics.
+        m_line = shell.find_magic(target, 'line')
+        m_cell = shell.find_magic(target, 'cell')
+        if args.line and m_line is None:
+            raise UsageError('Line magic function `%s%s` not found.' %
+                             (magic_escapes['line'], target))
+        if args.cell and m_cell is None:
+            raise UsageError('Cell magic function `%s%s` not found.' %
+                             (magic_escapes['cell'], target))
+
+        # If --line and --cell are not specified, default to the ones
+        # that are available.
+        if not args.line and not args.cell:
+            if not m_line and not m_cell:
+                raise UsageError(
+                    'No line or cell magic with name `%s` found.' % target
+                )
+            args.line = bool(m_line)
+            args.cell = bool(m_cell)
+
+        if args.line:
+            def wrapper(line): return m_line(line)
+            wrapper.__name__ = str(name)
+            wrapper.__doc__ = "Alias for `%s%s`." % \
+                              (magic_escapes['line'], target)
+            shell.register_magic_function(wrapper, 'line', name)
+
+        if args.cell:
+            def wrapper(line, cell): return m_cell(line, cell)
+            wrapper.__name__ = str(name)
+            wrapper.__doc__ = "Alias for `%s%s`." % \
+                              (magic_escapes['cell'], target)
+            shell.register_magic_function(wrapper, 'cell', name)
+
     def _lsmagic(self):
-        mesc = ESC_MAGIC
-        cesc = mesc*2
+        mesc = magic_escapes['line']
+        cesc = magic_escapes['cell']
         mman = self.shell.magics_manager
         magics = mman.lsmagic()
         out = ['Available line magics:',
@@ -70,10 +152,10 @@ def _magic_docs(self, brief=False, rest=False):
             format_string = '%s%s:\n\t%s\n'
 
         return ''.join(
-            [format_string % (ESC_MAGIC, fname, fndoc)
+            [format_string % (magic_escapes['line'], fname, fndoc)
              for fname, fndoc in sorted(docs['line'].items())]
             +
-            [format_string % (ESC_MAGIC*2, fname, fndoc)
+            [format_string % (magic_escapes['cell'], fname, fndoc)
              for fname, fndoc in sorted(docs['cell'].items())]
         )
 


Test patch:
diff --git a/IPython/core/tests/test_magic.py b/IPython/core/tests/test_magic.py
--- a/IPython/core/tests/test_magic.py
+++ b/IPython/core/tests/test_magic.py
@@ -688,7 +688,7 @@ class FooFoo(Magics):
     def line_foo(self, line):
         "I am line foo"
         pass
-            
+
     @cell_magic("foo")
     def cell_foo(self, line, cell):
         "I am cell foo, not line foo"
@@ -721,4 +721,23 @@ def test_multiple_magics():
     nt.assert_true(mm.magics['line']['foo'].im_self is foo1)
     mm.register(foo2)
     nt.assert_true(mm.magics['line']['foo'].im_self is foo2)
-    
\ No newline at end of file
+
+def test_alias_magic():
+    """Test %alias_magic."""
+    ip = get_ipython()
+    mm = ip.magics_manager
+
+    # Basic operation: both cell and line magics are created, if possible.
+    ip.run_line_magic('alias_magic', 'timeit_alias timeit')
+    nt.assert_true('timeit_alias' in mm.magics['line'])
+    nt.assert_true('timeit_alias' in mm.magics['cell'])
+
+    # --cell is specified, line magic not created.
+    ip.run_line_magic('alias_magic', '--cell timeit_cell_alias timeit')
+    nt.assert_false('timeit_cell_alias' in mm.magics['line'])
+    nt.assert_true('timeit_cell_alias' in mm.magics['cell'])
+
+    # Test that line alias is created successfully.
+    ip.run_line_magic('alias_magic', '--line env_alias env')
+    nt.assert_equal(ip.run_line_magic('env', ''),
+                    ip.run_line_magic('env_alias', ''))