##// END OF EJS Templates
remove blank lines
Jan-Hendrik Müller -
Show More
@@ -1,312 +1,310 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
41 40 Here is an elaborated example that uses default parameters in `argument` and calls the `args` in the cell magic::
42 41
43 42 from IPython.core.magic import register_cell_magic
44 43 from IPython.core.magic_arguments import (argument, magic_arguments,
45 44 parse_argstring)
46 45
47 46
48 47 @magic_arguments()
49 48 @argument(
50 49 "--option",
51 50 "-o",
52 51 help=("Add an option here"),
53 52 )
54 53 @argument(
55 54 "--style",
56 55 "-s",
57 56 default="foo",
58 57 help=("Add some style arguments"),
59 58 )
60 59 @register_cell_magic
61 60 def my_cell_magic(line, cell):
62 61 args = parse_argstring(my_cell_magic, line)
63 62 print(f"{args.option=}")
64 63 print(f"{args.style=}")
65 64 print(f"{cell=}")
66 65
67 66 In a jupyter notebook, this cell magic can be executed like this::
68 67
69 68 %%my_cell_magic -o Hello
70 69 print("bar")
71 70 i = 42
72 71
73
74 72 Inheritance diagram:
75 73
76 74 .. inheritance-diagram:: IPython.core.magic_arguments
77 75 :parts: 3
78 76
79 77 '''
80 78 #-----------------------------------------------------------------------------
81 79 # Copyright (C) 2010-2011, IPython Development Team.
82 80 #
83 81 # Distributed under the terms of the Modified BSD License.
84 82 #
85 83 # The full license is in the file COPYING.txt, distributed with this software.
86 84 #-----------------------------------------------------------------------------
87 85 import argparse
88 86 import re
89 87
90 88 # Our own imports
91 89 from IPython.core.error import UsageError
92 90 from IPython.utils.decorators import undoc
93 91 from IPython.utils.process import arg_split
94 92 from IPython.utils.text import dedent
95 93
96 94 NAME_RE = re.compile(r"[a-zA-Z][a-zA-Z0-9_-]*$")
97 95
98 96 @undoc
99 97 class MagicHelpFormatter(argparse.RawDescriptionHelpFormatter):
100 98 """A HelpFormatter with a couple of changes to meet our needs.
101 99 """
102 100 # Modified to dedent text.
103 101 def _fill_text(self, text, width, indent):
104 102 return argparse.RawDescriptionHelpFormatter._fill_text(self, dedent(text), width, indent)
105 103
106 104 # Modified to wrap argument placeholders in <> where necessary.
107 105 def _format_action_invocation(self, action):
108 106 if not action.option_strings:
109 107 metavar, = self._metavar_formatter(action, action.dest)(1)
110 108 return metavar
111 109
112 110 else:
113 111 parts = []
114 112
115 113 # if the Optional doesn't take a value, format is:
116 114 # -s, --long
117 115 if action.nargs == 0:
118 116 parts.extend(action.option_strings)
119 117
120 118 # if the Optional takes a value, format is:
121 119 # -s ARGS, --long ARGS
122 120 else:
123 121 default = action.dest.upper()
124 122 args_string = self._format_args(action, default)
125 123 # IPYTHON MODIFICATION: If args_string is not a plain name, wrap
126 124 # it in <> so it's valid RST.
127 125 if not NAME_RE.match(args_string):
128 126 args_string = "<%s>" % args_string
129 127 for option_string in action.option_strings:
130 128 parts.append('%s %s' % (option_string, args_string))
131 129
132 130 return ', '.join(parts)
133 131
134 132 # Override the default prefix ('usage') to our % magic escape,
135 133 # in a code block.
136 134 def add_usage(self, usage, actions, groups, prefix="::\n\n %"):
137 135 super(MagicHelpFormatter, self).add_usage(usage, actions, groups, prefix)
138 136
139 137 class MagicArgumentParser(argparse.ArgumentParser):
140 138 """ An ArgumentParser tweaked for use by IPython magics.
141 139 """
142 140 def __init__(self,
143 141 prog=None,
144 142 usage=None,
145 143 description=None,
146 144 epilog=None,
147 145 parents=None,
148 146 formatter_class=MagicHelpFormatter,
149 147 prefix_chars='-',
150 148 argument_default=None,
151 149 conflict_handler='error',
152 150 add_help=False):
153 151 if parents is None:
154 152 parents = []
155 153 super(MagicArgumentParser, self).__init__(prog=prog, usage=usage,
156 154 description=description, epilog=epilog,
157 155 parents=parents, formatter_class=formatter_class,
158 156 prefix_chars=prefix_chars, argument_default=argument_default,
159 157 conflict_handler=conflict_handler, add_help=add_help)
160 158
161 159 def error(self, message):
162 160 """ Raise a catchable error instead of exiting.
163 161 """
164 162 raise UsageError(message)
165 163
166 164 def parse_argstring(self, argstring):
167 165 """ Split a string into an argument list and parse that argument list.
168 166 """
169 167 argv = arg_split(argstring)
170 168 return self.parse_args(argv)
171 169
172 170
173 171 def construct_parser(magic_func):
174 172 """ Construct an argument parser using the function decorations.
175 173 """
176 174 kwds = getattr(magic_func, 'argcmd_kwds', {})
177 175 if 'description' not in kwds:
178 176 kwds['description'] = getattr(magic_func, '__doc__', None)
179 177 arg_name = real_name(magic_func)
180 178 parser = MagicArgumentParser(arg_name, **kwds)
181 179 # Reverse the list of decorators in order to apply them in the
182 180 # order in which they appear in the source.
183 181 group = None
184 182 for deco in magic_func.decorators[::-1]:
185 183 result = deco.add_to_parser(parser, group)
186 184 if result is not None:
187 185 group = result
188 186
189 187 # Replace the magic function's docstring with the full help text.
190 188 magic_func.__doc__ = parser.format_help()
191 189
192 190 return parser
193 191
194 192
195 193 def parse_argstring(magic_func, argstring):
196 194 """ Parse the string of arguments for the given magic function.
197 195 """
198 196 return magic_func.parser.parse_argstring(argstring)
199 197
200 198
201 199 def real_name(magic_func):
202 200 """ Find the real name of the magic.
203 201 """
204 202 magic_name = magic_func.__name__
205 203 if magic_name.startswith('magic_'):
206 204 magic_name = magic_name[len('magic_'):]
207 205 return getattr(magic_func, 'argcmd_name', magic_name)
208 206
209 207
210 208 class ArgDecorator(object):
211 209 """ Base class for decorators to add ArgumentParser information to a method.
212 210 """
213 211
214 212 def __call__(self, func):
215 213 if not getattr(func, 'has_arguments', False):
216 214 func.has_arguments = True
217 215 func.decorators = []
218 216 func.decorators.append(self)
219 217 return func
220 218
221 219 def add_to_parser(self, parser, group):
222 220 """ Add this object's information to the parser, if necessary.
223 221 """
224 222 pass
225 223
226 224
227 225 class magic_arguments(ArgDecorator):
228 226 """ Mark the magic as having argparse arguments and possibly adjust the
229 227 name.
230 228 """
231 229
232 230 def __init__(self, name=None):
233 231 self.name = name
234 232
235 233 def __call__(self, func):
236 234 if not getattr(func, 'has_arguments', False):
237 235 func.has_arguments = True
238 236 func.decorators = []
239 237 if self.name is not None:
240 238 func.argcmd_name = self.name
241 239 # This should be the first decorator in the list of decorators, thus the
242 240 # last to execute. Build the parser.
243 241 func.parser = construct_parser(func)
244 242 return func
245 243
246 244
247 245 class ArgMethodWrapper(ArgDecorator):
248 246
249 247 """
250 248 Base class to define a wrapper for ArgumentParser method.
251 249
252 250 Child class must define either `_method_name` or `add_to_parser`.
253 251
254 252 """
255 253
256 254 _method_name = None
257 255
258 256 def __init__(self, *args, **kwds):
259 257 self.args = args
260 258 self.kwds = kwds
261 259
262 260 def add_to_parser(self, parser, group):
263 261 """ Add this object's information to the parser.
264 262 """
265 263 if group is not None:
266 264 parser = group
267 265 getattr(parser, self._method_name)(*self.args, **self.kwds)
268 266 return None
269 267
270 268
271 269 class argument(ArgMethodWrapper):
272 270 """ Store arguments and keywords to pass to add_argument().
273 271
274 272 Instances also serve to decorate command methods.
275 273 """
276 274 _method_name = 'add_argument'
277 275
278 276
279 277 class defaults(ArgMethodWrapper):
280 278 """ Store arguments and keywords to pass to set_defaults().
281 279
282 280 Instances also serve to decorate command methods.
283 281 """
284 282 _method_name = 'set_defaults'
285 283
286 284
287 285 class argument_group(ArgMethodWrapper):
288 286 """ Store arguments and keywords to pass to add_argument_group().
289 287
290 288 Instances also serve to decorate command methods.
291 289 """
292 290
293 291 def add_to_parser(self, parser, group):
294 292 """ Add this object's information to the parser.
295 293 """
296 294 return parser.add_argument_group(*self.args, **self.kwds)
297 295
298 296
299 297 class kwds(ArgDecorator):
300 298 """ Provide other keywords to the sub-parser constructor.
301 299 """
302 300 def __init__(self, **kwds):
303 301 self.kwds = kwds
304 302
305 303 def __call__(self, func):
306 304 func = super(kwds, self).__call__(func)
307 305 func.argcmd_kwds = self.kwds
308 306 return func
309 307
310 308
311 309 __all__ = ['magic_arguments', 'argument', 'argument_group', 'kwds',
312 310 'parse_argstring']
General Comments 0
You need to be logged in to leave comments. Login now