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
1=== modified file 'cmd/command.go'
2--- cmd/command.go 2012-02-15 10:29:05 +0000
3+++ cmd/command.go 2012-03-02 16:10:26 +0000
4@@ -3,9 +3,7 @@
5 import (
6 "fmt"
7 "launchpad.net/gnuflag"
8- "launchpad.net/juju/go/log"
9 "os"
10- "strings"
11 )
12
13 // Info holds everything necessary to describe a Command's intent and usage.
14@@ -32,8 +30,7 @@
15 return fmt.Sprintf("%s %s", i.Name, i.Args)
16 }
17
18-// Command is implemented by types that interpret any command-line arguments
19-// passed to the "juju" command.
20+// Command is implemented by types that interpret command-line arguments.
21 type Command interface {
22 // Info returns information about the command.
23 Info() *Info
24@@ -43,41 +40,13 @@
25 InitFlagSet(f *gnuflag.FlagSet)
26
27 // ParsePositional is called by Parse to allow the Command to handle
28- // positional command-line arguments.
29- ParsePositional(args []string) error
30+ // positional command-line arguments; if any arguments are returned, the
31+ // Command will be reparsed using those arguments.
32+ ParsePositional(args []string) ([]string, error)
33
34- // Run will execute the command according to the options and positional
35+ // Run will execute the Command according to the options and positional
36 // arguments interpreted by a call to Parse.
37- Run() error
38-}
39-
40-// NewFlagSet returns a FlagSet initialized for use with c.
41-func NewFlagSet(c Command) *gnuflag.FlagSet {
42- f := gnuflag.NewFlagSet(c.Info().Name, gnuflag.ExitOnError)
43- f.Usage = func() { PrintUsage(c) }
44- c.InitFlagSet(f)
45- return f
46-}
47-
48-// PrintUsage prints usage information for c to stderr.
49-func PrintUsage(c Command) {
50- i := c.Info()
51- fmt.Fprintf(os.Stderr, "usage: %s\n", i.Usage())
52- fmt.Fprintf(os.Stderr, "purpose: %s\n", i.Purpose)
53- fmt.Fprintf(os.Stderr, "\noptions:\n")
54- NewFlagSet(c).PrintDefaults()
55- if i.Doc != "" {
56- fmt.Fprintf(os.Stderr, "\n%s\n", strings.TrimSpace(i.Doc))
57- }
58-}
59-
60-// Parse parses args on c. This must be called before c is Run.
61-func Parse(c Command, args []string) error {
62- f := NewFlagSet(c)
63- if err := f.Parse(c.Info().Intersperse, args); err != nil {
64- return err
65- }
66- return c.ParsePositional(f.Args())
67+ Run(ctx *Context) error
68 }
69
70 // CheckEmpty is a utility function that returns an error if args is not empty.
71@@ -88,17 +57,14 @@
72 return nil
73 }
74
75-// Main will Parse and Run a Command, and exit appropriately.
76+// Parse parses args on c in a default context suitable for a command-line tool.
77+// This must be called before c is Run.
78+func Parse(c Command, args []string) error {
79+ return NewContext().Parse(c, args)
80+}
81+
82+// Main will Parse and Run a Command in a default context suitable for a
83+// command-line tool, up to and including calling os.Exit.
84 func Main(c Command, args []string) {
85- if err := Parse(c, args[1:]); err != nil {
86- fmt.Fprintf(os.Stderr, "%v\n", err)
87- PrintUsage(c)
88- os.Exit(2)
89- }
90- if err := c.Run(); err != nil {
91- log.Debugf("%s command failed: %s\n", c.Info().Name, err)
92- fmt.Fprintf(os.Stderr, "%v\n", err)
93- os.Exit(1)
94- }
95- os.Exit(0)
96+ os.Exit(NewContext().Main(c, args))
97 }
98
99=== added file 'cmd/context.go'
100--- cmd/context.go 1970-01-01 00:00:00 +0000
101+++ cmd/context.go 2012-03-02 16:10:26 +0000
102@@ -0,0 +1,117 @@
103+package cmd
104+
105+import (
106+ "fmt"
107+ "io"
108+ "launchpad.net/gnuflag"
109+ "launchpad.net/juju/go/log"
110+ stdlog "log"
111+ "os"
112+ "strings"
113+)
114+
115+// sink is an io.Writer that doesn't write anything. This allows us to suppress
116+// gnuflag output when necessary (f.SetOutput(nil) will actually cause f to
117+// write to os.Stderr).
118+type sink struct{}
119+
120+func (*sink) Write(data []byte) (int, error) {
121+ return len(data), nil
122+}
123+
124+// Context holds those parts of the environment that it is useful to abstract
125+// out to allow well-behaved Commands to be executed as part of another process.
126+type Context struct {
127+ Stdout io.Writer
128+ Stderr io.Writer
129+ Log *log.Log
130+}
131+
132+// NewContext returns a Context suitable for a normal command-line tool.
133+func NewContext() *Context {
134+ return &Context{
135+ Stdout: os.Stdout,
136+ Stderr: os.Stderr,
137+ Log: log.Global,
138+ }
139+}
140+
141+// InitLog sets up logging to a file or to ctx.Stderr as directed.
142+func (ctx *Context) InitLog(verbose bool, debug bool, logfile string) error {
143+ if debug {
144+ ctx.Log.Debug = true
145+ }
146+ var target io.Writer
147+ if logfile != "" {
148+ var err error
149+ target, err = os.OpenFile(logfile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
150+ if err != nil {
151+ return err
152+ }
153+ } else if verbose || debug {
154+ target = ctx.Stderr
155+ }
156+ if target != nil {
157+ ctx.Log.Target = stdlog.New(target, "", stdlog.LstdFlags)
158+ }
159+ return nil
160+}
161+
162+// newFlagSet returns a FlagSet initialized for use with c.
163+func (ctx *Context) newFlagSet(c Command, output io.Writer) *gnuflag.FlagSet {
164+ f := gnuflag.NewFlagSet(c.Info().Name, gnuflag.ContinueOnError)
165+ f.SetOutput(output)
166+ f.Usage = func() { ctx.PrintUsage(c, output) }
167+ c.InitFlagSet(f)
168+ return f
169+}
170+
171+// PrintUsage prints usage information for c to output. If output is nil, usage
172+// information is printed to ctx.Stderr (for consistency with gnuflag.FlagSet's
173+// SetOutput method).
174+func (ctx *Context) PrintUsage(c Command, output io.Writer) {
175+ if output == nil {
176+ output = ctx.Stderr
177+ }
178+ i := c.Info()
179+ fmt.Fprintf(output, "usage: %s\n", i.Usage())
180+ fmt.Fprintf(output, "purpose: %s\n", i.Purpose)
181+ fmt.Fprintf(output, "\noptions:\n")
182+ f := ctx.newFlagSet(c, output)
183+ f.PrintDefaults()
184+ if i.Doc != "" {
185+ fmt.Fprintf(output, "\n%s\n", strings.TrimSpace(i.Doc))
186+ }
187+}
188+
189+// Parse parses args on c. This must be called before c is Run.
190+func (ctx *Context) Parse(c Command, args []string) error {
191+ f := ctx.newFlagSet(c, &sink{})
192+ if err := f.Parse(c.Info().Intersperse, args); err != nil {
193+ return err
194+ }
195+ reparse, err := c.ParsePositional(f.Args())
196+ if err != nil {
197+ return err
198+ }
199+ if reparse != nil {
200+ return ctx.Parse(c, reparse)
201+ }
202+ return nil
203+}
204+
205+// Main will Parse and Run a Command, and return a process exit code. args
206+// should contain flags and arguments only (and not the top-level command name).
207+func (ctx *Context) Main(c Command, args []string) int {
208+ if err := ctx.Parse(c, args); err != nil {
209+ fmt.Fprintf(ctx.Stderr, "%v\n", err)
210+ ctx.PrintUsage(c, nil)
211+ return 2
212+ }
213+ if err := c.Run(ctx); err != nil {
214+ ctx.Log.Debugf("%s command failed: %s\n", c.Info().Name, err)
215+ fmt.Fprintf(ctx.Stderr, "%v\n", err)
216+ return 1
217+ }
218+ return 0
219+}
220
221=== added file 'cmd/context_test.go'
222--- cmd/context_test.go 1970-01-01 00:00:00 +0000
223+++ cmd/context_test.go 2012-03-02 16:10:26 +0000
224@@ -0,0 +1,148 @@
225+package cmd_test
226+
227+import (
228+ "bytes"
229+ "errors"
230+ "io"
231+ "io/ioutil"
232+ "launchpad.net/gnuflag"
233+ . "launchpad.net/gocheck"
234+ cmd "launchpad.net/juju/go/cmd"
235+ "launchpad.net/juju/go/log"
236+ "path/filepath"
237+)
238+
239+func getContext() *cmd.Context {
240+ return &cmd.Context{
241+ bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{}), &log.Log{}}
242+}
243+
244+func str(stream io.Writer) string {
245+ return stream.(*bytes.Buffer).String()
246+}
247+
248+type CtxCommand struct {
249+ Value string
250+}
251+
252+func (c *CtxCommand) Info() *cmd.Info {
253+ return &cmd.Info{"cmd-name", "[options]", "cmd-purpose", "cmd-doc", true}
254+}
255+
256+func (c *CtxCommand) InitFlagSet(f *gnuflag.FlagSet) {
257+ f.StringVar(&c.Value, "opt", "", "opt-doc")
258+}
259+
260+func (c *CtxCommand) ParsePositional(args []string) ([]string, error) {
261+ return nil, cmd.CheckEmpty(args)
262+}
263+
264+func (c *CtxCommand) Run(ctx *cmd.Context) error {
265+ if c.Value == "error" {
266+ return errors.New("oh noes!")
267+ }
268+ ctx.Stdout.Write([]byte("hello stdout: " + c.Value))
269+ ctx.Stderr.Write([]byte("hello stderr: " + c.Value))
270+ return nil
271+}
272+
273+var usage = `usage: cmd-name [options]
274+purpose: cmd-purpose
275+
276+options:
277+--opt (= "")
278+ opt-doc
279+
280+cmd-doc
281+`
282+
283+type ContextSuite struct{}
284+
285+func (s *CommandSuite) TestPrintUsageBuf(c *C) {
286+ buf := bytes.NewBuffer([]byte{})
287+ getContext().PrintUsage(&CtxCommand{}, buf)
288+ c.Assert(buf.String(), Equals, usage)
289+}
290+
291+func (s *CommandSuite) TestPrintUsageNil(c *C) {
292+ ctx := getContext()
293+ ctx.PrintUsage(&CtxCommand{}, nil)
294+ c.Assert(ctx.Stderr.(*bytes.Buffer).String(), Equals, usage)
295+}
296+
297+func (s *CommandSuite) TestParseIsSilent(c *C) {
298+ ctx := getContext()
299+ err := ctx.Parse(&CtxCommand{}, []string{"--unknown"})
300+ c.Assert(err, ErrorMatches, "flag provided but not defined: --unknown")
301+ c.Assert(str(ctx.Stdout), Equals, "")
302+ c.Assert(str(ctx.Stderr), Equals, "")
303+}
304+
305+func (s *CommandSuite) TestMainIsNotSilent(c *C) {
306+ ctx := getContext()
307+ result := ctx.Main(&CtxCommand{}, []string{"--unknown"})
308+ c.Assert(result, Equals, 2)
309+ c.Assert(str(ctx.Stdout), Equals, "")
310+ expected := "flag provided but not defined: --unknown\n" + usage
311+ c.Assert(str(ctx.Stderr), Equals, expected)
312+}
313+
314+func (s *CommandSuite) TestParseSuccess(c *C) {
315+ ctx := getContext()
316+ err := ctx.Parse(&CtxCommand{}, []string{})
317+ c.Assert(err, IsNil)
318+ c.Assert(str(ctx.Stdout), Equals, "")
319+ c.Assert(str(ctx.Stderr), Equals, "")
320+}
321+
322+func (s *CommandSuite) TestMainBadRun(c *C) {
323+ ctx := getContext()
324+ result := ctx.Main(&CtxCommand{}, []string{"--opt", "error"})
325+ c.Assert(result, Equals, 1)
326+ c.Assert(str(ctx.Stdout), Equals, "")
327+ c.Assert(str(ctx.Stderr), Equals, "oh noes!\n")
328+}
329+
330+func (s *CommandSuite) TestMainSuccess(c *C) {
331+ ctx := getContext()
332+ result := ctx.Main(&CtxCommand{}, []string{"--opt", "success!"})
333+ c.Assert(result, Equals, 0)
334+ c.Assert(str(ctx.Stdout), Equals, "hello stdout: success!")
335+ c.Assert(str(ctx.Stderr), Equals, "hello stderr: success!")
336+}
337+
338+func AssertInitLog(c *C, verbose bool, debug bool, logfile string, logre string) {
339+ ctx := getContext()
340+ err := ctx.InitLog(verbose, debug, logfile)
341+ c.Assert(err, IsNil)
342+ ctx.Log.Printf("hello log")
343+ ctx.Log.Debugf("hello debug")
344+ c.Assert(str(ctx.Stdout), Equals, "")
345+
346+ var log string
347+ if logfile == "" {
348+ log = str(ctx.Stderr)
349+ } else {
350+ c.Assert(str(ctx.Stderr), Equals, "")
351+ raw, err := ioutil.ReadFile(logfile)
352+ c.Assert(err, IsNil)
353+ log = string(raw)
354+ }
355+ c.Assert(log, Matches, logre)
356+}
357+
358+func (s *CommandSuite) TestInitLog(c *C) {
359+ printfre := ".* JUJU hello log\n"
360+ debugfre := ".* JUJU:DEBUG hello debug\n"
361+
362+ AssertInitLog(c, false, false, "", "")
363+ AssertInitLog(c, true, false, "", printfre)
364+ AssertInitLog(c, false, true, "", printfre+debugfre)
365+ AssertInitLog(c, true, true, "", printfre+debugfre)
366+
367+ tmp := c.MkDir()
368+ AssertInitLog(c, false, false, filepath.Join(tmp, "1.log"), printfre)
369+ AssertInitLog(c, true, false, filepath.Join(tmp, "2.log"), printfre)
370+ AssertInitLog(c, false, true, filepath.Join(tmp, "3.log"), printfre+debugfre)
371+ AssertInitLog(c, true, true, filepath.Join(tmp, "4.log"), printfre+debugfre)
372+}
373
374=== modified file 'cmd/juju/bootstrap.go'
375--- cmd/juju/bootstrap.go 2012-02-09 11:35:24 +0000
376+++ cmd/juju/bootstrap.go 2012-03-02 16:10:26 +0000
377@@ -30,13 +30,13 @@
378
379 // ParsePositional checks that no unexpected extra command-line arguments have
380 // been specified.
381-func (c *BootstrapCommand) ParsePositional(args []string) error {
382- return cmd.CheckEmpty(args)
383+func (c *BootstrapCommand) ParsePositional(args []string) ([]string, error) {
384+ return nil, cmd.CheckEmpty(args)
385 }
386
387 // Run connects to the environment specified on the command line and bootstraps
388 // a juju in that environment if none already exists.
389-func (c *BootstrapCommand) Run() error {
390+func (c *BootstrapCommand) Run(ctx *cmd.Context) error {
391 conn, err := juju.NewConn(c.Environment)
392 if err != nil {
393 return err
394
395=== modified file 'cmd/juju/main.go'
396--- cmd/juju/main.go 2012-02-14 16:42:42 +0000
397+++ cmd/juju/main.go 2012-03-02 16:10:26 +0000
398@@ -18,7 +18,7 @@
399 func Main(args []string) {
400 jc := cmd.NewSuperCommand("juju", jujuDoc)
401 jc.Register(&BootstrapCommand{})
402- cmd.Main(jc, args)
403+ cmd.Main(jc, args[1:])
404 }
405
406 func main() {
407
408=== modified file 'cmd/jujud/agent.go'
409--- cmd/jujud/agent.go 2012-02-21 15:13:47 +0000
410+++ cmd/jujud/agent.go 2012-03-02 16:10:26 +0000
411@@ -36,15 +36,15 @@
412
413 // ParsePositional checks that there are no unwanted arguments, and that all
414 // required flags have been set.
415-func (c *agentConf) ParsePositional(args []string) error {
416+func (c *agentConf) ParsePositional(args []string) ([]string, error) {
417 if c.jujuDir == "" {
418- return requiredError("juju-directory")
419+ return nil, requiredError("juju-directory")
420 }
421 if c.stateInfo.Addrs == nil {
422- return requiredError("zookeeper-servers")
423+ return nil, requiredError("zookeeper-servers")
424 }
425 if c.sessionFile == "" {
426- return requiredError("session-file")
427+ return nil, requiredError("session-file")
428 }
429- return cmd.CheckEmpty(args)
430+ return nil, cmd.CheckEmpty(args)
431 }
432
433=== modified file 'cmd/jujud/initzk.go'
434--- cmd/jujud/initzk.go 2012-02-21 15:13:47 +0000
435+++ cmd/jujud/initzk.go 2012-03-02 16:10:26 +0000
436@@ -32,21 +32,21 @@
437
438 // ParsePositional checks that there are no unwanted arguments, and that all
439 // required flags have been set.
440-func (c *InitzkCommand) ParsePositional(args []string) error {
441+func (c *InitzkCommand) ParsePositional(args []string) ([]string, error) {
442 if c.StateInfo.Addrs == nil {
443- return requiredError("zookeeper-servers")
444+ return nil, requiredError("zookeeper-servers")
445 }
446 if c.InstanceId == "" {
447- return requiredError("instance-id")
448+ return nil, requiredError("instance-id")
449 }
450 if c.EnvType == "" {
451- return requiredError("env-type")
452+ return nil, requiredError("env-type")
453 }
454- return cmd.CheckEmpty(args)
455+ return nil, cmd.CheckEmpty(args)
456 }
457
458 // Run initializes zookeeper state for an environment.
459-func (c *InitzkCommand) Run() error {
460+func (c *InitzkCommand) Run(ctx *cmd.Context) error {
461 // TODO connect to zookeeper; call State.Initialize
462 return fmt.Errorf("InitzkCommand.Run not implemented")
463 }
464
465=== modified file 'cmd/jujud/machine.go'
466--- cmd/jujud/machine.go 2012-02-15 12:28:26 +0000
467+++ cmd/jujud/machine.go 2012-03-02 16:10:26 +0000
468@@ -3,6 +3,7 @@
469 import (
470 "fmt"
471 "launchpad.net/gnuflag"
472+ "launchpad.net/juju/go/cmd"
473 )
474
475 // MachineAgent is a cmd.Command responsible for running a machine agent.
476@@ -23,14 +24,14 @@
477
478 // ParsePositional checks that there are no unwanted arguments, and that all
479 // required flags have been set.
480-func (a *MachineAgent) ParsePositional(args []string) error {
481+func (a *MachineAgent) ParsePositional(args []string) ([]string, error) {
482 if a.MachineId < 0 {
483- return fmt.Errorf("--machine-id option must be set, and expects a non-negative integer")
484+ return nil, fmt.Errorf("--machine-id option must be set, and expects a non-negative integer")
485 }
486 return a.agentConf.ParsePositional(args)
487 }
488
489 // Run runs a machine agent.
490-func (a *MachineAgent) Run() error {
491+func (a *MachineAgent) Run(ctx *cmd.Context) error {
492 return fmt.Errorf("MachineAgent.Run not implemented")
493 }
494
495=== modified file 'cmd/jujud/main.go'
496--- cmd/jujud/main.go 2012-02-15 10:10:38 +0000
497+++ cmd/jujud/main.go 2012-03-02 16:10:26 +0000
498@@ -21,7 +21,7 @@
499 jc.Register(NewUnitAgent())
500 jc.Register(NewMachineAgent())
501 jc.Register(NewProvisioningAgent())
502- cmd.Main(jc, args)
503+ cmd.Main(jc, args[1:])
504 }
505
506 func main() {
507
508=== modified file 'cmd/jujud/provisioning.go'
509--- cmd/jujud/provisioning.go 2012-02-15 10:29:05 +0000
510+++ cmd/jujud/provisioning.go 2012-03-02 16:10:26 +0000
511@@ -1,6 +1,9 @@
512 package main
513
514-import "fmt"
515+import (
516+ "fmt"
517+ "launchpad.net/juju/go/cmd"
518+)
519
520 // ProvisioningAgent is a cmd.Command responsible for running a provisioning agent.
521 type ProvisioningAgent struct {
522@@ -12,6 +15,6 @@
523 }
524
525 // Run runs a provisioning agent.
526-func (a *ProvisioningAgent) Run() error {
527+func (a *ProvisioningAgent) Run(ctx *cmd.Context) error {
528 return fmt.Errorf("MachineAgent.Run not implemented")
529 }
530
531=== modified file 'cmd/jujud/unit.go'
532--- cmd/jujud/unit.go 2012-02-17 08:03:35 +0000
533+++ cmd/jujud/unit.go 2012-03-02 16:10:26 +0000
534@@ -3,6 +3,7 @@
535 import (
536 "fmt"
537 "launchpad.net/gnuflag"
538+ "launchpad.net/juju/go/cmd"
539 "launchpad.net/juju/go/juju"
540 )
541
542@@ -24,17 +25,17 @@
543
544 // ParsePositional checks that there are no unwanted arguments, and that all
545 // required flags have been set.
546-func (a *UnitAgent) ParsePositional(args []string) error {
547+func (a *UnitAgent) ParsePositional(args []string) ([]string, error) {
548 if a.UnitName == "" {
549- return requiredError("unit-name")
550+ return nil, requiredError("unit-name")
551 }
552 if !juju.ValidUnit.MatchString(a.UnitName) {
553- return fmt.Errorf(`--unit-name option expects "<service>/<n>" argument`)
554+ return nil, fmt.Errorf(`--unit-name option expects "<service>/<n>" argument`)
555 }
556 return a.agentConf.ParsePositional(args)
557 }
558
559 // Run runs a unit agent.
560-func (a *UnitAgent) Run() error {
561+func (a *UnitAgent) Run(ctx *cmd.Context) error {
562 return fmt.Errorf("UnitAgent.Run not implemented")
563 }
564
565=== modified file 'cmd/supercommand.go'
566--- cmd/supercommand.go 2012-03-02 16:10:26 +0000
567+++ cmd/supercommand.go 2012-03-02 16:10:26 +0000
568@@ -2,11 +2,7 @@
569
570 import (
571 "fmt"
572- "io"
573 "launchpad.net/gnuflag"
574- "launchpad.net/juju/go/log"
575- stdlog "log"
576- "os"
577 "sort"
578 "strings"
579 )
580@@ -99,51 +95,32 @@
581 f.BoolVar(&c.Debug, "debug", c.Debug, "if set, log debugging messages")
582 }
583
584-// ParsePositional selects the subcommand specified by subargs and uses it to
585-// Parse any remaining unconsumed command-line arguments.
586-func (c *SuperCommand) ParsePositional(subargs []string) error {
587+// ParsePositional selects the subcommand specified by subargs and returns the
588+// remaining args that it expects to be Parsed again; that is, those that would
589+// be passed to the subcommand had the subcommand itself been passed directly
590+// to Main. When a subcommand has been selected, c itself takes on its relevant
591+// properties, and once reparsing is complete Running c will Run the subcommand.
592+func (c *SuperCommand) ParsePositional(subargs []string) ([]string, error) {
593 if c.subcmd != nil {
594 return c.subcmd.ParsePositional(subargs)
595 }
596 if len(subargs) == 0 {
597- return fmt.Errorf("no command specified")
598+ return nil, fmt.Errorf("no command specified")
599 }
600 found := false
601 if c.subcmd, found = c.subcmds[subargs[0]]; !found {
602- return fmt.Errorf("unrecognised command: %s", subargs[0])
603- }
604- return Parse(c, subargs[1:])
605-}
606-
607-// initOutput sets up logging to a file or to stderr depending on what's been
608-// requested on the command line.
609-func (c *SuperCommand) initOutput() error {
610- if c.Debug {
611- log.Global.Debug = true
612- }
613- var target io.Writer
614- if c.LogFile != "" {
615- var err error
616- target, err = os.OpenFile(c.LogFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
617- if err != nil {
618- return err
619- }
620- } else if c.Verbose || c.Debug {
621- target = os.Stderr
622- }
623- if target != nil {
624- log.Global.Target = stdlog.New(target, "", stdlog.LstdFlags)
625- }
626- return nil
627+ return nil, fmt.Errorf("unrecognised command: %s", subargs[0])
628+ }
629+ return subargs[1:], nil
630 }
631
632 // Run executes the subcommand that was selected when Parse was called.
633-func (c *SuperCommand) Run() error {
634- if err := c.initOutput(); err != nil {
635+func (c *SuperCommand) Run(ctx *Context) error {
636+ if err := ctx.InitLog(c.Verbose, c.Debug, c.LogFile); err != nil {
637 return err
638 }
639 if c.subcmd == nil {
640 panic("Run: missing subcommand; Parse failed or not called")
641 }
642- return c.subcmd.Run()
643+ return c.subcmd.Run(ctx)
644 }
645
646=== modified file 'cmd/supercommand_test.go'
647--- cmd/supercommand_test.go 2012-03-02 16:10:26 +0000
648+++ cmd/supercommand_test.go 2012-03-02 16:10:26 +0000
649@@ -13,12 +13,12 @@
650
651 func Test(t *testing.T) { TestingT(t) }
652
653-type TestCommand struct {
654+type SubCommand struct {
655 Name string
656 Value string
657 }
658
659-func (c *TestCommand) Info() *cmd.Info {
660+func (c *SubCommand) Info() *cmd.Info {
661 return &cmd.Info{
662 c.Name, "[options]",
663 fmt.Sprintf("%s the juju", c.Name),
664@@ -27,18 +27,18 @@
665 }
666 }
667
668-func (c *TestCommand) InitFlagSet(f *gnuflag.FlagSet) {
669+func (c *SubCommand) InitFlagSet(f *gnuflag.FlagSet) {
670 f.StringVar(&c.Value, "value", "", "doc")
671 }
672
673-func (c *TestCommand) ParsePositional(args []string) error {
674+func (c *SubCommand) ParsePositional(args []string) ([]string, error) {
675 if len(args) != 0 {
676- return fmt.Errorf("BADARGS: %s", args)
677+ return nil, fmt.Errorf("BADARGS: %s", args)
678 }
679- return nil
680+ return nil, nil
681 }
682
683-func (c *TestCommand) Run() error {
684+func (c *SubCommand) Run(ctx *cmd.Context) error {
685 return fmt.Errorf("BORKEN: value is %s.", c.Value)
686 }
687
688@@ -48,9 +48,9 @@
689 return jc, err
690 }
691
692-func parseDefenestrate(args []string) (*cmd.SuperCommand, *TestCommand, error) {
693+func parseDefenestrate(args []string) (*cmd.SuperCommand, *SubCommand, error) {
694 jc := cmd.NewSuperCommand("jujutest", "")
695- tc := &TestCommand{Name: "defenestrate"}
696+ tc := &SubCommand{Name: "defenestrate"}
697 jc.Register(tc)
698 err := cmd.Parse(jc, args)
699 return jc, tc, err
700@@ -83,10 +83,10 @@
701
702 func (s *CommandSuite) TestRegister(c *C) {
703 jc := cmd.NewSuperCommand("jujutest", "")
704- jc.Register(&TestCommand{Name: "flip"})
705- jc.Register(&TestCommand{Name: "flap"})
706+ jc.Register(&SubCommand{Name: "flip"})
707+ jc.Register(&SubCommand{Name: "flap"})
708
709- badCall := func() { jc.Register(&TestCommand{Name: "flap"}) }
710+ badCall := func() { jc.Register(&SubCommand{Name: "flap"}) }
711 c.Assert(badCall, PanicMatches, "command already registered: flap")
712
713 cmds := jc.DescribeCommands()
714@@ -161,7 +161,7 @@
715 jc, _, err := parseDefenestrate(args)
716 c.Assert(err, IsNil)
717
718- err = jc.Run()
719+ err = jc.Run(cmd.NewContext())
720 c.Assert(err, ErrorMatches, "BORKEN: value is cheese.")
721
722 c.Assert(log.Global.Debug, Equals, debug)

Subscribers

People subscribed via source and target branches