Merge pull request #739 from trheyi/main
[feat] add SUI template.render process
This commit is contained in:
commit
8c44f63a3d
8 changed files with 219 additions and 5 deletions
|
|
@ -399,9 +399,6 @@ func (r *Request) scriptGuardExec(c *gin.Context, name string, args []interface{
|
||||||
message := err.Error()
|
message := err.Error()
|
||||||
if strings.HasPrefix(message, "Exception|") {
|
if strings.HasPrefix(message, "Exception|") {
|
||||||
parts := strings.Split(message, ": ")
|
parts := strings.Split(message, ": ")
|
||||||
|
|
||||||
fmt.Println(parts)
|
|
||||||
|
|
||||||
if len(parts) > 1 {
|
if len(parts) > 1 {
|
||||||
codestr := strings.TrimPrefix(parts[0], "Exception|")
|
codestr := strings.TrimPrefix(parts[0], "Exception|")
|
||||||
message := parts[1]
|
message := parts[1]
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,15 @@ func init() {
|
||||||
process.RegisterGroup("sui", map[string]process.Handler{
|
process.RegisterGroup("sui", map[string]process.Handler{
|
||||||
"setting": Setting,
|
"setting": Setting,
|
||||||
|
|
||||||
"render": Render,
|
"render": Render, // do not use this in script or flow, this is an internal method. Use the template.render instead
|
||||||
"run": Run,
|
"run": Run, // do not use this in script or flow, this is an internal method. Use the template.run instead
|
||||||
|
|
||||||
"template.get": TemplateGet,
|
"template.get": TemplateGet,
|
||||||
"template.find": TemplateFind,
|
"template.find": TemplateFind,
|
||||||
"template.asset": TemplateAsset,
|
"template.asset": TemplateAsset,
|
||||||
"template.assetupload": TemplateAssetUpload,
|
"template.assetupload": TemplateAssetUpload,
|
||||||
|
"template.render": TemplateRender,
|
||||||
|
// "template.run": TemplateRun,
|
||||||
|
|
||||||
"locale.get": LocaleGet,
|
"locale.get": LocaleGet,
|
||||||
"theme.get": ThemeGet,
|
"theme.get": ThemeGet,
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,12 @@ package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
jsoniter "github.com/json-iterator/go"
|
||||||
"github.com/yaoapp/gou/process"
|
"github.com/yaoapp/gou/process"
|
||||||
|
"github.com/yaoapp/kun/exception"
|
||||||
"github.com/yaoapp/yao/sui/core"
|
"github.com/yaoapp/yao/sui/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -115,3 +118,90 @@ func (r *Request) renderHTML(c *core.Cache, name string, html string, data core.
|
||||||
|
|
||||||
return html, nil
|
return html, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TemplateRender render the template asset
|
||||||
|
func TemplateRender(process *process.Process) interface{} {
|
||||||
|
process.ValidateArgNums(4)
|
||||||
|
sui := get(process)
|
||||||
|
tmpl, err := sui.GetTemplate(process.ArgsString(1))
|
||||||
|
if err != nil {
|
||||||
|
exception.New(err.Error(), 500).Throw()
|
||||||
|
}
|
||||||
|
|
||||||
|
opt := process.ArgsMap(4, map[string]interface{}{})
|
||||||
|
buildOptionData, ok := opt["data"].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
buildOptionData = map[string]interface{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
root, err := sui.PublicRoot(buildOptionData)
|
||||||
|
if err != nil {
|
||||||
|
exception.New(err.Error(), 500).Throw()
|
||||||
|
}
|
||||||
|
assetRoot := filepath.Join(root, "assets")
|
||||||
|
|
||||||
|
source := process.ArgsString(2)
|
||||||
|
page := tmpl.CreatePage(source)
|
||||||
|
route := page.Get().Route
|
||||||
|
globalCtx := core.NewGlobalBuildContext()
|
||||||
|
suicode, _, err := page.Get().CompileAsComponent(core.NewBuildContext(globalCtx), &core.BuildOption{
|
||||||
|
PublicRoot: root,
|
||||||
|
AssetRoot: assetRoot,
|
||||||
|
IgnoreDocument: true,
|
||||||
|
Data: buildOptionData,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
exception.New(err.Error(), 500).Throw()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
doc, err := core.NewDocument([]byte(suicode))
|
||||||
|
if err != nil {
|
||||||
|
exception.New(err.Error(), 500).Throw()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var imports map[string]string
|
||||||
|
importsSel := doc.Find("script[name=imports]")
|
||||||
|
if importsSel != nil && importsSel.Length() > 0 {
|
||||||
|
importsRaw := importsSel.Text()
|
||||||
|
importsSel.Remove()
|
||||||
|
err := jsoniter.UnmarshalFromString(importsRaw, &imports)
|
||||||
|
if err != nil {
|
||||||
|
exception.New(err.Error(), 500).Throw()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data := process.ArgsMap(3)
|
||||||
|
option := core.ParserOption{
|
||||||
|
Theme: opt["theme"],
|
||||||
|
Locale: opt["locale"],
|
||||||
|
Debug: false,
|
||||||
|
DisableCache: true,
|
||||||
|
Route: route,
|
||||||
|
Root: root,
|
||||||
|
Script: nil,
|
||||||
|
Imports: imports,
|
||||||
|
Request: &core.Request{
|
||||||
|
Theme: opt["theme"],
|
||||||
|
Locale: opt["locale"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
parser := core.NewTemplateParser(core.Data(data), &option)
|
||||||
|
sel := doc.Find("body")
|
||||||
|
err = parser.RenderSelection(sel)
|
||||||
|
if err != nil {
|
||||||
|
exception.New(err.Error(), 500).Throw()
|
||||||
|
}
|
||||||
|
|
||||||
|
sel.Find("[sui-hide]").Remove()
|
||||||
|
parser.Tidy(sel)
|
||||||
|
html, err := sel.Html()
|
||||||
|
if err != nil {
|
||||||
|
exception.New(err.Error(), 500).Throw()
|
||||||
|
}
|
||||||
|
|
||||||
|
return html
|
||||||
|
}
|
||||||
|
|
|
||||||
62
sui/api/render_test.go
Normal file
62
sui/api/render_test.go
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/yaoapp/gou/process"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTemplateRender(t *testing.T) {
|
||||||
|
prepare(t)
|
||||||
|
defer clean()
|
||||||
|
|
||||||
|
args := []any{"test", "advanced", "<div> {{ name}} </div>", map[string]any{"name": "test"}}
|
||||||
|
p, err := process.Of("sui.template.render", args...)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := p.Exec()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assert.Contains(t, res, "<div> test ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTemplateRenderWithComponent(t *testing.T) {
|
||||||
|
prepare(t)
|
||||||
|
defer clean()
|
||||||
|
source := `
|
||||||
|
<div>
|
||||||
|
<h2>Component Render {{ name }} </h2>
|
||||||
|
<div style="display: flex; gap: 20px">
|
||||||
|
<div s:for="{{ ['foo', 'bar'] }}">
|
||||||
|
<Component
|
||||||
|
is="/backend/{{ item }}"
|
||||||
|
world="World"
|
||||||
|
hello="{{ hello }}"
|
||||||
|
index="{{ index }}"
|
||||||
|
pets="{{ ['cat', 'dog'] }}"
|
||||||
|
>
|
||||||
|
{{ upper(item) }} {{ name }}
|
||||||
|
</Component>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
args := []any{"test", "advanced", source, map[string]any{"name": "test"}, map[string]any{"theme": "dark"}}
|
||||||
|
p, err := process.Of("sui.template.render", args...)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := p.Exec()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Contains(t, res, "Component Render test")
|
||||||
|
assert.Contains(t, res, "FOO test")
|
||||||
|
assert.Contains(t, res, "BAR test")
|
||||||
|
}
|
||||||
|
|
@ -29,6 +29,7 @@ type SUI interface {
|
||||||
PublicRootMatcher() *Matcher
|
PublicRootMatcher() *Matcher
|
||||||
GetPublic() *Public
|
GetPublic() *Public
|
||||||
PublicRootWithSid(sid string) (string, error)
|
PublicRootWithSid(sid string) (string, error)
|
||||||
|
PublicRoot(data map[string]any) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ITemplate is the interface for the ITemplate
|
// ITemplate is the interface for the ITemplate
|
||||||
|
|
@ -37,6 +38,7 @@ type ITemplate interface {
|
||||||
PageTree(route string) ([]*PageTreeNode, error)
|
PageTree(route string) ([]*PageTreeNode, error)
|
||||||
Page(route string) (IPage, error)
|
Page(route string) (IPage, error)
|
||||||
PageExist(route string) bool
|
PageExist(route string) bool
|
||||||
|
CreatePage(html string) IPage
|
||||||
CreateEmptyPage(route string, setting *PageSetting) (IPage, error)
|
CreateEmptyPage(route string, setting *PageSetting) (IPage, error)
|
||||||
RemovePage(route string) error
|
RemovePage(route string) error
|
||||||
GetPageFromAsset(asset string) (IPage, error)
|
GetPageFromAsset(asset string) (IPage, error)
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,8 @@ func (script *Script) Call(r *Request, method string, args ...any) (interface{},
|
||||||
}
|
}
|
||||||
defer ctx.Close()
|
defer ctx.Close()
|
||||||
|
|
||||||
|
// Set the sid
|
||||||
|
ctx.Sid = r.Sid
|
||||||
res, err := ctx.Call(method, args...)
|
res, err := ctx.Call(method, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -118,6 +120,8 @@ func (script *Script) BeforeRender(r *Request, props map[string]interface{}) (Da
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the sid
|
||||||
|
ctx.Sid = r.Sid
|
||||||
res, err := ctx.Call("BeforeRender", r, props)
|
res, err := ctx.Call("BeforeRender", r, props)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
jsoniter "github.com/json-iterator/go"
|
jsoniter "github.com/json-iterator/go"
|
||||||
"github.com/yaoapp/kun/log"
|
"github.com/yaoapp/kun/log"
|
||||||
"github.com/yaoapp/yao/sui/core"
|
"github.com/yaoapp/yao/sui/core"
|
||||||
|
|
@ -268,6 +269,30 @@ func (page *Page) SaveAs(route string, setting *core.PageSetting) (core.IPage, e
|
||||||
return page.tmpl.Page(route)
|
return page.tmpl.Page(route)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreatePage create a new page by the source
|
||||||
|
func (tmpl *Template) CreatePage(source string) core.IPage {
|
||||||
|
name := uuid.New().String()
|
||||||
|
route := "/" + uuid.New().String()
|
||||||
|
return &Page{
|
||||||
|
tmpl: tmpl,
|
||||||
|
Page: &core.Page{
|
||||||
|
Route: route,
|
||||||
|
TemplateID: tmpl.ID,
|
||||||
|
SuiID: tmpl.local.ID,
|
||||||
|
Path: filepath.Join(tmpl.Root, route),
|
||||||
|
Name: name,
|
||||||
|
Codes: core.SourceCodes{
|
||||||
|
HTML: core.Source{File: fmt.Sprintf("%s.html", name), Code: source},
|
||||||
|
CSS: core.Source{File: fmt.Sprintf("%s.css", name)},
|
||||||
|
JS: core.Source{File: fmt.Sprintf("%s.js", name)},
|
||||||
|
TS: core.Source{File: fmt.Sprintf("%s.ts", name)},
|
||||||
|
LESS: core.Source{File: fmt.Sprintf("%s.less", name)},
|
||||||
|
CONF: core.Source{File: fmt.Sprintf("%s.config", name)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// CreateEmptyPage create a new empty
|
// CreateEmptyPage create a new empty
|
||||||
func (tmpl *Template) CreateEmptyPage(route string, setting *core.PageSetting) (core.IPage, error) {
|
func (tmpl *Template) CreateEmptyPage(route string, setting *core.PageSetting) (core.IPage, error) {
|
||||||
if tmpl.PageExist(route) {
|
if tmpl.PageExist(route) {
|
||||||
|
|
|
||||||
|
|
@ -590,3 +590,35 @@ func TestPageAssetStyle(t *testing.T) {
|
||||||
assert.NotEmpty(t, asset.Content)
|
assert.NotEmpty(t, asset.Content)
|
||||||
assert.Equal(t, "text/css; charset=utf-8", asset.Type)
|
assert.Equal(t, "text/css; charset=utf-8", asset.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCreatePage(t *testing.T) {
|
||||||
|
tests := prepare(t)
|
||||||
|
defer clean()
|
||||||
|
|
||||||
|
tmpl, err := tests.Test.GetTemplate("advanced")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetTemplate error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
page := tmpl.CreatePage("<div>Test</div>")
|
||||||
|
if page == nil {
|
||||||
|
t.Fatalf("CreatePage error")
|
||||||
|
}
|
||||||
|
|
||||||
|
doc, _, err := page.Get().Build(core.NewBuildContext(nil), &core.BuildOption{
|
||||||
|
PublicRoot: tmpl.GetRoot(),
|
||||||
|
IgnoreDocument: true,
|
||||||
|
JitMode: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Compile error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sel := doc.Find("body")
|
||||||
|
html, err := sel.Html()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Html error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, "<div>Test</div>", html)
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue