command.go 1.88 KB
Newer Older
Matt Bell's avatar
Matt Bell committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
package commands

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

type Command struct {
  Help string
  Options []Option
  f func(*Request) (interface{}, error)
  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)
  }

  // TODO: check for duplicate option names

  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
func (c *Command) Call(path []string, req *Request) (interface{}, error) {
  options := make([]Option, len(c.Options))
  copy(options, c.Options)
  cmd := c

  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
    }
  }

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

    if !ok {
      return nil, fmt.Errorf("Unrecognized command option: '%s'", k)
    }

    for _, name := range opt.Names {
      if _, ok = req.options[name]; name != k && ok {
        return nil, fmt.Errorf("Duplicate command options were provided ('%s' and '%s')",
          k, name)
      }
    }

    kind := reflect.TypeOf(v).Kind()
    if kind != opt.Type {
      return nil, fmt.Errorf("Option '%s' should be type '%s', but got type '%s'",
        k, opt.Type.String(), kind.String())
    }
  }

  return cmd.f(req)
}

// Sub returns the subcommand with the given id
func (c *Command) Sub(id string) *Command {
  return c.subcommands[id]
}