Index: /trunk/zs.go
===================================================================
--- /trunk/zs.go	(revision 31)
+++ /trunk/zs.go	(revision 32)
@@ -68,22 +68,5 @@
 // Use standard Go templates
 func render(s string, funcs Funcs, vars Vars) (string, error) {
-	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[strings.TrimSuffix(name, filepath.Ext(name))] = pluginFunc(name, vars)
-			}
-		}
-	}
-
+	f := makeFuncs(funcs, vars)
 	tmpl, err := template.New("").Funcs(template.FuncMap(f)).Parse(s)
 	if err != nil {
@@ -107,5 +90,5 @@
 		return err
 	}
-	v["content"] = string(blackfriday.MarkdownBasic([]byte(content)))
+	v["content"] = string(blackfriday.MarkdownCommon([]byte(content)))
 	if w == nil {
 		out, err := os.Create(filepath.Join(PUBDIR, renameExt(path, "", ".html")))
@@ -157,5 +140,5 @@
 		data[k] = v
 	}
-	for k, v := range funcs {
+	for k, v := range makeFuncs(funcs, Vars{}) {
 		data[k] = v
 	}
@@ -303,15 +286,21 @@
 		buildAll(true)
 	case "var":
-		fmt.Println(Var(args))
+		fmt.Println(Var(args...))
 	case "lorem":
-		fmt.Println(Lorem(args))
+		fmt.Println(Lorem(args...))
 	case "dateparse":
-		fmt.Println(DateParse(args))
+		fmt.Println(DateParse(args...))
 	case "datefmt":
-		fmt.Println(DateFmt(args))
+		fmt.Println(DateFmt(args...))
 	case "wc":
-		fmt.Println(WordCount(args))
-	case "timetoread":
-		fmt.Println(TimeToRead(args))
+		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
 	default:
 		err := run(path.Join(ZSDIR, cmd), args, globals(), os.Stdout)
Index: /trunk/zs_ext.go
===================================================================
--- /trunk/zs_ext.go	(revision 31)
+++ /trunk/zs_ext.go	(revision 32)
@@ -3,5 +3,8 @@
 import (
 	"bytes"
+	"log"
 	"os"
+	"path/filepath"
+	"sort"
 	"strconv"
 	"strings"
@@ -16,5 +19,5 @@
 // zs var <filename> -- returns list of variables and their values
 // zs var <filename> <var...> -- returns list of variable values
-func Var(args []string) string {
+func Var(args ...string) string {
 	if len(args) == 0 {
 		return "var: filename expected"
@@ -39,5 +42,5 @@
 
 // zs lorem <n> -- returns <n> random lorem ipsum sentences
-func Lorem(args []string) string {
+func Lorem(args ...string) string {
 	if len(args) > 1 {
 		return "lorem: invalid usage"
@@ -54,5 +57,5 @@
 
 // zs datefmt <fmt> <date> -- returns formatted date from unix time
-func DateFmt(args []string) string {
+func DateFmt(args ...string) string {
 	if len(args) == 0 || len(args) > 2 {
 		return "datefmt: invalid usage"
@@ -66,5 +69,5 @@
 
 // zs dateparse <fmt> <date> -- returns unix time from the formatted date
-func DateParse(args []string) string {
+func DateParse(args ...string) string {
 	if len(args) == 0 || len(args) > 2 {
 		return "dateparse: invalid usage"
@@ -78,5 +81,5 @@
 
 // zs wc <file> -- returns word count in the file (markdown, html or amber)
-func WordCount(args []string) int {
+func WordCount(args ...string) int {
 	if os.Getenv("ZS_RECURSION") != "" {
 		return 0
@@ -98,6 +101,68 @@
 
 // zs timetoread <file> -- returns number of minutes required to read the text
-func TimeToRead(args []string) int {
-	wc := WordCount(args)
+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 31)
+++ /trunk/zs_util.go	(revision 32)
@@ -4,4 +4,5 @@
 	"bytes"
 	"io"
+	"io/ioutil"
 	"log"
 	"os"
@@ -10,4 +11,27 @@
 	"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 {
@@ -28,4 +52,20 @@
 }
 
+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 {
@@ -39,7 +79,18 @@
 	}
 	return Funcs{
-		"exec": exec,
-		"zs": func(args ...string) string {
-			return exec(os.Args[0], args...)
+		"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...)
 		},
 	}
