Files
go_blog/themes/manager.go
2025-05-21 20:38:25 +08:00

165 lines
3.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package themes
import (
"embed"
"errors"
"fmt"
"html/template"
"io/fs"
"os"
"path/filepath"
"strings"
"sync"
"github.com/gin-gonic/gin"
)
// 主题元数据结构
type ThemeMeta struct {
Name string `yaml:"name"`
Author string `yaml:"author"`
Version string `yaml:"version"`
Description string `yaml:"description"`
}
// 主题管理器
type ThemeManager struct {
mu sync.RWMutex
currentTheme string
templates map[string]*template.Template
baseTemplates embed.FS // 可选:嵌入基础模板
themesDir string
}
// 创建新主题管理器
func NewManager(themesDir string) *ThemeManager {
return &ThemeManager{
templates: make(map[string]*template.Template),
themesDir: themesDir,
}
}
// 核心方法:加载主题
func (m *ThemeManager) LoadTheme(themeName string) error {
m.mu.Lock()
defer m.mu.Unlock()
// 1. 验证主题目录结构
themePath := filepath.Join(m.themesDir, themeName)
if !isValidTheme(themePath) {
return fmt.Errorf("invalid theme structure: %s", themeName)
}
// 2. 加载模板文件
tpls, err := m.parseTemplates(themePath)
if err != nil {
return fmt.Errorf("template parsing failed: %w", err)
}
// 3. 更新当前主题
m.currentTheme = themeName
m.templates = tpls
return nil
}
// 获取当前主题名称
func (m *ThemeManager) CurrentTheme() string {
m.mu.RLock()
defer m.mu.RUnlock()
return m.currentTheme
}
// 获取编译后的模板
func (m *ThemeManager) GetTemplate(name string) *template.Template {
m.mu.RLock()
defer m.mu.RUnlock()
return m.templates[name]
}
// 注册静态文件路由Gin框架
func (m *ThemeManager) RegisterStaticRoutes(router *gin.Engine) {
staticPath := filepath.Join(m.themesDir, m.currentTheme, "static")
router.StaticFS("/theme/static", gin.Dir(staticPath, false))
}
// 校验主题完整性
func isValidTheme(themePath string) bool {
requiredFiles := []string{
"theme.yaml",
"templates/home.html",
"templates/post.html",
}
for _, f := range requiredFiles {
if _, err := os.Stat(filepath.Join(themePath, f)); os.IsNotExist(err) {
return false
}
}
return true
}
// 解析模板文件(支持布局继承)
func (m *ThemeManager) parseTemplates(themePath string) (map[string]*template.Template, error) {
templates := make(map[string]*template.Template)
// 加载公共基础模板(可选)
baseTpl := template.New("base").Funcs(template.FuncMap{
"safeHTML": func(s string) template.HTML { return template.HTML(s) },
})
// 从主题目录加载模板
tplDir := filepath.Join(themePath, "templates")
err := filepath.WalkDir(tplDir, func(path string, d fs.DirEntry, err error) error {
if err != nil || d.IsDir() || !strings.HasSuffix(path, ".html") {
return nil
}
// 读取模板内容
content, err := os.ReadFile(path)
if err != nil {
return err
}
// 生成模板名称(相对路径)
name := strings.TrimPrefix(path, tplDir+string(filepath.Separator))
name = strings.TrimSuffix(name, filepath.Ext(name))
// 克隆基础模板并解析
tpl := template.Must(baseTpl.Clone())
tpl, err = tpl.Parse(string(content))
if err != nil {
return fmt.Errorf("parse error in %s: %w", name, err)
}
templates[name] = tpl
return nil
})
if err != nil {
return nil, err
}
if len(templates) == 0 {
return nil, errors.New("no valid templates found")
}
return templates, nil
}
// 获取可用主题列表
func (m *ThemeManager) ListThemes() ([]string, error) {
dirs, err := os.ReadDir(m.themesDir)
if err != nil {
return nil, err
}
var themes []string
for _, d := range dirs {
if d.IsDir() && isValidTheme(filepath.Join(m.themesDir, d.Name())) {
themes = append(themes, d.Name())
}
}
return themes, nil
}