forked from x/icebergs
418 lines
12 KiB
Go
418 lines
12 KiB
Go
package ice
|
||
|
||
import (
|
||
"encoding/json"
|
||
"io"
|
||
"net/http"
|
||
"strings"
|
||
"sync/atomic"
|
||
"time"
|
||
|
||
kit "shylinux.com/x/toolkits"
|
||
"shylinux.com/x/toolkits/logs"
|
||
"shylinux.com/x/toolkits/task"
|
||
)
|
||
|
||
type List = []Any
|
||
type Any = interface{}
|
||
type Map = map[string]Any
|
||
type Maps = map[string]string
|
||
type Handler func(m *Message, arg ...string)
|
||
type Messages = map[string]*Message
|
||
type Contexts = map[string]*Context
|
||
type Commands = map[string]*Command
|
||
type Actions = map[string]*Action
|
||
type Configs = map[string]*Config
|
||
type Caches = map[string]*Cache
|
||
|
||
type Cache struct {
|
||
Name string
|
||
Help string
|
||
Value string
|
||
}
|
||
type Config struct {
|
||
Name string
|
||
Help string
|
||
Value Any
|
||
}
|
||
type Action struct {
|
||
Name string
|
||
Help string
|
||
Icon string
|
||
Role string
|
||
Style string
|
||
Hand Handler
|
||
List List
|
||
}
|
||
type Command struct {
|
||
Name string
|
||
Help string
|
||
Icon string
|
||
Role string
|
||
Actions Actions
|
||
Hand Handler
|
||
RawHand Any
|
||
List List
|
||
Meta Map
|
||
}
|
||
type Context struct {
|
||
Name string
|
||
Help string
|
||
|
||
Caches Caches
|
||
Configs Configs
|
||
Commands Commands
|
||
|
||
contexts Contexts
|
||
context *Context
|
||
root *Context
|
||
server Server
|
||
|
||
id int32
|
||
}
|
||
type Server interface {
|
||
Begin(m *Message, arg ...string)
|
||
Start(m *Message, arg ...string)
|
||
Close(m *Message, arg ...string)
|
||
}
|
||
|
||
func (c *Context) Cap(key string, arg ...Any) string {
|
||
kit.If(len(arg) > 0, func() { c.Caches[key].Value = kit.Format(arg[0]) })
|
||
return c.Caches[key].Value
|
||
}
|
||
func (c *Context) Cmd(m *Message, key string, arg ...string) *Message {
|
||
return c._command(m, c.Commands[key], key, arg...)
|
||
}
|
||
func (c *Context) Prefix(arg ...string) string {
|
||
return kit.Keys(c.Cap(CTX_FOLLOW), arg)
|
||
}
|
||
func (c *Context) Server() Server { return c.server }
|
||
func (c *Context) ID() int32 { return atomic.AddInt32(&c.id, 1) }
|
||
func (c *Context) Register(s *Context, x Server, cmd ...string) *Context {
|
||
kit.For(cmd, func(cmd string) {
|
||
if s, ok := Info.Index[cmd].(*Context); ok {
|
||
panic(kit.Format("%s %s registed by %v", ErrWarn, cmd, s.Name))
|
||
}
|
||
Info.Index[cmd] = s
|
||
})
|
||
kit.If(c.contexts == nil, func() { c.contexts = Contexts{} })
|
||
c.contexts[s.Name] = s
|
||
s.root = c.root
|
||
s.context = c
|
||
s.server = x
|
||
return s
|
||
}
|
||
func (c *Context) MergeCommands(Commands Commands) *Context {
|
||
for _, cmd := range Commands {
|
||
if cmd.Hand == nil && cmd.RawHand == nil {
|
||
cmd.RawHand = logs.FileLines(2)
|
||
}
|
||
}
|
||
configs := Configs{}
|
||
for k := range Commands {
|
||
configs[k] = &Config{Value: kit.Data()}
|
||
}
|
||
return c.Merge(&Context{Commands: Commands, Configs: configs})
|
||
}
|
||
func (c *Context) Merge(s *Context) *Context {
|
||
kit.If(c.Commands == nil, func() { c.Commands = Commands{} })
|
||
kit.If(c.Commands[CTX_INIT] == nil, func() { c.Commands[CTX_INIT] = &Command{Hand: func(m *Message, arg ...string) { Info.Load(m) }} })
|
||
kit.If(c.Commands[CTX_EXIT] == nil, func() { c.Commands[CTX_EXIT] = &Command{Hand: func(m *Message, arg ...string) { Info.Save(m) }} })
|
||
merge := func(pre *Command, init bool, key string, cmd *Command, cb Handler) {
|
||
if cb == nil {
|
||
return
|
||
}
|
||
last := pre.Hand
|
||
pre.Hand = func(m *Message, arg ...string) {
|
||
kit.If(init, func() { last(m, arg...) })
|
||
defer kit.If(!init, func() { last(m, arg...) })
|
||
_key, _cmd := m._key, m._cmd
|
||
defer func() { m._key, m._cmd = _key, _cmd }()
|
||
m._key, m._cmd = key, cmd
|
||
cb(m, arg...)
|
||
}
|
||
}
|
||
for key, cmd := range s.Commands {
|
||
if pre, ok := c.Commands[key]; ok && s != c {
|
||
switch key {
|
||
case CTX_INIT:
|
||
merge(pre, true, key, cmd, cmd.Hand)
|
||
continue
|
||
case CTX_EXIT:
|
||
merge(pre, false, key, cmd, cmd.Hand)
|
||
continue
|
||
}
|
||
}
|
||
c.Commands[key] = cmd
|
||
kit.If(cmd.Meta == nil, func() { cmd.Meta = kit.Dict() })
|
||
for sub, action := range cmd.Actions {
|
||
if pre, ok := c.Commands[sub]; ok && s == c {
|
||
kit.Switch(sub,
|
||
CTX_INIT, func() { merge(pre, true, key, cmd, action.Hand) },
|
||
CTX_EXIT, func() { merge(pre, false, key, cmd, action.Hand) },
|
||
)
|
||
}
|
||
if s == c {
|
||
for _, h := range Info.merges {
|
||
switch h := h.(type) {
|
||
case func(c *Context, key string, cmd *Command, sub string, action *Action) Handler:
|
||
merge(c.Commands[CTX_INIT], true, key, cmd, h(c, key, cmd, sub, action))
|
||
case func(c *Context, key string, cmd *Command, sub string, action *Action):
|
||
h(c, key, cmd, sub, action)
|
||
}
|
||
}
|
||
}
|
||
kit.If(sub == SELECT, func() { cmd.Name = kit.Select(action.Name, cmd.Name) })
|
||
kit.If(sub == SELECT, func() { cmd.Help = kit.Select(action.Help, cmd.Help) })
|
||
if help := kit.Split(action.Help, " ::"); len(help) > 0 {
|
||
if kit.Value(cmd.Meta, kit.Keys(CTX_TRANS, strings.TrimPrefix(sub, "_")), help[0]); len(help) > 1 {
|
||
kit.Value(cmd.Meta, kit.Keys(CTX_TITLE, sub), help[1])
|
||
}
|
||
}
|
||
kit.Value(cmd.Meta, kit.Keys(CTX_ICONS, sub), action.Icon)
|
||
if action.Hand == nil {
|
||
continue
|
||
}
|
||
kit.If(action.List == nil, func() { action.List = SplitCmd(action.Name, nil) })
|
||
kit.If(len(action.List) > 0, func() { cmd.Meta[sub] = action.List })
|
||
}
|
||
kit.If(strings.HasPrefix(cmd.Name, LIST), func() { cmd.Name = strings.Replace(cmd.Name, LIST, key, 1) })
|
||
kit.If(cmd.List == nil, func() { cmd.List = SplitCmd(cmd.Name, cmd.Actions) })
|
||
}
|
||
kit.If(c.Configs == nil, func() { c.Configs = Configs{} })
|
||
for k, v := range s.Configs {
|
||
if c.Configs[k] == nil || c.Configs[k].Value == nil {
|
||
c.Configs[k] = v
|
||
}
|
||
}
|
||
kit.If(c.Caches == nil, func() { c.Caches = Caches{} })
|
||
return c
|
||
}
|
||
func (c *Context) Begin(m *Message, arg ...string) *Context {
|
||
kit.If(c.Caches == nil, func() { c.Caches = Caches{} })
|
||
c.Caches[CTX_FOLLOW] = &Cache{Value: c.Name}
|
||
kit.If(c.context != nil && c.context != Index, func() { c.Cap(CTX_FOLLOW, c.context.Prefix(c.Name)) })
|
||
kit.If(c.server != nil, func() { c.server.Begin(m, arg...) })
|
||
return c.Merge(c)
|
||
}
|
||
func (c *Context) Start(m *Message, arg ...string) {
|
||
m.Log(CTX_START, c.Prefix(), logs.FileLineMeta(2))
|
||
kit.If(c.server != nil, func() { c.server.Start(m, arg...) })
|
||
}
|
||
func (c *Context) Close(m *Message, arg ...string) {
|
||
m.Log(CTX_CLOSE, c.Prefix(), logs.FileLineMeta(2))
|
||
kit.If(c.server != nil, func() { c.server.Close(m, arg...) })
|
||
}
|
||
|
||
type Message struct {
|
||
time time.Time
|
||
code int
|
||
|
||
_data Map
|
||
_meta map[string][]string
|
||
lock task.Lock
|
||
|
||
root *Message
|
||
message *Message
|
||
|
||
_source string
|
||
_target string
|
||
source *Context
|
||
target *Context
|
||
_cmd *Command
|
||
_key string
|
||
_sub string
|
||
|
||
W http.ResponseWriter
|
||
R *http.Request
|
||
O io.Writer
|
||
I io.Reader
|
||
}
|
||
|
||
func (m *Message) Time(arg ...string) string {
|
||
t := m.time
|
||
if len(arg) > 0 {
|
||
if d, e := time.ParseDuration(arg[0]); e == nil {
|
||
t, arg = t.Add(d), arg[1:]
|
||
}
|
||
}
|
||
return t.Format(kit.Select(MOD_TIME, arg, 0))
|
||
}
|
||
func (m *Message) Message() *Message { return m.message }
|
||
func (m *Message) Source() *Context { return m.source }
|
||
func (m *Message) Target() *Context { return m.target }
|
||
func (m *Message) _fileline() string {
|
||
switch m.target.Name {
|
||
case MDB, AAA, GDB:
|
||
return m._source
|
||
default:
|
||
return m._target
|
||
}
|
||
}
|
||
func (m *Message) Spawn(arg ...Any) *Message {
|
||
msg := &Message{time: time.Now(), code: int(m.target.root.ID()),
|
||
_meta: map[string][]string{}, _data: Map{}, message: m, root: m.root,
|
||
_source: logs.FileLine(2), source: m.target, target: m.target, _cmd: m._cmd, _key: m._key, _sub: m._sub,
|
||
W: m.W, R: m.R, O: m.O, I: m.I,
|
||
}
|
||
for _, val := range arg {
|
||
switch val := val.(type) {
|
||
case []byte:
|
||
if m.WarnNotValid(json.Unmarshal(val, &msg._meta), string(val)) {
|
||
m.Debug(m.FormatStack(1, 100))
|
||
}
|
||
case Option:
|
||
msg.Option(val.Name, val.Value)
|
||
case Maps:
|
||
kit.For(val, func(k, v string) { msg.Option(k, v) })
|
||
case Map:
|
||
kit.For(kit.KeyValue(nil, "", val), func(k string, v Any) { msg.Option(k, v) })
|
||
case *Context:
|
||
msg.target = val
|
||
case *Command:
|
||
msg._cmd = val
|
||
case string:
|
||
msg._key = val
|
||
case http.ResponseWriter:
|
||
msg.W = val
|
||
case *http.Request:
|
||
msg.R = val
|
||
}
|
||
}
|
||
return msg
|
||
}
|
||
func (m *Message) Start(key string, arg ...string) *Message {
|
||
return m.Search(key+PT, func(p *Context, s *Context) {
|
||
m.Cmd(ROUTINE, CREATE, kit.Select(m.Prefix(), key), func() { s.Start(m.Spawn(s), arg...) })
|
||
})
|
||
}
|
||
func (m *Message) Travel(cb Any) *Message {
|
||
target, cmd, key := m.target, m._cmd, m._key
|
||
defer func() { m.target, m._cmd, m._key = target, cmd, key }()
|
||
list := []*Context{m.root.target}
|
||
for i := 0; i < len(list); i++ {
|
||
switch cb := cb.(type) {
|
||
case func(*Context, *Context):
|
||
cb(list[i].context, list[i])
|
||
case func(*Context, *Context, string, *Command):
|
||
m.target = list[i]
|
||
kit.For(kit.SortedKey(list[i].Commands), func(k string) {
|
||
m._cmd, m._key = list[i].Commands[k], k
|
||
cb(list[i].context, list[i], k, list[i].Commands[k])
|
||
})
|
||
case func(*Context, *Context, string, *Config):
|
||
m.target = list[i]
|
||
kit.For(kit.SortedKey(list[i].Configs), func(k string) { cb(list[i].context, list[i], k, list[i].Configs[k]) })
|
||
default:
|
||
m.ErrorNotImplement(cb)
|
||
}
|
||
kit.For(kit.SortedKey(list[i].contexts), func(k string) { list = append(list, list[i].contexts[k]) })
|
||
}
|
||
return m
|
||
}
|
||
func (m *Message) Search(key string, cb Any) *Message {
|
||
if key == "" {
|
||
return m
|
||
}
|
||
_target, _key, _cmd := m.target, m._key, m._cmd
|
||
defer func() { m.target, m._key, m._cmd = _target, _key, _cmd }()
|
||
p := m.target.root
|
||
if key = strings.TrimPrefix(key, "ice."); key == PT {
|
||
p, key = m.target, ""
|
||
} else if key == ".." {
|
||
p, key = m.target.context, ""
|
||
} else if key == "..." {
|
||
p, key = m.target.root, ""
|
||
} else if strings.Contains(key, PT) {
|
||
ls := strings.Split(key, PT)
|
||
for _, p = range []*Context{m.target.root, m.target, m.source} {
|
||
if p == nil {
|
||
continue
|
||
}
|
||
for _, k := range ls[:len(ls)-1] {
|
||
if p = p.contexts[k]; p == nil {
|
||
break
|
||
}
|
||
}
|
||
if p != nil {
|
||
break
|
||
}
|
||
}
|
||
if p == nil {
|
||
return m
|
||
}
|
||
key = ls[len(ls)-1]
|
||
} else if ctx, ok := Info.Index[key].(*Context); ok {
|
||
p = ctx
|
||
} else {
|
||
p = m.target
|
||
}
|
||
switch cb := cb.(type) {
|
||
case func(key string, cmd *Command):
|
||
if cmd, ok := p.Commands[key]; ok {
|
||
m.target, m._key, m._cmd = p, key, cmd
|
||
cb(key, cmd)
|
||
}
|
||
case func(p *Context, s *Context, key string, cmd *Command):
|
||
for _, p := range []*Context{p, m.target, m.source} {
|
||
for s := p; s != nil; s = s.context {
|
||
if cmd, ok := s.Commands[key]; ok {
|
||
func() {
|
||
_target, _key := m.target, m._key
|
||
defer func() { m.target, m._key = _target, _key }()
|
||
m.target, m._key = s, key
|
||
cb(s.context, s, key, cmd)
|
||
}()
|
||
return m
|
||
}
|
||
}
|
||
}
|
||
case func(p *Context, s *Context, key string, conf *Config):
|
||
for _, p := range []*Context{p, m.target, m.source} {
|
||
for s := p; s != nil; s = s.context {
|
||
if cmd, ok := s.Configs[key]; ok {
|
||
cb(s.context, s, key, cmd)
|
||
return m
|
||
}
|
||
}
|
||
}
|
||
case func(p *Context, s *Context):
|
||
cb(p.context, p)
|
||
default:
|
||
m.ErrorNotImplement(cb)
|
||
}
|
||
return m
|
||
}
|
||
func (m *Message) Design(action Any, help string, input ...Any) {
|
||
list := kit.List()
|
||
for _, input := range input {
|
||
switch input := input.(type) {
|
||
case string:
|
||
list = append(list, SplitCmd("action "+input, nil)...)
|
||
case Map:
|
||
if kit.Format(input[TYPE]) != "" && kit.Format(input[NAME]) != "" {
|
||
list = append(list, input)
|
||
continue
|
||
}
|
||
kit.For(kit.KeyValue(nil, "", input), func(k string, v Any) {
|
||
list = append(list, kit.Dict(NAME, k, TYPE, TEXT, VALUE, v))
|
||
})
|
||
default:
|
||
m.ErrorNotImplement(input)
|
||
}
|
||
}
|
||
if k := kit.Format(action); k == LIST {
|
||
m._cmd.Meta[k], m._cmd.List = list, list
|
||
if help != "" {
|
||
kit.Value(m._cmd.Meta, kit.Keys(CTX_TRANS, k), help)
|
||
}
|
||
} else if a, ok := m._cmd.Actions[k]; ok {
|
||
m._cmd.Meta[k], a.List = list, list
|
||
if help != "" {
|
||
kit.Value(m._cmd.Meta, kit.Keys(CTX_TRANS, k), help)
|
||
}
|
||
}
|
||
}
|
||
func (m *Message) Actions(key string) *Action { return m._cmd.Actions[key] }
|
||
func (m *Message) Commands(key string) *Command { return m.Target().Commands[kit.Select(m._key, key)] }
|