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

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

type Command struct {
Matt Bell's avatar
Matt Bell committed
10 11 12 13
	Help        string
	Options     []Option
	f           func(*Request, *Response)
	subcommands map[string]*Command
Matt Bell's avatar
Matt Bell committed
14 15
}

Matt Bell's avatar
Matt Bell committed
16 17
var NotCallableError = errors.New("This command can't be called directly. Try one of its subcommands.")

Matt Bell's avatar
Matt Bell committed
18 19
// Register adds a subcommand
func (c *Command) Register(id string, sub *Command) error {
Matt Bell's avatar
Matt Bell committed
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
	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
39 40 41
}

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

Matt Bell's avatar
Matt Bell committed
45 46 47 48 49 50 51 52 53 54 55 56 57
  cmds, err := c.Resolve(req.path)
  if err != nil {
    res.SetError(err, Client)
    return res
  }
  cmd := cmds[len(cmds)-1]

  if(cmd.f == nil) {
    res.SetError(NotCallableError, Client)
    return res
  }

  options, err := c.GetOptions(req.path)
58 59 60 61
  if err != nil {
    res.SetError(err, Client)
    return res
  }
Matt Bell's avatar
Matt Bell committed
62

63 64 65 66 67
	err = req.convertOptions(options)
  if err != nil {
    res.SetError(err, Client)
    return res
  }
Matt Bell's avatar
Matt Bell committed
68 69 70 71

	cmd.f(req, res)

	return res
Matt Bell's avatar
Matt Bell committed
72 73
}

Matt Bell's avatar
Matt Bell committed
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
// Resolve gets the subcommands at the given path
func (c *Command) Resolve(path []string) ([]*Command, error) {
  cmds := make([]*Command, len(path) + 1)
  cmds[0] = c

  cmd := c
  for i, name := range path {
    cmd = cmd.Sub(name)

    if cmd == nil {
      pathS := strings.Join(path[0:i], "/")
      return nil, fmt.Errorf("Undefined command: '%s'", pathS)
    }

    cmds[i+1] = cmd
  }

  return cmds, nil
}

94 95 96 97 98 99
// GetOptions gets the options in the given path of commands
func (c *Command) GetOptions(path []string) (map[string]Option, error) {
  options := make([]Option, len(c.Options))
  copy(options, c.Options)
  options = append(options, globalOptions...)

Matt Bell's avatar
Matt Bell committed
100 101 102 103 104 105
  cmds, err := c.Resolve(path)
  if err != nil {
    return nil, err
  }
  for _, cmd := range cmds {
    options = append(options, cmd.Options...)
106 107 108 109 110 111 112 113 114 115 116 117
  }

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

  return optionsMap, nil
}

Matt Bell's avatar
Matt Bell committed
118 119
// Sub returns the subcommand with the given id
func (c *Command) Sub(id string) *Command {
Matt Bell's avatar
Matt Bell committed
120
	return c.subcommands[id]
Matt Bell's avatar
Matt Bell committed
121
}
122 123

func (c *Command) checkOptions(names map[string]bool) error {
Matt Bell's avatar
Matt Bell committed
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
	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
141
}