Add macro tag
This commit is contained in:
parent
3eca240b56
commit
17906cf329
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
|||||||
/readme
|
/readme
|
||||||
/replybot
|
/replybot
|
||||||
/include
|
/include
|
||||||
|
/macro
|
93
examples/macro/include.go
Normal file
93
examples/macro/include.go
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
16
examples/macro/tmpls/about.html
Normal file
16
examples/macro/tmpls/about.html
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#macro("content"):
|
||||||
|
<section class="hero is-fullheight-with-navbar">
|
||||||
|
<div class="hero-head">
|
||||||
|
<div class="container">
|
||||||
|
<p class="title has-text-centered mt-2">About Salix</p>
|
||||||
|
<p>
|
||||||
|
Salix (pronounced <i>say-lix</i>) is a Go templating engine inspired by <a href="https://github.com/vapor/leaf">Leaf</a>.
|
||||||
|
<br><br>
|
||||||
|
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! :)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
#!macro
|
||||||
|
|
||||||
|
#include("base.html")
|
55
examples/macro/tmpls/base.html
Normal file
55
examples/macro/tmpls/base.html
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>#(title)</title>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="navbar is-dark">
|
||||||
|
<div class="navbar-brand">
|
||||||
|
<a class="navbar-item" href="/">Salix</a>
|
||||||
|
|
||||||
|
<a class="navbar-burger" id="navbarMenuIcon" onclick="toggleNavMenu()">
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="navbar-menu" id="navbarMenu">
|
||||||
|
<div class="navbar-end">
|
||||||
|
<a class='navbar-item #(title == "Home" ? "is-active" : "")' href="/">
|
||||||
|
Home
|
||||||
|
</a>
|
||||||
|
<a class='navbar-item #(title == "About" ? "is-active" : "")' href="/about">
|
||||||
|
About
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
#macro("content")
|
||||||
|
|
||||||
|
<section class="hero is-small is-dark">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container">
|
||||||
|
<p class="has-text-centered">Copyright © #(now().Year()) Salix Contributors. Licensed under the GPLv3.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function toggleNavMenu() {
|
||||||
|
let navbarMenuIcon = document.getElementById("navbarMenuIcon");
|
||||||
|
let navbarMenu = document.getElementById("navbarMenu");
|
||||||
|
|
||||||
|
if (navbarMenu.classList.contains('is-active')) {
|
||||||
|
navbarMenuIcon.classList.remove('is-active')
|
||||||
|
navbarMenu.classList.remove('is-active')
|
||||||
|
} else {
|
||||||
|
navbarMenuIcon.classList.add('is-active')
|
||||||
|
navbarMenu.classList.add('is-active')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
13
examples/macro/tmpls/home.html
Normal file
13
examples/macro/tmpls/home.html
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#macro("content"):
|
||||||
|
<section class="hero is-fullheight-with-navbar">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container">
|
||||||
|
<p class="title">Hello, #(name | "World")!</p>
|
||||||
|
<p class="subtitle">This is a demo of the Salix template engine.</p>
|
||||||
|
<a class="button is-link is-rounded" href="/about">About →</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
#!macro
|
||||||
|
|
||||||
|
#include("base.html")
|
47
macro_tag.go
Normal file
47
macro_tag.go
Normal file
@ -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
|
||||||
|
}
|
18
namespace.go
18
namespace.go
@ -21,22 +21,26 @@ package salix
|
|||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"go.elara.ws/salix/ast"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Namespace represents a collection of templates that can include each other
|
// Namespace represents a collection of templates that can include each other
|
||||||
type Namespace struct {
|
type Namespace struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
tmpls map[string]*Template
|
tmpls map[string]*Template
|
||||||
vars map[string]reflect.Value
|
vars map[string]reflect.Value
|
||||||
tags map[string]Tag
|
tags map[string]Tag
|
||||||
|
macros map[string][]ast.Node
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new template namespace
|
// New returns a new template namespace
|
||||||
func New() *Namespace {
|
func New() *Namespace {
|
||||||
return &Namespace{
|
return &Namespace{
|
||||||
tmpls: map[string]*Template{},
|
tmpls: map[string]*Template{},
|
||||||
vars: map[string]reflect.Value{},
|
vars: map[string]reflect.Value{},
|
||||||
tags: map[string]Tag{},
|
tags: map[string]Tag{},
|
||||||
|
macros: map[string][]ast.Node{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
1
tags.go
1
tags.go
@ -33,6 +33,7 @@ var globalTags = map[string]Tag{
|
|||||||
"if": ifTag{},
|
"if": ifTag{},
|
||||||
"for": forTag{},
|
"for": forTag{},
|
||||||
"include": includeTag{},
|
"include": includeTag{},
|
||||||
|
"macro": macroTag{},
|
||||||
}
|
}
|
||||||
|
|
||||||
// TagContext is passed to Tag implementations to allow them to control the interpreter
|
// TagContext is passed to Tag implementations to allow them to control the interpreter
|
||||||
|
Loading…
Reference in New Issue
Block a user