diff --git a/mopidy/utils/command.py b/mopidy/utils/command.py index 158d6e2e..596adcdb 100644 --- a/mopidy/utils/command.py +++ b/mopidy/utils/command.py @@ -17,6 +17,7 @@ class Command(object): def __init__(self): self._children = collections.OrderedDict() self._arguments = [] + self._defaults = {} def _build(self): actions = [] @@ -35,6 +36,9 @@ class Command(object): def add_argument(self, *args, **kwargs): self._arguments.append((args, kwargs)) + def set_defaults(self, **kwargs): + self._defaults.update(kwargs) + def format_usage(self, prog=None): actions = self._build()[1] prog = prog or os.path.basename(sys.argv[0]) @@ -85,24 +89,27 @@ class Command(object): for childname, child in self._children.items(): child._subhelp(' '.join((name, childname)), result) - def parse(self, args, namespace=None): - if not namespace: - namespace = argparse.Namespace() + def parse(self, args): + return self._parse(args, argparse.Namespace(), self._defaults.copy()) + def _parse(self, args, namespace, defaults): + defaults.update(self._defaults) parser = self._build()[0] result, unknown = parser.parse_known_args(args, namespace) if unknown: raise CommandError('Unknown command options.') - args = result._args - delattr(result, '_args') - - if not args: + if not result._args: + for attr, value in defaults.items(): + if not hasattr(result, attr): + setattr(result, attr, value) + delattr(result, '_args') result.command = self return result - if args[0] not in self._children: + child = self._children.get(result._args[0]) + if not child: raise CommandError('Invalid sub-command provided.') - return self._children[args[0]].parse(args[1:], result) + return child._parse(result._args[1:], result, defaults) diff --git a/tests/utils/command_test.py b/tests/utils/command_test.py index cada744b..39cc7ab1 100644 --- a/tests/utils/command_test.py +++ b/tests/utils/command_test.py @@ -17,15 +17,6 @@ class CommandParsingTest(unittest.TestCase): result = cmd.parse([]) self.assertFalse(hasattr(result, '_args')) - def test_sub_command_delegation(self): - mock_cmd = mock.Mock(spec=command.Command) - - cmd = command.Command() - cmd.add_child('foo', mock_cmd) - - cmd.parse(['foo']) - mock_cmd.parse.assert_called_with([], mock.ANY) - def test_unknown_options_raises_error(self): cmd = command.Command() with self.assertRaises(command.CommandError): @@ -55,22 +46,6 @@ class CommandParsingTest(unittest.TestCase): self.assertEqual(result.bar, 'baz') self.assertEqual(result.baz, None) - def test_multiple_sub_commands(self): - mock_foo_cmd = mock.Mock(spec=command.Command) - mock_bar_cmd = mock.Mock(spec=command.Command) - mock_baz_cmd = mock.Mock(spec=command.Command) - - cmd = command.Command() - cmd.add_child('foo', mock_foo_cmd) - cmd.add_child('bar', mock_bar_cmd) - cmd.add_child('baz', mock_baz_cmd) - - cmd.parse(['bar']) - mock_bar_cmd.parse.assert_called_with([], mock.ANY) - - cmd.parse(['baz']) - mock_baz_cmd.parse.assert_called_with([], mock.ANY) - def test_subcommand_may_have_positional(self): child = command.Command() child.add_argument('bar') @@ -103,6 +78,18 @@ class CommandParsingTest(unittest.TestCase): result = cmd.parse([]) self.assertEqual(result.command, cmd) + child2 = command.Command() + cmd.add_child('bar', child2) + + subchild = command.Command() + child.add_child('baz', subchild) + + result = cmd.parse(['bar']) + self.assertEqual(result.command, child2) + + result = cmd.parse(['foo', 'baz']) + self.assertEqual(result.command, subchild) + def test_invalid_type(self): cmd = command.Command() cmd.add_argument('--bar', type=int) @@ -143,6 +130,34 @@ class CommandParsingTest(unittest.TestCase): self.assertEqual(cm.exception.message, 'too few arguments') + def test_set_defaults(self): + cmd = command.Command() + cmd.set_defaults(foo='bar') + + result = cmd.parse([]) + self.assertEqual(result.foo, 'bar') + + def test_defaults_propegate(self): + child = command.Command() + + cmd = command.Command() + cmd.set_defaults(foo='bar') + cmd.add_child('command', child) + + result = cmd.parse(['command']) + self.assertEqual(result.foo, 'bar') + + def test_innermost_defaults_wins(self): + child = command.Command() + child.set_defaults(foo='bar') + + cmd = command.Command() + cmd.set_defaults(foo='baz') + cmd.add_child('command', child) + + result = cmd.parse(['command']) + self.assertEqual(result.foo, 'bar') + class UsageTest(unittest.TestCase): @mock.patch('sys.argv')