16
16
__all__ = ["Command" , "Shell" , "Server" , "Manager" , "Group" , "Option" ,
17
17
"prompt" , "prompt_pass" , "prompt_bool" , "prompt_choices" ]
18
18
19
+ safe_actions = (argparse ._StoreAction ,
20
+ argparse ._StoreConstAction ,
21
+ argparse ._StoreTrueAction ,
22
+ argparse ._StoreFalseAction ,
23
+ argparse ._AppendAction ,
24
+ argparse ._AppendConstAction ,
25
+ argparse ._CountAction )
26
+
27
+
28
+ try :
29
+ import argcomplete
30
+ ARGCOMPLETE_IMPORTED = True
31
+ except ImportError :
32
+ ARGCOMPLETE_IMPORTED = False
33
+
19
34
20
35
class Manager (object ):
21
36
"""
@@ -44,9 +59,12 @@ def run(self):
44
59
:param app: Flask instance or callable returning a Flask instance.
45
60
:param with_default_commands: load commands **runserver** and **shell**
46
61
by default.
62
+ :param disable_argcomplete: disable automatic loading of argcomplete.
63
+
47
64
"""
48
65
49
- def __init__ (self , app = None , with_default_commands = None , usage = None ):
66
+ def __init__ (self , app = None , with_default_commands = None , usage = None ,
67
+ disable_argcomplete = False ):
50
68
51
69
self .app = app
52
70
@@ -58,7 +76,8 @@ def __init__(self, app=None, with_default_commands=None, usage=None):
58
76
if with_default_commands or (app and with_default_commands is None ):
59
77
self .add_default_commands ()
60
78
61
- self .usage = usage
79
+ self .usage = self .description = usage
80
+ self .disable_argcomplete = disable_argcomplete
62
81
63
82
self .parent = None
64
83
@@ -115,17 +134,42 @@ def create_app(self, **kwargs):
115
134
116
135
return self .app (** kwargs )
117
136
118
- def create_parser (self , prog ):
137
+ def create_parser (self , prog , parents = None ):
119
138
120
139
"""
121
140
Creates an ArgumentParser instance from options returned
122
141
by get_options(), and a subparser for the given command.
123
142
"""
124
-
125
143
prog = os .path .basename (prog )
126
- parser = argparse .ArgumentParser (prog = prog )
144
+
145
+ option_parser = argparse .ArgumentParser (add_help = False )
127
146
for option in self .get_options ():
128
- parser .add_argument (* option .args , ** option .kwargs )
147
+ option_parser .add_argument (* option .args , ** option .kwargs )
148
+
149
+ parser_parents = [option_parser ] if parents is None else parents
150
+
151
+ def _create_command (item ):
152
+ name , command = item
153
+ description = getattr (command , 'description' ,
154
+ command .__doc__ )
155
+ return name , command , description , \
156
+ command .create_parser (name , parents = parser_parents )
157
+
158
+ commands = map (_create_command , self ._commands .iteritems ())
159
+
160
+ parser = argparse .ArgumentParser (prog = prog , usage = self .usage ,
161
+ parents = parser_parents )
162
+
163
+ subparsers = parser .add_subparsers ()
164
+ for name , command , description , parent in commands :
165
+ subparsers .add_parser (name , usage = description , help = description ,
166
+ parents = [parent ], add_help = False )
167
+
168
+ ## enable autocomplete only for parent parser when argcomplete is
169
+ ## imported and it is NOT disabled in constructor
170
+ if parents is None and ARGCOMPLETE_IMPORTED \
171
+ and not self .disable_argcomplete :
172
+ argcomplete .autocomplete (parser , always_complete_options = True )
129
173
130
174
return parser
131
175
@@ -167,6 +211,7 @@ def command(self, func):
167
211
kwargs = dict (zip (* [reversed (l ) for l in (args , defaults )]))
168
212
169
213
for arg in args :
214
+
170
215
if arg in kwargs :
171
216
172
217
default = kwargs [arg ]
@@ -250,78 +295,50 @@ def _make_context(app):
250
295
251
296
return func
252
297
253
- def get_usage (self ):
298
+ def handle (self , prog , args = None ):
254
299
255
- """
256
- Returns string consisting of all commands and their
257
- descriptions.
258
- """
259
- pad = max (map (len , self ._commands .iterkeys ())) + 2
260
- format = ' %%- %ds%%s' % pad
300
+ app_parser = self .create_parser (prog )
261
301
262
- rv = []
263
-
264
- if self .usage :
265
- rv .append (self .usage )
266
-
267
- for name , command in sorted (self ._commands .iteritems ()):
268
- usage = name
269
-
270
- if isinstance (command , Manager ):
271
- description = command .usage or ''
272
- else :
273
- description = command .description or ''
274
-
275
- usage = format % (name , description )
276
- rv .append (usage )
277
-
278
- return "\n " .join (rv )
279
-
280
- def print_usage (self ):
281
-
282
- """
283
- Prints result of get_usage()
284
- """
285
-
286
- print self .get_usage ()
287
-
288
- def handle (self , prog , name , args = None ):
302
+ if args is None or len (args ) == 0 :
303
+ app_parser .print_help ()
304
+ return 2
289
305
290
306
args = list (args or [])
307
+ app_namespace , remaining_args = app_parser .parse_known_args (args )
291
308
292
- try :
293
- command = self . _commands [ name ]
294
- except KeyError :
295
- raise InvalidCommand ( "Command %s not found" % name )
309
+ ## get the handle function and remove it from parsed options
310
+ kwargs = app_namespace . __dict__
311
+ handle = kwargs [ 'func_handle' ]
312
+ del kwargs [ 'func_handle' ]
296
313
297
- if isinstance (command , Manager ):
298
- # Run sub-manager, stripping first argument
299
- sys .argv = sys .argv [1 :]
300
- command .run ()
301
- else :
302
- help_args = ('-h' , '--help' )
314
+ ## get only safe config options
315
+ app_config_keys = [action .dest for action in app_parser ._actions
316
+ if action .__class__ in safe_actions ]
303
317
304
- # remove -h/--help from args if present, and add to remaining args
305
- app_args = [a for a in args if a not in help_args ]
318
+ ## pass only safe app config keys
319
+ app_config = dict ((k , v ) for k , v in kwargs .iteritems ()
320
+ if k in app_config_keys )
306
321
307
- app_parser = self . create_parser ( prog )
308
- app_namespace , remaining_args = app_parser . parse_known_args ( app_args )
309
- app = self . create_app ( ** app_namespace . __dict__ )
322
+ ## remove application config keys from handle kwargs
323
+ kwargs = dict (( k , v ) for k , v in kwargs . iteritems ( )
324
+ if k not in app_config_keys )
310
325
311
- for arg in help_args :
312
- if arg in args :
313
- remaining_args .append (arg )
326
+ ## get command from bounded handle function (py2.7+)
327
+ command = handle .__self__
328
+ if getattr (command , 'capture_all_args' , False ):
329
+ positional_args = [remaining_args ]
330
+ else :
331
+ if len (remaining_args ):
332
+ # raise correct exception
333
+ # FIXME maybe change capture_all_args flag
334
+ app_parser .parse_args (args )
335
+ # sys.exit(2)
336
+ pass
337
+ positional_args = []
314
338
315
- command_parser = command .create_parser (prog + " " + name )
316
- if getattr (command , 'capture_all_args' , False ):
317
- command_namespace , unparsed_args = \
318
- command_parser .parse_known_args (remaining_args )
319
- positional_args = [unparsed_args ]
320
- else :
321
- command_namespace = command_parser .parse_args (remaining_args )
322
- positional_args = []
339
+ app = self .create_app (** app_config )
323
340
324
- return command . handle (app , * positional_args , ** command_namespace . __dict__ )
341
+ return handle (app , * positional_args , ** kwargs )
325
342
326
343
def run (self , commands = None , default_command = None ):
327
344
@@ -339,21 +356,14 @@ def run(self, commands=None, default_command=None):
339
356
if commands :
340
357
self ._commands .update (commands )
341
358
342
- try :
343
- try :
344
- command = sys .argv [1 ]
345
- except IndexError :
346
- command = default_command
359
+ if default_command is not None and len (sys .argv ) == 1 :
360
+ sys .argv .append (default_command )
347
361
348
- if command is None :
349
- raise InvalidCommand ("Please provide a command:" )
350
-
351
- result = self .handle (sys .argv [0 ], command , sys .argv [2 :])
352
-
353
- sys .exit (result or 0 )
354
-
355
- except InvalidCommand , e :
356
- print e
357
- self .print_usage ()
362
+ try :
363
+ result = self .handle (sys .argv [0 ], sys .argv [1 :])
364
+ except SystemExit as e :
365
+ result = e .code
366
+ except Exception as e :
367
+ raise e
358
368
359
- sys .exit (1 )
369
+ sys .exit (result or 0 )
0 commit comments