diff options
| author | Mikhail Osipov <mike.osipov@gmail.com> | 2021-03-08 02:25:01 +0300 |
|---|---|---|
| committer | Mikhail Osipov <mike.osipov@gmail.com> | 2021-03-08 02:25:01 +0300 |
| commit | a6e6d848d6f719710a43ca015027bd93befb721d (patch) | |
| tree | 8cd94dfb8f466ca9403072c76883d95b41fbe0ef /pkg/server/cmds.go | |
| parent | 95ae97eb7eaa268a06bc2b90184eaed6342b3142 (diff) | |
add cmd, hook, socket help
Diffstat (limited to 'pkg/server/cmds.go')
| -rw-r--r-- | pkg/server/cmds.go | 225 |
1 files changed, 203 insertions, 22 deletions
diff --git a/pkg/server/cmds.go b/pkg/server/cmds.go index dbd7083..a525b86 100644 --- a/pkg/server/cmds.go +++ b/pkg/server/cmds.go @@ -1,14 +1,29 @@ 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 - f func(r *request) + args string + desc string + f interface{} + cbs []parseFunc + rest parseSliceFunc + + variadic bool } type node struct { @@ -18,15 +33,171 @@ type node struct { var cmds = newNode() -func init() { - newCmd(help, "help") +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 newCmd(f func(r *request), where string) { +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, 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 @@ -48,30 +219,36 @@ func newCmd(f func(r *request), where string) { log.Panicf("handler already registered at '%s'", where) } - node.c = &cmd{ - name: where, - f: f, - } + node.c = c } -func getCmd(path []string) (*cmd, []string) { +func runCmd(r *request, args []string) { + var c *cmd + var v []string + node := cmds - for n, name := range path { + for n, name := range args { node = node.m[name] if node == nil { - return nil, nil + break } if node.c != nil { - return node.c, path[n+1:] + c = node.c + v = args[n+1:] + break } } - return nil, nil + if c == nil { + r.Fatal("command not found") + } + + c.run(r, v) } -func help(r *request) { +func help(r *request, args []string) { var ss []string var walker func([]string, *node) @@ -91,27 +268,27 @@ func help(r *request) { } } - root := cmds + node := cmds - for _, s := range r.args { - root = root.m[s] + for _, s := range args { + node = node.m[s] - if root == nil { + if node == nil { break } } - if root == nil { + if node == nil { r.Print("nothing to help") return } - if root.c != nil { - r.Print("usage: ", root.c.name, " [...]") + if node.c != nil { + r.Println("usage:", node.c.name, node.c.args) return } - walker(nil, root) + walker(nil, node) sort.Strings(ss) @@ -119,3 +296,7 @@ func help(r *request) { r.Println(s) } } + +func init() { + newCmd("help", help, "[path]") +} |
