changeset 18:ae3116ea938b draft

started migration to go templates
author zaitsev.serge
date Sat, 29 Aug 2015 16:46:05 +0000
parents 0214b1b5f5eb
children 802b96e67bae
files testdata/page/index.html zs.go zs_test.go
diffstat 3 files changed, 114 insertions(+), 72 deletions(-) [+]
line wrap: on
line diff
--- a/testdata/page/index.html	Sat Aug 29 15:47:16 2015 +0000
+++ b/testdata/page/index.html	Sat Aug 29 16:46:05 2015 +0000
@@ -1,5 +1,5 @@
 <html>
 	<body>
-		<h1>{{ echo Hello }}</h1>
+		<h1>{{ println "Hello" }}</h1>
 	</body>
 </html>
--- a/zs.go	Sat Aug 29 15:47:16 2015 +0000
+++ b/zs.go	Sat Aug 29 16:46:05 2015 +0000
@@ -11,6 +11,7 @@
 	"path"
 	"path/filepath"
 	"strings"
+	"text/template"
 	"time"
 
 	"github.com/eknkc/amber"
@@ -25,8 +26,6 @@
 
 type Vars map[string]string
 
-type EvalFn func(args []string, vars Vars) (string, error)
-
 // Splits a string in exactly two parts by delimiter
 // If no delimiter is found - the second string is be empty
 func split2(s, delim string) (string, string) {
@@ -66,37 +65,24 @@
 	return v, body, nil
 }
 
-func render(s string, vars Vars, eval EvalFn) (string, error) {
-	delim_open := "{{"
-	delim_close := "}}"
-
-	out := bytes.NewBuffer(nil)
-	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
-				}
-			}
-		}
+// Use standard Go templates
+func render(s string, funcs template.FuncMap, vars Vars) (string, error) {
+	f := template.FuncMap{}
+	for k, v := range funcs {
+		f[k] = v
+	}
+	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
@@ -132,6 +118,8 @@
 	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) {
 	outbuf := bytes.NewBuffer(nil)
 	err := run(path.Join(ZSDIR, cmd[0]), cmd[1:], vars, outbuf)
@@ -149,25 +137,31 @@
 	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)
 	if err != nil {
 		return err
 	}
-	content, err := render(body, v, eval)
+	content, err := render(body, funcs, v)
 	if err != nil {
 		return err
 	}
 	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)
 	if err != nil {
 		return err
 	}
-	content, err := render(string(b), vars, eval)
+	content, err := render(string(b), funcs, vars)
 	if err != nil {
 		return err
 	}
@@ -182,26 +176,8 @@
 	return nil
 }
 
-func buildGCSS(path string) error {
-	f, err := os.Open(path)
-	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 {
+// Renders .amber file into .html
+func buildAmber(path string, funcs template.FuncMap, vars Vars) error {
 	a := amber.New()
 	err := a.ParseFile(path)
 	if err != nil {
@@ -221,6 +197,26 @@
 	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) {
 	var in, out *os.File
 	if in, err = os.Open(path); err == nil {
@@ -233,11 +229,48 @@
 	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) {
 	lastModified := time.Unix(0, 0)
 	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 {
 		os.Mkdir(PUBDIR, 0755)
+		funcs := createFuncs()
 		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, ".") {
@@ -250,19 +283,20 @@
 			} else if info.ModTime().After(lastModified) {
 				if !modified {
 					// 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)
 					modified = true
 				}
 				ext := filepath.Ext(path)
 				if ext == ".md" || ext == ".mkd" {
 					log.Println("md: ", path)
-					return buildMarkdown(path)
+					return buildMarkdown(path, funcs, globals)
 				} else if ext == ".html" || ext == ".xml" {
 					log.Println("html: ", path)
-					return buildPlain(path, Vars{})
+					return buildPlain(path, funcs, globals)
 				} else if ext == ".amber" {
 					log.Println("html: ", path)
-					return buildAmber(path, Vars{})
+					return buildAmber(path, funcs, globals)
 				} else if ext == ".gcss" {
 					log.Println("css: ", path)
 					return buildGCSS(path)
@@ -278,6 +312,7 @@
 		}
 		if modified {
 			// Something was modified, so post-build hook
+			// FIXME on windows it might not work well
 			run(filepath.Join(ZSDIR, "post"), []string{}, nil, nil)
 			modified = false
 		}
--- a/zs_test.go	Sat Aug 29 15:47:16 2015 +0000
+++ b/zs_test.go	Sat Aug 29 16:46:05 2015 +0000
@@ -9,6 +9,7 @@
 	"os/exec"
 	"strings"
 	"testing"
+	"text/template"
 )
 
 func TestSplit2(t *testing.T) {
@@ -76,23 +77,29 @@
 }
 
 func TestRender(t *testing.T) {
-	eval := func(a []string, vars Vars) (string, error) {
-		return "hello", nil
+	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, " ")
+			}
+		},
 	}
-	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("plain text", funcs, vars); err != nil || s != "plain text" {
+		t.Error(s, err)
 	}
-	if s, err := render("a {{greet}} text", vars, eval); err != nil || s != "a hello text" {
-		t.Error()
+	if s, err := render("a {{greet}} text", funcs, vars); err != nil || s != "a hello text" {
+		t.Error(s, err)
 	}
-	if s, err := render("{{greet}} x{{foo}}z", vars, eval); err != nil || s != "hello xbarz" {
-		t.Error()
+	if s, err := render("{{greet}} x{{foo}}z", funcs, vars); err != nil || s != "hello xbarz" {
+		t.Error(s, err)
 	}
 	// Test error case
-	if s, err := render("a {{greet text ", vars, eval); err == nil || len(s) != 0 {
-		t.Error()
+	if s, err := render("a {{greet text ", funcs, vars); err == nil || len(s) != 0 {
+		t.Error(s, err)
 	}
 }