package cli import ( "errors" "fmt" "git.nspix.com/golang/micro/utils/console" "reflect" "sort" "strconv" "strings" "sync" "sync/atomic" "time" ) var ( _seq int64 ErrInvalidHandle = errors.New("invalid handle kind") ErrInvalidHandleResponse = errors.New("invalid handle response") errInterface = reflect.TypeOf((*error)(nil)).Elem() stringSliceKind = reflect.TypeOf([]string{}).Kind() ) type Executor struct { name string parent *Executor usage string description string handle interface{} children map[string]*Executor createdTime time.Time seq int64 locker sync.RWMutex NotFoundHandle func(ctx *Context, cmd string) ([]byte, error) } type ( executors []*Executor ) func (p executors) Swap(i, j int) { p[i], p[j] = p[j], p[i] } func (p executors) Len() int { return len(p) } func (p executors) Less(i, j int) bool { return p[i].seq < p[j].seq } func (exec *Executor) hasChildren(name string) bool { exec.locker.RLock() defer exec.locker.RUnlock() if name == "" { return len(exec.children) > 0 } if _, ok := exec.children[name]; ok { return true } return false } func (exec *Executor) getDescription() string { if exec.description != "" { return exec.description } var p *Executor var text string p = exec for { if p.parent == nil { break } text = p.name + " " + text p = p.parent } return text } func (exec *Executor) Children(name string) (*Executor, error) { exec.locker.RLock() defer exec.locker.RUnlock() if cmd, ok := exec.children[name]; ok { return cmd, nil } return nil, errors.New("children not found") } func (exec *Executor) Completer(tokens ...string) []string { exec.locker.RLock() defer exec.locker.RUnlock() p := exec.children length := len(tokens) var token, prefix string result := make([]string, 0) if length > 0 { for i := 0; i < length; i++ { token = tokens[i] if i == length-1 { for k, e := range p { if token == k { if e.children != nil { for kk, _ := range e.children { result = append(result, prefix+token+" "+kk) } } } else if strings.HasPrefix(k, token) { result = append(result, prefix+k) } } } else { if next, ok := p[token]; !ok || next.children == nil { return []string{} } else { p = next.children } } prefix += token + " " } } else { for k, _ := range p { result = append(result, k) } } return result } func (exec *Executor) String() string { values := make([][]interface{}, 0) var loop func(string, *Executor) loop = func(prefix string, e *Executor) { if e.children == nil { return } vs := make(executors, len(e.children)) var i int for _, v := range e.children { vs[i] = v i++ } sort.Sort(vs) for _, v := range vs { if prefix == "" { values = append(values, []interface{}{v.name, v.getDescription()}) } else { values = append(values, []interface{}{prefix + " " + v.name, v.getDescription()}) } if prefix == "" { loop(v.name, v) } else { loop(prefix+" "+v.name, v) } } } loop("", exec) return string(console.Pretty(values, nil)) } func (exec *Executor) Append(child ...*Executor) *Executor { exec.locker.Lock() defer exec.locker.Unlock() for _, v := range child { v.parent = exec exec.children[v.name] = v } return exec } func (exec *Executor) WithHandle(v interface{}) *Executor { exec.handle = v return exec } func (exec *Executor) Do(ctx *Context, args ...string) (b []byte, err error) { var ( root interface{} ) if root = ctx.Get("ROOT"); root == nil { ctx.Set("ROOT", exec) } ctx.Set("arguments", args) if len(args) > 0 && exec.hasChildren(args[0]) { if len(args) > 1 { return exec.children[args[0]].Do(ctx, args[1:]...) } else { return exec.children[args[0]].Do(ctx) } } if exec.handle == nil { if exec.NotFoundHandle != nil { b, err = exec.NotFoundHandle(ctx, ctx.CmdStr) } else { err = fmt.Errorf("%s not found", ctx.CmdStr) } return } refVal := reflect.ValueOf(exec.handle) refType := refVal.Type() if refType.Kind() != reflect.Func { err = ErrInvalidHandle return } //checking args if refType.NumIn() > 0 && len(args)+1 < refType.NumIn() { var usage string if exec.usage == "" { usage = "Usage: " + ctx.CmdStr + " [" for i := 1; i < refType.NumIn(); i++ { usage += " " + refType.In(i).String() } usage += " ]" } else { usage = "Usage: " + exec.usage } err = errors.New(usage) return } arguments := make([]reflect.Value, refType.NumIn()) for i := 0; i < refType.NumIn(); i++ { if i == 0 { arguments[i] = reflect.ValueOf(ctx) continue } switch refType.In(i).Kind() { case reflect.String: arguments[i] = reflect.ValueOf(args[i-1]) case reflect.Int: n, _ := strconv.ParseInt(args[i-1], 10, 64) arguments[i] = reflect.ValueOf(int(n)) case reflect.Int32: n, _ := strconv.ParseInt(args[i-1], 10, 32) arguments[i] = reflect.ValueOf(int32(n)) case reflect.Int64: n, _ := strconv.ParseInt(args[i-1], 10, 64) arguments[i] = reflect.ValueOf(n) case reflect.Float32: n, _ := strconv.ParseFloat(args[i-1], 32) arguments[i] = reflect.ValueOf(float32(n)) case reflect.Float64: n, _ := strconv.ParseFloat(args[i-1], 64) arguments[i] = reflect.ValueOf(n) case stringSliceKind: arguments[i] = reflect.ValueOf(args[i-1:]) default: err = fmt.Errorf("unsupported argument %d kind %s", i-1, refType.In(i).Kind().String()) return } } values := refVal.Call(arguments) for _, v := range values { if v.Type().Implements(errInterface) { err = v.Interface().(error) } else if v.Kind() == reflect.Slice { b = v.Bytes() } } return } func NewExecutor(args ...string) *Executor { var ( name string usage string description string ) if len(args) > 0 { name = args[0] } if len(args) > 1 { usage = args[1] } if len(args) > 2 { description = args[2] } return &Executor{ name: name, usage: usage, createdTime: time.Now(), description: description, children: make(map[string]*Executor), seq: atomic.AddInt64(&_seq, 1), } }