command.go 4.25 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(Response, Request)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
17

18 19 20 21 22
// Formatter is a function that takes in a Response, and returns a human-readable string
// (or an error on failure)
// MAYBE_TODO: maybe this should be a io.Reader instead of a string?
type Formatter func(Response) (string, error)

23 24 25 26 27 28
// 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
29
// Command is a runnable command, with input arguments and options (flags).
30
// It can also have Subcommands, to group units of work into sets.
Matt Bell's avatar
Matt Bell committed
31
type Command struct {
32 33
	Help        string
	Options     []Option
34
	Arguments   []Argument
35
	Run         Function
36
	Format      Formatter
37
	Type        interface{}
38
	Subcommands map[string]*Command
Matt Bell's avatar
Matt Bell committed
39 40
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
41 42
// 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
43

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

46
// Call invokes the command for the given Request
47 48
func (c *Command) Call(req Request) Response {
	res := NewResponse(req)
Matt Bell's avatar
Matt Bell committed
49

50
	cmds, err := c.Resolve(req.Path())
Matt Bell's avatar
Matt Bell committed
51
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
52
		res.SetError(err, ErrClient)
Matt Bell's avatar
Matt Bell committed
53 54 55
		return res
	}
	cmd := cmds[len(cmds)-1]
Matt Bell's avatar
Matt Bell committed
56

57
	if cmd.Run == nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
58
		res.SetError(ErrNotCallable, ErrClient)
Matt Bell's avatar
Matt Bell committed
59 60
		return res
	}
Matt Bell's avatar
Matt Bell committed
61

62
	err = cmd.CheckArguments(req)
63 64 65 66 67
	if err != nil {
		res.SetError(err, ErrClient)
		return res
	}

68
	options, err := c.GetOptions(req.Path())
Matt Bell's avatar
Matt Bell committed
69
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
70
		res.SetError(err, ErrClient)
Matt Bell's avatar
Matt Bell committed
71 72
		return res
	}
Matt Bell's avatar
Matt Bell committed
73

74
	err = req.ConvertOptions(options)
Matt Bell's avatar
Matt Bell committed
75
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
76
		res.SetError(err, ErrClient)
Matt Bell's avatar
Matt Bell committed
77 78
		return res
	}
Matt Bell's avatar
Matt Bell committed
79

80
	cmd.Run(res, req)
Matt Bell's avatar
Matt Bell committed
81 82

	return res
Matt Bell's avatar
Matt Bell committed
83 84
}

Matt Bell's avatar
Matt Bell committed
85 86
// Resolve gets the subcommands at the given path
func (c *Command) Resolve(path []string) ([]*Command, error) {
Matt Bell's avatar
Matt Bell committed
87 88
	cmds := make([]*Command, len(path)+1)
	cmds[0] = c
Matt Bell's avatar
Matt Bell committed
89

Matt Bell's avatar
Matt Bell committed
90 91
	cmd := c
	for i, name := range path {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
92
		cmd = cmd.Subcommand(name)
Matt Bell's avatar
Matt Bell committed
93

Matt Bell's avatar
Matt Bell committed
94 95 96 97
		if cmd == nil {
			pathS := strings.Join(path[0:i], "/")
			return nil, fmt.Errorf("Undefined command: '%s'", pathS)
		}
Matt Bell's avatar
Matt Bell committed
98

Matt Bell's avatar
Matt Bell committed
99 100
		cmds[i+1] = cmd
	}
Matt Bell's avatar
Matt Bell committed
101

Matt Bell's avatar
Matt Bell committed
102
	return cmds, nil
Matt Bell's avatar
Matt Bell committed
103 104
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
105
// Get resolves and returns the Command addressed by path
Matt Bell's avatar
Matt Bell committed
106
func (c *Command) Get(path []string) (*Command, error) {
Matt Bell's avatar
Matt Bell committed
107 108 109 110 111
	cmds, err := c.Resolve(path)
	if err != nil {
		return nil, err
	}
	return cmds[len(cmds)-1], nil
Matt Bell's avatar
Matt Bell committed
112 113
}

114 115
// 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
116 117 118 119 120 121
	options := make([]Option, len(c.Options))

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

Matt Bell's avatar
Matt Bell committed
124 125 126 127 128 129 130
	for _, cmd := range cmds {
		options = append(options, cmd.Options...)
	}

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

Matt Bell's avatar
Matt Bell committed
135 136 137 138 139
			optionsMap[name] = opt
		}
	}

	return optionsMap, nil
140 141
}

142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
func (c *Command) CheckArguments(req Request) error {
	var argDef Argument
	args := req.Arguments()

	var length int
	if len(args) > len(c.Arguments) {
		length = len(args)
	} else {
		length = len(c.Arguments)
	}

	for i := 0; i < length; i++ {
		var arg interface{}
		if len(args) > i {
			arg = args[i]
		}

		if i < len(c.Arguments) {
			argDef = c.Arguments[i]
		} else if !argDef.Variadic {
			return fmt.Errorf("Expected %v arguments, got %v", len(c.Arguments), len(args))
		}

		if argDef.Required && arg == nil {
			return fmt.Errorf("Argument '%s' is required", argDef.Name)
		}
		if argDef.Type == ArgFile {
			_, ok := arg.(io.Reader)
			if !ok {
				return fmt.Errorf("Argument '%s' isn't valid", argDef.Name)
			}
		} else if argDef.Type == ArgString {
			_, ok := arg.(string)
			if !ok {
				return fmt.Errorf("Argument '%s' must be a string", argDef.Name)
			}
		}
	}

	return nil
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
184 185
// Subcommand returns the subcommand with the given id
func (c *Command) Subcommand(id string) *Command {
186
	return c.Subcommands[id]
187
}