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