diff --git a/go.mod b/go.mod index aeaf7413..8ee143bf 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.20 require ( github.com/PuerkitoBio/goquery v1.8.1 + github.com/antonmedv/expr v1.15.4 github.com/blang/semver v3.5.1+incompatible github.com/caarlos0/env/v6 v6.10.1 github.com/dchest/captcha v1.0.0 @@ -26,6 +27,7 @@ require ( github.com/yaoapp/kun v0.9.0 github.com/yaoapp/xun v0.9.0 golang.org/x/crypto v0.15.0 + golang.org/x/net v0.18.0 golang.org/x/text v0.14.0 gopkg.in/yaml.v3 v3.0.1 rogchap.com/v8go v0.9.0 @@ -107,7 +109,6 @@ require ( golang.org/x/arch v0.6.0 // indirect golang.org/x/image v0.14.0 // indirect golang.org/x/mod v0.14.0 // indirect - golang.org/x/net v0.18.0 // indirect golang.org/x/oauth2 v0.14.0 // indirect golang.org/x/sync v0.5.0 // indirect golang.org/x/sys v0.14.0 // indirect diff --git a/go.sum b/go.sum index c5945c76..2a90307c 100644 --- a/go.sum +++ b/go.sum @@ -5,6 +5,8 @@ github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2/go.mod h1:VSw github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss= github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= +github.com/antonmedv/expr v1.15.4 h1:CrNads8WDnDVJNWt/FeUINBO+vDNjurEwT7SoQN132o= +github.com/antonmedv/expr v1.15.4/go.mod h1:0E/6TxnOlRNp81GMzX9QfDPAmHo2Phg00y4JUv1ihsE= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= diff --git a/sui/core/data.go b/sui/core/data.go index 862c4174..88876546 100644 --- a/sui/core/data.go +++ b/sui/core/data.go @@ -2,209 +2,81 @@ package core import ( "fmt" + "regexp" "strings" - jsoniter "github.com/json-iterator/go" + "github.com/antonmedv/expr" + "github.com/antonmedv/expr/ast" + "github.com/antonmedv/expr/vm" "github.com/yaoapp/gou/process" - "github.com/yaoapp/kun/any" ) -// Data get the data -func (page *Page) Data(request *Request) (map[string]interface{}, map[string]interface{}, error) { +var stmtRe = regexp.MustCompile(`\{\{([^}]+)\}\}`) - setting := map[string]interface{}{ - "title": strings.ToUpper(page.Name), - } +// Data data for the template +type Data map[string]interface{} - if page.Codes.DATA.Code != "" { - err := jsoniter.UnmarshalFromString(page.Codes.DATA.Code, &setting) - if err != nil { - return nil, nil, err - } - } - return nil, setting, nil +var functions = map[string]*ast.Function{} + +var options = []expr.Option{ + expr.Function("P_", _process), + expr.AllowUndefinedVariables(), } -// Exec get the data -func (page *Page) Exec(request *Request) (map[string]interface{}, error) { +// New create a new expression +func (data Data) New(stmt string) (*vm.Program, error) { + stmt = strings.TrimSpace(strings.TrimRight(strings.TrimLeft(stmt, "{{ "), "}}")) + return expr.Compile(stmt, append([]expr.Option{expr.Env(data)}, options...)...) +} - if page.Codes.DATA.Code == "" { - return map[string]interface{}{}, nil +// Exec exec statement for the template +func (data Data) Exec(stmt string) (interface{}, error) { + program, err := data.New(stmt) + if err != nil { + return nil, err + } + return expr.Run(program, data) +} + +// ExecString exec statement for the template +func (data Data) ExecString(stmt string) (string, error) { + + res, err := data.Exec(stmt) + if err != nil { + return "", err } - data := map[string]interface{}{} - err := jsoniter.UnmarshalFromString(page.Codes.DATA.Code, &data) + if res == nil { + return "", nil + } + + if v, ok := res.(string); ok { + return v, nil + } + return fmt.Sprintf("%v", res), nil +} + +func _process(args ...any) (interface{}, error) { + + if len(args) < 1 { + return nil, fmt.Errorf("process should have at least one parameter") + } + + name, ok := args[0].(string) + if !ok { + return nil, fmt.Errorf("process function only accept string") + } + + args = append([]any{}, args[1:]...) + process, err := process.Of(name, args...) if err != nil { return nil, err } - err = page.execMap(request, data) + res, err := process.Exec() if err != nil { return nil, err } - return data, nil -} - -func (page *Page) execMap(r *Request, m map[string]interface{}) error { - - for key, value := range m { - - if strings.HasPrefix(key, "$") { - res, err := page.call(r, value) - if err != nil { - return err - } - newKey := key[1:] - m[newKey] = res - delete(m, key) - continue - } - - res, err := page.execValue(r, value) - if err != nil { - return err - } - m[key] = res - - } - - return nil -} - -func (page *Page) execValue(r *Request, value interface{}) (interface{}, error) { - switch v := value.(type) { - case string: - if strings.HasPrefix(v, "$") { - return page.call(r, strings.TrimLeft(v, "$")) - } - return v, nil - - case []interface{}: - for i, item := range v { - res, err := page.execValue(r, item) - if err != nil { - return nil, err - } - v[i] = res - } - return v, nil - - case []string: - interfaceSlice := make([]interface{}, len(v)) - for i, item := range v { - interfaceSlice[i] = item - } - return page.execValue(r, interfaceSlice) - - case map[string]interface{}: - - if _, ok := v["process"].(string); ok { - if call, _ := v["__exec"].(bool); call { - res, err := page.call(r, v) - if err != nil { - return nil, err - } - return res, nil - } - } - - err := page.execMap(r, v) - if err != nil { - return nil, err - } - return v, nil - - default: - return v, nil - } -} - -func (page *Page) call(r *Request, p interface{}) (interface{}, error) { - - processName := "" - processArgs := []interface{}{r} - switch v := p.(type) { - case string: - processName = v - break - - case map[string]interface{}: - if name, ok := v["process"].(string); ok { - processName = name - } - - if args, ok := v["args"].([]interface{}); ok { - args, err := page.parseArgs(r, args) - if err != nil { - return nil, err - } - processArgs = append(args, processArgs...) - } - } - - if processName == "" { - return nil, fmt.Errorf("process name is empty") - } - - process, err := process.Of(processName, processArgs...) - if err != nil { - return nil, err - } - - return process.Exec() -} - -func (page *Page) parseArgs(r *Request, args []interface{}) ([]interface{}, error) { - - data := any.MapOf(map[string]interface{}{ - "param": r.Params, - "query": r.Query, - "payload": map[string]interface{}{}, - "header": r.Headers, - "theme": r.Theme, - "locale": r.Locale, - }).Dot() - - for i, arg := range args { - switch v := arg.(type) { - - case string: - if strings.HasPrefix(v, "$") { - key := strings.TrimLeft(v, "$") - args[i] = key - if data.Has(key) { - v := data.Get(key) - if strings.HasPrefix(key, "query.") || strings.HasPrefix(key, "header.") { - switch arg := v.(type) { - case []interface{}: - if len(arg) == 1 { - args[i] = arg[0] - } - case []string: - if len(arg) == 1 { - args[i] = arg[0] - } - } - } - } - } - - case []interface{}: - res, err := page.parseArgs(r, v) - if err != nil { - return nil, err - } - args[i] = res - - case map[string]interface{}: - res, err := page.parseArgs(r, []interface{}{v}) - if err != nil { - return nil, err - } - args[i] = res[0] - } - } - - return args, nil + return res, nil } diff --git a/sui/core/editor.go b/sui/core/editor.go index 012d03c5..c9d41615 100644 --- a/sui/core/editor.go +++ b/sui/core/editor.go @@ -113,6 +113,11 @@ func (page *Page) EditorRender(request *Request) (*ResponseEditorRender, error) return res, nil } +// Render render for the html +func (page *Page) Render(html string, data map[string]interface{}, warnings []string) (string, error) { + return html, nil +} + // EditorPageSource get the editor page source code func (page *Page) EditorPageSource() SourceData { return SourceData{ diff --git a/sui/core/page.go b/sui/core/page.go index c4b4f1a2..04c9b76f 100644 --- a/sui/core/page.go +++ b/sui/core/page.go @@ -1,6 +1,10 @@ package core -import jsoniter "github.com/json-iterator/go" +import ( + "strings" + + jsoniter "github.com/json-iterator/go" +) // Get get the base info func (page *Page) Get() *Page { @@ -18,3 +22,34 @@ func (page *Page) GetConfig() *PageConfig { } return page.Config } + +// Data get the data +func (page *Page) Data(request *Request) (Data, map[string]interface{}, error) { + + setting := map[string]interface{}{ + "title": strings.ToUpper(page.Name), + } + + if page.Codes.DATA.Code != "" { + err := jsoniter.UnmarshalFromString(page.Codes.DATA.Code, &setting) + if err != nil { + return nil, nil, err + } + } + return nil, setting, nil +} + +// Exec get the data +func (page *Page) Exec(request *Request) (Data, error) { + + if page.Codes.DATA.Code == "" { + return map[string]interface{}{}, nil + } + + data, err := request.ExecString(page.Codes.DATA.Code) + if err != nil { + return nil, err + } + + return data, nil +} diff --git a/sui/core/data_test.go b/sui/core/page_test.go similarity index 72% rename from sui/core/data_test.go rename to sui/core/page_test.go index a39d7878..f032334b 100644 --- a/sui/core/data_test.go +++ b/sui/core/page_test.go @@ -50,40 +50,38 @@ func testPage(t *testing.T) *Page { File: "test.html", Code: `