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
1=== added file 'bin/chlp'
2--- bin/chlp 1970-01-01 00:00:00 +0000
3+++ bin/chlp 2013-06-19 23:02:29 +0000
4@@ -0,0 +1,7 @@
5+#!/usr/bin/env python
6+
7+from charmhelpers.cli import cmdline
8+from charmhelpers.cli.commands import *
9+
10+if __name__ == '__main__':
11+ cmdline.run()
12
13=== renamed directory 'charmhelpers/commandant' => 'charmhelpers/cli'
14=== modified file 'charmhelpers/cli/README.rst'
15--- charmhelpers/commandant/README.rst 2013-06-06 17:29:26 +0000
16+++ charmhelpers/cli/README.rst 2013-06-19 23:02:29 +0000
17@@ -14,6 +14,7 @@
18 =====
19
20 * Single decorator to expose a function as a command.
21+ * now two decorators - one "automatic" and one that allows authors to manipulate the arguments for fine-grained control.(MW)
22 * Automatic analysis of function signature through ``inspect.getargspec()``
23 * Command argument parser built automatically with ``argparse``
24 * Interactive interpreter loop object made with ``Cmd``
25@@ -30,5 +31,27 @@
26 - We could automatically detect arguments that default to False or True, and automatically support --no-foo for foo=True.
27 - We could automatically support hyphens as alternates for underscores
28 - Arguments defaulting to sequence types could support the ``append`` action.
29-
30-
31+
32+
33+-----------------------------------------------------
34+Implementing subcommands
35+-----------------------------------------------------
36+
37+(WIP)
38+
39+So 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.
40+
41+Some examples::
42+
43+ from charmhelpers.cli import CommandLine
44+ from charmhelpers.payload import execd
45+ from charmhelpers.foo import bar
46+
47+ cli = CommandLine()
48+
49+ cli.subcommand(execd.execd_run)
50+
51+ @cli.subcommand_builder("bar", help="Bar baz qux")
52+ def barcmd_builder(subparser):
53+ subparser.add_argument('argument1', help="yackety")
54+ return bar
55
56=== renamed file 'charmhelpers/commandant/signature.py' => 'charmhelpers/cli/__init__.py'
57--- charmhelpers/commandant/signature.py 2013-06-06 16:58:35 +0000
58+++ charmhelpers/cli/__init__.py 2013-06-19 23:02:29 +0000
59@@ -1,14 +1,73 @@
60-"""Tools to inspect function signatures and build argparse parsers from
61-them."""
62-
63 import inspect
64 import itertools
65+import argparse
66+
67+
68+class CommandLine(object):
69+ argument_parser = None
70+ subparsers = None
71+
72+ def __init__(self):
73+ if not self.argument_parser:
74+ self.argument_parser = argparse.ArgumentParser(description='Perform common charm tasks')
75+ if not self.subparsers:
76+ self.subparsers = self.argument_parser.add_subparsers(help='Commands')
77+
78+ def subcommand(self, command_name=None):
79+ """
80+ Decorate a function as a subcommand. Use its arguments as the
81+ command-line arguments"""
82+ def wrapper(decorated):
83+ cmd_name = command_name or decorated.__name__
84+ subparser = self.subparsers.add_parser(cmd_name,
85+ description=decorated.__doc__)
86+ for args, kwargs in describe_arguments(decorated):
87+ subparser.add_argument(*args, **kwargs)
88+ subparser.set_defaults(func=decorated)
89+ return decorated
90+ return wrapper
91+
92+ def subcommand_builder(self, command_name, description=None):
93+ """
94+ Decorate a function that builds a subcommand. Builders should accept a
95+ single argument (the subparser instance) and return the function to be
96+ run as the command."""
97+ def wrapper(decorated):
98+ subparser = self.subparsers.add_parser(command_name)
99+ func = decorated(subparser)
100+ subparser.set_defaults(func=func)
101+ subparser.description = description or func.__doc__
102+ return wrapper
103+
104+ def make_subparser(self, subcmd_name, function, description=None):
105+ "An argparse subparser for the named subcommand."
106+ subparser = self.subparsers.add_parser(subcmd_name, description)
107+ subparser.set_defaults(func=function)
108+ return subparser
109+
110+ def run(self):
111+ "Run cli, processing arguments and executing subcommands."
112+ arguments = self.argument_parser.parse_args()
113+ argspec = inspect.getargspec(arguments.func)
114+ vargs = []
115+ kwargs = {}
116+ if argspec.varargs:
117+ vargs = getattr(arguments, argspec.vargs)
118+ for arg in argspec.args:
119+ kwargs[arg] = getattr(arguments, arg)
120+ arguments.func(*vargs, **kwargs)
121+
122+
123+cmdline = CommandLine()
124+
125
126 def describe_arguments(func):
127- """Analyze a function's signature and return a data structure suitable for
128+ """
129+ Analyze a function's signature and return a data structure suitable for
130 passing in as arguments to an argparse parser's add_argument() method."""
131
132 argspec = inspect.getargspec(func)
133+ # we should probably raise an exception somewhere if func includes **kwargs
134 if argspec.defaults:
135 positional_args = argspec.args[:len(argspec.defaults)]
136 keyword_names = argspec.args[-len(argspec.defaults):]
137@@ -21,6 +80,3 @@
138 yield (arg,), {}
139 if argspec.varargs:
140 yield (argspec.varargs,), {'nargs': '*'}
141-
142-
143-
144
145=== added file 'charmhelpers/cli/commands.py'
146--- charmhelpers/cli/commands.py 1970-01-01 00:00:00 +0000
147+++ charmhelpers/cli/commands.py 2013-06-19 23:02:29 +0000
148@@ -0,0 +1,2 @@
149+from . import CommandLine
150+import host
151
152=== added file 'charmhelpers/cli/host.py'
153--- charmhelpers/cli/host.py 1970-01-01 00:00:00 +0000
154+++ charmhelpers/cli/host.py 2013-06-19 23:02:29 +0000
155@@ -0,0 +1,16 @@
156+from . import cmdline
157+from charmhelpers.core import host
158+
159+
160+@cmdline.subcommand()
161+def mounts():
162+ "List mounts"
163+ for mount in host.mounts():
164+ print mount
165+
166+
167+@cmdline.subcommand_builder('service', description="Control system services")
168+def service(subparser):
169+ subparser.add_argument("action", help="The action to perform (start, stop, etc...)")
170+ subparser.add_argument("service_name", help="Name of the service to control")
171+ return host.service
172
173=== removed file 'charmhelpers/commandant/__init__.py'
174=== modified file 'charmhelpers/core/host.py'
175--- charmhelpers/core/host.py 2013-05-30 11:02:07 +0000
176+++ charmhelpers/core/host.py 2013-06-19 23:02:29 +0000
177@@ -22,6 +22,7 @@
178
179
180 def service(action, service_name):
181+ "Control system services"
182 cmd = None
183 if os.path.exists(os.path.join('/etc/init', '%s.conf' % service_name)):
184 cmd = ['initctl', action, service_name]

Subscribers

People subscribed via source and target branches

to all changes: