Skip to content

Commit b68826e

Browse files
committed
Add embedx
1 parent 56d9fe1 commit b68826e

File tree

2 files changed

+231
-0
lines changed

2 files changed

+231
-0
lines changed

Diff for: embedx/fs.go

+152
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
package embedx
2+
3+
import (
4+
"errors"
5+
"io"
6+
"io/fs"
7+
"net/http"
8+
"os"
9+
"path/filepath"
10+
"strings"
11+
"time"
12+
)
13+
14+
var (
15+
initTime = time.Now()
16+
unixEpochTime = time.Unix(0, 0)
17+
)
18+
19+
// isZeroTime reports whether t is obviously unspecified (either zero or Unix()=0).
20+
func isZeroTime(t time.Time) bool {
21+
return t.IsZero() || t.Equal(unixEpochTime)
22+
}
23+
24+
type ioFS struct {
25+
fsys fs.FS
26+
}
27+
28+
type ioFile struct {
29+
file fs.File
30+
}
31+
32+
type ioFileInfo struct {
33+
fs.FileInfo
34+
}
35+
36+
// ModTime change modification time to the mod time of the executing binary, falling back on the init time
37+
func (f ioFileInfo) ModTime() time.Time {
38+
if t := f.FileInfo.ModTime(); !isZeroTime(t) {
39+
return t
40+
}
41+
if exe, err := os.Executable(); err == nil {
42+
if exe, err := filepath.EvalSymlinks(exe); err == nil {
43+
if d, err := os.Stat(exe); err == nil {
44+
if t := d.ModTime(); !isZeroTime(t) {
45+
return t
46+
}
47+
}
48+
}
49+
}
50+
return initTime
51+
}
52+
53+
func (f ioFS) Open(name string) (http.File, error) {
54+
if name == "/" {
55+
name = "."
56+
} else {
57+
name = strings.TrimPrefix(name, "/")
58+
}
59+
file, err := f.fsys.Open(name)
60+
if err != nil {
61+
return nil, mapOpenError(err, name, '/', func(path string) (fs.FileInfo, error) {
62+
return fs.Stat(f.fsys, path)
63+
})
64+
}
65+
return ioFile{file}, nil
66+
}
67+
68+
func (f ioFile) Close() error { return f.file.Close() }
69+
func (f ioFile) Read(b []byte) (int, error) { return f.file.Read(b) }
70+
func (f ioFile) Stat() (fs.FileInfo, error) {
71+
d, err := f.file.Stat()
72+
return ioFileInfo{d}, err
73+
}
74+
75+
var errMissingSeek = errors.New("io.File missing Seek method")
76+
var errMissingReadDir = errors.New("io.File directory missing ReadDir method")
77+
78+
func (f ioFile) Seek(offset int64, whence int) (int64, error) {
79+
s, ok := f.file.(io.Seeker)
80+
if !ok {
81+
return 0, errMissingSeek
82+
}
83+
return s.Seek(offset, whence)
84+
}
85+
86+
func (f ioFile) ReadDir(count int) ([]fs.DirEntry, error) {
87+
d, ok := f.file.(fs.ReadDirFile)
88+
if !ok {
89+
return nil, errMissingReadDir
90+
}
91+
return d.ReadDir(count)
92+
}
93+
94+
func (f ioFile) Readdir(count int) ([]fs.FileInfo, error) {
95+
d, ok := f.file.(fs.ReadDirFile)
96+
if !ok {
97+
return nil, errMissingReadDir
98+
}
99+
var list []fs.FileInfo
100+
for {
101+
dirs, err := d.ReadDir(count - len(list))
102+
for _, dir := range dirs {
103+
info, err := dir.Info()
104+
if err != nil {
105+
// Pretend it doesn't exist, like (*os.File).Readdir does.
106+
continue
107+
}
108+
list = append(list, info)
109+
}
110+
if err != nil {
111+
return list, err
112+
}
113+
if count < 0 || len(list) >= count {
114+
break
115+
}
116+
}
117+
return list, nil
118+
}
119+
120+
// FS converts fsys to a [FileSystem] implementation,
121+
// for use with [FileServer] and [NewFileTransport].
122+
// The files provided by fsys must implement [io.Seeker].
123+
// This custom FS implement solves the caching problem
124+
// when serving embedded files. The idea comes from
125+
// https://www.reddit.com/r/golang/comments/118pqb2/comment/j9io84v/
126+
func FS(fsys fs.FS) http.FileSystem {
127+
return ioFS{fsys}
128+
}
129+
130+
// mapOpenError maps the provided non-nil error from opening name
131+
// to a possibly better non-nil error. In particular, it turns OS-specific errors
132+
// about opening files in non-directories into fs.ErrNotExist. See Issues 18984 and 49552.
133+
func mapOpenError(originalErr error, name string, sep rune, stat func(string) (fs.FileInfo, error)) error {
134+
if errors.Is(originalErr, fs.ErrNotExist) || errors.Is(originalErr, fs.ErrPermission) {
135+
return originalErr
136+
}
137+
138+
parts := strings.Split(name, string(sep))
139+
for i := range parts {
140+
if parts[i] == "" {
141+
continue
142+
}
143+
fi, err := stat(strings.Join(parts[:i+1], string(sep)))
144+
if err != nil {
145+
return originalErr
146+
}
147+
if !fi.IsDir() {
148+
return fs.ErrNotExist
149+
}
150+
}
151+
return originalErr
152+
}

Diff for: embedx/template.go

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package embedx
2+
3+
import (
4+
"fmt"
5+
"html/template"
6+
"io/fs"
7+
"strings"
8+
9+
"github.com/zeromicro/go-zero/core/logx"
10+
)
11+
12+
// Template is convenient wrapper of html template
13+
type Template struct {
14+
*template.Template
15+
}
16+
17+
// ParseFS is different from go's standard implement that it can recursively find all
18+
// template files ending with `.tmpl` and load them with a template name of their path.
19+
func (t Template) ParseFS(fsys fs.FS) (*template.Template, error) {
20+
var filenames []string
21+
_ = fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
22+
if err != nil {
23+
logx.Errorf("Parse template fs %q: %v", path, err)
24+
return nil
25+
}
26+
if d.IsDir() {
27+
return nil
28+
}
29+
if strings.HasSuffix(path, ".tmpl") {
30+
filenames = append(filenames, path)
31+
}
32+
return nil
33+
})
34+
return parseFiles(t.Template, readFileFS(fsys), filenames...)
35+
}
36+
37+
// parseFiles is the helper for the method and function. If the argument
38+
// template is nil, it is created from the first file.
39+
func parseFiles(t *template.Template, readFile func(string) (string, []byte, error), filenames ...string) (*template.Template, error) {
40+
if len(filenames) == 0 {
41+
// Not really a problem, but be consistent.
42+
return nil, fmt.Errorf("html/template: no files named in call to ParseFiles")
43+
}
44+
for _, filename := range filenames {
45+
name, b, err := readFile(filename)
46+
if err != nil {
47+
return nil, err
48+
}
49+
s := string(b)
50+
// First template becomes return value if not already defined,
51+
// and we use that one for subsequent New calls to associate
52+
// all the templates together. Also, if this file has the same name
53+
// as t, this file becomes the contents of t, so
54+
// t, err := New(name).Funcs(xxx).ParseFiles(name)
55+
// works. Otherwise we create a new template associated with t.
56+
var tmpl *template.Template
57+
if t == nil {
58+
t = template.New(name)
59+
}
60+
if name == t.Name() {
61+
tmpl = t
62+
} else {
63+
tmpl = t.New(name)
64+
}
65+
_, err = tmpl.Parse(s)
66+
if err != nil {
67+
return nil, err
68+
}
69+
}
70+
return t, nil
71+
}
72+
73+
func readFileFS(fsys fs.FS) func(string) (string, []byte, error) {
74+
return func(file string) (name string, b []byte, err error) {
75+
name = file
76+
b, err = fs.ReadFile(fsys, file)
77+
return
78+
}
79+
}

0 commit comments

Comments
 (0)