Mercurial > yakumo_izuru > aya
annotate zs.go @ 19:802b96e67bae draft
added global variables, added default date for markdown
author | zaitsev.serge |
---|---|
date | Sat, 29 Aug 2015 17:54:55 +0000 |
parents | ae3116ea938b |
children | f8373c23a3ff |
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 |
19
802b96e67bae
added global variables, added default date for markdown
zaitsev.serge
parents:
18
diff
changeset
|
41 func md(path string, globals Vars) (Vars, string, error) { |
17 | 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 } | |
19
802b96e67bae
added global variables, added default date for markdown
zaitsev.serge
parents:
18
diff
changeset
|
54 if info, err := os.Stat(path); err == nil { |
802b96e67bae
added global variables, added default date for markdown
zaitsev.serge
parents:
18
diff
changeset
|
55 v["date"] = info.ModTime().Format("02-01-2006") |
802b96e67bae
added global variables, added default date for markdown
zaitsev.serge
parents:
18
diff
changeset
|
56 } |
802b96e67bae
added global variables, added default date for markdown
zaitsev.serge
parents:
18
diff
changeset
|
57 for name, value := range globals { |
802b96e67bae
added global variables, added default date for markdown
zaitsev.serge
parents:
18
diff
changeset
|
58 v[name] = value |
802b96e67bae
added global variables, added default date for markdown
zaitsev.serge
parents:
18
diff
changeset
|
59 } |
2 | 60 if strings.Index(s, "\n\n") == -1 { |
19
802b96e67bae
added global variables, added default date for markdown
zaitsev.serge
parents:
18
diff
changeset
|
61 return v, s, nil |
2 | 62 } |
0 | 63 header, body := split2(s, "\n\n") |
64 for _, line := range strings.Split(header, "\n") { | |
65 key, value := split2(line, ":") | |
66 v[strings.ToLower(strings.TrimSpace(key))] = strings.TrimSpace(value) | |
67 } | |
6 | 68 if strings.HasPrefix(v["url"], "./") { |
69 v["url"] = v["url"][2:] | |
70 } | |
17 | 71 return v, body, nil |
0 | 72 } |
73 | |
18 | 74 // Use standard Go templates |
75 func render(s string, funcs template.FuncMap, vars Vars) (string, error) { | |
76 f := template.FuncMap{} | |
77 for k, v := range funcs { | |
78 f[k] = v | |
79 } | |
80 for k, v := range vars { | |
81 f[k] = varFunc(v) | |
0 | 82 } |
18 | 83 tmpl, err := template.New("").Funcs(f).Parse(s) |
84 if err != nil { | |
85 return "", err | |
86 } | |
87 out := &bytes.Buffer{} | |
88 if err := tmpl.Execute(out, vars); err != nil { | |
89 return "", err | |
90 } | |
91 return string(out.Bytes()), nil | |
0 | 92 } |
93 | |
17 | 94 // Converts zs markdown variables into environment variables |
95 func env(vars Vars) []string { | |
7
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
96 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
|
97 env = append(env, os.Environ()...) |
7
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
98 if vars != nil { |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
99 for k, v := range vars { |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
100 env = append(env, "ZS_"+strings.ToUpper(k)+"="+v) |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
101 } |
0 | 102 } |
4
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
103 return env |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
104 } |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
105 |
17 | 106 // Runs command with given arguments and variables, intercepts stderr and |
107 // redirects stdout into the given writer | |
108 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
|
109 var errbuf bytes.Buffer |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
110 c := exec.Command(cmd, args...) |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
111 c.Env = env(vars) |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
112 c.Stdout = output |
0 | 113 c.Stderr = &errbuf |
4
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 err := c.Run() |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
116 |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
117 if errbuf.Len() > 0 { |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
118 log.Println(errbuf.String()) |
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 |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
121 if err != nil { |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
122 return err |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
123 } |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
124 return nil |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
125 } |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
126 |
18 | 127 // Expands macro: either replacing it with the variable value, or |
128 // running the plugin command and replacing it with the command's output | |
17 | 129 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
|
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(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
|
132 if err != nil { |
5 | 133 if _, ok := err.(*exec.ExitError); ok { |
134 return "", err | |
135 } | |
4
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
136 outbuf = bytes.NewBuffer(nil) |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
137 err := run(cmd[0], cmd[1:], vars, outbuf) |
5 | 138 // Return exit errors, but ignore if the command was not found |
139 if _, ok := err.(*exec.ExitError); ok { | |
0 | 140 return "", err |
141 } | |
142 } | |
143 return outbuf.String(), nil | |
144 } | |
145 | |
18 | 146 // Renders markdown with the given layout into html expanding all the macros |
147 func buildMarkdown(path string, funcs template.FuncMap, vars Vars) error { | |
19
802b96e67bae
added global variables, added default date for markdown
zaitsev.serge
parents:
18
diff
changeset
|
148 v, body, err := md(path, vars) |
0 | 149 if err != nil { |
150 return err | |
151 } | |
18 | 152 content, err := render(body, funcs, v) |
0 | 153 if err != nil { |
154 return err | |
155 } | |
156 v["content"] = string(blackfriday.MarkdownBasic([]byte(content))) | |
18 | 157 if strings.HasSuffix(v["layout"], ".amber") { |
158 return buildAmber(filepath.Join(ZSDIR, v["layout"]), funcs, v) | |
159 } else { | |
160 return buildPlain(filepath.Join(ZSDIR, v["layout"]), funcs, v) | |
161 } | |
14 | 162 } |
163 | |
18 | 164 // Renders text file expanding all variable macros inside it |
165 func buildPlain(path string, funcs template.FuncMap, vars Vars) error { | |
14 | 166 b, err := ioutil.ReadFile(path) |
0 | 167 if err != nil { |
168 return err | |
169 } | |
18 | 170 content, err := render(string(b), funcs, vars) |
0 | 171 if err != nil { |
172 return err | |
173 } | |
14 | 174 output := filepath.Join(PUBDIR, path) |
175 if s, ok := vars["output"]; ok { | |
176 output = s | |
177 } | |
178 err = ioutil.WriteFile(output, []byte(content), 0666) | |
0 | 179 if err != nil { |
180 return err | |
181 } | |
182 return nil | |
183 } | |
184 | |
18 | 185 // Renders .amber file into .html |
186 func buildAmber(path string, funcs template.FuncMap, vars Vars) error { | |
17 | 187 a := amber.New() |
188 err := a.ParseFile(path) | |
189 if err != nil { | |
190 return err | |
191 } | |
192 t, err := a.Compile() | |
193 if err != nil { | |
194 return err | |
195 } | |
196 //amber.FuncMap = amber.FuncMap | |
197 s := strings.TrimSuffix(path, ".amber") + ".html" | |
198 f, err := os.Create(filepath.Join(PUBDIR, s)) | |
199 if err != nil { | |
200 return err | |
201 } | |
202 defer f.Close() | |
203 return t.Execute(f, vars) | |
204 } | |
205 | |
18 | 206 // Compiles .gcss into .css |
207 func buildGCSS(path string) error { | |
208 f, err := os.Open(path) | |
209 if err != nil { | |
210 return err | |
211 } | |
212 s := strings.TrimSuffix(path, ".gcss") + ".css" | |
213 css, err := os.Create(filepath.Join(PUBDIR, s)) | |
214 if err != nil { | |
215 return err | |
216 } | |
217 | |
218 defer f.Close() | |
219 defer css.Close() | |
220 | |
221 _, err = gcss.Compile(css, f) | |
222 return err | |
223 } | |
224 | |
225 // 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
|
226 func copyFile(path string) (err error) { |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
227 var in, out *os.File |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
228 if in, err = os.Open(path); err == nil { |
0 | 229 defer in.Close() |
7
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
230 if out, err = os.Create(filepath.Join(PUBDIR, path)); err == nil { |
0 | 231 defer out.Close() |
232 _, err = io.Copy(out, in) | |
233 } | |
234 } | |
7
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
235 return err |
0 | 236 } |
237 | |
18 | 238 func varFunc(s string) func() string { |
239 return func() string { | |
240 return s | |
241 } | |
242 } | |
243 | |
244 func pluginFunc(cmd string) func() string { | |
245 return func() string { | |
246 return "Not implemented yet" | |
247 } | |
248 } | |
249 | |
250 func createFuncs() template.FuncMap { | |
251 // Builtin functions | |
19
802b96e67bae
added global variables, added default date for markdown
zaitsev.serge
parents:
18
diff
changeset
|
252 funcs := template.FuncMap{ |
802b96e67bae
added global variables, added default date for markdown
zaitsev.serge
parents:
18
diff
changeset
|
253 "exec": func(s ...string) string { |
802b96e67bae
added global variables, added default date for markdown
zaitsev.serge
parents:
18
diff
changeset
|
254 // Run external command with arguments |
802b96e67bae
added global variables, added default date for markdown
zaitsev.serge
parents:
18
diff
changeset
|
255 return "" |
802b96e67bae
added global variables, added default date for markdown
zaitsev.serge
parents:
18
diff
changeset
|
256 }, |
802b96e67bae
added global variables, added default date for markdown
zaitsev.serge
parents:
18
diff
changeset
|
257 "zs": func(args ...string) string { |
802b96e67bae
added global variables, added default date for markdown
zaitsev.serge
parents:
18
diff
changeset
|
258 // Run zs with arguments |
802b96e67bae
added global variables, added default date for markdown
zaitsev.serge
parents:
18
diff
changeset
|
259 return "" |
802b96e67bae
added global variables, added default date for markdown
zaitsev.serge
parents:
18
diff
changeset
|
260 }, |
802b96e67bae
added global variables, added default date for markdown
zaitsev.serge
parents:
18
diff
changeset
|
261 } |
18 | 262 // Plugin functions |
263 files, _ := ioutil.ReadDir(ZSDIR) | |
264 for _, f := range files { | |
265 if !f.IsDir() { | |
266 name := f.Name() | |
267 if !strings.HasSuffix(name, ".html") && !strings.HasSuffix(name, ".amber") { | |
268 funcs[strings.TrimSuffix(name, filepath.Ext(name))] = pluginFunc(name) | |
269 } | |
270 } | |
271 } | |
272 return funcs | |
273 } | |
274 | |
19
802b96e67bae
added global variables, added default date for markdown
zaitsev.serge
parents:
18
diff
changeset
|
275 func globals() Vars { |
802b96e67bae
added global variables, added default date for markdown
zaitsev.serge
parents:
18
diff
changeset
|
276 vars := Vars{} |
802b96e67bae
added global variables, added default date for markdown
zaitsev.serge
parents:
18
diff
changeset
|
277 for _, e := range os.Environ() { |
802b96e67bae
added global variables, added default date for markdown
zaitsev.serge
parents:
18
diff
changeset
|
278 pair := strings.Split(e, "=") |
802b96e67bae
added global variables, added default date for markdown
zaitsev.serge
parents:
18
diff
changeset
|
279 if strings.HasPrefix(pair[0], "ZS_") { |
802b96e67bae
added global variables, added default date for markdown
zaitsev.serge
parents:
18
diff
changeset
|
280 vars[strings.ToLower(pair[0][3:])] = pair[1] |
802b96e67bae
added global variables, added default date for markdown
zaitsev.serge
parents:
18
diff
changeset
|
281 } |
802b96e67bae
added global variables, added default date for markdown
zaitsev.serge
parents:
18
diff
changeset
|
282 } |
802b96e67bae
added global variables, added default date for markdown
zaitsev.serge
parents:
18
diff
changeset
|
283 return vars |
802b96e67bae
added global variables, added default date for markdown
zaitsev.serge
parents:
18
diff
changeset
|
284 } |
802b96e67bae
added global variables, added default date for markdown
zaitsev.serge
parents:
18
diff
changeset
|
285 |
0 | 286 func buildAll(once bool) { |
287 lastModified := time.Unix(0, 0) | |
7
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
288 modified := false |
19
802b96e67bae
added global variables, added default date for markdown
zaitsev.serge
parents:
18
diff
changeset
|
289 |
802b96e67bae
added global variables, added default date for markdown
zaitsev.serge
parents:
18
diff
changeset
|
290 vars := globals() |
0 | 291 for { |
292 os.Mkdir(PUBDIR, 0755) | |
18 | 293 funcs := createFuncs() |
0 | 294 err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error { |
295 // ignore hidden files and directories | |
296 if filepath.Base(path)[0] == '.' || strings.HasPrefix(path, ".") { | |
297 return nil | |
298 } | |
299 | |
7
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
300 if info.IsDir() { |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
301 os.Mkdir(filepath.Join(PUBDIR, path), 0755) |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
302 return nil |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
303 } else if info.ModTime().After(lastModified) { |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
304 if !modified { |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
305 // About to be modified, so run pre-build hook |
18 | 306 // 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
|
307 run(filepath.Join(ZSDIR, "pre"), []string{}, nil, nil) |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
308 modified = true |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
309 } |
0 | 310 ext := filepath.Ext(path) |
13 | 311 if ext == ".md" || ext == ".mkd" { |
17 | 312 log.Println("md: ", path) |
19
802b96e67bae
added global variables, added default date for markdown
zaitsev.serge
parents:
18
diff
changeset
|
313 return buildMarkdown(path, funcs, vars) |
15 | 314 } else if ext == ".html" || ext == ".xml" { |
17 | 315 log.Println("html: ", path) |
19
802b96e67bae
added global variables, added default date for markdown
zaitsev.serge
parents:
18
diff
changeset
|
316 return buildPlain(path, funcs, vars) |
17 | 317 } else if ext == ".amber" { |
318 log.Println("html: ", path) | |
19
802b96e67bae
added global variables, added default date for markdown
zaitsev.serge
parents:
18
diff
changeset
|
319 return buildAmber(path, funcs, vars) |
17 | 320 } else if ext == ".gcss" { |
321 log.Println("css: ", path) | |
322 return buildGCSS(path) | |
0 | 323 } else { |
324 log.Println("raw: ", path) | |
325 return copyFile(path) | |
326 } | |
327 } | |
328 return nil | |
329 }) | |
330 if err != nil { | |
331 log.Println("ERROR:", err) | |
332 } | |
7
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
333 if modified { |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
334 // Something was modified, so post-build hook |
18 | 335 // 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
|
336 run(filepath.Join(ZSDIR, "post"), []string{}, nil, nil) |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
337 modified = false |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
338 } |
0 | 339 lastModified = time.Now() |
340 if once { | |
341 break | |
342 } | |
343 time.Sleep(1 * time.Second) | |
344 } | |
345 } | |
346 | |
347 func main() { | |
348 if len(os.Args) == 1 { | |
349 fmt.Println(os.Args[0], "<command> [args]") | |
350 return | |
351 } | |
352 cmd := os.Args[1] | |
353 args := os.Args[2:] | |
354 switch cmd { | |
355 case "build": | |
356 buildAll(true) | |
357 case "watch": | |
358 buildAll(false) // pass duration | |
359 case "var": | |
360 if len(args) == 0 { | |
3
53dea9841cd9
moved eval func type to the top, added some error logs
zaitsev.serge
parents:
2
diff
changeset
|
361 log.Println("ERROR: filename expected") |
0 | 362 return |
363 } | |
19
802b96e67bae
added global variables, added default date for markdown
zaitsev.serge
parents:
18
diff
changeset
|
364 if vars, _, err := md(args[0], globals()); err == nil { |
0 | 365 if len(args) > 1 { |
366 for _, a := range args[1:] { | |
367 fmt.Println(vars[a]) | |
368 } | |
369 } else { | |
370 for k, v := range vars { | |
371 fmt.Println(k + ":" + v) | |
372 } | |
373 } | |
374 } else { | |
3
53dea9841cd9
moved eval func type to the top, added some error logs
zaitsev.serge
parents:
2
diff
changeset
|
375 log.Println(err) |
0 | 376 } |
377 default: | |
17 | 378 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
|
379 if err != nil { |
0 | 380 log.Println(err) |
381 } | |
382 } | |
383 } |