command.go 3 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 6
	"fmt"
	"strings"
7 8

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

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

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

17 18 19 20 21
// 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)

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
22
// Command is a runnable command, with input arguments and options (flags).
23
// It can also have Subcommands, to group units of work into sets.
Matt Bell's avatar
Matt Bell committed
24
type Command struct {
25 26
	Help        string
	Options     []Option
27
	Arguments   []Argument
28
	Run         Function
29
	Format      Formatter
30
	Type        interface{}
31
	Subcommands map[string]*Command
Matt Bell's avatar
Matt Bell committed
32 33
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
34 35
// 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
36

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

39
// Call invokes the command for the given Request
40 41
func (c *Command) Call(req Request) Response {
	res := NewResponse(req)
Matt Bell's avatar
Matt Bell committed
42

43
	cmds, err := c.Resolve(req.Path())
Matt Bell's avatar
Matt Bell committed
44
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
45
		res.SetError(err, ErrClient)
Matt Bell's avatar
Matt Bell committed
46 47 48
		return res
	}
	cmd := cmds[len(cmds)-1]
Matt Bell's avatar
Matt Bell committed
49

50
	if cmd.Run == nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
51
		res.SetError(ErrNotCallable, ErrClient)
Matt Bell's avatar
Matt Bell committed
52 53
		return res
	}
Matt Bell's avatar
Matt Bell committed
54

55
	options, err := c.GetOptions(req.Path())
Matt Bell's avatar
Matt Bell committed
56
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
57
		res.SetError(err, ErrClient)
Matt Bell's avatar
Matt Bell committed
58 59
		return res
	}
Matt Bell's avatar
Matt Bell committed
60

61
	err = req.ConvertOptions(options)
Matt Bell's avatar
Matt Bell committed
62
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
63
		res.SetError(err, ErrClient)
Matt Bell's avatar
Matt Bell committed
64 65
		return res
	}
Matt Bell's avatar
Matt Bell committed
66

67
	cmd.Run(res, req)
Matt Bell's avatar
Matt Bell committed
68 69

	return res
Matt Bell's avatar
Matt Bell committed
70 71
}

Matt Bell's avatar
Matt Bell committed
72 73
// Resolve gets the subcommands at the given path
func (c *Command) Resolve(path []string) ([]*Command, error) {
Matt Bell's avatar
Matt Bell committed
74 75
	cmds := make([]*Command, len(path)+1)
	cmds[0] = c
Matt Bell's avatar
Matt Bell committed
76

Matt Bell's avatar
Matt Bell committed
77 78
	cmd := c
	for i, name := range path {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
79
		cmd = cmd.Subcommand(name)
Matt Bell's avatar
Matt Bell committed
80

Matt Bell's avatar
Matt Bell committed
81 82 83 84
		if cmd == nil {
			pathS := strings.Join(path[0:i], "/")
			return nil, fmt.Errorf("Undefined command: '%s'", pathS)
		}
Matt Bell's avatar
Matt Bell committed
85

Matt Bell's avatar
Matt Bell committed
86 87
		cmds[i+1] = cmd
	}
Matt Bell's avatar
Matt Bell committed
88

Matt Bell's avatar
Matt Bell committed
89
	return cmds, nil
Matt Bell's avatar
Matt Bell committed
90 91
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
92
// Get resolves and returns the Command addressed by path
Matt Bell's avatar
Matt Bell committed
93
func (c *Command) Get(path []string) (*Command, error) {
Matt Bell's avatar
Matt Bell committed
94 95 96 97 98
	cmds, err := c.Resolve(path)
	if err != nil {
		return nil, err
	}
	return cmds[len(cmds)-1], nil
Matt Bell's avatar
Matt Bell committed
99 100
}

101 102
// 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
103 104 105 106 107 108
	options := make([]Option, len(c.Options))

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

Matt Bell's avatar
Matt Bell committed
111 112 113 114 115 116 117
	for _, cmd := range cmds {
		options = append(options, cmd.Options...)
	}

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

Matt Bell's avatar
Matt Bell committed
122 123 124 125 126
			optionsMap[name] = opt
		}
	}

	return optionsMap, nil
127 128
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
129 130
// Subcommand returns the subcommand with the given id
func (c *Command) Subcommand(id string) *Command {
131
	return c.Subcommands[id]
132
}