1
0
forked from x/ContextOS

506 lines
13 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 (
"context"
"bufio"
"fmt"
"io"
"os"
"os/exec"
"strconv"
"strings"
"time"
"unicode"
)
type CLI struct {
bio *bufio.Reader
out io.WriteCloser
which int
lines []string
alias map[string]string
lex *ctx.Message
target *ctx.Context
wait *ctx.Context
exit bool
test bool
save bool
*ctx.Context
}
func (cli *CLI) echo(str string, arg ...interface{}) bool {
if cli.out != nil {
fmt.Fprintf(cli.out, str, arg...)
return true
}
return false
}
func (cli *CLI) parse(m *ctx.Message) bool {
line := m.Cap("next")
if line == "" {
if m.Cap("level") == "0" {
cli.echo(m.Conf("PS1"))
}
if cli.bio == nil {
if cli.which == len(cli.lines) {
cli.exit = true
cli.which = 0
m.Spawn(cli.target).Set("detail", "end").Post(cli.Context)
return false
}
line = cli.lines[cli.which]
cli.which++
} else {
l, e := cli.bio.ReadString('\n')
m.Assert(e)
line = l
}
}
m.Cap("next", "")
if line = strings.TrimSpace(line); len(line) == 0 && m.Cap("stream") == "stdout" {
line = m.Cap("back")
m.Cap("back", "")
}
if len(line) == 0 || line[0] == '#' {
return true
}
ls := []string{}
if cli.lex == nil {
ls = strings.Split(line, " ")
} else {
lex := m.Spawn(cli.lex.Target())
m.Assert(lex.Cmd("split", line, "void"))
ls = lex.Meta["result"]
}
msg := m.Spawn(cli.target)
for i := 0; i < len(ls); i++ {
if ls[i] = strings.TrimSpace(ls[i]); ls[i] == "" {
continue
}
if ls[i][0] == '#' {
break
}
if r := rune(ls[i][0]); r == '$' || r == '_' || (!unicode.IsNumber(r) && !unicode.IsLetter(r)) {
if c, ok := cli.alias[string(r)]; ok {
if i == 0 {
if msg.Add("detail", c); len(ls[i]) == 1 {
continue
}
ls[i] = ls[i][1:]
} else if len(ls[i]) > 1 {
msg := m.Spawn(cli.target)
m.Assert(msg.Exec(c, ls[i][1:]))
ls[i] = msg.Get("result")
}
}
}
msg.Add("detail", ls[i])
}
msg.Wait = make(chan bool)
msg.Post(cli.Context)
if msg.Target().Context() != nil || msg.Target() == ctx.Index {
cli.target = msg.Target()
}
result := strings.TrimRight(strings.Join(msg.Meta["result"], ""), "\n")
m.Cap("result", result)
if len(result) > 0 {
cli.echo(result + "\n")
}
m.Cap("target", cli.target.Name)
m.Cap("back", line)
if cli.bio != nil {
cli.lines = append(cli.lines, line)
}
return !cli.exit
}
func (cli *CLI) Spawn(m *ctx.Message, c *ctx.Context, arg ...string) ctx.Server {
c.Caches = map[string]*ctx.Cache{}
c.Configs = map[string]*ctx.Config{}
s := new(CLI)
s.Context = c
s.lex = cli.lex
s.alias = cli.alias
return s
}
func (cli *CLI) Begin(m *ctx.Message, arg ...string) ctx.Server {
cli.Caches["target"] = &ctx.Cache{Name: "操作目标", Value: "", Help: "操作目标"}
cli.Caches["result"] = &ctx.Cache{Name: "前一条指令执行结果", Value: "", Help: "前一条指令执行结果"}
cli.Caches["back"] = &ctx.Cache{Name: "前一条指令", Value: "", Help: "前一条指令"}
cli.Caches["next"] = &ctx.Cache{Name: "下一条指令", Value: "", Help: "下一条指令"}
cli.Configs["PS1"] = &ctx.Config{Name: "命令行提示符(target/detail)", Value: "target", Help: "命令行提示符target:显示当前模块detail:显示详细信息", Hand: func(m *ctx.Message, x *ctx.Config, arg ...string) string {
if len(arg) > 0 { // {{{
return arg[0]
}
ps := make([]string, 0, 3)
if cli, ok := m.Target().Server.(*CLI); ok && cli.target != nil {
ps = append(ps, "[")
ps = append(ps, time.Now().Format("15:04:05"))
ps = append(ps, "]")
switch x.Value {
case "detail":
ps = append(ps, "(")
ps = append(ps, m.Cap("ncontext"))
ps = append(ps, ",")
ps = append(ps, m.Cap("nmessage"))
ps = append(ps, ",")
ps = append(ps, m.Cap("nserver"))
ps = append(ps, ")")
case "target":
}
ps = append(ps, "\033[32m")
ps = append(ps, cli.target.Name)
ps = append(ps, "\033[0m> ")
} else {
ps = append(ps, "[")
ps = append(ps, time.Now().Format("15:04:05"))
ps = append(ps, "]")
ps = append(ps, "\033[32m")
ps = append(ps, x.Value)
ps = append(ps, "\033[0m> ")
}
return strings.Join(ps, "")
// }}}
}}
cli.Configs["lex"] = &ctx.Config{Name: "词法解析器", Value: "", Help: "命令行词法解析器", Hand: func(m *ctx.Message, x *ctx.Config, arg ...string) string {
if len(arg) > 0 { // {{{
cli, ok := m.Target().Server.(*CLI)
m.Assert(ok, "模块类型错误")
lex := m.Find(arg[0], true)
m.Assert(lex != nil, "词法解析模块不存在")
if lex.Cap("status") != "start" {
lex.Target().Start(lex)
m.Spawn(lex.Target()).Cmd("train", "'[^']*'")
m.Spawn(lex.Target()).Cmd("train", "\"[^\"]*\"")
m.Spawn(lex.Target()).Cmd("train", "[^ \t\n]+")
m.Spawn(lex.Target()).Cmd("train", "[ \n\t]+", "void", "void")
m.Spawn(lex.Target()).Cmd("train", "#[^\n]*\n", "void", "void")
}
cli.lex = lex
}
return x.Value
// }}}
}}
if len(arg) > 0 {
cli.Configs["init.sh"] = &ctx.Config{Name: "启动脚本", Value: arg[0], Help: "模块启动时自动运行的脚本"}
}
if cli.Context == Index {
Pulse = m
}
cli.target = cli.Context
cli.alias = map[string]string{
"~": "context",
"!": "command",
"@": "config",
"$": "cache",
"&": "server",
"*": "message",
}
return cli
}
func (cli *CLI) Start(m *ctx.Message, arg ...string) bool {
cli.Context.Exit = make(chan bool)
cli.bio = nil
cli.exit = false
cli.test = false
if stream, ok := m.Data["io"]; ok {
cli.Context.Master(cli.Context)
io := stream.(io.ReadWriteCloser)
cli.bio = bufio.NewReader(io)
cli.out = io
if msg := m.Find("aaa", true); msg != nil {
cli.echo("username>")
username, e := cli.bio.ReadString('\n')
msg.Assert(e)
username = strings.TrimSpace(username)
cli.echo("password>")
password, e := cli.bio.ReadString('\n')
msg.Assert(e)
password = strings.TrimSpace(password)
msg.Name = "aaa"
msg.Wait = make(chan bool)
if msg.Cmd("login", username, password) == "" {
cli.echo("登录失败")
io.Close()
return true
}
if cli.Sessions == nil {
cli.Sessions = make(map[string]*ctx.Message)
}
cli.Sessions["aaa"] = msg
}
} else if len(arg) > 0 {
m.Cap("stream", "stdout")
m.Cap("next", "source "+m.Conf("init.sh"))
cli.Context.Master(cli.Context)
cli.bio = bufio.NewReader(os.Stdin)
cli.out = os.Stdout
cli.Caches["level"] = &ctx.Cache{Name: "操作目标", Value: "0", Help: "操作目标"}
} else if stream, ok := m.Data["file"]; ok {
if bio, ok := stream.(*bufio.Reader); ok {
m.Cap("stream", "file")
cli.bio = bio
} else {
m.Cap("stream", "file")
cli.bio = bufio.NewReader(stream.(io.ReadWriteCloser))
}
m.Capi("level", 1)
defer m.Capi("level", -1)
cli.test = m.Has("test")
cli.save = m.Has("save")
}
go m.AssertOne(m, true, func(m *ctx.Message) {
for cli.parse(m) {
}
})
m.Capi("nterm", 1)
defer m.Capi("nterm", -1)
m.Deal(func(msg *ctx.Message, arg ...string) bool {
if cli.test {
if _, ok := Index.Commands[msg.Get("detail")]; ok {
msg.Exec(msg.Meta["detail"][0], msg.Meta["detail"][1:]...)
}
return false
}
return true
}, func(msg *ctx.Message, arg ...string) bool {
if msg.Get("result") == "error: " {
if msg.Get("detail") != "login" {
msg.Log("system", nil, "%v", msg.Meta["detail"])
msg.Set("result")
msg.Set("append")
c := exec.Command(msg.Meta["detail"][0], msg.Meta["detail"][1:]...)
if cli.out == os.Stdout {
c.Stdin, c.Stdout, c.Stderr = os.Stdin, os.Stdout, os.Stderr
msg.Assert(c.Start())
msg.Assert(c.Wait())
} else {
if out, e := c.CombinedOutput(); e == nil {
msg.Echo(string(out))
} else {
msg.Echo("error: ")
msg.Echo("%s\n", e)
}
}
}
}
if msg.Target().Context() != nil || msg.Target() == ctx.Index {
cli.target = msg.Target()
}
return cli.exit == false
})
m.Cap("status", "close")
return !cli.save
}
func (cli *CLI) Close(m *ctx.Message, arg ...string) bool {
switch cli.Context {
case m.Target():
if m.Target() == Index && m.Capi("nserver") > 1 {
return false
}
case m.Source():
if m.Name == "aaa" {
msg := m.Spawn(cli.Context)
msg.Master(cli.Context)
if !cli.Context.Close(msg, arg...) {
return false
}
}
}
return true
}
var Pulse *ctx.Message
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{
"sleep": &ctx.Command{Name: "sleep time", Help: "运行脚本", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) {
t, e := strconv.Atoi(arg[0])
m.Assert(e)
m.Log("info", nil, "sleep %ds", t)
time.Sleep(time.Second * time.Duration(t))
m.Log("info", nil, "sleep %ds done", t)
}},
"source": &ctx.Command{Name: "source file", Help: "运行脚本", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) {
f, e := os.Open(arg[0])
m.Assert(e)
m.Put("option", "file", f).Start(key, "脚本文件")
<-m.Target().Exit
}},
"return": &ctx.Command{Name: "return result...", Help: "运行脚本", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) {
cli, ok := m.Source().Server.(*CLI)
m.Assert(ok)
cli.exit = true
for _, v := range arg {
cli.Pulse.Echo(v)
}
}},
"if": &ctx.Command{Name: "if a [ == | != ] b", Help: "运行脚本", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) {
cli, ok := m.Source().Server.(*CLI)
m.Assert(ok)
if arg[1] == "==" && arg[0] != arg[2] {
m.Add("option", "test")
}
if arg[1] == "!=" && arg[0] == arg[2] {
m.Add("option", "test")
}
m.Put("option", "file", cli.bio).Start(key, "条件语句")
<-m.Target().Exit
}},
"for": &ctx.Command{Name: "for var in list", Help: "运行脚本", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) {
cli, ok := m.Source().Server.(*CLI)
m.Assert(ok)
if arg[1] == "==" && arg[0] != arg[2] {
m.Add("option", "test")
}
if arg[1] == "!=" && arg[0] == arg[2] {
m.Add("option", "test")
}
m.Put("option", "file", cli.bio).Start(key, "条件语句")
<-m.Target().Exit
}},
"end": &ctx.Command{Name: "end", Help: "运行脚本", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) {
cli, ok := m.Source().Server.(*CLI)
m.Assert(ok)
cli.exit = true
m.Log("fuck", nil, "%v", cli.lines)
m.Echo(strings.Join(arg, ""))
}},
"var": &ctx.Command{Name: "var a [= b]", Help: "定义变量", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) {
cli, ok := m.Source().Server.(*CLI)
m.Assert(ok)
val := ""
if len(arg) > 2 {
val = arg[2]
}
cli.Pulse.Cap(arg[0], arg[0], val, "临时变量")
}},
"function": &ctx.Command{Name: "function name", Help: "运行脚本", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) {
cli, ok := m.Source().Server.(*CLI)
m.Target(m.Source().Context())
m.Assert(ok)
m.Add("option", "test")
m.Add("option", "save")
m.Put("option", "file", cli.bio).Start(arg[0], "定义函数")
m.Log("fuck", nil, "end function")
}},
"alias": &ctx.Command{Name: "alias [short [long]]", Help: "查看日志", Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...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]])
}
// }}}
}},
"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(m *ctx.Message, c *ctx.Context, key string, arg ...string) {
action := "dial" // {{{
if m.Has("listen") {
action = "listen"
}
msg := m.Find(m.Get("args"), true)
if m.Has("master") {
msg.Template = msg.Spawn(msg.Source()).Add("option", "master")
}
msg.Cmd(action, m.Get(action))
}}, // }}}
"open": &ctx.Command{Name: "open [master|slaver] [script [log]]", Help: "建立远程连接",
Options: map[string]string{"master": "主控终端", "slaver": "被控终端", "args": "启动参数", "io": "读写流"},
Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) {
m.Start(fmt.Sprintf("PTS%d", m.Capi("nterm")), "管理终端", "void.sh") // {{{
// }}}
}},
"master": &ctx.Command{Name: "open [master|slaver] [script [log]]", Help: "建立远程连接",
Options: map[string]string{"master": "主控终端", "slaver": "被控终端", "args": "启动参数", "io": "读写流"},
Hand: func(m *ctx.Message, c *ctx.Context, key string, arg ...string) {
_, ok := c.Server.(*CLI) // {{{
m.Assert(ok, "模块类型错误")
m.Assert(m.Target() != c, "模块是主控模块")
msg := m.Spawn(c)
msg.Start(fmt.Sprintf("PTS%d", Pulse.Capi("nterm")), arg[0], arg[1:]...)
// }}}
}},
},
Index: map[string]*ctx.Context{
"void": &ctx.Context{Name: "void",
Commands: map[string]*ctx.Command{
"open": &ctx.Command{},
},
},
},
}
func init() {
cli := &CLI{}
cli.Context = Index
ctx.Index.Register(Index, cli)
}