1""" 2Digress's CLI interface. 3""" 4 5import inspect 6import sys 7from optparse import OptionParser 8 9import textwrap 10 11from types import MethodType 12 13from digress import __version__ as version 14 15def dispatchable(func): 16 """ 17 Mark a method as dispatchable. 18 """ 19 func.digress_dispatchable = True 20 return func 21 22class Dispatcher(object): 23 """ 24 Dispatcher for CLI commands. 25 """ 26 def __init__(self, fixture): 27 self.fixture = fixture 28 fixture.dispatcher = self 29 30 def _monkey_print_help(self, optparse, *args, **kwargs): 31 # monkey patches OptionParser._print_help 32 OptionParser.print_help(optparse, *args, **kwargs) 33 34 print >>sys.stderr, "\nAvailable commands:" 35 36 maxlen = max([ len(command_name) for command_name in self.commands ]) 37 38 descwidth = 80 - maxlen - 4 39 40 for command_name, command_meth in self.commands.iteritems(): 41 print >>sys.stderr, " %s %s\n" % ( 42 command_name.ljust(maxlen + 1), 43 ("\n" + (maxlen + 4) * " ").join( 44 textwrap.wrap(" ".join(filter( 45 None, 46 command_meth.__doc__.strip().replace("\n", " ").split(" ") 47 )), 48 descwidth 49 ) 50 ) 51 ) 52 53 def _enable_flush(self): 54 self.fixture.flush_before = True 55 56 def _populate_parser(self): 57 self.commands = self._get_commands() 58 59 self.optparse = OptionParser( 60 usage = "usage: %prog [options] command [args]", 61 description = "Digress CLI frontend for %s." % self.fixture.__class__.__name__, 62 version = "Digress %s" % version 63 ) 64 65 self.optparse.print_help = MethodType(self._monkey_print_help, self.optparse, OptionParser) 66 67 self.optparse.add_option( 68 "-f", 69 "--flush", 70 action="callback", 71 callback=lambda option, opt, value, parser: self._enable_flush(), 72 help="flush existing data for a revision before testing" 73 ) 74 75 self.optparse.add_option( 76 "-c", 77 "--cases", 78 metavar="FOO,BAR", 79 action="callback", 80 dest="cases", 81 type=str, 82 callback=lambda option, opt, value, parser: self._select_cases(*value.split(",")), 83 help="test cases to run, run with command list to see full list" 84 ) 85 86 def _select_cases(self, *cases): 87 self.fixture.cases = filter(lambda case: case.__name__ in cases, self.fixture.cases) 88 89 def _get_commands(self): 90 commands = {} 91 92 for name, member in inspect.getmembers(self.fixture): 93 if hasattr(member, "digress_dispatchable"): 94 commands[name] = member 95 96 return commands 97 98 def _run_command(self, name, *args): 99 if name not in self.commands: 100 print >>sys.stderr, "error: %s is not a valid command\n" % name 101 self.optparse.print_help() 102 return 103 104 command = self.commands[name] 105 106 argspec = inspect.getargspec(command) 107 108 max_arg_len = len(argspec.args) - 1 109 min_arg_len = max_arg_len - ((argspec.defaults is not None) and len(argspec.defaults) or 0) 110 111 if len(args) < min_arg_len: 112 print >>sys.stderr, "error: %s takes at least %d arguments\n" % ( 113 name, 114 min_arg_len 115 ) 116 print >>sys.stderr, "%s\n" % command.__doc__ 117 self.optparse.print_help() 118 return 119 120 if len(args) > max_arg_len: 121 print >>sys.stderr, "error: %s takes at most %d arguments\n" % ( 122 name, 123 max_arg_len 124 ) 125 print >>sys.stderr, "%s\n" % command.__doc__ 126 self.optparse.print_help() 127 return 128 129 command(*args) 130 131 def pre_dispatch(self): 132 pass 133 134 def dispatch(self): 135 self._populate_parser() 136 137 self.optparse.parse_args() 138 self.pre_dispatch() 139 args = self.optparse.parse_args()[1] # arguments may require reparsing after pre_dispatch; see test_x264.py 140 141 if len(args) == 0: 142 print >>sys.stderr, "error: no comamnd specified\n" 143 self.optparse.print_help() 144 return 145 146 command = args[0] 147 addenda = args[1:] 148 149 self._run_command(command, *addenda) 150