Changeset 19 in code
- Timestamp:
- Aug 29, 2015, 4:46:05 PM (10 years ago)
- Location:
- trunk
- Files:
-
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/testdata/page/index.html
r17 r19 1 1 <html> 2 2 <body> 3 <h1>{{ echo Hello}}</h1>3 <h1>{{ println "Hello" }}</h1> 4 4 </body> 5 5 </html> -
trunk/zs.go
r18 r19 12 12 "path/filepath" 13 13 "strings" 14 "text/template" 14 15 "time" 15 16 … … 25 26 26 27 type Vars map[string]string 27 28 type EvalFn func(args []string, vars Vars) (string, error)29 28 30 29 // Splits a string in exactly two parts by delimiter … … 67 66 } 68 67 69 func render(s string, vars Vars, eval EvalFn) (string, error) { 70 delim_open := "{{" 71 delim_close := "}}" 72 73 out := bytes.NewBuffer(nil) 74 for { 75 if from := strings.Index(s, delim_open); from == -1 { 76 out.WriteString(s) 77 return out.String(), nil 78 } else { 79 if to := strings.Index(s, delim_close); to == -1 { 80 return "", fmt.Errorf("Close delim not found") 81 } else { 82 out.WriteString(s[:from]) 83 cmd := s[from+len(delim_open) : to] 84 s = s[to+len(delim_close):] 85 m := strings.Fields(cmd) 86 if len(m) == 1 { 87 if v, ok := vars[m[0]]; ok { 88 out.WriteString(v) 89 continue 90 } 91 } 92 if res, err := eval(m, vars); err == nil { 93 out.WriteString(res) 94 } else { 95 log.Println(err) // silent 96 } 97 } 98 } 99 } 68 // Use standard Go templates 69 func render(s string, funcs template.FuncMap, vars Vars) (string, error) { 70 f := template.FuncMap{} 71 for k, v := range funcs { 72 f[k] = v 73 } 74 for k, v := range vars { 75 f[k] = varFunc(v) 76 } 77 tmpl, err := template.New("").Funcs(f).Parse(s) 78 if err != nil { 79 return "", err 80 } 81 out := &bytes.Buffer{} 82 if err := tmpl.Execute(out, vars); err != nil { 83 return "", err 84 } 85 return string(out.Bytes()), nil 100 86 } 101 87 … … 133 119 } 134 120 121 // Expands macro: either replacing it with the variable value, or 122 // running the plugin command and replacing it with the command's output 135 123 func eval(cmd []string, vars Vars) (string, error) { 136 124 outbuf := bytes.NewBuffer(nil) … … 150 138 } 151 139 152 func buildMarkdown(path string) error { 140 // Renders markdown with the given layout into html expanding all the macros 141 func buildMarkdown(path string, funcs template.FuncMap, vars Vars) error { 153 142 v, body, err := md(path) 154 143 if err != nil { 155 144 return err 156 145 } 157 content, err := render(body, v, eval)146 content, err := render(body, funcs, v) 158 147 if err != nil { 159 148 return err 160 149 } 161 150 v["content"] = string(blackfriday.MarkdownBasic([]byte(content))) 162 return buildPlain(filepath.Join(ZSDIR, v["layout"]), v) 163 } 164 165 func buildPlain(path string, vars Vars) error { 151 if strings.HasSuffix(v["layout"], ".amber") { 152 return buildAmber(filepath.Join(ZSDIR, v["layout"]), funcs, v) 153 } else { 154 return buildPlain(filepath.Join(ZSDIR, v["layout"]), funcs, v) 155 } 156 } 157 158 // Renders text file expanding all variable macros inside it 159 func buildPlain(path string, funcs template.FuncMap, vars Vars) error { 166 160 b, err := ioutil.ReadFile(path) 167 161 if err != nil { 168 162 return err 169 163 } 170 content, err := render(string(b), vars, eval)164 content, err := render(string(b), funcs, vars) 171 165 if err != nil { 172 166 return err … … 183 177 } 184 178 185 func buildGCSS(path string) error { 186 f, err := os.Open(path) 187 if err != nil { 188 return err 189 } 190 s := strings.TrimSuffix(path, ".gcss") + ".css" 191 log.Println(s) 192 css, err := os.Create(filepath.Join(PUBDIR, s)) 193 if err != nil { 194 return err 195 } 196 197 defer f.Close() 198 defer css.Close() 199 200 _, err = gcss.Compile(css, f) 201 return err 202 } 203 204 func buildAmber(path string, vars Vars) error { 179 // Renders .amber file into .html 180 func buildAmber(path string, funcs template.FuncMap, vars Vars) error { 205 181 a := amber.New() 206 182 err := a.ParseFile(path) … … 222 198 } 223 199 200 // Compiles .gcss into .css 201 func buildGCSS(path string) error { 202 f, err := os.Open(path) 203 if err != nil { 204 return err 205 } 206 s := strings.TrimSuffix(path, ".gcss") + ".css" 207 css, err := os.Create(filepath.Join(PUBDIR, s)) 208 if err != nil { 209 return err 210 } 211 212 defer f.Close() 213 defer css.Close() 214 215 _, err = gcss.Compile(css, f) 216 return err 217 } 218 219 // Copies file from working directory into public directory 224 220 func copyFile(path string) (err error) { 225 221 var in, out *os.File … … 234 230 } 235 231 232 func varFunc(s string) func() string { 233 return func() string { 234 return s 235 } 236 } 237 238 func pluginFunc(cmd string) func() string { 239 return func() string { 240 return "Not implemented yet" 241 } 242 } 243 244 func createFuncs() template.FuncMap { 245 // Builtin functions 246 funcs := template.FuncMap{} 247 // Plugin functions 248 files, _ := ioutil.ReadDir(ZSDIR) 249 for _, f := range files { 250 if !f.IsDir() { 251 name := f.Name() 252 if !strings.HasSuffix(name, ".html") && !strings.HasSuffix(name, ".amber") { 253 funcs[strings.TrimSuffix(name, filepath.Ext(name))] = pluginFunc(name) 254 } 255 } 256 } 257 return funcs 258 } 259 236 260 func buildAll(once bool) { 237 261 lastModified := time.Unix(0, 0) 238 262 modified := false 263 // Convert env variables into zs global variables 264 globals := Vars{} 265 for _, e := range os.Environ() { 266 pair := strings.Split(e, "=") 267 if strings.HasPrefix(pair[0], "ZS_") { 268 globals[strings.ToLower(pair[0][3:])] = pair[1] 269 } 270 } 239 271 for { 240 272 os.Mkdir(PUBDIR, 0755) 273 funcs := createFuncs() 241 274 err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error { 242 275 // ignore hidden files and directories … … 251 284 if !modified { 252 285 // About to be modified, so run pre-build hook 286 // FIXME on windows it might not work well 253 287 run(filepath.Join(ZSDIR, "pre"), []string{}, nil, nil) 254 288 modified = true … … 257 291 if ext == ".md" || ext == ".mkd" { 258 292 log.Println("md: ", path) 259 return buildMarkdown(path )293 return buildMarkdown(path, funcs, globals) 260 294 } else if ext == ".html" || ext == ".xml" { 261 295 log.Println("html: ", path) 262 return buildPlain(path, Vars{})296 return buildPlain(path, funcs, globals) 263 297 } else if ext == ".amber" { 264 298 log.Println("html: ", path) 265 return buildAmber(path, Vars{})299 return buildAmber(path, funcs, globals) 266 300 } else if ext == ".gcss" { 267 301 log.Println("css: ", path) … … 279 313 if modified { 280 314 // Something was modified, so post-build hook 315 // FIXME on windows it might not work well 281 316 run(filepath.Join(ZSDIR, "post"), []string{}, nil, nil) 282 317 modified = false -
trunk/zs_test.go
r18 r19 10 10 "strings" 11 11 "testing" 12 "text/template" 12 13 ) 13 14 … … 77 78 78 79 func TestRender(t *testing.T) { 79 eval := func(a []string, vars Vars) (string, error) { 80 return "hello", nil 80 vars := map[string]string{"foo": "bar"} 81 funcs := template.FuncMap{ 82 "greet": func(s ...string) string { 83 if len(s) == 0 { 84 return "hello" 85 } else { 86 return "hello " + strings.Join(s, " ") 87 } 88 }, 81 89 } 82 vars := map[string]string{"foo": "bar"}83 90 84 if s, err := render("plain text", vars, eval); err != nil || s != "plain text" {85 t.Error( )91 if s, err := render("plain text", funcs, vars); err != nil || s != "plain text" { 92 t.Error(s, err) 86 93 } 87 if s, err := render("a {{greet}} text", vars, eval); err != nil || s != "a hello text" {88 t.Error( )94 if s, err := render("a {{greet}} text", funcs, vars); err != nil || s != "a hello text" { 95 t.Error(s, err) 89 96 } 90 if s, err := render("{{greet}} x{{foo}}z", vars, eval); err != nil || s != "hello xbarz" {91 t.Error( )97 if s, err := render("{{greet}} x{{foo}}z", funcs, vars); err != nil || s != "hello xbarz" { 98 t.Error(s, err) 92 99 } 93 100 // Test error case 94 if s, err := render("a {{greet text ", vars, eval); err == nil || len(s) != 0 {95 t.Error( )101 if s, err := render("a {{greet text ", funcs, vars); err == nil || len(s) != 0 { 102 t.Error(s, err) 96 103 } 97 104 }
Note:
See TracChangeset
for help on using the changeset viewer.