cli.py 4.3 KB

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