package server import ( "fmt" "log" "reflect" "sort" "strconv" "strings" "unsafe" ) const uintBitSize = 8 * int(unsafe.Sizeof(uint(0))) type parseFunc func(string) (interface{}, error) type parseSliceFunc func([]string) (interface{}, error) type cmd struct { name string args string desc string f interface{} cbs []parseFunc rest parseSliceFunc variadic bool } type node struct { c *cmd m map[string]*node } var cmds = newNode() func (c *cmd) run(r *request, v []string) { desc := func(n int) string { if n == 1 { return fmt.Sprint(n, " is expected") } return fmt.Sprint(n, " are expected") } argc := len(c.cbs) switch { case argc > len(v): r.Fatal("not enough args: ", desc(argc)) case argc < len(v) && c.rest == nil: if argc == 0 { r.Fatal("args are not expected") } r.Fatal("too many args: ", desc(argc)) } parse := func(f func(i interface{})) error { for n, cb := range c.cbs { i, err := cb(v[n]) if err != nil { return err } f(i) } if c.rest != nil { i, err := c.rest(v[argc:]) if err != nil { return err } f(i) } return nil } args := []reflect.Value{reflect.ValueOf(r)} err := parse(func(i interface{}) { args = append(args, reflect.ValueOf(i)) }) if err != nil { r.Fatal("args parse failed: ", err) } if c.variadic { reflect.ValueOf(c.f).CallSlice(args) } else { reflect.ValueOf(c.f).Call(args) } } func newNode() *node { return &node{m: map[string]*node{}} } func parseString(s string) (interface{}, error) { return s, nil } func parseInt(s string) (interface{}, error) { n, err := strconv.Atoi(s) if err != nil { return nil, fmt.Errorf("%s: bad integer", s) } return n, nil } func parseUint(s string) (interface{}, error) { n, err := strconv.ParseUint(s, 10, uintBitSize) if err != nil { return nil, fmt.Errorf("%s: bad unsigned integer", s) } return uint(n), nil } func parseSlice(t reflect.Type, f parseFunc, ss []string) (interface{}, error) { v := reflect.New(reflect.SliceOf(t)).Elem() for _, s := range ss { i, err := f(s) if err != nil { return nil, err } v = reflect.Append(v, reflect.ValueOf(i)) } return v.Interface(), nil } func newParseSlice(t reflect.Type, f parseFunc) parseSliceFunc { return func(ss []string) (interface{}, error) { return parseSlice(t, f, ss) } } var parseFuncMap = map[reflect.Kind]parseFunc{ reflect.Int: parseInt, reflect.Uint: parseUint, reflect.String: parseString, } func isPtrRequest(t reflect.Type) bool { if t.Kind() != reflect.Ptr { return false } if t.Elem().Kind() != reflect.Struct { return false } _, ok := reflect.New(t.Elem()).Interface().(*request) return ok } func (c *cmd) parseFuncSignature(i interface{}) error { t := reflect.TypeOf(i) if t.Kind() != reflect.Func { return fmt.Errorf("%s: not a function", t) } argc := t.NumIn() if argc < 1 || !isPtrRequest(t.In(0)) { return fmt.Errorf("%s: first arg *request is expected", t) } for n := 1; n < argc; n++ { slice := false t := t.In(n) if t.Kind() == reflect.Slice { if n < argc-1 { return fmt.Errorf("%s: slice cannot be in the middle", t) } slice = true t = t.Elem() } if f, ok := parseFuncMap[t.Kind()]; !ok { return fmt.Errorf("%s: unsupported %d arg type %s", t, n, t) } else if slice { c.rest = newParseSlice(t, f) } else { c.cbs = append(c.cbs, f) } } c.f = i c.variadic = t.IsVariadic() return nil } func newCmd(where string, f interface{}, args string) { c := &cmd{name: where, args: args} if err := c.parseFuncSignature(f); err != nil { log.Panicf("newCmd: %s", err) } path := strings.Fields(where) node := cmds for _, name := range path { if name == "" { panic("invalid command path") } v := node.m[name] if v == nil { v = newNode() node.m[name] = v } node = v } if node.c != nil { log.Panicf("handler already registered at '%s'", where) } node.c = c } func runCmd(r *request, args []string) { var c *cmd var v []string node := cmds for n, name := range args { node = node.m[name] if node == nil { break } if node.c != nil { c = node.c v = args[n+1:] break } } if c == nil { r.Fatal("command not found") } c.run(r, v) } func help(r *request, args []string) { var ss []string var walker func([]string, *node) walker = func(path []string, node *node) { if node.c != nil { ss = append(ss, strings.Join(path, " ")) return } n := len(path) path = append(path, "") for k, v := range node.m { path[n] = k walker(path, v) } } node := cmds for _, s := range args { node = node.m[s] if node == nil { break } } if node == nil { r.Print("nothing to help") return } if node.c != nil { r.Println("usage:", node.c.name, node.c.args) return } walker(nil, node) sort.Strings(ss) for _, s := range ss { r.Println(s) } } func init() { newCmd("help", help, "[path]") }