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