diff --git a/.gitignore b/.gitignore index 40fbc12..881590c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /readme /replybot -/include \ No newline at end of file +/include +/macro \ No newline at end of file diff --git a/examples/macro/include.go b/examples/macro/include.go new file mode 100644 index 0000000..b59dfa0 --- /dev/null +++ b/examples/macro/include.go @@ -0,0 +1,93 @@ +/* + * Salix - Go templating engine + * Copyright (C) 2023 Elara Musayelyan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package main + +import ( + "embed" + "io/fs" + "log" + "net/http" + "time" + + "go.elara.ws/salix" +) + +//go:embed tmpls +var tmpls embed.FS + +func main() { + tmplsFS, err := fs.Sub(tmpls, "tmpls") + if err != nil { + log.Fatalln(err) + } + + ns := salix.New().WithVarMap(map[string]any{"now": time.Now}) + + err = ns.ParseFSGlob(tmplsFS, "*.html") + if err != nil { + log.Fatalln(err) + } + + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + tmpl, ok := ns.GetTemplate("home.html") + if !ok { + w.WriteHeader(http.StatusInternalServerError) + return + } + + name := r.URL.Query().Get("name") + vars := map[string]any{"title": "Home"} + if name != "" { + vars["name"] = name + } + + err = tmpl. + WithVarMap(vars). + Execute(w) + if err != nil { + log.Println(err) + w.WriteHeader(http.StatusInternalServerError) + return + } + }) + + http.HandleFunc("/about", func(w http.ResponseWriter, r *http.Request) { + tmpl, ok := ns.GetTemplate("about.html") + if !ok { + w.WriteHeader(http.StatusInternalServerError) + return + } + + err = tmpl. + WithVarMap(map[string]any{"title": "About"}). + Execute(w) + if err != nil { + log.Println(err) + w.WriteHeader(http.StatusInternalServerError) + return + } + }) + + log.Println("Starting HTTP server on port 8080") + + err = http.ListenAndServe(":8080", nil) + if err != nil { + log.Fatalln(err) + } +} diff --git a/examples/macro/tmpls/about.html b/examples/macro/tmpls/about.html new file mode 100644 index 0000000..ae498a4 --- /dev/null +++ b/examples/macro/tmpls/about.html @@ -0,0 +1,16 @@ +#macro("content"): +
+
+
+

About Salix

+

+ Salix (pronounced say-lix) is a Go templating engine inspired by Leaf. +

+ Salix's syntax is similar to Leaf and (in my opinion at least), it's much more fun to write than the Go template syntax. If you like this project, please star its repo. I hope you enjoy! :) +

+
+
+
+#!macro + +#include("base.html") \ No newline at end of file diff --git a/examples/macro/tmpls/base.html b/examples/macro/tmpls/base.html new file mode 100644 index 0000000..30c7f43 --- /dev/null +++ b/examples/macro/tmpls/base.html @@ -0,0 +1,55 @@ + + + #(title) + + + + + + #macro("content") + +
+
+
+

Copyright © #(now().Year()) Salix Contributors. Licensed under the GPLv3.

+
+
+
+ + + + + diff --git a/examples/macro/tmpls/home.html b/examples/macro/tmpls/home.html new file mode 100644 index 0000000..5a2501d --- /dev/null +++ b/examples/macro/tmpls/home.html @@ -0,0 +1,13 @@ +#macro("content"): +
+
+
+

Hello, #(name | "World")!

+

This is a demo of the Salix template engine.

+ About → +
+
+
+#!macro + +#include("base.html") \ No newline at end of file diff --git a/macro_tag.go b/macro_tag.go new file mode 100644 index 0000000..fbcafd7 --- /dev/null +++ b/macro_tag.go @@ -0,0 +1,47 @@ +package salix + +import ( + "errors" + + "go.elara.ws/salix/ast" +) + +var ( + ErrMacroInvalidArgs = errors.New("macro expects one string argument") + ErrNoSuchMacro = errors.New("no such template") +) + +// macroTag represents an #macro tag within a Salix template +type macroTag struct{} + +func (mt macroTag) Run(tc *TagContext, block, args []ast.Node) error { + if len(args) != 1 { + return ErrMacroInvalidArgs + } + + nameVal, err := tc.GetValue(args[0], nil) + if err != nil { + return err + } + + name, ok := nameVal.(string) + if !ok { + return ErrMacroInvalidArgs + } + + if len(block) == 0 { + tc.t.ns.mu.Lock() + macro, ok := tc.t.ns.macros[name] + if !ok { + return ErrNoSuchMacro + } + tc.t.ns.mu.Unlock() + return tc.Execute(macro, nil) + } else { + tc.t.ns.mu.Lock() + tc.t.ns.macros[name] = block + tc.t.ns.mu.Unlock() + } + + return nil +} diff --git a/namespace.go b/namespace.go index 68e458b..b530980 100644 --- a/namespace.go +++ b/namespace.go @@ -21,22 +21,26 @@ package salix import ( "reflect" "sync" + + "go.elara.ws/salix/ast" ) // Namespace represents a collection of templates that can include each other type Namespace struct { - mu sync.Mutex - tmpls map[string]*Template - vars map[string]reflect.Value - tags map[string]Tag + mu sync.Mutex + tmpls map[string]*Template + vars map[string]reflect.Value + tags map[string]Tag + macros map[string][]ast.Node } // New returns a new template namespace func New() *Namespace { return &Namespace{ - tmpls: map[string]*Template{}, - vars: map[string]reflect.Value{}, - tags: map[string]Tag{}, + tmpls: map[string]*Template{}, + vars: map[string]reflect.Value{}, + tags: map[string]Tag{}, + macros: map[string][]ast.Node{}, } } diff --git a/tags.go b/tags.go index 4532dc7..3e5934e 100644 --- a/tags.go +++ b/tags.go @@ -33,6 +33,7 @@ var globalTags = map[string]Tag{ "if": ifTag{}, "for": forTag{}, "include": includeTag{}, + "macro": macroTag{}, } // TagContext is passed to Tag implementations to allow them to control the interpreter