kos/entry/cli/router.go

178 lines
3.6 KiB
Go

package cli
import (
"errors"
"fmt"
"github.com/mattn/go-runewidth"
"strconv"
"strings"
)
var (
ErrNotFound = errors.New("not found")
)
type Router struct {
name string
path []string
children []*Router
command Command
params []string
}
func (r *Router) getChildren(name string) *Router {
for _, child := range r.children {
if child.name == name {
return child
}
}
return nil
}
func (r *Router) Completer(tokens ...string) []string {
ss := make([]string, 0, 10)
if len(tokens) == 0 {
for _, child := range r.children {
ss = append(ss, strings.Join(child.path, " "))
}
return ss
}
children := r.getChildren(tokens[0])
if children == nil {
token := tokens[0]
for _, child := range r.children {
if strings.HasPrefix(child.name, token) {
ss = append(ss, strings.Join(child.path, " "))
}
}
return ss
}
return children.Completer(tokens[1:]...)
}
func (r *Router) Usage() string {
if len(r.path) <= 0 {
return ""
}
var (
sb strings.Builder
)
sb.WriteString("Usage: ")
sb.WriteString(strings.Join(r.path, " "))
if len(r.params) > 0 {
for _, s := range r.params {
sb.WriteString(" {" + s + "}")
}
}
return sb.String()
}
func (r *Router) Handle(path string, command Command) {
var (
pos int
name string
)
if strings.HasSuffix(path, "/") {
path = strings.TrimSuffix(path, "/")
}
if strings.HasPrefix(path, "/") {
path = strings.TrimPrefix(path, "/")
}
if path == "" {
r.command = command
return
}
if path[0] == ':' {
ss := strings.Split(path, "/")
for _, s := range ss {
r.params = append(r.params, strings.TrimPrefix(s, ":"))
}
r.command = command
return
}
if pos = strings.IndexByte(path, '/'); pos > -1 {
name = path[:pos]
path = path[pos:]
} else {
name = path
path = ""
}
children := r.getChildren(name)
if children == nil {
children = newRouter(name)
if len(r.path) == 0 {
children.path = append(children.path, name)
} else {
children.path = append(children.path, r.path...)
children.path = append(children.path, name)
}
r.children = append(r.children, children)
}
if children.command.Handle != nil {
panic("a handle is already registered for path /" + strings.Join(children.path, "/"))
}
children.Handle(path, command)
}
func (r *Router) Lookup(tokens []string) (router *Router, args []string, err error) {
if len(tokens) > 0 {
children := r.getChildren(tokens[0])
if children != nil {
return children.Lookup(tokens[1:])
}
}
if r.command.Handle == nil {
err = ErrNotFound
return
}
router = r
args = tokens
return
}
func (r *Router) String() string {
var (
sb strings.Builder
width int
maxWidth int
walkFunc func(router *Router) []commander
)
walkFunc = func(router *Router) []commander {
vs := make([]commander, 0, 5)
if router.command.Handle != nil {
vs = append(vs, commander{
Name: router.name,
Path: strings.Join(router.path, " "),
Description: router.command.Description,
})
} else {
if len(router.children) > 0 {
for _, child := range router.children {
vs = append(vs, walkFunc(child)...)
}
}
}
return vs
}
vs := walkFunc(r)
for _, v := range vs {
width = runewidth.StringWidth(v.Path)
if width > maxWidth {
maxWidth = width
}
}
for _, v := range vs {
sb.WriteString(fmt.Sprintf("%-"+strconv.Itoa(maxWidth+4)+"s %s\n", v.Path, v.Description))
}
return sb.String()
}
func newRouter(name string) *Router {
return &Router{
name: name,
path: make([]string, 0, 4),
params: make([]string, 0, 4),
children: make([]*Router, 0, 10),
}
}