Merge pull request #511 from trheyi/main
[add] SUI Template Engine command
This commit is contained in:
commit
bcb5ec47d0
8 changed files with 308 additions and 1 deletions
20
cmd/root.go
20
cmd/root.go
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/yaoapp/kun/exception"
|
"github.com/yaoapp/kun/exception"
|
||||||
"github.com/yaoapp/yao/cmd/studio"
|
"github.com/yaoapp/yao/cmd/studio"
|
||||||
|
"github.com/yaoapp/yao/cmd/sui"
|
||||||
"github.com/yaoapp/yao/config"
|
"github.com/yaoapp/yao/config"
|
||||||
"github.com/yaoapp/yao/pack"
|
"github.com/yaoapp/yao/pack"
|
||||||
"github.com/yaoapp/yao/share"
|
"github.com/yaoapp/yao/share"
|
||||||
|
|
@ -62,6 +63,7 @@ var langs = map[string]string{
|
||||||
"Error occurred while updating binary: %s": "更新二进制文件时出错: %s",
|
"Error occurred while updating binary: %s": "更新二进制文件时出错: %s",
|
||||||
"🎉Successfully updated to version: %s🎉": "🎉成功更新到版本: %s🎉",
|
"🎉Successfully updated to version: %s🎉": "🎉成功更新到版本: %s🎉",
|
||||||
"Print all version information": "显示详细版本信息",
|
"Print all version information": "显示详细版本信息",
|
||||||
|
"SUI Template Engine": "SUI 模板引擎命令",
|
||||||
}
|
}
|
||||||
|
|
||||||
// L 多语言切换
|
// L 多语言切换
|
||||||
|
|
@ -110,9 +112,26 @@ var studioCmd = &cobra.Command{
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var suiCmd = &cobra.Command{
|
||||||
|
Use: "sui",
|
||||||
|
Short: L("SUI Template Engine"),
|
||||||
|
Long: L("SUI Template Engine"),
|
||||||
|
Args: cobra.MinimumNArgs(1),
|
||||||
|
CompletionOptions: cobra.CompletionOptions{
|
||||||
|
DisableDefaultCmd: true,
|
||||||
|
},
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
fmt.Fprintln(os.Stderr, L("One or more arguments are not correct"), args)
|
||||||
|
os.Exit(1)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// 加载命令
|
// 加载命令
|
||||||
func init() {
|
func init() {
|
||||||
|
|
||||||
studioCmd.AddCommand(studio.RunCmd)
|
studioCmd.AddCommand(studio.RunCmd)
|
||||||
|
suiCmd.AddCommand(sui.WatchCmd)
|
||||||
|
|
||||||
rootCmd.AddCommand(
|
rootCmd.AddCommand(
|
||||||
versionCmd,
|
versionCmd,
|
||||||
migrateCmd,
|
migrateCmd,
|
||||||
|
|
@ -126,6 +145,7 @@ func init() {
|
||||||
// websocketCmd,
|
// websocketCmd,
|
||||||
packCmd,
|
packCmd,
|
||||||
studioCmd,
|
studioCmd,
|
||||||
|
suiCmd,
|
||||||
upgradeCmd,
|
upgradeCmd,
|
||||||
)
|
)
|
||||||
// rootCmd.SetHelpCommand(helpCmd)
|
// rootCmd.SetHelpCommand(helpCmd)
|
||||||
|
|
|
||||||
54
cmd/sui/utils.go
Normal file
54
cmd/sui/utils.go
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
package sui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/yaoapp/kun/exception"
|
||||||
|
"github.com/yaoapp/yao/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
var appPath string
|
||||||
|
var envFile string
|
||||||
|
|
||||||
|
var langs = map[string]string{
|
||||||
|
"Auto-build when the template file changes": "模板文件变化时自动构建",
|
||||||
|
"Session Data": "会话数据",
|
||||||
|
}
|
||||||
|
|
||||||
|
// L 多语言切换
|
||||||
|
func L(words string) string {
|
||||||
|
|
||||||
|
var lang = os.Getenv("YAO_LANG")
|
||||||
|
if lang == "" {
|
||||||
|
return words
|
||||||
|
}
|
||||||
|
|
||||||
|
if trans, has := langs[words]; has {
|
||||||
|
return trans
|
||||||
|
}
|
||||||
|
return words
|
||||||
|
}
|
||||||
|
|
||||||
|
// Boot 设定配置
|
||||||
|
func Boot() {
|
||||||
|
root := config.Conf.Root
|
||||||
|
if appPath != "" {
|
||||||
|
r, err := filepath.Abs(appPath)
|
||||||
|
if err != nil {
|
||||||
|
exception.New("Root error %s", 500, err.Error()).Throw()
|
||||||
|
}
|
||||||
|
root = r
|
||||||
|
}
|
||||||
|
if envFile != "" {
|
||||||
|
config.Conf = config.LoadFrom(envFile)
|
||||||
|
} else {
|
||||||
|
config.Conf = config.LoadFrom(filepath.Join(root, ".env"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Conf.Mode == "production" {
|
||||||
|
config.Production()
|
||||||
|
} else if config.Conf.Mode == "development" {
|
||||||
|
config.Development()
|
||||||
|
}
|
||||||
|
}
|
||||||
223
cmd/sui/watch.go
Normal file
223
cmd/sui/watch.go
Normal file
|
|
@ -0,0 +1,223 @@
|
||||||
|
package sui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/fatih/color"
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/yaoapp/gou/session"
|
||||||
|
"github.com/yaoapp/kun/log"
|
||||||
|
"github.com/yaoapp/yao/config"
|
||||||
|
"github.com/yaoapp/yao/engine"
|
||||||
|
"github.com/yaoapp/yao/sui/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
var data string
|
||||||
|
|
||||||
|
var watched sync.Map
|
||||||
|
|
||||||
|
// WatchCmd command
|
||||||
|
var WatchCmd = &cobra.Command{
|
||||||
|
Use: "watch",
|
||||||
|
Short: L("Auto-build when the template file changes"),
|
||||||
|
Long: L("Auto-build when the template file changes"),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
if len(args) < 2 {
|
||||||
|
fmt.Fprintln(os.Stderr, color.RedString(L("yao cui watch <sui> <template>")))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Boot()
|
||||||
|
|
||||||
|
cfg := config.Conf
|
||||||
|
err := engine.Load(cfg)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, color.RedString(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
id := args[0]
|
||||||
|
template := args[1]
|
||||||
|
|
||||||
|
var sessionData map[string]interface{}
|
||||||
|
err = jsoniter.UnmarshalFromString(strings.TrimPrefix(data, "::"), &sessionData)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, color.RedString(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sid := uuid.New().String()
|
||||||
|
if sessionData != nil && len(sessionData) > 0 {
|
||||||
|
session.Global().ID(sid).SetMany(sessionData)
|
||||||
|
}
|
||||||
|
|
||||||
|
sui, has := core.SUIs[id]
|
||||||
|
if !has {
|
||||||
|
fmt.Fprintf(os.Stderr, color.RedString(("the sui " + id + " does not exist")))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sui.WithSid(sid)
|
||||||
|
|
||||||
|
tmpl, err := sui.GetTemplate(template)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, color.RedString(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
exitSignal := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(exitSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT)
|
||||||
|
watchDone := make(chan uint8, 1)
|
||||||
|
|
||||||
|
// -
|
||||||
|
root := filepath.Join(cfg.DataRoot, tmpl.GetRoot())
|
||||||
|
publicRoot, err := sui.PublicRootWithSid(sid)
|
||||||
|
assetRoot := filepath.Join(publicRoot, "assets")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, color.RedString(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go watch(root, func(event, name string) {
|
||||||
|
if event == "WRITE" || event == "CREATE" || event == "RENAME" {
|
||||||
|
// @Todo build single page and sync single asset file to public
|
||||||
|
|
||||||
|
fmt.Printf(color.WhiteString("Building... "))
|
||||||
|
err := tmpl.Build(&core.BuildOption{SSR: true, AssetRoot: assetRoot})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprint(os.Stderr, color.RedString(fmt.Sprintf("Failed: %s\n", err.Error())))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Print(color.GreenString("Success\n"))
|
||||||
|
}
|
||||||
|
}, watchDone)
|
||||||
|
|
||||||
|
fmt.Println(color.WhiteString("-----------------------"))
|
||||||
|
fmt.Println(color.WhiteString("Public Root: /public%s", publicRoot))
|
||||||
|
fmt.Println(color.WhiteString(" Template: %s", tmpl.GetRoot()))
|
||||||
|
fmt.Println(color.WhiteString(" Session: %s", strings.TrimLeft(data, "::")))
|
||||||
|
fmt.Println(color.WhiteString("-----------------------"))
|
||||||
|
fmt.Println(color.GreenString("Watching..."))
|
||||||
|
fmt.Println(color.GreenString("Press Ctrl+C to exit"))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, color.RedString(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-exitSignal:
|
||||||
|
watchDone <- 1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
WatchCmd.PersistentFlags().StringVarP(&data, "data", "d", "::{}", L("Session Data"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func watch(root string, handler func(event string, name string), interrupt chan uint8) error {
|
||||||
|
watcher, err := fsnotify.NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer watcher.Close()
|
||||||
|
shutdown := make(chan bool, 1)
|
||||||
|
|
||||||
|
err = filepath.Walk(root, func(path string, info fs.FileInfo, err error) error {
|
||||||
|
if info.IsDir() {
|
||||||
|
if filepath.Base(path) == ".tmp" {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
|
||||||
|
err = watcher.Add(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info("[Watch] Watching: %s", strings.TrimPrefix(path, root))
|
||||||
|
watched.Store(path, true)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-shutdown:
|
||||||
|
log.Info("[Watch] handler exit")
|
||||||
|
return
|
||||||
|
|
||||||
|
case event, ok := <-watcher.Events:
|
||||||
|
if !ok {
|
||||||
|
interrupt <- 1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
basname := filepath.Base(event.Name)
|
||||||
|
isdir := true
|
||||||
|
if strings.Contains(basname, ".") {
|
||||||
|
isdir = false
|
||||||
|
}
|
||||||
|
|
||||||
|
events := strings.Split(event.Op.String(), "|")
|
||||||
|
for _, eventType := range events {
|
||||||
|
// ADD / REMOVE Watching dir
|
||||||
|
if isdir {
|
||||||
|
switch eventType {
|
||||||
|
case "CREATE":
|
||||||
|
log.Info("[Watch] Watching: %s", strings.TrimPrefix(event.Name, root))
|
||||||
|
watcher.Add(event.Name)
|
||||||
|
watched.Store(event.Name, true)
|
||||||
|
break
|
||||||
|
|
||||||
|
case "REMOVE":
|
||||||
|
log.Info("[Watch] Unwatching: %s", strings.TrimPrefix(event.Name, root))
|
||||||
|
watcher.Remove(event.Name)
|
||||||
|
watched.Delete(event.Name)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
file := strings.TrimLeft(event.Name, root)
|
||||||
|
handler(eventType, file)
|
||||||
|
log.Info("[Watch] %s %s", eventType, file)
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
case err, ok := <-watcher.Errors:
|
||||||
|
if !ok {
|
||||||
|
interrupt <- 2
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Error("[Watch] Error: %s", err.Error())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case code := <-interrupt:
|
||||||
|
shutdown <- true
|
||||||
|
log.Info("[Watch] Exit(%d)", code)
|
||||||
|
fmt.Println(color.YellowString("[Watch] Exit(%d)", code))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
2
go.mod
2
go.mod
|
|
@ -111,7 +111,7 @@ require (
|
||||||
golang.org/x/mod v0.14.0 // indirect
|
golang.org/x/mod v0.14.0 // indirect
|
||||||
golang.org/x/oauth2 v0.14.0 // indirect
|
golang.org/x/oauth2 v0.14.0 // indirect
|
||||||
golang.org/x/sync v0.5.0 // indirect
|
golang.org/x/sync v0.5.0 // indirect
|
||||||
golang.org/x/sys v0.14.0 // indirect
|
golang.org/x/sys v0.15.0 // indirect
|
||||||
golang.org/x/tools v0.15.0 // indirect
|
golang.org/x/tools v0.15.0 // indirect
|
||||||
google.golang.org/appengine v1.6.8 // indirect
|
google.golang.org/appengine v1.6.8 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
||||||
|
|
|
||||||
2
go.sum
2
go.sum
|
|
@ -319,6 +319,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
||||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||||
|
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ func NewRequestContext(c *gin.Context) (*Request, int, error) {
|
||||||
func (r *Request) Render() (string, int, error) {
|
func (r *Request) Render() (string, int, error) {
|
||||||
|
|
||||||
c := core.GetCache(r.File)
|
c := core.GetCache(r.File)
|
||||||
|
c = nil // disable cache @todo disable cache on development
|
||||||
if c == nil {
|
if c == nil {
|
||||||
// Read the file
|
// Read the file
|
||||||
content, err := application.App.Read(r.File)
|
content, err := application.App.Read(r.File)
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,8 @@ type ITemplate interface {
|
||||||
|
|
||||||
Build(option *BuildOption) error
|
Build(option *BuildOption) error
|
||||||
SyncAssets(option *BuildOption) error
|
SyncAssets(option *BuildOption) error
|
||||||
|
|
||||||
|
GetRoot() string
|
||||||
}
|
}
|
||||||
|
|
||||||
// IPage is the interface for the page
|
// IPage is the interface for the page
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,11 @@ func (tmpl *Template) Assets() []string {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRoot get the root path
|
||||||
|
func (tmpl *Template) GetRoot() string {
|
||||||
|
return tmpl.Root
|
||||||
|
}
|
||||||
|
|
||||||
// Locales get the global locales
|
// Locales get the global locales
|
||||||
func (tmpl *Template) Locales() []core.SelectOption {
|
func (tmpl *Template) Locales() []core.SelectOption {
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue