From a5beb59f275d5e920cc41abc1f3372a259ff968e 2012-07-24 03:37:47 From: Fernando Perez Date: 2012-07-24 03:37:47 Subject: [PATCH] Merge pull request #2124 from bfroehle/use_alias_magic Add an API for registering magic aliases. Add a method `register_alias` to `MagicsManager` which can be used to register new magic aliases. Each magic alias is an instance of `MagicAlias`, a helper class whose `__call__` looks up the target of the alias (at call time) and dispatches the magic call. As a future benefit, this could be easily extended to allow for new aliases which contain some flags to pass to the function. For example, it would be easy to change the behavior to allow the creation of an `%ex` alias for `%edit -x`. --- diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index e11eef1..c2bbdd4 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -2053,6 +2053,12 @@ class InteractiveShell(SingletonConfigurable): m.NamespaceMagics, m.OSMagics, m.PylabMagics, m.ScriptMagics, ) + # Register Magic Aliases + mman = self.magics_manager + mman.register_alias('ed', 'edit') + mman.register_alias('hist', 'history') + mman.register_alias('rep', 'recall') + # FIXME: Move the color initialization to the DisplayHook, which # should be split into a prompt manager and displayhook. We probably # even need a centralize colors management object. diff --git a/IPython/core/magic.py b/IPython/core/magic.py index 1be6d12..010f311 100644 --- a/IPython/core/magic.py +++ b/IPython/core/magic.py @@ -452,6 +452,36 @@ class MagicsManager(Configurable): setattr(self.user_magics, name, meth) record_magic(self.magics, 'line', name, meth) + def register_alias(self, alias_name, magic_name, magic_kind='line'): + """Register an alias to a magic function. + + The alias is an instance of :class:`MagicAlias`, which holds the + name and kind of the magic it should call. Binding is done at + call time, so if the underlying magic function is changed the alias + will call the new function. + + Parameters + ---------- + alias_name : str + The name of the magic to be registered. + + magic_name : str + The name of an existing magic. + + magic_kind : str + Kind of magic, one of 'line' or 'cell' + """ + + # `validate_type` is too permissive, as it allows 'line_cell' + # which we do not handle. + if magic_kind not in magic_kinds: + raise ValueError('magic_kind must be one of %s, %s given' % + magic_kinds, magic_kind) + + alias = MagicAlias(self.shell, magic_name, magic_kind) + setattr(self.user_magics, alias_name, alias) + record_magic(self.magics, magic_kind, alias_name, alias) + # Key base class that provides the central functionality for magics. class Magics(object): @@ -615,3 +645,45 @@ class Magics(object): if fn not in self.lsmagic(): error("%s is not a magic function" % fn) self.options_table[fn] = optstr + +class MagicAlias(object): + """Store a magic alias. + + An alias is determined by its magic name and magic kind. Lookup + is done at call time, so if the underlying magic changes the alias + will call the new function. + + Use the :meth:`MagicsManager.register_alias` method or the + `%alias_magic` magic function to create and register a new alias. + """ + def __init__(self, shell, magic_name, magic_kind): + self.shell = shell + self.magic_name = magic_name + self.magic_kind = magic_kind + + self._in_call = False + + @property + def pretty_target(self): + """A formatted version of the target of the alias.""" + return '%s%s' % (magic_escapes[self.magic_kind], self.magic_name) + + @property + def __doc__(self): + return "Alias for `%s`." % self.pretty_target + + def __call__(self, *args, **kwargs): + """Call the magic alias.""" + fn = self.shell.find_magic(self.magic_name, self.magic_kind) + if fn is None: + raise UsageError("Magic `%s` not found." % self.pretty_target) + + # Protect against infinite recursion. + if self._in_call: + raise UsageError("Infinite recursion detected; " + "magic aliases cannot call themselves.") + self._in_call = True + try: + return fn(*args, **kwargs) + finally: + self._in_call = False diff --git a/IPython/core/magics/basic.py b/IPython/core/magics/basic.py index 6b75f17..5825640 100644 --- a/IPython/core/magics/basic.py +++ b/IPython/core/magics/basic.py @@ -65,6 +65,8 @@ class BasicMagics(Magics): -------- :: In [1]: %alias_magic t timeit + Created `%t` as an alias for `%timeit`. + Created `%%t` as an alias for `%%timeit`. In [2]: %t -n1 pass 1 loops, best of 3: 954 ns per loop @@ -77,12 +79,14 @@ class BasicMagics(Magics): In [4]: %alias_magic --cell whereami pwd UsageError: Cell magic function `%%pwd` not found. In [5]: %alias_magic --line whereami pwd + Created `%whereami` as an alias for `%pwd`. In [6]: %whereami Out[6]: u'/home/testuser' """ args = magic_arguments.parse_argstring(self.alias_magic, line) shell = self.shell + mman = self.shell.magics_manager escs = ''.join(magic_escapes.values()) target = args.target.lstrip(escs) @@ -109,18 +113,16 @@ class BasicMagics(Magics): 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) + mman.register_alias(name, target, 'line') + print('Created `%s%s` as an alias for `%s%s`.' % ( + magic_escapes['line'], name, + magic_escapes['line'], target)) 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) + mman.register_alias(name, target, 'cell') + print('Created `%s%s` as an alias for `%s%s`.' % ( + magic_escapes['cell'], name, + magic_escapes['cell'], target)) def _lsmagic(self): mesc = magic_escapes['line'] diff --git a/IPython/core/magics/code.py b/IPython/core/magics/code.py index 5414f4f..47df10f 100644 --- a/IPython/core/magics/code.py +++ b/IPython/core/magics/code.py @@ -333,11 +333,6 @@ class CodeMagics(Magics): mfile.close() self.shell.user_ns[mname] = Macro(mvalue) - @line_magic - def ed(self, parameter_s=''): - """Alias to %edit.""" - return self.edit(parameter_s) - @skip_doctest @line_magic def edit(self, parameter_s='',last_call=['','']): @@ -431,7 +426,7 @@ class CodeMagics(Magics): This is an example of creating a simple function inside the editor and then modifying it. First, start up the editor:: - In [1]: ed + In [1]: edit Editing... done. Executing edited code... Out[1]: 'def foo():\\n print "foo() was defined in an editing session"\\n' @@ -444,7 +439,7 @@ class CodeMagics(Magics): Now we edit foo. IPython automatically loads the editor with the (temporary) file where foo() was previously defined:: - In [3]: ed foo + In [3]: edit foo Editing... done. Executing edited code... And if we call foo() again we get the modified version:: @@ -455,21 +450,21 @@ class CodeMagics(Magics): Here is an example of how to edit a code snippet successive times. First we call the editor:: - In [5]: ed + In [5]: edit Editing... done. Executing edited code... hello Out[5]: "print 'hello'\\n" Now we call it again with the previous output (stored in _):: - In [6]: ed _ + In [6]: edit _ Editing... done. Executing edited code... hello world Out[6]: "print 'hello world'\\n" Now we call it with the output #8 (stored in _8, also as Out[8]):: - In [7]: ed _8 + In [7]: edit _8 Editing... done. Executing edited code... hello again Out[7]: "print 'hello again'\\n" diff --git a/IPython/core/magics/history.py b/IPython/core/magics/history.py index f5076b6..501ba62 100644 --- a/IPython/core/magics/history.py +++ b/IPython/core/magics/history.py @@ -91,10 +91,10 @@ class HistoryMagics(Magics): -------- :: - In [6]: %hist -n 4-6 + In [6]: %history -n 4-6 4:a = 12 5:print a**2 - 6:%hist -n 4-6 + 6:%history -n 4-6 """ @@ -187,15 +187,8 @@ class HistoryMagics(Magics): if close_at_end: outfile.close() - # 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__ - - @line_magic - def rep(self, arg): + def recall(self, arg): r"""Repeat a command, or get command to input line for editing. %recall and %rep are equivalent. @@ -209,7 +202,7 @@ class HistoryMagics(Magics): In[1]: l = ["hei", "vaan"] In[2]: "".join(l) Out[2]: heivaan - In[3]: %rep + In[3]: %recall In[4]: heivaan_ <== cursor blinking %recall 45 @@ -244,7 +237,7 @@ class HistoryMagics(Magics): except Exception: # Search for term in history histlines = self.shell.history_manager.search("*"+arg+"*") for h in reversed([x[2] for x in histlines]): - if 'rep' in h: + if 'recall' in h or 'rep' in h: continue self.shell.set_next_input(h.rstrip()) return @@ -292,10 +285,3 @@ class HistoryMagics(Magics): print(histlines) print("=== Output: ===") self.shell.run_cell("\n".join(hist), store_history=False) - - @line_magic - def recall(self,arg): - self.rep(arg) - - recall.__doc__ = rep.__doc__ - diff --git a/IPython/zmq/zmqshell.py b/IPython/zmq/zmqshell.py index 60fa06d..0691ef6 100644 --- a/IPython/zmq/zmqshell.py +++ b/IPython/zmq/zmqshell.py @@ -575,7 +575,7 @@ class ZMQInteractiveShell(InteractiveShell): def init_magics(self): super(ZMQInteractiveShell, self).init_magics() self.register_magics(KernelMagics) - self.run_line_magic('alias_magic', 'ed edit') + self.magics_manager.register_alias('ed', 'edit')