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