krz/skunky-art
Alternative privacy frontend for DeviantArt.
clone: git clone https://gitbay.org/krz/skunky-art.git
v1.3.3: static/templates-noembed.go · raw
1//go:build !embed
2
3package static
4
5import (
6 "bytes"
7 "io/fs"
8 "os"
9 "strings"
10 "time"
11)
12
13// Templates is the in-memory asset filesystem populated by CopyTemplatesToMemory.
14var Templates FS
15
16type file struct {
17 path string
18 name string
19 content []byte
20}
21
22var templateNames = []string{}
23var templates = make(map[string][]file)
24
25// StaticPath is the directory assets are read from at startup.
26var StaticPath string
27
28// CopyTemplatesToMemory reads every asset under StaticPath into memory. It exits
29// the process on failure, since the frontend cannot serve anything without them.
30func CopyTemplatesToMemory() {
31 baseDir, err := os.ReadDir(StaticPath)
32 try(err)
33
34 for _, c := range baseDir {
35 if c.IsDir() {
36 templateNames = append(templateNames, c.Name())
37
38 var filePath strings.Builder
39 filePath.WriteString(StaticPath)
40 filePath.WriteString("/")
41 filePath.WriteString(c.Name())
42
43 dir, err := os.ReadDir(filePath.String())
44 try(err)
45
46 filePath.WriteString("/")
47 for _, cd := range dir {
48 f, err := os.ReadFile(filePath.String() + cd.Name())
49 try(err)
50 templates[c.Name()] = append(templates[c.Name()], file{
51 content: f,
52 name: cd.Name(),
53 path: c.Name() + "/" + cd.Name(),
54 })
55 }
56 }
57 }
58}
59
60// FS serves the in-memory assets. It implements the subset of fs.FS that
61// template.ParseFS requires.
62type FS struct{}
63
64// Open returns the asset stored at name, or an fs.PathError if there is none.
65func (FS) Open(name string) (fs.File, error) {
66 for i, l := 0, len(templateNames); i < l; i++ {
67 for _, x := range templates[templateNames[i]] {
68 if x.content != nil && name == x.path {
69 return &File{
70 name: x.path,
71 content: bytes.NewBuffer(x.content),
72 }, nil
73 }
74 }
75 }
76 return nil, &fs.PathError{}
77}
78
79// Glob returns the paths of every asset in the directory named by pattern's
80// first segment, or an fs.PathError if none match.
81func (FS) Glob(pattern string) ([]string, error) {
82 trimmed := strings.Split(pattern, "/")
83 var matches = []string{}
84 for x, s := range templates {
85 for i, l := 0, len(s); i < l && trimmed[0] == x; i++ {
86 s := s[i]
87 matches = append(matches, s.path)
88 }
89 }
90 if len(matches) != 0 {
91 return matches, nil
92 }
93 return nil, &fs.PathError{}
94}
95
96func try(err error) {
97 if err != nil {
98 println(err.Error())
99 os.Exit(1)
100 }
101}
102
103// fileInfo is a minimal fs.FileInfo. Assets are held in memory and never stat'd
104// for anything but their name, so the remaining fields report fixed values.
105//
106// Based on https://github.com/psanford/memfs; required for templates.ParseFS to
107// work correctly.
108type fileInfo struct {
109 name string
110}
111
112// Name returns the asset's path.
113func (fi fileInfo) Name() string {
114 return fi.name
115}
116
117// Size reports a fixed placeholder size; callers here never use it.
118func (fi fileInfo) Size() int64 {
119 return 4096
120}
121
122// Mode reports no mode bits: in-memory assets have no filesystem permissions.
123func (fileInfo) Mode() fs.FileMode {
124 return 0
125}
126
127// ModTime reports the zero time, as in-memory assets are never modified.
128func (fileInfo) ModTime() time.Time {
129 return time.Time{}
130}
131
132// IsDir always reports false: only files are stored, never directories.
133func (fileInfo) IsDir() bool {
134 return false
135}
136
137// Sys returns nil, as there is no underlying data source.
138func (fileInfo) Sys() any {
139 return nil
140}
141
142// File is a read-once handle to an in-memory asset.
143type File struct {
144 name string
145 content *bytes.Buffer
146 closed bool
147}
148
149// Stat returns the file's fileInfo. It never fails.
150func (f *File) Stat() (fs.FileInfo, error) {
151 return fileInfo{
152 name: f.name,
153 }, nil
154}
155
156// Read consumes the asset's contents, reporting fs.ErrClosed once closed.
157func (f *File) Read(b []byte) (int, error) {
158 if f.closed {
159 return 0, fs.ErrClosed
160 }
161 return f.content.Read(b)
162}
163
164// Close marks the file closed. Closing twice reports fs.ErrClosed.
165func (f *File) Close() error {
166 if f.closed {
167 return fs.ErrClosed
168 }
169 f.closed = true
170 return nil
171}