~juju-qa/ubuntu/yakkety/juju/2.0-rc3-again

« back to all changes in this revision

Viewing changes to src/launchpad.net/juju-core/cmd/cmd.go

  • Committer: Package Import Robot
  • Author(s): James Page
  • Date: 2013-04-24 22:34:47 UTC
  • Revision ID: package-import@ubuntu.com-20130424223447-f0qdji7ubnyo0s71
Tags: upstream-1.10.0.1
ImportĀ upstreamĀ versionĀ 1.10.0.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
package cmd
 
2
 
 
3
import (
 
4
        "bytes"
 
5
        "errors"
 
6
        "fmt"
 
7
        "io"
 
8
        "io/ioutil"
 
9
        "launchpad.net/gnuflag"
 
10
        "os"
 
11
        "path/filepath"
 
12
        "strings"
 
13
)
 
14
 
 
15
// ErrSilent can be returned from Run to signal that Main should exit with
 
16
// code 1 without producing error output.
 
17
var ErrSilent = errors.New("cmd: error out silently")
 
18
 
 
19
// Command is implemented by types that interpret command-line arguments.
 
20
type Command interface {
 
21
        // Info returns information about the Command.
 
22
        Info() *Info
 
23
 
 
24
        // SetFlags adds command specific flags to the flag set.
 
25
        SetFlags(f *gnuflag.FlagSet)
 
26
 
 
27
        // Init initializes the Command before running.
 
28
        Init(args []string) error
 
29
 
 
30
        // Run will execute the Command as directed by the options and positional
 
31
        // arguments passed to Init.
 
32
        Run(ctx *Context) error
 
33
}
 
34
 
 
35
// CommandBase provides the default implementation for SetFlags, Init, and Help.
 
36
type CommandBase struct{}
 
37
 
 
38
// SetFlags does nothing in the simplest case.
 
39
func (c *CommandBase) SetFlags(f *gnuflag.FlagSet) {}
 
40
 
 
41
// Init in the simplest case makes sure there are no args.
 
42
func (c *CommandBase) Init(args []string) error {
 
43
        return CheckEmpty(args)
 
44
}
 
45
 
 
46
// Context represents the run context of a Command. Command implementations
 
47
// should interpret file names relative to Dir (see AbsPath below), and print
 
48
// output and errors to Stdout and Stderr respectively.
 
49
type Context struct {
 
50
        Dir    string
 
51
        Stdin  io.Reader
 
52
        Stdout io.Writer
 
53
        Stderr io.Writer
 
54
}
 
55
 
 
56
// AbsPath returns an absolute representation of path, with relative paths
 
57
// interpreted as relative to ctx.Dir.
 
58
func (ctx *Context) AbsPath(path string) string {
 
59
        if filepath.IsAbs(path) {
 
60
                return path
 
61
        }
 
62
        return filepath.Join(ctx.Dir, path)
 
63
}
 
64
 
 
65
// Info holds some of the usage documentation of a Command.
 
66
type Info struct {
 
67
        // Name is the Command's name.
 
68
        Name string
 
69
 
 
70
        // Args describes the command's expected positional arguments.
 
71
        Args string
 
72
 
 
73
        // Purpose is a short explanation of the Command's purpose.
 
74
        Purpose string
 
75
 
 
76
        // Doc is the long documentation for the Command.
 
77
        Doc string
 
78
 
 
79
        // Aliases are other names for the Command.
 
80
        Aliases []string
 
81
}
 
82
 
 
83
// Help renders i's content, along with documentation for any
 
84
// flags defined in f. It calls f.SetOutput(ioutil.Discard).
 
85
func (i *Info) Help(f *gnuflag.FlagSet) []byte {
 
86
        buf := &bytes.Buffer{}
 
87
        fmt.Fprintf(buf, "usage: %s", i.Name)
 
88
        hasOptions := false
 
89
        f.VisitAll(func(f *gnuflag.Flag) { hasOptions = true })
 
90
        if hasOptions {
 
91
                fmt.Fprintf(buf, " [options]")
 
92
        }
 
93
        if i.Args != "" {
 
94
                fmt.Fprintf(buf, " %s", i.Args)
 
95
        }
 
96
        fmt.Fprintf(buf, "\n")
 
97
        if i.Purpose != "" {
 
98
                fmt.Fprintf(buf, "purpose: %s\n", i.Purpose)
 
99
        }
 
100
        if hasOptions {
 
101
                fmt.Fprintf(buf, "\noptions:\n")
 
102
                f.SetOutput(buf)
 
103
                f.PrintDefaults()
 
104
        }
 
105
        f.SetOutput(ioutil.Discard)
 
106
        if i.Doc != "" {
 
107
                fmt.Fprintf(buf, "\n%s\n", strings.TrimSpace(i.Doc))
 
108
        }
 
109
        if len(i.Aliases) > 0 {
 
110
                fmt.Fprintf(buf, "\naliases: %s\n", strings.Join(i.Aliases, ", "))
 
111
        }
 
112
        return buf.Bytes()
 
113
}
 
114
 
 
115
// ParseArgs encapsulate the parsing of the args so this function can be
 
116
// called from the testing module too.
 
117
func ParseArgs(c Command, f *gnuflag.FlagSet, args []string) error {
 
118
        // If the command is a SuperCommand, we want to parse the args with
 
119
        // allowIntersperse=false (i.e. the first parameter to Parse.  This will
 
120
        // mean that the args may contain other options that haven't been defined
 
121
        // yet, and that only options that relate to the SuperCommand itself can
 
122
        // come prior to the subcommand name.
 
123
        _, isSuperCommand := c.(*SuperCommand)
 
124
        return f.Parse(!isSuperCommand, args)
 
125
}
 
126
 
 
127
// Errors from commands can be either ErrHelp, which means "show the help" or
 
128
// some other error related to needed flags missing, or needed positional args
 
129
// missing, in which case we should print the error and return a non-zero
 
130
// return code.
 
131
func handleCommandError(c Command, ctx *Context, err error, f *gnuflag.FlagSet) (int, bool) {
 
132
        if err == gnuflag.ErrHelp {
 
133
                ctx.Stdout.Write(c.Info().Help(f))
 
134
                return 0, true
 
135
        }
 
136
        if err != nil {
 
137
                fmt.Fprintf(ctx.Stderr, "error: %v\n", err)
 
138
                return 2, true
 
139
        }
 
140
        return 0, false
 
141
}
 
142
 
 
143
// Main runs the given Command in the supplied Context with the given
 
144
// arguments, which should not include the command name. It returns a code
 
145
// suitable for passing to os.Exit.
 
146
func Main(c Command, ctx *Context, args []string) int {
 
147
        f := gnuflag.NewFlagSet(c.Info().Name, gnuflag.ContinueOnError)
 
148
        f.SetOutput(ioutil.Discard)
 
149
        c.SetFlags(f)
 
150
        if rc, done := handleCommandError(c, ctx, ParseArgs(c, f, args), f); done {
 
151
                return rc
 
152
        }
 
153
        // Since SuperCommands can also return gnuflag.ErrHelp errors, we need to
 
154
        // handle both those types of errors as well as "real" errors.
 
155
        if rc, done := handleCommandError(c, ctx, c.Init(f.Args()), f); done {
 
156
                return rc
 
157
        }
 
158
        if err := c.Run(ctx); err != nil {
 
159
                if err != ErrSilent {
 
160
                        fmt.Fprintf(ctx.Stderr, "error: %v\n", err)
 
161
                }
 
162
                return 1
 
163
        }
 
164
        return 0
 
165
}
 
166
 
 
167
// DefaultContext returns a Context suitable for use in non-hosted situations.
 
168
func DefaultContext() *Context {
 
169
        dir, err := os.Getwd()
 
170
        if err != nil {
 
171
                panic(err)
 
172
        }
 
173
        abs, err := filepath.Abs(dir)
 
174
        if err != nil {
 
175
                panic(err)
 
176
        }
 
177
        return &Context{
 
178
                Dir:    abs,
 
179
                Stdin:  os.Stdin,
 
180
                Stdout: os.Stdout,
 
181
                Stderr: os.Stderr,
 
182
        }
 
183
}
 
184
 
 
185
// CheckEmpty is a utility function that returns an error if args is not empty.
 
186
func CheckEmpty(args []string) error {
 
187
        if len(args) != 0 {
 
188
                return fmt.Errorf("unrecognized args: %q", args)
 
189
        }
 
190
        return nil
 
191
}
 
192
 
 
193
// ZeroOrOneArgs checks to see that there are zero or one args, and returns
 
194
// the value of the arg if provided, or the empty string if not.
 
195
func ZeroOrOneArgs(args []string) (string, error) {
 
196
        var result string
 
197
        if len(args) > 0 {
 
198
                result, args = args[0], args[1:]
 
199
        }
 
200
        if err := CheckEmpty(args); err != nil {
 
201
                return "", err
 
202
        }
 
203
        return result, nil
 
204
}