Merge lp:~fwereade/pyjuju/go-cmd-context into lp:pyjuju/go

Proposed by William Reade
Status: Rejected
Rejected by: William Reade
Proposed branch: lp:~fwereade/pyjuju/go-cmd-context
Merge into: lp:pyjuju/go
Prerequisite: lp:~fwereade/pyjuju/go-log-type
Diff against target: 722 lines (+336/-123)
13 files modified
cmd/command.go (+15/-49)
cmd/context.go (+117/-0)
cmd/context_test.go (+148/-0)
cmd/juju/bootstrap.go (+3/-3)
cmd/juju/main.go (+1/-1)
cmd/jujud/agent.go (+5/-5)
cmd/jujud/initzk.go (+6/-6)
cmd/jujud/machine.go (+4/-3)
cmd/jujud/main.go (+1/-1)
cmd/jujud/provisioning.go (+5/-2)
cmd/jujud/unit.go (+5/-4)
cmd/supercommand.go (+13/-36)
cmd/supercommand_test.go (+13/-13)
To merge this branch: bzr merge lp:~fwereade/pyjuju/go-cmd-context
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+95602@code.launchpad.net

Description of the change

add cmd.Context to allow for commands with non-global output/logging

prereq: lp:~fwereade/juju/go-log-type

The top-level Main, Run, Parse and NewFlagSet function have all become methods
on the new Context type, as has SuperCommand.initOutput. Main and Parse, which
are both used by code outside this package, have been retained for
convenience's sake and act as they did before.

The Command interface has changed slightly; Run now expects a *Context, and
ParsePositional can now return a []string to indicate that the Command should
be Parsed again with the returned args; this allows us to move the re-Parsing
logic to Context.Parse; even though SuperCommand is the only user, this
allows us to avoid passing a Context into ParsePositional, which feels quite
inappropriate. (Given that every Command has to implement ParsePositional,
it's better to add additional nil return values everywhere than to pass an
unused Context around everywhere).

This doesn't feel like an overwhelmingly large change, but it doesn't diff
very nicely. Most of the changes should be explained by the above; there's
also an unrelated type rename in supercommand_test.go because, er, it seemed
like a good idea at the time (and still doesn't feel like a bad one, even if
it adds to the noise a bit).

https://codereview.appspot.com/5719050/

To post a comment you must log in.
Revision history for this message
William Reade (fwereade) wrote :

Reviewers: mp+95602_code.launchpad.net,

Message:
Please take a look.

Description:
prereq: lp:~fwereade/juju/go-log-type

The top-level Main, Run, Parse and NewFlagSet function have all become
methods
on the new Context type, as has SuperCommand.initOutput. Main and Parse,
which
are both used by code outside this package, have been retained for
convenience's sake and act as they did before.

The Command interface has changed slightly; Run now expects a *Context,
and
ParsePositional can now return a []string to indicate that the Command
should
be Parsed again with the returned args; this allows us to move the
re-Parsing
logic to Context.Parse; even though SuperCommand is the only user, this
allows us to avoid passing a Context into ParsePositional, which feels
quite
inappropriate. (Given that every Command has to implement
ParsePositional,
it's better to add additional nil return values everywhere than to pass
an
unused Context around everywhere).

This doesn't feel like an overwhelmingly large change, but it doesn't
diff
very nicely. Most of the changes should be explained by the above;
there's
also an unrelated type rename in supercommand_test.go because, er, it
seemed
like a good idea at the time (and still doesn't feel like a bad one,
even if
it adds to the noise a bit).

https://code.launchpad.net/~fwereade/juju/go-cmd-context/+merge/95602

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/5719050/

Affected files:
   M cmd/command.go
   A cmd/context.go
   A cmd/context_test.go
   M cmd/juju/bootstrap.go
   M cmd/juju/main.go
   M cmd/jujud/agent.go
   M cmd/jujud/initzk.go
   M cmd/jujud/machine.go
   M cmd/jujud/main.go
   M cmd/jujud/provisioning.go
   M cmd/jujud/unit.go
   M cmd/supercommand.go
   M cmd/supercommand_test.go
   M log/log.go
   M log/log_test.go
   M store/store_test.go

Unmerged revisions

83. By William Reade

merge parent

82. By William Reade

added direct tests for Context

81. By William Reade

rearranged cmd output to avoid reprinting errors/usage

80. By William Reade

er, remove the Exit field itself...

79. By William Reade

Exit doesn't really want to be part of Context, I think...

78. By William Reade

added cmd.Context, which now exposes most of the original top-level functions, and SuperCommand's erstwhile initOutput method; retained top-level Parse and Main functions, which act as before, but by acting via a default Context rather than by directly hitting os.Exit, etc

77. By William Reade

add log.Log type, with an instance always available as log.Global

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'cmd/command.go'
--- cmd/command.go 2012-02-15 10:29:05 +0000
+++ cmd/command.go 2012-03-02 16:10:26 +0000
@@ -3,9 +3,7 @@
3import (3import (
4 "fmt"4 "fmt"
5 "launchpad.net/gnuflag"5 "launchpad.net/gnuflag"
6 "launchpad.net/juju/go/log"
7 "os"6 "os"
8 "strings"
9)7)
108
11// Info holds everything necessary to describe a Command's intent and usage.9// Info holds everything necessary to describe a Command's intent and usage.
@@ -32,8 +30,7 @@
32 return fmt.Sprintf("%s %s", i.Name, i.Args)30 return fmt.Sprintf("%s %s", i.Name, i.Args)
33}31}
3432
35// Command is implemented by types that interpret any command-line arguments33// Command is implemented by types that interpret command-line arguments.
36// passed to the "juju" command.
37type Command interface {34type Command interface {
38 // Info returns information about the command.35 // Info returns information about the command.
39 Info() *Info36 Info() *Info
@@ -43,41 +40,13 @@
43 InitFlagSet(f *gnuflag.FlagSet)40 InitFlagSet(f *gnuflag.FlagSet)
4441
45 // ParsePositional is called by Parse to allow the Command to handle42 // ParsePositional is called by Parse to allow the Command to handle
46 // positional command-line arguments.43 // positional command-line arguments; if any arguments are returned, the
47 ParsePositional(args []string) error44 // Command will be reparsed using those arguments.
45 ParsePositional(args []string) ([]string, error)
4846
49 // Run will execute the command according to the options and positional47 // Run will execute the Command according to the options and positional
50 // arguments interpreted by a call to Parse.48 // arguments interpreted by a call to Parse.
51 Run() error49 Run(ctx *Context) error
52}
53
54// NewFlagSet returns a FlagSet initialized for use with c.
55func NewFlagSet(c Command) *gnuflag.FlagSet {
56 f := gnuflag.NewFlagSet(c.Info().Name, gnuflag.ExitOnError)
57 f.Usage = func() { PrintUsage(c) }
58 c.InitFlagSet(f)
59 return f
60}
61
62// PrintUsage prints usage information for c to stderr.
63func PrintUsage(c Command) {
64 i := c.Info()
65 fmt.Fprintf(os.Stderr, "usage: %s\n", i.Usage())
66 fmt.Fprintf(os.Stderr, "purpose: %s\n", i.Purpose)
67 fmt.Fprintf(os.Stderr, "\noptions:\n")
68 NewFlagSet(c).PrintDefaults()
69 if i.Doc != "" {
70 fmt.Fprintf(os.Stderr, "\n%s\n", strings.TrimSpace(i.Doc))
71 }
72}
73
74// Parse parses args on c. This must be called before c is Run.
75func Parse(c Command, args []string) error {
76 f := NewFlagSet(c)
77 if err := f.Parse(c.Info().Intersperse, args); err != nil {
78 return err
79 }
80 return c.ParsePositional(f.Args())
81}50}
8251
83// CheckEmpty is a utility function that returns an error if args is not empty.52// CheckEmpty is a utility function that returns an error if args is not empty.
@@ -88,17 +57,14 @@
88 return nil57 return nil
89}58}
9059
91// Main will Parse and Run a Command, and exit appropriately.60// Parse parses args on c in a default context suitable for a command-line tool.
61// This must be called before c is Run.
62func Parse(c Command, args []string) error {
63 return NewContext().Parse(c, args)
64}
65
66// Main will Parse and Run a Command in a default context suitable for a
67// command-line tool, up to and including calling os.Exit.
92func Main(c Command, args []string) {68func Main(c Command, args []string) {
93 if err := Parse(c, args[1:]); err != nil {69 os.Exit(NewContext().Main(c, args))
94 fmt.Fprintf(os.Stderr, "%v\n", err)
95 PrintUsage(c)
96 os.Exit(2)
97 }
98 if err := c.Run(); err != nil {
99 log.Debugf("%s command failed: %s\n", c.Info().Name, err)
100 fmt.Fprintf(os.Stderr, "%v\n", err)
101 os.Exit(1)
102 }
103 os.Exit(0)
104}70}
10571
=== added file 'cmd/context.go'
--- cmd/context.go 1970-01-01 00:00:00 +0000
+++ cmd/context.go 2012-03-02 16:10:26 +0000
@@ -0,0 +1,117 @@
1package cmd
2
3import (
4 "fmt"
5 "io"
6 "launchpad.net/gnuflag"
7 "launchpad.net/juju/go/log"
8 stdlog "log"
9 "os"
10 "strings"
11)
12
13// sink is an io.Writer that doesn't write anything. This allows us to suppress
14// gnuflag output when necessary (f.SetOutput(nil) will actually cause f to
15// write to os.Stderr).
16type sink struct{}
17
18func (*sink) Write(data []byte) (int, error) {
19 return len(data), nil
20}
21
22// Context holds those parts of the environment that it is useful to abstract
23// out to allow well-behaved Commands to be executed as part of another process.
24type Context struct {
25 Stdout io.Writer
26 Stderr io.Writer
27 Log *log.Log
28}
29
30// NewContext returns a Context suitable for a normal command-line tool.
31func NewContext() *Context {
32 return &Context{
33 Stdout: os.Stdout,
34 Stderr: os.Stderr,
35 Log: log.Global,
36 }
37}
38
39// InitLog sets up logging to a file or to ctx.Stderr as directed.
40func (ctx *Context) InitLog(verbose bool, debug bool, logfile string) error {
41 if debug {
42 ctx.Log.Debug = true
43 }
44 var target io.Writer
45 if logfile != "" {
46 var err error
47 target, err = os.OpenFile(logfile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
48 if err != nil {
49 return err
50 }
51 } else if verbose || debug {
52 target = ctx.Stderr
53 }
54 if target != nil {
55 ctx.Log.Target = stdlog.New(target, "", stdlog.LstdFlags)
56 }
57 return nil
58}
59
60// newFlagSet returns a FlagSet initialized for use with c.
61func (ctx *Context) newFlagSet(c Command, output io.Writer) *gnuflag.FlagSet {
62 f := gnuflag.NewFlagSet(c.Info().Name, gnuflag.ContinueOnError)
63 f.SetOutput(output)
64 f.Usage = func() { ctx.PrintUsage(c, output) }
65 c.InitFlagSet(f)
66 return f
67}
68
69// PrintUsage prints usage information for c to output. If output is nil, usage
70// information is printed to ctx.Stderr (for consistency with gnuflag.FlagSet's
71// SetOutput method).
72func (ctx *Context) PrintUsage(c Command, output io.Writer) {
73 if output == nil {
74 output = ctx.Stderr
75 }
76 i := c.Info()
77 fmt.Fprintf(output, "usage: %s\n", i.Usage())
78 fmt.Fprintf(output, "purpose: %s\n", i.Purpose)
79 fmt.Fprintf(output, "\noptions:\n")
80 f := ctx.newFlagSet(c, output)
81 f.PrintDefaults()
82 if i.Doc != "" {
83 fmt.Fprintf(output, "\n%s\n", strings.TrimSpace(i.Doc))
84 }
85}
86
87// Parse parses args on c. This must be called before c is Run.
88func (ctx *Context) Parse(c Command, args []string) error {
89 f := ctx.newFlagSet(c, &sink{})
90 if err := f.Parse(c.Info().Intersperse, args); err != nil {
91 return err
92 }
93 reparse, err := c.ParsePositional(f.Args())
94 if err != nil {
95 return err
96 }
97 if reparse != nil {
98 return ctx.Parse(c, reparse)
99 }
100 return nil
101}
102
103// Main will Parse and Run a Command, and return a process exit code. args
104// should contain flags and arguments only (and not the top-level command name).
105func (ctx *Context) Main(c Command, args []string) int {
106 if err := ctx.Parse(c, args); err != nil {
107 fmt.Fprintf(ctx.Stderr, "%v\n", err)
108 ctx.PrintUsage(c, nil)
109 return 2
110 }
111 if err := c.Run(ctx); err != nil {
112 ctx.Log.Debugf("%s command failed: %s\n", c.Info().Name, err)
113 fmt.Fprintf(ctx.Stderr, "%v\n", err)
114 return 1
115 }
116 return 0
117}
0118
=== added file 'cmd/context_test.go'
--- cmd/context_test.go 1970-01-01 00:00:00 +0000
+++ cmd/context_test.go 2012-03-02 16:10:26 +0000
@@ -0,0 +1,148 @@
1package cmd_test
2
3import (
4 "bytes"
5 "errors"
6 "io"
7 "io/ioutil"
8 "launchpad.net/gnuflag"
9 . "launchpad.net/gocheck"
10 cmd "launchpad.net/juju/go/cmd"
11 "launchpad.net/juju/go/log"
12 "path/filepath"
13)
14
15func getContext() *cmd.Context {
16 return &cmd.Context{
17 bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{}), &log.Log{}}
18}
19
20func str(stream io.Writer) string {
21 return stream.(*bytes.Buffer).String()
22}
23
24type CtxCommand struct {
25 Value string
26}
27
28func (c *CtxCommand) Info() *cmd.Info {
29 return &cmd.Info{"cmd-name", "[options]", "cmd-purpose", "cmd-doc", true}
30}
31
32func (c *CtxCommand) InitFlagSet(f *gnuflag.FlagSet) {
33 f.StringVar(&c.Value, "opt", "", "opt-doc")
34}
35
36func (c *CtxCommand) ParsePositional(args []string) ([]string, error) {
37 return nil, cmd.CheckEmpty(args)
38}
39
40func (c *CtxCommand) Run(ctx *cmd.Context) error {
41 if c.Value == "error" {
42 return errors.New("oh noes!")
43 }
44 ctx.Stdout.Write([]byte("hello stdout: " + c.Value))
45 ctx.Stderr.Write([]byte("hello stderr: " + c.Value))
46 return nil
47}
48
49var usage = `usage: cmd-name [options]
50purpose: cmd-purpose
51
52options:
53--opt (= "")
54 opt-doc
55
56cmd-doc
57`
58
59type ContextSuite struct{}
60
61func (s *CommandSuite) TestPrintUsageBuf(c *C) {
62 buf := bytes.NewBuffer([]byte{})
63 getContext().PrintUsage(&CtxCommand{}, buf)
64 c.Assert(buf.String(), Equals, usage)
65}
66
67func (s *CommandSuite) TestPrintUsageNil(c *C) {
68 ctx := getContext()
69 ctx.PrintUsage(&CtxCommand{}, nil)
70 c.Assert(ctx.Stderr.(*bytes.Buffer).String(), Equals, usage)
71}
72
73func (s *CommandSuite) TestParseIsSilent(c *C) {
74 ctx := getContext()
75 err := ctx.Parse(&CtxCommand{}, []string{"--unknown"})
76 c.Assert(err, ErrorMatches, "flag provided but not defined: --unknown")
77 c.Assert(str(ctx.Stdout), Equals, "")
78 c.Assert(str(ctx.Stderr), Equals, "")
79}
80
81func (s *CommandSuite) TestMainIsNotSilent(c *C) {
82 ctx := getContext()
83 result := ctx.Main(&CtxCommand{}, []string{"--unknown"})
84 c.Assert(result, Equals, 2)
85 c.Assert(str(ctx.Stdout), Equals, "")
86 expected := "flag provided but not defined: --unknown\n" + usage
87 c.Assert(str(ctx.Stderr), Equals, expected)
88}
89
90func (s *CommandSuite) TestParseSuccess(c *C) {
91 ctx := getContext()
92 err := ctx.Parse(&CtxCommand{}, []string{})
93 c.Assert(err, IsNil)
94 c.Assert(str(ctx.Stdout), Equals, "")
95 c.Assert(str(ctx.Stderr), Equals, "")
96}
97
98func (s *CommandSuite) TestMainBadRun(c *C) {
99 ctx := getContext()
100 result := ctx.Main(&CtxCommand{}, []string{"--opt", "error"})
101 c.Assert(result, Equals, 1)
102 c.Assert(str(ctx.Stdout), Equals, "")
103 c.Assert(str(ctx.Stderr), Equals, "oh noes!\n")
104}
105
106func (s *CommandSuite) TestMainSuccess(c *C) {
107 ctx := getContext()
108 result := ctx.Main(&CtxCommand{}, []string{"--opt", "success!"})
109 c.Assert(result, Equals, 0)
110 c.Assert(str(ctx.Stdout), Equals, "hello stdout: success!")
111 c.Assert(str(ctx.Stderr), Equals, "hello stderr: success!")
112}
113
114func AssertInitLog(c *C, verbose bool, debug bool, logfile string, logre string) {
115 ctx := getContext()
116 err := ctx.InitLog(verbose, debug, logfile)
117 c.Assert(err, IsNil)
118 ctx.Log.Printf("hello log")
119 ctx.Log.Debugf("hello debug")
120 c.Assert(str(ctx.Stdout), Equals, "")
121
122 var log string
123 if logfile == "" {
124 log = str(ctx.Stderr)
125 } else {
126 c.Assert(str(ctx.Stderr), Equals, "")
127 raw, err := ioutil.ReadFile(logfile)
128 c.Assert(err, IsNil)
129 log = string(raw)
130 }
131 c.Assert(log, Matches, logre)
132}
133
134func (s *CommandSuite) TestInitLog(c *C) {
135 printfre := ".* JUJU hello log\n"
136 debugfre := ".* JUJU:DEBUG hello debug\n"
137
138 AssertInitLog(c, false, false, "", "")
139 AssertInitLog(c, true, false, "", printfre)
140 AssertInitLog(c, false, true, "", printfre+debugfre)
141 AssertInitLog(c, true, true, "", printfre+debugfre)
142
143 tmp := c.MkDir()
144 AssertInitLog(c, false, false, filepath.Join(tmp, "1.log"), printfre)
145 AssertInitLog(c, true, false, filepath.Join(tmp, "2.log"), printfre)
146 AssertInitLog(c, false, true, filepath.Join(tmp, "3.log"), printfre+debugfre)
147 AssertInitLog(c, true, true, filepath.Join(tmp, "4.log"), printfre+debugfre)
148}
0149
=== modified file 'cmd/juju/bootstrap.go'
--- cmd/juju/bootstrap.go 2012-02-09 11:35:24 +0000
+++ cmd/juju/bootstrap.go 2012-03-02 16:10:26 +0000
@@ -30,13 +30,13 @@
3030
31// ParsePositional checks that no unexpected extra command-line arguments have31// ParsePositional checks that no unexpected extra command-line arguments have
32// been specified.32// been specified.
33func (c *BootstrapCommand) ParsePositional(args []string) error {33func (c *BootstrapCommand) ParsePositional(args []string) ([]string, error) {
34 return cmd.CheckEmpty(args)34 return nil, cmd.CheckEmpty(args)
35}35}
3636
37// Run connects to the environment specified on the command line and bootstraps37// Run connects to the environment specified on the command line and bootstraps
38// a juju in that environment if none already exists.38// a juju in that environment if none already exists.
39func (c *BootstrapCommand) Run() error {39func (c *BootstrapCommand) Run(ctx *cmd.Context) error {
40 conn, err := juju.NewConn(c.Environment)40 conn, err := juju.NewConn(c.Environment)
41 if err != nil {41 if err != nil {
42 return err42 return err
4343
=== modified file 'cmd/juju/main.go'
--- cmd/juju/main.go 2012-02-14 16:42:42 +0000
+++ cmd/juju/main.go 2012-03-02 16:10:26 +0000
@@ -18,7 +18,7 @@
18func Main(args []string) {18func Main(args []string) {
19 jc := cmd.NewSuperCommand("juju", jujuDoc)19 jc := cmd.NewSuperCommand("juju", jujuDoc)
20 jc.Register(&BootstrapCommand{})20 jc.Register(&BootstrapCommand{})
21 cmd.Main(jc, args)21 cmd.Main(jc, args[1:])
22}22}
2323
24func main() {24func main() {
2525
=== modified file 'cmd/jujud/agent.go'
--- cmd/jujud/agent.go 2012-02-21 15:13:47 +0000
+++ cmd/jujud/agent.go 2012-03-02 16:10:26 +0000
@@ -36,15 +36,15 @@
3636
37// ParsePositional checks that there are no unwanted arguments, and that all37// ParsePositional checks that there are no unwanted arguments, and that all
38// required flags have been set.38// required flags have been set.
39func (c *agentConf) ParsePositional(args []string) error {39func (c *agentConf) ParsePositional(args []string) ([]string, error) {
40 if c.jujuDir == "" {40 if c.jujuDir == "" {
41 return requiredError("juju-directory")41 return nil, requiredError("juju-directory")
42 }42 }
43 if c.stateInfo.Addrs == nil {43 if c.stateInfo.Addrs == nil {
44 return requiredError("zookeeper-servers")44 return nil, requiredError("zookeeper-servers")
45 }45 }
46 if c.sessionFile == "" {46 if c.sessionFile == "" {
47 return requiredError("session-file")47 return nil, requiredError("session-file")
48 }48 }
49 return cmd.CheckEmpty(args)49 return nil, cmd.CheckEmpty(args)
50}50}
5151
=== modified file 'cmd/jujud/initzk.go'
--- cmd/jujud/initzk.go 2012-02-21 15:13:47 +0000
+++ cmd/jujud/initzk.go 2012-03-02 16:10:26 +0000
@@ -32,21 +32,21 @@
3232
33// ParsePositional checks that there are no unwanted arguments, and that all33// ParsePositional checks that there are no unwanted arguments, and that all
34// required flags have been set.34// required flags have been set.
35func (c *InitzkCommand) ParsePositional(args []string) error {35func (c *InitzkCommand) ParsePositional(args []string) ([]string, error) {
36 if c.StateInfo.Addrs == nil {36 if c.StateInfo.Addrs == nil {
37 return requiredError("zookeeper-servers")37 return nil, requiredError("zookeeper-servers")
38 }38 }
39 if c.InstanceId == "" {39 if c.InstanceId == "" {
40 return requiredError("instance-id")40 return nil, requiredError("instance-id")
41 }41 }
42 if c.EnvType == "" {42 if c.EnvType == "" {
43 return requiredError("env-type")43 return nil, requiredError("env-type")
44 }44 }
45 return cmd.CheckEmpty(args)45 return nil, cmd.CheckEmpty(args)
46}46}
4747
48// Run initializes zookeeper state for an environment.48// Run initializes zookeeper state for an environment.
49func (c *InitzkCommand) Run() error {49func (c *InitzkCommand) Run(ctx *cmd.Context) error {
50 // TODO connect to zookeeper; call State.Initialize50 // TODO connect to zookeeper; call State.Initialize
51 return fmt.Errorf("InitzkCommand.Run not implemented")51 return fmt.Errorf("InitzkCommand.Run not implemented")
52}52}
5353
=== modified file 'cmd/jujud/machine.go'
--- cmd/jujud/machine.go 2012-02-15 12:28:26 +0000
+++ cmd/jujud/machine.go 2012-03-02 16:10:26 +0000
@@ -3,6 +3,7 @@
3import (3import (
4 "fmt"4 "fmt"
5 "launchpad.net/gnuflag"5 "launchpad.net/gnuflag"
6 "launchpad.net/juju/go/cmd"
6)7)
78
8// MachineAgent is a cmd.Command responsible for running a machine agent.9// MachineAgent is a cmd.Command responsible for running a machine agent.
@@ -23,14 +24,14 @@
2324
24// ParsePositional checks that there are no unwanted arguments, and that all25// ParsePositional checks that there are no unwanted arguments, and that all
25// required flags have been set.26// required flags have been set.
26func (a *MachineAgent) ParsePositional(args []string) error {27func (a *MachineAgent) ParsePositional(args []string) ([]string, error) {
27 if a.MachineId < 0 {28 if a.MachineId < 0 {
28 return fmt.Errorf("--machine-id option must be set, and expects a non-negative integer")29 return nil, fmt.Errorf("--machine-id option must be set, and expects a non-negative integer")
29 }30 }
30 return a.agentConf.ParsePositional(args)31 return a.agentConf.ParsePositional(args)
31}32}
3233
33// Run runs a machine agent.34// Run runs a machine agent.
34func (a *MachineAgent) Run() error {35func (a *MachineAgent) Run(ctx *cmd.Context) error {
35 return fmt.Errorf("MachineAgent.Run not implemented")36 return fmt.Errorf("MachineAgent.Run not implemented")
36}37}
3738
=== modified file 'cmd/jujud/main.go'
--- cmd/jujud/main.go 2012-02-15 10:10:38 +0000
+++ cmd/jujud/main.go 2012-03-02 16:10:26 +0000
@@ -21,7 +21,7 @@
21 jc.Register(NewUnitAgent())21 jc.Register(NewUnitAgent())
22 jc.Register(NewMachineAgent())22 jc.Register(NewMachineAgent())
23 jc.Register(NewProvisioningAgent())23 jc.Register(NewProvisioningAgent())
24 cmd.Main(jc, args)24 cmd.Main(jc, args[1:])
25}25}
2626
27func main() {27func main() {
2828
=== modified file 'cmd/jujud/provisioning.go'
--- cmd/jujud/provisioning.go 2012-02-15 10:29:05 +0000
+++ cmd/jujud/provisioning.go 2012-03-02 16:10:26 +0000
@@ -1,6 +1,9 @@
1package main1package main
22
3import "fmt"3import (
4 "fmt"
5 "launchpad.net/juju/go/cmd"
6)
47
5// ProvisioningAgent is a cmd.Command responsible for running a provisioning agent.8// ProvisioningAgent is a cmd.Command responsible for running a provisioning agent.
6type ProvisioningAgent struct {9type ProvisioningAgent struct {
@@ -12,6 +15,6 @@
12}15}
1316
14// Run runs a provisioning agent.17// Run runs a provisioning agent.
15func (a *ProvisioningAgent) Run() error {18func (a *ProvisioningAgent) Run(ctx *cmd.Context) error {
16 return fmt.Errorf("MachineAgent.Run not implemented")19 return fmt.Errorf("MachineAgent.Run not implemented")
17}20}
1821
=== modified file 'cmd/jujud/unit.go'
--- cmd/jujud/unit.go 2012-02-17 08:03:35 +0000
+++ cmd/jujud/unit.go 2012-03-02 16:10:26 +0000
@@ -3,6 +3,7 @@
3import (3import (
4 "fmt"4 "fmt"
5 "launchpad.net/gnuflag"5 "launchpad.net/gnuflag"
6 "launchpad.net/juju/go/cmd"
6 "launchpad.net/juju/go/juju"7 "launchpad.net/juju/go/juju"
7)8)
89
@@ -24,17 +25,17 @@
2425
25// ParsePositional checks that there are no unwanted arguments, and that all26// ParsePositional checks that there are no unwanted arguments, and that all
26// required flags have been set.27// required flags have been set.
27func (a *UnitAgent) ParsePositional(args []string) error {28func (a *UnitAgent) ParsePositional(args []string) ([]string, error) {
28 if a.UnitName == "" {29 if a.UnitName == "" {
29 return requiredError("unit-name")30 return nil, requiredError("unit-name")
30 }31 }
31 if !juju.ValidUnit.MatchString(a.UnitName) {32 if !juju.ValidUnit.MatchString(a.UnitName) {
32 return fmt.Errorf(`--unit-name option expects "<service>/<n>" argument`)33 return nil, fmt.Errorf(`--unit-name option expects "<service>/<n>" argument`)
33 }34 }
34 return a.agentConf.ParsePositional(args)35 return a.agentConf.ParsePositional(args)
35}36}
3637
37// Run runs a unit agent.38// Run runs a unit agent.
38func (a *UnitAgent) Run() error {39func (a *UnitAgent) Run(ctx *cmd.Context) error {
39 return fmt.Errorf("UnitAgent.Run not implemented")40 return fmt.Errorf("UnitAgent.Run not implemented")
40}41}
4142
=== modified file 'cmd/supercommand.go'
--- cmd/supercommand.go 2012-03-02 16:10:26 +0000
+++ cmd/supercommand.go 2012-03-02 16:10:26 +0000
@@ -2,11 +2,7 @@
22
3import (3import (
4 "fmt"4 "fmt"
5 "io"
6 "launchpad.net/gnuflag"5 "launchpad.net/gnuflag"
7 "launchpad.net/juju/go/log"
8 stdlog "log"
9 "os"
10 "sort"6 "sort"
11 "strings"7 "strings"
12)8)
@@ -99,51 +95,32 @@
99 f.BoolVar(&c.Debug, "debug", c.Debug, "if set, log debugging messages")95 f.BoolVar(&c.Debug, "debug", c.Debug, "if set, log debugging messages")
100}96}
10197
102// ParsePositional selects the subcommand specified by subargs and uses it to98// ParsePositional selects the subcommand specified by subargs and returns the
103// Parse any remaining unconsumed command-line arguments.99// remaining args that it expects to be Parsed again; that is, those that would
104func (c *SuperCommand) ParsePositional(subargs []string) error {100// be passed to the subcommand had the subcommand itself been passed directly
101// to Main. When a subcommand has been selected, c itself takes on its relevant
102// properties, and once reparsing is complete Running c will Run the subcommand.
103func (c *SuperCommand) ParsePositional(subargs []string) ([]string, error) {
105 if c.subcmd != nil {104 if c.subcmd != nil {
106 return c.subcmd.ParsePositional(subargs)105 return c.subcmd.ParsePositional(subargs)
107 }106 }
108 if len(subargs) == 0 {107 if len(subargs) == 0 {
109 return fmt.Errorf("no command specified")108 return nil, fmt.Errorf("no command specified")
110 }109 }
111 found := false110 found := false
112 if c.subcmd, found = c.subcmds[subargs[0]]; !found {111 if c.subcmd, found = c.subcmds[subargs[0]]; !found {
113 return fmt.Errorf("unrecognised command: %s", subargs[0])112 return nil, fmt.Errorf("unrecognised command: %s", subargs[0])
114 }113 }
115 return Parse(c, subargs[1:])114 return subargs[1:], nil
116}
117
118// initOutput sets up logging to a file or to stderr depending on what's been
119// requested on the command line.
120func (c *SuperCommand) initOutput() error {
121 if c.Debug {
122 log.Global.Debug = true
123 }
124 var target io.Writer
125 if c.LogFile != "" {
126 var err error
127 target, err = os.OpenFile(c.LogFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
128 if err != nil {
129 return err
130 }
131 } else if c.Verbose || c.Debug {
132 target = os.Stderr
133 }
134 if target != nil {
135 log.Global.Target = stdlog.New(target, "", stdlog.LstdFlags)
136 }
137 return nil
138}115}
139116
140// Run executes the subcommand that was selected when Parse was called.117// Run executes the subcommand that was selected when Parse was called.
141func (c *SuperCommand) Run() error {118func (c *SuperCommand) Run(ctx *Context) error {
142 if err := c.initOutput(); err != nil {119 if err := ctx.InitLog(c.Verbose, c.Debug, c.LogFile); err != nil {
143 return err120 return err
144 }121 }
145 if c.subcmd == nil {122 if c.subcmd == nil {
146 panic("Run: missing subcommand; Parse failed or not called")123 panic("Run: missing subcommand; Parse failed or not called")
147 }124 }
148 return c.subcmd.Run()125 return c.subcmd.Run(ctx)
149}126}
150127
=== modified file 'cmd/supercommand_test.go'
--- cmd/supercommand_test.go 2012-03-02 16:10:26 +0000
+++ cmd/supercommand_test.go 2012-03-02 16:10:26 +0000
@@ -13,12 +13,12 @@
1313
14func Test(t *testing.T) { TestingT(t) }14func Test(t *testing.T) { TestingT(t) }
1515
16type TestCommand struct {16type SubCommand struct {
17 Name string17 Name string
18 Value string18 Value string
19}19}
2020
21func (c *TestCommand) Info() *cmd.Info {21func (c *SubCommand) Info() *cmd.Info {
22 return &cmd.Info{22 return &cmd.Info{
23 c.Name, "[options]",23 c.Name, "[options]",
24 fmt.Sprintf("%s the juju", c.Name),24 fmt.Sprintf("%s the juju", c.Name),
@@ -27,18 +27,18 @@
27 }27 }
28}28}
2929
30func (c *TestCommand) InitFlagSet(f *gnuflag.FlagSet) {30func (c *SubCommand) InitFlagSet(f *gnuflag.FlagSet) {
31 f.StringVar(&c.Value, "value", "", "doc")31 f.StringVar(&c.Value, "value", "", "doc")
32}32}
3333
34func (c *TestCommand) ParsePositional(args []string) error {34func (c *SubCommand) ParsePositional(args []string) ([]string, error) {
35 if len(args) != 0 {35 if len(args) != 0 {
36 return fmt.Errorf("BADARGS: %s", args)36 return nil, fmt.Errorf("BADARGS: %s", args)
37 }37 }
38 return nil38 return nil, nil
39}39}
4040
41func (c *TestCommand) Run() error {41func (c *SubCommand) Run(ctx *cmd.Context) error {
42 return fmt.Errorf("BORKEN: value is %s.", c.Value)42 return fmt.Errorf("BORKEN: value is %s.", c.Value)
43}43}
4444
@@ -48,9 +48,9 @@
48 return jc, err48 return jc, err
49}49}
5050
51func parseDefenestrate(args []string) (*cmd.SuperCommand, *TestCommand, error) {51func parseDefenestrate(args []string) (*cmd.SuperCommand, *SubCommand, error) {
52 jc := cmd.NewSuperCommand("jujutest", "")52 jc := cmd.NewSuperCommand("jujutest", "")
53 tc := &TestCommand{Name: "defenestrate"}53 tc := &SubCommand{Name: "defenestrate"}
54 jc.Register(tc)54 jc.Register(tc)
55 err := cmd.Parse(jc, args)55 err := cmd.Parse(jc, args)
56 return jc, tc, err56 return jc, tc, err
@@ -83,10 +83,10 @@
8383
84func (s *CommandSuite) TestRegister(c *C) {84func (s *CommandSuite) TestRegister(c *C) {
85 jc := cmd.NewSuperCommand("jujutest", "")85 jc := cmd.NewSuperCommand("jujutest", "")
86 jc.Register(&TestCommand{Name: "flip"})86 jc.Register(&SubCommand{Name: "flip"})
87 jc.Register(&TestCommand{Name: "flap"})87 jc.Register(&SubCommand{Name: "flap"})
8888
89 badCall := func() { jc.Register(&TestCommand{Name: "flap"}) }89 badCall := func() { jc.Register(&SubCommand{Name: "flap"}) }
90 c.Assert(badCall, PanicMatches, "command already registered: flap")90 c.Assert(badCall, PanicMatches, "command already registered: flap")
9191
92 cmds := jc.DescribeCommands()92 cmds := jc.DescribeCommands()
@@ -161,7 +161,7 @@
161 jc, _, err := parseDefenestrate(args)161 jc, _, err := parseDefenestrate(args)
162 c.Assert(err, IsNil)162 c.Assert(err, IsNil)
163163
164 err = jc.Run()164 err = jc.Run(cmd.NewContext())
165 c.Assert(err, ErrorMatches, "BORKEN: value is cheese.")165 c.Assert(err, ErrorMatches, "BORKEN: value is cheese.")
166166
167 c.Assert(log.Global.Debug, Equals, debug)167 c.Assert(log.Global.Debug, Equals, debug)

Subscribers

People subscribed via source and target branches