| @@ -7,6 +7,7 @@ from collections import namedtuple | |||
| __all__ = [ | |||
| 'command', | |||
| 'subcommand', | |||
| 'param', | |||
| 'Application' | |||
| ] | |||
| @@ -15,33 +16,35 @@ class CommandLine(type): | |||
| """ | |||
| Meta class that enables declarative command definitions | |||
| """ | |||
| def __new__(cls, name, bases, attrs): | |||
| instance = super(CommandLine, cls).__new__(cls, name, bases, attrs) | |||
| def __new__(mcs, name, bases, attrs): | |||
| instance = super(CommandLine, mcs).__new__(mcs, name, bases, attrs) | |||
| subcommands = [] | |||
| main_command = None | |||
| for name, member in attrs.iteritems(): | |||
| if hasattr(member, "command"): | |||
| main_command = member | |||
| # else if member.params: | |||
| # subcommands.append(member) | |||
| parser = None | |||
| elif hasattr(member, "subcommand"): | |||
| subcommands.append(member) | |||
| main_parser = None | |||
| def add_arguments(parser, params): | |||
| """ | |||
| Adds parameters to the parser | |||
| """ | |||
| for parameter in params: | |||
| parser.add_argument(*parameter.args, **parameter.kwargs) | |||
| if main_command: | |||
| parser = ArgumentParser(*main_command.command.args, **main_command.command.kwargs) | |||
| for param in main_command.params: | |||
| parser.add_argument(*param.args, **param.kwargs) | |||
| main_parser = ArgumentParser(*main_command.command.args, **main_command.command.kwargs) | |||
| add_arguments(main_parser, main_command.params) | |||
| subparsers = None | |||
| if len(subcommands): | |||
| subparsers = main_parser.add_subparsers() | |||
| for sub in subcommands: | |||
| parser = subparsers.add_parser(*sub.subcommand.args, **sub.subcommand.kwargs) | |||
| parser.set_defaults(run=sub) | |||
| add_arguments(parser, sub.params) | |||
| # subparsers = None | |||
| # if subcommands.length: | |||
| # subparsers = parser.add_subparsers() | |||
| # | |||
| # for command in subcommands: | |||
| # | |||
| # for param in main_command.params: | |||
| # parser.add_argument(*param.args, **param.kwargs) | |||
| instance.parser = parser | |||
| instance.main = main_command | |||
| instance.__parser__ = main_parser | |||
| instance.__main__ = main_command | |||
| return instance | |||
| values = namedtuple('__meta_values', 'args, kwargs') | |||
| @@ -53,29 +56,39 @@ class metarator(object): | |||
| def __init__(self, *args, **kwargs): | |||
| self.values = values._make((args, kwargs)) | |||
| def metarate(self, f, name='values'): | |||
| setattr(f, name, self.values) | |||
| return f | |||
| def metarate(self, func, name='values'): | |||
| """ | |||
| Set the values object to the function object's namespace | |||
| """ | |||
| setattr(func, name, self.values) | |||
| return func | |||
| def __call__(self, f): | |||
| return self.metarate(f) | |||
| def __call__(self, func): | |||
| return self.metarate(func) | |||
| class command(metarator): | |||
| """ | |||
| Used to decorate the main entry point | |||
| """ | |||
| def __call__(self, f): | |||
| return self.metarate(f, name='command') | |||
| def __call__(self, func): | |||
| return self.metarate(func, name='command') | |||
| class subcommand(metarator): | |||
| """ | |||
| Used to decorate the subcommands | |||
| """ | |||
| def __call__(self, func): | |||
| return self.metarate(func, name='subcommand') | |||
| class param(metarator): | |||
| """ | |||
| Use this decorator instead of `ArgumentParser.add_argument`. | |||
| """ | |||
| def __call__(self, f): | |||
| f.params = f.params if hasattr(f, 'params') else [] | |||
| f.params.append(self.values) | |||
| return f | |||
| def __call__(self, func): | |||
| func.params = func.params if hasattr(func, 'params') else [] | |||
| func.params.append(self.values) | |||
| return func | |||
| class Application(object): | |||
| @@ -86,10 +99,16 @@ class Application(object): | |||
| __metaclass__ = CommandLine | |||
| def parse(self, argv): | |||
| return self.parser.parse_args(argv) | |||
| """ | |||
| Simple method that delegates to the ArgumentParser | |||
| """ | |||
| return self.__parser__.parse_args(argv) | |||
| def run(self, args): | |||
| """ | |||
| Runs the main command or sub command based on user input | |||
| """ | |||
| if hasattr(args, 'run'): | |||
| args.run(args) | |||
| args.run(self, args) | |||
| else: | |||
| self.main(args) | |||
| self.__main__(args) | |||
| @@ -5,32 +5,91 @@ Use nose | |||
| `$ nosetests` | |||
| """ | |||
| from hyde.command_line import Application, command, param | |||
| from util import trap_exit | |||
| from contextlib import nested | |||
| from hyde.command_line import Application, command, subcommand, param | |||
| from util import trap_exit_pass, trap_exit_fail | |||
| from mock import Mock, patch | |||
| try: | |||
| import cStringIO as StringIO | |||
| except ImportError: | |||
| import StringIO | |||
| @trap_exit | |||
| def test_command_basic(): | |||
| import sys | |||
| class BasicCommandLine(Application): | |||
| number_of_calls = 0 | |||
| class TestCommandLine(Application): | |||
| @command(description='test', prog='Basic') | |||
| @param('--force', action='store_true', dest='force1') | |||
| @param('--force2', action='store', dest='force2') | |||
| @param('--version', action='version', version='%(prog)s 1.0') | |||
| def main(self, params): | |||
| assert params.force1 == eval(params.force2) | |||
| self._main() | |||
| @command(description='test') | |||
| @param('--force', action='store_true', dest='force1') | |||
| @param('--force2', action='store', dest='force2') | |||
| @param('--version', action='version', version='%(prog)s 1.0') | |||
| def main(self, params): | |||
| assert params.force1 == eval(params.force2) | |||
| self._main() | |||
| def _main(): pass | |||
| def _main(): pass | |||
| with patch.object(TestCommandLine, '_main') as _main: | |||
| c = TestCommandLine() | |||
| @trap_exit_fail | |||
| def test_command_basic(): | |||
| with patch.object(BasicCommandLine, '_main') as _main: | |||
| c = BasicCommandLine() | |||
| args = c.parse(['--force', '--force2', 'True']) | |||
| c.run(args) | |||
| assert _main.call_count == 1 | |||
| args = c.parse(['--force2', 'False']) | |||
| c.run(args) | |||
| assert _main.call_count == 2 | |||
| assert _main.call_count == 2 | |||
| def test_command_version(): | |||
| with patch.object(BasicCommandLine, '_main') as _main: | |||
| c = BasicCommandLine() | |||
| exception = False | |||
| try: | |||
| c.parse(['--version']) | |||
| assert False | |||
| except SystemExit: | |||
| exception = True | |||
| assert exception | |||
| assert not _main.called | |||
| class ComplexCommandLine(Application): | |||
| @command(description='test', prog='Complex') | |||
| @param('--force', action='store_true', dest='force1') | |||
| @param('--force2', action='store', dest='force2') | |||
| @param('--version', action='version', version='%(prog)s 1.0') | |||
| def main(self, params): | |||
| assert params.force1 == eval(params.force2) | |||
| self._main() | |||
| @subcommand('sub', description='test') | |||
| @param('--launch', action='store_true', dest='launch1') | |||
| @param('--launch2', action='store', dest='launch2') | |||
| def sub(self, params): | |||
| assert params.launch1 == eval(params.launch2) | |||
| self._sub() | |||
| def _main(): pass | |||
| def _sub(): pass | |||
| @trap_exit_pass | |||
| def test_command_subcommands_usage(): | |||
| with nested(patch.object(ComplexCommandLine, '_main'), | |||
| patch.object(ComplexCommandLine, '_sub')) as (_main, _sub): | |||
| c = ComplexCommandLine() | |||
| c.parse(['--usage']) | |||
| @trap_exit_fail | |||
| def test_command_subcommands(): | |||
| with nested(patch.object(ComplexCommandLine, '_main'), | |||
| patch.object(ComplexCommandLine, '_sub')) as (_main, _sub): | |||
| c = ComplexCommandLine() | |||
| args = c.parse(['sub', '--launch', '--launch2', 'True']) | |||
| c.run(args) | |||
| assert not _main.called | |||
| assert _sub.call_count == 1 | |||
| @@ -8,13 +8,21 @@ def assert_html_equals(expected, actual, sanitize=None): | |||
| actual = sanitize(actual) | |||
| assert expected == actual | |||
| def trap_exit(f): | |||
| def trap_exit_fail(f): | |||
| def test_wrapper(*args): | |||
| try: | |||
| f(*args) | |||
| except SystemExit, e: | |||
| print "Error running test [%s]" % f.__name__ | |||
| print e.message | |||
| raise e | |||
| except SystemExit: | |||
| assert False | |||
| test_wrapper.__name__ = f.__name__ | |||
| return test_wrapper | |||
| def trap_exit_pass(f): | |||
| def test_wrapper(*args): | |||
| try: | |||
| print f.__name__ | |||
| f(*args) | |||
| except SystemExit: | |||
| pass | |||
| test_wrapper.__name__ = f.__name__ | |||
| return test_wrapper | |||