Mercurial > yakumo_izuru > aya
annotate zs.go @ 5:e0ee0721d34f draft
added tests for eval command runner
author | zaitsev.serge |
---|---|
date | Fri, 05 Dec 2014 18:21:42 +0000 |
parents | 05fc24caac37 |
children | cb66f5b86616 |
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 | |
16 "github.com/russross/blackfriday" | |
17 ) | |
18 | |
19 const ( | |
20 ZSDIR = ".zs" | |
21 PUBDIR = ".pub" | |
22 ) | |
23 | |
3
53dea9841cd9
moved eval func type to the top, added some error logs
zaitsev.serge
parents:
2
diff
changeset
|
24 type EvalFn func(args []string, vars map[string]string) (string, error) |
53dea9841cd9
moved eval func type to the top, added some error logs
zaitsev.serge
parents:
2
diff
changeset
|
25 |
0 | 26 func split2(s, delim string) (string, string) { |
27 parts := strings.SplitN(s, delim, 2) | |
28 if len(parts) == 2 { | |
29 return parts[0], parts[1] | |
30 } else { | |
31 return parts[0], "" | |
32 } | |
33 } | |
34 | |
35 func md(s string) (map[string]string, string) { | |
36 v := map[string]string{} | |
2 | 37 if strings.Index(s, "\n\n") == -1 { |
38 return map[string]string{}, s | |
39 } | |
0 | 40 header, body := split2(s, "\n\n") |
41 for _, line := range strings.Split(header, "\n") { | |
42 key, value := split2(line, ":") | |
43 v[strings.ToLower(strings.TrimSpace(key))] = strings.TrimSpace(value) | |
44 } | |
45 return v, body | |
46 } | |
47 | |
48 func render(s string, vars map[string]string, eval EvalFn) (string, error) { | |
4
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
49 delim_open := "{{" |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
50 delim_close := "}}" |
0 | 51 |
52 out := bytes.NewBuffer(nil) | |
53 for { | |
4
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
54 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
|
55 out.WriteString(s) |
0 | 56 return out.String(), nil |
57 } else { | |
4
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
58 if to := strings.Index(s, delim_close); to == -1 { |
0 | 59 return "", fmt.Errorf("Close delim not found") |
60 } else { | |
4
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
61 out.WriteString(s[:from]) |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
62 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
|
63 s = s[to+len(delim_close):] |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
64 m := strings.Fields(cmd) |
0 | 65 if len(m) == 1 { |
66 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
|
67 out.WriteString(v) |
0 | 68 continue |
69 } | |
70 } | |
71 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
|
72 out.WriteString(res) |
0 | 73 } else { |
74 log.Println(err) // silent | |
75 } | |
76 } | |
77 } | |
78 } | |
79 } | |
80 | |
4
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
81 func env(vars map[string]string) []string { |
0 | 82 env := []string{"ZS=" + os.Args[0]} |
4
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
83 env = append(env, os.Environ()...) |
0 | 84 for k, v := range vars { |
85 env = append(env, "ZS_"+strings.ToUpper(k)+"="+v) | |
86 } | |
4
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
87 return env |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
88 } |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
89 |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
90 func run(cmd string, args []string, vars map[string]string, output io.Writer) error { |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
91 var errbuf bytes.Buffer |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
92 c := exec.Command(cmd, args...) |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
93 c.Env = env(vars) |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
94 c.Stdout = output |
0 | 95 c.Stderr = &errbuf |
4
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
96 |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
97 err := c.Run() |
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 if errbuf.Len() > 0 { |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
100 log.Println(errbuf.String()) |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
101 } |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
102 |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
103 if err != nil { |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
104 return err |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
105 } |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
106 return nil |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
107 } |
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 func eval(cmd []string, vars map[string]string) (string, error) { |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
110 outbuf := bytes.NewBuffer(nil) |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
111 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
|
112 if err != nil { |
5 | 113 if _, ok := err.(*exec.ExitError); ok { |
114 return "", err | |
115 } | |
4
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
116 outbuf = bytes.NewBuffer(nil) |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
117 err := run(cmd[0], cmd[1:], vars, outbuf) |
5 | 118 // Return exit errors, but ignore if the command was not found |
119 if _, ok := err.(*exec.ExitError); ok { | |
0 | 120 return "", err |
121 } | |
122 } | |
123 return outbuf.String(), nil | |
124 } | |
125 | |
126 func buildMarkdown(path string) error { | |
127 b, err := ioutil.ReadFile(path) | |
128 if err != nil { | |
129 return err | |
130 } | |
131 v, body := md(string(b)) | |
132 defaultVars(v, path) | |
133 content, err := render(body, v, eval) | |
134 if err != nil { | |
135 return err | |
136 } | |
137 v["content"] = string(blackfriday.MarkdownBasic([]byte(content))) | |
138 b, err = ioutil.ReadFile(filepath.Join(ZSDIR, v["layout"])) | |
139 if err != nil { | |
140 return err | |
141 } | |
142 content, err = render(string(b), v, eval) | |
143 if err != nil { | |
144 return err | |
145 } | |
146 err = ioutil.WriteFile(v["output"], []byte(content), 0666) | |
147 if err != nil { | |
148 return err | |
149 } | |
150 return nil | |
151 } | |
152 | |
153 func defaultVars(vars map[string]string, path string) { | |
154 if _, ok := vars["file"]; !ok { | |
155 vars["file"] = path | |
156 } | |
157 if _, ok := vars["url"]; !ok { | |
158 vars["url"] = path[:len(path)-len(filepath.Ext(path))] + ".html" | |
159 if strings.HasPrefix(vars["url"], "./") { | |
160 vars["url"] = vars["url"][2:] | |
161 } | |
162 } | |
163 if _, ok := vars["outdir"]; !ok { | |
164 vars["outdir"] = PUBDIR | |
165 } | |
166 if _, ok := vars["output"]; !ok { | |
167 vars["output"] = filepath.Join(PUBDIR, vars["url"]) | |
168 } | |
169 if _, ok := vars["layout"]; !ok { | |
170 vars["layout"] = "index.html" | |
171 } | |
172 } | |
173 | |
174 func copyFile(path string) error { | |
175 if in, err := os.Open(path); err != nil { | |
176 return err | |
177 } else { | |
178 defer in.Close() | |
179 if stat, err := in.Stat(); err != nil { | |
180 return err | |
181 } else { | |
182 // Directory? | |
183 if stat.Mode().IsDir() { | |
184 os.Mkdir(filepath.Join(PUBDIR, path), 0755) | |
185 return nil | |
186 } | |
187 if !stat.Mode().IsRegular() { | |
188 return nil | |
189 } | |
190 } | |
191 if out, err := os.Create(filepath.Join(PUBDIR, path)); err != nil { | |
192 return err | |
193 } else { | |
194 defer out.Close() | |
195 _, err = io.Copy(out, in) | |
196 return err | |
197 } | |
198 } | |
199 } | |
200 | |
201 func buildAll(once bool) { | |
202 lastModified := time.Unix(0, 0) | |
203 for { | |
204 os.Mkdir(PUBDIR, 0755) | |
205 err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error { | |
206 // ignore hidden files and directories | |
207 if filepath.Base(path)[0] == '.' || strings.HasPrefix(path, ".") { | |
208 return nil | |
209 } | |
210 | |
211 if info.ModTime().After(lastModified) { | |
212 ext := filepath.Ext(path) | |
213 if ext == ".md" || ext == "mkd" { | |
214 log.Println("mkd: ", path) | |
215 return buildMarkdown(path) | |
216 } else { | |
217 log.Println("raw: ", path) | |
218 return copyFile(path) | |
219 } | |
220 } | |
221 return nil | |
222 }) | |
223 if err != nil { | |
224 log.Println("ERROR:", err) | |
225 } | |
226 lastModified = time.Now() | |
227 if once { | |
228 break | |
229 } | |
230 time.Sleep(1 * time.Second) | |
231 } | |
232 } | |
233 | |
234 func main() { | |
235 if len(os.Args) == 1 { | |
236 fmt.Println(os.Args[0], "<command> [args]") | |
237 return | |
238 } | |
239 cmd := os.Args[1] | |
240 args := os.Args[2:] | |
241 switch cmd { | |
242 case "build": | |
243 buildAll(true) | |
244 case "watch": | |
245 buildAll(false) // pass duration | |
246 case "var": | |
247 if len(args) == 0 { | |
3
53dea9841cd9
moved eval func type to the top, added some error logs
zaitsev.serge
parents:
2
diff
changeset
|
248 log.Println("ERROR: filename expected") |
0 | 249 return |
250 } | |
251 if b, err := ioutil.ReadFile(args[0]); err == nil { | |
252 vars, _ := md(string(b)) | |
253 defaultVars(vars, args[0]) | |
254 if len(args) > 1 { | |
255 for _, a := range args[1:] { | |
256 fmt.Println(vars[a]) | |
257 } | |
258 } else { | |
259 for k, v := range vars { | |
260 fmt.Println(k + ":" + v) | |
261 } | |
262 } | |
263 } else { | |
3
53dea9841cd9
moved eval func type to the top, added some error logs
zaitsev.serge
parents:
2
diff
changeset
|
264 log.Println(err) |
0 | 265 } |
266 default: | |
4
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
267 err := run(path.Join(ZSDIR, cmd), args, map[string]string{}, os.Stdout) |
05fc24caac37
render uses strings, not bytes; added helpers for env and run
zaitsev.serge
parents:
3
diff
changeset
|
268 if err != nil { |
0 | 269 log.Println(err) |
270 } | |
271 } | |
272 } |