command.go 2.37 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 7 8
)

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

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

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

43
  options, err := cmd.GetOptions(req.path)
44 45 46 47
  if err != nil {
    res.SetError(err, Client)
    return res
  }
Matt Bell's avatar
Matt Bell committed
48

49 50 51 52 53
	err = req.convertOptions(options)
  if err != nil {
    res.SetError(err, Client)
    return res
  }
Matt Bell's avatar
Matt Bell committed
54 55 56 57

	cmd.f(req, res)

	return res
Matt Bell's avatar
Matt Bell committed
58 59
}

60 61 62 63 64 65
// 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...)

66
  // a nil path means this command, not a subcommand (same as an empty path)
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
  if path != nil {
    for i, id := range path {
      cmd := c.Sub(id)

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

      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
}

Matt Bell's avatar
Matt Bell committed
90 91
// Sub returns the subcommand with the given id
func (c *Command) Sub(id string) *Command {
Matt Bell's avatar
Matt Bell committed
92
	return c.subcommands[id]
Matt Bell's avatar
Matt Bell committed
93
}
94 95

func (c *Command) checkOptions(names map[string]bool) error {
Matt Bell's avatar
Matt Bell committed
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
	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
113
}