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