yao/sui/storages/local/template.go

441 lines
9.8 KiB
Go

package local
import (
"fmt"
"io"
"net/url"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/google/uuid"
"github.com/yaoapp/gou/process"
v8 "github.com/yaoapp/gou/runtime/v8"
"github.com/yaoapp/yao/sui/core"
"golang.org/x/text/language"
)
// Assets get the assets treelist
func (tmpl *Template) Assets() []string {
return nil
}
// GetRoot get the root path
func (tmpl *Template) GetRoot() string {
return tmpl.Root
}
// Glob the files
func (tmpl *Template) Glob(pattern string) ([]string, error) {
path := filepath.Join(tmpl.Root, pattern)
paths, err := tmpl.local.fs.Glob(path)
if err != nil {
return nil, err
}
routes := []string{}
for _, p := range paths {
routes = append(routes, strings.TrimPrefix(p, tmpl.Root))
}
return routes, nil
}
// GlobRoutes the files
func (tmpl *Template) GlobRoutes(patterns []string, unique ...bool) ([]string, error) {
routes := []string{}
for _, pattern := range patterns {
paths, err := tmpl.Glob(pattern)
if err != nil {
return nil, err
}
for _, path := range paths {
if !tmpl.local.fs.IsDir(filepath.Join(tmpl.Root, path)) {
continue
}
routes = append(routes, path)
}
}
// Unique
if len(unique) > 0 && unique[0] {
mapRoutes := map[string]bool{}
for _, route := range routes {
mapRoutes[route] = true
}
routes = []string{}
for route := range mapRoutes {
routes = append(routes, route)
}
return routes, nil
}
return routes, nil
}
// Reload the template
func (tmpl *Template) Reload() error {
newTmpl, err := tmpl.local.getTemplateFrom(tmpl.Root)
if err != nil {
return err
}
*tmpl = *newTmpl
return nil
}
// LoadBuildScript load the build script
func (tmpl *Template) loadBuildScript() error {
file, source, err := tmpl.backendScriptSource("__build.backend")
if err != nil {
return err
}
if file == "" {
return nil
}
approot := tmpl.local.AppRoot()
file = filepath.Join(approot, file)
script, err := v8.MakeScript(source, file, 5*time.Second)
if err != nil {
return err
}
tmpl.BuildScript = &core.Script{Script: script}
return nil
}
func (tmpl *Template) backendScriptSource(name string) (string, []byte, error) {
path := filepath.Join(tmpl.Root, fmt.Sprintf("%s.ts", name))
if !tmpl.local.fs.IsFile(path) {
path = filepath.Join(tmpl.Root, fmt.Sprintf("%s.js", name))
}
if !tmpl.local.fs.IsFile(path) {
return "", nil, nil
}
content, err := tmpl.local.fs.ReadFile(path)
if err != nil {
return "", nil, err
}
return path, content, nil
}
// ExecBuildCompleteScripts execute the build complete scripts
func (tmpl *Template) ExecBuildCompleteScripts() []core.TemplateScirptResult {
if tmpl.Scripts == nil || len(tmpl.Scripts.BuildComplete) == 0 {
return nil
}
return tmpl.ExecScripts(tmpl.Scripts.BuildComplete)
}
// ExecBeforeBuildScripts execute the before build scripts
func (tmpl *Template) ExecBeforeBuildScripts() []core.TemplateScirptResult {
if tmpl.Scripts == nil || len(tmpl.Scripts.BeforeBuild) == 0 {
return nil
}
return tmpl.ExecScripts(tmpl.Scripts.BeforeBuild)
}
// ExecAfterBuildScripts execute the after build scripts
func (tmpl *Template) ExecAfterBuildScripts() []core.TemplateScirptResult {
if tmpl.Scripts == nil || len(tmpl.Scripts.AfterBuild) == 0 {
return nil
}
return tmpl.ExecScripts(tmpl.Scripts.AfterBuild)
}
// ExecScripts execute the scripts
func (tmpl *Template) ExecScripts(scripts []*core.TemplateScript) []core.TemplateScirptResult {
results := []core.TemplateScirptResult{}
if scripts == nil {
return results
}
for _, script := range scripts {
switch script.Type {
case "command":
results = append(results, tmpl.execCommand(script))
case "process":
results = append(results, tmpl.execProcess(script))
}
}
return results
}
func (tmpl *Template) execProcess(script *core.TemplateScript) core.TemplateScirptResult {
result := core.TemplateScirptResult{Script: script, Message: "", Error: nil}
name := script.Content
p, err := process.Of(name, tmpl.Root)
if err != nil {
result.Error = err
return result
}
output, err := p.Exec()
result.Error = err
result.Message = fmt.Sprintf("%v", output)
return result
}
func (tmpl *Template) execCommand(script *core.TemplateScript) core.TemplateScirptResult {
result := core.TemplateScirptResult{Script: script, Message: "", Error: nil}
root := filepath.Join(tmpl.local.fs.Root(), tmpl.Root)
// Parse the command
cmd := strings.Split(script.Content, " ")
if len(cmd) == 0 {
result.Error = fmt.Errorf("Command is empty")
return result
}
execCmd := exec.Command(cmd[0], cmd[1:]...)
execCmd.Dir = root
output, err := execCmd.CombinedOutput()
result.Error = err
result.Message = string(output)
return result
}
// Locales get the global locales
func (tmpl *Template) Locales() []core.SelectOption {
if tmpl.locales != nil {
return tmpl.locales
}
// Defined the support locales
supportLocales := []core.SelectOption{}
localeMap := map[string]bool{}
locales := tmpl.Template.Locales
for _, locale := range locales {
if localeMap[locale.Value] {
continue
}
localeMap[locale.Value] = true
supportLocales = append(supportLocales, locale)
}
path := filepath.Join(tmpl.Root, "__locales")
if !tmpl.local.fs.IsDir(path) {
return supportLocales
}
dirs, err := tmpl.local.fs.ReadDir(path, false)
if err != nil {
return supportLocales
}
// Get the support locales
for _, dir := range dirs {
locale := filepath.Base(dir)
if localeMap[locale] {
continue
}
label := language.Make(locale).String()
localeMap[locale] = true
supportLocales = append(supportLocales, core.SelectOption{
Value: locale,
Label: label,
})
}
tmpl.locales = supportLocales
return tmpl.locales
}
// Themes get the global themes
func (tmpl *Template) Themes() []core.SelectOption {
return tmpl.Template.Themes
}
// MediaSearch search the asset
func (tmpl *Template) MediaSearch(query url.Values, page int, pageSize int) (core.MediaSearchResult, error) {
res := core.MediaSearchResult{Data: []core.Media{}, Page: page, PageSize: pageSize}
keyword := query.Get("keyword")
types := query["types"]
if types == nil {
types = []string{"image", "video", "audio"}
}
exts := tmpl.mediaExts(types)
path := filepath.Join(tmpl.Root, "__assets", "upload")
files, total, pagecnt, err := tmpl.local.fs.List(path, exts, page, pageSize, func(s string) bool {
if keyword == "" {
return true
}
return strings.Contains(s, keyword)
})
if err != nil {
return res, err
}
for _, file := range files {
file = strings.TrimPrefix(file, filepath.Join(tmpl.Root, "__assets", "upload"))
res.Data = append(res.Data, core.Media{
ID: file,
URL: filepath.Join("@assets", "upload", file),
Thumb: filepath.Join("@assets", "upload", file),
Type: tmpl.mediaType(file),
Width: 100,
Height: 100,
})
}
res.Next = page + 1
if (page+1)*pageSize >= total {
res.Next = 0
}
res.Prev = page - 1
if page == 1 {
res.Prev = 0
}
res.Total = total
res.PageCount = pagecnt
return res, nil
}
func (tmpl *Template) mediaExts(types []string) []string {
exts := []string{}
for _, typ := range types {
switch typ {
case "image":
exts = append(exts, []string{".jpg", ".jpeg", ".png"}...)
break
case "video":
exts = append(exts, []string{".mp4"}...)
break
case "audio":
exts = append(exts, []string{".mp3"}...)
break
}
}
return exts
}
func (tmpl *Template) mediaType(file string) string {
ext := strings.ToLower(filepath.Ext(file))
switch ext {
case ".jpg":
return "image"
case ".jpeg":
return "image"
case ".png":
return "image"
case ".gif":
return "image"
case ".bmp":
return "image"
case ".mp4":
return "video"
case ".mp3":
return "audio"
}
return "file"
}
// AssetUpload upload the asset
func (tmpl *Template) AssetUpload(reader io.Reader, name string) (string, error) {
fingerprint := strings.ToUpper(uuid.NewString())
dir := strings.Join([]string{string(os.PathSeparator), time.Now().Format("20060102")}, "")
ext := filepath.Ext(name)
file := filepath.Join(tmpl.Root, "__assets", "upload", dir, fmt.Sprintf("%s%s", fingerprint, ext))
_, err := tmpl.local.fs.Write(file, reader, 0644)
if err != nil {
return "", err
}
return filepath.Join("upload", dir, fmt.Sprintf("%s%s", fingerprint, ext)), nil
}
// Asset get the asset
func (tmpl *Template) Asset(file string, width, height uint) (*core.Asset, error) {
file = filepath.Join(tmpl.Root, "__assets", file)
if exist, _ := tmpl.local.fs.Exists(file); exist {
ext := strings.ToLower(filepath.Ext(file))
if (width > 0 || height > 0) && (ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".gif" || ext == ".bmp") {
return tmpl.assetThumb(file, width, height)
}
content, err := tmpl.local.fs.ReadFile(file)
if err != nil {
return nil, err
}
typ := "text/plain"
switch ext {
case ".css":
typ = "text/css; charset=utf-8"
break
case ".js":
typ = "application/javascript; charset=utf-8"
break
case ".ts":
typ = "application/javascript; charset=utf-8"
break
case ".json":
typ = "application/json; charset=utf-8"
break
case ".html":
typ = "text/html; charset=utf-8"
break
default:
typ, err = tmpl.local.fs.MimeType(file)
if err != nil {
return nil, err
}
}
return &core.Asset{Type: typ, Content: content}, nil
}
return nil, fmt.Errorf("Asset %s not found", file)
}
func (tmpl *Template) assetThumb(file string, width, height uint) (*core.Asset, error) {
cacheFile := filepath.Join(tmpl.Root, "__assets", ".cache", fmt.Sprintf("%dx%d", width, height), file)
exist, _ := tmpl.local.fs.Exists(cacheFile)
if !exist {
err := tmpl.local.fs.Resize(file, cacheFile, width, height)
if err != nil {
return nil, err
}
}
typ, err := tmpl.local.fs.MimeType(file)
if err != nil {
return nil, err
}
content, err := tmpl.local.fs.ReadFile(cacheFile)
if err != nil {
return nil, err
}
return &core.Asset{Type: typ, Content: content}, nil
}