Skip to content

Commit

Permalink
Remove embedded structs in config; fix new test script
Browse files Browse the repository at this point in the history
  • Loading branch information
infogulch committed Mar 25, 2024
1 parent eb734f6 commit ddd287f
Show file tree
Hide file tree
Showing 12 changed files with 144 additions and 76 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ jobs:
- name: Build
run: go build -v ./...

- name: Test
- name: Run Go Tests
run: go test -v ./...

- name: Run Hurl Tests
run: ./test/exec.sh
- name: Run Integration Tests
run: go run ./test

- name: Build binaries for all platforms
run: .github/workflows/release.sh
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
go.work*
xtemplate
xtemplate.exe
xtemplate.*
caddy
dist
10 changes: 5 additions & 5 deletions build.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ var extensionContentTypes = map[string]string{

func (x *Instance) addStaticFileHandler(path_ string) error {
// Open and stat the file
fsfile, err := x.config.Template.FS.Open(path_)
fsfile, err := x.config.FS.Open(path_)
if err != nil {
return fmt.Errorf("failed to open static file '%s': %w", path_, err)
}
Expand Down Expand Up @@ -115,7 +115,7 @@ func (x *Instance) addStaticFileHandler(path_ string) error {
file.encodings = []encodingInfo{{encoding: encoding, path: path_, size: size, modtime: stat.ModTime()}}

pattern := "GET " + identityPath
handler := staticFileHandler(x.config.Template.FS, file)
handler := staticFileHandler(x.config.FS, file)
if err = catch("add handler to servemux", func() { x.router.HandleFunc(pattern, handler) }); err != nil {
return err
}
Expand Down Expand Up @@ -150,7 +150,7 @@ func catch(description string, fn func()) (err error) {
var routeMatcher *regexp.Regexp = regexp.MustCompile("^(GET|POST|PUT|PATCH|DELETE|SSE) (.*)$")

func (x *Instance) addTemplateHandler(path_ string, minify *minify.M) error {
content, err := fs.ReadFile(x.config.Template.FS, path_)
content, err := fs.ReadFile(x.config.FS, path_)
if err != nil {
return fmt.Errorf("could not read template file '%s': %v", path_, err)
}
Expand All @@ -163,7 +163,7 @@ func (x *Instance) addTemplateHandler(path_ string, minify *minify.M) error {
path_ = path.Clean("/" + path_)
// parse each template file manually to have more control over its final
// names in the template namespace.
newtemplates, err := parse.Parse(path_, string(content), x.config.Template.Delimiters.Left, x.config.Template.Delimiters.Right, x.funcs, buliltinsSkeleton)
newtemplates, err := parse.Parse(path_, string(content), x.config.LDelim, x.config.RDelim, x.funcs, buliltinsSkeleton)
if err != nil {
return fmt.Errorf("could not parse template file '%s': %v", path_, err)
}
Expand All @@ -189,7 +189,7 @@ func (x *Instance) addTemplateHandler(path_ string, minify *minify.M) error {
continue
}
// strip the extension from the handled path
routePath := strings.TrimSuffix(path_, x.config.Template.TemplateExtension)
routePath := strings.TrimSuffix(path_, x.config.TemplateExtension)
// files named 'index' handle requests to the directory
if path.Base(routePath) == "index" {
routePath = path.Dir(routePath)
Expand Down
4 changes: 2 additions & 2 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func Main(overrides ...xtemplate.ConfigOverride) {
}

if args.WatchTemplates {
args.Watch = append(args.Watch, args.Config.Template.Path)
args.Watch = append(args.Watch, args.Config.TemplatesDir)
}
if len(args.Watch) != 0 {
_, err := watch.Watch(args.Watch, 200*time.Millisecond, log.WithGroup("fswatch"), func() bool {
Expand All @@ -60,5 +60,5 @@ func Main(overrides ...xtemplate.ConfigOverride) {
}
}

log.Info("server stopped", server.Serve(args.Listen))
log.Info("server stopped", slog.Any("exit", server.Serve(args.Listen)))
}
57 changes: 24 additions & 33 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,21 @@ func New() (c *Config) {
}

type Config struct {
// Control where and how templates are loaded.
Template struct {
// The FS to load templates from. Overrides Path if not nil.
FS fs.FS `json:"-" arg:"-"`

// The path to the templates directory.
Path string `json:"path,omitempty" arg:"-t,--template-dir"`

// File extension to search for to find template files. Default `.html`.
TemplateExtension string `json:"template_extension,omitempty" arg:"--template-ext"`

// The template action delimiters, default "{{" and "}}".
Delimiters struct {
Left string `json:"left,omitempty" arg:"--ldelim"`
Right string `json:"right,omitempty" arg:"--rdelim"`
} `json:"delimiters,omitempty"`

// Minify html templates as they're loaded.
//
// > Minification is the process of removing bytes from a file (such as
// whitespace) without changing its output and therefore shrinking its
// size and speeding up transmission over the internet
Minify bool `json:"minify,omitempty" arg:"--minify"`
} `json:"template,omitempty" arg:"-"`
// The FS to load templates from. Overrides Path if not nil.
FS fs.FS `json:"-" arg:"-"`

// The path to the templates directory.
TemplatesDir string `json:"templates_dir,omitempty" arg:"-t,--template-dir" default:"templates"`

// File extension to search for to find template files. Default `.html`.
TemplateExtension string `json:"template_extension,omitempty" arg:"--template-ext" default:".html"`

// The template action delimiters, default "{{" and "}}".
LDelim string `json:"left,omitempty" arg:"--ldelim" default:"{{"`
RDelim string `json:"right,omitempty" arg:"--rdelim" default:"}}"`

// Minify html templates at load time.
Minify bool `json:"minify,omitempty" arg:"-m,--minify" default:"true"`

Dot []DotConfig `json:"dot_config" arg:"-c,--dot-config,separate"`

Expand All @@ -53,20 +44,20 @@ type Config struct {

// FillDefaults sets default values for unset fields
func (config *Config) Defaults() *Config {
if config.Template.Path == "" {
config.Template.Path = "templates"
if config.TemplatesDir == "" {
config.TemplatesDir = "templates"
}

if config.Template.TemplateExtension == "" {
config.Template.TemplateExtension = ".html"
if config.TemplateExtension == "" {
config.TemplateExtension = ".html"
}

if config.Template.Delimiters.Left == "" {
config.Template.Delimiters.Left = "{{"
if config.LDelim == "" {
config.LDelim = "{{"
}

if config.Template.Delimiters.Right == "" {
config.Template.Delimiters.Right = "}}"
if config.RDelim == "" {
config.RDelim = "}}"
}

return config
Expand All @@ -76,7 +67,7 @@ type ConfigOverride func(*Config)

func WithTemplateFS(fs fs.FS) ConfigOverride {
return func(c *Config) {
c.Template.FS = fs
c.FS = fs
}
}

Expand Down
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ require (
)

require (
github.com/Hellseher/go-shellquote v0.0.0-20240324000151-06aa0e50b601 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.2.1 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/Hellseher/go-shellquote v0.0.0-20240324000151-06aa0e50b601 h1:qip+zkEa+Gwd/M0r6/mLk3Y4tcBCnzVhF1pNK8IzlQM=
github.com/Hellseher/go-shellquote v0.0.0-20240324000151-06aa0e50b601/go.mod h1:t4xAP6TrFpgvp/U7szp27rYrXXVu9yn3WhNylBvnfb8=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
Expand Down
2 changes: 1 addition & 1 deletion handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func staticFileHandler(fs fs.FS, fileinfo *fileInfo) http.HandlerFunc {

// If the request provides a hash, check that it matches. If not, we don't have that file.
queryhash := r.URL.Query().Get("hash")
if queryhash != "" && queryhash == fileinfo.hash {
if queryhash != "" && queryhash != fileinfo.hash {
log.LogAttrs(r.Context(), slog.LevelDebug, "request for file with wrong hash query parameter", slog.String("expected", fileinfo.hash), slog.String("queryhash", queryhash))
http.NotFound(w, r)
return
Expand Down
14 changes: 7 additions & 7 deletions instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ func (config Config) Instance() (*Instance, error) {
inst.config.Logger = inst.config.Logger.With(slog.Int64("instance", inst.id))
inst.config.Logger.Info("initializing")

if inst.config.Template.FS == nil {
inst.config.Template.FS = os.DirFS(inst.config.Template.Path)
if inst.config.FS == nil {
inst.config.FS = os.DirFS(inst.config.TemplatesDir)
}

{
Expand All @@ -95,27 +95,27 @@ func (config Config) Instance() (*Instance, error) {

inst.files = make(map[string]*fileInfo)
inst.router = http.NewServeMux()
inst.templates = template.New(".").Delims(inst.config.Template.Delimiters.Left, inst.config.Template.Delimiters.Right).Funcs(inst.funcs)
inst.templates = template.New(".").Delims(inst.config.LDelim, inst.config.RDelim).Funcs(inst.funcs)

var m *minify.M
if config.Template.Minify {
if config.Minify {
m = minify.New()
m.Add("text/css", &css.Minifier{})
m.Add("image/svg+xml", &svg.Minifier{})
m.Add("text/html", &html.Minifier{
TemplateDelims: [...]string{inst.config.Template.Delimiters.Left, inst.config.Template.Delimiters.Right},
TemplateDelims: [...]string{inst.config.LDelim, inst.config.RDelim},
})
m.AddRegexp(regexp.MustCompile("^(application|text)/(x-)?(java|ecma)script$"), &js.Minifier{})
}

inst.bufferDot = makeDot(slices.Concat([]DotConfig{{"X", instanceDotProvider{inst}}, {"Req", requestDotProvider{}}}, config.Dot, []DotConfig{{"Resp", responseDotProvider{}}}))
inst.flusherDot = makeDot(slices.Concat([]DotConfig{{"X", instanceDotProvider{inst}}, {"Req", requestDotProvider{}}}, config.Dot, []DotConfig{{"Flush", flushDotProvider{}}}))

if err := fs.WalkDir(inst.config.Template.FS, ".", func(path string, d fs.DirEntry, err error) error {
if err := fs.WalkDir(inst.config.FS, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil || d.IsDir() {
return err
}
if ext := filepath.Ext(path); ext == inst.config.Template.TemplateExtension {
if ext := filepath.Ext(path); ext == inst.config.TemplateExtension {
err = inst.addTemplateHandler(path, m)
} else {
err = inst.addStaticFileHandler(path)
Expand Down
5 changes: 5 additions & 0 deletions test/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/infogulch/xtemplate/test

go 1.22.1

require github.com/Hellseher/go-shellquote v0.0.0-20240324000151-06aa0e50b601
2 changes: 2 additions & 0 deletions test/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github.com/Hellseher/go-shellquote v0.0.0-20240324000151-06aa0e50b601 h1:qip+zkEa+Gwd/M0r6/mLk3Y4tcBCnzVhF1pNK8IzlQM=
github.com/Hellseher/go-shellquote v0.0.0-20240324000151-06aa0e50b601/go.mod h1:t4xAP6TrFpgvp/U7szp27rYrXXVu9yn3WhNylBvnfb8=
115 changes: 94 additions & 21 deletions test/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,91 @@ import (
"io/fs"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"sync"
"time"

"github.com/Hellseher/go-shellquote"
)

func main() {
log := try(os.Create("xtemplate.log"))("open log file")
log.Truncate(0)

argStr := `--loglevel -4 -c DB:sql:sqlite:file:test.sqlite -c FS:fs:./context`
args := try(shellquote.Split(argStr))("split args")
xtemplate := exec.Command("go", append([]string{"run", "../cmd"}, args...)...)
xtemplate.Stdout = log
xtemplate.Stderr = log
try0(xtemplate.Start(), "start xtemplate")
defer try0(xtemplate.Process.Kill(), "kill xtemplate")
go func() {
try0(xtemplate.Wait(), "wait for xtemplate")
os.Exit(1)
defer func() {
if err := recover(); err != nil {
fmt.Printf("exiting because: %v\n", err)
}
}()

files := try(fs.Glob(os.DirFS("."), "tests/*.hurl"))("glob files")
argStr = "--continue-on-error --test --report-html report"
args = try(shellquote.Split(argStr))("split args")
hurl := exec.Command("hurl", append(args, files...)...)
hurl.Stdout = os.Stdout
hurl.Stderr = os.Stderr
hurl.Run()
_, file, _, _ := runtime.Caller(0)
testdir := filepath.Dir(file)

if len(os.Args) > 1 {
switch os.Args[1] {
case "hurl":
goto hurl
}
}

// Build xtemplate
{
args := split(`go build -o xtemplate ../cmd`)
cmd := exec.Command(args[0], args[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stdout
cmd.Dir = testdir
try0(cmd.Run(), "go build")
fmt.Println("~ Build ~")
}

// Run xtemplate, wait until its ready, exit test if it fails early
{
args := split(`./xtemplate --loglevel -4 -c DB:sql:sqlite:file:test.sqlite -c FS:fs:./context`)
cmd := exec.Command(args[0], args[1:]...)
cmd.Dir = testdir

logpath := filepath.Join(testdir, "xtemplate.log")
log := try(os.Create(logpath))("open log file")
try(log.Seek(0, 0))("seek to beginning")
defer log.Close()
cmd.Stdout = log
cmd.Stderr = log

try0(cmd.Start(), "start xtemplate")
defer kill(cmd)

go func() {
try0(cmd.Wait(), "wait for xtemplate")
time.Sleep(time.Second)
panic("xtemplate exited")
}()

waitUntilFileContainsString(logpath, "starting server")

fmt.Println("~ Run xtemplate ~")
}

hurl:
{
files := try(fs.Glob(os.DirFS(testdir), "tests/*.hurl"))("glob files")
args := split("hurl --continue-on-error --test --report-html report")
cmd := exec.Command(args[0], append(args[1:], files...)...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = testdir
defer kill(cmd)
try0(cmd.Run(), "run hurl")
fmt.Println("~ Run hurl ~")
}
}

func split(a string) []string { return try(shellquote.Split(a))("split args") }

func kill(c *exec.Cmd) {
err := c.Process.Kill()
if err != nil && err != os.ErrProcessDone {
panic(fmt.Sprintf("failed to kill %s: %v", c.Path, err))
}
}

func try[T any](t T, err error) func(string) T {
Expand All @@ -45,6 +103,21 @@ func try[T any](t T, err error) func(string) T {

func try0(err error, desc string) {
if err != nil {
panic(fmt.Sprintf(desc, err))
panic(fmt.Sprintf("failed to %s: %v\n", desc, err))
}
}

func waitUntilFileContainsString(filename string, needle string) {
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
for {
if strings.Contains(string(try(os.ReadFile(filename))("read file")), needle) {
wg.Done()
break
}
time.Sleep(10 * time.Millisecond)
}
}()
wg.Wait()
}

0 comments on commit ddd287f

Please sign in to comment.