forked from x/ContextOS
560 lines
14 KiB
Go
560 lines
14 KiB
Go
package cli // {{{
|
||
// }}}
|
||
import ( // {{{
|
||
"bufio"
|
||
"context"
|
||
"fmt"
|
||
"io"
|
||
"os"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
"unicode"
|
||
)
|
||
|
||
// }}}
|
||
|
||
type CLI struct {
|
||
out io.WriteCloser
|
||
in io.ReadCloser
|
||
ins []io.ReadCloser
|
||
bio *bufio.Reader
|
||
bios []*bufio.Reader
|
||
bufs [][]byte
|
||
|
||
history []map[string]string
|
||
alias map[string]string
|
||
next string
|
||
exit bool
|
||
login *ctx.Context
|
||
lex *ctx.Message
|
||
|
||
target *ctx.Context
|
||
*ctx.Context
|
||
}
|
||
|
||
func (cli *CLI) push(f io.ReadCloser) { // {{{
|
||
if cli.ins == nil || cli.bios == nil {
|
||
cli.ins = make([]io.ReadCloser, 0, 3)
|
||
cli.bios = make([]*bufio.Reader, 0, 3)
|
||
}
|
||
|
||
cli.in = f
|
||
cli.ins = append(cli.ins, cli.in)
|
||
cli.bio = bufio.NewReader(f)
|
||
cli.bios = append(cli.bios, cli.bio)
|
||
}
|
||
|
||
// }}}
|
||
func (cli *CLI) parse(m *ctx.Message) bool { // {{{
|
||
if false && len(cli.ins) == 1 && cli.Owner == nil {
|
||
if msg := m.Spawn(cli.Root).Find("aaa"); msg != nil {
|
||
|
||
username := ""
|
||
fmt.Fprintf(cli.out, "username>")
|
||
fmt.Fscanln(cli.in, &username)
|
||
|
||
password := ""
|
||
fmt.Fprintf(cli.out, "password>")
|
||
fmt.Fscanln(cli.in, &password)
|
||
|
||
if msg.Cmd("login", username, password) == "" {
|
||
fmt.Fprintln(cli.out, "登录失败")
|
||
m.Cmd("exit")
|
||
cli.out.Close()
|
||
cli.in.Close()
|
||
return false
|
||
}
|
||
|
||
m.Cap("username", msg.Cap("username"))
|
||
}
|
||
}
|
||
|
||
if len(cli.ins) == 1 || m.Conf("slient") != "yes" {
|
||
cli.echo(m.Conf("PS1"))
|
||
}
|
||
|
||
line := ""
|
||
if cli.next == "" {
|
||
ls, e := cli.bio.ReadString('\n')
|
||
if e == io.EOF {
|
||
l := len(cli.ins)
|
||
if l > 1 {
|
||
cli.ins = cli.ins[:l-1]
|
||
cli.bios = cli.bios[:l-1]
|
||
cli.in = cli.ins[l-2]
|
||
cli.bio = cli.bios[l-2]
|
||
return true
|
||
}
|
||
// cli.echo("\n%s\n", cli.Conf("结束语"))
|
||
return false
|
||
}
|
||
m.Assert(e)
|
||
line = ls
|
||
|
||
if len(cli.ins) > 1 && m.Conf("slient") != "yes" {
|
||
cli.echo(line)
|
||
}
|
||
|
||
if len(line) == 1 {
|
||
if len(cli.ins) == 1 {
|
||
line = cli.history[len(cli.history)-1]["cli"]
|
||
} else {
|
||
return true
|
||
}
|
||
}
|
||
} else {
|
||
line = cli.next
|
||
cli.next = ""
|
||
|
||
if m.Conf("slient") != "yes" {
|
||
cli.echo(line)
|
||
cli.echo("\n")
|
||
}
|
||
}
|
||
|
||
back:
|
||
|
||
line = strings.TrimSpace(line)
|
||
if line[0] == '#' {
|
||
return true
|
||
}
|
||
ls := strings.Split(line, " ")
|
||
if cli.lex != nil {
|
||
msg := m.Spawn(cli.lex.Target)
|
||
msg.Cmd("split", line)
|
||
ls = msg.Meta["result"]
|
||
for i := 0; i < len(ls); i++ {
|
||
if ls[i][0] == '"' {
|
||
ls[i] = ls[i][1 : len(ls[i])-1]
|
||
}
|
||
}
|
||
if len(ls) == 0 {
|
||
return true
|
||
}
|
||
}
|
||
|
||
msg := m.Spawn(cli.target)
|
||
msg.Wait = make(chan bool)
|
||
|
||
r := rune(ls[0][0])
|
||
if !unicode.IsNumber(r) || !unicode.IsLetter(r) || r == '$' || r == '_' {
|
||
if _, ok := cli.alias[string(r)]; ok {
|
||
msg.Add("detail", ls[0][:1])
|
||
if len(ls[0]) > 1 {
|
||
ls[0] = ls[0][1:]
|
||
} else {
|
||
if len(ls) > 1 {
|
||
ls = ls[1:]
|
||
} else {
|
||
ls = nil
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
for i := 0; i < len(ls); i++ {
|
||
if cli.lex == nil {
|
||
ls[i] = strings.TrimSpace(ls[i])
|
||
}
|
||
|
||
if ls[i][0] == '#' {
|
||
break
|
||
}
|
||
if ls[i] != "" {
|
||
msg.Add("detail", ls[i])
|
||
}
|
||
}
|
||
|
||
ls = msg.Meta["detail"]
|
||
if n, e := strconv.Atoi(ls[0]); e == nil && 0 <= n && n < len(cli.history) && ls[0] != cli.history[n]["cli"] {
|
||
line = cli.history[n]["cli"]
|
||
msg.Meta["detail"] = nil
|
||
goto back
|
||
}
|
||
|
||
msg.Post(cli.Context)
|
||
|
||
for _, v := range msg.Meta["result"] {
|
||
cli.echo(v)
|
||
}
|
||
|
||
return true
|
||
}
|
||
|
||
// }}}
|
||
func (cli *CLI) echo(str string, arg ...interface{}) { // {{{
|
||
// if len(cli.ins) == 1 || m.Conf("slient") != "yes" {
|
||
fmt.Fprintf(cli.out, str, arg...)
|
||
// }
|
||
}
|
||
|
||
// }}}
|
||
|
||
func (cli *CLI) Spawn(c *ctx.Context, m *ctx.Message, arg ...string) ctx.Server { // {{{
|
||
c.Caches = map[string]*ctx.Cache{}
|
||
c.Configs = map[string]*ctx.Config{}
|
||
|
||
c.Owner = nil
|
||
s := new(CLI)
|
||
s.Context = c
|
||
return s
|
||
}
|
||
|
||
// }}}
|
||
func (cli *CLI) Begin(m *ctx.Message, arg ...string) ctx.Server { // {{{
|
||
cli.Caches["username"] = &ctx.Cache{Name: "登录用户", Value: "", Help: "登录用户名"}
|
||
cli.Caches["nhistory"] = &ctx.Cache{Name: "历史命令数量", Value: "0", Help: "当前终端已经执行命令的数量", Hand: func(m *ctx.Message, x *ctx.Cache, arg ...string) string {
|
||
x.Value = fmt.Sprintf("%d", len(cli.history))
|
||
return x.Value
|
||
}}
|
||
|
||
cli.Configs["lex"] = &ctx.Config{Name: "屏蔽脚本输出(yes/no)", Value: "", Help: "屏蔽脚本输出的信息,yes:屏蔽,no:不屏蔽", Hand: func(m *ctx.Message, x *ctx.Config, arg ...string) string {
|
||
if len(arg) > 0 {
|
||
cli, ok := m.Target.Server.(*CLI)
|
||
if !ok {
|
||
return ""
|
||
}
|
||
|
||
cli.lex = m.Find(arg[0], m.Target.Root)
|
||
if cli.lex == nil {
|
||
return ""
|
||
}
|
||
|
||
cli.lex.Cmd("train", "[ \n\t]+", "1")
|
||
}
|
||
return ""
|
||
}}
|
||
cli.Configs["slient"] = &ctx.Config{Name: "屏蔽脚本输出(yes/no)", Value: "yes", Help: "屏蔽脚本输出的信息,yes:屏蔽,no:不屏蔽"}
|
||
cli.Configs["default"] = &ctx.Config{Name: "默认的搜索起点(root/back/home)", Value: "root", Help: "模块搜索的默认起点,root:从根模块,back:从父模块,home:从当前模块"}
|
||
cli.Configs["PS1"] = &ctx.Config{Name: "命令行提示符(target/detail)", Value: "target", Help: "命令行提示符,target:显示当前模块,detail:显示详细信息", Hand: func(m *ctx.Message, x *ctx.Config, arg ...string) string {
|
||
cli, ok := m.Target.Server.(*CLI) // {{{
|
||
if ok && cli.target != nil {
|
||
// c = cli.target
|
||
switch x.Value {
|
||
case "target":
|
||
return fmt.Sprintf("%s[%s]\033[32m%s\033[0m> ", m.Cap("nhistory"), time.Now().Format("15:04:05"), cli.target.Name)
|
||
case "detail":
|
||
return fmt.Sprintf("%s[%s](%s,%s,%s)\033[32m%s\033[0m> ", m.Cap("nhistory"), time.Now().Format("15:04:05"), m.Cap("ncontext"), m.Cap("nmessage"), m.Cap("nserver"), m.Target.Name)
|
||
}
|
||
|
||
}
|
||
|
||
return fmt.Sprintf("[%s]\033[32m%s\033[0m ", time.Now().Format("15:04:05"), x.Value)
|
||
// }}}
|
||
}}
|
||
|
||
if len(arg) > 0 {
|
||
cli.Configs["init.sh"] = &ctx.Config{Name: "启动脚本", Value: arg[0], Help: "模块启动时自动运行的脚本"}
|
||
}
|
||
|
||
cli.target = cli.Context
|
||
cli.history = make([]map[string]string, 0, 100)
|
||
cli.alias = map[string]string{
|
||
"~": "context",
|
||
"!": "history",
|
||
"@": "config",
|
||
"$": "cache",
|
||
"&": "server",
|
||
"*": "message",
|
||
":": "command",
|
||
}
|
||
|
||
return cli
|
||
}
|
||
|
||
// }}}
|
||
func (cli *CLI) Start(m *ctx.Message, arg ...string) bool { // {{{
|
||
m.Capi("nterm", 1)
|
||
defer m.Capi("nterm", -1)
|
||
|
||
if stream, ok := m.Data["io"]; ok {
|
||
io := stream.(io.ReadWriteCloser)
|
||
cli.out = io
|
||
cli.push(io)
|
||
|
||
if m.Has("master") {
|
||
m.Log("info", "%s: master terminal", cli.Name)
|
||
if cli.bufs == nil {
|
||
cli.bufs = make([][]byte, 0, 10)
|
||
}
|
||
for {
|
||
b := make([]byte, 128)
|
||
n, e := cli.bio.Read(b)
|
||
m.Log("info", "%s: read %d", cli.Name, n)
|
||
m.Assert(e)
|
||
cli.bufs = append(cli.bufs, b)
|
||
}
|
||
return true
|
||
} else {
|
||
m.Log("info", "%s: slaver terminal", cli.Name)
|
||
m.Log("info", "%s: open %s", cli.Name, m.Conf("init.sh"))
|
||
|
||
if f, e := os.Open(m.Conf("init.sh")); e == nil {
|
||
cli.push(f)
|
||
}
|
||
|
||
go m.AssertOne(m, true, func(m *ctx.Message) {
|
||
for cli.parse(m) {
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
for m.Deal(func(msg *ctx.Message, arg ...string) bool {
|
||
if a, ok := cli.alias[arg[0]]; ok {
|
||
arg[0] = a
|
||
}
|
||
return true
|
||
|
||
}, func(msg *ctx.Message, arg ...string) bool {
|
||
cli.history = append(cli.history, map[string]string{
|
||
"time": time.Now().Format("15:04:05"),
|
||
"index": fmt.Sprintf("%d", len(cli.history)),
|
||
"cli": strings.Join(msg.Meta["detail"], " "),
|
||
})
|
||
|
||
if len(arg) > 0 {
|
||
// cli.next = arg[0]
|
||
// arg[0] = ""
|
||
}
|
||
|
||
if cli.exit == true {
|
||
return false
|
||
}
|
||
return true
|
||
}) {
|
||
}
|
||
|
||
return true
|
||
}
|
||
|
||
// }}}
|
||
func (cli *CLI) Close(m *ctx.Message, arg ...string) bool { // {{{
|
||
switch cli.Context {
|
||
case m.Source:
|
||
return false
|
||
|
||
case m.Target:
|
||
m.Log("exit", "%s: release", cli.Name)
|
||
}
|
||
|
||
return true
|
||
}
|
||
|
||
// }}}
|
||
|
||
var Index = &ctx.Context{Name: "cli", Help: "管理终端",
|
||
Caches: map[string]*ctx.Cache{
|
||
"nterm": &ctx.Cache{Name: "终端数量", Value: "0", Help: "已经运行的终端数量"},
|
||
},
|
||
Configs: map[string]*ctx.Config{},
|
||
Commands: map[string]*ctx.Command{
|
||
"context": &ctx.Command{Name: "context [root|back|home] [[find|search] name] [list|show|spawn|start|switch|close][args]", Help: "查找并操作模块,\n查找起点root:根模块、back:父模块、home:本模块,\n查找方法find:路径匹配、search:模糊匹配,\n查找对象name:支持点分和正则,\n操作类型show:显示信息、switch:切换为当前、start:启动模块、spawn:分裂子模块,args:启动参数",
|
||
Formats: map[string]int{"root": 0, "back": 0, "home": 0, "find": 1, "search": 1, "list": 0, "show": 0, "close": 0, "switch": 0, "start": 0, "spawn": 0},
|
||
Hand: func(c *ctx.Context, m *ctx.Message, key string, arg ...string) string {
|
||
cli, ok := m.Source.Server.(*CLI) // {{{
|
||
if !ok {
|
||
cli, ok = c.Server.(*CLI)
|
||
if !ok {
|
||
return ""
|
||
}
|
||
}
|
||
|
||
target := m.Target
|
||
if m.Has("home") {
|
||
target = m.Target
|
||
}
|
||
if m.Has("root") {
|
||
target = m.Target.Root
|
||
}
|
||
if m.Has("back") && target.Context != nil {
|
||
target = m.Target.Context
|
||
}
|
||
|
||
ms := []*ctx.Message{}
|
||
switch {
|
||
case m.Has("search"):
|
||
if s := m.Search(m.Get("search"), target); len(s) > 0 {
|
||
ms = append(ms, s...)
|
||
}
|
||
case m.Has("find"):
|
||
if msg := m.Find(m.Get("find"), target); msg != nil {
|
||
ms = append(ms, msg)
|
||
}
|
||
case m.Has("args"):
|
||
if s := m.Search(m.Get("args"), target); len(s) > 0 {
|
||
ms = append(ms, s...)
|
||
arg = arg[1:]
|
||
break
|
||
}
|
||
fallthrough
|
||
default:
|
||
ms = append(ms, m.Spawn(target))
|
||
}
|
||
|
||
for _, v := range ms {
|
||
switch {
|
||
case m.Has("spawn"):
|
||
v.Target.Spawn(v, arg[0], arg[1]).Begin(v)
|
||
cli.target = v.Target
|
||
case m.Has("start"):
|
||
v.Set("detail", arg...).Target.Start(v)
|
||
case m.Has("switch"):
|
||
cli.target = v.Target
|
||
case m.Has("close"):
|
||
v.Target.Close(v)
|
||
case m.Has("show"):
|
||
m.Echo("%s(%s): %s\n", v.Target.Name, v.Target.Owner.Name, v.Target.Help)
|
||
if len(v.Target.Requests) > 0 {
|
||
m.Echo("模块资源:\n")
|
||
for i, v := range v.Target.Requests {
|
||
m.Echo(" %d(%d): <- %s %s\n", i, v.Code, v.Source.Name, v.Meta["detail"])
|
||
for i, v := range v.Messages {
|
||
m.Echo(" %d(%d): -> %s %s\n", i, v.Code, v.Source.Name, v.Meta["detail"])
|
||
}
|
||
}
|
||
}
|
||
if len(v.Target.Sessions) > 0 {
|
||
m.Echo("模块引用:\n")
|
||
for k, v := range v.Target.Sessions {
|
||
m.Echo(" %s(%d): -> %s %v\n", k, v.Code, v.Target.Name, v.Meta["detail"])
|
||
}
|
||
}
|
||
case m.Has("list") || cli.target == v.Target:
|
||
m.Travel(v.Target, func(msg *ctx.Message) bool {
|
||
if msg.Target.Context != nil {
|
||
target := msg.Target
|
||
m.Echo("%s: %s(%s)", target.Context.Name, target.Name, target.Help)
|
||
|
||
msg.Target = msg.Target.Owner
|
||
if msg.Target != nil && msg.Check(msg.Target, "caches", "username") && msg.Check(msg.Target, "caches", "group") {
|
||
m.Echo(" %s %s", msg.Cap("username"), msg.Cap("group"))
|
||
}
|
||
|
||
m.Echo("\n")
|
||
msg.Target = target
|
||
}
|
||
return true
|
||
})
|
||
default:
|
||
cli.target = v.Target
|
||
return ""
|
||
}
|
||
}
|
||
return ""
|
||
// }}}
|
||
}},
|
||
"source": &ctx.Command{Name: "source file", Help: "运行脚本", Hand: func(c *ctx.Context, m *ctx.Message, key string, arg ...string) string {
|
||
cli := c.Server.(*CLI) // {{{
|
||
switch len(arg) {
|
||
case 1:
|
||
f, e := os.Open(arg[0])
|
||
m.Assert(e)
|
||
cli.push(f)
|
||
}
|
||
|
||
return ""
|
||
// }}}
|
||
}},
|
||
"alias": &ctx.Command{Name: "alias [short [long]]", Help: "查看日志", Hand: func(c *ctx.Context, m *ctx.Message, key string, arg ...string) string {
|
||
cli := c.Server.(*CLI) // {{{
|
||
switch len(arg) {
|
||
case 0:
|
||
for k, v := range cli.alias {
|
||
m.Echo("%s: %s\n", k, v)
|
||
}
|
||
case 2:
|
||
switch arg[0] {
|
||
case "delete":
|
||
delete(cli.alias, arg[1])
|
||
default:
|
||
cli.alias[arg[0]] = arg[1]
|
||
m.Echo("%s: %s\n", arg[0], cli.alias[arg[0]])
|
||
}
|
||
default:
|
||
cli.alias[arg[0]] = strings.Join(arg[1:], " ")
|
||
m.Echo("%s: %s\n", arg[0], cli.alias[arg[0]])
|
||
}
|
||
return ""
|
||
// }}}
|
||
}},
|
||
"history": &ctx.Command{Name: "history number", Help: "查看日志", Hand: func(c *ctx.Context, m *ctx.Message, key string, arg ...string) string {
|
||
cli := c.Server.(*CLI) // {{{
|
||
switch len(arg) {
|
||
case 0:
|
||
for i, v := range cli.history {
|
||
m.Echo("%d %s %s\n", i, v["time"], v["cli"])
|
||
}
|
||
case 1:
|
||
n, e := strconv.Atoi(arg[0])
|
||
if e == nil && 0 <= n && n < len(cli.history) {
|
||
cli.next = cli.history[n]["cli"]
|
||
}
|
||
default:
|
||
n, e := strconv.Atoi(arg[0])
|
||
if e == nil && 0 <= n && n < len(cli.history) {
|
||
cli.history[n]["cli"] = strings.Join(arg[1:], " ")
|
||
}
|
||
}
|
||
return ""
|
||
// }}}
|
||
}},
|
||
"remote": &ctx.Command{Name: "remote [send args...]|[[master|slaver] listen|dial address protocol]", Help: "建立远程连接",
|
||
Formats: map[string]int{"send": -1, "master": 0, "slaver": 0, "listen": 1, "dial": 1},
|
||
Hand: func(c *ctx.Context, m *ctx.Message, key string, arg ...string) string {
|
||
if m.Has("send") { // {{{
|
||
cli := m.Target.Server.(*CLI)
|
||
|
||
cli.out.Write([]byte(strings.Join(m.Meta["args"], " ") + "\n"))
|
||
m.Echo("~~~remote~~~\n")
|
||
time.Sleep(100 * time.Millisecond)
|
||
for _, b := range cli.bufs {
|
||
m.Echo("%s", string(b))
|
||
}
|
||
cli.bufs = cli.bufs[0:0]
|
||
m.Echo("\n~~~remote~~~\n")
|
||
|
||
return ""
|
||
}
|
||
|
||
action := "dial"
|
||
if m.Has("listen") {
|
||
action = "listen"
|
||
}
|
||
msg := m.Find(m.Get("args"), m.Target.Root)
|
||
|
||
if m.Has("master") {
|
||
msg.Template = msg.Spawn(msg.Source).Add("option", "master")
|
||
}
|
||
msg.Cmd(action, m.Get(action), m.Get(action), action, m.Get(action))
|
||
|
||
return ""
|
||
}},
|
||
// }}}
|
||
"open": &ctx.Command{Name: "open [master|slaver] [script [log]]", Help: "建立远程连接",
|
||
Options: map[string]string{"master": "主控终端", "slaver": "被控终端", "args": "启动参数", "io": "读写流"},
|
||
Hand: func(c *ctx.Context, m *ctx.Message, key string, arg ...string) string {
|
||
m.Start(fmt.Sprintf("PTS%d", m.Capi("nterm")), "管理终端", arg...) // {{{
|
||
return ""
|
||
// }}}
|
||
}},
|
||
"void": &ctx.Command{Name: "", Help: "", Hand: nil},
|
||
},
|
||
Messages: make(chan *ctx.Message, 10),
|
||
Index: map[string]*ctx.Context{
|
||
"void": &ctx.Context{Name: "void",
|
||
Commands: map[string]*ctx.Command{
|
||
"context": &ctx.Command{},
|
||
"open": &ctx.Command{},
|
||
},
|
||
},
|
||
},
|
||
}
|
||
|
||
func init() {
|
||
cli := &CLI{}
|
||
cli.Context = Index
|
||
ctx.Index.Register(Index, cli)
|
||
}
|