forked from x/icebergs
436 lines
11 KiB
Go
436 lines
11 KiB
Go
package ssh
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path"
|
|
"strings"
|
|
"time"
|
|
|
|
ice "github.com/shylinux/icebergs"
|
|
"github.com/shylinux/icebergs/base/aaa"
|
|
"github.com/shylinux/icebergs/base/ctx"
|
|
"github.com/shylinux/icebergs/base/mdb"
|
|
"github.com/shylinux/icebergs/base/nfs"
|
|
kit "github.com/shylinux/toolkits"
|
|
)
|
|
|
|
func Render(msg *ice.Message, cmd string, args ...interface{}) string {
|
|
switch arg := kit.Simple(args...); cmd {
|
|
case ice.RENDER_VOID:
|
|
case ice.RENDER_RESULT:
|
|
// 转换结果
|
|
if len(arg) > 0 {
|
|
msg.Resultv(arg)
|
|
}
|
|
res := msg.Result()
|
|
|
|
// 输出结果
|
|
if fmt.Fprint(msg.O, res); !strings.HasSuffix(res, ice.MOD_NL) {
|
|
fmt.Fprint(msg.O, ice.MOD_NL)
|
|
}
|
|
return res
|
|
|
|
default:
|
|
// 转换结果
|
|
res := msg.Result()
|
|
if res == "" {
|
|
res = msg.Table().Result()
|
|
}
|
|
|
|
// 输出结果
|
|
if fmt.Fprint(msg.O, res); !strings.HasSuffix(res, ice.MOD_NL) {
|
|
fmt.Fprint(msg.O, ice.MOD_NL)
|
|
}
|
|
return res
|
|
}
|
|
return ""
|
|
}
|
|
func Script(m *ice.Message, name string) io.Reader {
|
|
if strings.Contains(m.Option(ice.MSG_SCRIPT), "/") {
|
|
name = path.Join(path.Dir(m.Option(ice.MSG_SCRIPT)), name)
|
|
}
|
|
m.Option(ice.MSG_SCRIPT, name)
|
|
|
|
// 本地文件
|
|
back := kit.Split(m.Option(nfs.DIR_ROOT))
|
|
for i := len(back) - 1; i >= 0; i-- {
|
|
if s, e := os.Open(path.Join(path.Join(back[:i]...), name)); e == nil {
|
|
return s
|
|
}
|
|
}
|
|
if s, e := os.Open(name); e == nil {
|
|
return s
|
|
}
|
|
|
|
switch strings.Split(name, "/")[0] {
|
|
case kit.SSH_ETC, kit.SSH_VAR:
|
|
m.Warn(true, ice.ErrNotFound)
|
|
return nil
|
|
}
|
|
|
|
// 打包文件
|
|
if b, ok := ice.BinPack["/"+name]; ok {
|
|
m.Info("binpack %v %v", len(b), name)
|
|
return bytes.NewReader(b)
|
|
}
|
|
|
|
// 远程文件
|
|
if msg := m.Cmd("web.spide", "dev", "GET", path.Join("/share/local/", name)); msg.Result(0) != ice.ErrWarn {
|
|
return bytes.NewBuffer([]byte(msg.Result()))
|
|
}
|
|
|
|
// 源码文件
|
|
if strings.HasPrefix(name, kit.SSH_USR) {
|
|
ls := strings.Split(name, "/")
|
|
m.Cmd("web.code.git.repos", ls[1], path.Join(kit.SSH_USR, ls[1]))
|
|
if s, e := os.Open(name); e == nil {
|
|
return s
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type Frame struct {
|
|
source string
|
|
target *ice.Context
|
|
stdout io.Writer
|
|
stdin io.Reader
|
|
pipe io.Writer
|
|
|
|
count int
|
|
last string
|
|
ps1 []string
|
|
ps2 []string
|
|
}
|
|
|
|
func (f *Frame) prompt(m *ice.Message, list ...string) *Frame {
|
|
if f.source != STDIO {
|
|
return f
|
|
}
|
|
if len(list) == 0 {
|
|
list = append(list, f.ps1...)
|
|
}
|
|
|
|
m.Sleep("30ms")
|
|
fmt.Fprintf(f.stdout, "\r")
|
|
for _, v := range list {
|
|
switch v {
|
|
case kit.MDB_COUNT:
|
|
fmt.Fprintf(f.stdout, "%d", f.count)
|
|
case kit.MDB_TIME:
|
|
fmt.Fprintf(f.stdout, time.Now().Format("15:04:05"))
|
|
case TARGET:
|
|
fmt.Fprintf(f.stdout, f.target.Name)
|
|
default:
|
|
fmt.Fprintf(f.stdout, v)
|
|
}
|
|
}
|
|
return f
|
|
}
|
|
func (f *Frame) printf(m *ice.Message, res string, arg ...interface{}) *Frame {
|
|
if len(arg) > 0 {
|
|
fmt.Fprintf(f.stdout, res, arg...)
|
|
} else {
|
|
fmt.Fprint(f.stdout, res)
|
|
}
|
|
return f
|
|
}
|
|
func (f *Frame) option(m *ice.Message, ls []string) []string {
|
|
ln := []string{}
|
|
m.Option(mdb.CACHE_LIMIT, 10)
|
|
for i := 0; i < len(ls); i++ {
|
|
if ls[i] == "--" {
|
|
ln = append(ln, ls[i+1:]...)
|
|
break
|
|
}
|
|
|
|
if strings.HasPrefix(ls[i], "-") {
|
|
for j := i; j < len(ls); j++ {
|
|
if j == len(ls)-1 || strings.HasPrefix(ls[j+1], "-") {
|
|
if i < j {
|
|
m.Option(ls[i][1:], ls[i+1:j+1])
|
|
} else {
|
|
m.Option(ls[i][1:], "true")
|
|
}
|
|
i = j
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
ln = append(ln, ls[i])
|
|
}
|
|
}
|
|
return ln
|
|
}
|
|
func (f *Frame) change(m *ice.Message, ls []string) []string {
|
|
if len(ls) == 1 && ls[0] == "~" { // 模块列表
|
|
ls = []string{ctx.CONTEXT}
|
|
|
|
} else if len(ls) > 0 && strings.HasPrefix(ls[0], "~") { // 切换模块
|
|
target := ls[0][1:]
|
|
if ls = ls[1:]; len(target) == 0 && len(ls) > 0 {
|
|
target, ls = ls[0], ls[1:]
|
|
}
|
|
if target == "~" {
|
|
target = ""
|
|
}
|
|
m.Spawn(f.target).Search(target+".", func(p *ice.Context, s *ice.Context, key string) {
|
|
m.Info("choice: %s", s.Name)
|
|
f.target = s
|
|
})
|
|
}
|
|
return ls
|
|
}
|
|
func (f *Frame) alias(m *ice.Message, ls []string) []string {
|
|
if alias, ok := m.Optionv(ice.MSG_ALIAS).(map[string]interface{}); ok {
|
|
if len(ls) > 0 {
|
|
if a := kit.Simple(alias[ls[0]]); len(a) > 0 {
|
|
ls = append(append([]string{}, a...), ls[1:]...)
|
|
}
|
|
}
|
|
}
|
|
return ls
|
|
}
|
|
func (f *Frame) parse(m *ice.Message, line string) string {
|
|
if strings.HasPrefix(line, "<") {
|
|
fmt.Fprintf(m.O, line)
|
|
return ""
|
|
}
|
|
|
|
for _, one := range kit.Split(line, ";", ";", ";") {
|
|
async, one := false, strings.TrimSpace(one)
|
|
if strings.TrimSuffix(one, "&") != one {
|
|
async, one = true, strings.TrimSuffix(one, "&")
|
|
}
|
|
|
|
msg := m.Spawn(f.target)
|
|
msg.Option("_cmd", one)
|
|
|
|
ls := kit.Split(one)
|
|
ls = f.alias(msg, ls)
|
|
ls = f.change(msg, ls)
|
|
ls = f.option(msg, ls)
|
|
if len(ls) == 0 {
|
|
continue
|
|
}
|
|
|
|
if async {
|
|
msg.Gos(msg, func(msg *ice.Message) { msg.Cmd(ls[0], ls[1:]) })
|
|
continue
|
|
} else {
|
|
msg.Cmdy(ls[0], ls[1:])
|
|
}
|
|
|
|
if strings.HasPrefix(msg.Result(), ice.ErrWarn) && m.Option("render") == "raw" {
|
|
fmt.Fprintf(msg.O, line)
|
|
continue
|
|
}
|
|
|
|
// 渲染引擎
|
|
_args, _ := msg.Optionv(ice.MSG_ARGS).([]interface{})
|
|
f.last = Render(msg, msg.Option(ice.MSG_OUTPUT), _args...)
|
|
}
|
|
return ""
|
|
}
|
|
func (f *Frame) scan(m *ice.Message, h, line string) *Frame {
|
|
m.Option(kit.Keycb(RETURN), func() { f.close() })
|
|
f.ps1 = kit.Simple(m.Confv(PROMPT, kit.Keym(PS1)))
|
|
f.ps2 = kit.Simple(m.Confv(PROMPT, kit.Keym(PS2)))
|
|
ps := f.ps1
|
|
|
|
m.Sleep("300ms")
|
|
m.I, m.O = f.stdin, f.stdout
|
|
bio := bufio.NewScanner(f.stdin)
|
|
for f.prompt(m, ps...); bio.Scan() && f.stdin != nil; f.prompt(m, ps...) {
|
|
if h == STDIO && len(bio.Text()) == 0 {
|
|
continue // 空行
|
|
}
|
|
|
|
m.Cmdx(mdb.INSERT, SOURCE, kit.Keys(kit.MDB_HASH, h), mdb.LIST, kit.MDB_TEXT, bio.Text())
|
|
f.count++
|
|
|
|
if len(bio.Text()) == 0 {
|
|
if strings.Count(line, "`")%2 == 1 {
|
|
line += "\n"
|
|
}
|
|
continue // 空行
|
|
}
|
|
|
|
if strings.HasSuffix(bio.Text(), "\\") {
|
|
line += bio.Text()[:len(bio.Text())-1]
|
|
ps = f.ps2
|
|
continue // 续行
|
|
}
|
|
if line += bio.Text(); strings.Count(line, "`")%2 == 1 {
|
|
line += "\n"
|
|
ps = f.ps2
|
|
continue // 多行
|
|
}
|
|
if strings.HasPrefix(strings.TrimSpace(line), "#") {
|
|
line = ""
|
|
continue // 注释
|
|
}
|
|
if ps = f.ps1; f.stdout == os.Stdout {
|
|
// 清空格式
|
|
f.printf(m, "\033[0m")
|
|
}
|
|
line = f.parse(m, line)
|
|
}
|
|
return f
|
|
}
|
|
func (f *Frame) close() {
|
|
if stdin, ok := f.stdin.(io.Closer); ok {
|
|
stdin.Close()
|
|
f.stdin = nil
|
|
}
|
|
}
|
|
|
|
func (f *Frame) Begin(m *ice.Message, arg ...string) ice.Server {
|
|
return f
|
|
}
|
|
func (f *Frame) Spawn(m *ice.Message, c *ice.Context, arg ...string) ice.Server {
|
|
return &Frame{}
|
|
}
|
|
func (f *Frame) Start(m *ice.Message, arg ...string) bool {
|
|
f.source, f.target = kit.Select(STDIO, arg, 0), m.Target()
|
|
|
|
switch m.Cap(ice.CTX_STREAM, f.source) {
|
|
case STDIO: // 终端交互
|
|
r, w, _ := os.Pipe()
|
|
m.Go(func() { io.Copy(w, os.Stdin) })
|
|
f.stdin, f.stdout = r, os.Stdout
|
|
f.pipe = w
|
|
|
|
m.Option(ice.MSG_OPTS, ice.MSG_USERNAME)
|
|
aaa.UserRoot(m)
|
|
|
|
default: // 脚本文件
|
|
f.target = m.Source()
|
|
|
|
if strings.HasPrefix(f.source, "/dev") {
|
|
f.stdin, f.stdout = m.I, m.O
|
|
break
|
|
}
|
|
|
|
buf := bytes.NewBuffer(make([]byte, 0, ice.MOD_BUFS))
|
|
defer func() { m.Echo(buf.String()) }()
|
|
|
|
if s := Script(m, f.source); s != nil {
|
|
f.stdin, f.stdout = s, buf
|
|
break
|
|
}
|
|
|
|
// 查找失败
|
|
return true
|
|
}
|
|
|
|
// 解析脚本
|
|
if f.count = 1; f.source == STDIO {
|
|
m.Conf(SOURCE, kit.Keys(kit.MDB_HASH, STDIO, kit.MDB_META, kit.MDB_NAME), STDIO)
|
|
m.Conf(SOURCE, kit.Keys(kit.MDB_HASH, STDIO, kit.MDB_META, kit.MDB_TIME), m.Time())
|
|
|
|
f.count = kit.Int(m.Conf(SOURCE, kit.Keys(kit.MDB_HASH, STDIO, kit.Keym(kit.MDB_COUNT)))) + 1
|
|
f.scan(m, STDIO, "")
|
|
} else {
|
|
h := m.Cmdx(mdb.INSERT, SOURCE, "", mdb.HASH, kit.MDB_NAME, f.source)
|
|
m.Conf(SOURCE, kit.Keys(kit.MDB_HASH, h, kit.Keym(kit.MDB_COUNT)), 0)
|
|
m.Conf(SOURCE, kit.Keys(kit.MDB_HASH, h, kit.MDB_LIST), "")
|
|
|
|
f.scan(m, h, "")
|
|
}
|
|
return true
|
|
}
|
|
func (f *Frame) Close(m *ice.Message, arg ...string) bool {
|
|
return true
|
|
}
|
|
|
|
const (
|
|
STDIO = "stdio"
|
|
PS1 = "PS1"
|
|
PS2 = "PS2"
|
|
)
|
|
const (
|
|
SOURCE = "source"
|
|
TARGET = "target"
|
|
PROMPT = "prompt"
|
|
PRINTF = "printf"
|
|
SCREEN = "screen"
|
|
RETURN = "return"
|
|
)
|
|
|
|
func init() {
|
|
Index.Merge(&ice.Context{
|
|
Configs: map[string]*ice.Config{
|
|
SOURCE: {Name: SOURCE, Help: "加载脚本", Value: kit.Data(kit.MDB_SHORT, kit.MDB_NAME)},
|
|
PROMPT: {Name: PROMPT, Help: "命令提示", Value: kit.Data(
|
|
PS1, []interface{}{"\033[33;44m", kit.MDB_COUNT, "[", kit.MDB_TIME, "]", "\033[5m", TARGET, "\033[0m", "\033[44m", ">", "\033[0m ", "\033[?25h", "\033[32m"},
|
|
PS2, []interface{}{kit.MDB_COUNT, " ", TARGET, "> "},
|
|
)},
|
|
},
|
|
Commands: map[string]*ice.Command{
|
|
SOURCE: {Name: "source hash id limit offend auto", Help: "脚本解析", Action: map[string]*ice.Action{
|
|
mdb.REPEAT: {Name: "repeat", Help: "执行", Hand: func(m *ice.Message, arg ...string) {
|
|
m.Cmdy(SCREEN, m.Option(kit.MDB_TEXT))
|
|
m.ProcessInner()
|
|
}},
|
|
}, Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {
|
|
if len(arg) > 0 && kit.Ext(arg[0]) == "shy" { // 解析脚本
|
|
m.Starts(strings.Replace(arg[0], ".", "_", -1), arg[0], arg[0:]...)
|
|
return
|
|
}
|
|
|
|
if len(arg) == 0 { // 脚本列表
|
|
m.Fields(len(arg) == 0, "time,hash,name,count")
|
|
m.Cmdy(mdb.SELECT, SOURCE, "", mdb.HASH)
|
|
m.Sort(kit.MDB_NAME)
|
|
return
|
|
}
|
|
|
|
if m.Option(mdb.CACHE_OFFEND, kit.Select("0", arg, 3)); arg[0] == STDIO {
|
|
m.Option(mdb.CACHE_LIMIT, kit.Select("10", arg, 2))
|
|
} else {
|
|
m.Option(mdb.CACHE_LIMIT, kit.Select("-1", arg, 2))
|
|
}
|
|
|
|
// 命令列表
|
|
m.Fields(len(arg) == 1 || arg[1] == "", "time,id,text")
|
|
m.Cmdy(mdb.SELECT, SOURCE, kit.Keys(kit.MDB_HASH, arg[0]), mdb.LIST, kit.MDB_ID, arg[1:])
|
|
m.PushAction(mdb.REPEAT)
|
|
}},
|
|
TARGET: {Name: "target name 执行:button", Help: "当前模块", Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {
|
|
f := m.Target().Server().(*Frame)
|
|
m.Search(arg[0]+".", func(p *ice.Context, s *ice.Context, key string) { f.target = s })
|
|
f.prompt(m)
|
|
}},
|
|
PROMPT: {Name: "prompt arg 执行:button", Help: "命令提示", Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {
|
|
f := m.Target().Server().(*Frame)
|
|
f.ps1 = arg
|
|
f.prompt(m)
|
|
}},
|
|
PRINTF: {Name: "printf 执行:button text:textarea", Help: "输出显示", Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {
|
|
f := m.Target().Server().(*Frame)
|
|
f.printf(m, arg[0])
|
|
}},
|
|
SCREEN: {Name: "screen 执行:button text:textarea", Help: "输出命令", Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {
|
|
f := m.Target().Server().(*Frame)
|
|
for _, line := range kit.Split(arg[0], "\n", "\n", "\n") {
|
|
f.printf(m, line+"\n")
|
|
fmt.Fprintf(f.pipe, line+"\n")
|
|
m.Sleep("300ms")
|
|
}
|
|
m.Echo(f.last)
|
|
}},
|
|
RETURN: {Name: "return", Help: "结束脚本", Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) {
|
|
switch cb := m.Optionv(kit.Keycb(RETURN)).(type) {
|
|
case func():
|
|
cb()
|
|
}
|
|
}},
|
|
},
|
|
})
|
|
}
|