123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278 |
- ''' A decorator-based method of constructing IPython magics with `argparse`
- option handling.
- New magic functions can be defined like so::
- from IPython.core.magic_arguments import (argument, magic_arguments,
- parse_argstring)
- @magic_arguments()
- @argument('-o', '--option', help='An optional argument.')
- @argument('arg', type=int, help='An integer positional argument.')
- def magic_cool(self, arg):
- """ A really cool magic command.
- """
- args = parse_argstring(magic_cool, arg)
- ...
- The `@magic_arguments` decorator marks the function as having argparse arguments.
- The `@argument` decorator adds an argument using the same syntax as argparse's
- `add_argument()` method. More sophisticated uses may also require the
- `@argument_group` or `@kwds` decorator to customize the formatting and the
- parsing.
- Help text for the magic is automatically generated from the docstring and the
- arguments::
- In[1]: %cool?
- %cool [-o OPTION] arg
-
- A really cool magic command.
-
- positional arguments:
- arg An integer positional argument.
-
- optional arguments:
- -o OPTION, --option OPTION
- An optional argument.
- Inheritance diagram:
- .. inheritance-diagram:: IPython.core.magic_arguments
- :parts: 3
- '''
- #-----------------------------------------------------------------------------
- # Copyright (C) 2010-2011, IPython Development Team.
- #
- # Distributed under the terms of the Modified BSD License.
- #
- # The full license is in the file COPYING.txt, distributed with this software.
- #-----------------------------------------------------------------------------
- import argparse
- import re
- # Our own imports
- from IPython.core.error import UsageError
- from IPython.utils.decorators import undoc
- from IPython.utils.process import arg_split
- from IPython.utils.text import dedent
- NAME_RE = re.compile(r"[a-zA-Z][a-zA-Z0-9_-]*$")
- @undoc
- class MagicHelpFormatter(argparse.RawDescriptionHelpFormatter):
- """A HelpFormatter with a couple of changes to meet our needs.
- """
- # Modified to dedent text.
- def _fill_text(self, text, width, indent):
- return argparse.RawDescriptionHelpFormatter._fill_text(self, dedent(text), width, indent)
- # Modified to wrap argument placeholders in <> where necessary.
- def _format_action_invocation(self, action):
- if not action.option_strings:
- metavar, = self._metavar_formatter(action, action.dest)(1)
- return metavar
- else:
- parts = []
- # if the Optional doesn't take a value, format is:
- # -s, --long
- if action.nargs == 0:
- parts.extend(action.option_strings)
- # if the Optional takes a value, format is:
- # -s ARGS, --long ARGS
- else:
- default = action.dest.upper()
- args_string = self._format_args(action, default)
- # IPYTHON MODIFICATION: If args_string is not a plain name, wrap
- # it in <> so it's valid RST.
- if not NAME_RE.match(args_string):
- args_string = "<%s>" % args_string
- for option_string in action.option_strings:
- parts.append('%s %s' % (option_string, args_string))
- return ', '.join(parts)
- # Override the default prefix ('usage') to our % magic escape,
- # in a code block.
- def add_usage(self, usage, actions, groups, prefix="::\n\n %"):
- super(MagicHelpFormatter, self).add_usage(usage, actions, groups, prefix)
- class MagicArgumentParser(argparse.ArgumentParser):
- """ An ArgumentParser tweaked for use by IPython magics.
- """
- def __init__(self,
- prog=None,
- usage=None,
- description=None,
- epilog=None,
- parents=None,
- formatter_class=MagicHelpFormatter,
- prefix_chars='-',
- argument_default=None,
- conflict_handler='error',
- add_help=False):
- if parents is None:
- parents = []
- super(MagicArgumentParser, self).__init__(prog=prog, usage=usage,
- description=description, epilog=epilog,
- parents=parents, formatter_class=formatter_class,
- prefix_chars=prefix_chars, argument_default=argument_default,
- conflict_handler=conflict_handler, add_help=add_help)
- def error(self, message):
- """ Raise a catchable error instead of exiting.
- """
- raise UsageError(message)
- def parse_argstring(self, argstring):
- """ Split a string into an argument list and parse that argument list.
- """
- argv = arg_split(argstring)
- return self.parse_args(argv)
- def construct_parser(magic_func):
- """ Construct an argument parser using the function decorations.
- """
- kwds = getattr(magic_func, 'argcmd_kwds', {})
- if 'description' not in kwds:
- kwds['description'] = getattr(magic_func, '__doc__', None)
- arg_name = real_name(magic_func)
- parser = MagicArgumentParser(arg_name, **kwds)
- # Reverse the list of decorators in order to apply them in the
- # order in which they appear in the source.
- group = None
- for deco in magic_func.decorators[::-1]:
- result = deco.add_to_parser(parser, group)
- if result is not None:
- group = result
- # Replace the magic function's docstring with the full help text.
- magic_func.__doc__ = parser.format_help()
- return parser
- def parse_argstring(magic_func, argstring):
- """ Parse the string of arguments for the given magic function.
- """
- return magic_func.parser.parse_argstring(argstring)
- def real_name(magic_func):
- """ Find the real name of the magic.
- """
- magic_name = magic_func.__name__
- if magic_name.startswith('magic_'):
- magic_name = magic_name[len('magic_'):]
- return getattr(magic_func, 'argcmd_name', magic_name)
- class ArgDecorator(object):
- """ Base class for decorators to add ArgumentParser information to a method.
- """
- def __call__(self, func):
- if not getattr(func, 'has_arguments', False):
- func.has_arguments = True
- func.decorators = []
- func.decorators.append(self)
- return func
- def add_to_parser(self, parser, group):
- """ Add this object's information to the parser, if necessary.
- """
- pass
- class magic_arguments(ArgDecorator):
- """ Mark the magic as having argparse arguments and possibly adjust the
- name.
- """
- def __init__(self, name=None):
- self.name = name
- def __call__(self, func):
- if not getattr(func, 'has_arguments', False):
- func.has_arguments = True
- func.decorators = []
- if self.name is not None:
- func.argcmd_name = self.name
- # This should be the first decorator in the list of decorators, thus the
- # last to execute. Build the parser.
- func.parser = construct_parser(func)
- return func
- class ArgMethodWrapper(ArgDecorator):
- """
- Base class to define a wrapper for ArgumentParser method.
- Child class must define either `_method_name` or `add_to_parser`.
- """
- _method_name = None
- def __init__(self, *args, **kwds):
- self.args = args
- self.kwds = kwds
- def add_to_parser(self, parser, group):
- """ Add this object's information to the parser.
- """
- if group is not None:
- parser = group
- getattr(parser, self._method_name)(*self.args, **self.kwds)
- return None
- class argument(ArgMethodWrapper):
- """ Store arguments and keywords to pass to add_argument().
- Instances also serve to decorate command methods.
- """
- _method_name = 'add_argument'
- class defaults(ArgMethodWrapper):
- """ Store arguments and keywords to pass to set_defaults().
- Instances also serve to decorate command methods.
- """
- _method_name = 'set_defaults'
- class argument_group(ArgMethodWrapper):
- """ Store arguments and keywords to pass to add_argument_group().
- Instances also serve to decorate command methods.
- """
- def add_to_parser(self, parser, group):
- """ Add this object's information to the parser.
- """
- return parser.add_argument_group(*self.args, **self.kwds)
- class kwds(ArgDecorator):
- """ Provide other keywords to the sub-parser constructor.
- """
- def __init__(self, **kwds):
- self.kwds = kwds
- def __call__(self, func):
- func = super(kwds, self).__call__(func)
- func.argcmd_kwds = self.kwds
- return func
- __all__ = ['magic_arguments', 'argument', 'argument_group', 'kwds',
- 'parse_argstring']
|