comparison frontends/src/jp/base.py @ 1950:227a4e617549

jp: --output option: - new --output option can be added wich use_output. use_output can be True (in which case it wild default to C.OUTPUT_TEXT), or any of C.OUTPUT_TYPES (currently text, list and dict) - output change the output format mainly to make command result parsing more easy, but it can also be use to add fancy effects (like coloration) - outputs are added with plugins in the same way as commands (import of both is done in the same method) - in the command class, output need to be declared with use_output=C.OUTPUT_xxx, then the data only need to be processed with self.output(data) - outputs can have description (not used yet) - use_xxx argument handling has been refactored (minor refactoring) to be more generic - first outputs are "default", "json" (pretty printed) and "json_raw" (compact json) - the first command to use them is "profile list"
author Goffi <goffi@goffi.org>
date Sat, 23 Apr 2016 23:10:03 +0200
parents 2c75011d7b2d
children 4633cfcbcccb
comparison
equal deleted inserted replaced
1949:c5fd304d0976 1950:227a4e617549
37 from sat.core import exceptions 37 from sat.core import exceptions
38 import sat_frontends.jp 38 import sat_frontends.jp
39 from sat_frontends.jp.constants import Const as C 39 from sat_frontends.jp.constants import Const as C
40 import xml.etree.ElementTree as ET # FIXME: used temporarily to manage XMLUI 40 import xml.etree.ElementTree as ET # FIXME: used temporarily to manage XMLUI
41 import shlex 41 import shlex
42 from collections import OrderedDict
42 43
43 if sys.version_info < (2, 7, 3): 44 if sys.version_info < (2, 7, 3):
44 # XXX: shlex.split only handle unicode since python 2.7.3 45 # XXX: shlex.split only handle unicode since python 2.7.3
45 # this is a workaround for older versions 46 # this is a workaround for older versions
46 old_split = shlex.split 47 old_split = shlex.split
114 115
115 # progress attributes 116 # progress attributes
116 self._progress_id = None # TODO: manage several progress ids 117 self._progress_id = None # TODO: manage several progress ids
117 self.quit_on_progress_end = True 118 self.quit_on_progress_end = True
118 119
120 # outputs
121 self._outputs = {}
122 for type_ in C.OUTPUT_TYPES:
123 self._outputs[type_] = OrderedDict()
124
119 @property 125 @property
120 def version(self): 126 def version(self):
121 return self.bridge.getVersion() 127 return self.bridge.getVersion()
122 128
123 @property 129 @property
177 if error: 183 if error:
178 print >>sys.stderr,msg 184 print >>sys.stderr,msg
179 else: 185 else:
180 print msg 186 print msg
181 187
188 def output(self, type_, name, data):
189 self._outputs[type_][name]['callback'](data)
190
182 def addOnQuitCallback(self, callback, *args, **kwargs): 191 def addOnQuitCallback(self, callback, *args, **kwargs):
183 """Add a callback which will be called on quit command 192 """Add a callback which will be called on quit command
184 193
185 @param callback(callback): method to call 194 @param callback(callback): method to call
186 """ 195 """
188 callbacks_list = self._onQuitCallbacks 197 callbacks_list = self._onQuitCallbacks
189 except AttributeError: 198 except AttributeError:
190 callbacks_list = self._onQuitCallbacks = [] 199 callbacks_list = self._onQuitCallbacks = []
191 finally: 200 finally:
192 callbacks_list.append((callback, args, kwargs)) 201 callbacks_list.append((callback, args, kwargs))
202
203 def getOutputChoices(self, output_type):
204 """Return valid output filters for output_type
205
206 @param output_type: True for default,
207 else can be any registered type
208 """
209 return self._outputs[output_type].keys()
193 210
194 def _make_parents(self): 211 def _make_parents(self):
195 self.parents = {} 212 self.parents = {}
196 213
197 # we have a special case here as the start-session option is present only if connection is not needed, 214 # we have a special case here as the start-session option is present only if connection is not needed,
218 verbose_parent.add_argument('--verbose', '-v', action='count', default=0, help=_(u"Add a verbosity level (can be used multiple times)")) 235 verbose_parent.add_argument('--verbose', '-v', action='count', default=0, help=_(u"Add a verbosity level (can be used multiple times)"))
219 236
220 def add_parser_options(self): 237 def add_parser_options(self):
221 self.parser.add_argument('--version', action='version', version=("%(name)s %(version)s %(copyleft)s" % {'name': PROG_NAME, 'version': self.version, 'copyleft': COPYLEFT})) 238 self.parser.add_argument('--version', action='version', version=("%(name)s %(version)s %(copyleft)s" % {'name': PROG_NAME, 'version': self.version, 'copyleft': COPYLEFT}))
222 239
223 def import_commands(self): 240 def register_output(self, type_, name, callback, description=""):
224 """ Automaticaly import commands to jp 241 if type_ not in C.OUTPUT_TYPES:
242 log.error(u"Invalid output type {}".format(type_))
243 return
244 self._outputs[type_][name] = {'callback': callback,
245 'description': description
246 }
247
248 def import_plugins(self):
249 """Automaticaly import commands and outputs in jp
250
225 looks from modules names cmd_*.py in jp path and import them 251 looks from modules names cmd_*.py in jp path and import them
226
227 """ 252 """
228 path = os.path.dirname(sat_frontends.jp.__file__) 253 path = os.path.dirname(sat_frontends.jp.__file__)
229 modules = (os.path.splitext(module)[0] for module in map(os.path.basename, iglob(os.path.join(path, "cmd_*.py")))) 254 # XXX: outputs must be imported before commands as they are used for arguments
230 for module_name in modules: 255 for type_, pattern in ((C.PLUGIN_OUTPUT, 'output_*.py'), (C.PLUGIN_CMD, 'cmd_*.py')):
231 module = import_module("sat_frontends.jp."+module_name) 256 modules = (os.path.splitext(module)[0] for module in map(os.path.basename, iglob(os.path.join(path, pattern))))
232 try: 257 for module_name in modules:
233 self.import_command_module(module) 258 module = import_module("sat_frontends.jp."+module_name)
234 except ImportError: 259 try:
235 continue 260 self.import_plugin_module(module, type_)
236 261 except ImportError:
237 def import_command_module(self, module): 262 continue
238 """ Add commands from a module to jp 263
239 @param module: module containing commands 264 def import_plugin_module(self, module, type_):
240 265 """add commands or outpus from a module to jp
241 """ 266
242 try: 267 @param module: module containing commands or outputs
243 for classname in module.__commands__: 268 @param type_(str): one of C_PLUGIN_*
244 cls = getattr(module, classname) 269 """
245 except AttributeError: 270 try:
246 log.warning(_("Invalid module %s") % module) 271 class_names = getattr(module, '__{}__'.format(type_))
272 except AttributeError:
273 log.warning(_("Invalid plugin module [{type}] {module}").format(type=type_, module=module))
247 raise ImportError 274 raise ImportError
248 cls(self) 275 else:
276 for class_name in class_names:
277 cls = getattr(module, class_name)
278 cls(self)
249 279
250 def run(self, args=None): 280 def run(self, args=None):
251 self.args = self.parser.parse_args(args) 281 self.args = self.parser.parse_args(args)
252 try: 282 try:
253 self.args.func() 283 self.args.func()
403 return param_jid 433 return param_jid
404 434
405 435
406 class CommandBase(object): 436 class CommandBase(object):
407 437
408 def __init__(self, host, name, use_profile=True, use_progress=False, use_verbose=False, need_connect=None, help=None, **kwargs): 438 def __init__(self, host, name, use_profile=True, use_output=False,
439 need_connect=None, help=None, **kwargs):
409 """ Initialise CommandBase 440 """ Initialise CommandBase
410 @param host: Jp instance 441 @param host: Jp instance
411 @param name(unicode): name of the new command 442 @param name(unicode): name of the new command
412 @param use_profile(bool): if True, add profile selection/connection commands 443 @param use_profile(bool): if True, add profile selection/connection commands
413 @param use_progress(bool): if True, add progress bar activation commands 444 @param use_output(bool, dict): if not False, add --output option
414 progress* signals will be handled
415 @param use_verbose(bool): if True, add verbosity command
416 @param need_connect(bool, None): True if profile connection is needed 445 @param need_connect(bool, None): True if profile connection is needed
417 False else (profile session must still be started) 446 False else (profile session must still be started)
418 None to set auto value (i.e. True if use_profile is set) 447 None to set auto value (i.e. True if use_profile is set)
419 Can't be set if use_profile is False 448 Can't be set if use_profile is False
420 @param help(unicode): help message to display 449 @param help(unicode): help message to display
421 @param **kwargs: args passed to ArgumentParser 450 @param **kwargs: args passed to ArgumentParser
451 use_* are handled directly, they can be:
452 - use_progress(bool): if True, add progress bar activation option
453 progress* signals will be handled
454 - use_verbose(bool): if True, add verbosity option
422 @attribute need_loop(bool): to set by commands when loop is needed 455 @attribute need_loop(bool): to set by commands when loop is needed
423 456
424 """ 457 """
425 self.need_loop = False # to be set by commands when loop is needed 458 self.need_loop = False # to be set by commands when loop is needed
426 try: # If we have subcommands, host is a CommandBase and we need to use host.host 459 try: # If we have subcommands, host is a CommandBase and we need to use host.host
427 self.host = host.host 460 self.host = host.host
428 except AttributeError: 461 except AttributeError:
429 self.host = host 462 self.host = host
430 463
464 # --profile option
431 parents = kwargs.setdefault('parents', set()) 465 parents = kwargs.setdefault('parents', set())
432 if use_profile: 466 if use_profile:
433 #self.host.parents['profile'] is an ArgumentParser with profile connection arguments 467 #self.host.parents['profile'] is an ArgumentParser with profile connection arguments
434 if need_connect is None: 468 if need_connect is None:
435 need_connect = True 469 need_connect = True
438 assert need_connect is None 472 assert need_connect is None
439 self.need_connect = need_connect 473 self.need_connect = need_connect
440 # from this point, self.need_connect is None if connection is not needed at all 474 # from this point, self.need_connect is None if connection is not needed at all
441 # False if session starting is needed, and True if full connection is needed 475 # False if session starting is needed, and True if full connection is needed
442 476
443 if use_progress: 477 # --output option
444 parents.add(self.host.parents['progress']) 478 if use_output:
445 479 if use_output == True:
446 if use_verbose: 480 use_output = C.OUTPUT_TEXT
447 parents.add(self.host.parents['verbose']) 481 assert use_output in C.OUTPUT_TYPES
482 self._output_type = use_output
483 output_parent = argparse.ArgumentParser(add_help=False)
484 choices = self.host.getOutputChoices(use_output)
485 if not choices:
486 raise exceptions.InternalError("No choice found for {} output type".format(use_output))
487 default = 'default' if 'default' in choices else choices[0]
488 output_parent.add_argument('--output', '-O', choices=choices, default=default, help=_(u"Select output format"))
489 parents.add(output_parent)
490
491 # other common options
492 use_opts = {k:v for k,v in kwargs.iteritems() if k.startswith('use_')}
493 for param, do_use in use_opts.iteritems():
494 opt=param[4:] # if param is use_verbose, opt is verbose
495 if opt not in self.host.parents:
496 raise exceptions.InternalError(u"Unknown parent option {}".format(opt))
497 del kwargs[param]
498 if do_use:
499 parents.add(self.host.parents[opt])
448 500
449 self.parser = host.subparsers.add_parser(name, help=help, **kwargs) 501 self.parser = host.subparsers.add_parser(name, help=help, **kwargs)
450 if hasattr(self, "subcommands"): 502 if hasattr(self, "subcommands"):
451 self.subparsers = self.parser.add_subparsers() 503 self.subparsers = self.parser.add_subparsers()
452 else: 504 else:
570 self.disp(_(u"Error while doing operation: {}").format(error_msg), error=True) 622 self.disp(_(u"Error while doing operation: {}").format(error_msg), error=True)
571 623
572 def disp(self, msg, verbosity=0, error=False): 624 def disp(self, msg, verbosity=0, error=False):
573 return self.host.disp(msg, verbosity, error) 625 return self.host.disp(msg, verbosity, error)
574 626
627 def output(self, data):
628 return self.host.output(self._output_type, self.args.output, data)
629
575 def add_parser_options(self): 630 def add_parser_options(self):
576 try: 631 try:
577 subcommands = self.subcommands 632 subcommands = self.subcommands
578 except AttributeError: 633 except AttributeError:
579 # We don't have subcommands, the class need to implements add_parser_options 634 # We don't have subcommands, the class need to implements add_parser_options