/
theme.go
134 lines (108 loc) · 3.81 KB
/
theme.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// SPDX-License-Identifier: MIT
package loader
import (
"io/fs"
"path"
"strconv"
"github.com/alecthomas/chroma/styles"
"github.com/issue9/sliceutil"
"github.com/caixw/blogit/internal/filesystem"
"github.com/caixw/blogit/internal/vars"
)
// Theme 主题
type Theme struct {
ID string `yaml:"-"`
URL string `yaml:"url"`
Description string `yaml:"description,omitempty"`
Authors []*Author `yaml:"authors,omitempty"`
Screenshots []string `yaml:"screenshots,omitempty"`
Templates []string `yaml:"templates,omitempty"`
// 指定高亮的主题名称
//
// 名称值可以从 https://pkg.go.dev/github.com/alecthomas/chroma@v0.8.2/styles 获取
Highlights []*Highlight `yaml:"highlights,omitempty"`
// 部分可选内容的模板,如果为空,则其输出相应的 xml 文件时不会为其添加 xsl 文件。
// 模板名称为相对于当前主题目录的文件路径。
Sitemap string `yaml:"sitemap,omitempty"`
RSS string `yaml:"rss,omitempty"`
Atom string `yaml:"atom,omitempty"`
}
// Highlight 高亮主题指定
type Highlight struct {
Name string `yaml:"name"` // 指向的主题名称
Media string `yaml:"media,omitempty"`
}
// dir 为当前主题所在的目录;
// id 为主题目录的名称
func (t *Theme) sanitize(fs fs.FS, dir, id string) *FieldError {
t.ID = id
for index, author := range t.Authors {
if err := author.sanitize(); err != nil {
err.Field = "authors[" + strconv.Itoa(index) + "]." + err.Field
return err
}
}
if sliceutil.Count(t.Templates, func(i int) bool { return t.Templates[i] == vars.DefaultTemplate }) == 0 {
t.Templates = append(t.Templates, vars.DefaultTemplate)
}
indexes := sliceutil.Dup(t.Templates, func(i, j int) bool { return t.Templates[i] == t.Templates[j] })
if len(indexes) > 0 {
return &FieldError{Message: "重复的值模板列表", Field: "templates." + t.Templates[indexes[0]]}
}
for index, s := range t.Screenshots {
if !filesystem.Exists(fs, path.Join(dir, s)) {
return &FieldError{Message: "不存在的示例图", Field: "screenshots[" + strconv.Itoa(index) + "]"}
}
}
indexes = sliceutil.Dup(t.Screenshots, func(i, j int) bool { return t.Screenshots[i] == t.Screenshots[j] })
if len(indexes) > 0 {
return &FieldError{Message: "重复的示例图", Field: "screenshots[" + strconv.Itoa(indexes[0]) + "]"}
}
if t.Sitemap != "" && !filesystem.Exists(fs, path.Join(dir, t.Sitemap)) {
return &FieldError{Message: "不存在该模板文件", Field: "sitemap", Value: t.Sitemap}
}
if t.RSS != "" && !filesystem.Exists(fs, path.Join(dir, t.RSS)) {
return &FieldError{Message: "不存在该模板文件", Field: "rss", Value: t.RSS}
}
if t.Atom != "" && !filesystem.Exists(fs, path.Join(dir, t.Atom)) {
return &FieldError{Message: "不存在该模板文件", Field: "atom", Value: t.Atom}
}
var mediaIsEmpty bool
for index, h := range t.Highlights {
i := strconv.Itoa(index)
prefix := "highlight[" + i + "]."
if err := h.sanitize(); err != nil {
err.Field = prefix + err.Field
return err
}
if h.Media == "" {
if mediaIsEmpty {
return &FieldError{Message: "只能一个为空", Field: prefix + "media"}
}
mediaIsEmpty = true
}
}
return nil
}
var highlightCSSName = styles.Names()
func (h *Highlight) sanitize() *FieldError {
names := highlightCSSName
if sliceutil.Count(names, func(i int) bool { return names[i] == h.Name }) == 0 {
return &FieldError{Message: "不存在", Field: "name", Value: h.Name}
}
return nil
}
// LoadTheme 加载指定主题
func LoadTheme(fs fs.FS, id string) (*Theme, error) {
dir := path.Join(vars.ThemesDir, id)
p := path.Join(dir, vars.ThemeYAML)
theme := &Theme{}
if err := loadYAML(fs, p, &theme); err != nil {
return nil, err
}
if err := theme.sanitize(fs, dir, id); err != nil {
err.File = p
return nil, err
}
return theme, nil
}