Index: trunk/.gitignore
===================================================================
--- trunk/.gitignore	(revision 93)
+++ trunk/.gitignore	(revision 1)
@@ -1,9 +1,1 @@
-*~
-*.bak
-**.pub
-*.sw*
-
-/aya
-/dist
-/test.md
-*.tar.*
+zs
Index: trunk/LICENSE
===================================================================
--- trunk/LICENSE	(revision 93)
+++ 	(revision )
@@ -1,22 +1,0 @@
-The MIT License (MIT)
-
-Copyright (c) 2014 Serge Zaitsev <hello@zserge.com>
-Copyright (c) 2023 Izuru Yakumo <eternal-servant@yakumo.dev>
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
Index: trunk/Makefile
===================================================================
--- trunk/Makefile	(revision 93)
+++ 	(revision )
@@ -1,23 +1,0 @@
-DESTDIR ?= 
-GO ?= go
-GOFLAGS ?= -v -buildvcs=false -buildmode=exe -ldflags "-w -X `${GO} list`.Date=${DATE} -X `${GO} list`.Vendor=${GOOS} -X `${GO} list`.Version=${VERSION}"
-PREFIX ?= /usr/local
-BINDIR ?= ${PREFIX}/bin
-MANDIR ?= ${PREFIX}/man
-DATE ?= `date -u +%F`
-GOOS ?= `${GO} env GOOS`
-VERSION ?= 1.0H+${REV}
-REV ?= `svn info --show-item revision || git rev-list --all | wc -l`
-build:
-	${GO} build ${GOFLAGS} ./cmd/aya
-clean:
-	rm -f aya
-pre-install:
-	mkdir -p ${DESTDIR}${BINDIR}
-	mkdir -p ${DESTDIR}${MANDIR}/man1
-install:
-	install -m0755 aya ${DESTDIR}${BINDIR}
-	install -m0644 aya.1 ${DESTDIR}${MANDIR}/man1
-uninstall:
-	rm -f ${DESTDIR}${BINDIR}/aya
-	rm -f ${DESTDIR}${MANDIR}/man1/aya.1
Index: trunk/README.md
===================================================================
--- trunk/README.md	(revision 93)
+++ 	(revision )
@@ -1,109 +1,0 @@
-# aya
-
-aya is an extremely minimal static site generator written in Go.
-
-Named after [Aya Shameimaru](https://en.touhouwiki.net/wiki/Aya_Shameimaru) from [Touhou 9.5: Shoot the Bullet](https://en.touhouwiki.net/wiki/Shoot_the_Bullet)
-
-## Features
-
-* Zero configuration (no configuration file needed)
-* Cross-platform
-* Highly extensible
-* Works well for blogs and generic static websites (landing pages etc)
-* Easy to learn (you literally don't need to)
-* Fast (goes without saying)
-
-## Installation
-
-Build it manually provided you have Go (>=1.17) installed:
-
-	$ go install mahou-no-mori.yakumo.dev/aya/cmd/aya@latest (1)
-	--- or ---
-	$ git clone https://git.mentality.rip/novaburst/aya
-	$ cd aya
-	$ make
-	# make install
-
-(1) If you use this method, the `aya version` subcommand may print the wrong string,
-but it should not be a problem unless you use it on a page.
-
-You can also disable certain features at build time, with the `-tags` switch.
-Currently, these tags are available: `noamber`, `nogcss`.
-See `go help buildconstraint` for more details.
-
-## Ideology
-
-Keep your texts in markdown, [amber](https://github.com/eknkc/amber), or html format right in the main directory
-of your blog/site.
-
-Keep all service files (extensions, layout pages, deployment scripts etc)
-in the `.aya` subdirectory.
-
-Define variables in the header of the content files using [YAML](https://noyaml.com) :
-
-```markdown
-title: My web site
-keywords: best website, hello, world
----
-
-Markdown text goes after a header *separator*
-```
-
-Use placeholders for variables and plugins in your markdown or html
-files, e.g. `{{ title }}` or `{{ command arg1 arg2 }}.
-
-Write extensions in any language you like and put them into the `.aya`
-subdiretory.
-
-Everything the extensions prints to [stdout](https://man.freebsd.org/cgi/man.cgi?fd) becomes the value of the
-placeholder.
-
-Every variable from the content header will be passed via environment variables like `title` becomes `$AYA_TITLE` and so on. 
-There are some special variables:
-
-* `$AYA` - a path to the `aya` executable
-* `$AYA_OUTDIR` - a path to the directory with generated files
-* `$AYA_FILE` - a path to the currently processed markdown file
-* `$AYA_URL` - a URL for the currently generated page
-
-## Example of RSS generation
-
-Extensions can be written in any language you know (Bash, Python, Lua, JavaScript, Go, even Assembler). Here's an example of how to scan all markdown blog posts and create RSS items:
-
-``` bash
-#!/bin/sh
-echo "Generating RSS feed"
-
-echo '<?xml version="1.0" encoding="utf-8"?>' > $AYA_OUTDIR/blog/rss.xml
-echo '<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">' >> $AYA_OUTDIR/blog/rss.xml
-echo '<channel>' >> $AYA_OUTDIR/blog/rss.xml
-for f in ./blog/*/*.md ; do
-    d=$($AYA var $f date)
-    if [ ! -z $d ] ; then
-        timestamp=`gdate --date "$d" +%s`
-        url=`$AYA var $f url`
-        title=`$AYA var $f title | tr A-Z a-z`
-        descr=`$AYA var $f description`
-        echo $timestamp "<item><title>$title</title><link>https://technicalmarisa.yakumo.dev/blog/$url</link><description>$descr</description><pubDate>$(gdate --date @$timestamp -R)</pubDate><guid>http://technicalmarisa.yakumo.dev/blog/$url</guid></item>"
-    fi
-done | sort -r -n | cut -d' ' -f2- >> $AYA_OUTDIR/blog/rss.xml
-echo '</channel>' >> $AYA_OUTDIR/blog/rss.xml
-echo '</rss>' >> $AYA_OUTDIR/blog/rss.xml
-```
-
-## Hooks
-There are two special plugin names that are executed every time the build
-happens - `prehook` and `posthook`. You can define some global actions here like
-content generation, or additional commands, like LESS to CSS conversion
-
-Note, you can also place `.gcss` files for [gcss](https://github.com/yosssi/gcss) to process instead
-
-## Command line usage
-Read `aya(1)`
-
-## License
-The software is distributed under the [MIT/X11](LICENSE) license.
-
----
-
-Ayaya~
Index: trunk/aya.1
===================================================================
--- trunk/aya.1	(revision 93)
+++ 	(revision )
@@ -1,91 +1,0 @@
-.Dd $Mdocdate$
-.Dt AYA 1
-.Os
-.Sh NAME
-.Nm aya
-.Nd The fastest static site generator
-.Sh SYNOPSIS
-.Nm
-.Op build
-.Op clean
-.Op serve
-.Op var Ar file Ar variable
-.Op watch
-.Sh DESCRIPTION
-Does it need one?
-.Sh FEATURES
-.Bl -tag -width 11n -compact
-.It Zero configuration (no configuration file needed)
-.It Cross-platform
-.It Highly extensible
-.It Works well for blogs and generic static websites (landing pages etc)
-.It Easy to learn
-.It Fast (of course)
-.It Only 4 external dependencies!
-.El
-.Sh USAGE
-.Bl -tag -width 11n -compact
-.It build
-(Re-)build a site
-or a file in particular
-.It clean
-Removes the generated
-site directory
-.It serve
-Serves the .pub
-directory over
-HTTP
-.It var
-Print a list of variables
-defined in a given
-markdown file,
-including empty
-variables.
-.It watch
-(Re-)build a site if
-any file changed.
-.El
-.Sh HISTORY
-.Nm
-was forked from
-.Lk https://git.mills.io/prologic/zs
-due to disgust with how the project
-turned out to be.
-.Pp
-Later on, features from the original
-program were reimplemented and
-it took a different path from both.
-.Sh ENVIRONMENT
-.Bl -tag -width 11n
-.It Ev AYADIR
-Directory where
-extensions, layouts and
-hooks placed in to be used
-by the program
-.It Ev PUBDIR
-Directory where generated files are
-located
-.It Ev AYA_*
-Internal variables regarding a
-Markdown document in question,
-see
-.Dq aya var <file>
-.El
-.Sh EXIT STATUS
-.Ex -std
-.Sh AUTHORS
-.Nm
-is maintained by
-.An Izuru Yakumo
-.Aq Mt eternal-servant@yakumo.dev
-.Pp
-.Nm zs
-was made by
-.An Serge Zaitsev
-.Aq Mt hello@zserge.com
-.Sh BUGS
-Always seems to generate HTML5 no matter what
-DOCTYPE you place in the layout file.
-.Pp
-This has to be a problem with
-.Nm blackfriday
Index: trunk/doc.go
===================================================================
--- trunk/doc.go	(revision 93)
+++ 	(revision )
@@ -1,15 +1,0 @@
-// Package aya is [...]
-
-// Aya is the fastest static site generator
-// Requires zero configuration to get started
-// Named after Aya Shameimaru from Touhou Project
-
-// Aya is licensed under the MIT license
-// This is a hard fork of git.mills.io/prologic/zs, which in turn is a fork of zserge's zs
-// This one (re)incorporates the features removed in the former from the latter
-
-// Copyright (c) 2014 Serge Zaitsev <hello@zserge.com>
-// Copyright (c) 2023 Izuru Yakumo <yakumo.izuru@chaotic.ninja>
-
-// Ayaya~
-package aya
Index: trunk/go.mod
===================================================================
--- trunk/go.mod	(revision 93)
+++ 	(revision )
@@ -1,10 +1,0 @@
-module mahou-no-mori.yakumo.dev/aya
-
-go 1.17
-
-require (
-	github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385
-	github.com/russross/blackfriday/v2 v2.1.0
-	github.com/yosssi/gcss v0.1.0
-	gopkg.in/yaml.v3 v3.0.1
-)
Index: trunk/go.sum
===================================================================
--- trunk/go.sum	(revision 93)
+++ 	(revision )
@@ -1,10 +1,0 @@
-github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 h1:clC1lXBpe2kTj2VHdaIu9ajZQe4kcEY9j0NsnDDBZ3o=
-github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
-github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
-github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/yosssi/gcss v0.1.0 h1:jRuino7qq7kqntBIhT+0xSUI5/sBgCA/zCQ1Tuzd6Gg=
-github.com/yosssi/gcss v0.1.0/go.mod h1:M3mTPOWZWjVROkXKZ2AiDzOBOXu2MqQeDXF/nKO44sI=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
-gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Index: trunk/serve.go
===================================================================
--- trunk/serve.go	(revision 93)
+++ 	(revision )
@@ -1,40 +1,0 @@
-/* The following code has been taken from https://github.com/fogleman/serve and it is used on this project as a library */
-/* Attribution goes to its original author */
-package aya
-
-import (
-	"fmt"
-	"log"
-	"net/http"
-)
-
-// ResponseWriter wraps http.ResponseWriter to capture the HTTP status code
-type ResponseWriter struct {
-	http.ResponseWriter
-	StatusCode int
-}
-
-func (w *ResponseWriter) WriteHeader(statusCode int) {
-	w.StatusCode = statusCode
-	w.ResponseWriter.WriteHeader(statusCode)
-}
-
-// Handler wraps http.Handler to log served files
-type Handler struct {
-	http.Handler
-}
-
-func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	rw := &ResponseWriter{w, 0}
-	h.Handler.ServeHTTP(rw, r)
-	log.Println(r.RemoteAddr, r.Method, rw.StatusCode, r.URL)
-}
-
-// This function is called by the `aya serve` subcommand
-func HttpServe(Dir string, Port int) {
-	handler := &Handler{http.FileServer(http.Dir(Dir))}
-	http.Handle("/", handler)
-	addr := fmt.Sprintf("127.0.0.1:%d", Port)
-	fmt.Printf("\033[0;32mListening on %s\033[0m\n", addr)
-	log.Fatal(http.ListenAndServe(addr, nil))
-}
Index: trunk/usage.go
===================================================================
--- trunk/usage.go	(revision 93)
+++ 	(revision )
@@ -1,18 +1,0 @@
-package aya
-
-import (
-	"fmt"
-)
-
-// This function is called by the `aya help` subcommand
-func PrintUsage() {
-	fmt.Printf("aya/%v\n", PrintFullVersion())
-	fmt.Println("Homepage: https://suzunaan.yakumo.dev/aya")
-	fmt.Println("Repository: https://svn.yakumo.dev/repo/aya")
-	fmt.Println("==")
-	fmt.Println("build [file] · (Re)build a site or a file in particular")
-	fmt.Println("clean · Remove the generated .pub directory")
-	fmt.Println("==")
-	fmt.Println("For more information please read the manual page")
-	fmt.Println("man aya")
-}
Index: trunk/version.go
===================================================================
--- trunk/version.go	(revision 93)
+++ 	(revision )
@@ -1,23 +1,0 @@
-// This is used for setting build-time variables
-package aya
-
-import (
-	"fmt"
-)
-
-var (
-	// Variables set at build-time
-	Date string
-	Vendor string
-	Version string
-)
-
-// PrintVersion only displays the obvious
-func PrintVersion() string {
-	return fmt.Sprintf("%s", Version)
-}
-
-// PrintFullVersion display the full version and build
-func PrintFullVersion() string {
-	return fmt.Sprintf("%s, built at %s, on %s", Version, Date, Vendor)
-}
Index: trunk/zs.go
===================================================================
--- trunk/zs.go	(revision 1)
+++ trunk/zs.go	(revision 1)
@@ -0,0 +1,256 @@
+package main
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"log"
+	"os"
+	"os/exec"
+	"path"
+	"path/filepath"
+	"strings"
+	"time"
+
+	"github.com/russross/blackfriday"
+)
+
+const (
+	ZSDIR  = ".zs"
+	PUBDIR = ".pub"
+)
+
+func split2(s, delim string) (string, string) {
+	parts := strings.SplitN(s, delim, 2)
+	if len(parts) == 2 {
+		return parts[0], parts[1]
+	} else {
+		return parts[0], ""
+	}
+}
+
+func md(s string) (map[string]string, string) {
+	v := map[string]string{}
+	// FIXME: if no header?
+	header, body := split2(s, "\n\n")
+	for _, line := range strings.Split(header, "\n") {
+		key, value := split2(line, ":")
+		v[strings.ToLower(strings.TrimSpace(key))] = strings.TrimSpace(value)
+	}
+	return v, body
+}
+
+type EvalFn func(args []string, vars map[string]string) (string, error)
+
+func render(s string, vars map[string]string, eval EvalFn) (string, error) {
+	b := []byte(s)
+	delim_open := []byte("{{")
+	delim_close := []byte("}}")
+
+	out := bytes.NewBuffer(nil)
+	for {
+		if from := bytes.Index(b, delim_open); from == -1 {
+			out.Write(b)
+			return out.String(), nil
+		} else {
+			to := bytes.Index(b, delim_close)
+			if to == -1 {
+				return "", fmt.Errorf("Close delim not found")
+			} else {
+				out.Write(b[:from])
+				cmd := b[from+len(delim_open) : to]
+				b = b[to+len(delim_close):]
+				m := strings.Fields(string(cmd))
+				if len(m) == 1 {
+					if v, ok := vars[m[0]]; ok {
+						out.Write([]byte(v))
+						continue
+					}
+				}
+				if res, err := eval(m, vars); err == nil {
+					out.Write([]byte(res))
+				} else {
+					log.Println(err) // silent
+				}
+			}
+		}
+	}
+}
+
+func eval(cmd []string, vars map[string]string) (string, error) {
+	var outbuf, errbuf bytes.Buffer
+	c := exec.Command(path.Join(ZSDIR, cmd[0]), cmd[1:]...)
+	env := []string{"ZS=" + os.Args[0]}
+	for k, v := range vars {
+		env = append(env, "ZS_"+strings.ToUpper(k)+"="+v)
+	}
+	c.Env = append(c.Env, env...)
+	c.Stdout = &outbuf
+	c.Stderr = &errbuf
+	if err := c.Run(); err != nil {
+		log.Println(err)
+		c := exec.Command(path.Join(cmd[0]), cmd[1:]...)
+		c.Env = append(c.Env, env...)
+		c.Stdout = &outbuf
+		c.Stderr = &errbuf
+		if err := c.Run(); err != nil {
+			return "", err
+		}
+	}
+	if errbuf.Len() > 0 {
+		log.Println(errbuf.String())
+	}
+	return outbuf.String(), nil
+}
+
+func buildMarkdown(path string) error {
+	b, err := ioutil.ReadFile(path)
+	if err != nil {
+		return err
+	}
+	v, body := md(string(b))
+	defaultVars(v, path)
+	content, err := render(body, v, eval)
+	if err != nil {
+		return err
+	}
+	v["content"] = string(blackfriday.MarkdownBasic([]byte(content)))
+	b, err = ioutil.ReadFile(filepath.Join(ZSDIR, v["layout"]))
+	if err != nil {
+		return err
+	}
+	content, err = render(string(b), v, eval)
+	if err != nil {
+		return err
+	}
+	err = ioutil.WriteFile(v["output"], []byte(content), 0666)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func defaultVars(vars map[string]string, path string) {
+	if _, ok := vars["file"]; !ok {
+		vars["file"] = path
+	}
+	if _, ok := vars["url"]; !ok {
+		vars["url"] = path[:len(path)-len(filepath.Ext(path))] + ".html"
+		if strings.HasPrefix(vars["url"], "./") {
+			vars["url"] = vars["url"][2:]
+		}
+	}
+	if _, ok := vars["outdir"]; !ok {
+		vars["outdir"] = PUBDIR
+	}
+	if _, ok := vars["output"]; !ok {
+		vars["output"] = filepath.Join(PUBDIR, vars["url"])
+	}
+	if _, ok := vars["layout"]; !ok {
+		vars["layout"] = "index.html"
+	}
+}
+
+func copyFile(path string) error {
+	if in, err := os.Open(path); err != nil {
+		return err
+	} else {
+		defer in.Close()
+		if stat, err := in.Stat(); err != nil {
+			return err
+		} else {
+			// Directory?
+			if stat.Mode().IsDir() {
+				os.Mkdir(filepath.Join(PUBDIR, path), 0755)
+				return nil
+			}
+			if !stat.Mode().IsRegular() {
+				return nil
+			}
+		}
+		if out, err := os.Create(filepath.Join(PUBDIR, path)); err != nil {
+			return err
+		} else {
+			defer out.Close()
+			_, err = io.Copy(out, in)
+			return err
+		}
+	}
+}
+
+func buildAll(once bool) {
+	lastModified := time.Unix(0, 0)
+	for {
+		os.Mkdir(PUBDIR, 0755)
+		err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
+			// ignore hidden files and directories
+			if filepath.Base(path)[0] == '.' || strings.HasPrefix(path, ".") {
+				return nil
+			}
+
+			if info.ModTime().After(lastModified) {
+				ext := filepath.Ext(path)
+				if ext == ".md" || ext == "mkd" {
+					log.Println("mkd: ", path)
+					return buildMarkdown(path)
+				} else {
+					log.Println("raw: ", path)
+					return copyFile(path)
+				}
+			}
+			return nil
+		})
+		if err != nil {
+			log.Println("ERROR:", err)
+		}
+		lastModified = time.Now()
+		if once {
+			break
+		}
+		time.Sleep(1 * time.Second)
+	}
+}
+
+func main() {
+	if len(os.Args) == 1 {
+		fmt.Println(os.Args[0], "<command> [args]")
+		return
+	}
+	cmd := os.Args[1]
+	args := os.Args[2:]
+	switch cmd {
+	case "build":
+		buildAll(true)
+	case "watch":
+		buildAll(false) // pass duration
+	case "var":
+		if len(args) == 0 {
+			// print error
+			return
+		}
+		if b, err := ioutil.ReadFile(args[0]); err == nil {
+			vars, _ := md(string(b))
+			defaultVars(vars, args[0])
+			if len(args) > 1 {
+				for _, a := range args[1:] {
+					fmt.Println(vars[a])
+				}
+			} else {
+				for k, v := range vars {
+					fmt.Println(k + ":" + v)
+				}
+			}
+		} else {
+			// print error
+		}
+	default:
+		cmd := exec.Command(path.Join(ZSDIR, cmd), args...)
+		cmd.Env = append(cmd.Env, "ZS="+os.Args[0])
+		cmd.Stdout = os.Stdout
+		cmd.Stderr = os.Stderr
+		if err := cmd.Run(); err != nil {
+			log.Println(err)
+		}
+	}
+}
Index: trunk/zs_test.go
===================================================================
--- trunk/zs_test.go	(revision 1)
+++ trunk/zs_test.go	(revision 1)
@@ -0,0 +1,66 @@
+package main
+
+import "testing"
+
+func TestSplit2(t *testing.T) {
+	if a, b := split2("a:b", ":"); a != "a" || b != "b" {
+		t.Fail()
+	}
+	if a, b := split2(":b", ":"); a != "" || b != "b" {
+		t.Fail()
+	}
+	if a, b := split2("a:", ":"); a != "a" || b != "" {
+		t.Fail()
+	}
+	if a, b := split2(":", ":"); a != "" || b != "" {
+		t.Fail()
+	}
+	if a, b := split2("a", ":"); a != "a" || b != "" {
+		t.Fail()
+	}
+	if a, b := split2("", ":"); a != "" || b != "" {
+		t.Fail()
+	}
+}
+
+func TestMD(t *testing.T) {
+	v, body := md(`
+	title: Hello, world!
+	keywords: foo, bar, baz
+	empty:
+	bayan: [:|||:]
+
+this: is a content`)
+	if v["title"] != "Hello, world!" {
+		t.Error()
+	}
+	if v["keywords"] != "foo, bar, baz" {
+		t.Error()
+	}
+	if s, ok := v["empty"]; !ok || len(s) != 0 {
+		t.Error()
+	}
+	if v["bayan"] != "[:|||:]" {
+		t.Error()
+	}
+	if body != "this: is a content" {
+		t.Error(body)
+	}
+}
+
+func TestRender(t *testing.T) {
+	eval := func(a []string) ([]byte, error) {
+		return []byte("hello"), nil
+	}
+	vars := map[string]string{"foo": "bar"}
+
+	if s, err := render("plain text", vars, eval); err != nil || s != "plain text" {
+		t.Error()
+	}
+	if s, err := render("a {{greet}} text", vars, eval); err != nil || s != "a hello text" {
+		t.Error()
+	}
+	if s, err := render("{{greet}} x{{foo}}z", vars, eval); err != nil || s != "hello xbarz" {
+		t.Error()
+	}
+}
