Files
indra/pkg/proc/cmds/marshal.go

239 lines
5.3 KiB
Go

package cmds
import (
"encoding"
"fmt"
path2 "github.com/indra-labs/indra/pkg/util/path"
"io"
"os"
"sort"
"strings"
"time"
"github.com/naoina/toml"
"github.com/indra-labs/indra/pkg/proc/opts/duration"
"github.com/indra-labs/indra/pkg/proc/opts/float"
"github.com/indra-labs/indra/pkg/proc/opts/integer"
"github.com/indra-labs/indra/pkg/proc/opts/list"
"github.com/indra-labs/indra/pkg/proc/opts/meta"
"github.com/indra-labs/indra/pkg/proc/opts/text"
"github.com/indra-labs/indra/pkg/proc/opts/toggle"
)
type Entry struct {
path path2.Path
name string
value interface{}
}
func (e Entry) String() string {
return fmt.Sprint(e.path, "/", e.name, "=", e.value)
}
type Entries []Entry
func (e Entries) Len() int {
return len(e)
}
func (e Entries) Less(i, j int) bool {
iPath, jPath := e[i].String(), e[j].String()
return iPath < jPath
}
func (e Entries) Swap(i, j int) {
e[i], e[j] = e[j], e[i]
}
func walk(p []string, v interface{}, in Entries) (o Entries) {
o = append(o, in...)
var parent []string
for i := range p {
parent = append(parent, p[i])
}
switch vv := v.(type) {
case map[string]interface{}:
for i := range vv {
switch vvv := vv[i].(type) {
case map[string]interface{}:
o = walk(append(parent, i), vvv, o)
default:
o = append(o, Entry{
path: append(parent, i),
name: i,
value: vv[i],
})
}
}
}
return
}
var _ encoding.TextMarshaler = &Command{}
func (c *Command) MarshalText() (text []byte, err error) {
c.ForEach(func(cmd *Command, depth int) bool {
if cmd == nil {
log.D.Ln("cmd empty")
return true
}
cfgNames := make([]string, 0, len(cmd.Configs))
for i := range cmd.Configs {
cfgNames = append(cfgNames, i)
}
if len(cfgNames) < 1 {
return true
}
if cmd.Name != "" {
var cmdPath string
current := cmd.Parent
for current != nil {
if current.Name != "" {
cmdPath = current.Name + "."
}
current = current.Parent
}
text = append(text,
[]byte("# "+cmdPath+cmd.Name+": "+
cmd.Description+"\n")...)
text = append(text,
[]byte("["+cmdPath+cmd.Name+"]"+"\n\n")...)
}
sort.Strings(cfgNames)
for _, i := range cfgNames {
md := cmd.Configs[i].Meta()
lq, rq := "", ""
st := cmd.Configs[i].String()
df := md.Default()
switch cmd.Configs[i].Type() {
case meta.Duration, meta.Text:
lq, rq = "\"", "\""
case meta.List:
lq, rq = "[ \"", "\" ]"
st = strings.ReplaceAll(st, ",", "\", \"")
df = strings.ReplaceAll(df, ",", "\", \"")
if st == "" {
lq, rq = "[ ", "]"
}
}
text = append(text,
[]byte("# "+i+" - "+md.Description()+
" - default: "+lq+df+rq+"\n")...)
text = append(text,
[]byte(i+" = "+lq+st+rq+"\n")...)
}
text = append(text, []byte("\n")...)
return true
}, 0, 0, c)
return
}
var _ encoding.TextUnmarshaler = &Command{}
func (c *Command) UnmarshalText(t []byte) (err error) {
var out interface{}
err = toml.Unmarshal(t, &out)
oo := walk([]string{}, out, []Entry{})
sort.Sort(oo)
for i := range oo {
op := c.GetOpt(oo[i].path)
if op != nil {
switch op.Type() {
case meta.Bool:
v := op.(*toggle.Opt)
if nv, ok := oo[i].value.(bool); ok {
v.FromValue(nv)
log.T.Ln("setting value of", oo[i].path,
"to", nv)
}
case meta.Duration:
v := op.(*duration.Opt)
if nv, ok := oo[i].value.(time.Duration); ok {
v.FromValue(nv)
log.T.Ln("setting value of", oo[i].path,
"to", nv)
}
case meta.Float:
v := op.(*float.Opt)
if nv, ok := oo[i].value.(float64); ok {
v.FromValue(nv)
log.T.Ln("setting value of", oo[i].path,
"to", nv)
}
case meta.Integer:
v := op.(*integer.Opt)
if nv, ok := oo[i].value.(int64); ok {
v.FromValue(nv)
log.T.Ln("setting value of", oo[i].path,
"to", nv)
}
case meta.List:
v := op.(*list.Opt)
nv, ok := oo[i].value.([]interface{})
var strings []string
for i := range nv {
strings = append(strings, nv[i].(string))
}
if ok {
v.FromValue(strings)
}
log.T.Ln("setting value of", oo[i].path, "to",
nv)
case meta.Text:
v := op.(*text.Opt)
if nv, ok := oo[i].value.(string); ok {
v.FromValue(nv)
log.T.Ln("setting value of", oo[i].path,
"to", nv)
}
default:
log.E.Ln("option type unknown:",
oo[i].path, op.Type())
}
} else {
log.D.Ln("option not found:", oo[i].path)
}
}
return nil
}
func (c *Command) LoadConfig() (err error) {
cfgFile := c.GetOpt(path2.Path{c.Name, "ConfigFile"})
var file io.Reader
if file, err = os.Open(cfgFile.Expanded()); err != nil {
log.T.F("creating config file at path: '%s'", cfgFile.Expanded())
// If no config found, create data dir and drop the default in
// place
return c.SaveConfig()
} else {
var all []byte
all, err = io.ReadAll(file)
err = c.UnmarshalText(all)
if log.E.Chk(err) {
return
}
}
return
}
func (c *Command) SaveConfig() (err error) {
dd := c.GetOpt(path2.Path{c.Name, "DataDir"})
if err = os.MkdirAll(dd.Expanded(), 0700); log.E.Chk(err) {
return err
}
var f *os.File
cfgFile := c.GetOpt(path2.From(c.Name + " configfile"))
f, err = os.OpenFile(cfgFile.Expanded(), os.O_RDWR|os.O_CREATE, 0666)
if log.E.Chk(err) {
return
}
var cf []byte
if cf, err = c.MarshalText(); log.E.Chk(err) {
return
}
_, err = f.Write(cf)
log.E.Chk(err)
return
}