Mercurial > yakumo_izuru > aya
annotate 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 |
rev | line source |
---|---|
0 | 1 package main |
2 | |
3 import ( | |
4 "bytes" | |
5 "fmt" | |
6 "io" | |
7 "io/ioutil" | |
8 "log" | |
9 "os" | |
10 "os/exec" | |
11 "path" | |
12 "path/filepath" | |
13 "strings" | |
18 | 14 "text/template" |
0 | 15 "time" |
16 | |
17 | 17 "github.com/eknkc/amber" |
0 | 18 "github.com/russross/blackfriday" |
17 | 19 "github.com/yosssi/gcss" |
0 | 20 ) |
21 | |
22 const ( | |
23 ZSDIR = ".zs" | |
24 PUBDIR = ".pub" | |
25 ) | |
26 | |
17 | 27 type Vars map[string]string |
3
53dea9841cd9
moved eval func type to the top, added some error logs
zaitsev.serge
parents:
2
diff
changeset
|
28 |
17 | 29 // Splits a string in exactly two parts by delimiter |
30 // If no delimiter is found - the second string is be empty | |
0 | 31 func split2(s, delim string) (string, string) { |
32 parts := strings.SplitN(s, delim, 2) | |
33 if len(parts) == 2 { | |
34 return parts[0], parts[1] | |
35 } else { | |
36 return parts[0], "" | |
37 } | |
38 } | |
39 | |
17 | 40 // Parses markdown content. Returns parsed header variables and content |
41 func md(path string) (Vars, string, error) { | |
42 b, err := ioutil.ReadFile(path) | |
43 if err != nil { | |
44 return nil, "", err | |
45 } | |
46 s := string(b) | |
6 | 47 url := path[:len(path)-len(filepath.Ext(path))] + ".html" |
17 | 48 v := Vars{ |
6 | 49 "file": path, |
50 "url": url, | |
51 "output": filepath.Join(PUBDIR, url), | |
52 "layout": "index.html", | |
53 } | |
2 | 54 if strings.Index(s, "\n\n") == -1 { |
17 | 55 return Vars{}, s, nil |
2 | 56 } |
0 | 57 header, body := split2(s, "\n\n") |
58 for _, line := range strings.Split(header, "\n") { | |
59 key, value := split2(line, ":") | |
60 v[strings.ToLower(strings.TrimSpace(key))] = strings.TrimSpace(value) | |
61 } | |
6 | 62 if strings.HasPrefix(v["url"], "./") { |
63 v["url"] = v["url"][2:] | |
64 } | |
17 | 65 return v, body, nil |
0 | 66 } |
67 | |
18 | 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) | |
0 | 76 } |
18 | 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 | |
0 | 86 } |
87 | |
17 | 88 // Converts zs markdown variables into environment variables |
89 func env(vars Vars) []string { | |
7
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
90 env := []string{"ZS=" + os.Args[0], "ZS_OUTDIR=" + PUBDIR} |
4
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
91 env = append(env, os.Environ()...) |
7
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
92 if vars != nil { |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
93 for k, v := range vars { |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
94 env = append(env, "ZS_"+strings.ToUpper(k)+"="+v) |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
95 } |
0 | 96 } |
4
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
97 return env |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
98 } |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
99 |
17 | 100 // Runs command with given arguments and variables, intercepts stderr and |
101 // redirects stdout into the given writer | |
102 func run(cmd string, args []string, vars Vars, output io.Writer) error { | |
4
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
103 var errbuf bytes.Buffer |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
104 c := exec.Command(cmd, args...) |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
105 c.Env = env(vars) |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
106 c.Stdout = output |
0 | 107 c.Stderr = &errbuf |
4
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
108 |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
109 err := c.Run() |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
110 |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
111 if errbuf.Len() > 0 { |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
112 log.Println(errbuf.String()) |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
113 } |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
114 |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
115 if err != nil { |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
116 return err |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
117 } |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
118 return nil |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
119 } |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
120 |
18 | 121 // Expands macro: either replacing it with the variable value, or |
122 // running the plugin command and replacing it with the command's output | |
17 | 123 func eval(cmd []string, vars Vars) (string, error) { |
4
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
124 outbuf := bytes.NewBuffer(nil) |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
125 err := run(path.Join(ZSDIR, cmd[0]), cmd[1:], vars, outbuf) |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
126 if err != nil { |
5 | 127 if _, ok := err.(*exec.ExitError); ok { |
128 return "", err | |
129 } | |
4
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
130 outbuf = bytes.NewBuffer(nil) |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
131 err := run(cmd[0], cmd[1:], vars, outbuf) |
5 | 132 // Return exit errors, but ignore if the command was not found |
133 if _, ok := err.(*exec.ExitError); ok { | |
0 | 134 return "", err |
135 } | |
136 } | |
137 return outbuf.String(), nil | |
138 } | |
139 | |
18 | 140 // Renders markdown with the given layout into html expanding all the macros |
141 func buildMarkdown(path string, funcs template.FuncMap, vars Vars) error { | |
17 | 142 v, body, err := md(path) |
0 | 143 if err != nil { |
144 return err | |
145 } | |
18 | 146 content, err := render(body, funcs, v) |
0 | 147 if err != nil { |
148 return err | |
149 } | |
150 v["content"] = string(blackfriday.MarkdownBasic([]byte(content))) | |
18 | 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 } | |
14 | 156 } |
157 | |
18 | 158 // Renders text file expanding all variable macros inside it |
159 func buildPlain(path string, funcs template.FuncMap, vars Vars) error { | |
14 | 160 b, err := ioutil.ReadFile(path) |
0 | 161 if err != nil { |
162 return err | |
163 } | |
18 | 164 content, err := render(string(b), funcs, vars) |
0 | 165 if err != nil { |
166 return err | |
167 } | |
14 | 168 output := filepath.Join(PUBDIR, path) |
169 if s, ok := vars["output"]; ok { | |
170 output = s | |
171 } | |
172 err = ioutil.WriteFile(output, []byte(content), 0666) | |
0 | 173 if err != nil { |
174 return err | |
175 } | |
176 return nil | |
177 } | |
178 | |
18 | 179 // Renders .amber file into .html |
180 func buildAmber(path string, funcs template.FuncMap, vars Vars) error { | |
17 | 181 a := amber.New() |
182 err := a.ParseFile(path) | |
183 if err != nil { | |
184 return err | |
185 } | |
186 t, err := a.Compile() | |
187 if err != nil { | |
188 return err | |
189 } | |
190 //amber.FuncMap = amber.FuncMap | |
191 s := strings.TrimSuffix(path, ".amber") + ".html" | |
192 f, err := os.Create(filepath.Join(PUBDIR, s)) | |
193 if err != nil { | |
194 return err | |
195 } | |
196 defer f.Close() | |
197 return t.Execute(f, vars) | |
198 } | |
199 | |
18 | 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 | |
7
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
220 func copyFile(path string) (err error) { |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
221 var in, out *os.File |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
222 if in, err = os.Open(path); err == nil { |
0 | 223 defer in.Close() |
7
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
224 if out, err = os.Create(filepath.Join(PUBDIR, path)); err == nil { |
0 | 225 defer out.Close() |
226 _, err = io.Copy(out, in) | |
227 } | |
228 } | |
7
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
229 return err |
0 | 230 } |
231 | |
18 | 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 | |
0 | 260 func buildAll(once bool) { |
261 lastModified := time.Unix(0, 0) | |
7
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
262 modified := false |
18 | 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 } | |
0 | 271 for { |
272 os.Mkdir(PUBDIR, 0755) | |
18 | 273 funcs := createFuncs() |
0 | 274 err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error { |
275 // ignore hidden files and directories | |
276 if filepath.Base(path)[0] == '.' || strings.HasPrefix(path, ".") { | |
277 return nil | |
278 } | |
279 | |
7
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
280 if info.IsDir() { |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
281 os.Mkdir(filepath.Join(PUBDIR, path), 0755) |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
282 return nil |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
283 } else if info.ModTime().After(lastModified) { |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
284 if !modified { |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
285 // About to be modified, so run pre-build hook |
18 | 286 // FIXME on windows it might not work well |
7
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
287 run(filepath.Join(ZSDIR, "pre"), []string{}, nil, nil) |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
288 modified = true |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
289 } |
0 | 290 ext := filepath.Ext(path) |
13 | 291 if ext == ".md" || ext == ".mkd" { |
17 | 292 log.Println("md: ", path) |
18 | 293 return buildMarkdown(path, funcs, globals) |
15 | 294 } else if ext == ".html" || ext == ".xml" { |
17 | 295 log.Println("html: ", path) |
18 | 296 return buildPlain(path, funcs, globals) |
17 | 297 } else if ext == ".amber" { |
298 log.Println("html: ", path) | |
18 | 299 return buildAmber(path, funcs, globals) |
17 | 300 } else if ext == ".gcss" { |
301 log.Println("css: ", path) | |
302 return buildGCSS(path) | |
0 | 303 } else { |
304 log.Println("raw: ", path) | |
305 return copyFile(path) | |
306 } | |
307 } | |
308 return nil | |
309 }) | |
310 if err != nil { | |
311 log.Println("ERROR:", err) | |
312 } | |
7
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
313 if modified { |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
314 // Something was modified, so post-build hook |
18 | 315 // FIXME on windows it might not work well |
7
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
316 run(filepath.Join(ZSDIR, "post"), []string{}, nil, nil) |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
317 modified = false |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
318 } |
0 | 319 lastModified = time.Now() |
320 if once { | |
321 break | |
322 } | |
323 time.Sleep(1 * time.Second) | |
324 } | |
325 } | |
326 | |
327 func main() { | |
328 if len(os.Args) == 1 { | |
329 fmt.Println(os.Args[0], "<command> [args]") | |
330 return | |
331 } | |
332 cmd := os.Args[1] | |
333 args := os.Args[2:] | |
334 switch cmd { | |
335 case "build": | |
336 buildAll(true) | |
337 case "watch": | |
338 buildAll(false) // pass duration | |
339 case "var": | |
340 if len(args) == 0 { | |
3
53dea9841cd9
moved eval func type to the top, added some error logs
zaitsev.serge
parents:
2
diff
changeset
|
341 log.Println("ERROR: filename expected") |
0 | 342 return |
343 } | |
17 | 344 if vars, _, err := md(args[0]); err == nil { |
0 | 345 if len(args) > 1 { |
346 for _, a := range args[1:] { | |
347 fmt.Println(vars[a]) | |
348 } | |
349 } else { | |
350 for k, v := range vars { | |
351 fmt.Println(k + ":" + v) | |
352 } | |
353 } | |
354 } else { | |
3
53dea9841cd9
moved eval func type to the top, added some error logs
zaitsev.serge
parents:
2
diff
changeset
|
355 log.Println(err) |
0 | 356 } |
357 default: | |
17 | 358 err := run(path.Join(ZSDIR, cmd), args, Vars{}, os.Stdout) |
4
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
359 if err != nil { |
0 | 360 log.Println(err) |
361 } | |
362 } | |
363 } |