command.go 10.1 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
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125

	// NoRemote denotes that a command cannot be executed in a remote environment
	NoRemote bool

	// NoLocal denotes that a command cannot be executed in a local environment
	NoLocal bool

	// Extra contains a set of other command-specific parameters
	Extra *Extra
}

// Extra is a set of tag information for a command
type Extra struct {
	m map[interface{}]interface{}
}

func (e *Extra) SetValue(key, value interface{}) *Extra {
	if e == nil {
		e = &Extra{}
	}
	if e.m == nil {
		e.m = make(map[interface{}]interface{})
	}
	e.m[key] = value
	return e
}

func (e *Extra) GetValue(key interface{}) (interface{}, bool) {
	if e == nil || e.m == nil {
		return nil, false
	}
	val, found := e.m[key]
	return val, found
Matt Bell's avatar
Matt Bell committed
126 127
}

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

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

keks's avatar
keks committed
135
	// ErrIncorrectType signales that the commands returned a value with unexpected type.
Hector Sanjuan's avatar
Hector Sanjuan committed
136
	ErrIncorrectType = errors.New("the command returned a value with a different type than expected")
keks's avatar
keks committed
137
)
138

139
// Call invokes the command for the given Request
140
func (c *Command) Call(req *Request, re ResponseEmitter, env Environment) {
141
	var closeErr error
keks's avatar
keks committed
142

143
	err := c.call(req, re, env)
keks's avatar
keks committed
144
	if err != nil {
keks's avatar
keks committed
145
		log.Debugf("error occured in call, closing with error: %s", err)
146
	}
keks's avatar
keks committed
147

keks's avatar
keks committed
148
	closeErr = re.CloseWithError(err)
keks's avatar
keks committed
149 150
	// ignore double close errors
	if closeErr != nil && closeErr != ErrClosingClosedEmitter {
keks's avatar
keks committed
151
		log.Errorf("error closing ResponseEmitter: %s", closeErr)
152 153
	}
}
Jan Winkelmann's avatar
Jan Winkelmann committed
154

155 156
func (c *Command) call(req *Request, re ResponseEmitter, env Environment) error {
	cmd, err := c.Get(req.Path)
Matt Bell's avatar
Matt Bell committed
157
	if err != nil {
keks's avatar
keks committed
158
		log.Errorf("could not get cmd from path %q: %q", req.Path, err)
159
		return err
Matt Bell's avatar
Matt Bell committed
160
	}
Matt Bell's avatar
Matt Bell committed
161

162
	if cmd.Run == nil {
keks's avatar
keks committed
163
		log.Errorf("returned command has nil Run function")
164
		return err
Matt Bell's avatar
Matt Bell committed
165
	}
Matt Bell's avatar
Matt Bell committed
166

167
	err = cmd.CheckArguments(req)
168
	if err != nil {
keks's avatar
keks committed
169
		log.Errorf("CheckArguments returned an error for path %q: %q", req.Path, err)
170
		return err
171 172
	}

173
	return cmd.Run(req, re, env)
174
}
Jan Winkelmann's avatar
Jan Winkelmann committed
175

176
// Resolve returns the subcommands at the given path
177
// The returned set of subcommands starts with this command and therefore is always at least size 1
178 179
func (c *Command) Resolve(pth []string) ([]*Command, error) {
	cmds := make([]*Command, len(pth)+1)
Matt Bell's avatar
Matt Bell committed
180
	cmds[0] = c
Matt Bell's avatar
Matt Bell committed
181

Matt Bell's avatar
Matt Bell committed
182
	cmd := c
183
	for i, name := range pth {
keks's avatar
cleanup  
keks committed
184
		cmd = cmd.Subcommands[name]
Matt Bell's avatar
Matt Bell committed
185

Matt Bell's avatar
Matt Bell committed
186
		if cmd == nil {
187 188
			pathS := strings.Join(pth[:i], "/")
			return nil, fmt.Errorf("undefined command: %q", pathS)
Matt Bell's avatar
Matt Bell committed
189
		}
Matt Bell's avatar
Matt Bell committed
190

Matt Bell's avatar
Matt Bell committed
191 192
		cmds[i+1] = cmd
	}
Matt Bell's avatar
Matt Bell committed
193

Matt Bell's avatar
Matt Bell committed
194
	return cmds, nil
Matt Bell's avatar
Matt Bell committed
195 196
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
197
// Get resolves and returns the Command addressed by path
Matt Bell's avatar
Matt Bell committed
198
func (c *Command) Get(path []string) (*Command, error) {
Matt Bell's avatar
Matt Bell committed
199 200 201 202 203
	cmds, err := c.Resolve(path)
	if err != nil {
		return nil, err
	}
	return cmds[len(cmds)-1], nil
Matt Bell's avatar
Matt Bell committed
204 205
}

206
// GetOptions returns the options in the given path of commands
Steven Allen's avatar
Steven Allen committed
207 208
func (c *Command) GetOptions(path []string) (map[string]Option, error) {
	options := make([]Option, 0, len(c.Options))
Matt Bell's avatar
Matt Bell committed
209 210 211 212 213

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

Matt Bell's avatar
Matt Bell committed
215 216 217 218
	for _, cmd := range cmds {
		options = append(options, cmd.Options...)
	}

Steven Allen's avatar
Steven Allen committed
219
	optionsMap := make(map[string]Option)
Matt Bell's avatar
Matt Bell committed
220
	for _, opt := range options {
221
		for _, name := range opt.Names() {
222
			if _, found := optionsMap[name]; found {
223
				return nil, fmt.Errorf("option name %q used multiple times", name)
224 225
			}

Matt Bell's avatar
Matt Bell committed
226 227 228 229 230
			optionsMap[name] = opt
		}
	}

	return optionsMap, nil
231 232
}

233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285
// 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
}

286 287
// CheckArguments checks that we have all the required string arguments, loading
// any from stdin if necessary.
288
func (c *Command) CheckArguments(req *Request) error {
289 290 291
	if len(c.Arguments) == 0 {
		return nil
	}
292

293
	lastArg := c.Arguments[len(c.Arguments)-1]
294
	if req.bodyArgs == nil && // check this as we can end up calling CheckArguments multiple times. See #80.
295
		lastArg.SupportsStdin &&
Steven Allen's avatar
Steven Allen committed
296
		lastArg.Type == ArgString &&
297 298
		req.Files != nil {

Łukasz Magiera's avatar
Łukasz Magiera committed
299 300 301
		it := req.Files.Entries()
		if it.Next() {
			req.bodyArgs = newArguments(files.FileFromEntry(it))
302 303
			// Can't pass files and stdin arguments.
			req.Files = nil
Łukasz Magiera's avatar
Łukasz Magiera committed
304 305 306 307
		} else {
			if it.Err() != nil {
				return it.Err()
			}
308 309 310
		}
	}

311
	// iterate over the arg definitions
312
	requiredStringArgs := 0 // number of required string arguments
313 314
	for _, argDef := range req.Command.Arguments {
		// Is this a string?
Steven Allen's avatar
Steven Allen committed
315
		if argDef.Type != ArgString {
316
			// No, skip it.
317 318
			continue
		}
319

320 321 322 323
		// No more required arguments?
		if !argDef.Required {
			// Yes, we're all done.
			break
324
		}
325
		requiredStringArgs++
326

327 328
		// Do we have enough string arguments?
		if requiredStringArgs <= len(req.Arguments) {
329 330
			// all good
			continue
331
		}
332

333 334
		// Can we get it from stdin?
		if argDef.SupportsStdin && req.bodyArgs != nil {
335
			if req.bodyArgs.Scan() {
336
				// Found it!
337
				req.Arguments = append(req.Arguments, req.bodyArgs.Argument())
338 339
				continue
			}
340
			if err := req.bodyArgs.Err(); err != nil {
341
				return err
342
			}
343
			// No, just missing.
344
		}
345
		return fmt.Errorf("argument %q is required", argDef.Name)
346 347 348 349 350
	}

	return nil
}

351 352 353 354 355
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
356 357
	for _, sub := range c.Subcommands {
		sub.Walk(visitor)
358 359 360 361 362 363 364 365 366 367 368 369
	}
}

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

370
func ClientError(msg string) error {
Steven Allen's avatar
Steven Allen committed
371
	return &Error{Code: ErrClient, Message: msg}
372
}