command.go 2.66 KB
Newer Older
Matt Bell's avatar
Matt Bell committed
1 2 3 4 5 6 7 8 9 10 11
package commands

import (
  "fmt"
  "strings"
  "reflect"
)

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

// Register adds a subcommand
func (c *Command) Register(id string, sub *Command) error {
  if c.subcommands == nil {
    c.subcommands = make(map[string]*Command)
  }

22 23
  // check for duplicate option names (only checks downwards)
  names := make(map[string]bool)
24
  globalCommand.checkOptions(names)
25 26 27 28 29
  c.checkOptions(names)
  err := sub.checkOptions(names)
  if err != nil {
    return err
  }
Matt Bell's avatar
Matt Bell committed
30 31 32 33 34 35 36 37 38 39

  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
}

// Call invokes the command at the given subcommand path
40
func (c *Command) Call(path []string, req *Request) *Response {
Matt Bell's avatar
Matt Bell committed
41 42
  options := make([]Option, len(c.Options))
  copy(options, c.Options)
43
  options = append(options, globalOptions...)
Matt Bell's avatar
Matt Bell committed
44
  cmd := c
45
  res := &Response{ req: req }
Matt Bell's avatar
Matt Bell committed
46 47 48 49 50 51 52

  if path != nil {
    for i, id := range path {
      cmd = c.Sub(id)

      if cmd == nil {
        pathS := strings.Join(path[0:i], "/")
53 54
        res.SetError(fmt.Errorf("Undefined command: '%s'", pathS), Client)
        return res
Matt Bell's avatar
Matt Bell committed
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
      }

      options = append(options, cmd.Options...)
    }
  }

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

  for k, v := range req.options {
    opt, ok := optionsMap[k]

    if !ok {
72 73
      res.SetError(fmt.Errorf("Unrecognized command option: '%s'", k), Client)
      return res
Matt Bell's avatar
Matt Bell committed
74 75 76 77
    }

    for _, name := range opt.Names {
      if _, ok = req.options[name]; name != k && ok {
78 79 80
        res.SetError(fmt.Errorf("Duplicate command options were provided ('%s' and '%s')",
          k, name), Client)
        return res
Matt Bell's avatar
Matt Bell committed
81 82 83 84 85
      }
    }

    kind := reflect.TypeOf(v).Kind()
    if kind != opt.Type {
86 87 88
      res.SetError(fmt.Errorf("Option '%s' should be type '%s', but got type '%s'",
        k, opt.Type.String(), kind.String()), Client)
      return res
Matt Bell's avatar
Matt Bell committed
89 90 91
    }
  }

92 93 94
  cmd.f(req, res)

  return res
Matt Bell's avatar
Matt Bell committed
95 96 97 98 99 100
}

// Sub returns the subcommand with the given id
func (c *Command) Sub(id string) *Command {
  return c.subcommands[id]
}
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120

func (c *Command) checkOptions(names map[string]bool) error {
  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
}