##// END OF EJS Templates
Produce rst-compatible options lists from magic_arguments
Thomas Kluyver -
Show More
@@ -1,246 +1,278 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 Inheritance diagram:
40 Inheritance diagram:
41
41
42 .. inheritance-diagram:: IPython.core.magic_arguments
42 .. inheritance-diagram:: IPython.core.magic_arguments
43 :parts: 3
43 :parts: 3
44
44
45 '''
45 '''
46 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
47 # Copyright (C) 2010-2011, IPython Development Team.
47 # Copyright (C) 2010-2011, IPython Development Team.
48 #
48 #
49 # Distributed under the terms of the Modified BSD License.
49 # Distributed under the terms of the Modified BSD License.
50 #
50 #
51 # The full license is in the file COPYING.txt, distributed with this software.
51 # The full license is in the file COPYING.txt, distributed with this software.
52 #-----------------------------------------------------------------------------
52 #-----------------------------------------------------------------------------
53 import argparse
53 import argparse
54 import re
54
55
55 # Our own imports
56 # Our own imports
56 from IPython.core.error import UsageError
57 from IPython.core.error import UsageError
58 from IPython.utils.decorators import undoc
57 from IPython.utils.process import arg_split
59 from IPython.utils.process import arg_split
58 from IPython.utils.text import dedent
60 from IPython.utils.text import dedent
59
61
62 NAME_RE = re.compile(r"[a-zA-Z][a-zA-Z0-9_-]*$")
63
64 @undoc
60 class MagicHelpFormatter(argparse.RawDescriptionHelpFormatter):
65 class MagicHelpFormatter(argparse.RawDescriptionHelpFormatter):
61 """ A HelpFormatter which dedents but otherwise preserves indentation.
66 """A HelpFormatter with a couple of changes to meet our needs.
62 """
67 """
68 # Modified to dedent text.
63 def _fill_text(self, text, width, indent):
69 def _fill_text(self, text, width, indent):
64 return argparse.RawDescriptionHelpFormatter._fill_text(self, dedent(text), width, indent)
70 return argparse.RawDescriptionHelpFormatter._fill_text(self, dedent(text), width, indent)
65
71
72 # Modified to wrap argument placeholders in <> where necessary.
73 def _format_action_invocation(self, action):
74 if not action.option_strings:
75 metavar, = self._metavar_formatter(action, action.dest)(1)
76 return metavar
77
78 else:
79 parts = []
80
81 # if the Optional doesn't take a value, format is:
82 # -s, --long
83 if action.nargs == 0:
84 parts.extend(action.option_strings)
85
86 # if the Optional takes a value, format is:
87 # -s ARGS, --long ARGS
88 else:
89 default = action.dest.upper()
90 args_string = self._format_args(action, default)
91 # IPYTHON MODIFICATION: If args_string is not a plain name, wrap
92 # it in <> so it's valid RST.
93 if not NAME_RE.match(args_string):
94 args_string = "<%s>" % args_string
95 for option_string in action.option_strings:
96 parts.append('%s %s' % (option_string, args_string))
97
98 return ', '.join(parts)
99
100 # Override the default prefix ('usage') to our % magic escape,
101 # in a code block.
102 def add_usage(self, usage, actions, groups, prefix="::\n\n %"):
103 super(MagicHelpFormatter, self).add_usage(usage, actions, groups, prefix)
104
66 class MagicArgumentParser(argparse.ArgumentParser):
105 class MagicArgumentParser(argparse.ArgumentParser):
67 """ An ArgumentParser tweaked for use by IPython magics.
106 """ An ArgumentParser tweaked for use by IPython magics.
68 """
107 """
69 def __init__(self,
108 def __init__(self,
70 prog=None,
109 prog=None,
71 usage=None,
110 usage=None,
72 description=None,
111 description=None,
73 epilog=None,
112 epilog=None,
74 parents=None,
113 parents=None,
75 formatter_class=MagicHelpFormatter,
114 formatter_class=MagicHelpFormatter,
76 prefix_chars='-',
115 prefix_chars='-',
77 argument_default=None,
116 argument_default=None,
78 conflict_handler='error',
117 conflict_handler='error',
79 add_help=False):
118 add_help=False):
80 if parents is None:
119 if parents is None:
81 parents = []
120 parents = []
82 super(MagicArgumentParser, self).__init__(prog=prog, usage=usage,
121 super(MagicArgumentParser, self).__init__(prog=prog, usage=usage,
83 description=description, epilog=epilog,
122 description=description, epilog=epilog,
84 parents=parents, formatter_class=formatter_class,
123 parents=parents, formatter_class=formatter_class,
85 prefix_chars=prefix_chars, argument_default=argument_default,
124 prefix_chars=prefix_chars, argument_default=argument_default,
86 conflict_handler=conflict_handler, add_help=add_help)
125 conflict_handler=conflict_handler, add_help=add_help)
87
126
88 def error(self, message):
127 def error(self, message):
89 """ Raise a catchable error instead of exiting.
128 """ Raise a catchable error instead of exiting.
90 """
129 """
91 raise UsageError(message)
130 raise UsageError(message)
92
131
93 def parse_argstring(self, argstring):
132 def parse_argstring(self, argstring):
94 """ Split a string into an argument list and parse that argument list.
133 """ Split a string into an argument list and parse that argument list.
95 """
134 """
96 argv = arg_split(argstring)
135 argv = arg_split(argstring)
97 return self.parse_args(argv)
136 return self.parse_args(argv)
98
137
99
138
100 def construct_parser(magic_func):
139 def construct_parser(magic_func):
101 """ Construct an argument parser using the function decorations.
140 """ Construct an argument parser using the function decorations.
102 """
141 """
103 kwds = getattr(magic_func, 'argcmd_kwds', {})
142 kwds = getattr(magic_func, 'argcmd_kwds', {})
104 if 'description' not in kwds:
143 if 'description' not in kwds:
105 kwds['description'] = getattr(magic_func, '__doc__', None)
144 kwds['description'] = getattr(magic_func, '__doc__', None)
106 arg_name = real_name(magic_func)
145 arg_name = real_name(magic_func)
107 parser = MagicArgumentParser(arg_name, **kwds)
146 parser = MagicArgumentParser(arg_name, **kwds)
108 # Reverse the list of decorators in order to apply them in the
147 # Reverse the list of decorators in order to apply them in the
109 # order in which they appear in the source.
148 # order in which they appear in the source.
110 group = None
149 group = None
111 for deco in magic_func.decorators[::-1]:
150 for deco in magic_func.decorators[::-1]:
112 result = deco.add_to_parser(parser, group)
151 result = deco.add_to_parser(parser, group)
113 if result is not None:
152 if result is not None:
114 group = result
153 group = result
115
154
116 # Replace the starting 'usage: ' with IPython's %.
117 help_text = parser.format_help()
118 if help_text.startswith('usage: '):
119 help_text = help_text.replace('usage: ', '%', 1)
120 else:
121 help_text = '%' + help_text
122
123 # Replace the magic function's docstring with the full help text.
155 # Replace the magic function's docstring with the full help text.
124 magic_func.__doc__ = help_text
156 magic_func.__doc__ = parser.format_help()
125
157
126 return parser
158 return parser
127
159
128
160
129 def parse_argstring(magic_func, argstring):
161 def parse_argstring(magic_func, argstring):
130 """ Parse the string of arguments for the given magic function.
162 """ Parse the string of arguments for the given magic function.
131 """
163 """
132 return magic_func.parser.parse_argstring(argstring)
164 return magic_func.parser.parse_argstring(argstring)
133
165
134
166
135 def real_name(magic_func):
167 def real_name(magic_func):
136 """ Find the real name of the magic.
168 """ Find the real name of the magic.
137 """
169 """
138 magic_name = magic_func.__name__
170 magic_name = magic_func.__name__
139 if magic_name.startswith('magic_'):
171 if magic_name.startswith('magic_'):
140 magic_name = magic_name[len('magic_'):]
172 magic_name = magic_name[len('magic_'):]
141 return getattr(magic_func, 'argcmd_name', magic_name)
173 return getattr(magic_func, 'argcmd_name', magic_name)
142
174
143
175
144 class ArgDecorator(object):
176 class ArgDecorator(object):
145 """ Base class for decorators to add ArgumentParser information to a method.
177 """ Base class for decorators to add ArgumentParser information to a method.
146 """
178 """
147
179
148 def __call__(self, func):
180 def __call__(self, func):
149 if not getattr(func, 'has_arguments', False):
181 if not getattr(func, 'has_arguments', False):
150 func.has_arguments = True
182 func.has_arguments = True
151 func.decorators = []
183 func.decorators = []
152 func.decorators.append(self)
184 func.decorators.append(self)
153 return func
185 return func
154
186
155 def add_to_parser(self, parser, group):
187 def add_to_parser(self, parser, group):
156 """ Add this object's information to the parser, if necessary.
188 """ Add this object's information to the parser, if necessary.
157 """
189 """
158 pass
190 pass
159
191
160
192
161 class magic_arguments(ArgDecorator):
193 class magic_arguments(ArgDecorator):
162 """ Mark the magic as having argparse arguments and possibly adjust the
194 """ Mark the magic as having argparse arguments and possibly adjust the
163 name.
195 name.
164 """
196 """
165
197
166 def __init__(self, name=None):
198 def __init__(self, name=None):
167 self.name = name
199 self.name = name
168
200
169 def __call__(self, func):
201 def __call__(self, func):
170 if not getattr(func, 'has_arguments', False):
202 if not getattr(func, 'has_arguments', False):
171 func.has_arguments = True
203 func.has_arguments = True
172 func.decorators = []
204 func.decorators = []
173 if self.name is not None:
205 if self.name is not None:
174 func.argcmd_name = self.name
206 func.argcmd_name = self.name
175 # This should be the first decorator in the list of decorators, thus the
207 # This should be the first decorator in the list of decorators, thus the
176 # last to execute. Build the parser.
208 # last to execute. Build the parser.
177 func.parser = construct_parser(func)
209 func.parser = construct_parser(func)
178 return func
210 return func
179
211
180
212
181 class ArgMethodWrapper(ArgDecorator):
213 class ArgMethodWrapper(ArgDecorator):
182
214
183 """
215 """
184 Base class to define a wrapper for ArgumentParser method.
216 Base class to define a wrapper for ArgumentParser method.
185
217
186 Child class must define either `_method_name` or `add_to_parser`.
218 Child class must define either `_method_name` or `add_to_parser`.
187
219
188 """
220 """
189
221
190 _method_name = None
222 _method_name = None
191
223
192 def __init__(self, *args, **kwds):
224 def __init__(self, *args, **kwds):
193 self.args = args
225 self.args = args
194 self.kwds = kwds
226 self.kwds = kwds
195
227
196 def add_to_parser(self, parser, group):
228 def add_to_parser(self, parser, group):
197 """ Add this object's information to the parser.
229 """ Add this object's information to the parser.
198 """
230 """
199 if group is not None:
231 if group is not None:
200 parser = group
232 parser = group
201 getattr(parser, self._method_name)(*self.args, **self.kwds)
233 getattr(parser, self._method_name)(*self.args, **self.kwds)
202 return None
234 return None
203
235
204
236
205 class argument(ArgMethodWrapper):
237 class argument(ArgMethodWrapper):
206 """ Store arguments and keywords to pass to add_argument().
238 """ Store arguments and keywords to pass to add_argument().
207
239
208 Instances also serve to decorate command methods.
240 Instances also serve to decorate command methods.
209 """
241 """
210 _method_name = 'add_argument'
242 _method_name = 'add_argument'
211
243
212
244
213 class defaults(ArgMethodWrapper):
245 class defaults(ArgMethodWrapper):
214 """ Store arguments and keywords to pass to set_defaults().
246 """ Store arguments and keywords to pass to set_defaults().
215
247
216 Instances also serve to decorate command methods.
248 Instances also serve to decorate command methods.
217 """
249 """
218 _method_name = 'set_defaults'
250 _method_name = 'set_defaults'
219
251
220
252
221 class argument_group(ArgMethodWrapper):
253 class argument_group(ArgMethodWrapper):
222 """ Store arguments and keywords to pass to add_argument_group().
254 """ Store arguments and keywords to pass to add_argument_group().
223
255
224 Instances also serve to decorate command methods.
256 Instances also serve to decorate command methods.
225 """
257 """
226
258
227 def add_to_parser(self, parser, group):
259 def add_to_parser(self, parser, group):
228 """ Add this object's information to the parser.
260 """ Add this object's information to the parser.
229 """
261 """
230 return parser.add_argument_group(*self.args, **self.kwds)
262 return parser.add_argument_group(*self.args, **self.kwds)
231
263
232
264
233 class kwds(ArgDecorator):
265 class kwds(ArgDecorator):
234 """ Provide other keywords to the sub-parser constructor.
266 """ Provide other keywords to the sub-parser constructor.
235 """
267 """
236 def __init__(self, **kwds):
268 def __init__(self, **kwds):
237 self.kwds = kwds
269 self.kwds = kwds
238
270
239 def __call__(self, func):
271 def __call__(self, func):
240 func = super(kwds, self).__call__(func)
272 func = super(kwds, self).__call__(func)
241 func.argcmd_kwds = self.kwds
273 func.argcmd_kwds = self.kwds
242 return func
274 return func
243
275
244
276
245 __all__ = ['magic_arguments', 'argument', 'argument_group', 'kwds',
277 __all__ = ['magic_arguments', 'argument', 'argument_group', 'kwds',
246 'parse_argstring']
278 'parse_argstring']
General Comments 0
You need to be logged in to leave comments. Login now