summaryrefslogtreecommitdiff
path: root/pkg/server/cmds.go
diff options
context:
space:
mode:
authorMikhail Osipov <mike.osipov@gmail.com>2021-03-08 02:25:01 +0300
committerMikhail Osipov <mike.osipov@gmail.com>2021-03-08 02:25:01 +0300
commita6e6d848d6f719710a43ca015027bd93befb721d (patch)
tree8cd94dfb8f466ca9403072c76883d95b41fbe0ef /pkg/server/cmds.go
parent95ae97eb7eaa268a06bc2b90184eaed6342b3142 (diff)
add cmd, hook, socket help
Diffstat (limited to 'pkg/server/cmds.go')
-rw-r--r--pkg/server/cmds.go225
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]")
+}