command.go 5.92 KB
Newer Older
Matt Bell's avatar
Matt Bell committed
1 2 3
package commands

import (
Matt Bell's avatar
Matt Bell committed
4
	"errors"
Matt Bell's avatar
Matt Bell committed
5
	"fmt"
6
	"io"
Matt Bell's avatar
Matt Bell committed
7
	"strings"
8 9

	u "github.com/jbenet/go-ipfs/util"
Matt Bell's avatar
Matt Bell committed
10 11
)

12 13
var log = u.Logger("command")

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
14 15
// Function is the type of function that Commands use.
// It reads from the Request, and writes results to the Response.
16
type Function func(Request) (interface{}, error)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
17

18
// Marshaller is a function that takes in a Response, and returns a marshalled []byte
19
// (or an error on failure)
20
type Marshaller func(Response) ([]byte, error)
21

22 23 24 25 26 27
// TODO: check Argument definitions when creating a Command
//   (might need to use a Command constructor)
//   * make sure any variadic args are at the end
//   * make sure there aren't duplicate names
//   * make sure optional arguments aren't followed by required arguments

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
28
// Command is a runnable command, with input arguments and options (flags).
29
// It can also have Subcommands, to group units of work into sets.
Matt Bell's avatar
Matt Bell committed
30
type Command struct {
31 32 33 34 35 36 37
	// MAYBE_TODO: move all the text fields into a struct
	// MAYBE_TODO: move these out of command and put them somewhere in commands/cli
	Description    string
	Help           string
	SubcommandHelp string
	OptionHelp     string
	ArgumentHelp   string
38

39
	Options     []Option
40
	Arguments   []Argument
41
	Run         Function
42
	Marshallers map[EncodingType]Marshaller
Brian Tiger Chow's avatar
Brian Tiger Chow committed
43 44

	// Type describes the type of the output of the Command's Run Function.
Brian Tiger Chow's avatar
Brian Tiger Chow committed
45 46
	// 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
47 48
	//
	// ie. If command Run returns &Block{}, then Command.Type == &Block{}
49
	Type        interface{}
50
	Subcommands map[string]*Command
Matt Bell's avatar
Matt Bell committed
51 52
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
53 54
// ErrNotCallable signals a command that cannot be called.
var ErrNotCallable = errors.New("This command can't be called directly. Try one of its subcommands.")
Matt Bell's avatar
Matt Bell committed
55

56 57
var ErrNoFormatter = errors.New("This command cannot be formatted to plain text")

58
// Call invokes the command for the given Request
59 60
func (c *Command) Call(req Request) Response {
	res := NewResponse(req)
Matt Bell's avatar
Matt Bell committed
61

62
	cmds, err := c.Resolve(req.Path())
Matt Bell's avatar
Matt Bell committed
63
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
64
		res.SetError(err, ErrClient)
Matt Bell's avatar
Matt Bell committed
65 66 67
		return res
	}
	cmd := cmds[len(cmds)-1]
Matt Bell's avatar
Matt Bell committed
68

69
	if cmd.Run == nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
70
		res.SetError(ErrNotCallable, ErrClient)
Matt Bell's avatar
Matt Bell committed
71 72
		return res
	}
Matt Bell's avatar
Matt Bell committed
73

74
	err = cmd.CheckArguments(req)
75 76 77 78 79
	if err != nil {
		res.SetError(err, ErrClient)
		return res
	}

80
	err = req.ConvertOptions()
Matt Bell's avatar
Matt Bell committed
81
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
82
		res.SetError(err, ErrClient)
Matt Bell's avatar
Matt Bell committed
83 84
		return res
	}
Matt Bell's avatar
Matt Bell committed
85

86 87 88 89 90 91 92 93 94 95 96 97 98
	output, err := cmd.Run(req)
	if err != nil {
		// if returned error is a commands.Error, use its error code
		// otherwise, just default the code to ErrNormal
		var e Error
		e, ok := err.(Error)
		if ok {
			res.SetError(e, e.Code)
		} else {
			res.SetError(err, ErrNormal)
		}
		return res
	}
Matt Bell's avatar
Matt Bell committed
99

100
	res.SetOutput(output)
Matt Bell's avatar
Matt Bell committed
101
	return res
Matt Bell's avatar
Matt Bell committed
102 103
}

Matt Bell's avatar
Matt Bell committed
104 105
// Resolve gets the subcommands at the given path
func (c *Command) Resolve(path []string) ([]*Command, error) {
Matt Bell's avatar
Matt Bell committed
106 107
	cmds := make([]*Command, len(path)+1)
	cmds[0] = c
Matt Bell's avatar
Matt Bell committed
108

Matt Bell's avatar
Matt Bell committed
109 110
	cmd := c
	for i, name := range path {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
111
		cmd = cmd.Subcommand(name)
Matt Bell's avatar
Matt Bell committed
112

Matt Bell's avatar
Matt Bell committed
113 114 115 116
		if cmd == nil {
			pathS := strings.Join(path[0:i], "/")
			return nil, fmt.Errorf("Undefined command: '%s'", pathS)
		}
Matt Bell's avatar
Matt Bell committed
117

Matt Bell's avatar
Matt Bell committed
118 119
		cmds[i+1] = cmd
	}
Matt Bell's avatar
Matt Bell committed
120

Matt Bell's avatar
Matt Bell committed
121
	return cmds, nil
Matt Bell's avatar
Matt Bell committed
122 123
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
124
// Get resolves and returns the Command addressed by path
Matt Bell's avatar
Matt Bell committed
125
func (c *Command) Get(path []string) (*Command, error) {
Matt Bell's avatar
Matt Bell committed
126 127 128 129 130
	cmds, err := c.Resolve(path)
	if err != nil {
		return nil, err
	}
	return cmds[len(cmds)-1], nil
Matt Bell's avatar
Matt Bell committed
131 132
}

133 134
// GetOptions gets the options in the given path of commands
func (c *Command) GetOptions(path []string) (map[string]Option, error) {
Matt Bell's avatar
Matt Bell committed
135 136 137 138 139 140
	options := make([]Option, len(c.Options))

	cmds, err := c.Resolve(path)
	if err != nil {
		return nil, err
	}
141 142
	cmds = append(cmds, globalCommand)

Matt Bell's avatar
Matt Bell committed
143 144 145 146 147 148 149
	for _, cmd := range cmds {
		options = append(options, cmd.Options...)
	}

	optionsMap := make(map[string]Option)
	for _, opt := range options {
		for _, name := range opt.Names {
150 151 152 153
			if _, found := optionsMap[name]; found {
				return nil, fmt.Errorf("Option name '%s' used multiple times", name)
			}

Matt Bell's avatar
Matt Bell committed
154 155 156 157 158
			optionsMap[name] = opt
		}
	}

	return optionsMap, nil
159 160
}

161 162
func (c *Command) CheckArguments(req Request) error {
	args := req.Arguments()
163
	argDefs := c.Arguments
164

165 166 167 168 169
	// if we have more arg values provided than argument definitions,
	// and the last arg definition is not variadic (or there are no definitions), return an error
	notVariadic := len(argDefs) == 0 || !argDefs[len(argDefs)-1].Variadic
	if notVariadic && len(args) > len(argDefs) {
		return fmt.Errorf("Expected %v arguments, got %v", len(argDefs), len(args))
170 171
	}

172
	// count required argument definitions
173
	numRequired := 0
174 175
	for _, argDef := range c.Arguments {
		if argDef.Required {
176
			numRequired++
177 178 179
		}
	}

180
	// iterate over the arg definitions
Matt Bell's avatar
Matt Bell committed
181
	valueIndex := 0 // the index of the current value (in `args`)
182 183
	for _, argDef := range c.Arguments {
		// skip optional argument definitions if there aren't sufficient remaining values
Matt Bell's avatar
Matt Bell committed
184
		if len(args)-valueIndex <= numRequired && !argDef.Required {
185 186
			continue
		}
187

188 189
		// the value for this argument definition. can be nil if it wasn't provided by the caller
		var v interface{}
Matt Bell's avatar
Matt Bell committed
190 191 192
		if valueIndex < len(args) {
			v = args[valueIndex]
			valueIndex++
193 194
		}

195 196 197
		err := checkArgValue(v, argDef)
		if err != nil {
			return err
198
		}
199 200

		// any additional values are for the variadic arg definition
Matt Bell's avatar
Matt Bell committed
201 202
		if argDef.Variadic && valueIndex < len(args)-1 {
			for _, val := range args[valueIndex:] {
203 204 205 206
				err := checkArgValue(val, argDef)
				if err != nil {
					return err
				}
207 208 209 210 211 212 213
			}
		}
	}

	return nil
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
214 215
// Subcommand returns the subcommand with the given id
func (c *Command) Subcommand(id string) *Command {
216
	return c.Subcommands[id]
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

// checkArgValue returns an error if a given arg value is not valid for the given Argument
func checkArgValue(v interface{}, def Argument) error {
	if v == nil {
		if def.Required {
			return fmt.Errorf("Argument '%s' is required", def.Name)
		}

		return nil
	}

	if def.Type == ArgFile {
		_, ok := v.(io.Reader)
		if !ok {
			return fmt.Errorf("Argument '%s' isn't valid", def.Name)
		}

	} else if def.Type == ArgString {
		_, ok := v.(string)
		if !ok {
			return fmt.Errorf("Argument '%s' must be a string", def.Name)
		}
	}

	return nil
}