mirror of
https://git.mills.io/prologic/zs
synced 2024-11-29 08:46:13 +03:00
started migration to go templates
This commit is contained in:
parent
49a47d065d
commit
85f08718a7
2
testdata/page/index.html
vendored
2
testdata/page/index.html
vendored
@ -1,5 +1,5 @@
|
|||||||
<html>
|
<html>
|
||||||
<body>
|
<body>
|
||||||
<h1>{{ echo Hello }}</h1>
|
<h1>{{ println "Hello" }}</h1>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
155
zs.go
155
zs.go
@ -11,6 +11,7 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/eknkc/amber"
|
"github.com/eknkc/amber"
|
||||||
@ -25,8 +26,6 @@ const (
|
|||||||
|
|
||||||
type Vars map[string]string
|
type Vars map[string]string
|
||||||
|
|
||||||
type EvalFn func(args []string, vars Vars) (string, error)
|
|
||||||
|
|
||||||
// Splits a string in exactly two parts by delimiter
|
// Splits a string in exactly two parts by delimiter
|
||||||
// If no delimiter is found - the second string is be empty
|
// If no delimiter is found - the second string is be empty
|
||||||
func split2(s, delim string) (string, string) {
|
func split2(s, delim string) (string, string) {
|
||||||
@ -66,37 +65,24 @@ func md(path string) (Vars, string, error) {
|
|||||||
return v, body, nil
|
return v, body, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func render(s string, vars Vars, eval EvalFn) (string, error) {
|
// Use standard Go templates
|
||||||
delim_open := "{{"
|
func render(s string, funcs template.FuncMap, vars Vars) (string, error) {
|
||||||
delim_close := "}}"
|
f := template.FuncMap{}
|
||||||
|
for k, v := range funcs {
|
||||||
out := bytes.NewBuffer(nil)
|
f[k] = v
|
||||||
for {
|
|
||||||
if from := strings.Index(s, delim_open); from == -1 {
|
|
||||||
out.WriteString(s)
|
|
||||||
return out.String(), nil
|
|
||||||
} else {
|
|
||||||
if to := strings.Index(s, delim_close); to == -1 {
|
|
||||||
return "", fmt.Errorf("Close delim not found")
|
|
||||||
} else {
|
|
||||||
out.WriteString(s[:from])
|
|
||||||
cmd := s[from+len(delim_open) : to]
|
|
||||||
s = s[to+len(delim_close):]
|
|
||||||
m := strings.Fields(cmd)
|
|
||||||
if len(m) == 1 {
|
|
||||||
if v, ok := vars[m[0]]; ok {
|
|
||||||
out.WriteString(v)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if res, err := eval(m, vars); err == nil {
|
|
||||||
out.WriteString(res)
|
|
||||||
} else {
|
|
||||||
log.Println(err) // silent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
for k, v := range vars {
|
||||||
|
f[k] = varFunc(v)
|
||||||
|
}
|
||||||
|
tmpl, err := template.New("").Funcs(f).Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
out := &bytes.Buffer{}
|
||||||
|
if err := tmpl.Execute(out, vars); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(out.Bytes()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Converts zs markdown variables into environment variables
|
// Converts zs markdown variables into environment variables
|
||||||
@ -132,6 +118,8 @@ func run(cmd string, args []string, vars Vars, output io.Writer) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Expands macro: either replacing it with the variable value, or
|
||||||
|
// running the plugin command and replacing it with the command's output
|
||||||
func eval(cmd []string, vars Vars) (string, error) {
|
func eval(cmd []string, vars Vars) (string, error) {
|
||||||
outbuf := bytes.NewBuffer(nil)
|
outbuf := bytes.NewBuffer(nil)
|
||||||
err := run(path.Join(ZSDIR, cmd[0]), cmd[1:], vars, outbuf)
|
err := run(path.Join(ZSDIR, cmd[0]), cmd[1:], vars, outbuf)
|
||||||
@ -149,25 +137,31 @@ func eval(cmd []string, vars Vars) (string, error) {
|
|||||||
return outbuf.String(), nil
|
return outbuf.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildMarkdown(path string) error {
|
// Renders markdown with the given layout into html expanding all the macros
|
||||||
|
func buildMarkdown(path string, funcs template.FuncMap, vars Vars) error {
|
||||||
v, body, err := md(path)
|
v, body, err := md(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
content, err := render(body, v, eval)
|
content, err := render(body, funcs, v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
v["content"] = string(blackfriday.MarkdownBasic([]byte(content)))
|
v["content"] = string(blackfriday.MarkdownBasic([]byte(content)))
|
||||||
return buildPlain(filepath.Join(ZSDIR, v["layout"]), v)
|
if strings.HasSuffix(v["layout"], ".amber") {
|
||||||
|
return buildAmber(filepath.Join(ZSDIR, v["layout"]), funcs, v)
|
||||||
|
} else {
|
||||||
|
return buildPlain(filepath.Join(ZSDIR, v["layout"]), funcs, v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildPlain(path string, vars Vars) error {
|
// Renders text file expanding all variable macros inside it
|
||||||
|
func buildPlain(path string, funcs template.FuncMap, vars Vars) error {
|
||||||
b, err := ioutil.ReadFile(path)
|
b, err := ioutil.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
content, err := render(string(b), vars, eval)
|
content, err := render(string(b), funcs, vars)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -182,26 +176,8 @@ func buildPlain(path string, vars Vars) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildGCSS(path string) error {
|
// Renders .amber file into .html
|
||||||
f, err := os.Open(path)
|
func buildAmber(path string, funcs template.FuncMap, vars Vars) error {
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s := strings.TrimSuffix(path, ".gcss") + ".css"
|
|
||||||
log.Println(s)
|
|
||||||
css, err := os.Create(filepath.Join(PUBDIR, s))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer f.Close()
|
|
||||||
defer css.Close()
|
|
||||||
|
|
||||||
_, err = gcss.Compile(css, f)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildAmber(path string, vars Vars) error {
|
|
||||||
a := amber.New()
|
a := amber.New()
|
||||||
err := a.ParseFile(path)
|
err := a.ParseFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -221,6 +197,26 @@ func buildAmber(path string, vars Vars) error {
|
|||||||
return t.Execute(f, vars)
|
return t.Execute(f, vars)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compiles .gcss into .css
|
||||||
|
func buildGCSS(path string) error {
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s := strings.TrimSuffix(path, ".gcss") + ".css"
|
||||||
|
css, err := os.Create(filepath.Join(PUBDIR, s))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer f.Close()
|
||||||
|
defer css.Close()
|
||||||
|
|
||||||
|
_, err = gcss.Compile(css, f)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copies file from working directory into public directory
|
||||||
func copyFile(path string) (err error) {
|
func copyFile(path string) (err error) {
|
||||||
var in, out *os.File
|
var in, out *os.File
|
||||||
if in, err = os.Open(path); err == nil {
|
if in, err = os.Open(path); err == nil {
|
||||||
@ -233,11 +229,48 @@ func copyFile(path string) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func varFunc(s string) func() string {
|
||||||
|
return func() string {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pluginFunc(cmd string) func() string {
|
||||||
|
return func() string {
|
||||||
|
return "Not implemented yet"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createFuncs() template.FuncMap {
|
||||||
|
// Builtin functions
|
||||||
|
funcs := template.FuncMap{}
|
||||||
|
// Plugin functions
|
||||||
|
files, _ := ioutil.ReadDir(ZSDIR)
|
||||||
|
for _, f := range files {
|
||||||
|
if !f.IsDir() {
|
||||||
|
name := f.Name()
|
||||||
|
if !strings.HasSuffix(name, ".html") && !strings.HasSuffix(name, ".amber") {
|
||||||
|
funcs[strings.TrimSuffix(name, filepath.Ext(name))] = pluginFunc(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return funcs
|
||||||
|
}
|
||||||
|
|
||||||
func buildAll(once bool) {
|
func buildAll(once bool) {
|
||||||
lastModified := time.Unix(0, 0)
|
lastModified := time.Unix(0, 0)
|
||||||
modified := false
|
modified := false
|
||||||
|
// Convert env variables into zs global variables
|
||||||
|
globals := Vars{}
|
||||||
|
for _, e := range os.Environ() {
|
||||||
|
pair := strings.Split(e, "=")
|
||||||
|
if strings.HasPrefix(pair[0], "ZS_") {
|
||||||
|
globals[strings.ToLower(pair[0][3:])] = pair[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
for {
|
for {
|
||||||
os.Mkdir(PUBDIR, 0755)
|
os.Mkdir(PUBDIR, 0755)
|
||||||
|
funcs := createFuncs()
|
||||||
err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
|
err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
|
||||||
// ignore hidden files and directories
|
// ignore hidden files and directories
|
||||||
if filepath.Base(path)[0] == '.' || strings.HasPrefix(path, ".") {
|
if filepath.Base(path)[0] == '.' || strings.HasPrefix(path, ".") {
|
||||||
@ -250,19 +283,20 @@ func buildAll(once bool) {
|
|||||||
} else if info.ModTime().After(lastModified) {
|
} else if info.ModTime().After(lastModified) {
|
||||||
if !modified {
|
if !modified {
|
||||||
// About to be modified, so run pre-build hook
|
// About to be modified, so run pre-build hook
|
||||||
|
// FIXME on windows it might not work well
|
||||||
run(filepath.Join(ZSDIR, "pre"), []string{}, nil, nil)
|
run(filepath.Join(ZSDIR, "pre"), []string{}, nil, nil)
|
||||||
modified = true
|
modified = true
|
||||||
}
|
}
|
||||||
ext := filepath.Ext(path)
|
ext := filepath.Ext(path)
|
||||||
if ext == ".md" || ext == ".mkd" {
|
if ext == ".md" || ext == ".mkd" {
|
||||||
log.Println("md: ", path)
|
log.Println("md: ", path)
|
||||||
return buildMarkdown(path)
|
return buildMarkdown(path, funcs, globals)
|
||||||
} else if ext == ".html" || ext == ".xml" {
|
} else if ext == ".html" || ext == ".xml" {
|
||||||
log.Println("html: ", path)
|
log.Println("html: ", path)
|
||||||
return buildPlain(path, Vars{})
|
return buildPlain(path, funcs, globals)
|
||||||
} else if ext == ".amber" {
|
} else if ext == ".amber" {
|
||||||
log.Println("html: ", path)
|
log.Println("html: ", path)
|
||||||
return buildAmber(path, Vars{})
|
return buildAmber(path, funcs, globals)
|
||||||
} else if ext == ".gcss" {
|
} else if ext == ".gcss" {
|
||||||
log.Println("css: ", path)
|
log.Println("css: ", path)
|
||||||
return buildGCSS(path)
|
return buildGCSS(path)
|
||||||
@ -278,6 +312,7 @@ func buildAll(once bool) {
|
|||||||
}
|
}
|
||||||
if modified {
|
if modified {
|
||||||
// Something was modified, so post-build hook
|
// Something was modified, so post-build hook
|
||||||
|
// FIXME on windows it might not work well
|
||||||
run(filepath.Join(ZSDIR, "post"), []string{}, nil, nil)
|
run(filepath.Join(ZSDIR, "post"), []string{}, nil, nil)
|
||||||
modified = false
|
modified = false
|
||||||
}
|
}
|
||||||
|
29
zs_test.go
29
zs_test.go
@ -9,6 +9,7 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"text/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSplit2(t *testing.T) {
|
func TestSplit2(t *testing.T) {
|
||||||
@ -76,23 +77,29 @@ this: is a content`))
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRender(t *testing.T) {
|
func TestRender(t *testing.T) {
|
||||||
eval := func(a []string, vars Vars) (string, error) {
|
|
||||||
return "hello", nil
|
|
||||||
}
|
|
||||||
vars := map[string]string{"foo": "bar"}
|
vars := map[string]string{"foo": "bar"}
|
||||||
|
funcs := template.FuncMap{
|
||||||
|
"greet": func(s ...string) string {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return "hello"
|
||||||
|
} else {
|
||||||
|
return "hello " + strings.Join(s, " ")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
if s, err := render("plain text", vars, eval); err != nil || s != "plain text" {
|
if s, err := render("plain text", funcs, vars); err != nil || s != "plain text" {
|
||||||
t.Error()
|
t.Error(s, err)
|
||||||
}
|
}
|
||||||
if s, err := render("a {{greet}} text", vars, eval); err != nil || s != "a hello text" {
|
if s, err := render("a {{greet}} text", funcs, vars); err != nil || s != "a hello text" {
|
||||||
t.Error()
|
t.Error(s, err)
|
||||||
}
|
}
|
||||||
if s, err := render("{{greet}} x{{foo}}z", vars, eval); err != nil || s != "hello xbarz" {
|
if s, err := render("{{greet}} x{{foo}}z", funcs, vars); err != nil || s != "hello xbarz" {
|
||||||
t.Error()
|
t.Error(s, err)
|
||||||
}
|
}
|
||||||
// Test error case
|
// Test error case
|
||||||
if s, err := render("a {{greet text ", vars, eval); err == nil || len(s) != 0 {
|
if s, err := render("a {{greet text ", funcs, vars); err == nil || len(s) != 0 {
|
||||||
t.Error()
|
t.Error(s, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user