147 lines
3.5 KiB
Go
147 lines
3.5 KiB
Go
package resources
|
|
|
|
import (
|
|
"embed"
|
|
"flag"
|
|
"git.thequux.com/thequux/ipasso/util/startup"
|
|
"github.com/CloudyKit/jet/v6"
|
|
"github.com/julienschmidt/httprouter"
|
|
"go.uber.org/zap"
|
|
"io"
|
|
"io/fs"
|
|
"net/http"
|
|
"os"
|
|
"path"
|
|
"strings"
|
|
)
|
|
|
|
var (
|
|
dumpResources = flag.Bool("extract-resources", false, "Extract resources to directory indicated by --external-resources")
|
|
externalResources = flag.String("external-resources", "", "Directory in which to find external resources")
|
|
)
|
|
|
|
//go:embed static templates
|
|
var embedded embed.FS
|
|
|
|
var Resources fs.FS = &embedded
|
|
|
|
var l *zap.Logger
|
|
var Templates TemplateSource
|
|
var StaticFiles fs.FS
|
|
|
|
func init() {
|
|
|
|
startup.Logger.Add(func() {
|
|
l = zap.L().Named("resources")
|
|
})
|
|
startup.PostFlags.Add(func() {
|
|
var err error
|
|
if *dumpResources {
|
|
if *externalResources == "" {
|
|
l.Fatal("Cannot extract resources without --external-resources")
|
|
}
|
|
extractResources(*externalResources)
|
|
os.Exit(0)
|
|
}
|
|
|
|
if *externalResources != "" {
|
|
Resources = os.DirFS(*externalResources)
|
|
}
|
|
|
|
fs.WalkDir(Resources, ".", func(path string, d fs.DirEntry, err error) error {
|
|
l.Debug("found static file", zap.String("path", path))
|
|
return nil
|
|
})
|
|
|
|
templateFS, err := fs.Sub(Resources, "templates")
|
|
if err != nil {
|
|
l.Fatal("Unable to find templates")
|
|
}
|
|
|
|
Templates = loadTemplates(templateFS, *externalResources == "")
|
|
StaticFiles, err = fs.Sub(Resources, "static")
|
|
if err != nil {
|
|
l.Fatal("Cannot find static files", zap.Error(err))
|
|
}
|
|
})
|
|
|
|
startup.Routes.Add(func(router *httprouter.Router) {
|
|
router.NotFound = http.FileServer(http.FS(StaticFiles))
|
|
})
|
|
}
|
|
|
|
func extractResources(dst string) {
|
|
if err := os.MkdirAll(dst, 0755); err != nil {
|
|
l.Fatal("Failed to create external resource directory", zap.Error(err))
|
|
}
|
|
_ = fs.WalkDir(embedded, "/", func(filePath string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
l.Warn("Failed to walk file", zap.String("path", filePath), zap.Error(err))
|
|
return nil
|
|
}
|
|
fullPath := path.Join(dst, filePath)
|
|
if d.IsDir() {
|
|
if err = os.Mkdir(fullPath, 0755); err != nil {
|
|
l.Warn("Failed to extract directory", zap.String("dst", fullPath), zap.Error(err))
|
|
return fs.SkipDir
|
|
}
|
|
} else {
|
|
data, err := fs.ReadFile(embedded, filePath)
|
|
if err == nil {
|
|
err = os.WriteFile(fullPath, data, 0644)
|
|
}
|
|
if err != nil {
|
|
l.Warn("Failed to copy file", zap.String("dst", fullPath), zap.Error(err))
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
type TemplateSource struct {
|
|
htmlCache *jet.Set
|
|
textCache *jet.Set
|
|
}
|
|
|
|
func loadTemplates(src fs.FS, devMode bool) TemplateSource {
|
|
var devModeOpt jet.Option
|
|
if devMode {
|
|
devModeOpt = jet.InDevelopmentMode()
|
|
} else {
|
|
devModeOpt = func(set *jet.Set) {}
|
|
}
|
|
|
|
loader := genFsLoader{fs: src}
|
|
return TemplateSource{
|
|
htmlCache: jet.NewSet(loader, devModeOpt),
|
|
textCache: jet.NewSet(loader, devModeOpt, jet.WithSafeWriter(nil)),
|
|
}
|
|
}
|
|
|
|
func (ts TemplateSource) HtmlTemplate(name string) (*jet.Template, error) {
|
|
return ts.htmlCache.GetTemplate(name)
|
|
}
|
|
|
|
func (ts TemplateSource) TextTemplate(name string) (*jet.Template, error) {
|
|
return ts.textCache.GetTemplate(name)
|
|
}
|
|
|
|
type genFsLoader struct {
|
|
fs fs.FS
|
|
}
|
|
|
|
func (g genFsLoader) Exists(templatePath string) bool {
|
|
templatePath = strings.TrimPrefix(templatePath, "/")
|
|
l.Debug("Probe", zap.String("path", templatePath))
|
|
v, err := g.fs.Open(templatePath)
|
|
if err == nil {
|
|
_ = v.Close()
|
|
}
|
|
return err == nil
|
|
}
|
|
|
|
func (g genFsLoader) Open(templatePath string) (io.ReadCloser, error) {
|
|
templatePath = strings.TrimPrefix(templatePath, "/")
|
|
return g.fs.Open(templatePath)
|
|
}
|