1
0
forked from x/ContextOS

560 lines
14 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}