##// END OF EJS Templates
Refactor magic_arguments.py
Takafumi Arakaki -
Show More
@@ -1,240 +1,241 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 '''
41 41 #-----------------------------------------------------------------------------
42 42 # Copyright (C) 2010-2011, IPython Development Team.
43 43 #
44 44 # Distributed under the terms of the Modified BSD License.
45 45 #
46 46 # The full license is in the file COPYING.txt, distributed with this software.
47 47 #-----------------------------------------------------------------------------
48 48
49 49 # Our own imports
50 50 from IPython.external import argparse
51 51 from IPython.core.error import UsageError
52 52 from IPython.utils.process import arg_split
53 53 from IPython.utils.text import dedent
54 54
55 55 class MagicHelpFormatter(argparse.RawDescriptionHelpFormatter):
56 56 """ A HelpFormatter which dedents but otherwise preserves indentation.
57 57 """
58 58 def _fill_text(self, text, width, indent):
59 59 return argparse.RawDescriptionHelpFormatter._fill_text(self, dedent(text), width, indent)
60 60
61 61 class MagicArgumentParser(argparse.ArgumentParser):
62 62 """ An ArgumentParser tweaked for use by IPython magics.
63 63 """
64 64 def __init__(self,
65 65 prog=None,
66 66 usage=None,
67 67 description=None,
68 68 epilog=None,
69 69 parents=None,
70 70 formatter_class=MagicHelpFormatter,
71 71 prefix_chars='-',
72 72 argument_default=None,
73 73 conflict_handler='error',
74 74 add_help=False):
75 75 if parents is None:
76 76 parents = []
77 77 super(MagicArgumentParser, self).__init__(prog=prog, usage=usage,
78 78 description=description, epilog=epilog,
79 79 parents=parents, formatter_class=formatter_class,
80 80 prefix_chars=prefix_chars, argument_default=argument_default,
81 81 conflict_handler=conflict_handler, add_help=add_help)
82 82
83 83 def error(self, message):
84 84 """ Raise a catchable error instead of exiting.
85 85 """
86 86 raise UsageError(message)
87 87
88 88 def parse_argstring(self, argstring):
89 89 """ Split a string into an argument list and parse that argument list.
90 90 """
91 91 argv = arg_split(argstring)
92 92 return self.parse_args(argv)
93 93
94 94
95 95 def construct_parser(magic_func):
96 96 """ Construct an argument parser using the function decorations.
97 97 """
98 98 kwds = getattr(magic_func, 'argcmd_kwds', {})
99 99 if 'description' not in kwds:
100 100 kwds['description'] = getattr(magic_func, '__doc__', None)
101 101 arg_name = real_name(magic_func)
102 102 parser = MagicArgumentParser(arg_name, **kwds)
103 103 # Reverse the list of decorators in order to apply them in the
104 104 # order in which they appear in the source.
105 105 group = None
106 106 for deco in magic_func.decorators[::-1]:
107 107 result = deco.add_to_parser(parser, group)
108 108 if result is not None:
109 109 group = result
110 110
111 111 # Replace the starting 'usage: ' with IPython's %.
112 112 help_text = parser.format_help()
113 113 if help_text.startswith('usage: '):
114 114 help_text = help_text.replace('usage: ', '%', 1)
115 115 else:
116 116 help_text = '%' + help_text
117 117
118 118 # Replace the magic function's docstring with the full help text.
119 119 magic_func.__doc__ = help_text
120 120
121 121 return parser
122 122
123 123
124 124 def parse_argstring(magic_func, argstring):
125 125 """ Parse the string of arguments for the given magic function.
126 126 """
127 127 return magic_func.parser.parse_argstring(argstring)
128 128
129 129
130 130 def real_name(magic_func):
131 131 """ Find the real name of the magic.
132 132 """
133 133 magic_name = magic_func.__name__
134 134 if magic_name.startswith('magic_'):
135 135 magic_name = magic_name[len('magic_'):]
136 136 return getattr(magic_func, 'argcmd_name', magic_name)
137 137
138 138
139 139 class ArgDecorator(object):
140 140 """ Base class for decorators to add ArgumentParser information to a method.
141 141 """
142 142
143 143 def __call__(self, func):
144 144 if not getattr(func, 'has_arguments', False):
145 145 func.has_arguments = True
146 146 func.decorators = []
147 147 func.decorators.append(self)
148 148 return func
149 149
150 150 def add_to_parser(self, parser, group):
151 151 """ Add this object's information to the parser, if necessary.
152 152 """
153 153 pass
154 154
155 155
156 156 class magic_arguments(ArgDecorator):
157 157 """ Mark the magic as having argparse arguments and possibly adjust the
158 158 name.
159 159 """
160 160
161 161 def __init__(self, name=None):
162 162 self.name = name
163 163
164 164 def __call__(self, func):
165 165 if not getattr(func, 'has_arguments', False):
166 166 func.has_arguments = True
167 167 func.decorators = []
168 168 if self.name is not None:
169 169 func.argcmd_name = self.name
170 170 # This should be the first decorator in the list of decorators, thus the
171 171 # last to execute. Build the parser.
172 172 func.parser = construct_parser(func)
173 173 return func
174 174
175 175
176 class argument(ArgDecorator):
177 """ Store arguments and keywords to pass to add_argument().
176 class ArgMethodWrapper(ArgDecorator):
177
178 """
179 Base class to define a wrapper for ArgumentParser method.
180
181 Child class must define either `_method_name` or `add_to_parser`.
178 182
179 Instances also serve to decorate command methods.
180 183 """
184
185 _method_name = None
186
181 187 def __init__(self, *args, **kwds):
182 188 self.args = args
183 189 self.kwds = kwds
184 190
185 191 def add_to_parser(self, parser, group):
186 192 """ Add this object's information to the parser.
187 193 """
188 194 if group is not None:
189 195 parser = group
190 parser.add_argument(*self.args, **self.kwds)
196 getattr(parser, self._method_name)(*self.args, **self.kwds)
191 197 return None
192 198
193 199
194 class defaults(ArgDecorator):
195 """ Store arguments and keywords to pass to set_defaults().
200 class argument(ArgMethodWrapper):
201 """ Store arguments and keywords to pass to add_argument().
196 202
197 203 Instances also serve to decorate command methods.
198 204 """
199 def __init__(self, *args, **kwds):
200 self.args = args
201 self.kwds = kwds
205 _method_name = 'add_argument'
202 206
203 def add_to_parser(self, parser, group):
204 """ Add this object's information to the parser.
207
208 class defaults(ArgMethodWrapper):
209 """ Store arguments and keywords to pass to set_defaults().
210
211 Instances also serve to decorate command methods.
205 212 """
206 if group is not None:
207 parser = group
208 parser.set_defaults(*self.args, **self.kwds)
209 return None
213 _method_name = 'set_defaults'
210 214
211 215
212 class argument_group(ArgDecorator):
216 class argument_group(ArgMethodWrapper):
213 217 """ Store arguments and keywords to pass to add_argument_group().
214 218
215 219 Instances also serve to decorate command methods.
216 220 """
217 def __init__(self, *args, **kwds):
218 self.args = args
219 self.kwds = kwds
220 221
221 222 def add_to_parser(self, parser, group):
222 223 """ Add this object's information to the parser.
223 224 """
224 225 return parser.add_argument_group(*self.args, **self.kwds)
225 226
226 227
227 228 class kwds(ArgDecorator):
228 229 """ Provide other keywords to the sub-parser constructor.
229 230 """
230 231 def __init__(self, **kwds):
231 232 self.kwds = kwds
232 233
233 234 def __call__(self, func):
234 235 func = super(kwds, self).__call__(func)
235 236 func.argcmd_kwds = self.kwds
236 237 return func
237 238
238 239
239 240 __all__ = ['magic_arguments', 'argument', 'argument_group', 'kwds',
240 241 'parse_argstring']
General Comments 0
You need to be logged in to leave comments. Login now