195 lines
6.1 KiB
Go
195 lines
6.1 KiB
Go
package agent
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"aego/data"
|
|
"aego/format/aetext"
|
|
"aego/project"
|
|
"aego/scene/mutate"
|
|
)
|
|
|
|
func dataCommands() []*Command {
|
|
file := Arg{Name: "file", Type: "string", Doc: ".aedata or .aetable file", Required: true, Pos: true}
|
|
row := Arg{Name: "row", Type: "string", Doc: "row id, for a table"}
|
|
id := Arg{Name: "id", Type: "string", Doc: "row id", Required: true, Pos: true}
|
|
csv := Arg{Name: "csv", Type: "string", Doc: "csv file, relative to the project", Required: true, Pos: true}
|
|
field := Arg{Name: "path", Type: "string", Doc: "field, field.x or field[2]", Required: true, Pos: true}
|
|
return []*Command{
|
|
{Name: "data.get", Doc: "fields set in a data asset or a table row; omitted fields use the base or the type default", Args: []Arg{file, row}, Run: (*Session).dataGet},
|
|
{Name: "data.set", Doc: "set a field", Write: true, Args: []Arg{file, field, argValue, row}, Run: func(s *Session, a Args) (*Output, error) {
|
|
return s.dataTx(a, mutate.DataSet(a.Str("row"), a.Str("path"), a.Str("value")))
|
|
}},
|
|
{Name: "data.reset", Doc: "remove a field so it falls back to the base or the default", Write: true, Args: []Arg{file, field, row}, Run: func(s *Session, a Args) (*Output, error) {
|
|
return s.dataTx(a, mutate.DataReset(a.Str("row"), a.Str("path")))
|
|
}},
|
|
{Name: "data.base", Doc: "set or clear the base asset of a data asset", Write: true,
|
|
Args: []Arg{file, {Name: "asset", Type: "string", Doc: "base id or path, empty to clear", Pos: true}}, Run: func(s *Session, a Args) (*Output, error) {
|
|
return s.dataTx(a, mutate.DataBase(a.Str("asset")))
|
|
}},
|
|
{Name: "data.rows", Doc: "row ids of a table", Args: []Arg{file}, Run: (*Session).dataRows},
|
|
{Name: "data.add-row", Doc: "add a row with defaults", Write: true, Args: []Arg{file, id}, Run: func(s *Session, a Args) (*Output, error) {
|
|
return s.dataTx(a, mutate.RowAdd(a.Str("id")))
|
|
}},
|
|
{Name: "data.remove-row", Doc: "remove a row", Write: true, Args: []Arg{file, id}, Run: func(s *Session, a Args) (*Output, error) {
|
|
return s.dataTx(a, mutate.RowRemove(a.Str("id")))
|
|
}},
|
|
{Name: "data.rename-row", Doc: "change a row id", Write: true, Args: []Arg{file, id, {Name: "name", Type: "string", Doc: "new id", Required: true, Pos: true}}, Run: func(s *Session, a Args) (*Output, error) {
|
|
return s.dataTx(a, mutate.RowRename(a.Str("id"), a.Str("name")))
|
|
}},
|
|
{Name: "data.import", Doc: "replace the rows of a table with a csv file", Write: true, Args: []Arg{file, csv}, Run: (*Session).dataImport},
|
|
{Name: "data.export", Doc: "write the rows of a table to a csv file", Write: true, Args: []Arg{file, csv}, Run: (*Session).dataExport},
|
|
}
|
|
}
|
|
|
|
func (s *Session) dataDoc(a Args) (string, []byte, *mutate.DataDocument, error) {
|
|
rel, err := s.rel(a.Str("file"))
|
|
if err != nil {
|
|
return "", nil, nil, err
|
|
}
|
|
src, err := s.read(rel)
|
|
if err != nil {
|
|
return "", nil, nil, err
|
|
}
|
|
dd, err := mutate.LoadData(src, rel, s.env())
|
|
return rel, src, dd, err
|
|
}
|
|
|
|
func (s *Session) dataTx(a Args, ops ...mutate.Op) (*Output, error) {
|
|
rel, src, dd, err := s.dataDoc(a)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tx := mutate.Tx{Label: "Data", Ops: ops}
|
|
var res *mutate.Result
|
|
if a.Bool("dry-run") {
|
|
res, err = dd.DryRun(tx)
|
|
} else {
|
|
res, err = dd.Apply(tx)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out := &Output{DryRun: res.DryRun, Changes: res.Changes, Problems: res.Problems}
|
|
if !res.DryRun {
|
|
out.Files, err = s.save(rel, src, dd.Bytes())
|
|
}
|
|
return out, err
|
|
}
|
|
|
|
func values(fs []aetext.Field) map[string]string {
|
|
out := make(map[string]string, len(fs))
|
|
for _, f := range fs {
|
|
out[f.Name] = mutate.FormatValue(f.Value)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func (s *Session) dataGet(a Args) (*Output, error) {
|
|
_, _, dd, err := s.dataDoc(a)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
v := map[string]any{"type": dd.Type.FullName}
|
|
if !dd.Table() {
|
|
d := dd.Doc()
|
|
if !d.Base.IsZero() {
|
|
v["base"] = s.label(d.Base)
|
|
}
|
|
v["fields"] = values(d.Fields)
|
|
return &Output{Value: v}, nil
|
|
}
|
|
if !a.Has("row") {
|
|
return nil, errors.New("this is a table: pass --row; data rows lists them")
|
|
}
|
|
for _, r := range dd.TableDoc().Rows {
|
|
if r.ID == a.Str("row") {
|
|
v["row"], v["fields"] = r.ID, values(r.Fields)
|
|
return &Output{Value: v}, nil
|
|
}
|
|
}
|
|
return nil, errors.New("no row " + a.Str("row"))
|
|
}
|
|
|
|
func (s *Session) dataRows(a Args) (*Output, error) {
|
|
_, _, dd, err := s.dataDoc(a)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !dd.Table() {
|
|
return nil, errors.New("not a table")
|
|
}
|
|
var list []string
|
|
for _, r := range dd.TableDoc().Rows {
|
|
list = append(list, r.ID)
|
|
}
|
|
return &Output{Value: list}, nil
|
|
}
|
|
|
|
func tableCopy(t *data.TableDoc) *data.TableDoc {
|
|
out := &data.TableDoc{Type: t.Type, Rows: make([]data.TableRow, len(t.Rows))}
|
|
for i, r := range t.Rows {
|
|
fs := make([]aetext.Field, len(r.Fields))
|
|
for k, f := range r.Fields {
|
|
fs[k] = aetext.Field{Name: f.Name, Value: f.Value.Clone()}
|
|
}
|
|
out.Rows[i] = data.TableRow{ID: r.ID, Fields: fs}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func (s *Session) dataImport(a Args) (*Output, error) {
|
|
_, _, dd, err := s.dataDoc(a)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !dd.Table() {
|
|
return nil, errors.New("not a table")
|
|
}
|
|
csvRel, err := s.rel(a.Str("csv"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
src, err := s.read(csvRel)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
next, err := data.ImportCSV(s.Types.Schema(), tableCopy(dd.TableDoc()), src)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var ops []mutate.Op
|
|
for _, r := range dd.TableDoc().Rows {
|
|
ops = append(ops, mutate.RowRemove(r.ID))
|
|
}
|
|
for _, r := range next.Rows {
|
|
ops = append(ops, mutate.RowRestore(r.ID, r.Fields))
|
|
}
|
|
return s.dataTx(a, ops...)
|
|
}
|
|
|
|
func (s *Session) dataExport(a Args) (*Output, error) {
|
|
_, _, dd, err := s.dataDoc(a)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !dd.Table() {
|
|
return nil, errors.New("not a table")
|
|
}
|
|
csvRel, err := s.rel(a.Str("csv"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
b, err := data.ExportCSV(s.Types.Schema(), dd.TableDoc())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if a.Bool("dry-run") {
|
|
return &Output{DryRun: true, Notes: []string{"would write " + csvRel}}, nil
|
|
}
|
|
if err := project.WriteAtomic(s.Pipeline.Abs(csvRel), b); err != nil {
|
|
return nil, err
|
|
}
|
|
return &Output{Files: []string{csvRel}}, nil
|
|
}
|