Mercurial > yakumo_izuru > aya
comparison zs.go @ 18:ae3116ea938b draft
started migration to go templates
author | zaitsev.serge |
---|---|
date | Sat, 29 Aug 2015 16:46:05 +0000 |
parents | 0214b1b5f5eb |
children | 802b96e67bae |
comparison
equal
deleted
inserted
replaced
17:0214b1b5f5eb | 18:ae3116ea938b |
---|---|
9 "os" | 9 "os" |
10 "os/exec" | 10 "os/exec" |
11 "path" | 11 "path" |
12 "path/filepath" | 12 "path/filepath" |
13 "strings" | 13 "strings" |
14 "text/template" | |
14 "time" | 15 "time" |
15 | 16 |
16 "github.com/eknkc/amber" | 17 "github.com/eknkc/amber" |
17 "github.com/russross/blackfriday" | 18 "github.com/russross/blackfriday" |
18 "github.com/yosssi/gcss" | 19 "github.com/yosssi/gcss" |
22 ZSDIR = ".zs" | 23 ZSDIR = ".zs" |
23 PUBDIR = ".pub" | 24 PUBDIR = ".pub" |
24 ) | 25 ) |
25 | 26 |
26 type Vars map[string]string | 27 type Vars map[string]string |
27 | |
28 type EvalFn func(args []string, vars Vars) (string, error) | |
29 | 28 |
30 // Splits a string in exactly two parts by delimiter | 29 // Splits a string in exactly two parts by delimiter |
31 // If no delimiter is found - the second string is be empty | 30 // If no delimiter is found - the second string is be empty |
32 func split2(s, delim string) (string, string) { | 31 func split2(s, delim string) (string, string) { |
33 parts := strings.SplitN(s, delim, 2) | 32 parts := strings.SplitN(s, delim, 2) |
64 v["url"] = v["url"][2:] | 63 v["url"] = v["url"][2:] |
65 } | 64 } |
66 return v, body, nil | 65 return v, body, nil |
67 } | 66 } |
68 | 67 |
69 func render(s string, vars Vars, eval EvalFn) (string, error) { | 68 // Use standard Go templates |
70 delim_open := "{{" | 69 func render(s string, funcs template.FuncMap, vars Vars) (string, error) { |
71 delim_close := "}}" | 70 f := template.FuncMap{} |
72 | 71 for k, v := range funcs { |
73 out := bytes.NewBuffer(nil) | 72 f[k] = v |
74 for { | 73 } |
75 if from := strings.Index(s, delim_open); from == -1 { | 74 for k, v := range vars { |
76 out.WriteString(s) | 75 f[k] = varFunc(v) |
77 return out.String(), nil | 76 } |
78 } else { | 77 tmpl, err := template.New("").Funcs(f).Parse(s) |
79 if to := strings.Index(s, delim_close); to == -1 { | 78 if err != nil { |
80 return "", fmt.Errorf("Close delim not found") | 79 return "", err |
81 } else { | 80 } |
82 out.WriteString(s[:from]) | 81 out := &bytes.Buffer{} |
83 cmd := s[from+len(delim_open) : to] | 82 if err := tmpl.Execute(out, vars); err != nil { |
84 s = s[to+len(delim_close):] | 83 return "", err |
85 m := strings.Fields(cmd) | 84 } |
86 if len(m) == 1 { | 85 return string(out.Bytes()), nil |
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 } | |
100 } | 86 } |
101 | 87 |
102 // Converts zs markdown variables into environment variables | 88 // Converts zs markdown variables into environment variables |
103 func env(vars Vars) []string { | 89 func env(vars Vars) []string { |
104 env := []string{"ZS=" + os.Args[0], "ZS_OUTDIR=" + PUBDIR} | 90 env := []string{"ZS=" + os.Args[0], "ZS_OUTDIR=" + PUBDIR} |
130 return err | 116 return err |
131 } | 117 } |
132 return nil | 118 return nil |
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 func eval(cmd []string, vars Vars) (string, error) { | 123 func eval(cmd []string, vars Vars) (string, error) { |
136 outbuf := bytes.NewBuffer(nil) | 124 outbuf := bytes.NewBuffer(nil) |
137 err := run(path.Join(ZSDIR, cmd[0]), cmd[1:], vars, outbuf) | 125 err := run(path.Join(ZSDIR, cmd[0]), cmd[1:], vars, outbuf) |
138 if err != nil { | 126 if err != nil { |
139 if _, ok := err.(*exec.ExitError); ok { | 127 if _, ok := err.(*exec.ExitError); ok { |
147 } | 135 } |
148 } | 136 } |
149 return outbuf.String(), nil | 137 return outbuf.String(), 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 v, body, err := md(path) | 142 v, body, err := md(path) |
154 if err != nil { | 143 if err != nil { |
155 return err | 144 return err |
156 } | 145 } |
157 content, err := render(body, v, eval) | 146 content, err := render(body, funcs, v) |
158 if err != nil { | 147 if err != nil { |
159 return err | 148 return err |
160 } | 149 } |
161 v["content"] = string(blackfriday.MarkdownBasic([]byte(content))) | 150 v["content"] = string(blackfriday.MarkdownBasic([]byte(content))) |
162 return buildPlain(filepath.Join(ZSDIR, v["layout"]), v) | 151 if strings.HasSuffix(v["layout"], ".amber") { |
163 } | 152 return buildAmber(filepath.Join(ZSDIR, v["layout"]), funcs, v) |
164 | 153 } else { |
165 func buildPlain(path string, vars Vars) error { | 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 b, err := ioutil.ReadFile(path) | 160 b, err := ioutil.ReadFile(path) |
167 if err != nil { | 161 if err != nil { |
168 return err | 162 return err |
169 } | 163 } |
170 content, err := render(string(b), vars, eval) | 164 content, err := render(string(b), funcs, vars) |
171 if err != nil { | 165 if err != nil { |
172 return err | 166 return err |
173 } | 167 } |
174 output := filepath.Join(PUBDIR, path) | 168 output := filepath.Join(PUBDIR, path) |
175 if s, ok := vars["output"]; ok { | 169 if s, ok := vars["output"]; ok { |
180 return err | 174 return err |
181 } | 175 } |
182 return nil | 176 return nil |
183 } | 177 } |
184 | 178 |
185 func buildGCSS(path string) error { | 179 // Renders .amber file into .html |
186 f, err := os.Open(path) | 180 func buildAmber(path string, funcs template.FuncMap, vars Vars) error { |
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 { | |
205 a := amber.New() | 181 a := amber.New() |
206 err := a.ParseFile(path) | 182 err := a.ParseFile(path) |
207 if err != nil { | 183 if err != nil { |
208 return err | 184 return err |
209 } | 185 } |
219 } | 195 } |
220 defer f.Close() | 196 defer f.Close() |
221 return t.Execute(f, vars) | 197 return t.Execute(f, vars) |
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 func copyFile(path string) (err error) { | 220 func copyFile(path string) (err error) { |
225 var in, out *os.File | 221 var in, out *os.File |
226 if in, err = os.Open(path); err == nil { | 222 if in, err = os.Open(path); err == nil { |
227 defer in.Close() | 223 defer in.Close() |
228 if out, err = os.Create(filepath.Join(PUBDIR, path)); err == nil { | 224 if out, err = os.Create(filepath.Join(PUBDIR, path)); err == nil { |
231 } | 227 } |
232 } | 228 } |
233 return err | 229 return err |
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 func buildAll(once bool) { | 260 func buildAll(once bool) { |
237 lastModified := time.Unix(0, 0) | 261 lastModified := time.Unix(0, 0) |
238 modified := false | 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 for { | 271 for { |
240 os.Mkdir(PUBDIR, 0755) | 272 os.Mkdir(PUBDIR, 0755) |
273 funcs := createFuncs() | |
241 err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error { | 274 err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error { |
242 // ignore hidden files and directories | 275 // ignore hidden files and directories |
243 if filepath.Base(path)[0] == '.' || strings.HasPrefix(path, ".") { | 276 if filepath.Base(path)[0] == '.' || strings.HasPrefix(path, ".") { |
244 return nil | 277 return nil |
245 } | 278 } |
248 os.Mkdir(filepath.Join(PUBDIR, path), 0755) | 281 os.Mkdir(filepath.Join(PUBDIR, path), 0755) |
249 return nil | 282 return nil |
250 } else if info.ModTime().After(lastModified) { | 283 } else if info.ModTime().After(lastModified) { |
251 if !modified { | 284 if !modified { |
252 // About to be modified, so run pre-build hook | 285 // About to be modified, so run pre-build hook |
286 // FIXME on windows it might not work well | |
253 run(filepath.Join(ZSDIR, "pre"), []string{}, nil, nil) | 287 run(filepath.Join(ZSDIR, "pre"), []string{}, nil, nil) |
254 modified = true | 288 modified = true |
255 } | 289 } |
256 ext := filepath.Ext(path) | 290 ext := filepath.Ext(path) |
257 if ext == ".md" || ext == ".mkd" { | 291 if ext == ".md" || ext == ".mkd" { |
258 log.Println("md: ", path) | 292 log.Println("md: ", path) |
259 return buildMarkdown(path) | 293 return buildMarkdown(path, funcs, globals) |
260 } else if ext == ".html" || ext == ".xml" { | 294 } else if ext == ".html" || ext == ".xml" { |
261 log.Println("html: ", path) | 295 log.Println("html: ", path) |
262 return buildPlain(path, Vars{}) | 296 return buildPlain(path, funcs, globals) |
263 } else if ext == ".amber" { | 297 } else if ext == ".amber" { |
264 log.Println("html: ", path) | 298 log.Println("html: ", path) |
265 return buildAmber(path, Vars{}) | 299 return buildAmber(path, funcs, globals) |
266 } else if ext == ".gcss" { | 300 } else if ext == ".gcss" { |
267 log.Println("css: ", path) | 301 log.Println("css: ", path) |
268 return buildGCSS(path) | 302 return buildGCSS(path) |
269 } else { | 303 } else { |
270 log.Println("raw: ", path) | 304 log.Println("raw: ", path) |
276 if err != nil { | 310 if err != nil { |
277 log.Println("ERROR:", err) | 311 log.Println("ERROR:", err) |
278 } | 312 } |
279 if modified { | 313 if modified { |
280 // Something was modified, so post-build hook | 314 // Something was modified, so post-build hook |
315 // FIXME on windows it might not work well | |
281 run(filepath.Join(ZSDIR, "post"), []string{}, nil, nil) | 316 run(filepath.Join(ZSDIR, "post"), []string{}, nil, nil) |
282 modified = false | 317 modified = false |
283 } | 318 } |
284 lastModified = time.Now() | 319 lastModified = time.Now() |
285 if once { | 320 if once { |