Mercurial > yakumo_izuru > aya
annotate zs.go @ 17:0214b1b5f5eb draft
added amber and gcss compilers
author | zaitsev.serge |
---|---|
date | Sat, 29 Aug 2015 15:47:16 +0000 |
parents | a9c42bd52f64 |
children | ae3116ea938b |
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" | |
14 "time" | |
15 | |
17 | 16 "github.com/eknkc/amber" |
0 | 17 "github.com/russross/blackfriday" |
17 | 18 "github.com/yosssi/gcss" |
0 | 19 ) |
20 | |
21 const ( | |
22 ZSDIR = ".zs" | |
23 PUBDIR = ".pub" | |
24 ) | |
25 | |
17 | 26 type Vars map[string]string |
3
53dea9841cd9
moved eval func type to the top, added some error logs
zaitsev.serge
parents:
2
diff
changeset
|
27 |
17 | 28 type EvalFn func(args []string, vars Vars) (string, error) |
29 | |
30 // Splits a string in exactly two parts by delimiter | |
31 // If no delimiter is found - the second string is be empty | |
0 | 32 func split2(s, delim string) (string, string) { |
33 parts := strings.SplitN(s, delim, 2) | |
34 if len(parts) == 2 { | |
35 return parts[0], parts[1] | |
36 } else { | |
37 return parts[0], "" | |
38 } | |
39 } | |
40 | |
17 | 41 // Parses markdown content. Returns parsed header variables and content |
42 func md(path string) (Vars, string, error) { | |
43 b, err := ioutil.ReadFile(path) | |
44 if err != nil { | |
45 return nil, "", err | |
46 } | |
47 s := string(b) | |
6 | 48 url := path[:len(path)-len(filepath.Ext(path))] + ".html" |
17 | 49 v := Vars{ |
6 | 50 "file": path, |
51 "url": url, | |
52 "output": filepath.Join(PUBDIR, url), | |
53 "layout": "index.html", | |
54 } | |
2 | 55 if strings.Index(s, "\n\n") == -1 { |
17 | 56 return Vars{}, s, nil |
2 | 57 } |
0 | 58 header, body := split2(s, "\n\n") |
59 for _, line := range strings.Split(header, "\n") { | |
60 key, value := split2(line, ":") | |
61 v[strings.ToLower(strings.TrimSpace(key))] = strings.TrimSpace(value) | |
62 } | |
6 | 63 if strings.HasPrefix(v["url"], "./") { |
64 v["url"] = v["url"][2:] | |
65 } | |
17 | 66 return v, body, nil |
0 | 67 } |
68 | |
17 | 69 func render(s string, vars Vars, eval EvalFn) (string, error) { |
4
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
70 delim_open := "{{" |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
71 delim_close := "}}" |
0 | 72 |
73 out := bytes.NewBuffer(nil) | |
74 for { | |
4
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
75 if from := strings.Index(s, delim_open); from == -1 { |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
76 out.WriteString(s) |
0 | 77 return out.String(), nil |
78 } else { | |
4
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
79 if to := strings.Index(s, delim_close); to == -1 { |
0 | 80 return "", fmt.Errorf("Close delim not found") |
81 } else { | |
4
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
82 out.WriteString(s[:from]) |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
83 cmd := s[from+len(delim_open) : to] |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
84 s = s[to+len(delim_close):] |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
85 m := strings.Fields(cmd) |
0 | 86 if len(m) == 1 { |
87 if v, ok := vars[m[0]]; ok { | |
4
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
88 out.WriteString(v) |
0 | 89 continue |
90 } | |
91 } | |
92 if res, err := eval(m, vars); err == nil { | |
4
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
93 out.WriteString(res) |
0 | 94 } else { |
95 log.Println(err) // silent | |
96 } | |
97 } | |
98 } | |
99 } | |
100 } | |
101 | |
17 | 102 // Converts zs markdown variables into environment variables |
103 func env(vars Vars) []string { | |
7
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
104 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
|
105 env = append(env, os.Environ()...) |
7
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
106 if vars != nil { |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
107 for k, v := range vars { |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
108 env = append(env, "ZS_"+strings.ToUpper(k)+"="+v) |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
109 } |
0 | 110 } |
4
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
111 return env |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
112 } |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
113 |
17 | 114 // Runs command with given arguments and variables, intercepts stderr and |
115 // redirects stdout into the given writer | |
116 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
|
117 var errbuf bytes.Buffer |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
118 c := exec.Command(cmd, args...) |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
119 c.Env = env(vars) |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
120 c.Stdout = output |
0 | 121 c.Stderr = &errbuf |
4
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
122 |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
123 err := c.Run() |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
124 |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
125 if errbuf.Len() > 0 { |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
126 log.Println(errbuf.String()) |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
127 } |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
128 |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
129 if err != nil { |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
130 return err |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
131 } |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
132 return nil |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
133 } |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
134 |
17 | 135 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
|
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(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
|
138 if err != nil { |
5 | 139 if _, ok := err.(*exec.ExitError); ok { |
140 return "", err | |
141 } | |
4
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
142 outbuf = bytes.NewBuffer(nil) |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
143 err := run(cmd[0], cmd[1:], vars, outbuf) |
5 | 144 // Return exit errors, but ignore if the command was not found |
145 if _, ok := err.(*exec.ExitError); ok { | |
0 | 146 return "", err |
147 } | |
148 } | |
149 return outbuf.String(), nil | |
150 } | |
151 | |
152 func buildMarkdown(path string) error { | |
17 | 153 v, body, err := md(path) |
0 | 154 if err != nil { |
155 return err | |
156 } | |
157 content, err := render(body, v, eval) | |
158 if err != nil { | |
159 return err | |
160 } | |
161 v["content"] = string(blackfriday.MarkdownBasic([]byte(content))) | |
14 | 162 return buildPlain(filepath.Join(ZSDIR, v["layout"]), v) |
163 } | |
164 | |
17 | 165 func buildPlain(path string, vars Vars) error { |
14 | 166 b, err := ioutil.ReadFile(path) |
0 | 167 if err != nil { |
168 return err | |
169 } | |
14 | 170 content, err := render(string(b), vars, eval) |
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 | |
17 | 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 { | |
205 a := amber.New() | |
206 err := a.ParseFile(path) | |
207 if err != nil { | |
208 return err | |
209 } | |
210 t, err := a.Compile() | |
211 if err != nil { | |
212 return err | |
213 } | |
214 //amber.FuncMap = amber.FuncMap | |
215 s := strings.TrimSuffix(path, ".amber") + ".html" | |
216 f, err := os.Create(filepath.Join(PUBDIR, s)) | |
217 if err != nil { | |
218 return err | |
219 } | |
220 defer f.Close() | |
221 return t.Execute(f, vars) | |
222 } | |
223 | |
7
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
224 func copyFile(path string) (err error) { |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
225 var in, out *os.File |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
226 if in, err = os.Open(path); err == nil { |
0 | 227 defer in.Close() |
7
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
228 if out, err = os.Create(filepath.Join(PUBDIR, path)); err == nil { |
0 | 229 defer out.Close() |
230 _, err = io.Copy(out, in) | |
231 } | |
232 } | |
7
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
233 return err |
0 | 234 } |
235 | |
236 func buildAll(once bool) { | |
237 lastModified := time.Unix(0, 0) | |
7
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
238 modified := false |
0 | 239 for { |
240 os.Mkdir(PUBDIR, 0755) | |
241 err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error { | |
242 // ignore hidden files and directories | |
243 if filepath.Base(path)[0] == '.' || strings.HasPrefix(path, ".") { | |
244 return nil | |
245 } | |
246 | |
7
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
247 if info.IsDir() { |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
248 os.Mkdir(filepath.Join(PUBDIR, path), 0755) |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
249 return nil |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
250 } else if info.ModTime().After(lastModified) { |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
251 if !modified { |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
252 // About to be modified, so run pre-build hook |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
253 run(filepath.Join(ZSDIR, "pre"), []string{}, nil, nil) |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
254 modified = true |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
255 } |
0 | 256 ext := filepath.Ext(path) |
13 | 257 if ext == ".md" || ext == ".mkd" { |
17 | 258 log.Println("md: ", path) |
0 | 259 return buildMarkdown(path) |
15 | 260 } else if ext == ".html" || ext == ".xml" { |
17 | 261 log.Println("html: ", path) |
262 return buildPlain(path, Vars{}) | |
263 } else if ext == ".amber" { | |
264 log.Println("html: ", path) | |
265 return buildAmber(path, Vars{}) | |
266 } else if ext == ".gcss" { | |
267 log.Println("css: ", path) | |
268 return buildGCSS(path) | |
0 | 269 } else { |
270 log.Println("raw: ", path) | |
271 return copyFile(path) | |
272 } | |
273 } | |
274 return nil | |
275 }) | |
276 if err != nil { | |
277 log.Println("ERROR:", err) | |
278 } | |
7
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
279 if modified { |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
280 // Something was modified, so post-build hook |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
281 run(filepath.Join(ZSDIR, "post"), []string{}, nil, nil) |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
282 modified = false |
11073a30f71f
simplified build process, added pre and post build hooks
zaitsev.serge
parents:
6
diff
changeset
|
283 } |
0 | 284 lastModified = time.Now() |
285 if once { | |
286 break | |
287 } | |
288 time.Sleep(1 * time.Second) | |
289 } | |
290 } | |
291 | |
292 func main() { | |
293 if len(os.Args) == 1 { | |
294 fmt.Println(os.Args[0], "<command> [args]") | |
295 return | |
296 } | |
297 cmd := os.Args[1] | |
298 args := os.Args[2:] | |
299 switch cmd { | |
300 case "build": | |
301 buildAll(true) | |
302 case "watch": | |
303 buildAll(false) // pass duration | |
304 case "var": | |
305 if len(args) == 0 { | |
3
53dea9841cd9
moved eval func type to the top, added some error logs
zaitsev.serge
parents:
2
diff
changeset
|
306 log.Println("ERROR: filename expected") |
0 | 307 return |
308 } | |
17 | 309 if vars, _, err := md(args[0]); err == nil { |
0 | 310 if len(args) > 1 { |
311 for _, a := range args[1:] { | |
312 fmt.Println(vars[a]) | |
313 } | |
314 } else { | |
315 for k, v := range vars { | |
316 fmt.Println(k + ":" + v) | |
317 } | |
318 } | |
319 } else { | |
3
53dea9841cd9
moved eval func type to the top, added some error logs
zaitsev.serge
parents:
2
diff
changeset
|
320 log.Println(err) |
0 | 321 } |
322 default: | |
17 | 323 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
|
324 if err != nil { |
0 | 325 log.Println(err) |
326 } | |
327 } | |
328 } |