command.go 3.14 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"
Matt Bell's avatar
Matt Bell committed
7 8
)

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
9
// Command is an object that defines a command.
Matt Bell's avatar
Matt Bell committed
10
type Command struct {
Matt Bell's avatar
Matt Bell committed
11 12 13 14
	Help        string
	Options     []Option
	f           func(*Request, *Response)
	subcommands map[string]*Command
Matt Bell's avatar
Matt Bell committed
15 16
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
17 18
// 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
19

Matt Bell's avatar
Matt Bell committed
20 21
// Register adds a subcommand
func (c *Command) Register(id string, sub *Command) error {
Matt Bell's avatar
Matt Bell committed
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
	if c.subcommands == nil {
		c.subcommands = make(map[string]*Command)
	}

	// check for duplicate option names (only checks downwards)
	names := make(map[string]bool)
	globalCommand.checkOptions(names)
	c.checkOptions(names)
	err := sub.checkOptions(names)
	if err != nil {
		return err
	}

	if _, ok := c.subcommands[id]; ok {
		return fmt.Errorf("There is already a subcommand registered with id '%s'", id)
	}

	c.subcommands[id] = sub
	return nil
Matt Bell's avatar
Matt Bell committed
41 42 43
}

// Call invokes the command at the given subcommand path
44
func (c *Command) Call(req *Request) *Response {
Matt Bell's avatar
Matt Bell committed
45 46
	res := &Response{req: req}

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

Matt Bell's avatar
Matt Bell committed
54
	if cmd.f == nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
55
		res.SetError(ErrNotCallable, ErrClient)
Matt Bell's avatar
Matt Bell committed
56 57
		return res
	}
Matt Bell's avatar
Matt Bell committed
58

Matt Bell's avatar
Matt Bell committed
59 60
	options, err := c.GetOptions(req.path)
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
61
		res.SetError(err, ErrClient)
Matt Bell's avatar
Matt Bell committed
62 63
		return res
	}
Matt Bell's avatar
Matt Bell committed
64

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

	cmd.f(req, res)

	return res
Matt Bell's avatar
Matt Bell committed
74 75
}

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

Matt Bell's avatar
Matt Bell committed
81 82 83
	cmd := c
	for i, name := range path {
		cmd = cmd.Sub(name)
Matt Bell's avatar
Matt Bell committed
84

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

Matt Bell's avatar
Matt Bell committed
90 91
		cmds[i+1] = cmd
	}
Matt Bell's avatar
Matt Bell committed
92

Matt Bell's avatar
Matt Bell committed
93
	return cmds, nil
Matt Bell's avatar
Matt Bell committed
94 95
}

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

105 106
// 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
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
	options := make([]Option, len(c.Options))
	copy(options, c.Options)
	options = append(options, globalOptions...)

	cmds, err := c.Resolve(path)
	if err != nil {
		return nil, err
	}
	for _, cmd := range cmds {
		options = append(options, cmd.Options...)
	}

	optionsMap := make(map[string]Option)
	for _, opt := range options {
		for _, name := range opt.Names {
			optionsMap[name] = opt
		}
	}

	return optionsMap, nil
127 128
}

Matt Bell's avatar
Matt Bell committed
129 130
// Sub returns the subcommand with the given id
func (c *Command) Sub(id string) *Command {
Matt Bell's avatar
Matt Bell committed
131
	return c.subcommands[id]
Matt Bell's avatar
Matt Bell committed
132
}
133 134

func (c *Command) checkOptions(names map[string]bool) error {
Matt Bell's avatar
Matt Bell committed
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
	for _, opt := range c.Options {
		for _, name := range opt.Names {
			if _, ok := names[name]; ok {
				return fmt.Errorf("Multiple options are using the same name ('%s')", name)
			}
			names[name] = true
		}
	}

	for _, cmd := range c.subcommands {
		err := cmd.checkOptions(names)
		if err != nil {
			return err
		}
	}

	return nil
152
}