Merge lp:~mew/charm-helpers/commandant into lp:~nick-moffitt/charm-helpers/commandant

Proposed by Matthew Wedgwood
Status: Merged
Merged at revision: 24
Proposed branch: lp:~mew/charm-helpers/commandant
Merge into: lp:~nick-moffitt/charm-helpers/commandant
Diff against target: 184 lines (+114/-9)
6 files modified
bin/chlp (+7/-0)
charmhelpers/cli/README.rst (+25/-2)
charmhelpers/cli/__init__.py (+63/-7)
charmhelpers/cli/commands.py (+2/-0)
charmhelpers/cli/host.py (+16/-0)
charmhelpers/core/host.py (+1/-0)
To merge this branch: bzr merge lp:~mew/charm-helpers/commandant
Reviewer Review Type Date Requested Status
Nick Moffitt Approve
Review via email: mp+169073@code.launchpad.net

Description of the change

This is more advisory than anything else. I've continued in your footsteps.

To post a comment you must log in.
lp:~mew/charm-helpers/commandant updated
24. By Matthew Wedgwood

command line interface that works. placement of subcommand module isn't right, but it's illustrative

Revision history for this message
Nick Moffitt (nick-moffitt) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'bin/chlp'
--- bin/chlp 1970-01-01 00:00:00 +0000
+++ bin/chlp 2013-06-19 23:02:29 +0000
@@ -0,0 +1,7 @@
1#!/usr/bin/env python
2
3from charmhelpers.cli import cmdline
4from charmhelpers.cli.commands import *
5
6if __name__ == '__main__':
7 cmdline.run()
08
=== renamed directory 'charmhelpers/commandant' => 'charmhelpers/cli'
=== modified file 'charmhelpers/cli/README.rst'
--- charmhelpers/commandant/README.rst 2013-06-06 17:29:26 +0000
+++ charmhelpers/cli/README.rst 2013-06-19 23:02:29 +0000
@@ -14,6 +14,7 @@
14=====14=====
1515
16* Single decorator to expose a function as a command.16* Single decorator to expose a function as a command.
17 * now two decorators - one "automatic" and one that allows authors to manipulate the arguments for fine-grained control.(MW)
17* Automatic analysis of function signature through ``inspect.getargspec()``18* Automatic analysis of function signature through ``inspect.getargspec()``
18* Command argument parser built automatically with ``argparse``19* Command argument parser built automatically with ``argparse``
19* Interactive interpreter loop object made with ``Cmd``20* Interactive interpreter loop object made with ``Cmd``
@@ -30,5 +31,27 @@
30 - We could automatically detect arguments that default to False or True, and automatically support --no-foo for foo=True.31 - We could automatically detect arguments that default to False or True, and automatically support --no-foo for foo=True.
31 - We could automatically support hyphens as alternates for underscores32 - We could automatically support hyphens as alternates for underscores
32 - Arguments defaulting to sequence types could support the ``append`` action.33 - Arguments defaulting to sequence types could support the ``append`` action.
33 34
3435
36-----------------------------------------------------
37Implementing subcommands
38-----------------------------------------------------
39
40(WIP)
41
42So as to avoid dependencies on the cli module, subcommands should be defined separately from their implementations. The recommmendation would be to place definitions into separate modules near the implementations which they expose.
43
44Some examples::
45
46 from charmhelpers.cli import CommandLine
47 from charmhelpers.payload import execd
48 from charmhelpers.foo import bar
49
50 cli = CommandLine()
51
52 cli.subcommand(execd.execd_run)
53
54 @cli.subcommand_builder("bar", help="Bar baz qux")
55 def barcmd_builder(subparser):
56 subparser.add_argument('argument1', help="yackety")
57 return bar
3558
=== renamed file 'charmhelpers/commandant/signature.py' => 'charmhelpers/cli/__init__.py'
--- charmhelpers/commandant/signature.py 2013-06-06 16:58:35 +0000
+++ charmhelpers/cli/__init__.py 2013-06-19 23:02:29 +0000
@@ -1,14 +1,73 @@
1"""Tools to inspect function signatures and build argparse parsers from
2them."""
3
4import inspect1import inspect
5import itertools2import itertools
3import argparse
4
5
6class CommandLine(object):
7 argument_parser = None
8 subparsers = None
9
10 def __init__(self):
11 if not self.argument_parser:
12 self.argument_parser = argparse.ArgumentParser(description='Perform common charm tasks')
13 if not self.subparsers:
14 self.subparsers = self.argument_parser.add_subparsers(help='Commands')
15
16 def subcommand(self, command_name=None):
17 """
18 Decorate a function as a subcommand. Use its arguments as the
19 command-line arguments"""
20 def wrapper(decorated):
21 cmd_name = command_name or decorated.__name__
22 subparser = self.subparsers.add_parser(cmd_name,
23 description=decorated.__doc__)
24 for args, kwargs in describe_arguments(decorated):
25 subparser.add_argument(*args, **kwargs)
26 subparser.set_defaults(func=decorated)
27 return decorated
28 return wrapper
29
30 def subcommand_builder(self, command_name, description=None):
31 """
32 Decorate a function that builds a subcommand. Builders should accept a
33 single argument (the subparser instance) and return the function to be
34 run as the command."""
35 def wrapper(decorated):
36 subparser = self.subparsers.add_parser(command_name)
37 func = decorated(subparser)
38 subparser.set_defaults(func=func)
39 subparser.description = description or func.__doc__
40 return wrapper
41
42 def make_subparser(self, subcmd_name, function, description=None):
43 "An argparse subparser for the named subcommand."
44 subparser = self.subparsers.add_parser(subcmd_name, description)
45 subparser.set_defaults(func=function)
46 return subparser
47
48 def run(self):
49 "Run cli, processing arguments and executing subcommands."
50 arguments = self.argument_parser.parse_args()
51 argspec = inspect.getargspec(arguments.func)
52 vargs = []
53 kwargs = {}
54 if argspec.varargs:
55 vargs = getattr(arguments, argspec.vargs)
56 for arg in argspec.args:
57 kwargs[arg] = getattr(arguments, arg)
58 arguments.func(*vargs, **kwargs)
59
60
61cmdline = CommandLine()
62
663
7def describe_arguments(func):64def describe_arguments(func):
8 """Analyze a function's signature and return a data structure suitable for65 """
66 Analyze a function's signature and return a data structure suitable for
9 passing in as arguments to an argparse parser's add_argument() method."""67 passing in as arguments to an argparse parser's add_argument() method."""
1068
11 argspec = inspect.getargspec(func)69 argspec = inspect.getargspec(func)
70 # we should probably raise an exception somewhere if func includes **kwargs
12 if argspec.defaults:71 if argspec.defaults:
13 positional_args = argspec.args[:len(argspec.defaults)]72 positional_args = argspec.args[:len(argspec.defaults)]
14 keyword_names = argspec.args[-len(argspec.defaults):]73 keyword_names = argspec.args[-len(argspec.defaults):]
@@ -21,6 +80,3 @@
21 yield (arg,), {}80 yield (arg,), {}
22 if argspec.varargs:81 if argspec.varargs:
23 yield (argspec.varargs,), {'nargs': '*'}82 yield (argspec.varargs,), {'nargs': '*'}
24
25
26
2783
=== added file 'charmhelpers/cli/commands.py'
--- charmhelpers/cli/commands.py 1970-01-01 00:00:00 +0000
+++ charmhelpers/cli/commands.py 2013-06-19 23:02:29 +0000
@@ -0,0 +1,2 @@
1from . import CommandLine
2import host
03
=== added file 'charmhelpers/cli/host.py'
--- charmhelpers/cli/host.py 1970-01-01 00:00:00 +0000
+++ charmhelpers/cli/host.py 2013-06-19 23:02:29 +0000
@@ -0,0 +1,16 @@
1from . import cmdline
2from charmhelpers.core import host
3
4
5@cmdline.subcommand()
6def mounts():
7 "List mounts"
8 for mount in host.mounts():
9 print mount
10
11
12@cmdline.subcommand_builder('service', description="Control system services")
13def service(subparser):
14 subparser.add_argument("action", help="The action to perform (start, stop, etc...)")
15 subparser.add_argument("service_name", help="Name of the service to control")
16 return host.service
017
=== removed file 'charmhelpers/commandant/__init__.py'
=== modified file 'charmhelpers/core/host.py'
--- charmhelpers/core/host.py 2013-05-30 11:02:07 +0000
+++ charmhelpers/core/host.py 2013-06-19 23:02:29 +0000
@@ -22,6 +22,7 @@
2222
2323
24def service(action, service_name):24def service(action, service_name):
25 "Control system services"
25 cmd = None26 cmd = None
26 if os.path.exists(os.path.join('/etc/init', '%s.conf' % service_name)):27 if os.path.exists(os.path.join('/etc/init', '%s.conf' % service_name)):
27 cmd = ['initctl', action, service_name]28 cmd = ['initctl', action, service_name]

Subscribers

People subscribed via source and target branches

to all changes: