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