9
"launchpad.net/gnuflag"
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")
19
// Command is implemented by types that interpret command-line arguments.
20
type Command interface {
21
// Info returns information about the Command.
24
// SetFlags adds command specific flags to the flag set.
25
SetFlags(f *gnuflag.FlagSet)
27
// Init initializes the Command before running.
28
Init(args []string) error
30
// Run will execute the Command as directed by the options and positional
31
// arguments passed to Init.
32
Run(ctx *Context) error
35
// CommandBase provides the default implementation for SetFlags, Init, and Help.
36
type CommandBase struct{}
38
// SetFlags does nothing in the simplest case.
39
func (c *CommandBase) SetFlags(f *gnuflag.FlagSet) {}
41
// Init in the simplest case makes sure there are no args.
42
func (c *CommandBase) Init(args []string) error {
43
return CheckEmpty(args)
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.
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) {
62
return filepath.Join(ctx.Dir, path)
65
// Info holds some of the usage documentation of a Command.
67
// Name is the Command's name.
70
// Args describes the command's expected positional arguments.
73
// Purpose is a short explanation of the Command's purpose.
76
// Doc is the long documentation for the Command.
79
// Aliases are other names for the Command.
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)
89
f.VisitAll(func(f *gnuflag.Flag) { hasOptions = true })
91
fmt.Fprintf(buf, " [options]")
94
fmt.Fprintf(buf, " %s", i.Args)
96
fmt.Fprintf(buf, "\n")
98
fmt.Fprintf(buf, "purpose: %s\n", i.Purpose)
101
fmt.Fprintf(buf, "\noptions:\n")
105
f.SetOutput(ioutil.Discard)
107
fmt.Fprintf(buf, "\n%s\n", strings.TrimSpace(i.Doc))
109
if len(i.Aliases) > 0 {
110
fmt.Fprintf(buf, "\naliases: %s\n", strings.Join(i.Aliases, ", "))
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)
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
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))
137
fmt.Fprintf(ctx.Stderr, "error: %v\n", err)
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)
150
if rc, done := handleCommandError(c, ctx, ParseArgs(c, f, args), f); done {
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 {
158
if err := c.Run(ctx); err != nil {
159
if err != ErrSilent {
160
fmt.Fprintf(ctx.Stderr, "error: %v\n", err)
167
// DefaultContext returns a Context suitable for use in non-hosted situations.
168
func DefaultContext() *Context {
169
dir, err := os.Getwd()
173
abs, err := filepath.Abs(dir)
185
// CheckEmpty is a utility function that returns an error if args is not empty.
186
func CheckEmpty(args []string) error {
188
return fmt.Errorf("unrecognized args: %q", args)
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) {
198
result, args = args[0], args[1:]
200
if err := CheckEmpty(args); err != nil {