Browse Source

First Release at 2020-06-07 19:36

zry 3 years ago
parent
commit
7271f0185f

+ 4 - 0
.gitignore

@@ -24,3 +24,7 @@ _testmain.go
 *.test
 *.prof
 
+*.log
+
+oldfiles
+

+ 33 - 0
examples/test01/main.go

@@ -0,0 +1,33 @@
+package main
+
+import (
+	"fmt"
+	"git.swzry.com/zry/go-hhc-cli/hhccli"
+	"git.swzry.com/zry/go-hhc-cli/oldfiles"
+	"golang.org/x/crypto/ssh/terminal"
+	"os"
+)
+
+func main() {
+	fdbg, err := os.OpenFile("debug.log", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
+	defer func() { _ = fdbg.Close() }()
+	if err != nil {
+		fmt.Println("Failed Open Debug Log File:", err.Error())
+		return
+	}
+	_, err = terminal.MakeRaw(int(os.Stdin.Fd()))
+	if err != nil {
+		fmt.Print("Failed Make Terminal Raw:", err.Error(), "\r\n")
+		return
+	}
+	sysview := hhccli.NewCliView(maketree())
+	cih := oldfiles.NewInteractiveCli(os.Stdin, os.Stdout, os.Stderr, "[GO-HHC-CLI]")
+	cih.SetDebugOut(fdbg)
+	cih.RegisterView("system", sysview)
+	_ = cih.SetCurrentView("system")
+	err = cih.Run()
+	if err != nil {
+		fmt.Print("Failed Processing CLI:", err.Error(), "\r\n")
+		return
+	}
+}

+ 189 - 0
examples/test01/test_tree.go

@@ -0,0 +1,189 @@
+package main
+
+import (
+	"fmt"
+	"git.swzry.com/zry/go-hhc-cli/hhc_ast"
+	"git.swzry.com/zry/go-hhc-cli/hhc_common"
+	"net"
+	"regexp"
+	"strconv"
+	"strings"
+)
+
+var FRGEXP *regexp.Regexp
+var ExecSB strings.Builder
+
+func CtxToCommand(ctx *hhc_ast.SDTWalkContext) string {
+	sb := strings.Builder{}
+	for _, v := range ctx.ASTNodes {
+		sb.WriteString(v.GetTokenRaw() + " ")
+	}
+	return sb.String()
+}
+
+func FExec(ctx *hhc_ast.SDTWalkContext) hhc_common.SDTWalkError {
+	fmt.Println("! Command Execute: ", CtxToCommand(ctx))
+	ExecSB.WriteString(CtxToCommand(ctx) + "\r\n")
+	return nil
+}
+
+func VLoopbackInf(t string) bool {
+	i, e := strconv.Atoi(t)
+	if e != nil {
+		return false
+	}
+	if i < 0 || i > 7 {
+		return false
+	}
+	return true
+}
+
+func VSubnetLen(t string) bool {
+	i, e := strconv.Atoi(t)
+	if e != nil {
+		return false
+	}
+	if i < 1 || i > 31 {
+		return false
+	}
+	return true
+}
+
+func VMTU(t string) bool {
+	i, e := strconv.Atoi(t)
+	if e != nil {
+		return false
+	}
+	if i < 128 || i > 1500 {
+		return false
+	}
+	return true
+}
+
+func VVlanInf(t string) bool {
+	i, e := strconv.Atoi(t)
+	if e != nil {
+		return false
+	}
+	if i < 0 || i > 4095 {
+		return false
+	}
+	return true
+}
+
+func VIPAddress(t string) bool {
+	_, err := net.ResolveIPAddr("ip", t)
+	return err == nil
+}
+
+func maketree() *hhc_ast.SyntaxDefinitionTreeRoot {
+	var err error
+	FRGEXP, err = regexp.Compile("INTEGER<([0-9]+)\\-([0-9]+)>")
+	if err != nil {
+		fmt.Println("Failed Compile Regexp:", err)
+		return nil
+	}
+	treeroot := &hhc_ast.SyntaxDefinitionTreeRoot{}
+	treeroot.AddCommand(
+		(&hhc_ast.SDTNode_Command{
+			Name:        "ip",
+			Description: "Specify IP configuration",
+		}).AddSubCommand(
+			(&hhc_ast.SDTNode_Command{
+				Name:        "address",
+				Description: "Set the IP address of an interface",
+			}).AddSubCommand((&hhc_ast.SDTNode_Command{
+				Name:        "dhcp-alloc",
+				Description: "Obtain an IP address through DHCP",
+			}).AddEnd(&hhc_ast.SDTNode_End{
+				Exec: FExec,
+			})).AddSubCommand((&hhc_ast.SDTNode_Command{
+				Name:        "unnumbered",
+				Description: "Share an address with another interface",
+			}).AddSubCommand((&hhc_ast.SDTNode_Command{
+				Name:        "interface",
+				Description: "Specify the interface whose ip address was unnumbered",
+			}).AddSubCommand((&hhc_ast.SDTNode_Command{
+				Name:        "loopback",
+				Description: "LoopBack interface",
+			}).AddValArgument((&hhc_ast.SDTNode_Argument{
+				FormatDescription: "INTEGER<0-7>",
+				Description:       "LoopBack interface number",
+				Validator:         VLoopbackInf,
+			}).AddEnd(&hhc_ast.SDTNode_End{
+				Exec: FExec,
+			}))).AddSubCommand((&hhc_ast.SDTNode_Command{
+				Name:        "vlan-interface",
+				Description: "VLAN interface",
+			}).AddValArgument((&hhc_ast.SDTNode_Argument{
+				FormatDescription: "INTEGER<0-4095>",
+				Description:       "Vlan-interface interface number",
+				Validator:         VVlanInf,
+			}).AddEnd(&hhc_ast.SDTNode_End{
+				Exec: FExec,
+			}))))).AddValArgument((&hhc_ast.SDTNode_Argument{
+				FormatDescription: "X.X.X.X",
+				Description:       "IP address",
+				Validator:         VIPAddress,
+			}).AddValArgument((&hhc_ast.SDTNode_Argument{
+				FormatDescription: "INTEGER<1-31>",
+				Description:       "IP mask length",
+				Validator:         VSubnetLen,
+			}).AddSubCommand((&hhc_ast.SDTNode_Command{
+				Name:        "sub",
+				Description: "Indicate a subordinate address",
+			}).AddEnd(&hhc_ast.SDTNode_End{
+				Exec: FExec,
+			})).AddEnd(&hhc_ast.SDTNode_End{
+				Exec: FExec,
+			})).AddValArgument((&hhc_ast.SDTNode_Argument{
+				FormatDescription: "X.X.X.X",
+				Description:       "IP mask",
+				Validator:         VIPAddress,
+			}).AddSubCommand((&hhc_ast.SDTNode_Command{
+				Name:        "sub",
+				Description: "Indicate a subordinate address",
+			}).AddEnd(&hhc_ast.SDTNode_End{
+				Exec: FExec,
+			})).AddEnd(&hhc_ast.SDTNode_End{
+				Exec: FExec,
+			})))).AddSubCommand((&hhc_ast.SDTNode_Command{
+			Name:        "mtu",
+			Description: "Specify the MTU of the interface",
+		}).AddValArgument((&hhc_ast.SDTNode_Argument{
+			FormatDescription: "INTEGER<128-1500>",
+			Description:       "MTU in bytes",
+			Validator:         VMTU,
+		}).AddEnd(&hhc_ast.SDTNode_End{
+			Exec: FExec,
+		})))).AddCommand((&hhc_ast.SDTNode_Command{
+		Name:        "ipv6",
+		Description: "Specify IPv6 configuration",
+	}).AddSubCommand((&hhc_ast.SDTNode_Command{
+		Name:        "dhcp",
+		Description: "Configure DHCPv6",
+	}).AddSubCommand((&hhc_ast.SDTNode_Command{
+		Name:        "select",
+		Description: "Specify process mode of DHCPv6 packet",
+	}).AddSubCommand((&hhc_ast.SDTNode_Command{
+		Name:        "relay",
+		Description: "Relay mode",
+	}).AddEnd(&hhc_ast.SDTNode_End{
+		Exec: FExec,
+	})).AddSubCommand((&hhc_ast.SDTNode_Command{
+		Name:        "server",
+		Description: "Server mode",
+	}).AddEnd(&hhc_ast.SDTNode_End{
+		Exec: FExec,
+	})))).AddSubCommand((&hhc_ast.SDTNode_Command{
+		Name:        "mtu",
+		Description: "Specify the IPv6 MTU of the interface",
+	}).AddValArgument((&hhc_ast.SDTNode_Argument{
+		FormatDescription: "INTEGER<128-1500>",
+		Description:       "MTU in bytes",
+		Validator:         VMTU,
+	}).AddEnd(&hhc_ast.SDTNode_End{
+		Exec: FExec,
+	}))))
+	return treeroot
+}

+ 282 - 0
examples/test02/main.go

@@ -0,0 +1,282 @@
+package main
+
+import (
+	"fmt"
+	"git.swzry.com/zry/go-hhc-cli/hhc_ast"
+	"git.swzry.com/zry/go-hhc-cli/hhc_common"
+	"github.com/cheynewallace/tabby"
+	"math/rand"
+	"net"
+	"os"
+	"regexp"
+	"strconv"
+	"strings"
+)
+
+var FRGEXP *regexp.Regexp
+var ExecSB strings.Builder
+
+func FExec(ctx *hhc_ast.SDTWalkContext) hhc_common.SDTWalkError {
+	fmt.Println("! Command Execute: ", CtxToCommand(ctx))
+	ExecSB.WriteString(CtxToCommand(ctx) + "\r\n")
+	return nil
+}
+
+func VLoopbackInf(t string) bool {
+	i, e := strconv.Atoi(t)
+	if e != nil {
+		return false
+	}
+	if i < 0 || i > 7 {
+		return false
+	}
+	return true
+}
+
+func VSubnetLen(t string) bool {
+	i, e := strconv.Atoi(t)
+	if e != nil {
+		return false
+	}
+	if i < 1 || i > 31 {
+		return false
+	}
+	return true
+}
+
+func VMTU(t string) bool {
+	i, e := strconv.Atoi(t)
+	if e != nil {
+		return false
+	}
+	if i < 128 || i > 1500 {
+		return false
+	}
+	return true
+}
+
+func VVlanInf(t string) bool {
+	i, e := strconv.Atoi(t)
+	if e != nil {
+		return false
+	}
+	if i < 0 || i > 4095 {
+		return false
+	}
+	return true
+}
+
+func VIPAddress(t string) bool {
+	_, err := net.ResolveIPAddr("ip", t)
+	return err == nil
+}
+
+func main() {
+	var err error
+	FRGEXP, err = regexp.Compile("INTEGER<([0-9]+)\\-([0-9]+)>")
+	if err != nil {
+		fmt.Println("Failed Compile Regexp:", err)
+		return
+	}
+	ctx := &hhc_ast.SDTWalkContext{ASTNodes: []hhc_ast.ASTNode{}}
+	var cur hhc_ast.SyntaxDefTreeNode
+	cur = (&hhc_ast.SyntaxDefinitionTreeRoot{}).AddCommand(
+		(&hhc_ast.SDTNode_Command{
+			Name:        "ip",
+			Description: "Specify IP configuration",
+		}).AddSubCommand(
+			(&hhc_ast.SDTNode_Command{
+				Name:        "address",
+				Description: "Set the IP address of an interface",
+			}).AddSubCommand((&hhc_ast.SDTNode_Command{
+				Name:        "dhcp-alloc",
+				Description: "Obtain an IP address through DHCP",
+			}).AddEnd(&hhc_ast.SDTNode_End{
+				Exec: FExec,
+			})).AddSubCommand((&hhc_ast.SDTNode_Command{
+				Name:        "unnumbered",
+				Description: "Share an address with another interface",
+			}).AddSubCommand((&hhc_ast.SDTNode_Command{
+				Name:        "interface",
+				Description: "Specify the interface whose ip address was unnumbered",
+			}).AddSubCommand((&hhc_ast.SDTNode_Command{
+				Name:        "loopback",
+				Description: "LoopBack interface",
+			}).AddValArgument((&hhc_ast.SDTNode_Argument{
+				FormatDescription: "INTEGER<0-7>",
+				Description:       "LoopBack interface number",
+				Validator:         VLoopbackInf,
+			}).AddEnd(&hhc_ast.SDTNode_End{
+				Exec: FExec,
+			}))).AddSubCommand((&hhc_ast.SDTNode_Command{
+				Name:        "vlan-interface",
+				Description: "VLAN interface",
+			}).AddValArgument((&hhc_ast.SDTNode_Argument{
+				FormatDescription: "INTEGER<0-4095>",
+				Description:       "Vlan-interface interface number",
+				Validator:         VVlanInf,
+			}).AddEnd(&hhc_ast.SDTNode_End{
+				Exec: FExec,
+			}))))).AddValArgument((&hhc_ast.SDTNode_Argument{
+				FormatDescription: "X.X.X.X",
+				Description:       "IP address",
+				Validator:         VIPAddress,
+			}).AddValArgument((&hhc_ast.SDTNode_Argument{
+				FormatDescription: "INTEGER<1-31>",
+				Description:       "IP mask length",
+				Validator:         VSubnetLen,
+			}).AddSubCommand((&hhc_ast.SDTNode_Command{
+				Name:        "sub",
+				Description: "Indicate a subordinate address",
+			}).AddEnd(&hhc_ast.SDTNode_End{
+				Exec: FExec,
+			})).AddEnd(&hhc_ast.SDTNode_End{
+				Exec: FExec,
+			})).AddValArgument((&hhc_ast.SDTNode_Argument{
+				FormatDescription: "X.X.X.X",
+				Description:       "IP mask",
+				Validator:         VIPAddress,
+			}).AddSubCommand((&hhc_ast.SDTNode_Command{
+				Name:        "sub",
+				Description: "Indicate a subordinate address",
+			}).AddEnd(&hhc_ast.SDTNode_End{
+				Exec: FExec,
+			})).AddEnd(&hhc_ast.SDTNode_End{
+				Exec: FExec,
+			})))).AddSubCommand((&hhc_ast.SDTNode_Command{
+			Name:        "mtu",
+			Description: "Specify the MTU of the interface",
+		}).AddValArgument((&hhc_ast.SDTNode_Argument{
+			FormatDescription: "INTEGER<128-1500>",
+			Description:       "MTU in bytes",
+			Validator:         VMTU,
+		}).AddEnd(&hhc_ast.SDTNode_End{
+			Exec: FExec,
+		})))).AddCommand((&hhc_ast.SDTNode_Command{
+		Name:        "ipv6",
+		Description: "Specify IPv6 configuration",
+	}).AddSubCommand((&hhc_ast.SDTNode_Command{
+		Name:        "dhcp",
+		Description: "Configure DHCPv6",
+	}).AddSubCommand((&hhc_ast.SDTNode_Command{
+		Name:        "select",
+		Description: "Specify process mode of DHCPv6 packet",
+	}).AddSubCommand((&hhc_ast.SDTNode_Command{
+		Name:        "relay",
+		Description: "Relay mode",
+	}).AddEnd(&hhc_ast.SDTNode_End{
+		Exec: FExec,
+	})).AddSubCommand((&hhc_ast.SDTNode_Command{
+		Name:        "server",
+		Description: "Server mode",
+	}).AddEnd(&hhc_ast.SDTNode_End{
+		Exec: FExec,
+	})))).AddSubCommand((&hhc_ast.SDTNode_Command{
+		Name:        "mtu",
+		Description: "Specify the IPv6 MTU of the interface",
+	}).AddValArgument((&hhc_ast.SDTNode_Argument{
+		FormatDescription: "INTEGER<128-1500>",
+		Description:       "MTU in bytes",
+		Validator:         VMTU,
+	}).AddEnd(&hhc_ast.SDTNode_End{
+		Exec: FExec,
+	}))))
+	t := GenerateValByFormatDesc("X.X.X.X")
+	fmt.Println(t, VIPAddress(t))
+	t = GenerateValByFormatDesc("INTEGER<1-31>")
+	fmt.Println(t, VSubnetLen(t))
+	fmt.Println("==== ==== ==== Ready ==== ==== ====")
+	AutoWalk(ctx, cur)
+	fmt.Println("==== ==== ==== End of AutoWalk ==== ==== ====")
+	fmt.Println("All Command Lines:")
+	fmt.Println(ExecSB.String())
+	//_, _ = ctx, cur
+}
+
+func Walk(ctx *hhc_ast.SDTWalkContext, cur hhc_ast.SyntaxDefTreeNode, token string) hhc_ast.SyntaxDefTreeNode {
+	fmt.Println("Walk into with token:", token)
+	ncur, err := cur.WalkNext(ctx, token)
+	if err != nil {
+		fmt.Println("Error In Walk")
+		fmt.Println("FES:", err.FES())
+		fmt.Println("EES:", err.EES())
+		os.Exit(-1)
+	}
+	fmt.Println("Walked into", GetCtxPath(ctx))
+	return ncur
+}
+
+func AutoWalk(ctx *hhc_ast.SDTWalkContext, cur hhc_ast.SyntaxDefTreeNode) {
+	fmt.Println("======== Help of", GetCtxPath(ctx), "========")
+	if cur.IsEnd() {
+		switch cur.(type) {
+		case *hhc_ast.SDTNode_End:
+			ncur := cur.(*hhc_ast.SDTNode_End)
+			ncur.Exec(ctx)
+		}
+		return
+	}
+	PrintHelp(cur.GetHelps(""))
+	for _, v := range cur.GetHelps("") {
+		nctx := CopyCtx(ctx)
+		var cin hhc_ast.SyntaxDefTreeNode
+		if v.IsArg {
+			cin = Walk(nctx, cur, GenerateValByFormatDesc(v.Name))
+		} else {
+			cin = Walk(nctx, cur, v.Name)
+		}
+		AutoWalk(nctx, cin)
+	}
+}
+
+func CopyCtx(ctx *hhc_ast.SDTWalkContext) *hhc_ast.SDTWalkContext {
+	nctx := &hhc_ast.SDTWalkContext{ASTNodes: make([]hhc_ast.ASTNode, len(ctx.ASTNodes))}
+	for i, v := range ctx.ASTNodes {
+		nctx.ASTNodes[i] = v
+	}
+	return nctx
+}
+
+func PrintHelp(h []hhc_common.SDTHelpInfo) {
+	t := tabby.New()
+	for i, v := range h {
+		t.AddLine(i, v.IsArg, v.Name, v.Description)
+	}
+	t.Print()
+}
+
+func GetCtxPath(ctx *hhc_ast.SDTWalkContext) string {
+	sb := strings.Builder{}
+	sb.WriteString("/")
+	for _, v := range ctx.ASTNodes {
+		sb.WriteString(v.GetTokenRaw() + "/")
+	}
+	return sb.String()
+}
+
+func CtxToCommand(ctx *hhc_ast.SDTWalkContext) string {
+	sb := strings.Builder{}
+	for _, v := range ctx.ASTNodes {
+		sb.WriteString(v.GetTokenRaw() + " ")
+	}
+	return sb.String()
+}
+
+func GenerateValByFormatDesc(format string) string {
+	if format == "<cr>" {
+		return ""
+	}
+	if format == "X.X.X.X" {
+		return "192.168.1.1"
+	}
+	ret := FRGEXP.FindAllStringSubmatch(format, 1)
+	if len(ret) == 1 {
+		if len(ret[0]) == 3 {
+			min, _ := strconv.Atoi(ret[0][1])
+			max, _ := strconv.Atoi(ret[0][2])
+			vi := rand.Intn(max-min) + min
+			return strconv.Itoa(vi)
+		}
+	}
+	return ""
+}

+ 118 - 0
examples/test03/main.go

@@ -0,0 +1,118 @@
+package main
+
+import (
+	"fmt"
+	"git.swzry.com/zry/go-hhc-cli/hhc_ast"
+	"git.swzry.com/zry/go-hhc-cli/hhc_common"
+	"github.com/cheynewallace/tabby"
+)
+
+func PrintHelp(h []hhc_common.SDTHelpInfo) {
+	t := tabby.New()
+	for i, v := range h {
+		t.AddLine(i, v.IsArg, v.Name, v.Description)
+	}
+	t.Print()
+}
+
+func main() {
+	tree := maketree()
+	if tree == nil {
+		fmt.Println("Failed Make Test Tree")
+		return
+	}
+	walker := hhc_ast.NewSDTWalker(tree)
+	TypeAndHelp(walker, "")
+	TypeAndHelp(walker, "i")
+	TypeAndHelp(walker, "ip")
+	WalkIn(walker, "ip")
+	TypeAndHelp(walker, "m")
+	WalkIn(walker, "mtu")
+	WalkBack(walker)
+	TypeAndHelp(walker, "a")
+	WalkIn(walker, "addr")
+	TypeAndHelp(walker, "192.")
+	TypeAndHelp(walker, "192.168")
+	WalkIn(walker, "192.168.114.114")
+	TypeAndHelp(walker, "")
+	TypeAndHelp(walker, "255.")
+	TypeAndHelp(walker, "255.255")
+	WalkIn(walker, "255.255.255.0")
+	TypeAndHelp(walker, "")
+	WalkBack(walker)
+	TypeAndHelp(walker, "24")
+	WalkIn(walker, "24")
+	WalkIn(walker, "sub")
+	TypeAndHelp(walker, "")
+	TypeAndHelp(walker, "test")
+	WalkIn(walker, "test1")
+	WalkIn(walker, "test2")
+	WalkIn(walker, "test3")
+	WalkBack(walker)
+	WalkBack(walker)
+	WalkBack(walker)
+	WalkBack(walker)
+	WalkBack(walker)
+	WalkBack(walker)
+	TypeAndHelp(walker, "")
+	TypeAndHelp(walker, "dh")
+	WalkIn(walker, "dh")
+	WalkBack(walker)
+	TypeAndHelp(walker, "")
+	WalkIn(walker, "u")
+	WalkIn(walker, "int")
+	WalkIn(walker, "vl")
+	WalkIn(walker, "1000")
+	WalkBack(walker)
+	WalkBack(walker)
+	WalkIn(walker, "lo")
+	WalkIn(walker, "9")
+	WalkBack(walker)
+	WalkIn(walker, "6")
+	WalkBack(walker)
+	WalkBack(walker)
+	WalkBack(walker)
+	WalkBack(walker)
+	WalkBack(walker)
+	WalkBack(walker)
+	WalkBack(walker)
+	WalkBack(walker)
+	WalkBack(walker)
+	WalkBack(walker)
+	WalkBack(walker)
+	WalkBack(walker)
+	WalkBack(walker)
+	WalkBack(walker)
+	WalkBack(walker)
+	WalkIn(walker, "ipv")
+
+}
+
+func TypeAndHelp(walker *hhc_ast.SDTWalker, token string) {
+	println("[H9C]" + CtxToCommand(walker.GetContext()) + token + "?")
+	if walker.HasError() {
+		println(" % " + walker.GetError().FES())
+		return
+	}
+	PrintHelp(walker.GetHelps(token))
+}
+
+func WalkIn(walker *hhc_ast.SDTWalker, token string) {
+	walker.NextStep(token)
+	println("[H9C]" + CtxToCommand(walker.GetContext()) + " ?")
+	if walker.HasError() {
+		println(" % " + walker.GetError().FES())
+		return
+	}
+	PrintHelp(walker.GetHelps(""))
+}
+
+func WalkBack(walker *hhc_ast.SDTWalker) {
+	walker.Back()
+	println("[H9C]" + CtxToCommand(walker.GetContext()) + " ?")
+	if walker.HasError() {
+		println(" % " + walker.GetError().FES())
+		return
+	}
+	PrintHelp(walker.GetHelps(""))
+}

+ 189 - 0
examples/test03/test_tree.go

@@ -0,0 +1,189 @@
+package main
+
+import (
+	"fmt"
+	"git.swzry.com/zry/go-hhc-cli/hhc_ast"
+	"git.swzry.com/zry/go-hhc-cli/hhc_common"
+	"net"
+	"regexp"
+	"strconv"
+	"strings"
+)
+
+var FRGEXP *regexp.Regexp
+var ExecSB strings.Builder
+
+func CtxToCommand(ctx *hhc_ast.SDTWalkContext) string {
+	sb := strings.Builder{}
+	for _, v := range ctx.ASTNodes {
+		sb.WriteString(v.GetTokenRaw() + " ")
+	}
+	return sb.String()
+}
+
+func FExec(ctx *hhc_ast.SDTWalkContext) hhc_common.SDTWalkError {
+	fmt.Println("! Command Execute: ", CtxToCommand(ctx))
+	ExecSB.WriteString(CtxToCommand(ctx) + "\r\n")
+	return nil
+}
+
+func VLoopbackInf(t string) bool {
+	i, e := strconv.Atoi(t)
+	if e != nil {
+		return false
+	}
+	if i < 0 || i > 7 {
+		return false
+	}
+	return true
+}
+
+func VSubnetLen(t string) bool {
+	i, e := strconv.Atoi(t)
+	if e != nil {
+		return false
+	}
+	if i < 1 || i > 31 {
+		return false
+	}
+	return true
+}
+
+func VMTU(t string) bool {
+	i, e := strconv.Atoi(t)
+	if e != nil {
+		return false
+	}
+	if i < 128 || i > 1500 {
+		return false
+	}
+	return true
+}
+
+func VVlanInf(t string) bool {
+	i, e := strconv.Atoi(t)
+	if e != nil {
+		return false
+	}
+	if i < 0 || i > 4095 {
+		return false
+	}
+	return true
+}
+
+func VIPAddress(t string) bool {
+	_, err := net.ResolveIPAddr("ip", t)
+	return err == nil
+}
+
+func maketree() *hhc_ast.SyntaxDefinitionTreeRoot {
+	var err error
+	FRGEXP, err = regexp.Compile("INTEGER<([0-9]+)\\-([0-9]+)>")
+	if err != nil {
+		fmt.Println("Failed Compile Regexp:", err)
+		return nil
+	}
+	treeroot := &hhc_ast.SyntaxDefinitionTreeRoot{}
+	treeroot.AddCommand(
+		(&hhc_ast.SDTNode_Command{
+			Name:        "ip",
+			Description: "Specify IP configuration",
+		}).AddSubCommand(
+			(&hhc_ast.SDTNode_Command{
+				Name:        "address",
+				Description: "Set the IP address of an interface",
+			}).AddSubCommand((&hhc_ast.SDTNode_Command{
+				Name:        "dhcp-alloc",
+				Description: "Obtain an IP address through DHCP",
+			}).AddEnd(&hhc_ast.SDTNode_End{
+				Exec: FExec,
+			})).AddSubCommand((&hhc_ast.SDTNode_Command{
+				Name:        "unnumbered",
+				Description: "Share an address with another interface",
+			}).AddSubCommand((&hhc_ast.SDTNode_Command{
+				Name:        "interface",
+				Description: "Specify the interface whose ip address was unnumbered",
+			}).AddSubCommand((&hhc_ast.SDTNode_Command{
+				Name:        "loopback",
+				Description: "LoopBack interface",
+			}).AddValArgument((&hhc_ast.SDTNode_Argument{
+				FormatDescription: "INTEGER<0-7>",
+				Description:       "LoopBack interface number",
+				Validator:         VLoopbackInf,
+			}).AddEnd(&hhc_ast.SDTNode_End{
+				Exec: FExec,
+			}))).AddSubCommand((&hhc_ast.SDTNode_Command{
+				Name:        "vlan-interface",
+				Description: "VLAN interface",
+			}).AddValArgument((&hhc_ast.SDTNode_Argument{
+				FormatDescription: "INTEGER<0-4095>",
+				Description:       "Vlan-interface interface number",
+				Validator:         VVlanInf,
+			}).AddEnd(&hhc_ast.SDTNode_End{
+				Exec: FExec,
+			}))))).AddValArgument((&hhc_ast.SDTNode_Argument{
+				FormatDescription: "X.X.X.X",
+				Description:       "IP address",
+				Validator:         VIPAddress,
+			}).AddValArgument((&hhc_ast.SDTNode_Argument{
+				FormatDescription: "INTEGER<1-31>",
+				Description:       "IP mask length",
+				Validator:         VSubnetLen,
+			}).AddSubCommand((&hhc_ast.SDTNode_Command{
+				Name:        "sub",
+				Description: "Indicate a subordinate address",
+			}).AddEnd(&hhc_ast.SDTNode_End{
+				Exec: FExec,
+			})).AddEnd(&hhc_ast.SDTNode_End{
+				Exec: FExec,
+			})).AddValArgument((&hhc_ast.SDTNode_Argument{
+				FormatDescription: "X.X.X.X",
+				Description:       "IP mask",
+				Validator:         VIPAddress,
+			}).AddSubCommand((&hhc_ast.SDTNode_Command{
+				Name:        "sub",
+				Description: "Indicate a subordinate address",
+			}).AddEnd(&hhc_ast.SDTNode_End{
+				Exec: FExec,
+			})).AddEnd(&hhc_ast.SDTNode_End{
+				Exec: FExec,
+			})))).AddSubCommand((&hhc_ast.SDTNode_Command{
+			Name:        "mtu",
+			Description: "Specify the MTU of the interface",
+		}).AddValArgument((&hhc_ast.SDTNode_Argument{
+			FormatDescription: "INTEGER<128-1500>",
+			Description:       "MTU in bytes",
+			Validator:         VMTU,
+		}).AddEnd(&hhc_ast.SDTNode_End{
+			Exec: FExec,
+		})))).AddCommand((&hhc_ast.SDTNode_Command{
+		Name:        "ipv6",
+		Description: "Specify IPv6 configuration",
+	}).AddSubCommand((&hhc_ast.SDTNode_Command{
+		Name:        "dhcp",
+		Description: "Configure DHCPv6",
+	}).AddSubCommand((&hhc_ast.SDTNode_Command{
+		Name:        "select",
+		Description: "Specify process mode of DHCPv6 packet",
+	}).AddSubCommand((&hhc_ast.SDTNode_Command{
+		Name:        "relay",
+		Description: "Relay mode",
+	}).AddEnd(&hhc_ast.SDTNode_End{
+		Exec: FExec,
+	})).AddSubCommand((&hhc_ast.SDTNode_Command{
+		Name:        "server",
+		Description: "Server mode",
+	}).AddEnd(&hhc_ast.SDTNode_End{
+		Exec: FExec,
+	})))).AddSubCommand((&hhc_ast.SDTNode_Command{
+		Name:        "mtu",
+		Description: "Specify the IPv6 MTU of the interface",
+	}).AddValArgument((&hhc_ast.SDTNode_Argument{
+		FormatDescription: "INTEGER<128-1500>",
+		Description:       "MTU in bytes",
+		Validator:         VMTU,
+	}).AddEnd(&hhc_ast.SDTNode_End{
+		Exec: FExec,
+	}))))
+	return treeroot
+}

+ 42 - 0
examples/test04/main.go

@@ -0,0 +1,42 @@
+package main
+
+import (
+	"fmt"
+	"git.swzry.com/zry/go-hhc-cli/hhc_ast"
+	"git.swzry.com/zry/go-hhc-cli/hhc_common"
+	"os"
+)
+
+func main() {
+	tree := maketree()
+	if tree == nil {
+		fmt.Println("Failed Make Test Tree")
+		return
+	}
+	walker := hhc_ast.NewSDTWalker(tree)
+	lp := hhc_ast.NewSDTLineParser(walker)
+	DoParse(lp, "ip?")
+	DoParse(lp, "ip a?")
+	DoParse(lp, "ipv?")
+	DoParse(lp, "ipv ?")
+	DoParse(lp, "ipv mt?")
+	DoParse(lp, "ipv mt ?")
+}
+
+func DoParse(parser *hhc_ast.SDTLineParser, s string) {
+	fmt.Println("Command:", s)
+	ra := []rune(s)
+	parser.Reset()
+	parser.Parse(ra)
+	if parser.HelpTriggerd() {
+		if parser.HasError() {
+			e, p := parser.GetError()
+			fmt.Printf("Error at %d: %s\r\n", p, e.EES())
+		} else {
+			if parser.WillPrintHelp() {
+				fmt.Println("Help:")
+				hhc_common.FPrintHelp(os.Stdout, parser.GetHelpMessage())
+			}
+		}
+	}
+}

+ 189 - 0
examples/test04/test_tree.go

@@ -0,0 +1,189 @@
+package main
+
+import (
+	"fmt"
+	"git.swzry.com/zry/go-hhc-cli/hhc_ast"
+	"git.swzry.com/zry/go-hhc-cli/hhc_common"
+	"net"
+	"regexp"
+	"strconv"
+	"strings"
+)
+
+var FRGEXP *regexp.Regexp
+var ExecSB strings.Builder
+
+func CtxToCommand(ctx *hhc_ast.SDTWalkContext) string {
+	sb := strings.Builder{}
+	for _, v := range ctx.ASTNodes {
+		sb.WriteString(v.GetTokenRaw() + " ")
+	}
+	return sb.String()
+}
+
+func FExec(ctx *hhc_ast.SDTWalkContext) hhc_common.SDTWalkError {
+	fmt.Println("! Command Execute: ", CtxToCommand(ctx))
+	ExecSB.WriteString(CtxToCommand(ctx) + "\r\n")
+	return nil
+}
+
+func VLoopbackInf(t string) bool {
+	i, e := strconv.Atoi(t)
+	if e != nil {
+		return false
+	}
+	if i < 0 || i > 7 {
+		return false
+	}
+	return true
+}
+
+func VSubnetLen(t string) bool {
+	i, e := strconv.Atoi(t)
+	if e != nil {
+		return false
+	}
+	if i < 1 || i > 31 {
+		return false
+	}
+	return true
+}
+
+func VMTU(t string) bool {
+	i, e := strconv.Atoi(t)
+	if e != nil {
+		return false
+	}
+	if i < 128 || i > 1500 {
+		return false
+	}
+	return true
+}
+
+func VVlanInf(t string) bool {
+	i, e := strconv.Atoi(t)
+	if e != nil {
+		return false
+	}
+	if i < 0 || i > 4095 {
+		return false
+	}
+	return true
+}
+
+func VIPAddress(t string) bool {
+	_, err := net.ResolveIPAddr("ip", t)
+	return err == nil
+}
+
+func maketree() *hhc_ast.SyntaxDefinitionTreeRoot {
+	var err error
+	FRGEXP, err = regexp.Compile("INTEGER<([0-9]+)\\-([0-9]+)>")
+	if err != nil {
+		fmt.Println("Failed Compile Regexp:", err)
+		return nil
+	}
+	treeroot := &hhc_ast.SyntaxDefinitionTreeRoot{}
+	treeroot.AddCommand(
+		(&hhc_ast.SDTNode_Command{
+			Name:        "ip",
+			Description: "Specify IP configuration",
+		}).AddSubCommand(
+			(&hhc_ast.SDTNode_Command{
+				Name:        "address",
+				Description: "Set the IP address of an interface",
+			}).AddSubCommand((&hhc_ast.SDTNode_Command{
+				Name:        "dhcp-alloc",
+				Description: "Obtain an IP address through DHCP",
+			}).AddEnd(&hhc_ast.SDTNode_End{
+				Exec: FExec,
+			})).AddSubCommand((&hhc_ast.SDTNode_Command{
+				Name:        "unnumbered",
+				Description: "Share an address with another interface",
+			}).AddSubCommand((&hhc_ast.SDTNode_Command{
+				Name:        "interface",
+				Description: "Specify the interface whose ip address was unnumbered",
+			}).AddSubCommand((&hhc_ast.SDTNode_Command{
+				Name:        "loopback",
+				Description: "LoopBack interface",
+			}).AddValArgument((&hhc_ast.SDTNode_Argument{
+				FormatDescription: "INTEGER<0-7>",
+				Description:       "LoopBack interface number",
+				Validator:         VLoopbackInf,
+			}).AddEnd(&hhc_ast.SDTNode_End{
+				Exec: FExec,
+			}))).AddSubCommand((&hhc_ast.SDTNode_Command{
+				Name:        "vlan-interface",
+				Description: "VLAN interface",
+			}).AddValArgument((&hhc_ast.SDTNode_Argument{
+				FormatDescription: "INTEGER<0-4095>",
+				Description:       "Vlan-interface interface number",
+				Validator:         VVlanInf,
+			}).AddEnd(&hhc_ast.SDTNode_End{
+				Exec: FExec,
+			}))))).AddValArgument((&hhc_ast.SDTNode_Argument{
+				FormatDescription: "X.X.X.X",
+				Description:       "IP address",
+				Validator:         VIPAddress,
+			}).AddValArgument((&hhc_ast.SDTNode_Argument{
+				FormatDescription: "INTEGER<1-31>",
+				Description:       "IP mask length",
+				Validator:         VSubnetLen,
+			}).AddSubCommand((&hhc_ast.SDTNode_Command{
+				Name:        "sub",
+				Description: "Indicate a subordinate address",
+			}).AddEnd(&hhc_ast.SDTNode_End{
+				Exec: FExec,
+			})).AddEnd(&hhc_ast.SDTNode_End{
+				Exec: FExec,
+			})).AddValArgument((&hhc_ast.SDTNode_Argument{
+				FormatDescription: "X.X.X.X",
+				Description:       "IP mask",
+				Validator:         VIPAddress,
+			}).AddSubCommand((&hhc_ast.SDTNode_Command{
+				Name:        "sub",
+				Description: "Indicate a subordinate address",
+			}).AddEnd(&hhc_ast.SDTNode_End{
+				Exec: FExec,
+			})).AddEnd(&hhc_ast.SDTNode_End{
+				Exec: FExec,
+			})))).AddSubCommand((&hhc_ast.SDTNode_Command{
+			Name:        "mtu",
+			Description: "Specify the MTU of the interface",
+		}).AddValArgument((&hhc_ast.SDTNode_Argument{
+			FormatDescription: "INTEGER<128-1500>",
+			Description:       "MTU in bytes",
+			Validator:         VMTU,
+		}).AddEnd(&hhc_ast.SDTNode_End{
+			Exec: FExec,
+		})))).AddCommand((&hhc_ast.SDTNode_Command{
+		Name:        "ipv6",
+		Description: "Specify IPv6 configuration",
+	}).AddSubCommand((&hhc_ast.SDTNode_Command{
+		Name:        "dhcp",
+		Description: "Configure DHCPv6",
+	}).AddSubCommand((&hhc_ast.SDTNode_Command{
+		Name:        "select",
+		Description: "Specify process mode of DHCPv6 packet",
+	}).AddSubCommand((&hhc_ast.SDTNode_Command{
+		Name:        "relay",
+		Description: "Relay mode",
+	}).AddEnd(&hhc_ast.SDTNode_End{
+		Exec: FExec,
+	})).AddSubCommand((&hhc_ast.SDTNode_Command{
+		Name:        "server",
+		Description: "Server mode",
+	}).AddEnd(&hhc_ast.SDTNode_End{
+		Exec: FExec,
+	})))).AddSubCommand((&hhc_ast.SDTNode_Command{
+		Name:        "mtu",
+		Description: "Specify the IPv6 MTU of the interface",
+	}).AddValArgument((&hhc_ast.SDTNode_Argument{
+		FormatDescription: "INTEGER<128-1500>",
+		Description:       "MTU in bytes",
+		Validator:         VMTU,
+	}).AddEnd(&hhc_ast.SDTNode_End{
+		Exec: FExec,
+	}))))
+	return treeroot
+}

+ 93 - 0
examples/test05/main.go

@@ -0,0 +1,93 @@
+package main
+
+import (
+	"fmt"
+	"git.swzry.com/zry/go-hhc-cli/hhc_common"
+	"github.com/ttacon/chalk"
+)
+
+func main() {
+	pm := []hhc_common.PrintableMessage{
+		{
+			Text:  "Hakurei Reimu ",
+			Style: chalk.Red.NewStyle().WithTextStyle(chalk.Underline),
+		},
+		{
+			Text:  "is a girl in gensokyo, ",
+			Style: chalk.Blue.NewStyle().WithTextStyle(chalk.Italic),
+		},
+		{
+			Text:  "she is the miko of ",
+			Style: chalk.Cyan.NewStyle(),
+		},
+		{
+			Text:  "Hakurei Shirine ",
+			Style: chalk.Green.NewStyle().WithTextStyle(chalk.Bold),
+		},
+		{
+			Text:  ". She loves money and has no ",
+			Style: chalk.White.NewStyle(),
+		},
+		{
+			Text:  "ethics.",
+			Style: chalk.Magenta.NewStyle(),
+		},
+		{
+			Text:  "博丽灵梦",
+			Style: chalk.Red.NewStyle().WithTextStyle(chalk.Underline),
+		},
+		{
+			Text:  "是一个幻想乡中的女孩,",
+			Style: chalk.Blue.NewStyle().WithTextStyle(chalk.Italic),
+		},
+		{
+			Text:  "她是",
+			Style: chalk.Cyan.NewStyle(),
+		},
+		{
+			Text:  "博丽神社",
+			Style: chalk.Green.NewStyle().WithTextStyle(chalk.Bold),
+		},
+		{
+			Text:  "的一名巫女,她喜欢钱而且没有",
+			Style: chalk.White.NewStyle(),
+		},
+		{
+			Text:  "节操。",
+			Style: chalk.Magenta.NewStyle(),
+		},
+	}
+	Pr(80, true, pm)
+	Pr(80, false, pm)
+	Pr(40, true, pm)
+	Pr(40, false, pm)
+	Pr(20, true, pm)
+	Pr(20, false, pm)
+	Pr(10, true, pm)
+	Pr(10, false, pm)
+	Pr(1, true, pm)
+	Pr(1, false, pm)
+	fmt.Println("=====================================")
+	test2 := "Hakurei Reimu is a girl in gensokyo, she is the miko of Hakurei Shirine . She loves money and has no ethics. 博丽灵梦是一个幻想乡中的女孩,她是博丽神社的一名巫女,她喜欢钱而且没有节操。"
+	Ps(80, test2)
+	Ps(40, test2)
+	Ps(20, test2)
+	Ps(10, test2)
+	Ps(1, test2)
+}
+
+func Pr(w int, c bool, pms []hhc_common.PrintableMessage) {
+	lines := hhc_common.PrintPrintablMessagesIntoLines(pms, w, c)
+	fmt.Println("TermWidth", w, "Colorful", c)
+	for _, v := range lines {
+		fmt.Println(v)
+	}
+}
+
+func Ps(w int, s string) {
+	lines := hhc_common.PrintStringIntoLines(s, w)
+	fmt.Println("TermWidth", w)
+	for _, v := range lines {
+		fmt.Println(v)
+	}
+}

+ 55 - 0
examples/test06/main.go

@@ -0,0 +1,55 @@
+package main
+
+import (
+	"fmt"
+	"git.swzry.com/zry/go-hhc-cli/hhc_common"
+	"git.swzry.com/zry/go-hhc-cli/hhccli"
+)
+
+func main() {
+	p := hhccli.NewSimpleTCESParser()
+	t(p, "[A")
+	t(p, "[B")
+	t(p, "[C")
+	t(p, "[D")
+	t(p, "[H")
+	t(p, "[F")
+	t(p, "[2~")
+	t(p, "[3~")
+	t(p, "[5~")
+	t(p, "[6~")
+	t(p, "OP")
+	t(p, "OQ")
+	t(p, "OR")
+	t(p, "OS")
+	t(p, "[15~")
+	t(p, "[17~")
+	t(p, "[18~")
+	t(p, "[19~")
+	t(p, "[20~")
+	t(p, "[21~")
+	t(p, "[23~")
+	t(p, "[24~")
+
+	t(p, "[14~")
+	t(p, "[25~")
+	t(p, "Kaa")
+	t(p, "[T")
+	k := "unrecognized command:aap at position 3"
+	uk := hhc_common.FirstToUpper(k)
+	fmt.Println(k)
+	fmt.Println(uk)
+}
+
+func t(p *hhccli.SimpleTCESParser, seq string) {
+	p.ResetState()
+	rs := []rune(seq)
+	for _, v := range rs {
+		b, r := p.PushRune(v)
+		if b {
+			fmt.Println("Seq", seq, "Result", r, "Name", r.Name())
+			return
+		}
+	}
+	fmt.Println("Seq", seq, "Failed!")
+}

+ 17 - 0
examples/test07/main.go

@@ -0,0 +1,17 @@
+package main
+
+import (
+	"github.com/olekukonko/tablewriter"
+	"os"
+)
+
+func main() {
+	tw := tablewriter.NewWriter(os.Stdout)
+	tw.SetAutoWrapText(true)
+	tw.SetColWidth(10)
+	tw.Append([]string{"aaa", "因为有人说什么ee人要用e5仿真(我提到了你用e3用了好几年"})
+	tw.Append([]string{"谔谔人", "任何仿真肯定都是性能越高越好"})
+	tw.Append([]string{"长长长长长得很非常非常长非常长", "这个短点"})
+	tw.Append([]string{"short", "这个要更长更长更长更长更长更长更长更长长长长long long long long long long long长得多😂😂😂"})
+	tw.Render()
+}

+ 21 - 0
hhc_ast/ast_list.go

@@ -0,0 +1,21 @@
+package hhc_ast
+
+type ASTNode interface {
+	GetTokenRaw() string
+}
+
+type ASTNode_Command struct {
+	Name string
+}
+
+func (n ASTNode_Command) GetTokenRaw() string {
+	return n.Name
+}
+
+type ASTNode_ValArg struct {
+	RawToken string
+}
+
+func (n ASTNode_ValArg) GetTokenRaw() string {
+	return n.RawToken
+}

+ 197 - 0
hhc_ast/exec_context.go

@@ -0,0 +1,197 @@
+package hhc_ast
+
+import (
+	"bytes"
+	"fmt"
+	"git.swzry.com/zry/go-hhc-cli/hhc_common"
+	"io"
+	"strings"
+)
+
+type ExecuteFunc func(ctx *ExecUserContext)
+
+type ExecContext struct {
+	sdtctx *SDTWalkContext
+	euc    *ExecUserContext
+}
+
+func WrapNewExecContext(wc *SDTWalkContext, stdout, stderr io.Writer, termWidth int) *ExecContext {
+	euc := &ExecUserContext{
+		stderr:                  stderr,
+		stdout:                  stdout,
+		willGotoPrintResultMode: false,
+		enableRawInput:          false,
+		resBuffer:               make([]string, 0),
+		termWidth:               termWidth,
+		nextView:                "",
+		willChangePrompt:        false,
+		nextPrompt:              "",
+	}
+	ec := &ExecContext{
+		sdtctx: wc,
+		euc:    euc,
+	}
+	euc.ec = ec
+	return ec
+}
+
+func (ec *ExecContext) GetUserContext() *ExecUserContext {
+	return ec.euc
+}
+
+func (ec *ExecContext) WillGotoPrintResultMode() bool {
+	return ec.euc.willGotoPrintResultMode
+}
+
+func (ec *ExecContext) GetResultPrintLines() []string {
+	return ec.euc.resBuffer
+}
+
+func (ec *ExecContext) WriteRune(r rune) {
+	if ec.euc.enableRawInput {
+		if ec.euc.rawInputBuffer != nil {
+			ec.euc.rawInputBuffer.WriteRune(r)
+		}
+	}
+}
+
+func (ec *ExecContext) GetNextView() string {
+	return ec.euc.nextView
+}
+
+func (ec *ExecContext) GetNextPrompt() (bool, string) {
+	return ec.euc.willChangePrompt, ec.euc.nextPrompt
+}
+
+type ExecUserContext struct {
+	ec                      *ExecContext
+	willGotoPrintResultMode bool
+	stdout                  io.Writer
+	stderr                  io.Writer
+	enableRawInput          bool
+	rawInputBuffer          *bytes.Buffer
+	resBuffer               []string
+	termWidth               int
+	nextView                string
+	willChangePrompt        bool
+	nextPrompt              string
+}
+
+func (euc *ExecUserContext) RawStdOutPrint(a ...interface{}) (int, error) {
+	return fmt.Fprint(euc.stdout, a...)
+}
+
+func (euc *ExecUserContext) RawStdErrPrint(a ...interface{}) (int, error) {
+	return fmt.Fprint(euc.stderr, a...)
+}
+
+func (euc *ExecUserContext) RawStdOutPrintf(format string, a ...interface{}) (int, error) {
+	return fmt.Fprintf(euc.stdout, format, a...)
+}
+
+func (euc *ExecUserContext) RawStdErrPrintf(format string, a ...interface{}) (int, error) {
+	return fmt.Fprintf(euc.stderr, format, a...)
+}
+
+func (euc *ExecUserContext) EnableRawInput() {
+	euc.enableRawInput = true
+	euc.rawInputBuffer = bytes.NewBuffer([]byte{})
+}
+
+func (euc *ExecUserContext) DisableRawInput() {
+	euc.enableRawInput = false
+	euc.rawInputBuffer = nil
+}
+
+func (euc *ExecUserContext) RawReadRune() (rune, int, error) {
+	if euc.enableRawInput {
+		if euc.rawInputBuffer != nil {
+			return euc.rawInputBuffer.ReadRune()
+		}
+	}
+	return 0, 0, fmt.Errorf("raw input not enable")
+}
+
+func (euc *ExecUserContext) ResultPrintRaw(r []string) {
+	euc.willGotoPrintResultMode = true
+	euc.resBuffer = append(euc.resBuffer, r...)
+}
+
+func (euc *ExecUserContext) ResultPrintSingleLineString(s string) {
+	r := hhc_common.PrintStringIntoLines(s, euc.termWidth)
+	euc.ResultPrintRaw(r)
+}
+
+func (euc *ExecUserContext) ResultPrintMultiLineString(s string) {
+	ssr := strings.Replace(s, "\r\n", "\n", -1)
+	ssr = strings.Replace(ssr, "\r", "\n", -1)
+	lines := strings.Split(ssr, "\n")
+	for _, v := range lines {
+		euc.ResultPrintSingleLineString(v)
+	}
+}
+
+func (euc *ExecUserContext) GetSubCount() int {
+	an := euc.ec.sdtctx.ASTNodes
+	return len(an)
+}
+
+func (euc *ExecUserContext) GetSubType(index int) (isArg, isSubCmd bool) {
+	an := euc.ec.sdtctx.ASTNodes
+	lan := len(an)
+	if index >= lan {
+		return false, false
+	}
+	san := an[index]
+	switch san.(type) {
+	case ASTNode_ValArg:
+		return true, false
+	case ASTNode_Command:
+		return false, true
+	default:
+		return false, false
+	}
+}
+
+func (euc *ExecUserContext) GetArgument(index int) (string, bool) {
+	an := euc.ec.sdtctx.ASTNodes
+	lan := len(an)
+	if index >= lan {
+		return "", false
+	}
+	san := an[index]
+	arg, ok := san.(ASTNode_ValArg)
+	if ok {
+		return arg.GetTokenRaw(), true
+	} else {
+		return "", false
+	}
+}
+
+func (euc *ExecUserContext) GetSubCommand(index int) (string, bool) {
+	an := euc.ec.sdtctx.ASTNodes
+	lan := len(an)
+	if index >= lan {
+		return "", false
+	}
+	san := an[index]
+	cmd, ok := san.(ASTNode_Command)
+	if ok {
+		return cmd.Name, true
+	} else {
+		return "", false
+	}
+}
+
+func (euc *ExecUserContext) ChangeView(nextView string) {
+	euc.nextView = nextView
+}
+
+func (euc *ExecUserContext) ChangePrompt(nextPrompt string) {
+	euc.willChangePrompt = true
+	euc.nextPrompt = nextPrompt
+}
+
+func (euc *ExecUserContext) ChangeTitle(title string) {
+	_, _ = euc.RawStdOutPrintf("\033]0;%s\007", title)
+}

+ 189 - 0
hhc_ast/sdt_lineparser.go

@@ -0,0 +1,189 @@
+package hhc_ast
+
+import (
+	"git.swzry.com/zry/go-hhc-cli/hhc_common"
+)
+
+type SDTLineParser struct {
+	walker         *SDTWalker
+	currentToken   []rune
+	currentRunePos int
+	errInParse     bool
+	errMsg         hhc_common.SDTWalkError
+	errPos         int
+	status         hhc_common.InputStatusCode
+	willPrintHelp  bool
+	helpMsg        []hhc_common.SDTHelpInfo
+	helpTrigged    bool
+}
+
+func NewSDTLineParser(walker *SDTWalker) *SDTLineParser {
+	p := &SDTLineParser{
+		walker: walker,
+	}
+	p.Reset()
+	return p
+}
+
+func (p *SDTLineParser) HasError() bool {
+	return p.errInParse
+}
+
+func (p *SDTLineParser) HelpTriggerd() bool {
+	return p.helpTrigged
+}
+
+func (p *SDTLineParser) WillPrintHelp() bool {
+	return p.willPrintHelp
+}
+
+func (p *SDTLineParser) GetError() (err hhc_common.SDTWalkError, pos int) {
+	return p.errMsg, p.errPos
+}
+
+func (p *SDTLineParser) GetHelpMessage() []hhc_common.SDTHelpInfo {
+	return p.helpMsg
+}
+
+func (p *SDTLineParser) Reset() {
+	p.currentToken = []rune{}
+	p.errInParse = false
+	p.currentRunePos = 0
+	p.errMsg = nil
+	p.errPos = 0
+	p.status = hhc_common.InputStat_Normal
+	p.willPrintHelp = false
+	p.helpMsg = nil
+	p.helpTrigged = false
+	p.walker.Reset()
+}
+
+func (p *SDTLineParser) Parse(rs []rune) {
+	for i, v := range rs {
+		p.currentRunePos = i
+		quit := p.pushRune(v)
+		if quit {
+			return
+		}
+	}
+}
+
+func (p *SDTLineParser) TryGetRunFunc() (bool, ExecuteFunc) {
+	if !p.errInParse {
+		if len(p.currentToken) > 0 {
+			tok := string(p.currentToken)
+			p.currentToken = []rune{}
+			ok := p.walker.NextStep(tok)
+			if !ok {
+				p.errInParse = true
+				p.errPos = p.currentRunePos
+				if p.walker.HasError() {
+					p.errMsg = p.walker.GetError()
+				}
+				return false, nil
+			}
+		}
+		ok := p.walker.NextStep("")
+		if !ok {
+			p.errInParse = true
+			p.errPos = p.currentRunePos
+			if p.walker.HasError() {
+				p.errMsg = p.walker.GetError()
+			}
+			return false, nil
+		} else {
+			return p.walker.GetEndExecute()
+		}
+	}
+	return false, nil
+}
+
+func (p *SDTLineParser) pushRune(r rune) bool {
+	switch p.status {
+	case hhc_common.InputStat_Normal:
+		return p.pushNormal(r)
+	case hhc_common.InputStat_Quote:
+		return p.pushQuote(r)
+	case hhc_common.InputStat_BackslashEscape:
+		return p.pushBackslashEscape(r)
+	default:
+		return true
+	}
+}
+
+func (p *SDTLineParser) pushBackslashEscape(r rune) bool {
+	switch r {
+	case '\\':
+		p.currentToken = append(p.currentToken, '\\')
+		break
+	case '"':
+		p.currentToken = append(p.currentToken, '"')
+		break
+	}
+	p.status = hhc_common.InputStat_Quote
+	return false
+}
+
+func (p *SDTLineParser) pushQuote(r rune) bool {
+	switch r {
+	case '\\':
+		p.status = hhc_common.InputStat_BackslashEscape
+		return false
+	case '"':
+		p.status = hhc_common.InputStat_Normal
+		return false
+	default:
+		p.currentToken = append(p.currentToken, r)
+		return false
+	}
+}
+
+func (p *SDTLineParser) GetHelpListForTabComplete() ([]hhc_common.SDTHelpInfo, string) {
+	tok := string(p.currentToken)
+	return p.walker.GetHelps(tok), tok
+}
+
+func (p *SDTLineParser) GetWalkContext() *SDTWalkContext {
+	return p.walker.GetContext()
+}
+
+func (p *SDTLineParser) pushNormal(r rune) bool {
+	switch r {
+	case ' ':
+		if !p.errInParse {
+			tok := string(p.currentToken)
+			p.currentToken = []rune{}
+			if len(tok) == 0 {
+				return false
+			}
+			ok := p.walker.NextStep(tok)
+			if !ok {
+				p.errInParse = true
+				p.errPos = p.currentRunePos
+				if p.walker.HasError() {
+					p.errMsg = p.walker.GetError()
+				}
+			}
+		}
+		return false
+	case '?':
+		p.helpTrigged = true
+		if !p.errInParse {
+			p.willPrintHelp = true
+			p.helpMsg = p.walker.GetHelps(string(p.currentToken))
+			if len(p.helpMsg) == 0 {
+				p.willPrintHelp = false
+				p.errInParse = true
+				p.errMsg = hhc_common.SDTWalkError_UnrecognizedCommand{RelyToken: string(p.currentToken)}
+				p.errPos = p.currentRunePos
+			}
+		}
+		return true
+	case '"':
+		p.status = hhc_common.InputStat_Quote
+		return false
+	default:
+		p.currentToken = append(p.currentToken, r)
+		return false
+	}
+}

+ 130 - 0
hhc_ast/sdt_node_arg.go

@@ -0,0 +1,130 @@
+package hhc_ast
+
+import (
+	"git.swzry.com/zry/go-hhc-cli/hhc_common"
+	"strconv"
+)
+
+type SDTNodeArgumentValidator func(token string) bool
+
+type SDTNode_Argument struct {
+	FormatDescription string
+	Description       string
+	Parent            SyntaxDefTreeNode
+	SubCommands       *SDT_TrieContainer
+	ValArguments      []*SDTNode_Argument
+	End               *SDTNode_End
+	Validator         SDTNodeArgumentValidator
+}
+
+func (n *SDTNode_Argument) Validate(token string) bool {
+	return n.Validator(token)
+}
+
+func (n *SDTNode_Argument) IsEnd() bool {
+	return false
+}
+
+func (n *SDTNode_Argument) WalkNext(ctx *SDTWalkContext, token string) (SyntaxDefTreeNode, hhc_common.SDTWalkError) {
+	if token == "" {
+		if n.End != nil {
+			return n.End, nil
+		} else {
+			return nil, hhc_common.SDTWalkError_IncompleteCommand{}
+		}
+	}
+	if n.SubCommands != nil {
+		v, err := n.SubCommands.WalkUtil(token)
+		if err != nil {
+			return nil, err
+		}
+		if v != nil {
+			ctx.ASTNodes = append(ctx.ASTNodes, ASTNode_Command{
+				Name: v.Name,
+			})
+			return v, nil
+		}
+	}
+	if n.ValArguments != nil {
+		for i, v := range n.ValArguments {
+			if v != nil {
+				if v.Validate(token) {
+					ctx.ASTNodes = append(ctx.ASTNodes, ASTNode_ValArg{
+						RawToken: token,
+					})
+					return v, nil
+				}
+			} else {
+				return nil, &hhc_common.SDTWalkError_NullPointerException{RelyToken: token, RelyEntity: "ValArguments[" + strconv.Itoa(i) + "]"}
+			}
+		}
+	}
+	return nil, hhc_common.SDTWalkError_UnrecognizedCommand{RelyToken: token}
+}
+
+func (n *SDTNode_Argument) GetHelps(prefix string) []hhc_common.SDTHelpInfo {
+	hi := make([]hhc_common.SDTHelpInfo, 0)
+	if n.ValArguments != nil {
+		for _, v := range n.ValArguments {
+			hi = append(hi, hhc_common.SDTHelpInfo{
+				IsArg:       true,
+				Name:        v.FormatDescription,
+				Description: v.Description,
+			})
+		}
+	}
+	if n.SubCommands != nil {
+		kl := n.SubCommands.PrefixGetList(prefix)
+		for _, v := range kl {
+			hi = append(hi, hhc_common.SDTHelpInfo{
+				IsArg:       false,
+				Name:        v.Name,
+				Description: v.Description,
+			})
+		}
+	}
+	if n.End != nil {
+		hi = append(hi, hhc_common.SDTHelpInfo{
+			IsArg:       true,
+			Name:        "<cr>",
+			Description: "",
+		})
+	}
+	return hi
+}
+
+func (n *SDTNode_Argument) Fallback(ctx *SDTWalkContext) (SyntaxDefTreeNode, hhc_common.SDTWalkError) {
+	if n.Parent != nil {
+		if len(ctx.ASTNodes) > 0 {
+			ctx.ASTNodes = ctx.ASTNodes[:len(ctx.ASTNodes)-1]
+		}
+		return n.Parent, nil
+	} else {
+		return nil, hhc_common.SDTWalkError_LossingParent{RelyName: "#ValArgument"}
+	}
+}
+func (n *SDTNode_Argument) AddSubCommand(cmd *SDTNode_Command) *SDTNode_Argument {
+	if n.SubCommands == nil {
+		n.SubCommands = NewSDT_TrieContainer()
+	}
+	n.SubCommands.Put(cmd)
+	cmd.Parent = n
+	return n
+}
+
+func (n *SDTNode_Argument) AddValArgument(valarg *SDTNode_Argument) *SDTNode_Argument {
+	if n.ValArguments == nil {
+		n.ValArguments = make([]*SDTNode_Argument, 1)
+		n.ValArguments[0] = valarg
+	} else {
+		n.ValArguments = append(n.ValArguments, valarg)
+	}
+	valarg.Parent = n
+	return n
+}
+
+func (n *SDTNode_Argument) AddEnd(end *SDTNode_End) *SDTNode_Argument {
+	n.End = end
+	end.Parent = n
+	return n
+}

+ 125 - 0
hhc_ast/sdt_node_command.go

@@ -0,0 +1,125 @@
+package hhc_ast
+
+import (
+	"git.swzry.com/zry/go-hhc-cli/hhc_common"
+	"strconv"
+)
+
+type SDTNode_Command struct {
+	Name        string
+	Description string
+	Parent      SyntaxDefTreeNode
+	//SubCommands map[string]*SDTNode_Command
+	SubCommands  *SDT_TrieContainer
+	ValArguments []*SDTNode_Argument
+	End          *SDTNode_End
+}
+
+func (n *SDTNode_Command) IsEnd() bool {
+	return false
+}
+
+func (n *SDTNode_Command) WalkNext(ctx *SDTWalkContext, token string) (SyntaxDefTreeNode, hhc_common.SDTWalkError) {
+	if token == "" {
+		if n.End != nil {
+			return n.End, nil
+		} else {
+			return nil, hhc_common.SDTWalkError_IncompleteCommand{}
+		}
+	}
+	if n.SubCommands != nil {
+		v, err := n.SubCommands.WalkUtil(token)
+		if err != nil {
+			return nil, err
+		}
+		if v != nil {
+			ctx.ASTNodes = append(ctx.ASTNodes, ASTNode_Command{
+				Name: v.Name,
+			})
+			return v, nil
+		}
+	}
+	if n.ValArguments != nil {
+		for i, v := range n.ValArguments {
+			if v != nil {
+				if v.Validate(token) {
+					ctx.ASTNodes = append(ctx.ASTNodes, ASTNode_ValArg{
+						RawToken: token,
+					})
+					return v, nil
+				}
+			} else {
+				return nil, &hhc_common.SDTWalkError_NullPointerException{RelyToken: token, RelyEntity: "ValArguments[" + strconv.Itoa(i) + "]"}
+			}
+		}
+	}
+	return nil, hhc_common.SDTWalkError_UnrecognizedCommand{RelyToken: token}
+}
+
+func (n *SDTNode_Command) GetHelps(prefix string) []hhc_common.SDTHelpInfo {
+	hi := make([]hhc_common.SDTHelpInfo, 0)
+	if n.ValArguments != nil {
+		for _, v := range n.ValArguments {
+			hi = append(hi, hhc_common.SDTHelpInfo{
+				IsArg:       true,
+				Name:        v.FormatDescription,
+				Description: v.Description,
+			})
+		}
+	}
+	if n.SubCommands != nil {
+		kl := n.SubCommands.PrefixGetList(prefix)
+		for _, v := range kl {
+			hi = append(hi, hhc_common.SDTHelpInfo{
+				IsArg:       false,
+				Name:        v.Name,
+				Description: v.Description,
+			})
+		}
+	}
+	if n.End != nil {
+		hi = append(hi, hhc_common.SDTHelpInfo{
+			IsArg:       true,
+			Name:        "<cr>",
+			Description: "",
+		})
+	}
+	return hi
+}
+
+func (n *SDTNode_Command) Fallback(ctx *SDTWalkContext) (SyntaxDefTreeNode, hhc_common.SDTWalkError) {
+	if n.Parent != nil {
+		if len(ctx.ASTNodes) > 0 {
+			ctx.ASTNodes = ctx.ASTNodes[:len(ctx.ASTNodes)-1]
+		}
+		return n.Parent, nil
+	} else {
+		return nil, hhc_common.SDTWalkError_LossingParent{RelyName: n.Name}
+	}
+}
+
+func (n *SDTNode_Command) AddSubCommand(cmd *SDTNode_Command) *SDTNode_Command {
+	if n.SubCommands == nil {
+		n.SubCommands = NewSDT_TrieContainer()
+	}
+	n.SubCommands.Put(cmd)
+	cmd.Parent = n
+	return n
+}
+
+func (n *SDTNode_Command) AddValArgument(valarg *SDTNode_Argument) *SDTNode_Command {
+	if n.ValArguments == nil {
+		n.ValArguments = make([]*SDTNode_Argument, 1)
+		n.ValArguments[0] = valarg
+	} else {
+		n.ValArguments = append(n.ValArguments, valarg)
+	}
+	valarg.Parent = n
+	return n
+}
+
+func (n *SDTNode_Command) AddEnd(end *SDTNode_End) *SDTNode_Command {
+	n.End = end
+	end.Parent = n
+	return n
+}

+ 35 - 0
hhc_ast/sdt_node_end.go

@@ -0,0 +1,35 @@
+package hhc_ast
+
+import "git.swzry.com/zry/go-hhc-cli/hhc_common"
+
+type SDTNode_End struct {
+	Parent SyntaxDefTreeNode
+	Exec   ExecuteFunc
+}
+
+func (n *SDTNode_End) IsEnd() bool {
+	return true
+}
+
+func (n *SDTNode_End) WalkNext(ctx *SDTWalkContext, token string) (SyntaxDefTreeNode, hhc_common.SDTWalkError) {
+	return nil, hhc_common.SDTWalkError_TooManyParameters{}
+}
+
+func (n *SDTNode_End) GetHelps(prefix string) []hhc_common.SDTHelpInfo {
+	return []hhc_common.SDTHelpInfo{}
+}
+
+func (n *SDTNode_End) Fallback(ctx *SDTWalkContext) (SyntaxDefTreeNode, hhc_common.SDTWalkError) {
+	if n.Parent != nil {
+		if len(ctx.ASTNodes) > 0 {
+			ctx.ASTNodes = ctx.ASTNodes[:len(ctx.ASTNodes)-1]
+		}
+		return n.Parent, nil
+	} else {
+		return nil, hhc_common.SDTWalkError_LossingParent{RelyName: "#End"}
+	}
+}
+
+func (n *SDTNode_End) GetExecuteFunc() ExecuteFunc {
+	return n.Exec
+}

+ 77 - 0
hhc_ast/sdt_trie_dcnt.go

@@ -0,0 +1,77 @@
+package hhc_ast
+
+import (
+	"git.swzry.com/zry/go-hhc-cli/hhc_common"
+	"github.com/derekparker/trie"
+)
+
+type SDT_TrieContainer struct {
+	TrieTree *trie.Trie
+	DataMap  map[string]*SDTNode_Command
+}
+
+func NewSDT_TrieContainer() *SDT_TrieContainer {
+	return &SDT_TrieContainer{
+		TrieTree: trie.New(),
+		DataMap:  make(map[string]*SDTNode_Command),
+	}
+}
+
+func (c *SDT_TrieContainer) Put(cmd *SDTNode_Command) {
+	k := cmd.Name
+	c.TrieTree.Add(k, nil)
+	c.DataMap[k] = cmd
+}
+
+func (c *SDT_TrieContainer) ExactGet(key string) (*SDTNode_Command, bool) {
+	v, ok := c.DataMap[key]
+	return v, ok
+}
+
+func (c *SDT_TrieContainer) PrefixGetList(prefix string) []*SDTNode_Command {
+	lst := make([]*SDTNode_Command, 0)
+	kl := c.TrieTree.PrefixSearch(prefix)
+	for _, v := range kl {
+		ov, ok := c.DataMap[v]
+		if ok {
+			lst = append(lst, ov)
+		}
+	}
+	return lst
+}
+
+func (c *SDT_TrieContainer) PrefixGetOne(prefix string) (*SDTNode_Command, bool, bool) {
+	kl := c.TrieTree.PrefixSearch(prefix)
+	if len(kl) == 0 {
+		return nil, false, false
+	}
+	if len(kl) > 1 {
+		return nil, true, false
+	}
+	v, ok := c.DataMap[kl[0]]
+	if ok {
+		return v, true, true
+	}
+	return nil, false, true
+}
+
+func (c *SDT_TrieContainer) WalkUtil(token string) (*SDTNode_Command, hhc_common.SDTWalkError) {
+	v, okx := c.DataMap[token]
+	if okx {
+		return v, nil
+	}
+	v, ok1, ok2 := c.PrefixGetOne(token)
+	if ok1 {
+		if ok2 {
+			if v != nil {
+				return v, nil
+			} else {
+				return nil, hhc_common.SDTWalkError_NullPointerException{RelyToken: token, RelyEntity: "Commands"}
+			}
+		} else {
+			return nil, hhc_common.SDTWalkError_TooShortPrefix{}
+		}
+	} else {
+		return nil, nil
+	}
+}

+ 5 - 0
hhc_ast/sdt_walk_context.go

@@ -0,0 +1,5 @@
+package hhc_ast
+
+type SDTWalkContext struct {
+	ASTNodes []ASTNode
+}

+ 113 - 0
hhc_ast/sdt_walker.go

@@ -0,0 +1,113 @@
+package hhc_ast
+
+import (
+	"git.swzry.com/zry/go-hhc-cli/hhc_common"
+)
+
+type SDTWalker struct {
+	root         *SyntaxDefinitionTreeRoot
+	current      SyntaxDefTreeNode
+	ctx          *SDTWalkContext
+	currentError hhc_common.SDTWalkError
+	depthCount   uint32
+	actualDepth  uint32
+}
+
+func NewSDTWalker(rootnode *SyntaxDefinitionTreeRoot) *SDTWalker {
+	w := &SDTWalker{
+		root:         rootnode,
+		current:      rootnode,
+		ctx:          &SDTWalkContext{ASTNodes: []ASTNode{}},
+		currentError: nil,
+		depthCount:   0,
+		actualDepth:  0,
+	}
+	return w
+}
+
+func (w *SDTWalker) Reset() {
+	w.current = w.root
+	w.ctx = &SDTWalkContext{ASTNodes: []ASTNode{}}
+	w.currentError = nil
+	w.depthCount = 0
+	w.actualDepth = 0
+}
+
+func (w *SDTWalker) NextStep(token string) bool {
+	//fmt.Println("On NextStep, dc=", w.depthCount, "ad=", w.actualDepth)
+	w.depthCount++
+	if w.depthCount == w.actualDepth+1 {
+		nc, serr := w.current.WalkNext(w.ctx, token)
+		if serr != nil {
+			w.currentError = serr
+			return false
+		}
+		w.current = nc
+		w.actualDepth++
+		w.currentError = nil
+		return true
+	}
+	return false
+}
+
+func (w *SDTWalker) Back() bool {
+	//fmt.Println("On Back, dc=", w.depthCount, "ad=", w.actualDepth)
+	if w.depthCount > 0 {
+		w.depthCount--
+		if w.depthCount < w.actualDepth {
+			nc, serr := w.current.Fallback(w.ctx)
+			if serr != nil {
+				switch serr.(type) {
+				case hhc_common.SDTWalkError_RootNodeCannotFallback:
+					w.Reset()
+					return false
+				}
+				w.currentError = serr
+				return false
+			}
+			w.current = nc
+			w.actualDepth--
+			w.currentError = nil
+			return true
+		}
+	}
+	return false
+}
+
+func (w *SDTWalker) GetHelps(prefix string) []hhc_common.SDTHelpInfo {
+	return w.current.GetHelps(prefix)
+}
+
+func (w *SDTWalker) GetContext() *SDTWalkContext {
+	return w.ctx
+}
+
+func (w *SDTWalker) GetError() hhc_common.SDTWalkError {
+	return w.currentError
+}
+
+func (w *SDTWalker) HasError() bool {
+	return w.currentError != nil
+}
+
+func (w *SDTWalker) IsSync() bool {
+	return w.actualDepth == w.actualDepth
+}
+
+func (w *SDTWalker) IsEnd() bool {
+	return w.current.IsEnd()
+}
+
+func (w *SDTWalker) GetEndExecute() (bool, ExecuteFunc) {
+	if w.IsEnd() {
+		e := w.current.(*SDTNode_End)
+		if e != nil {
+			ef := e.Exec
+			return true, ef
+		} else {
+			return false, nil
+		}
+	} else {
+		return false, nil
+	}
+}

+ 63 - 0
hhc_ast/syntax_def_tree.go

@@ -0,0 +1,63 @@
+package hhc_ast
+
+import "git.swzry.com/zry/go-hhc-cli/hhc_common"
+
+type SyntaxDefTreeNode interface {
+	IsEnd() bool
+	WalkNext(ctx *SDTWalkContext, token string) (SyntaxDefTreeNode, hhc_common.SDTWalkError)
+	GetHelps(prefix string) []hhc_common.SDTHelpInfo
+	Fallback(ctx *SDTWalkContext) (SyntaxDefTreeNode, hhc_common.SDTWalkError)
+}
+
+type SyntaxDefinitionTreeRoot struct {
+	//Commands map[string]*SDTNode_Command
+	Commands *SDT_TrieContainer
+}
+
+func (n *SyntaxDefinitionTreeRoot) IsEnd() bool {
+	return false
+}
+
+func (n *SyntaxDefinitionTreeRoot) GetHelps(prefix string) []hhc_common.SDTHelpInfo {
+	hi := make([]hhc_common.SDTHelpInfo, 0)
+	if n.Commands != nil {
+		kl := n.Commands.PrefixGetList(prefix)
+		for _, v := range kl {
+			hi = append(hi, hhc_common.SDTHelpInfo{
+				IsArg:       false,
+				Name:        v.Name,
+				Description: v.Description,
+			})
+		}
+	}
+	return hi
+}
+
+func (n *SyntaxDefinitionTreeRoot) WalkNext(ctx *SDTWalkContext, token string) (SyntaxDefTreeNode, hhc_common.SDTWalkError) {
+	if n.Commands != nil {
+		v, err := n.Commands.WalkUtil(token)
+		if err != nil {
+			return nil, err
+		}
+		if v != nil {
+			ctx.ASTNodes = append(ctx.ASTNodes, ASTNode_Command{
+				Name: v.Name,
+			})
+			return v, nil
+		}
+	}
+	return nil, &hhc_common.SDTWalkError_UnrecognizedCommand{RelyToken: token}
+}
+
+func (n *SyntaxDefinitionTreeRoot) Fallback(ctx *SDTWalkContext) (SyntaxDefTreeNode, hhc_common.SDTWalkError) {
+	return nil, hhc_common.SDTWalkError_RootNodeCannotFallback{}
+}
+
+func (n *SyntaxDefinitionTreeRoot) AddCommand(cmd *SDTNode_Command) *SyntaxDefinitionTreeRoot {
+	if n.Commands == nil {
+		n.Commands = NewSDT_TrieContainer()
+	}
+	n.Commands.Put(cmd)
+	cmd.Parent = n
+	return n
+}

+ 309 - 0
hhc_common/runes_editor.go

@@ -0,0 +1,309 @@
+package hhc_common
+
+import (
+	"fmt"
+	"io"
+	"strings"
+)
+
+type RunesEditor struct {
+	currentLine     []rune
+	history         [][]rune
+	insertPosition  int
+	historyPosition int
+	cleanLineOffset int
+	cleanLineCount  int
+	lastTermWidth   int
+	lastTermPeekOut []string
+}
+
+func NewRunesEditor() *RunesEditor {
+	re := &RunesEditor{}
+	re.ClearAll()
+	return re
+}
+
+func (re *RunesEditor) ClearHistory() {
+	re.history = make([][]rune, 0)
+	re.historyPosition = 0
+}
+
+func (re *RunesEditor) ClearCurrentLine(saveHistory bool) {
+	if saveHistory {
+		if len(re.currentLine) > 0 {
+			l := len(re.history)
+			if l > 0 {
+				if !TestRuneSlice(re.history[l-1], re.currentLine) {
+					re.history = append(re.history, re.currentLine)
+				}
+			} else {
+				re.history = append(re.history, re.currentLine)
+			}
+		}
+	}
+	re.historyPosition = len(re.history)
+	re.currentLine = []rune{}
+	re.insertPosition = 0
+}
+
+func (re *RunesEditor) ClearAll() {
+	re.ClearCurrentLine(false)
+	re.ClearHistory()
+}
+
+func (re *RunesEditor) fixInsertPos() {
+	bl := len(re.currentLine)
+	if re.insertPosition > bl {
+		re.insertPosition = bl
+	}
+	if re.insertPosition < 0 {
+		re.insertPosition = 0
+	}
+}
+
+func (re *RunesEditor) InsertString(s string) {
+	rs := []rune(s)
+	for _, v := range rs {
+		re.InsertRune(v)
+	}
+}
+
+func (re *RunesEditor) InsertRune(r rune) {
+	re.fixInsertPos()
+	p := re.insertPosition
+	switch {
+	case p == 0:
+		nrs := make([]rune, 1, len(re.currentLine)+1)
+		nrs[0] = r
+		re.currentLine = append(nrs, re.currentLine...)
+		break
+	case p == len(re.currentLine):
+		re.currentLine = append(re.currentLine, r)
+		break
+	default:
+		left := re.currentLine[:p]
+		right := re.currentLine[p:]
+		nrs := make([]rune, 0, len(re.currentLine)+1)
+		nrs = append(nrs, left...)
+		nrs = append(nrs, r)
+		re.currentLine = append(nrs, right...)
+		break
+	}
+	re.insertPosition++
+	re.fixInsertPos()
+}
+
+func (re *RunesEditor) MoveInsertPosition(offset int) {
+	re.insertPosition += offset
+	re.fixInsertPos()
+}
+
+func (re *RunesEditor) SetInsertPosition(pos int) {
+	re.insertPosition = pos
+	re.fixInsertPos()
+}
+
+func (re *RunesEditor) Backspace() {
+	re.fixInsertPos()
+	l := len(re.currentLine)
+	switch {
+	case re.insertPosition <= 0:
+		re.insertPosition = 0
+		return
+	case l == 0:
+		return
+	case re.insertPosition == l:
+		re.currentLine = re.currentLine[:l-1]
+		break
+	default:
+		p := re.insertPosition
+		left := re.currentLine[:p-1]
+		right := re.currentLine[p:]
+		nrs := make([]rune, 0, len(re.currentLine)-1)
+		nrs = append(nrs, left...)
+		re.currentLine = append(nrs, right...)
+		break
+	}
+	re.insertPosition--
+	re.fixInsertPos()
+}
+
+func (re *RunesEditor) Delete() {
+	re.fixInsertPos()
+	l := len(re.currentLine)
+	switch {
+	case re.insertPosition == l:
+		return
+	default:
+		p := re.insertPosition
+		left := re.currentLine[:p]
+		right := re.currentLine[p+1:]
+		nrs := make([]rune, 0, len(re.currentLine)-1)
+		nrs = append(nrs, left...)
+		re.currentLine = append(nrs, right...)
+		break
+	}
+	re.fixInsertPos()
+}
+
+func (re *RunesEditor) Enter() []rune {
+	ret := re.currentLine
+	re.ClearCurrentLine(true)
+	return ret
+}
+
+func (re *RunesEditor) Peek() []rune {
+	return re.currentLine
+}
+
+func (re *RunesEditor) fixHistoryPos() {
+	l := len(re.history)
+	if re.historyPosition < 0 {
+		re.historyPosition = 0
+	}
+	if re.historyPosition > l {
+		re.historyPosition = l
+	}
+}
+
+func (re *RunesEditor) LoadNextHistory() {
+	l := len(re.history)
+	if re.historyPosition == l {
+		return
+	}
+	re.historyPosition++
+	re.fixHistoryPos()
+	if re.historyPosition == l {
+		re.ClearCurrentLine(false)
+		return
+	}
+	re.currentLine = re.history[re.historyPosition]
+	re.MoveInsertPositionToEnd()
+}
+
+func (re *RunesEditor) LoadPreviousHistory() {
+	l := len(re.history)
+	if l == 0 {
+		return
+	}
+	re.historyPosition--
+	re.fixHistoryPos()
+	if re.historyPosition == l {
+		re.ClearCurrentLine(false)
+		return
+	}
+	re.currentLine = re.history[re.historyPosition]
+	re.MoveInsertPositionToEnd()
+}
+
+func (re *RunesEditor) MoveInsertPositionToEnd() {
+	re.insertPosition = len(re.currentLine)
+}
+
+func (re *RunesEditor) MoveInsertPositionToHome() {
+	re.insertPosition = 0
+}
+
+func (re *RunesEditor) GetInsertPosition() int {
+	return re.insertPosition
+}
+
+func (re *RunesEditor) CleanTerminalPeek(wr io.Writer) {
+	switch {
+	case re.cleanLineOffset > 0:
+		_, _ = fmt.Fprintf(wr, "\033[%dA", re.cleanLineOffset)
+		break
+	case re.cleanLineOffset < 0:
+		_, _ = fmt.Fprintf(wr, "\033[%dB", -re.cleanLineOffset)
+		break
+	}
+	for i := 0; i < re.cleanLineCount; i++ {
+		_, _ = fmt.Fprint(wr, "\r\033[K")
+		if i != re.cleanLineCount-1 {
+			_, _ = fmt.Fprint(wr, "\033[1A")
+		}
+	}
+}
+
+func (re *RunesEditor) backTerminalPeekCursorToHome(wr io.Writer) {
+	switch {
+	case re.cleanLineOffset > 0:
+		_, _ = fmt.Fprintf(wr, "\033[%dA", re.cleanLineOffset)
+		break
+	case re.cleanLineOffset < 0:
+		_, _ = fmt.Fprintf(wr, "\033[%dB", -re.cleanLineOffset)
+		break
+	}
+	for i := 0; i < re.cleanLineCount; i++ {
+		if i != re.cleanLineCount-1 {
+			_, _ = fmt.Fprint(wr, "\033[1A")
+		} else {
+			_, _ = fmt.Fprint(wr, "\r")
+		}
+	}
+}
+
+func (re *RunesEditor) RefreshTerminal(wr io.Writer, prefix string, termWidth int, diffRefresh bool) {
+	if diffRefresh {
+		if termWidth != re.lastTermWidth {
+			re.hardRefreshTerminal(wr, prefix, termWidth)
+			return
+		}
+		// TODO: Support for diffRefresh
+		// Now temporary using hardRefresh instead.
+		re.hardRefreshTerminal(wr, prefix, termWidth)
+		return
+	} else {
+		re.hardRefreshTerminal(wr, prefix, termWidth)
+	}
+}
+
+func (re *RunesEditor) hardRefreshTerminal(wr io.Writer, prefix string, termWidth int) {
+	re.CleanTerminalPeek(wr)
+	re.PeekIntoTerminal(prefix, termWidth, wr)
+}
+
+func (re *RunesEditor) PeekIntoTerminal(prefix string, termWidth int, wr io.Writer) {
+	re.lastTermWidth = termWidth
+	re.fixInsertPos()
+	rc := re.Peek()
+	sc := string(rc)
+	psb := strings.Builder{}
+	psb.WriteString(prefix)
+	psb.WriteString(sc)
+	ws := psb.String()
+	lpre := len([]rune(prefix))
+	curpos := lpre + re.insertPosition
+	lines, cy, cx := PrintRunesIntoLinesWithCursor([]rune(ws), termWidth, curpos)
+	re.lastTermPeekOut = lines
+	re.cleanLineCount = len(lines)
+	lastln := re.cleanLineCount - 1
+	lastline := lines[lastln]
+	lll := len(lastline)
+	for i, line := range lines {
+		_, _ = fmt.Fprint(wr, line)
+		if i != lastln {
+			_, _ = fmt.Fprint(wr, "\r\n")
+		}
+	}
+	bky := lastln - cy
+	bkx := lll - cx + 1
+	re.cleanLineOffset = -bky
+	//fmt.Println("BKY=", bky, "BKX=", bkx)
+	switch {
+	case bky > 0:
+		_, _ = fmt.Fprintf(wr, "\033[%dA", bky)
+		break
+	case bky < 0:
+		_, _ = fmt.Fprintf(wr, "\033[%dB", -bky)
+		break
+	}
+	switch {
+	case bkx > 0:
+		_, _ = fmt.Fprintf(wr, "\033[%dD", bkx)
+		break
+	case bkx < 0:
+		_, _ = fmt.Fprintf(wr, "\033[%dC", -bkx)
+		break
+	}
+}

+ 7 - 0
hhc_common/sdt_helpinfo.go

@@ -0,0 +1,7 @@
+package hhc_common
+
+type SDTHelpInfo struct {
+	IsArg       bool
+	Name        string
+	Description string
+}

+ 89 - 0
hhc_common/sdt_walk_error.go

@@ -0,0 +1,89 @@
+package hhc_common
+
+import "fmt"
+
+type SDTWalkError interface {
+	FES() string // Friendly Error String
+	EES() string // Expert Error String
+}
+
+type SDTWalkError_NullPointerException struct {
+	RelyToken  string
+	RelyEntity string
+}
+
+func (e SDTWalkError_NullPointerException) FES() string {
+	return "go-hhc-cli parser internal error"
+}
+
+func (e SDTWalkError_NullPointerException) EES() string {
+	return fmt.Sprint("null pointer exception. rely token:", e.RelyToken, "rely entity:", e.RelyEntity)
+}
+
+type SDTWalkError_UnrecognizedCommand struct {
+	RelyToken string
+}
+
+func (e SDTWalkError_UnrecognizedCommand) FES() string {
+	return fmt.Sprint("unrecognized command: ", e.RelyToken)
+}
+
+func (e SDTWalkError_UnrecognizedCommand) EES() string {
+	return fmt.Sprint("unrecognized command: ", e.RelyToken)
+}
+
+type SDTWalkError_RootNodeCannotFallback struct {
+}
+
+func (e SDTWalkError_RootNodeCannotFallback) FES() string {
+	return "go-hhc-cli parser internal error"
+}
+
+func (e SDTWalkError_RootNodeCannotFallback) EES() string {
+	return "root node can not fallback"
+}
+
+type SDTWalkError_IncompleteCommand struct {
+}
+
+func (e SDTWalkError_IncompleteCommand) FES() string {
+	return "incomplete command"
+}
+
+func (e SDTWalkError_IncompleteCommand) EES() string {
+	return "incomplete command"
+}
+
+type SDTWalkError_LossingParent struct {
+	RelyName string
+}
+
+func (e SDTWalkError_LossingParent) FES() string {
+	return "go-hhc-cli parser internal error"
+}
+
+func (e SDTWalkError_LossingParent) EES() string {
+	return fmt.Sprint("lossing parent for", e.RelyName)
+}
+
+type SDTWalkError_TooManyParameters struct {
+}
+
+func (e SDTWalkError_TooManyParameters) FES() string {
+	return "too many parameters"
+}
+
+func (e SDTWalkError_TooManyParameters) EES() string {
+	return "too many parameters"
+}
+
+type SDTWalkError_TooShortPrefix struct {
+}
+
+func (e SDTWalkError_TooShortPrefix) FES() string {
+	return "your input prefix too short so it means multiple selection"
+}
+
+func (e SDTWalkError_TooShortPrefix) EES() string {
+	return "too short prefix"
+}

+ 20 - 0
hhc_common/status.go

@@ -0,0 +1,20 @@
+package hhc_common
+
+const (
+	InputStat_Normal             InputStatusCode = 0
+	InputStat_Quote              InputStatusCode = 1
+	InputStat_BackslashEscape    InputStatusCode = 2
+	InputStat_EscControlSequence InputStatusCode = 3
+)
+
+const (
+	TerminalState_Idle        = 0
+	TerminalState_Input       = 1
+	TerminalState_TCES        = 2
+	TerminalState_Execute     = 3
+	TerminalState_PrintResult = 4
+)
+
+type InputStatusCode uint8
+
+type TerminalState uint8

+ 200 - 0
hhc_common/utils.go

@@ -0,0 +1,200 @@
+package hhc_common
+
+import (
+	"fmt"
+	"github.com/cheynewallace/tabby"
+	"github.com/mattn/go-runewidth"
+	"github.com/ttacon/chalk"
+	"io"
+	"strings"
+	"text/tabwriter"
+	"unicode"
+)
+
+func FPrintHelp(w io.Writer, h []SDTHelpInfo) {
+	sw := &strings.Builder{}
+	t := tabby.NewCustom(tabwriter.NewWriter(sw, 0, 0, 2, ' ', 0))
+	for i, v := range h {
+		t.AddLine(i, v.Name, v.Description)
+	}
+	t.Print()
+	ostr := sw.String()
+	pcrlf := strings.ReplaceAll(ostr, "\n", "\r\n")
+	_, _ = fmt.Fprint(w, pcrlf)
+}
+
+func GetUnicodeDeleteWidth(r rune) int {
+	// Tested with ASCII non-control, Chinese, Japanese characters and Emoji.
+	// I am not sure this is ok for all Unicode characters, so I made it as a dedicated function.
+	return runewidth.RuneWidth(r)
+}
+
+type PrintableMessage struct {
+	Text  string
+	Style chalk.Style
+}
+
+func splitStringInWidth(tw int, cll int, s string) []string {
+	rstrl := []string{}
+	nstr := strings.Builder{}
+	lcc := cll
+	for _, v := range []rune(s) {
+		rw := runewidth.RuneWidth(v)
+		if rw+lcc > tw {
+			lcc = rw
+			rstrl = append(rstrl, nstr.String())
+			nstr = strings.Builder{}
+			nstr.WriteRune(v)
+		} else {
+			lcc += rw
+			nstr.WriteRune(v)
+		}
+	}
+	if nstr.Len() > 0 {
+		rstrl = append(rstrl, nstr.String())
+	}
+	return rstrl
+}
+
+func transToChalkedText(colorful bool, s string, styl chalk.Style) string {
+	if colorful {
+		if styl != nil {
+			return styl.Style(s)
+		}
+	}
+	return s
+}
+
+func PrintPrintablMessagesIntoLines(pm []PrintableMessage, termWidth int, colorful bool) []string {
+	rlns := []string{}
+	cln := strings.Builder{}
+	clcnt := 0
+	for _, v := range pm {
+		sw := runewidth.StringWidth(v.Text)
+		if sw+clcnt > termWidth {
+			spll := splitStringInWidth(termWidth, clcnt, v.Text)
+			for xis, vs := range spll {
+				if xis == 0 {
+					cln.WriteString(transToChalkedText(colorful, vs, v.Style))
+					rlns = append(rlns, cln.String())
+					cln = strings.Builder{}
+					clcnt = 0
+				} else {
+					if xis == len(spll)-1 {
+						cln.WriteString(transToChalkedText(colorful, vs, v.Style))
+						clcnt += runewidth.StringWidth(vs)
+					} else {
+						rlns = append(rlns, transToChalkedText(colorful, vs, v.Style))
+					}
+				}
+			}
+		} else {
+			clcnt += sw
+			cln.WriteString(transToChalkedText(colorful, v.Text, v.Style))
+		}
+		if clcnt == termWidth {
+			rlns = append(rlns, cln.String())
+			cln = strings.Builder{}
+		}
+	}
+	if cln.Len() > 0 {
+		rlns = append(rlns, cln.String())
+	}
+	return rlns
+}
+
+func PrintStringIntoLines(s string, termWidth int) []string {
+	if runewidth.StringWidth(s) <= termWidth {
+		return []string{s}
+	}
+	rlns := []string{}
+	cln := strings.Builder{}
+	clcnt := 0
+	ra := []rune(s)
+	for _, v := range ra {
+		rw := runewidth.RuneWidth(v)
+		if rw+clcnt > termWidth {
+			rlns = append(rlns, cln.String())
+			cln = strings.Builder{}
+			cln.WriteRune(v)
+			clcnt = rw
+		} else {
+			cln.WriteRune(v)
+			clcnt += rw
+		}
+	}
+	if cln.Len() > 0 {
+		rlns = append(rlns, cln.String())
+	}
+	return rlns
+}
+
+func TestRuneSlice(r1, r2 []rune) bool {
+	if r1 == nil && r2 == nil {
+		return true
+	}
+	if r1 == nil || r2 == nil {
+		return false
+	}
+	if len(r1) != len(r2) {
+		return false
+	}
+	for i := range r1 {
+		if r1[i] != r2[i] {
+			return false
+		}
+	}
+	return true
+}
+
+func PrintRunesIntoLinesWithCursor(ra []rune, termWidth int, cursorPos int) (l []string, curY, curX int) {
+	if runewidth.StringWidth(string(ra)) <= termWidth {
+		return []string{string(ra)}, 0, cursorPos + 1
+	}
+	rlns := []string{}
+	cln := strings.Builder{}
+	clcnt := 0
+	linecount := 0
+	cpy, cpx := 0, 0
+	hit := false
+	for i, v := range ra {
+		rw := runewidth.RuneWidth(v)
+		if rw+clcnt > termWidth {
+			rlns = append(rlns, cln.String())
+			linecount++
+			cln = strings.Builder{}
+			cln.WriteRune(v)
+			clcnt = rw
+		} else {
+			cln.WriteRune(v)
+			clcnt += rw
+		}
+		if i == cursorPos {
+			cpy = linecount
+			cpx = clcnt
+			hit = true
+		}
+	}
+	if cln.Len() > 0 {
+		rlns = append(rlns, cln.String())
+		linecount++
+	}
+	if !hit {
+		cpy = linecount - 1
+		cpx = clcnt + 1
+	}
+	//fmt.Println("CPY=", cpy, "CPX=", cpx)
+	return rlns, cpy, cpx
+}
+
+func FirstToUpper(s string) string {
+	rs := []rune(s)
+	if len(rs) > 0 {
+		rs[0] = unicode.ToUpper(rs[0])
+	}
+	return string(rs)
+}
+
+func GetCompleteSuffix(cmd, prefix string) string {
+	return strings.TrimPrefix(cmd, prefix) + " "
+}

+ 21 - 0
hhc_mangekyo/handler.go

@@ -0,0 +1,21 @@
+package hhc_mangekyo
+
+import (
+	"git.swzry.com/zry/go-hhc-cli/hhc_telws"
+	"git.swzry.com/zry/go-hhc-cli/hhccli"
+)
+
+type MangekyoCustomViewPortalCommandDefine struct {
+	CommandName     string
+	Description     string
+	GetViewNameFunc func(ctx *hhc_telws.TelwsSessionContext) string
+	GetPromptFunc   func(ctx *hhc_telws.TelwsSessionContext) string
+	GetTitleFunc    func(ctx *hhc_telws.TelwsSessionContext) string
+}
+
+type MangekyoUserLogicHandler interface {
+	GetDefaultPromptString(ctx *hhc_telws.TelwsSessionContext) string
+	GetDefaultTitle(ctx *hhc_telws.TelwsSessionContext) string
+	GetCustomViewPortalCommands() []MangekyoCustomViewPortalCommandDefine
+	NewSession(ctx *hhc_telws.TelwsSessionContext, cli *hhccli.TerminalInteractive) error
+}

+ 64 - 0
hhc_mangekyo/mangekyo.go

@@ -0,0 +1,64 @@
+package hhc_mangekyo
+
+import (
+	"context"
+	"fmt"
+	"git.swzry.com/zry/go-hhc-cli/hhc_telws"
+	"git.swzry.com/zry/go-hhc-cli/hhccli"
+)
+
+type MangekyoHandlerFactory struct {
+	uhdl MangekyoUserLogicHandler
+	mh   *MangekyoHandler
+}
+
+type MangekyoHandler struct {
+	fact *MangekyoHandlerFactory
+}
+
+func NewMangekyoHandlerFactory(handler MangekyoUserLogicHandler) *MangekyoHandlerFactory {
+	mhf := &MangekyoHandlerFactory{
+		uhdl: handler,
+	}
+	mhf.mh = &MangekyoHandler{
+		fact: mhf,
+	}
+	return mhf
+}
+
+func (mhf *MangekyoHandlerFactory) GetSessionHandler() hhc_telws.TelwsSessionHandler {
+	return mhf.mh
+}
+
+func (mh *MangekyoHandler) HandleConnection(ctx *hhc_telws.TelwsSessionContext) error {
+	rw := ctx.GetReadWriter()
+	dtitle := mh.fact.uhdl.GetDefaultTitle(ctx)
+	_, _ = fmt.Fprintf(rw, "\033]0;%s\007", dtitle)
+	_, _ = fmt.Fprintf(rw, "Welcome, %s.\r\n", ctx.GetUsername())
+	cfg := hhccli.TerminalInteractiveConfig{
+		InputStream:          rw,
+		OutputStream:         rw,
+		ErrorStream:          rw,
+		InitialTerminalWidth: 80,
+		DebugLogFunction: func(lv hhccli.TerminalInteractiveDebugLogLevel, msg ...interface{}) {
+
+		},
+		InitialPrompt: mh.fact.uhdl.GetDefaultPromptString(ctx),
+		BackspaceRune: '\x7F',
+	}
+	cli := hhccli.NewTerminalInteractive(cfg)
+	rctx := context.Background()
+	cvps := mh.fact.uhdl.GetCustomViewPortalCommands()
+	uv := NewMangeKyoUserView(ctx, cvps, cli)
+	cli.RegisterView("entry", uv)
+	err := mh.fact.uhdl.NewSession(ctx, cli)
+	if err != nil {
+		return err
+	}
+	err = cli.SetCurrentView("entry")
+	if err != nil {
+		return err
+	}
+	err = cli.Run(rctx)
+	return err
+}

+ 121 - 0
hhc_mangekyo/user_view.go

@@ -0,0 +1,121 @@
+package hhc_mangekyo
+
+import (
+	"fmt"
+	"git.swzry.com/zry/go-hhc-cli/hhc_ast"
+	"git.swzry.com/zry/go-hhc-cli/hhc_telws"
+	"git.swzry.com/zry/go-hhc-cli/hhc_validators"
+	"git.swzry.com/zry/go-hhc-cli/hhccli"
+	"strconv"
+)
+
+type MangekyoUserView struct {
+	treeroot *hhc_ast.SyntaxDefinitionTreeRoot
+	ctx      *hhc_telws.TelwsSessionContext
+	cvps     []MangekyoCustomViewPortalCommandDefine
+	cli      *hhccli.TerminalInteractive
+}
+
+func NewMangeKyoUserView(
+	ctx *hhc_telws.TelwsSessionContext,
+	cvps []MangekyoCustomViewPortalCommandDefine,
+	cli *hhccli.TerminalInteractive,
+) *MangekyoUserView {
+	uv := &MangekyoUserView{
+		ctx:  ctx,
+		cvps: cvps,
+		cli:  cli,
+	}
+	uv.makeTree()
+	return uv
+}
+
+func (uv *MangekyoUserView) GetSDTRoot() *hhc_ast.SyntaxDefinitionTreeRoot {
+	return uv.treeroot
+}
+
+func (uv *MangekyoUserView) makeTree() {
+	uv.treeroot = &hhc_ast.SyntaxDefinitionTreeRoot{}
+	for _, v := range uv.cvps {
+		cmd := &hhc_ast.SDTNode_Command{
+			Name:        v.CommandName,
+			Description: v.Description,
+		}
+		end := &hhc_ast.SDTNode_End{
+			Exec: func(ectx *hhc_ast.ExecUserContext) {
+				fview := v.GetViewNameFunc
+				fprompt := v.GetPromptFunc
+				ftitle := v.GetTitleFunc
+				ectx.ChangeView(fview(uv.ctx))
+				ectx.ChangePrompt(fprompt(uv.ctx))
+				ectx.ChangeTitle(ftitle(uv.ctx))
+			},
+		}
+		cmd.AddEnd(end)
+		uv.treeroot.AddCommand(cmd)
+	}
+	uv.addCommands()
+}
+
+func (uv *MangekyoUserView) addCommands() {
+	c_termwidth := &hhc_ast.SDTNode_Command{
+		Name:        "termwidth",
+		Description: "Set terminal width",
+	}
+	c_termwidth_arg1 := &hhc_ast.SDTNode_Argument{
+		FormatDescription: "INTEGER<10-1024>",
+		Description:       "Terminal width",
+		Validator: hhc_validators.RangedInteger{
+			Min: 10,
+			Max: 1024,
+		}.Validate,
+	}
+	c_termwidth_arg1_end := &hhc_ast.SDTNode_End{
+		Exec: uv.execTermwidth,
+	}
+	c_termwidth_arg1.AddEnd(c_termwidth_arg1_end)
+	c_termwidth.AddValArgument(c_termwidth_arg1)
+	uv.treeroot.AddCommand(c_termwidth)
+	c_disp := &hhc_ast.SDTNode_Command{
+		Name:        "disp",
+		Description: "Display information",
+	}
+	c_disp_termwidth := &hhc_ast.SDTNode_Command{
+		Name:        "termwidth",
+		Description: "Display terminal width",
+	}
+	c_disp_termwidth_end := &hhc_ast.SDTNode_End{
+		Exec: uv.execDispTermwidth,
+	}
+	c_disp_termwidth.AddEnd(c_disp_termwidth_end)
+	c_disp.AddSubCommand(c_disp_termwidth)
+	uv.treeroot.AddCommand(c_disp)
+}
+
+func (uv *MangekyoUserView) execDispTermwidth(ctx *hhc_ast.ExecUserContext) {
+	tw := uv.cli.GetTerminalWidth()
+	ctx.ResultPrintSingleLineString(fmt.Sprintf("Terminal width: %d", tw))
+}
+
+func (uv *MangekyoUserView) execTermwidth(ctx *hhc_ast.ExecUserContext) {
+	v, ok := ctx.GetArgument(1)
+	if !ok {
+		ctx.ResultPrintSingleLineString(" % invalid argument")
+		return
+	}
+	n, err := strconv.Atoi(v)
+	if err != nil {
+		ctx.ResultPrintSingleLineString(fmt.Sprintf(" %% invalid argument: %s", v))
+		return
+	}
+	if n < 10 {
+		ctx.ResultPrintSingleLineString(" % invalid argument - too small")
+		return
+	}
+	if n > 1024 {
+		ctx.ResultPrintSingleLineString(" % invalid argument - too large")
+		return
+	}
+	uv.cli.SetTerminalWidth(n)
+	ctx.ResultPrintSingleLineString(fmt.Sprintf("Terminal width set to %d.", n))
+}

+ 8 - 0
hhc_telws/auth_interface.go

@@ -0,0 +1,8 @@
+package hhc_telws
+
+import "time"
+
+type TelwsAuthHandler interface {
+	Register(jwtkey []byte, jwtttl time.Duration) (ahname string)
+	Login(authData map[string]string) (isSuccess bool, jwtdata string, emsg string)
+}

+ 75 - 0
hhc_telws/sess_context.go

@@ -0,0 +1,75 @@
+package hhc_telws
+
+import (
+	"github.com/pascaldekloe/jwt"
+	"io"
+	"net"
+)
+
+type TelwsSessionContext struct {
+	tscc *TelwsSessionContextContainer
+}
+
+type TelwsSessionContextContainer struct {
+	tsc       *TelwsSessionContext
+	jwtClaims *jwt.Claims
+	conn      net.Conn
+}
+
+func WrapNewTelwsSessionContext() *TelwsSessionContextContainer {
+	tscc := &TelwsSessionContextContainer{}
+	tsc := &TelwsSessionContext{
+		tscc: tscc,
+	}
+	tscc.tsc = tsc
+	return tscc
+}
+
+func (tscc *TelwsSessionContextContainer) GetUserContext() *TelwsSessionContext {
+	return tscc.tsc
+}
+
+func (tscc *TelwsSessionContextContainer) SetJwtClaims(clm *jwt.Claims) {
+	tscc.jwtClaims = clm
+}
+
+func (tscc *TelwsSessionContextContainer) SetConn(conn net.Conn) {
+	tscc.conn = conn
+}
+
+func (tsc *TelwsSessionContext) GetAuthInfo() map[string]interface{} {
+	return tsc.tscc.jwtClaims.Set
+}
+
+func (tsc *TelwsSessionContext) GetUsername() string {
+	t := tsc.tscc.jwtClaims.Set
+	if t == nil {
+		return ""
+	}
+	un, ok := t["username"]
+	if ok {
+		switch un.(type) {
+		case string:
+			return un.(string)
+		default:
+			return ""
+		}
+	}
+	return ""
+}
+
+func (tsc *TelwsSessionContext) GetWriter() io.Writer {
+	return tsc.tscc.conn
+}
+
+func (tsc *TelwsSessionContext) GetReader() io.Reader {
+	return tsc.tscc.conn
+}
+
+func (tsc *TelwsSessionContext) GetReadWriter() io.ReadWriter {
+	return tsc.tscc.conn
+}
+
+func (tsc *TelwsSessionContext) GetConn() net.Conn {
+	return tsc.tscc.conn
+}

+ 5 - 0
hhc_telws/sess_handler.go

@@ -0,0 +1,5 @@
+package hhc_telws
+
+type TelwsSessionHandler interface {
+	HandleConnection(ctx *TelwsSessionContext) error
+}

+ 5 - 0
hhc_telws/sess_handler_fact.go

@@ -0,0 +1,5 @@
+package hhc_telws
+
+type TelwsSessionHandlerFactory interface {
+	GetSessionHandler() TelwsSessionHandler
+}

+ 100 - 0
hhc_telws/simple_aaa.go

@@ -0,0 +1,100 @@
+package hhc_telws
+
+import (
+	"fmt"
+	"github.com/pascaldekloe/jwt"
+	"golang.org/x/crypto/bcrypt"
+	"time"
+)
+
+type TelwsAuthSimpleAAA struct {
+	jwtKey       []byte
+	userDatabase map[string]string
+	authLogFunc  func(isSuccess bool, msg string)
+	jwtttl       time.Duration
+}
+
+func NewTelwsAuthSimpleAAA() *TelwsAuthSimpleAAA {
+	ah := &TelwsAuthSimpleAAA{
+		jwtKey:       []byte{},
+		userDatabase: make(map[string]string),
+		authLogFunc:  func(isSuccess bool, msg string) {},
+	}
+	return ah
+}
+
+func (ah *TelwsAuthSimpleAAA) SetAuthLogFunc(authLogFunc func(isSuccess bool, msg string)) {
+	ah.authLogFunc = authLogFunc
+}
+
+func (ah *TelwsAuthSimpleAAA) SimpleAAAAddSimple(username, password string, cost int) error {
+	hash, err := bcrypt.GenerateFromPassword([]byte(password), cost)
+	if err != nil {
+		return err
+	}
+	ah.userDatabase[username] = string(hash)
+	return nil
+}
+
+func (ah *TelwsAuthSimpleAAA) SimpleAAAAddBcrypt(username, hash string) {
+	ah.userDatabase[username] = hash
+}
+
+func (ah *TelwsAuthSimpleAAA) Register(jwtkey []byte, jwtttl time.Duration) (ahname string) {
+	ah.jwtKey = jwtkey
+	ah.jwtttl = jwtttl
+	return "simple-aaa"
+}
+
+func (ah *TelwsAuthSimpleAAA) Login(authData map[string]string) (isSuccess bool, jwtdata string, emsg string) {
+	amet, ok := authData["method"]
+	if !ok {
+		ah.authLogFunc(false, "no auth method specified")
+		return false, "", "no auth method specified"
+	}
+	if amet != "simple-aaa" {
+		ah.authLogFunc(false, "auth method not support")
+		return false, "", "auth method not support"
+	}
+	username, ok := authData["username"]
+	if !ok {
+		ah.authLogFunc(false, "no username specified")
+		return false, "", "no username specified"
+	}
+	password, ok := authData["password"]
+	if !ok {
+		ah.authLogFunc(false, "no password specified")
+		return false, "", "no password specified"
+	}
+	if username == "" {
+		ah.authLogFunc(false, "username can not be null")
+		return false, "", "username can not be null"
+	}
+	ubc, ok := ah.userDatabase[username]
+	if !ok {
+		ah.authLogFunc(false, fmt.Sprintf("user '%s' not exist", username))
+		return false, "", "username or password not match"
+	}
+	err := bcrypt.CompareHashAndPassword([]byte(ubc), []byte(password))
+	if err != nil {
+		ah.authLogFunc(false, fmt.Sprintf("user '%s' auth failed with error: %s", username, err.Error()))
+		return false, "", "username or password not match"
+	}
+	clm := jwt.Claims{}
+	clm.Issuer = "simple-aaa"
+	clm.Subject = "telws-auth"
+	ntm := jwt.NewNumericTime(time.Now())
+	clm.Issued = ntm
+	clm.NotBefore = ntm
+	clm.Expires = jwt.NewNumericTime(time.Now().Add(ah.jwtttl))
+	clm.Set = map[string]interface{}{
+		"username": username,
+	}
+	tok, err := clm.HMACSign(jwt.HS256, ah.jwtKey)
+	if err != nil {
+		ah.authLogFunc(false, fmt.Sprintf("user '%s' auth success but generate jwt error: %s", username, err.Error()))
+		return false, "", "internal error"
+	}
+	ah.authLogFunc(false, fmt.Sprintf("user '%s' auth success", username))
+	return true, string(tok), ""
+}

+ 206 - 0
hhc_telws/telws_handler.go

@@ -0,0 +1,206 @@
+package hhc_telws
+
+import (
+	"crypto/rand"
+	"encoding/hex"
+	"encoding/json"
+	"fmt"
+	"git.swzry.com/zry/gorillaws2netconn"
+	"github.com/gin-gonic/gin"
+	"github.com/gorilla/websocket"
+	"github.com/pascaldekloe/jwt"
+	"github.com/tjfoc/gmsm/sm2"
+	"io"
+	"net/http"
+	"time"
+)
+
+const JWT_KEY_LENGTH = 256
+
+type CliBackendHandler struct {
+	jwtKey             []byte
+	errLog             func(err error)
+	authHandlers       map[string]TelwsAuthHandler
+	authMethodList     []string
+	gingrp             *gin.RouterGroup
+	jwtttl             time.Duration
+	sm2key             *sm2.PrivateKey
+	sm2pubkey          string
+	upgrader           *websocket.Upgrader
+	sessHandlerFactory TelwsSessionHandlerFactory
+}
+
+func NewCliBackendHandler(gingrp *gin.RouterGroup, tsfactory TelwsSessionHandlerFactory) *CliBackendHandler {
+	cbh := &CliBackendHandler{
+		gingrp:             gingrp,
+		authMethodList:     make([]string, 0),
+		authHandlers:       make(map[string]TelwsAuthHandler),
+		sessHandlerFactory: tsfactory,
+	}
+	cbh.upgrader = &websocket.Upgrader{
+		CheckOrigin: func(r *http.Request) bool {
+			return true
+		},
+	}
+	cbh.gingrp.GET("/", cbh.handleGetTelwsInfo)
+	cbh.gingrp.POST("/login.satori", cbh.handleLogin)
+	cbh.gingrp.GET("/ws.satori", cbh.handleWebSocket)
+	return cbh
+}
+
+func (cbh *CliBackendHandler) InitSecure(jwtttl time.Duration) error {
+	cbh.jwtttl = jwtttl
+	jbuf := make([]byte, JWT_KEY_LENGTH)
+	_, err := io.ReadFull(rand.Reader, jbuf)
+	if err != nil {
+		return err
+	}
+	cbh.jwtKey = jbuf
+	pk, err := sm2.GenerateKey()
+	if err != nil {
+		return err
+	}
+	cbh.sm2key = pk
+	xenc := hex.EncodeToString(pk.X.Bytes())
+	yenc := hex.EncodeToString(pk.Y.Bytes())
+	cbh.sm2pubkey = SM2PubKeyCombine(xenc, yenc)
+	return nil
+}
+
+func (cbh *CliBackendHandler) AddAuthHandler(ah TelwsAuthHandler) {
+	ahname := ah.Register(cbh.jwtKey, cbh.jwtttl)
+	cbh.authHandlers[ahname] = ah
+	cbh.authMethodList = append(cbh.authMethodList, ahname)
+}
+
+func (cbh *CliBackendHandler) SetErrorLogFunction(fe func(err error)) {
+	cbh.errLog = fe
+}
+
+func (cbh *CliBackendHandler) handleGetTelwsInfo(ctx *gin.Context) {
+	ctx.JSON(200, gin.H{
+		"protocol":     "telws",
+		"protocol_ver": "1.0",
+		"auth_methods": cbh.authMethodList,
+		"encrypt": map[string]string{
+			"method":     "sm2",
+			"pubkey":     cbh.sm2pubkey,
+			"cipherMode": "C1C3C2",
+			"header":     "04",
+		},
+	})
+}
+
+type postJson_Login struct {
+	AuthData string `json:"authData"`
+}
+
+func (cbh *CliBackendHandler) handleLogin(ctx *gin.Context) {
+	var pjl postJson_Login
+	err := ctx.BindJSON(&pjl)
+	if err != nil {
+		ctx.JSON(200, gin.H{
+			"suc":   false,
+			"etype": "input_json_error",
+		})
+		return
+	}
+	data := pjl.AuthData
+	if data == "" {
+		ctx.JSON(200, gin.H{
+			"suc":   false,
+			"etype": "no_cipher_text",
+		})
+		return
+	}
+	hdec, err := hex.DecodeString(data)
+	if err != nil {
+		ctx.JSON(200, gin.H{
+			"suc":   false,
+			"etype": "hex_decode",
+		})
+		return
+	}
+	pt, err := sm2.Decrypt(cbh.sm2key, hdec)
+	if err != nil {
+		ctx.JSON(200, gin.H{
+			"suc":   false,
+			"etype": "sm2_decrypt",
+		})
+		return
+	}
+	var jdata map[string]string
+	err = json.Unmarshal(pt, &jdata)
+	if err != nil {
+		ctx.JSON(200, gin.H{
+			"suc":   false,
+			"etype": "decrypted_json_error",
+		})
+		return
+	}
+	met, ok := jdata["method"]
+	if !ok {
+		ctx.JSON(200, gin.H{
+			"suc":   false,
+			"etype": "no_auth_method_specified",
+		})
+		return
+	}
+	ah, ok := cbh.authHandlers[met]
+	if !ok {
+		ctx.JSON(200, gin.H{
+			"suc":   false,
+			"etype": "unsupported_auth_method",
+		})
+		return
+	}
+	suc, jwtstr, emsg := ah.Login(jdata)
+	if !suc {
+		ctx.JSON(200, gin.H{
+			"suc":            false,
+			"etype":          "auth_failed",
+			"auth_error_msg": emsg,
+		})
+		return
+	}
+	ctx.JSON(200, gin.H{
+		"suc": true,
+		"jwt": jwtstr,
+	})
+}
+
+func (cbh *CliBackendHandler) handleWebSocket(ctx *gin.Context) {
+	jwtstr := ctx.Request.FormValue("token")
+	ws, err := cbh.upgrader.Upgrade(ctx.Writer, ctx.Request, nil)
+	if err != nil {
+		fmt.Println("WS Error:", err.Error())
+		return
+	}
+	defer ws.Close()
+	nconn := &gorillaws2netconn.NetConn4Gorilla{WS: ws, WriteMessageType: websocket.BinaryMessage}
+	vclm, err := jwt.HMACCheck([]byte(jwtstr), cbh.jwtKey)
+	if err != nil {
+		if cbh.errLog != nil {
+			cbh.errLog(err)
+		}
+		_, _ = fmt.Fprintf(nconn, "Websocket auth failed: %s\r\n", err.Error())
+		ws.Close()
+		return
+	}
+	tscc := WrapNewTelwsSessionContext()
+	tscc.SetJwtClaims(vclm)
+	tscc.SetConn(nconn)
+	sh := cbh.sessHandlerFactory.GetSessionHandler()
+	err = sh.HandleConnection(tscc.GetUserContext())
+	if err != nil {
+		if cbh.errLog != nil {
+			cbh.errLog(err)
+		}
+		_, _ = fmt.Fprintf(nconn, "Websocket connection end by error: %s\r\n", err.Error())
+		ws.Close()
+		return
+	} else {
+		_, _ = fmt.Fprintf(nconn, "Websocket connection closed.\r\n")
+		ws.Close()
+	}
+}

+ 16 - 0
hhc_telws/utils.go

@@ -0,0 +1,16 @@
+package hhc_telws
+
+import "strings"
+
+func SM2PubKeyCombine(x, y string) string {
+	sb := strings.Builder{}
+	lx, ly := len(x), len(y)
+	lpx, lpy := 64-lx, 64-ly
+	spx := strings.Repeat("0", lpx)
+	spy := strings.Repeat("0", lpy)
+	sb.WriteString(spx)
+	sb.WriteString(x)
+	sb.WriteString(spy)
+	sb.WriteString(y)
+	return sb.String()
+}

+ 19 - 0
hhc_validators/ranged_int.go

@@ -0,0 +1,19 @@
+package hhc_validators
+
+import "strconv"
+
+type RangedInteger struct {
+	Min int
+	Max int
+}
+
+func (v RangedInteger) Validate(t string) bool {
+	i, e := strconv.Atoi(t)
+	if e != nil {
+		return false
+	}
+	if i < v.Min || i > v.Max {
+		return false
+	}
+	return true
+}

+ 282 - 0
hhccli/simple_tces_parser.go

@@ -0,0 +1,282 @@
+package hhccli
+
+type simpleTCESParserStates uint8
+
+const (
+	stces_state_initial simpleTCESParserStates = 0
+	stces_state_seq91   simpleTCESParserStates = 1
+	stces_state_seq79   simpleTCESParserStates = 2
+	stces_state_s91n1   simpleTCESParserStates = 3
+	stces_state_s91n2   simpleTCESParserStates = 4
+	stces_state_s91n3   simpleTCESParserStates = 5
+	stces_state_s91n5   simpleTCESParserStates = 7
+	stces_state_s91n6   simpleTCESParserStates = 8
+	stces_state_s91n15  simpleTCESParserStates = 9
+	stces_state_s91n17  simpleTCESParserStates = 10
+	stces_state_s91n18  simpleTCESParserStates = 11
+	stces_state_s91n19  simpleTCESParserStates = 12
+	stces_state_s91n20  simpleTCESParserStates = 13
+	stces_state_s91n21  simpleTCESParserStates = 14
+	stces_state_s91n23  simpleTCESParserStates = 15
+	stces_state_s91n24  simpleTCESParserStates = 16
+)
+
+type SimpleTCESParserResult uint8
+
+const (
+	SimpleTCESParserResult_Unsupported SimpleTCESParserResult = 0
+	SimpleTCESParserResult_UpArrow     SimpleTCESParserResult = 1
+	SimpleTCESParserResult_DownArrow   SimpleTCESParserResult = 2
+	SimpleTCESParserResult_LeftArrow   SimpleTCESParserResult = 3
+	SimpleTCESParserResult_RightArrow  SimpleTCESParserResult = 4
+	SimpleTCESParserResult_HomeKey     SimpleTCESParserResult = 5
+	SimpleTCESParserResult_EndKey      SimpleTCESParserResult = 6
+	SimpleTCESParserResult_InsKey      SimpleTCESParserResult = 7
+	SimpleTCESParserResult_DelKey      SimpleTCESParserResult = 8
+	SimpleTCESParserResult_PageUpKey   SimpleTCESParserResult = 9
+	SimpleTCESParserResult_PageDnKey   SimpleTCESParserResult = 10
+	SimpleTCESParserResult_F1Key       SimpleTCESParserResult = 11
+	SimpleTCESParserResult_F2Key       SimpleTCESParserResult = 12
+	SimpleTCESParserResult_F3Key       SimpleTCESParserResult = 13
+	SimpleTCESParserResult_F4Key       SimpleTCESParserResult = 14
+	SimpleTCESParserResult_F5Key       SimpleTCESParserResult = 15
+	SimpleTCESParserResult_F6Key       SimpleTCESParserResult = 16
+	SimpleTCESParserResult_F7Key       SimpleTCESParserResult = 17
+	SimpleTCESParserResult_F8Key       SimpleTCESParserResult = 18
+	SimpleTCESParserResult_F9Key       SimpleTCESParserResult = 19
+	SimpleTCESParserResult_F10Key      SimpleTCESParserResult = 20
+	SimpleTCESParserResult_F11Key      SimpleTCESParserResult = 21
+	SimpleTCESParserResult_F12Key      SimpleTCESParserResult = 22
+)
+
+func (r SimpleTCESParserResult) Name() string {
+	switch r {
+	case SimpleTCESParserResult_Unsupported:
+		return "Unsupported"
+	case SimpleTCESParserResult_UpArrow:
+		return "UpArrow"
+	case SimpleTCESParserResult_DownArrow:
+		return "DownArrow"
+	case SimpleTCESParserResult_LeftArrow:
+		return "LeftArrow"
+	case SimpleTCESParserResult_RightArrow:
+		return "RightArrow"
+	case SimpleTCESParserResult_HomeKey:
+		return "Home"
+	case SimpleTCESParserResult_EndKey:
+		return "End"
+	case SimpleTCESParserResult_InsKey:
+		return "Insert"
+	case SimpleTCESParserResult_DelKey:
+		return "Delete"
+	case SimpleTCESParserResult_PageUpKey:
+		return "PageUp"
+	case SimpleTCESParserResult_PageDnKey:
+		return "PageDown"
+	case SimpleTCESParserResult_F1Key:
+		return "F1"
+	case SimpleTCESParserResult_F2Key:
+		return "F2"
+	case SimpleTCESParserResult_F3Key:
+		return "F3"
+	case SimpleTCESParserResult_F4Key:
+		return "F4"
+	case SimpleTCESParserResult_F5Key:
+		return "F5"
+	case SimpleTCESParserResult_F6Key:
+		return "F6"
+	case SimpleTCESParserResult_F7Key:
+		return "F7"
+	case SimpleTCESParserResult_F8Key:
+		return "F8"
+	case SimpleTCESParserResult_F9Key:
+		return "F9"
+	case SimpleTCESParserResult_F10Key:
+		return "F10"
+	case SimpleTCESParserResult_F11Key:
+		return "F11"
+	case SimpleTCESParserResult_F12Key:
+		return "F12"
+	default:
+		return "Unknown"
+	}
+}
+
+type SimpleTCESParser struct {
+	state simpleTCESParserStates
+}
+
+func NewSimpleTCESParser() *SimpleTCESParser {
+	p := &SimpleTCESParser{}
+	p.ResetState()
+	return p
+}
+
+func (p *SimpleTCESParser) ResetState() {
+	p.state = stces_state_initial
+}
+
+func (p *SimpleTCESParser) PushRune(r rune) (bool, SimpleTCESParserResult) {
+	switch p.state {
+	case stces_state_initial:
+		switch r {
+		case '[':
+			p.state = stces_state_seq91
+			return false, 0
+		case 'O':
+			p.state = stces_state_seq79
+			return false, 0
+		default:
+			return true, SimpleTCESParserResult_Unsupported
+		}
+	case stces_state_seq79:
+		switch r {
+		case 'P':
+			return true, SimpleTCESParserResult_F1Key
+		case 'Q':
+			return true, SimpleTCESParserResult_F2Key
+		case 'R':
+			return true, SimpleTCESParserResult_F3Key
+		case 'S':
+			return true, SimpleTCESParserResult_F4Key
+		default:
+			return true, SimpleTCESParserResult_Unsupported
+		}
+	case stces_state_seq91:
+		switch r {
+		case 'A':
+			return true, SimpleTCESParserResult_UpArrow
+		case 'B':
+			return true, SimpleTCESParserResult_DownArrow
+		case 'D':
+			return true, SimpleTCESParserResult_LeftArrow
+		case 'C':
+			return true, SimpleTCESParserResult_RightArrow
+		case 'H':
+			return true, SimpleTCESParserResult_HomeKey
+		case 'F':
+			return true, SimpleTCESParserResult_EndKey
+		case '1':
+			p.state = stces_state_s91n1
+			return false, 0
+		case '2':
+			p.state = stces_state_s91n2
+			return false, 0
+		case '3':
+			p.state = stces_state_s91n3
+			return false, 0
+		case '5':
+			p.state = stces_state_s91n5
+			return false, 0
+		case '6':
+			p.state = stces_state_s91n6
+			return false, 0
+		default:
+			return true, SimpleTCESParserResult_Unsupported
+		}
+	case stces_state_s91n1:
+		switch r {
+		case '5':
+			p.state = stces_state_s91n15
+			return false, 0
+		case '7':
+			p.state = stces_state_s91n17
+			return false, 0
+		case '8':
+			p.state = stces_state_s91n18
+			return false, 0
+		case '9':
+			p.state = stces_state_s91n19
+			return false, 0
+		default:
+			return true, SimpleTCESParserResult_Unsupported
+		}
+	case stces_state_s91n2:
+		switch r {
+		case '~':
+			return true, SimpleTCESParserResult_InsKey
+		case '0':
+			p.state = stces_state_s91n20
+			return false, 0
+		case '1':
+			p.state = stces_state_s91n21
+			return false, 0
+		case '3':
+			p.state = stces_state_s91n23
+			return false, 0
+		case '4':
+			p.state = stces_state_s91n24
+			return false, 0
+		default:
+			return true, SimpleTCESParserResult_Unsupported
+		}
+	case stces_state_s91n3:
+		if r == '~' {
+			return true, SimpleTCESParserResult_DelKey
+		} else {
+			return true, SimpleTCESParserResult_Unsupported
+		}
+	case stces_state_s91n5:
+		if r == '~' {
+			return true, SimpleTCESParserResult_PageUpKey
+		} else {
+			return true, SimpleTCESParserResult_Unsupported
+		}
+	case stces_state_s91n6:
+		if r == '~' {
+			return true, SimpleTCESParserResult_PageDnKey
+		} else {
+			return true, SimpleTCESParserResult_Unsupported
+		}
+	case stces_state_s91n15:
+		if r == '~' {
+			return true, SimpleTCESParserResult_F5Key
+		} else {
+			return true, SimpleTCESParserResult_Unsupported
+		}
+	case stces_state_s91n17:
+		if r == '~' {
+			return true, SimpleTCESParserResult_F6Key
+		} else {
+			return true, SimpleTCESParserResult_Unsupported
+		}
+	case stces_state_s91n18:
+		if r == '~' {
+			return true, SimpleTCESParserResult_F7Key
+		} else {
+			return true, SimpleTCESParserResult_Unsupported
+		}
+	case stces_state_s91n19:
+		if r == '~' {
+			return true, SimpleTCESParserResult_F8Key
+		} else {
+			return true, SimpleTCESParserResult_Unsupported
+		}
+	case stces_state_s91n20:
+		if r == '~' {
+			return true, SimpleTCESParserResult_F9Key
+		} else {
+			return true, SimpleTCESParserResult_Unsupported
+		}
+	case stces_state_s91n21:
+		if r == '~' {
+			return true, SimpleTCESParserResult_F10Key
+		} else {
+			return true, SimpleTCESParserResult_Unsupported
+		}
+	case stces_state_s91n23:
+		if r == '~' {
+			return true, SimpleTCESParserResult_F11Key
+		} else {
+			return true, SimpleTCESParserResult_Unsupported
+		}
+	case stces_state_s91n24:
+		if r == '~' {
+			return true, SimpleTCESParserResult_F12Key
+		} else {
+			return true, SimpleTCESParserResult_Unsupported
+		}
+	default:
+		return true, SimpleTCESParserResult_Unsupported
+	}
+}

+ 549 - 0
hhccli/terminal_handler.go

@@ -0,0 +1,549 @@
+package hhccli
+
+import (
+	"bufio"
+	"context"
+	"fmt"
+	"git.swzry.com/zry/go-hhc-cli/hhc_ast"
+	"git.swzry.com/zry/go-hhc-cli/hhc_common"
+	"git.swzry.com/zry/ztermtab"
+	"io"
+	"runtime"
+	"unicode"
+	"unicode/utf8"
+)
+
+const (
+	TermInteractiveLogLevel_Fatal TerminalInteractiveDebugLogLevel = 0
+	TermInteractiveLogLevel_Error TerminalInteractiveDebugLogLevel = 1
+	TermInteractiveLogLevel_Warn  TerminalInteractiveDebugLogLevel = 2
+	TermInteractiveLogLevel_Info  TerminalInteractiveDebugLogLevel = 3
+	TermInteractiveLogLevel_Debug TerminalInteractiveDebugLogLevel = 4
+)
+
+func (lv TerminalInteractiveDebugLogLevel) Name() string {
+	switch lv {
+	case TermInteractiveLogLevel_Fatal:
+		return "FATAL"
+	case TermInteractiveLogLevel_Error:
+		return "ERROR"
+	case TermInteractiveLogLevel_Warn:
+		return "WARN"
+	case TermInteractiveLogLevel_Info:
+		return "INFO"
+	case TermInteractiveLogLevel_Debug:
+		return "DEBUG"
+	default:
+		return "???"
+	}
+}
+
+type TerminalInteractiveDebugLogLevel uint8
+
+type TerminalInteractiveDebugLog func(lv TerminalInteractiveDebugLogLevel, msg ...interface{})
+
+type TerminalInteractive struct {
+	inStream             io.Reader
+	outStream            io.Writer
+	errStream            io.Writer
+	debugWrite           TerminalInteractiveDebugLog
+	termWidth            int
+	inBufReader          *bufio.Reader
+	errCh                chan error
+	runeCh               chan rune
+	printResultRuneCh    chan rune
+	currentPrompt        string
+	views                map[string]CliView
+	currentView          CliView
+	termState            hhc_common.TerminalState
+	parser               *hhc_ast.SDTLineParser
+	runesEditor          *hhc_common.RunesEditor
+	backspaceRune        rune
+	tcesParser           *SimpleTCESParser
+	printModePrintLines  []string
+	printModeLineCount   int
+	printModeCurrentLine int
+	printModeIsHelp      bool
+	execModeContext      *hhc_ast.ExecContext
+}
+
+type TerminalHandlerInterface interface {
+	Reset(iow io.Writer, promptStr string, sdtp *hhc_ast.SDTLineParser)
+	ProcessRune(r rune) (end bool, reprint bool)
+	GetCmdLinePrintable(termWidth int) []string
+	GetCalculatedCleanLineCount() int
+}
+
+type TerminalInteractiveConfig struct {
+	InputStream          io.Reader
+	OutputStream         io.Writer
+	ErrorStream          io.Writer
+	InitialTerminalWidth int
+	DebugLogFunction     TerminalInteractiveDebugLog
+	InitialPrompt        string
+	BackspaceRune        rune
+}
+
+func nullDebugLogFunc(lv TerminalInteractiveDebugLogLevel, msg ...interface{}) {
+}
+
+func NewTerminalInteractive(cfg TerminalInteractiveConfig) *TerminalInteractive {
+	ti := &TerminalInteractive{
+		inStream:      cfg.InputStream,
+		outStream:     cfg.OutputStream,
+		errStream:     cfg.ErrorStream,
+		termWidth:     cfg.InitialTerminalWidth,
+		inBufReader:   bufio.NewReader(cfg.InputStream),
+		currentPrompt: cfg.InitialPrompt,
+		views:         map[string]CliView{},
+		termState:     hhc_common.TerminalState_Idle,
+		backspaceRune: cfg.BackspaceRune,
+		tcesParser:    NewSimpleTCESParser(),
+	}
+	if cfg.DebugLogFunction == nil {
+		ti.debugWrite = nullDebugLogFunc
+	} else {
+		ti.debugWrite = cfg.DebugLogFunction
+	}
+	if ti.currentPrompt == "" {
+		ti.currentPrompt = ">"
+	}
+	if ti.backspaceRune == 0 {
+		ti.backspaceRune = '\x7F'
+	}
+	return ti
+}
+
+func (ti *TerminalInteractive) RegisterView(viewClassName string, view CliView) {
+	ti.views[viewClassName] = view
+}
+
+func (ti *TerminalInteractive) SetCurrentView(viewClassName string) error {
+	v, ok := ti.views[viewClassName]
+	if ok {
+		ti.currentView = v
+	} else {
+		ti.currentView = nil
+		return fmt.Errorf("invalid vcn")
+	}
+	return nil
+}
+
+func (ti *TerminalInteractive) readRunesRoutine() {
+	for {
+		rr, _, err := ti.inBufReader.ReadRune()
+		if err != nil {
+			ti.errCh <- err
+			return
+		}
+		ti.runeCh <- rr
+	}
+}
+
+func (ti *TerminalInteractive) SetPrompt(p string) {
+	ti.currentPrompt = p
+}
+
+func (ti *TerminalInteractive) Run(ctx context.Context) error {
+	ti.errCh = make(chan error)
+	ti.runeCh = make(chan rune)
+	go ti.readRunesRoutine()
+	err := ti.gotoStateInput(false)
+	if err != nil {
+		return err
+	}
+	for {
+		select {
+		case <-ctx.Done():
+			ti.debugWrite(TermInteractiveLogLevel_Info, "canceled by user")
+			return nil
+		case err := <-ti.errCh:
+			ti.debugWrite(TermInteractiveLogLevel_Error, err.Error())
+			return err
+		case rr := <-ti.runeCh:
+			err := ti.stateMachinePushRune(rr)
+			if err != nil {
+				return err
+			}
+		}
+	}
+}
+
+func (ti *TerminalInteractive) stateMachinePushRune(r rune) error {
+	for {
+		switch ti.termState {
+		case hhc_common.TerminalState_Idle:
+			return ti.gotoStateInput(false)
+		case hhc_common.TerminalState_Input:
+			b, err := ti.stateInputPushRune(r)
+			if b || err != nil {
+				return err
+			}
+		case hhc_common.TerminalState_TCES:
+			b, err := ti.stateTCESPushRune(r)
+			if b || err != nil {
+				return err
+			}
+		case hhc_common.TerminalState_Execute:
+			if ti.execModeContext != nil {
+				ti.execModeContext.WriteRune(r)
+			}
+			return nil
+		case hhc_common.TerminalState_PrintResult:
+			b, err := ti.statePrintResultPushRune(r)
+			if b || err != nil {
+				return err
+			}
+		default:
+			return fmt.Errorf("invalid state for terminal handler state machine: %d", ti.termState)
+		}
+	}
+}
+
+func (ti *TerminalInteractive) gotoStateInput(saveHistory bool) error {
+	if ti.currentView == nil {
+		return fmt.Errorf("no current view specified")
+	}
+	sdtf := ti.currentView.GetSDTRoot()
+	sdtw := hhc_ast.NewSDTWalker(sdtf)
+	ti.parser = hhc_ast.NewSDTLineParser(sdtw)
+	if ti.runesEditor == nil {
+		ti.runesEditor = hhc_common.NewRunesEditor()
+	} else {
+		ti.runesEditor.ClearCurrentLine(saveHistory)
+	}
+	_, _ = fmt.Fprint(ti.outStream, "\r\n")
+	ti.runesEditor.PeekIntoTerminal(ti.currentPrompt, ti.termWidth, ti.outStream)
+	ti.termState = hhc_common.TerminalState_Input
+	return nil
+}
+
+func (ti *TerminalInteractive) gotoStateTCES() error {
+	ti.termState = hhc_common.TerminalState_TCES
+	ti.tcesParser.ResetState()
+	return nil
+}
+
+func (ti *TerminalInteractive) gotoStatePrintResult(s []string, isHelp bool) {
+	ti.printModeIsHelp = isHelp
+	ti.printModeLineCount = len(s)
+	ti.printModeCurrentLine = 0
+	ti.printModePrintLines = s
+	ti.termState = hhc_common.TerminalState_PrintResult
+	_, _ = fmt.Fprint(ti.errStream, "---- More ----")
+	ti.printLinesInPrintResultState(20)
+}
+
+func (ti *TerminalInteractive) gotoStateExecute(ef hhc_ast.ExecuteFunc, ctx *hhc_ast.SDTWalkContext) {
+	ti.termState = hhc_common.TerminalState_Execute
+	ti.execModeContext = hhc_ast.WrapNewExecContext(ctx, ti.outStream, ti.errStream, ti.termWidth)
+	go ti.executeRoutine(ef)
+}
+
+func (ti *TerminalInteractive) exitStateExecute() {
+	if ti.execModeContext != nil {
+		nv := ti.execModeContext.GetNextView()
+		if nv != "" {
+			err := ti.SetCurrentView(nv)
+			if err != nil {
+				_, _ = fmt.Fprintf(ti.errStream, "\r\n %% command line system error in change view: %s\r\n", err.Error())
+			}
+		}
+		wcp, np := ti.execModeContext.GetNextPrompt()
+		if wcp {
+			ti.SetPrompt(np)
+		}
+		if ti.execModeContext.WillGotoPrintResultMode() {
+			res := ti.execModeContext.GetResultPrintLines()
+			_, _ = fmt.Fprintf(ti.errStream, "\r\n")
+			ti.gotoStatePrintResult(res, false)
+		} else {
+			err := ti.gotoStateInput(false)
+			if err != nil {
+				_, _ = fmt.Fprintf(ti.errStream, "\r\n %% command line system error: %s\r\n", err.Error())
+			}
+		}
+		ti.execModeContext = nil
+	} else {
+		_, _ = fmt.Fprintf(ti.errStream, "\r\n %% broken command (this is a bug... or may be a feature?)\r\n")
+		err := ti.gotoStateInput(false)
+		if err != nil {
+			_, _ = fmt.Fprintf(ti.errStream, "\r\n %% command line system error: %s\r\n", err.Error())
+		}
+	}
+}
+
+func (ti *TerminalInteractive) executeRoutine(ef hhc_ast.ExecuteFunc) {
+	if ti.execModeContext != nil {
+		if ef != nil {
+			err := ti.protectedExecute(ef, ti.execModeContext)
+			if err != nil {
+				_, _ = fmt.Fprintf(ti.errStream, "\r\n %% broken command (this is a bug... or may be a feature?)\r\n")
+				_, _ = fmt.Fprintf(ti.errStream, "command line execute error: %s\r\n", err.Error())
+			}
+			ti.exitStateExecute()
+		} else {
+			ti.exitStateExecute()
+		}
+	} else {
+		ti.exitStateExecute()
+	}
+}
+
+func (ti *TerminalInteractive) protectedExecute(ef hhc_ast.ExecuteFunc, ctx *hhc_ast.ExecContext) error {
+	var err error
+	defer func() {
+		rec := recover()
+		switch rec.(type) {
+		case runtime.Error:
+			err = rec.(runtime.Error)
+			break
+		case error:
+			err = rec.(error)
+			break
+		}
+	}()
+	ef(ctx.GetUserContext())
+	return err
+}
+
+func (ti *TerminalInteractive) printLinesInPrintResultState(nline int) {
+	if ti.printModePrintLines == nil {
+		if ti.printModeIsHelp {
+			ti.termState = hhc_common.TerminalState_Input
+			ti.runesEditor.PeekIntoTerminal(ti.currentPrompt, ti.termWidth, ti.outStream)
+			return
+		} else {
+			_ = ti.gotoStateInput(true)
+			return
+		}
+	}
+	isend := false
+	_, _ = fmt.Fprint(ti.errStream, "\r\033[K")
+	for i := 0; i < nline; i++ {
+		if ti.printModeCurrentLine < ti.printModeLineCount {
+			_, _ = fmt.Fprintf(ti.errStream, " %s\r\n", ti.printModePrintLines[ti.printModeCurrentLine])
+			ti.printModeCurrentLine++
+		}
+		if ti.printModeCurrentLine == ti.printModeLineCount {
+			isend = true
+			break
+		}
+	}
+	if isend {
+		_, _ = fmt.Fprint(ti.errStream, "\r\n")
+		ti.printModePrintLines = nil
+		if ti.printModeIsHelp {
+			ti.termState = hhc_common.TerminalState_Input
+			ti.runesEditor.PeekIntoTerminal(ti.currentPrompt, ti.termWidth, ti.outStream)
+			return
+		} else {
+			_ = ti.gotoStateInput(true)
+			return
+		}
+	} else {
+		_, _ = fmt.Fprint(ti.errStream, "---- More ----")
+	}
+}
+
+func (ti *TerminalInteractive) stateInputPushRune(r rune) (bool, error) {
+	if utf8.ValidRune(r) {
+		if unicode.IsControl(r) {
+			return ti.stateInputPushControlRune(r)
+		}
+		if unicode.IsGraphic(r) {
+			return ti.stateInputPushGraphicRune(r)
+		}
+	}
+	return true, nil
+}
+
+func (ti *TerminalInteractive) stateInputPushControlRune(r rune) (bool, error) {
+	switch r {
+	case '\x03':
+		err := ti.gotoStateInput(false)
+		if err != nil {
+			return true, err
+		}
+		return true, nil
+	case ti.backspaceRune:
+		if ti.runesEditor != nil {
+			ti.runesEditor.Backspace()
+			ti.runesEditor.RefreshTerminal(ti.outStream, ti.currentPrompt, ti.termWidth, false)
+		}
+		return true, nil
+	case '\x1b':
+		err := ti.gotoStateTCES()
+		if err != nil {
+			return true, err
+		}
+		return true, nil
+	case '\x09':
+		ti.parser.Reset()
+		rs := ti.runesEditor.Peek()
+		ti.parser.Parse(rs)
+		if ti.parser.HasError() {
+			return true, nil
+		}
+		hi, tok := ti.parser.GetHelpListForTabComplete()
+		if len(hi) == 1 {
+			if hi[0].IsArg {
+				return true, nil
+			}
+			suf := hhc_common.GetCompleteSuffix(hi[0].Name, tok)
+			ti.runesEditor.InsertString(suf)
+			ti.runesEditor.RefreshTerminal(ti.outStream, ti.currentPrompt, ti.termWidth, false)
+		} else {
+			ttp := ztermtab.NewTerminalTablePrinter(ti.termWidth, "  ", " | ", "")
+			ttp.AddColumn("", 10, 30, false)
+			ttp.AddColumn("", 0, 0, false)
+			ttp.SetAutoWidthColumn(1)
+			for _, v := range hi {
+				ttp.AddRow([]string{v.Name, v.Description})
+			}
+			_, _ = fmt.Fprint(ti.errStream, "\r\n")
+			ti.gotoStatePrintResult(ttp.RenderToTerminalLines(), true)
+		}
+		return true, nil
+	case '\r':
+		if len(ti.runesEditor.Peek()) <= 0 {
+			_, _ = fmt.Fprintf(ti.errStream, "\r\n")
+			ti.runesEditor.PeekIntoTerminal(ti.currentPrompt, ti.termWidth, ti.outStream)
+			return true, nil
+		}
+		ti.parser.Reset()
+		rs := ti.runesEditor.Enter()
+		ti.parser.Parse(rs)
+		ok, ef := ti.parser.TryGetRunFunc()
+		if ti.parser.HasError() {
+			e, p := ti.parser.GetError()
+			_, _ = fmt.Fprintf(ti.errStream, "\r\n %% %s at position %d\r\n", hhc_common.FirstToUpper(e.EES()), p)
+			ti.termState = hhc_common.TerminalState_Input
+			ti.runesEditor.PeekIntoTerminal(ti.currentPrompt, ti.termWidth, ti.outStream)
+			return true, nil
+		}
+		if ok {
+			if ef == nil {
+				_, _ = fmt.Fprintf(ti.errStream, "\r\n %% broken command (this is a bug... or may be a feature?)\r\n")
+				ti.termState = hhc_common.TerminalState_Input
+				ti.runesEditor.PeekIntoTerminal(ti.currentPrompt, ti.termWidth, ti.outStream)
+				return true, nil
+			} else {
+				ti.gotoStateExecute(ef, ti.parser.GetWalkContext())
+				return true, nil
+			}
+
+		} else {
+			_, _ = fmt.Fprintf(ti.errStream, "\r\n %% incomplete command\r\n")
+			ti.termState = hhc_common.TerminalState_Input
+			ti.runesEditor.PeekIntoTerminal(ti.currentPrompt, ti.termWidth, ti.outStream)
+			return true, nil
+		}
+	}
+	return true, nil
+}
+
+func (ti *TerminalInteractive) stateInputPushGraphicRune(r rune) (bool, error) {
+	if ti.runesEditor != nil {
+		ti.runesEditor.InsertRune(r)
+		ti.runesEditor.RefreshTerminal(ti.outStream, ti.currentPrompt, ti.termWidth, false)
+	}
+	if r == '?' {
+		ti.parser.Reset()
+		rs := ti.runesEditor.Peek()
+		ti.parser.Parse(rs)
+		if !ti.parser.HelpTriggerd() {
+			return true, nil
+		}
+		_, _ = fmt.Fprint(ti.errStream, "\r\n")
+		if ti.parser.HasError() {
+			e, p := ti.parser.GetError()
+			_, _ = fmt.Fprintf(ti.errStream, " %% %s at position %d\r\n", hhc_common.FirstToUpper(e.EES()), p)
+			ti.runesEditor.Backspace()
+			ti.termState = hhc_common.TerminalState_Input
+			ti.runesEditor.PeekIntoTerminal(ti.currentPrompt, ti.termWidth, ti.outStream)
+			return true, nil
+		}
+		if ti.parser.WillPrintHelp() {
+			ti.runesEditor.Backspace()
+			hi := ti.parser.GetHelpMessage()
+			ttp := ztermtab.NewTerminalTablePrinter(ti.termWidth, "  ", " | ", "")
+			ttp.AddColumn("", 10, 30, false)
+			ttp.AddColumn("", 0, 0, false)
+			ttp.SetAutoWidthColumn(1)
+			for _, v := range hi {
+				ttp.AddRow([]string{v.Name, v.Description})
+			}
+			ti.gotoStatePrintResult(ttp.RenderToTerminalLines(), true)
+			return true, nil
+		}
+	}
+	return true, nil
+}
+
+func (ti *TerminalInteractive) stateTCESPushRune(r rune) (bool, error) {
+	b, res := ti.tcesParser.PushRune(r)
+	if b {
+		ti.termState = hhc_common.TerminalState_Input
+		if ti.runesEditor == nil {
+			return true, nil
+		}
+		switch res {
+		case SimpleTCESParserResult_LeftArrow:
+			ti.runesEditor.MoveInsertPosition(-1)
+			break
+		case SimpleTCESParserResult_RightArrow:
+			ti.runesEditor.MoveInsertPosition(1)
+			break
+		case SimpleTCESParserResult_UpArrow:
+			ti.runesEditor.LoadPreviousHistory()
+			break
+		case SimpleTCESParserResult_DownArrow:
+			ti.runesEditor.LoadNextHistory()
+			break
+		case SimpleTCESParserResult_HomeKey:
+			ti.runesEditor.MoveInsertPositionToHome()
+			break
+		case SimpleTCESParserResult_EndKey:
+			ti.runesEditor.MoveInsertPositionToEnd()
+			break
+		case SimpleTCESParserResult_DelKey:
+			ti.runesEditor.Delete()
+			break
+		default:
+			break
+		}
+		ti.runesEditor.RefreshTerminal(ti.outStream, ti.currentPrompt, ti.termWidth, false)
+	}
+	return true, nil
+}
+
+func (ti *TerminalInteractive) statePrintResultPushRune(r rune) (bool, error) {
+	switch r {
+	case '\r':
+		ti.printLinesInPrintResultState(1)
+		break
+	case ' ':
+		ti.printLinesInPrintResultState(20)
+		break
+	case '\x03':
+		_, _ = fmt.Fprint(ti.errStream, "\r\n")
+		ti.printModePrintLines = nil
+		if ti.printModeIsHelp {
+			ti.termState = hhc_common.TerminalState_Input
+			ti.runesEditor.PeekIntoTerminal(ti.currentPrompt, ti.termWidth, ti.outStream)
+		} else {
+			_ = ti.gotoStateInput(true)
+		}
+		break
+	}
+	return true, nil
+}
+
+func (ti *TerminalInteractive) SetTerminalWidth(w int) {
+	ti.termWidth = w
+}
+
+func (ti *TerminalInteractive) GetTerminalWidth() int {
+	return ti.termWidth
+}

+ 22 - 0
hhccli/view.go

@@ -0,0 +1,22 @@
+package hhccli
+
+import "git.swzry.com/zry/go-hhc-cli/hhc_ast"
+
+type CliView interface {
+	GetSDTRoot() *hhc_ast.SyntaxDefinitionTreeRoot
+}
+
+type EmptyCliView struct {
+	SyntaxDefineTree *hhc_ast.SyntaxDefinitionTreeRoot
+}
+
+func NewEmptyCliView(sdtroot *hhc_ast.SyntaxDefinitionTreeRoot) *EmptyCliView {
+	cv := &EmptyCliView{
+		SyntaxDefineTree: sdtroot,
+	}
+	return cv
+}
+
+func (emc *EmptyCliView) GetSDTRoot() *hhc_ast.SyntaxDefinitionTreeRoot {
+	return emc.SyntaxDefineTree
+}