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