##// END OF EJS Templates
ENH: Add the argparse-based option parsing for magics.
Robert Kern -
Show More
@@ -0,0 +1,222 b''
1 ''' A decorator-based method of constructing IPython magics with `argparse`
2 option handling.
3
4 New magic functions can be defined like so::
5
6 from IPython.core.magic_arguments import (argument, magic_arguments,
7 parse_argstring)
8
9 @magic_arguments()
10 @argument('-o', '--option', help='An optional argument.')
11 @argument('arg', type=int, help='An integer positional argument.')
12 def magic_cool(self, arg):
13 """ A really cool magic command.
14
15 """
16 args = parse_argstring(magic_cool, arg)
17 ...
18
19 The `@magic_arguments` decorator marks the function as having argparse arguments.
20 The `@argument` decorator adds an argument using the same syntax as argparse's
21 `add_argument()` method. More sophisticated uses may also require the
22 `@argument_group` or `@kwds` decorator to customize the formatting and the
23 parsing.
24
25 Help text for the magic is automatically generated from the docstring and the
26 arguments::
27
28 In[1]: %cool?
29 %cool [-o OPTION] arg
30
31 A really cool magic command.
32
33 positional arguments:
34 arg An integer positional argument.
35
36 optional arguments:
37 -o OPTION, --option OPTION
38 An optional argument.
39
40 '''
41 #-----------------------------------------------------------------------------
42 # Copyright (c) 2010, IPython Development Team.
43 #
44 # Distributed under the terms of the Modified BSD License.
45 #
46 # The full license is in the file COPYING.txt, distributed with this software.
47 #-----------------------------------------------------------------------------
48
49 # Stdlib imports
50 import shlex
51
52 # Our own imports
53 from IPython.external import argparse
54 from IPython.core.error import UsageError
55
56
57 class MagicArgumentParser(argparse.ArgumentParser):
58 """ An ArgumentParser tweaked for use by IPython magics.
59 """
60 def __init__(self,
61 prog=None,
62 usage=None,
63 description=None,
64 epilog=None,
65 version=None,
66 parents=None,
67 formatter_class=argparse.HelpFormatter,
68 prefix_chars='-',
69 argument_default=None,
70 conflict_handler='error',
71 add_help=False):
72 if parents is None:
73 parents = []
74 super(MagicArgumentParser, self).__init__(prog=prog, usage=usage,
75 description=description, epilog=epilog, version=version,
76 parents=parents, formatter_class=formatter_class,
77 prefix_chars=prefix_chars, argument_default=argument_default,
78 conflict_handler=conflict_handler, add_help=add_help)
79
80 def error(self, message):
81 """ Raise a catchable error instead of exiting.
82 """
83 raise UsageError(message)
84
85 def parse_argstring(self, argstring):
86 """ Split a string into an argument list and parse that argument list.
87 """
88 argv = shlex.split(argstring)
89 return self.parse_args(argv)
90
91
92 def construct_parser(magic_func):
93 """ Construct an argument parser using the function decorations.
94 """
95 kwds = getattr(magic_func, 'argcmd_kwds', {})
96 if 'description' not in kwds:
97 kwds['description'] = getattr(magic_func, '__doc__', None)
98 arg_name = real_name(magic_func)
99 parser = MagicArgumentParser(arg_name, **kwds)
100 # Reverse the list of decorators in order to apply them in the
101 # order in which they appear in the source.
102 group = None
103 for deco in magic_func.decorators[::-1]:
104 result = deco.add_to_parser(parser, group)
105 if result is not None:
106 group = result
107
108 # Replace the starting 'usage: ' with IPython's %.
109 help_text = parser.format_help()
110 if help_text.startswith('usage: '):
111 help_text = help_text.replace('usage: ', '%', 1)
112 else:
113 help_text = '%' + help_text
114
115 # Replace the magic function's docstring with the full help text.
116 magic_func.__doc__ = help_text
117
118 return parser
119
120
121 def parse_argstring(magic_func, argstring):
122 """ Parse the string of arguments for the given magic function.
123 """
124 args = magic_func.parser.parse_argstring(argstring)
125 return args
126
127
128 def real_name(magic_func):
129 """ Find the real name of the magic.
130 """
131 magic_name = magic_func.__name__
132 if magic_name.startswith('magic_'):
133 magic_name = magic_name[len('magic_'):]
134 arg_name = getattr(magic_func, 'argcmd_name', magic_name)
135 return arg_name
136
137
138 class ArgDecorator(object):
139 """ Base class for decorators to add ArgumentParser information to a method.
140 """
141
142 def __call__(self, func):
143 if not getattr(func, 'has_arguments', False):
144 func.has_arguments = True
145 func.decorators = []
146 func.decorators.append(self)
147 return func
148
149 def add_to_parser(self, parser, group):
150 """ Add this object's information to the parser, if necessary.
151 """
152 pass
153
154
155 class magic_arguments(ArgDecorator):
156 """ Mark the magic as having argparse arguments and possibly adjust the
157 name.
158 """
159
160 def __init__(self, name=None):
161 self.name = name
162
163 def __call__(self, func):
164 if not getattr(func, 'has_arguments', False):
165 func.has_arguments = True
166 func.decorators = []
167 if self.name is not None:
168 func.argcmd_name = self.name
169 # This should be the first decorator in the list of decorators, thus the
170 # last to execute. Build the parser.
171 func.parser = construct_parser(func)
172 return func
173
174
175 class argument(ArgDecorator):
176 """ Store arguments and keywords to pass to add_argument().
177
178 Instances also serve to decorate command methods.
179 """
180 def __init__(self, *args, **kwds):
181 self.args = args
182 self.kwds = kwds
183
184 def add_to_parser(self, parser, group):
185 """ Add this object's information to the parser.
186 """
187 if group is not None:
188 parser = group
189 parser.add_argument(*self.args, **self.kwds)
190 return None
191
192
193 class argument_group(ArgDecorator):
194 """ Store arguments and keywords to pass to add_argument_group().
195
196 Instances also serve to decorate command methods.
197 """
198 def __init__(self, *args, **kwds):
199 self.args = args
200 self.kwds = kwds
201
202 def add_to_parser(self, parser, group):
203 """ Add this object's information to the parser.
204 """
205 group = parser.add_argument_group(*self.args, **self.kwds)
206 return group
207
208
209 class kwds(ArgDecorator):
210 """ Provide other keywords to the sub-parser constructor.
211 """
212 def __init__(self, **kwds):
213 self.kwds = kwds
214
215 def __call__(self, func):
216 func = super(kwds, self).__call__(func)
217 func.argcmd_kwds = self.kwds
218 return func
219
220
221 __all__ = ['magic_arguments', 'argument', 'argument_group', 'kwds',
222 'parse_argstring']
@@ -0,0 +1,113 b''
1 #-----------------------------------------------------------------------------
2 # Copyright (c) 2010, IPython Development Team.
3 #
4 # Distributed under the terms of the Modified BSD License.
5 #
6 # The full license is in the file COPYING.txt, distributed with this software.
7 #-----------------------------------------------------------------------------
8
9 from nose.tools import assert_equal, assert_true
10
11 from IPython.external import argparse
12 from IPython.core.magic_arguments import (argument, argument_group, kwds,
13 magic_arguments, parse_argstring, real_name)
14
15
16 @magic_arguments()
17 @argument('-f', '--foo', help="an argument")
18 def magic_foo1(self, args):
19 """ A docstring.
20 """
21 return parse_argstring(magic_foo1, args)
22
23 @magic_arguments()
24 def magic_foo2(self, args):
25 """ A docstring.
26 """
27 return parse_argstring(magic_foo2, args)
28
29 @magic_arguments()
30 @argument('-f', '--foo', help="an argument")
31 @argument_group('Group')
32 @argument('-b', '--bar', help="a grouped argument")
33 @argument_group('Second Group')
34 @argument('-z', '--baz', help="another grouped argument")
35 def magic_foo3(self, args):
36 """ A docstring.
37 """
38 return parse_argstring(magic_foo3, args)
39
40 @magic_arguments()
41 @kwds(argument_default=argparse.SUPPRESS)
42 @argument('-f', '--foo', help="an argument")
43 def magic_foo4(self, args):
44 """ A docstring.
45 """
46 return parse_argstring(magic_foo4, args)
47
48 @magic_arguments('frobnicate')
49 @argument('-f', '--foo', help="an argument")
50 def magic_foo5(self, args):
51 """ A docstring.
52 """
53 return parse_argstring(magic_foo5, args)
54
55 @magic_arguments()
56 @argument('-f', '--foo', help="an argument")
57 def magic_magic_foo(self, args):
58 """ A docstring.
59 """
60 return parse_argstring(magic_magic_foo, args)
61
62 @magic_arguments()
63 @argument('-f', '--foo', help="an argument")
64 def foo(self, args):
65 """ A docstring.
66 """
67 return parse_argstring(foo, args)
68
69 def test_magic_arguments():
70 # Ideally, these would be doctests, but I could not get it to work.
71 yield assert_equal, magic_foo1.__doc__, '%foo1 [-f FOO]\n\nA docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n'
72 yield assert_equal, getattr(magic_foo1, 'argcmd_name', None), None
73 yield assert_equal, real_name(magic_foo1), 'foo1'
74 yield assert_equal, magic_foo1(None, ''), argparse.Namespace(foo=None)
75 yield assert_true, hasattr(magic_foo1, 'has_arguments')
76
77 yield assert_equal, magic_foo2.__doc__, '%foo2\n\nA docstring.\n'
78 yield assert_equal, getattr(magic_foo2, 'argcmd_name', None), None
79 yield assert_equal, real_name(magic_foo2), 'foo2'
80 yield assert_equal, magic_foo2(None, ''), argparse.Namespace()
81 yield assert_true, hasattr(magic_foo2, 'has_arguments')
82
83 yield assert_equal, magic_foo3.__doc__, '%foo3 [-f FOO] [-b BAR] [-z BAZ]\n\nA docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n\nGroup:\n -b BAR, --bar BAR a grouped argument\n\nSecond Group:\n -z BAZ, --baz BAZ another grouped argument\n'
84 yield assert_equal, getattr(magic_foo3, 'argcmd_name', None), None
85 yield assert_equal, real_name(magic_foo3), 'foo3'
86 yield assert_equal, magic_foo3(None, ''), argparse.Namespace(bar=None, baz=None, foo=None)
87 yield assert_true, hasattr(magic_foo3, 'has_arguments')
88
89 yield assert_equal, magic_foo4.__doc__, '%foo4 [-f FOO]\n\nA docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n'
90 yield assert_equal, getattr(magic_foo4, 'argcmd_name', None), None
91 yield assert_equal, real_name(magic_foo4), 'foo4'
92 yield assert_equal, magic_foo4(None, ''), argparse.Namespace()
93 yield assert_true, hasattr(magic_foo4, 'has_arguments')
94
95 yield assert_equal, magic_foo5.__doc__, '%frobnicate [-f FOO]\n\nA docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n'
96 yield assert_equal, getattr(magic_foo5, 'argcmd_name', None), 'frobnicate'
97 yield assert_equal, real_name(magic_foo5), 'frobnicate'
98 yield assert_equal, magic_foo5(None, ''), argparse.Namespace(foo=None)
99 yield assert_true, hasattr(magic_foo5, 'has_arguments')
100
101 yield assert_equal, magic_magic_foo.__doc__, '%magic_foo [-f FOO]\n\nA docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n'
102 yield assert_equal, getattr(magic_magic_foo, 'argcmd_name', None), None
103 yield assert_equal, real_name(magic_magic_foo), 'magic_foo'
104 yield assert_equal, magic_magic_foo(None, ''), argparse.Namespace(foo=None)
105 yield assert_true, hasattr(magic_magic_foo, 'has_arguments')
106
107 yield assert_equal, foo.__doc__, '%foo [-f FOO]\n\nA docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n'
108 yield assert_equal, getattr(foo, 'argcmd_name', None), None
109 yield assert_equal, real_name(foo), 'foo'
110 yield assert_equal, foo(None, ''), argparse.Namespace(foo=None)
111 yield assert_true, hasattr(foo, 'has_arguments')
112
113
General Comments 0
You need to be logged in to leave comments. Login now