##// END OF EJS Templates
Add new decorator magic_arguments.defaults
Takafumi Arakaki -
Show More
@@ -1,222 +1,240 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 argument(ArgDecorator):
177 """ Store arguments and keywords to pass to add_argument().
177 """ Store arguments and keywords to pass to add_argument().
178
178
179 Instances also serve to decorate command methods.
179 Instances also serve to decorate command methods.
180 """
180 """
181 def __init__(self, *args, **kwds):
181 def __init__(self, *args, **kwds):
182 self.args = args
182 self.args = args
183 self.kwds = kwds
183 self.kwds = kwds
184
184
185 def add_to_parser(self, parser, group):
185 def add_to_parser(self, parser, group):
186 """ Add this object's information to the parser.
186 """ Add this object's information to the parser.
187 """
187 """
188 if group is not None:
188 if group is not None:
189 parser = group
189 parser = group
190 parser.add_argument(*self.args, **self.kwds)
190 parser.add_argument(*self.args, **self.kwds)
191 return None
191 return None
192
192
193
193
194 class defaults(ArgDecorator):
195 """ Store arguments and keywords to pass to set_defaults().
196
197 Instances also serve to decorate command methods.
198 """
199 def __init__(self, *args, **kwds):
200 self.args = args
201 self.kwds = kwds
202
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
211
194 class argument_group(ArgDecorator):
212 class argument_group(ArgDecorator):
195 """ Store arguments and keywords to pass to add_argument_group().
213 """ Store arguments and keywords to pass to add_argument_group().
196
214
197 Instances also serve to decorate command methods.
215 Instances also serve to decorate command methods.
198 """
216 """
199 def __init__(self, *args, **kwds):
217 def __init__(self, *args, **kwds):
200 self.args = args
218 self.args = args
201 self.kwds = kwds
219 self.kwds = kwds
202
220
203 def add_to_parser(self, parser, group):
221 def add_to_parser(self, parser, group):
204 """ Add this object's information to the parser.
222 """ Add this object's information to the parser.
205 """
223 """
206 return parser.add_argument_group(*self.args, **self.kwds)
224 return parser.add_argument_group(*self.args, **self.kwds)
207
225
208
226
209 class kwds(ArgDecorator):
227 class kwds(ArgDecorator):
210 """ Provide other keywords to the sub-parser constructor.
228 """ Provide other keywords to the sub-parser constructor.
211 """
229 """
212 def __init__(self, **kwds):
230 def __init__(self, **kwds):
213 self.kwds = kwds
231 self.kwds = kwds
214
232
215 def __call__(self, func):
233 def __call__(self, func):
216 func = super(kwds, self).__call__(func)
234 func = super(kwds, self).__call__(func)
217 func.argcmd_kwds = self.kwds
235 func.argcmd_kwds = self.kwds
218 return func
236 return func
219
237
220
238
221 __all__ = ['magic_arguments', 'argument', 'argument_group', 'kwds',
239 __all__ = ['magic_arguments', 'argument', 'argument_group', 'kwds',
222 'parse_argstring']
240 'parse_argstring']
General Comments 0
You need to be logged in to leave comments. Login now