Index: trunk/zs.go
===================================================================
--- trunk/zs.go	(revision 32)
+++ trunk/zs.go	(revision 33)
@@ -8,4 +8,5 @@
 	"log"
 	"os"
+	"os/exec"
 	"path"
 	"path/filepath"
@@ -25,5 +26,70 @@
 
 type Vars map[string]string
-type Funcs template.FuncMap
+
+func renameExt(path, from, to string) string {
+	if from == "" {
+		from = filepath.Ext(path)
+	}
+	if strings.HasSuffix(path, from) {
+		return strings.TrimSuffix(path, from) + to
+	} else {
+		return path
+	}
+}
+
+func globals() Vars {
+	vars := Vars{}
+	for _, e := range os.Environ() {
+		pair := strings.Split(e, "=")
+		if strings.HasPrefix(pair[0], "ZS_") {
+			vars[strings.ToLower(pair[0][3:])] = pair[1]
+		}
+	}
+	return vars
+}
+
+// Converts zs markdown variables into environment variables
+func env(vars Vars) []string {
+	env := []string{"ZS=" + os.Args[0], "ZS_OUTDIR=" + PUBDIR}
+	env = append(env, os.Environ()...)
+	if vars != nil {
+		for k, v := range vars {
+			env = append(env, "ZS_"+strings.ToUpper(k)+"="+v)
+		}
+	}
+	return env
+}
+
+// Runs command with given arguments and variables, intercepts stderr and
+// redirects stdout into the given writer
+func run(cmd string, args []string, vars Vars, output io.Writer) error {
+	var errbuf bytes.Buffer
+	c := exec.Command(cmd, args...)
+	c.Env = env(vars)
+	c.Stdout = output
+	c.Stderr = &errbuf
+
+	err := c.Run()
+
+	if errbuf.Len() > 0 {
+		log.Println("ERROR:", errbuf.String())
+	}
+
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// 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) {
+	parts := strings.SplitN(s, delim, 2)
+	if len(parts) == 2 {
+		return parts[0], parts[1]
+	} else {
+		return parts[0], ""
+	}
+}
 
 // Parses markdown content. Returns parsed header variables and content
@@ -67,7 +133,6 @@
 
 // Use standard Go templates
-func render(s string, funcs Funcs, vars Vars) (string, error) {
-	f := makeFuncs(funcs, vars)
-	tmpl, err := template.New("").Funcs(template.FuncMap(f)).Parse(s)
+func render(s string, vars Vars) (string, error) {
+	tmpl, err := template.New("").Parse(s)
 	if err != nil {
 		return "", err
@@ -81,10 +146,10 @@
 
 // Renders markdown with the given layout into html expanding all the macros
-func buildMarkdown(path string, w io.Writer, funcs Funcs, vars Vars) error {
+func buildMarkdown(path string, w io.Writer, vars Vars) error {
 	v, body, err := md(path, vars)
 	if err != nil {
 		return err
 	}
-	content, err := render(body, funcs, v)
+	content, err := render(body, v)
 	if err != nil {
 		return err
@@ -100,17 +165,17 @@
 	}
 	if strings.HasSuffix(v["layout"], ".amber") {
-		return buildAmber(filepath.Join(ZSDIR, v["layout"]), w, funcs, v)
-	} else {
-		return buildHTML(filepath.Join(ZSDIR, v["layout"]), w, funcs, v)
+		return buildAmber(filepath.Join(ZSDIR, v["layout"]), w, v)
+	} else {
+		return buildHTML(filepath.Join(ZSDIR, v["layout"]), w, v)
 	}
 }
 
 // Renders text file expanding all variable macros inside it
-func buildHTML(path string, w io.Writer, funcs Funcs, vars Vars) error {
+func buildHTML(path string, w io.Writer, vars Vars) error {
 	b, err := ioutil.ReadFile(path)
 	if err != nil {
 		return err
 	}
-	content, err := render(string(b), funcs, vars)
+	content, err := render(string(b), vars)
 	if err != nil {
 		return err
@@ -129,5 +194,5 @@
 
 // Renders .amber file into .html
-func buildAmber(path string, w io.Writer, funcs Funcs, vars Vars) error {
+func buildAmber(path string, w io.Writer, vars Vars) error {
 	a := amber.New()
 	err := a.ParseFile(path)
@@ -140,7 +205,4 @@
 		data[k] = v
 	}
-	for k, v := range makeFuncs(funcs, Vars{}) {
-		data[k] = v
-	}
 
 	t, err := a.Compile()
@@ -199,12 +261,12 @@
 }
 
-func build(path string, w io.Writer, funcs Funcs, vars Vars) error {
+func build(path string, w io.Writer, vars Vars) error {
 	ext := filepath.Ext(path)
 	if ext == ".md" || ext == ".mkd" {
-		return buildMarkdown(path, w, funcs, vars)
+		return buildMarkdown(path, w, vars)
 	} else if ext == ".html" || ext == ".xml" {
-		return buildHTML(path, w, funcs, vars)
+		return buildHTML(path, w, vars)
 	} else if ext == ".amber" {
-		return buildAmber(path, w, funcs, vars)
+		return buildAmber(path, w, vars)
 	} else if ext == ".gcss" {
 		return buildGCSS(path, w)
@@ -221,5 +283,4 @@
 	for {
 		os.Mkdir(PUBDIR, 0755)
-		funcs := builtins()
 		err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
 			// ignore hidden files and directories
@@ -244,5 +305,5 @@
 				}
 				log.Println("build: ", path)
-				return build(path, nil, funcs, vars)
+				return build(path, nil, vars)
 			}
 			return nil
@@ -277,5 +338,5 @@
 			buildAll(false)
 		} else if len(args) == 1 {
-			if err := build(args[0], os.Stdout, builtins(), globals()); err != nil {
+			if err := build(args[0], os.Stdout, globals()); err != nil {
 				fmt.Println("ERROR: " + err.Error())
 			}
@@ -286,21 +347,23 @@
 		buildAll(true)
 	case "var":
-		fmt.Println(Var(args...))
-	case "lorem":
-		fmt.Println(Lorem(args...))
-	case "dateparse":
-		fmt.Println(DateParse(args...))
-	case "datefmt":
-		fmt.Println(DateFmt(args...))
-	case "wc":
-		fmt.Println(WordCount(args...))
-	case "ttr":
-		fmt.Println(TimeToRead(args...))
-	case "ls":
-		fmt.Println(strings.Join(List(args...), "\n"))
-	case "sort":
-		fmt.Println(strings.Join(Sort(args...), "\n"))
-	case "exec":
-		// TODO
+		if len(args) == 0 {
+			fmt.Println("var: filename expected")
+		} else {
+			s := ""
+			if vars, _, err := md(args[0], globals()); err != nil {
+				fmt.Println("var: " + err.Error())
+			} else {
+				if len(args) > 1 {
+					for _, a := range args[1:] {
+						s = s + vars[a] + "\n"
+					}
+				} else {
+					for k, v := range vars {
+						s = s + k + ":" + v + "\n"
+					}
+				}
+			}
+			fmt.Println(strings.TrimSpace(s))
+		}
 	default:
 		err := run(path.Join(ZSDIR, cmd), args, globals(), os.Stdout)
Index: trunk/zs_ext.go
===================================================================
--- trunk/zs_ext.go	(revision 32)
+++ 	(revision )
@@ -1,168 +1,0 @@
-package main
-
-import (
-	"bytes"
-	"log"
-	"os"
-	"path/filepath"
-	"sort"
-	"strconv"
-	"strings"
-	"time"
-
-	"math"
-
-	"github.com/drhodes/golorem"
-	"github.com/jaytaylor/html2text"
-)
-
-// zs var <filename> -- returns list of variables and their values
-// zs var <filename> <var...> -- returns list of variable values
-func Var(args ...string) string {
-	if len(args) == 0 {
-		return "var: filename expected"
-	} else {
-		s := ""
-		if vars, _, err := md(args[0], globals()); err != nil {
-			return "var: " + err.Error()
-		} else {
-			if len(args) > 1 {
-				for _, a := range args[1:] {
-					s = s + vars[a] + "\n"
-				}
-			} else {
-				for k, v := range vars {
-					s = s + k + ":" + v + "\n"
-				}
-			}
-		}
-		return strings.TrimSpace(s)
-	}
-}
-
-// zs lorem <n> -- returns <n> random lorem ipsum sentences
-func Lorem(args ...string) string {
-	if len(args) > 1 {
-		return "lorem: invalid usage"
-	}
-	if len(args) == 0 {
-		return lorem.Paragraph(5, 5)
-	}
-	if n, err := strconv.Atoi(args[0]); err == nil {
-		return lorem.Paragraph(n, n)
-	} else {
-		return "lorem: " + err.Error()
-	}
-}
-
-// zs datefmt <fmt> <date> -- returns formatted date from unix time
-func DateFmt(args ...string) string {
-	if len(args) == 0 || len(args) > 2 {
-		return "datefmt: invalid usage"
-	}
-	if n, err := strconv.ParseInt(args[1], 10, 64); err == nil {
-		return time.Unix(n, 0).Format(args[0])
-	} else {
-		return "datefmt: " + err.Error()
-	}
-}
-
-// zs dateparse <fmt> <date> -- returns unix time from the formatted date
-func DateParse(args ...string) string {
-	if len(args) == 0 || len(args) > 2 {
-		return "dateparse: invalid usage"
-	}
-	if d, err := time.Parse(args[0], args[1]); err != nil {
-		return "dateparse: " + err.Error()
-	} else {
-		return strconv.FormatInt(d.Unix(), 10)
-	}
-}
-
-// zs wc <file> -- returns word count in the file (markdown, html or amber)
-func WordCount(args ...string) int {
-	if os.Getenv("ZS_RECURSION") != "" {
-		return 0
-	}
-	if len(args) != 1 {
-		return 0
-	}
-	os.Setenv("ZS_RECURSION", "1")
-	out := &bytes.Buffer{}
-	if err := build(args[0], out, builtins(), globals()); err != nil {
-		return 0
-	}
-	if s, err := html2text.FromString(string(out.Bytes())); err != nil {
-		return 0
-	} else {
-		return len(strings.Fields(s))
-	}
-}
-
-// zs timetoread <file> -- returns number of minutes required to read the text
-func TimeToRead(args ...string) int {
-	wc := WordCount(args...)
-	return int(math.Floor(float64(wc)/200.0 + .5))
-}
-
-// zs ls <dir> <regexp>
-func List(args ...string) []string {
-	if len(args) != 2 {
-		return []string{}
-	}
-
-	dir := args[0]
-	mask := args[1]
-
-	res := []string{}
-	filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
-		if err != nil {
-			return nil
-		}
-		if !info.IsDir() {
-			if ok, err := filepath.Match(mask, info.Name()); ok && err == nil {
-				res = append(res, path)
-			}
-		}
-		return nil
-	})
-	return res
-}
-
-// zs sort <key> <files...>
-func Sort(args ...string) []string {
-	delim := -1
-	for i, s := range args {
-		if s == "--" {
-			delim = i
-		}
-	}
-	cmd := []string{"var", "title"}
-	if delim != -1 {
-		cmd = args[:delim]
-		args = args[delim+1:]
-	}
-
-	sorted := map[string][]string{}
-	sortedKeys := []string{}
-	for _, f := range args {
-		params := append(cmd, f)
-		out := bytes.NewBuffer(nil)
-		run(os.Args[0], params, globals(), out)
-		val := string(out.Bytes())
-		sorted[val] = append(sorted[val], f)
-		sortedKeys = append(sortedKeys, val)
-	}
-	log.Println(sortedKeys)
-	sort.Strings(sortedKeys)
-	if !asc {
-	}
-
-	list := []string{}
-	for _, k := range sortedKeys {
-		vals := sorted[k]
-		sort.Strings(vals)
-		list = append(list, vals...)
-	}
-	return list
-}
Index: trunk/zs_util.go
===================================================================
--- trunk/zs_util.go	(revision 32)
+++ 	(revision )
@@ -1,163 +1,0 @@
-package main
-
-import (
-	"bytes"
-	"io"
-	"io/ioutil"
-	"log"
-	"os"
-	"os/exec"
-	"path/filepath"
-	"strings"
-)
-
-func makeFuncs(funcs Funcs, vars Vars) Funcs {
-	f := Funcs{}
-	for k, v := range funcs {
-		f[k] = v
-	}
-	for k, v := range vars {
-		f[k] = varFunc(v)
-	}
-	// Plugin functions
-	files, _ := ioutil.ReadDir(ZSDIR)
-	for _, file := range files {
-		if !file.IsDir() {
-			name := file.Name()
-			if !strings.HasSuffix(name, ".html") && !strings.HasSuffix(name, ".amber") {
-				f[renameExt(name, "", "")] = pluginFunc(name, vars)
-			} else {
-				f[renameExt(name, "", "")] = partialFunc(name, f, vars)
-			}
-		}
-	}
-	return f
-}
-
-func varFunc(s string) func() string {
-	return func() string {
-		return s
-	}
-}
-
-func pluginFunc(cmd string, vars Vars) func(args ...string) string {
-	return func(args ...string) string {
-		out := bytes.NewBuffer(nil)
-		if err := run(filepath.Join(ZSDIR, cmd), args, vars, out); err != nil {
-			return cmd + ":" + err.Error()
-		} else {
-			return string(out.Bytes())
-		}
-	}
-}
-
-func partialFunc(name string, funcs Funcs, vars Vars) func() string {
-	return func() string {
-		var err error
-		w := bytes.NewBuffer(nil)
-		if strings.HasSuffix(name, ".amber") {
-			err = buildAmber(filepath.Join(ZSDIR, name), w, funcs, vars)
-		} else {
-			err = buildHTML(filepath.Join(ZSDIR, name), w, funcs, vars)
-		}
-		if err != nil {
-			return name + ":" + err.Error()
-		}
-		return string(w.Bytes())
-	}
-}
-
-func builtins() Funcs {
-	exec := func(cmd string, args ...string) string {
-		out := bytes.NewBuffer(nil)
-		if err := run(cmd, args, Vars{}, out); err != nil {
-			return cmd + ":" + err.Error()
-		} else {
-			return string(out.Bytes())
-		}
-		return ""
-	}
-	return Funcs{
-		"exec":      exec,
-		"var":       Var,
-		"lorem":     Lorem,
-		"dateparse": DateParse,
-		"datefmt":   DateFmt,
-		"wc":        WordCount,
-		"ttr":       TimeToRead,
-		"ls":        List,
-		"...": func(args ...string) []string {
-			return append([]string{"..."}, args...)
-		},
-		"sort": func(args ...string) []string {
-
-			return Sort(args...)
-		},
-	}
-}
-
-func renameExt(path, from, to string) string {
-	if from == "" {
-		from = filepath.Ext(path)
-	}
-	if strings.HasSuffix(path, from) {
-		return strings.TrimSuffix(path, from) + to
-	} else {
-		return path
-	}
-}
-
-func globals() Vars {
-	vars := Vars{}
-	for _, e := range os.Environ() {
-		pair := strings.Split(e, "=")
-		if strings.HasPrefix(pair[0], "ZS_") {
-			vars[strings.ToLower(pair[0][3:])] = pair[1]
-		}
-	}
-	return vars
-}
-
-// Converts zs markdown variables into environment variables
-func env(vars Vars) []string {
-	env := []string{"ZS=" + os.Args[0], "ZS_OUTDIR=" + PUBDIR}
-	env = append(env, os.Environ()...)
-	if vars != nil {
-		for k, v := range vars {
-			env = append(env, "ZS_"+strings.ToUpper(k)+"="+v)
-		}
-	}
-	return env
-}
-
-// Runs command with given arguments and variables, intercepts stderr and
-// redirects stdout into the given writer
-func run(cmd string, args []string, vars Vars, output io.Writer) error {
-	var errbuf bytes.Buffer
-	c := exec.Command(cmd, args...)
-	c.Env = env(vars)
-	c.Stdout = output
-	c.Stderr = &errbuf
-
-	err := c.Run()
-
-	if errbuf.Len() > 0 {
-		log.Println("ERROR:", errbuf.String())
-	}
-
-	if err != nil {
-		return err
-	}
-	return nil
-}
-
-// 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) {
-	parts := strings.SplitN(s, delim, 2)
-	if len(parts) == 2 {
-		return parts[0], parts[1]
-	} else {
-		return parts[0], ""
-	}
-}
