Browse Source

Added subcommand support to commandline

main
Lakshmi Vyasarajan 14 years ago
parent
commit
c1bda17655
3 changed files with 142 additions and 56 deletions
  1. +53
    -34
      hyde/command_line.py
  2. +76
    -17
      hyde/tests/test_command_line.py
  3. +13
    -5
      hyde/tests/util.py

+ 53
- 34
hyde/command_line.py View File

@@ -7,6 +7,7 @@ from collections import namedtuple


__all__ = [ __all__ = [
'command', 'command',
'subcommand',
'param', 'param',
'Application' 'Application'
] ]
@@ -15,33 +16,35 @@ class CommandLine(type):
""" """
Meta class that enables declarative command definitions 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 = [] subcommands = []
main_command = None main_command = None
for name, member in attrs.iteritems(): for name, member in attrs.iteritems():
if hasattr(member, "command"): if hasattr(member, "command"):
main_command = member 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: 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 return instance


values = namedtuple('__meta_values', 'args, kwargs') values = namedtuple('__meta_values', 'args, kwargs')
@@ -53,29 +56,39 @@ class metarator(object):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.values = values._make((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): class command(metarator):
""" """
Used to decorate the main entry point 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): class param(metarator):
""" """
Use this decorator instead of `ArgumentParser.add_argument`. 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): class Application(object):
@@ -86,10 +99,16 @@ class Application(object):
__metaclass__ = CommandLine __metaclass__ = CommandLine


def parse(self, argv): 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): def run(self, args):
"""
Runs the main command or sub command based on user input
"""
if hasattr(args, 'run'): if hasattr(args, 'run'):
args.run(args)
args.run(self, args)
else: else:
self.main(args)
self.__main__(args)

+ 76
- 17
hyde/tests/test_command_line.py View File

@@ -5,32 +5,91 @@ Use nose
`$ nosetests` `$ 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 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']) args = c.parse(['--force', '--force2', 'True'])
c.run(args) c.run(args)
assert _main.call_count == 1


args = c.parse(['--force2', 'False']) args = c.parse(['--force2', 'False'])
c.run(args) 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

+ 13
- 5
hyde/tests/util.py View File

@@ -8,13 +8,21 @@ def assert_html_equals(expected, actual, sanitize=None):
actual = sanitize(actual) actual = sanitize(actual)
assert expected == actual assert expected == actual


def trap_exit(f):
def trap_exit_fail(f):
def test_wrapper(*args): def test_wrapper(*args):
try: try:
f(*args) 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 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

Loading…
Cancel
Save