[add] compute parser
This commit is contained in:
parent
a57d13e49e
commit
6ef7688e65
4 changed files with 415 additions and 5 deletions
152
widgets/component/compute.go
Normal file
152
widgets/component/compute.go
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
package component
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/yaoapp/gou"
|
||||
"github.com/yaoapp/kun/maps"
|
||||
)
|
||||
|
||||
// "$C(value)", "$C(props)", "$C(type)"}
|
||||
var defaults = []CArg{
|
||||
{IsExp: true, key: "value", value: nil},
|
||||
{IsExp: true, key: "props", value: nil},
|
||||
{IsExp: true, key: "type", value: nil},
|
||||
}
|
||||
|
||||
// Value compute value
|
||||
func (compute *Compute) Value(data maps.MapStr, sid string, global map[string]interface{}) (interface{}, error) {
|
||||
|
||||
if compute.Process == "" {
|
||||
return nil, fmt.Errorf("compute process is required")
|
||||
}
|
||||
|
||||
// Build-In handlers
|
||||
args := compute.GetArgs(data)
|
||||
if handler, has := hanlders[compute.Process]; has {
|
||||
return handler(args...)
|
||||
}
|
||||
|
||||
if !strings.Contains(compute.Process, ".") {
|
||||
return nil, fmt.Errorf("compute %s does not found", compute.Process)
|
||||
}
|
||||
|
||||
// Run process
|
||||
process, err := gou.ProcessOf(compute.Process, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := process.WithSID(sid).WithGlobal(global).Exec()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// GetArgs return args
|
||||
func (compute *Compute) GetArgs(data maps.MapStr) []interface{} {
|
||||
args := []interface{}{}
|
||||
for _, arg := range compute.Args {
|
||||
args = append(args, arg.Value(data))
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
// Value compute arg value
|
||||
func (arg CArg) Value(data maps.MapStr) interface{} {
|
||||
if !arg.IsExp {
|
||||
return arg.value
|
||||
}
|
||||
return data.Get(arg.key)
|
||||
}
|
||||
|
||||
// MarshalJSON Custom JSON parse
|
||||
func (compute Compute) MarshalJSON() ([]byte, error) {
|
||||
|
||||
if compute.Args == nil || len(compute.Args) == 0 || reflect.DeepEqual(compute.Args, defaults) {
|
||||
return jsoniter.Marshal(compute.Process)
|
||||
}
|
||||
|
||||
return jsoniter.Marshal(computeAlias(compute))
|
||||
}
|
||||
|
||||
// UnmarshalJSON Custom JSON parse
|
||||
func (compute *Compute) UnmarshalJSON(data []byte) error {
|
||||
|
||||
// allow null
|
||||
if data == nil || len(data) < 1 || (len(data) == 2 && data[0] == '"' && data[1] == '"') {
|
||||
*compute = Compute{Args: []CArg{}}
|
||||
return fmt.Errorf("Compute should be {} or string")
|
||||
}
|
||||
|
||||
switch data[0] {
|
||||
|
||||
case '[':
|
||||
*compute = Compute{Args: []CArg{}}
|
||||
return fmt.Errorf("Compute should be {} or string")
|
||||
|
||||
case '{': // json
|
||||
var new computeAlias
|
||||
err := jsoniter.Unmarshal(data, &new)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
new.Process = strings.TrimSpace(new.Process)
|
||||
*compute = Compute(new)
|
||||
return nil
|
||||
|
||||
default:
|
||||
compute.Process = strings.TrimSpace((strings.Trim(string(data), `"`)))
|
||||
compute.Args = defaults
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalJSON for JSON parse
|
||||
func (arg CArg) MarshalJSON() ([]byte, error) {
|
||||
if arg.IsExp {
|
||||
return []byte(fmt.Sprintf(`"$C(%s)"`, arg.key)), nil
|
||||
}
|
||||
|
||||
if v, ok := arg.value.(string); ok && strings.HasPrefix(v, "::") {
|
||||
return jsoniter.Marshal(fmt.Sprintf("\\%s", v))
|
||||
}
|
||||
|
||||
return jsoniter.Marshal(arg.value)
|
||||
}
|
||||
|
||||
// UnmarshalJSON for JSON parse
|
||||
func (arg *CArg) UnmarshalJSON(data []byte) error {
|
||||
|
||||
if data == nil || len(data) < 1 {
|
||||
*arg = CArg{value: nil, IsExp: false}
|
||||
return nil
|
||||
}
|
||||
|
||||
// "$C(value)", "$C(props)", "$C(type)"}
|
||||
if len(data) > 3 && data[0] == '"' && data[1] == '$' && data[2] == 'C' && data[3] == '(' {
|
||||
key := strings.TrimSpace(strings.TrimRight(strings.TrimLeft(string(data), `"$C(`), `)"`))
|
||||
*arg = CArg{key: key, IsExp: true}
|
||||
return nil
|
||||
|
||||
} else if len(data) > 4 && data[0] == '"' && data[1] == '\\' && data[2] == '\\' && data[3] == ':' && data[4] == ':' {
|
||||
|
||||
// ["$C(row.type)", "\\::", "$C(value)", "-", "$C(row.status)"]
|
||||
value := string(data[3 : len(data)-1])
|
||||
*arg = CArg{value: value, IsExp: false}
|
||||
return nil
|
||||
}
|
||||
|
||||
var v interface{}
|
||||
err := jsoniter.Unmarshal(data, &v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*arg = CArg{value: v, IsExp: false}
|
||||
return nil
|
||||
}
|
||||
190
widgets/component/compute_test.go
Normal file
190
widgets/component/compute_test.go
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
package component
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/yaoapp/gou/session"
|
||||
"github.com/yaoapp/kun/maps"
|
||||
)
|
||||
|
||||
func TestComputeUnmarshalJSON(t *testing.T) {
|
||||
|
||||
tests := testComputeData()
|
||||
|
||||
var compute Compute
|
||||
err := jsoniter.Unmarshal(tests["Trim"], &compute)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, "Trim", compute.Process)
|
||||
assert.Equal(t, true, compute.Args[0].IsExp)
|
||||
assert.Equal(t, "value", compute.Args[0].key)
|
||||
assert.Equal(t, nil, compute.Args[0].value)
|
||||
assert.Equal(t, true, compute.Args[1].IsExp)
|
||||
assert.Equal(t, "props", compute.Args[1].key)
|
||||
assert.Equal(t, nil, compute.Args[1].value)
|
||||
assert.Equal(t, true, compute.Args[2].IsExp)
|
||||
assert.Equal(t, "type", compute.Args[2].key)
|
||||
assert.Equal(t, nil, compute.Args[2].value)
|
||||
|
||||
err = jsoniter.Unmarshal(tests["Concat"], &compute)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, "Concat", compute.Process)
|
||||
assert.Equal(t, false, compute.Args[1].IsExp)
|
||||
assert.Equal(t, "::", compute.Args[1].value)
|
||||
assert.Equal(t, "", compute.Args[1].key)
|
||||
|
||||
err = jsoniter.Unmarshal(tests["Mapping"], &compute)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, "Mapping", compute.Process)
|
||||
assert.Equal(t, false, compute.Args[1].IsExp)
|
||||
assert.Equal(t, "checked", compute.Args[1].value.(map[string]interface{})["0"])
|
||||
assert.Equal(t, "", compute.Args[1].key)
|
||||
|
||||
err = jsoniter.Unmarshal(tests["MappingOnline"], &compute)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, "scripts.compute.MappingOnline", compute.Process)
|
||||
|
||||
err = jsoniter.Unmarshal(tests["Empty"], &compute)
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, "", compute.Process)
|
||||
|
||||
err = jsoniter.Unmarshal(tests["Error"], &compute)
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, "", compute.Process)
|
||||
}
|
||||
|
||||
func TestComputeMarshalJSON(t *testing.T) {
|
||||
|
||||
tests := testComputeData()
|
||||
|
||||
var compute Compute
|
||||
err := jsoniter.Unmarshal(tests["Trim"], &compute)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes, err := jsoniter.Marshal(compute)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, tests["Trim"], bytes)
|
||||
|
||||
err = jsoniter.Unmarshal(tests["Concat"], &compute)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, "Concat", compute.Process)
|
||||
bytes, err = jsoniter.Marshal(compute)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Contains(t, string(bytes), `Concat`)
|
||||
assert.Contains(t, string(bytes), `$C(value)`)
|
||||
assert.Contains(t, string(bytes), `\\::`)
|
||||
|
||||
err = jsoniter.Unmarshal(tests["Mapping"], &compute)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, "Mapping", compute.Process)
|
||||
bytes, err = jsoniter.Marshal(compute)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Contains(t, string(bytes), `Mapping`)
|
||||
assert.Contains(t, string(bytes), `curing`)
|
||||
|
||||
err = jsoniter.Unmarshal(tests["Empty"], &compute)
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, "", compute.Process)
|
||||
bytes, err = jsoniter.Marshal(compute)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, `""`, string(bytes))
|
||||
|
||||
err = jsoniter.Unmarshal(tests["Error"], &compute)
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, "", compute.Process)
|
||||
bytes, err = jsoniter.Marshal(compute)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, `""`, string(bytes))
|
||||
|
||||
}
|
||||
|
||||
func TestComputeValue(t *testing.T) {
|
||||
tests := testComputeData()
|
||||
|
||||
data := maps.MapStr{
|
||||
"value": " Concat-Test ",
|
||||
"row.type": "UnitTest",
|
||||
"row.status": "enabled",
|
||||
}
|
||||
|
||||
var compute Compute
|
||||
err := jsoniter.Unmarshal(tests["Concat"], &compute)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
id := session.ID()
|
||||
res, err := compute.Value(data, id, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, "UnitTest:: Concat-Test -enabled", res)
|
||||
|
||||
err = jsoniter.Unmarshal(tests["Trim"], &compute)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res, err = compute.Value(data, id, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, "Concat-Test", res)
|
||||
|
||||
compute = Compute{Process: "NotFound", Args: []CArg{}}
|
||||
res, err = compute.Value(data, id, nil)
|
||||
assert.Contains(t, err.Error(), "does not found")
|
||||
assert.Nil(t, res)
|
||||
}
|
||||
|
||||
func testComputeData() map[string][]byte {
|
||||
|
||||
return map[string][]byte{
|
||||
"Trim": []byte(`"Trim"`),
|
||||
|
||||
"Concat": []byte(`{
|
||||
"process": "Concat",
|
||||
"args": ["$C(row.type)", "\\::", "$C(value)", "-", "$C(row.status)"]
|
||||
}`),
|
||||
|
||||
"Mapping": []byte(` {
|
||||
"process": "Mapping",
|
||||
"args": [
|
||||
"$C(value)",
|
||||
{ "0": "checked", "1": "curing", "2": "cured" }
|
||||
]
|
||||
}`),
|
||||
|
||||
"MappingOnline": []byte(`{
|
||||
"process": "scripts.compute.MappingOnline",
|
||||
"args": ["$C(value)", "$C(props.mapping)"]
|
||||
}`),
|
||||
|
||||
"Empty": []byte(`""`),
|
||||
"Error": []byte("[]"),
|
||||
}
|
||||
}
|
||||
48
widgets/component/handlers.go
Normal file
48
widgets/component/handlers.go
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
package component
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var hanlders = map[string]ComputeHanlder{
|
||||
"Trim": Trim,
|
||||
"Concat": Concat,
|
||||
"QueryString": Trim,
|
||||
"ImagesView": Trim,
|
||||
"ImagesEdit": Trim,
|
||||
"Duration": Trim,
|
||||
"HumanDataTime": Trim,
|
||||
"Mapping": Trim,
|
||||
"Currency": Trim,
|
||||
}
|
||||
|
||||
// Trim string
|
||||
func Trim(args ...interface{}) (interface{}, error) {
|
||||
if len(args) < 1 {
|
||||
return nil, fmt.Errorf("Trim args[0] is required")
|
||||
}
|
||||
|
||||
if args[0] == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
v, ok := args[0].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Trim args[0] is not a string value")
|
||||
}
|
||||
|
||||
return strings.TrimSpace(v), nil
|
||||
}
|
||||
|
||||
// Concat string
|
||||
func Concat(args ...interface{}) (interface{}, error) {
|
||||
res := ""
|
||||
for _, arg := range args {
|
||||
if arg == nil {
|
||||
continue
|
||||
}
|
||||
res = fmt.Sprintf("%v%v", res, arg)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
|
@ -2,11 +2,12 @@ package component
|
|||
|
||||
// DSL the component DSL
|
||||
type DSL struct {
|
||||
Bind string `json:"bind,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
In string `json:"in,omitempty"`
|
||||
Out string `json:"out,omitempty"`
|
||||
Props PropsDSL `json:"props,omitempty"`
|
||||
Bind string `json:"bind,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
In string `json:"in,omitempty"`
|
||||
Out string `json:"out,omitempty"`
|
||||
Compute *Compute `json:"compute,omitempty"`
|
||||
Props PropsDSL `json:"props,omitempty"`
|
||||
}
|
||||
|
||||
// Actions the actions
|
||||
|
|
@ -45,6 +46,25 @@ type PropsDSL map[string]interface{}
|
|||
// ParamsDSL action params
|
||||
type ParamsDSL map[string]interface{}
|
||||
|
||||
// Compute process
|
||||
type Compute struct {
|
||||
Process string `json:"process"`
|
||||
Args []CArg `json:"args,omitempty"`
|
||||
}
|
||||
|
||||
// computeAlias for JSON UnmarshalJSON
|
||||
type computeAlias Compute
|
||||
|
||||
// CArg compute interface{}
|
||||
type CArg struct {
|
||||
IsExp bool
|
||||
key string
|
||||
value interface{}
|
||||
}
|
||||
|
||||
// ComputeHanlder computeHanlder
|
||||
type ComputeHanlder func(args ...interface{}) (interface{}, error)
|
||||
|
||||
// CloudPropsDSL the cloud props
|
||||
type CloudPropsDSL struct {
|
||||
Xpath string `json:"xpath,omitempty"`
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue