command.go 9.3 KB
Newer Older
1 2 3 4 5 6 7 8
/*
Package commands provides an API for defining and parsing commands.

Supporting nested commands, options, arguments, etc.  The commands
package also supports a collection of marshallers for presenting
output to the user, including text, JSON, and XML marshallers.
*/

9
package cmds
Matt Bell's avatar
Matt Bell committed
10 11

import (
Matt Bell's avatar
Matt Bell committed
12
	"errors"
Matt Bell's avatar
Matt Bell committed
13
	"fmt"
14
	"strings"
15

Hector Sanjuan's avatar
Hector Sanjuan committed
16
	files "github.com/ipfs/go-ipfs-files"
17

keks's avatar
keks committed
18
	logging "github.com/ipfs/go-log"
Matt Bell's avatar
Matt Bell committed
19 20
)

Hector Sanjuan's avatar
Hector Sanjuan committed
21
// DefaultOutputEncoding defines the default API output encoding.
Jan Winkelmann's avatar
Jan Winkelmann committed
22 23
const DefaultOutputEncoding = JSON

24
var log = logging.Logger("cmds")
25

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
26
// Function is the type of function that Commands use.
Jan Winkelmann's avatar
Jan Winkelmann committed
27
// It reads from the Request, and writes results to the ResponseEmitter.
28
type Function func(*Request, ResponseEmitter, Environment) error
29

Jan Winkelmann's avatar
Jan Winkelmann committed
30
// PostRunMap is the map used in Command.PostRun.
keks's avatar
keks committed
31
type PostRunMap map[PostRunType]func(Response, ResponseEmitter) error
Jan Winkelmann's avatar
Jan Winkelmann committed
32

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
33
// Command is a runnable command, with input arguments and options (flags).
34
// It can also have Subcommands, to group units of work into sets.
Matt Bell's avatar
Matt Bell committed
35
type Command struct {
Steven Allen's avatar
Steven Allen committed
36 37 38 39 40 41 42 43 44 45 46 47
	// Options defines the flags accepted by the command. Flags on specified
	// on parent commands are inherited by sub commands.
	Options []Option

	// Arguments defines the positional arguments for the command. These
	// arguments can be strings and/or files.
	//
	// The rules for valid arguments are as follows:
	//
	// 1. No required arguments may follow optional arguments.
	// 2. There can be at most one STDIN argument.
	// 3. There can be at most one variadic argument, and it must be last.
Steven Allen's avatar
Steven Allen committed
48
	Arguments []Argument
Steven Allen's avatar
Steven Allen committed
49 50 51 52

	// PreRun is run before Run. When executing a command on a remote
	// daemon, PreRun is always run in the local process.
	PreRun func(req *Request, env Environment) error
53 54 55 56 57

	// Run is the function that processes the request to generate a response.
	// Note that when executing the command over the HTTP API you can only read
	// after writing when using multipart requests. The request body will not be
	// available for reading after the HTTP connection has been written to.
Steven Allen's avatar
Steven Allen committed
58 59 60 61 62 63 64 65 66
	Run Function

	// PostRun is run after Run, and can transform results returned by run.
	// When executing a command on a remote daemon, PostRun is always run in
	// the local process.
	PostRun PostRunMap

	// Encoders encode results from Run (and/or PostRun) in the desired
	// encoding.
keks's avatar
keks committed
67
	Encoders EncoderMap
Steven Allen's avatar
Steven Allen committed
68 69

	// Helptext is the command's help text.
Steven Allen's avatar
Steven Allen committed
70
	Helptext HelpText
Brian Tiger Chow's avatar
Brian Tiger Chow committed
71

72 73 74 75
	// External denotes that a command is actually an external binary.
	// fewer checks and validations will be performed on such commands.
	External bool

Brian Tiger Chow's avatar
Brian Tiger Chow committed
76
	// Type describes the type of the output of the Command's Run Function.
Brian Tiger Chow's avatar
Brian Tiger Chow committed
77 78
	// In precise terms, the value of Type is an instance of the return type of
	// the Run Function.
Brian Tiger Chow's avatar
Brian Tiger Chow committed
79 80
	//
	// ie. If command Run returns &Block{}, then Command.Type == &Block{}
Steven Allen's avatar
Steven Allen committed
81 82 83 84 85 86 87 88 89 90 91
	Type interface{}

	// Subcommands allow attaching sub commands to a command.
	//
	// Note: A command can specify both a Run function and Subcommands. If
	// invoked with no arguments, or an argument that matches no
	// sub commands, the Run function of the current command will be invoked.
	//
	// Take care when specifying both a Run function and Subcommands. A
	// simple typo in a sub command will invoke the parent command and may
	// end up returning a cryptic error to the user.
92
	Subcommands map[string]*Command
Matt Bell's avatar
Matt Bell committed
93 94
}

keks's avatar
keks committed
95 96
var (
	// ErrNotCallable signals a command that cannot be called.
Hector Sanjuan's avatar
Hector Sanjuan committed
97
	ErrNotCallable = ClientError("this command cannot be called directly; try one of its subcommands.")
Matt Bell's avatar
Matt Bell committed
98

keks's avatar
keks committed
99
	// ErrNoFormatter signals that the command can not be formatted.
Hector Sanjuan's avatar
Hector Sanjuan committed
100
	ErrNoFormatter = ClientError("this command cannot be formatted to plain text")
101

keks's avatar
keks committed
102
	// ErrIncorrectType signales that the commands returned a value with unexpected type.
Hector Sanjuan's avatar
Hector Sanjuan committed
103
	ErrIncorrectType = errors.New("the command returned a value with a different type than expected")
keks's avatar
keks committed
104
)
105

106
// Call invokes the command for the given Request
107
func (c *Command) Call(req *Request, re ResponseEmitter, env Environment) {
108
	var closeErr error
keks's avatar
keks committed
109

110
	err := c.call(req, re, env)
keks's avatar
keks committed
111
	if err != nil {
keks's avatar
keks committed
112
		log.Debugf("error occured in call, closing with error: %s", err)
113
	}
keks's avatar
keks committed
114

keks's avatar
keks committed
115
	closeErr = re.CloseWithError(err)
keks's avatar
keks committed
116 117
	// ignore double close errors
	if closeErr != nil && closeErr != ErrClosingClosedEmitter {
keks's avatar
keks committed
118
		log.Errorf("error closing ResponseEmitter: %s", closeErr)
119 120
	}
}
Jan Winkelmann's avatar
Jan Winkelmann committed
121

122 123
func (c *Command) call(req *Request, re ResponseEmitter, env Environment) error {
	cmd, err := c.Get(req.Path)
Matt Bell's avatar
Matt Bell committed
124
	if err != nil {
keks's avatar
keks committed
125
		log.Errorf("could not get cmd from path %q: %q", req.Path, err)
126
		return err
Matt Bell's avatar
Matt Bell committed
127
	}
Matt Bell's avatar
Matt Bell committed
128

129
	if cmd.Run == nil {
keks's avatar
keks committed
130
		log.Errorf("returned command has nil Run function")
131
		return err
Matt Bell's avatar
Matt Bell committed
132
	}
Matt Bell's avatar
Matt Bell committed
133

134
	err = cmd.CheckArguments(req)
135
	if err != nil {
keks's avatar
keks committed
136
		log.Errorf("CheckArguments returned an error for path %q: %q", req.Path, err)
137
		return err
138 139
	}

140
	return cmd.Run(req, re, env)
141
}
Jan Winkelmann's avatar
Jan Winkelmann committed
142

143
// Resolve returns the subcommands at the given path
144 145
func (c *Command) Resolve(pth []string) ([]*Command, error) {
	cmds := make([]*Command, len(pth)+1)
Matt Bell's avatar
Matt Bell committed
146
	cmds[0] = c
Matt Bell's avatar
Matt Bell committed
147

Matt Bell's avatar
Matt Bell committed
148
	cmd := c
149
	for i, name := range pth {
keks's avatar
cleanup  
keks committed
150
		cmd = cmd.Subcommands[name]
Matt Bell's avatar
Matt Bell committed
151

Matt Bell's avatar
Matt Bell committed
152
		if cmd == nil {
153 154
			pathS := strings.Join(pth[:i], "/")
			return nil, fmt.Errorf("undefined command: %q", pathS)
Matt Bell's avatar
Matt Bell committed
155
		}
Matt Bell's avatar
Matt Bell committed
156

Matt Bell's avatar
Matt Bell committed
157 158
		cmds[i+1] = cmd
	}
Matt Bell's avatar
Matt Bell committed
159

Matt Bell's avatar
Matt Bell committed
160
	return cmds, nil
Matt Bell's avatar
Matt Bell committed
161 162
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
163
// Get resolves and returns the Command addressed by path
Matt Bell's avatar
Matt Bell committed
164
func (c *Command) Get(path []string) (*Command, error) {
Matt Bell's avatar
Matt Bell committed
165 166 167 168 169
	cmds, err := c.Resolve(path)
	if err != nil {
		return nil, err
	}
	return cmds[len(cmds)-1], nil
Matt Bell's avatar
Matt Bell committed
170 171
}

172
// GetOptions returns the options in the given path of commands
Steven Allen's avatar
Steven Allen committed
173 174
func (c *Command) GetOptions(path []string) (map[string]Option, error) {
	options := make([]Option, 0, len(c.Options))
Matt Bell's avatar
Matt Bell committed
175 176 177 178 179

	cmds, err := c.Resolve(path)
	if err != nil {
		return nil, err
	}
180

Matt Bell's avatar
Matt Bell committed
181 182 183 184
	for _, cmd := range cmds {
		options = append(options, cmd.Options...)
	}

Steven Allen's avatar
Steven Allen committed
185
	optionsMap := make(map[string]Option)
Matt Bell's avatar
Matt Bell committed
186
	for _, opt := range options {
187
		for _, name := range opt.Names() {
188
			if _, found := optionsMap[name]; found {
189
				return nil, fmt.Errorf("option name %q used multiple times", name)
190 191
			}

Matt Bell's avatar
Matt Bell committed
192 193 194 195 196
			optionsMap[name] = opt
		}
	}

	return optionsMap, nil
197 198
}

199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
// DebugValidate checks if the command tree is well-formed.
//
// This operation is slow and should be called from tests only.
func (c *Command) DebugValidate() map[string][]error {
	errs := make(map[string][]error)
	var visit func(path string, cm *Command)

	liveOptions := make(map[string]struct{})
	visit = func(path string, cm *Command) {
		expectOptional := false
		for i, argDef := range cm.Arguments {
			// No required arguments after optional arguments.
			if argDef.Required {
				if expectOptional {
					errs[path] = append(errs[path], fmt.Errorf("required argument %s after optional arguments", argDef.Name))
					return
				}
			} else {
				expectOptional = true
			}

			// variadic arguments and those supporting stdin must be last
			if (argDef.Variadic || argDef.SupportsStdin) && i != len(cm.Arguments)-1 {
				errs[path] = append(errs[path], fmt.Errorf("variadic and/or optional argument %s must be last", argDef.Name))
			}
		}

		var goodOptions []string
		for _, option := range cm.Options {
			for _, name := range option.Names() {
				if _, ok := liveOptions[name]; ok {
					errs[path] = append(errs[path], fmt.Errorf("duplicate option name %s", name))
				} else {
					goodOptions = append(goodOptions, name)
					liveOptions[name] = struct{}{}
				}
			}
		}
		for scName, sc := range cm.Subcommands {
			visit(fmt.Sprintf("%s/%s", path, scName), sc)
		}

		for _, name := range goodOptions {
			delete(liveOptions, name)
		}
	}
	visit("", c)
	if len(errs) == 0 {
		errs = nil
	}
	return errs
}

252 253
// CheckArguments checks that we have all the required string arguments, loading
// any from stdin if necessary.
254
func (c *Command) CheckArguments(req *Request) error {
255 256 257
	if len(c.Arguments) == 0 {
		return nil
	}
258

259
	lastArg := c.Arguments[len(c.Arguments)-1]
260
	if req.bodyArgs == nil && // check this as we can end up calling CheckArguments multiple times. See #80.
261
		lastArg.SupportsStdin &&
Steven Allen's avatar
Steven Allen committed
262
		lastArg.Type == ArgString &&
263 264
		req.Files != nil {

Łukasz Magiera's avatar
Łukasz Magiera committed
265 266 267
		it := req.Files.Entries()
		if it.Next() {
			req.bodyArgs = newArguments(files.FileFromEntry(it))
268 269
			// Can't pass files and stdin arguments.
			req.Files = nil
Łukasz Magiera's avatar
Łukasz Magiera committed
270 271 272 273
		} else {
			if it.Err() != nil {
				return it.Err()
			}
274 275 276
		}
	}

277
	// iterate over the arg definitions
278
	requiredStringArgs := 0 // number of required string arguments
279 280
	for _, argDef := range req.Command.Arguments {
		// Is this a string?
Steven Allen's avatar
Steven Allen committed
281
		if argDef.Type != ArgString {
282
			// No, skip it.
283 284
			continue
		}
285

286 287 288 289
		// No more required arguments?
		if !argDef.Required {
			// Yes, we're all done.
			break
290
		}
291
		requiredStringArgs++
292

293 294
		// Do we have enough string arguments?
		if requiredStringArgs <= len(req.Arguments) {
295 296
			// all good
			continue
297
		}
298

299 300
		// Can we get it from stdin?
		if argDef.SupportsStdin && req.bodyArgs != nil {
301
			if req.bodyArgs.Scan() {
302
				// Found it!
303
				req.Arguments = append(req.Arguments, req.bodyArgs.Argument())
304 305
				continue
			}
306
			if err := req.bodyArgs.Err(); err != nil {
307
				return err
308
			}
309
			// No, just missing.
310
		}
311
		return fmt.Errorf("argument %q is required", argDef.Name)
312 313 314 315 316
	}

	return nil
}

317 318 319 320 321
type CommandVisitor func(*Command)

// Walks tree of all subcommands (including this one)
func (c *Command) Walk(visitor CommandVisitor) {
	visitor(c)
keks's avatar
cleanup  
keks committed
322 323
	for _, sub := range c.Subcommands {
		sub.Walk(visitor)
324 325 326 327 328 329 330 331 332 333 334 335
	}
}

func (c *Command) ProcessHelp() {
	c.Walk(func(cm *Command) {
		ht := &cm.Helptext
		if len(ht.LongDescription) == 0 {
			ht.LongDescription = ht.ShortDescription
		}
	})
}

336
func ClientError(msg string) error {
Steven Allen's avatar
Steven Allen committed
337
	return &Error{Code: ErrClient, Message: msg}
338
}