Mercurial > yakumo_izuru > aya
comparison zs.go @ 23:40f55059fbfa draft
fixed output file names in html pages, fixed amber function bindings, replaced print command with build, fixed plugin functions, implemented zs and exec functions
author | zaitsev.serge |
---|---|
date | Sun, 30 Aug 2015 12:20:35 +0000 |
parents | f5627d4212a3 |
children | d052f3a44195 |
comparison
equal
deleted
inserted
replaced
22:f5627d4212a3 | 23:40f55059fbfa |
---|---|
5 "fmt" | 5 "fmt" |
6 "io" | 6 "io" |
7 "io/ioutil" | 7 "io/ioutil" |
8 "log" | 8 "log" |
9 "os" | 9 "os" |
10 "os/exec" | |
11 "path" | 10 "path" |
12 "path/filepath" | 11 "path/filepath" |
13 "strings" | 12 "strings" |
14 "text/template" | 13 "text/template" |
15 "time" | 14 "time" |
23 ZSDIR = ".zs" | 22 ZSDIR = ".zs" |
24 PUBDIR = ".pub" | 23 PUBDIR = ".pub" |
25 ) | 24 ) |
26 | 25 |
27 type Vars map[string]string | 26 type Vars map[string]string |
28 | 27 type Funcs template.FuncMap |
29 // Splits a string in exactly two parts by delimiter | |
30 // If no delimiter is found - the second string is be empty | |
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 | 28 |
40 // Parses markdown content. Returns parsed header variables and content | 29 // Parses markdown content. Returns parsed header variables and content |
41 func md(path string, globals Vars) (Vars, string, error) { | 30 func md(path string, globals Vars) (Vars, string, error) { |
42 b, err := ioutil.ReadFile(path) | 31 b, err := ioutil.ReadFile(path) |
43 if err != nil { | 32 if err != nil { |
75 } | 64 } |
76 return v, body, nil | 65 return v, body, nil |
77 } | 66 } |
78 | 67 |
79 // Use standard Go templates | 68 // Use standard Go templates |
80 func render(s string, funcs template.FuncMap, vars Vars) (string, error) { | 69 func render(s string, funcs Funcs, vars Vars) (string, error) { |
81 f := template.FuncMap{} | 70 f := Funcs{} |
82 for k, v := range funcs { | 71 for k, v := range funcs { |
83 f[k] = v | 72 f[k] = v |
84 } | 73 } |
85 for k, v := range vars { | 74 for k, v := range vars { |
86 f[k] = varFunc(v) | 75 f[k] = varFunc(v) |
87 } | 76 } |
88 tmpl, err := template.New("").Funcs(f).Parse(s) | 77 // Plugin functions |
78 files, _ := ioutil.ReadDir(ZSDIR) | |
79 for _, file := range files { | |
80 if !file.IsDir() { | |
81 name := file.Name() | |
82 if !strings.HasSuffix(name, ".html") && !strings.HasSuffix(name, ".amber") { | |
83 f[strings.TrimSuffix(name, filepath.Ext(name))] = pluginFunc(name, vars) | |
84 } | |
85 } | |
86 } | |
87 | |
88 tmpl, err := template.New("").Funcs(template.FuncMap(f)).Parse(s) | |
89 if err != nil { | 89 if err != nil { |
90 return "", err | 90 return "", err |
91 } | 91 } |
92 out := &bytes.Buffer{} | 92 out := &bytes.Buffer{} |
93 if err := tmpl.Execute(out, vars); err != nil { | 93 if err := tmpl.Execute(out, vars); err != nil { |
94 return "", err | 94 return "", err |
95 } | 95 } |
96 return string(out.Bytes()), nil | 96 return string(out.Bytes()), nil |
97 } | 97 } |
98 | 98 |
99 // Converts zs markdown variables into environment variables | |
100 func env(vars Vars) []string { | |
101 env := []string{"ZS=" + os.Args[0], "ZS_OUTDIR=" + PUBDIR} | |
102 env = append(env, os.Environ()...) | |
103 if vars != nil { | |
104 for k, v := range vars { | |
105 env = append(env, "ZS_"+strings.ToUpper(k)+"="+v) | |
106 } | |
107 } | |
108 return env | |
109 } | |
110 | |
111 // Runs command with given arguments and variables, intercepts stderr and | |
112 // redirects stdout into the given writer | |
113 func run(cmd string, args []string, vars Vars, output io.Writer) error { | |
114 var errbuf bytes.Buffer | |
115 c := exec.Command(cmd, args...) | |
116 c.Env = env(vars) | |
117 c.Stdout = output | |
118 c.Stderr = &errbuf | |
119 | |
120 err := c.Run() | |
121 | |
122 if errbuf.Len() > 0 { | |
123 log.Println("ERROR:", errbuf.String()) | |
124 } | |
125 | |
126 if err != nil { | |
127 return err | |
128 } | |
129 return nil | |
130 } | |
131 | |
132 // Expands macro: either replacing it with the variable value, or | |
133 // running the plugin command and replacing it with the command's output | |
134 func eval(cmd []string, vars Vars) (string, error) { | |
135 outbuf := bytes.NewBuffer(nil) | |
136 err := run(path.Join(ZSDIR, cmd[0]), cmd[1:], vars, outbuf) | |
137 if err != nil { | |
138 if _, ok := err.(*exec.ExitError); ok { | |
139 return "", err | |
140 } | |
141 outbuf = bytes.NewBuffer(nil) | |
142 err := run(cmd[0], cmd[1:], vars, outbuf) | |
143 // Return exit errors, but ignore if the command was not found | |
144 if _, ok := err.(*exec.ExitError); ok { | |
145 return "", err | |
146 } | |
147 } | |
148 return outbuf.String(), nil | |
149 } | |
150 | |
151 // Renders markdown with the given layout into html expanding all the macros | 99 // Renders markdown with the given layout into html expanding all the macros |
152 func buildMarkdown(path string, funcs template.FuncMap, vars Vars) error { | 100 func buildMarkdown(path string, w io.Writer, funcs Funcs, vars Vars) error { |
153 v, body, err := md(path, vars) | 101 v, body, err := md(path, vars) |
154 if err != nil { | 102 if err != nil { |
155 return err | 103 return err |
156 } | 104 } |
157 content, err := render(body, funcs, v) | 105 content, err := render(body, funcs, v) |
158 if err != nil { | 106 if err != nil { |
159 return err | 107 return err |
160 } | 108 } |
161 v["content"] = string(blackfriday.MarkdownBasic([]byte(content))) | 109 v["content"] = string(blackfriday.MarkdownBasic([]byte(content))) |
110 if w == nil { | |
111 out, err := os.Create(filepath.Join(PUBDIR, renameExt(path, "", ".html"))) | |
112 if err != nil { | |
113 return err | |
114 } | |
115 defer out.Close() | |
116 w = out | |
117 } | |
162 if strings.HasSuffix(v["layout"], ".amber") { | 118 if strings.HasSuffix(v["layout"], ".amber") { |
163 return buildAmber(filepath.Join(ZSDIR, v["layout"]), | 119 return buildAmber(filepath.Join(ZSDIR, v["layout"]), w, funcs, v) |
164 renameExt(path, "", ".html"), funcs, v) | |
165 } else { | 120 } else { |
166 return buildPlain(filepath.Join(ZSDIR, v["layout"]), | 121 return buildHTML(filepath.Join(ZSDIR, v["layout"]), w, funcs, v) |
167 renameExt(path, "", ".html"), funcs, v) | |
168 } | 122 } |
169 } | 123 } |
170 | 124 |
171 // Renders text file expanding all variable macros inside it | 125 // Renders text file expanding all variable macros inside it |
172 func buildPlain(in, out string, funcs template.FuncMap, vars Vars) error { | 126 func buildHTML(path string, w io.Writer, funcs Funcs, vars Vars) error { |
173 b, err := ioutil.ReadFile(in) | 127 b, err := ioutil.ReadFile(path) |
174 if err != nil { | 128 if err != nil { |
175 return err | 129 return err |
176 } | 130 } |
177 content, err := render(string(b), funcs, vars) | 131 content, err := render(string(b), funcs, vars) |
178 if err != nil { | 132 if err != nil { |
179 return err | 133 return err |
180 } | 134 } |
181 output := filepath.Join(PUBDIR, out) | 135 output := filepath.Join(PUBDIR, path) |
182 if s, ok := vars["output"]; ok { | 136 if s, ok := vars["output"]; ok { |
183 output = s | 137 output = s |
184 } | 138 } |
185 err = ioutil.WriteFile(output, []byte(content), 0666) | 139 err = ioutil.WriteFile(output, []byte(content), 0666) |
186 if err != nil { | 140 if err != nil { |
188 } | 142 } |
189 return nil | 143 return nil |
190 } | 144 } |
191 | 145 |
192 // Renders .amber file into .html | 146 // Renders .amber file into .html |
193 func buildAmber(in, out string, funcs template.FuncMap, vars Vars) error { | 147 func buildAmber(path string, w io.Writer, funcs Funcs, vars Vars) error { |
194 a := amber.New() | 148 a := amber.New() |
195 err := a.ParseFile(in) | 149 err := a.ParseFile(path) |
196 if err != nil { | 150 if err != nil { |
197 return err | 151 return err |
198 } | 152 } |
199 t, err := a.Compile() | 153 t, err := a.Compile() |
200 if err != nil { | 154 if err != nil { |
201 return err | 155 return err |
202 } | 156 } |
203 //amber.FuncMap = amber.FuncMap | 157 if w == nil { |
204 f, err := os.Create(filepath.Join(PUBDIR, out)) | 158 f, err := os.Create(filepath.Join(PUBDIR, renameExt(path, ".amber", ".html"))) |
159 if err != nil { | |
160 return err | |
161 } | |
162 defer f.Close() | |
163 w = f | |
164 } | |
165 return t.Execute(w, vars) | |
166 } | |
167 | |
168 // Compiles .gcss into .css | |
169 func buildGCSS(path string, w io.Writer) error { | |
170 f, err := os.Open(path) | |
205 if err != nil { | 171 if err != nil { |
206 return err | 172 return err |
207 } | 173 } |
208 defer f.Close() | 174 defer f.Close() |
209 return t.Execute(f, vars) | 175 |
210 } | 176 if w == nil { |
211 | 177 s := strings.TrimSuffix(path, ".gcss") + ".css" |
212 // Compiles .gcss into .css | 178 css, err := os.Create(filepath.Join(PUBDIR, s)) |
213 func buildGCSS(path string) error { | 179 if err != nil { |
214 f, err := os.Open(path) | 180 return err |
215 if err != nil { | 181 } |
216 return err | 182 defer css.Close() |
217 } | 183 w = css |
218 s := strings.TrimSuffix(path, ".gcss") + ".css" | 184 } |
219 css, err := os.Create(filepath.Join(PUBDIR, s)) | 185 _, err = gcss.Compile(w, f) |
220 if err != nil { | |
221 return err | |
222 } | |
223 | |
224 defer f.Close() | |
225 defer css.Close() | |
226 | |
227 _, err = gcss.Compile(css, f) | |
228 return err | 186 return err |
229 } | 187 } |
230 | 188 |
231 // Copies file from working directory into public directory | 189 // Copies file as is from path to writer |
232 func copyFile(path string) (err error) { | 190 func buildRaw(path string, w io.Writer) error { |
233 var in, out *os.File | 191 in, err := os.Open(path) |
234 if in, err = os.Open(path); err == nil { | 192 if err != nil { |
235 defer in.Close() | 193 return err |
236 if out, err = os.Create(filepath.Join(PUBDIR, path)); err == nil { | 194 } |
195 defer in.Close() | |
196 if w == nil { | |
197 if out, err := os.Create(filepath.Join(PUBDIR, path)); err != nil { | |
198 return err | |
199 } else { | |
237 defer out.Close() | 200 defer out.Close() |
238 _, err = io.Copy(out, in) | 201 w = out |
239 } | 202 } |
240 } | 203 } |
204 _, err = io.Copy(w, in) | |
241 return err | 205 return err |
242 } | 206 } |
243 | 207 |
244 func varFunc(s string) func() string { | 208 func build(path string, w io.Writer, funcs Funcs, vars Vars) error { |
245 return func() string { | 209 ext := filepath.Ext(path) |
246 return s | 210 if ext == ".md" || ext == ".mkd" { |
247 } | 211 return buildMarkdown(path, w, funcs, vars) |
248 } | 212 } else if ext == ".html" || ext == ".xml" { |
249 | 213 return buildHTML(path, w, funcs, vars) |
250 func pluginFunc(cmd string) func() string { | 214 } else if ext == ".amber" { |
251 return func() string { | 215 return buildAmber(path, w, funcs, vars) |
252 return "Not implemented yet" | 216 } else if ext == ".gcss" { |
253 } | 217 return buildGCSS(path, w) |
254 } | 218 } else { |
255 | 219 return buildRaw(path, w) |
256 func createFuncs() template.FuncMap { | 220 } |
257 // Builtin functions | 221 } |
258 funcs := template.FuncMap{ | 222 |
259 "exec": func(s ...string) string { | 223 func buildAll(watch bool) { |
260 // Run external command with arguments | |
261 return "" | |
262 }, | |
263 "zs": func(args ...string) string { | |
264 // Run zs with arguments | |
265 return "" | |
266 }, | |
267 } | |
268 // Plugin functions | |
269 files, _ := ioutil.ReadDir(ZSDIR) | |
270 for _, f := range files { | |
271 if !f.IsDir() { | |
272 name := f.Name() | |
273 if !strings.HasSuffix(name, ".html") && !strings.HasSuffix(name, ".amber") { | |
274 funcs[strings.TrimSuffix(name, filepath.Ext(name))] = pluginFunc(name) | |
275 } | |
276 } | |
277 } | |
278 return funcs | |
279 } | |
280 | |
281 func renameExt(path, from, to string) string { | |
282 if from == "" { | |
283 from = filepath.Ext(path) | |
284 } | |
285 return strings.TrimSuffix(path, from) + to | |
286 } | |
287 | |
288 func globals() Vars { | |
289 vars := Vars{} | |
290 for _, e := range os.Environ() { | |
291 pair := strings.Split(e, "=") | |
292 if strings.HasPrefix(pair[0], "ZS_") { | |
293 vars[strings.ToLower(pair[0][3:])] = pair[1] | |
294 } | |
295 } | |
296 return vars | |
297 } | |
298 | |
299 func buildAll(once bool) { | |
300 lastModified := time.Unix(0, 0) | 224 lastModified := time.Unix(0, 0) |
301 modified := false | 225 modified := false |
302 | 226 |
303 vars := globals() | 227 vars := globals() |
304 for { | 228 for { |
305 os.Mkdir(PUBDIR, 0755) | 229 os.Mkdir(PUBDIR, 0755) |
306 funcs := createFuncs() | 230 funcs := builtins() |
307 err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error { | 231 err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error { |
308 // ignore hidden files and directories | 232 // ignore hidden files and directories |
309 if filepath.Base(path)[0] == '.' || strings.HasPrefix(path, ".") { | 233 if filepath.Base(path)[0] == '.' || strings.HasPrefix(path, ".") { |
310 return nil | 234 return nil |
311 } | 235 } |
318 // About to be modified, so run pre-build hook | 242 // About to be modified, so run pre-build hook |
319 // FIXME on windows it might not work well | 243 // FIXME on windows it might not work well |
320 run(filepath.Join(ZSDIR, "pre"), []string{}, nil, nil) | 244 run(filepath.Join(ZSDIR, "pre"), []string{}, nil, nil) |
321 modified = true | 245 modified = true |
322 } | 246 } |
323 ext := filepath.Ext(path) | 247 log.Println("build: ", path) |
324 if ext == ".md" || ext == ".mkd" { | 248 return build(path, nil, funcs, vars) |
325 log.Println("md: ", path) | |
326 return buildMarkdown(path, funcs, vars) | |
327 } else if ext == ".html" || ext == ".xml" { | |
328 log.Println("html: ", path) | |
329 return buildPlain(path, path, funcs, vars) | |
330 } else if ext == ".amber" { | |
331 log.Println("html: ", path) | |
332 return buildAmber(path, renameExt(path, ".amber", ".html"), funcs, vars) | |
333 } else if ext == ".gcss" { | |
334 log.Println("css: ", path) | |
335 return buildGCSS(path) | |
336 } else { | |
337 log.Println("raw: ", path) | |
338 return copyFile(path) | |
339 } | |
340 } | 249 } |
341 return nil | 250 return nil |
342 }) | 251 }) |
343 if err != nil { | 252 if err != nil { |
344 log.Println("ERROR:", err) | 253 log.Println("ERROR:", err) |
347 // Something was modified, so post-build hook | 256 // Something was modified, so post-build hook |
348 // FIXME on windows it might not work well | 257 // FIXME on windows it might not work well |
349 run(filepath.Join(ZSDIR, "post"), []string{}, nil, nil) | 258 run(filepath.Join(ZSDIR, "post"), []string{}, nil, nil) |
350 modified = false | 259 modified = false |
351 } | 260 } |
261 if !watch { | |
262 break | |
263 } | |
352 lastModified = time.Now() | 264 lastModified = time.Now() |
353 if once { | |
354 break | |
355 } | |
356 time.Sleep(1 * time.Second) | 265 time.Sleep(1 * time.Second) |
357 } | 266 } |
358 } | 267 } |
359 | 268 |
360 func main() { | 269 func main() { |
364 } | 273 } |
365 cmd := os.Args[1] | 274 cmd := os.Args[1] |
366 args := os.Args[2:] | 275 args := os.Args[2:] |
367 switch cmd { | 276 switch cmd { |
368 case "build": | 277 case "build": |
278 buildAll(false) | |
279 case "watch": | |
369 buildAll(true) | 280 buildAll(true) |
370 case "watch": | 281 case "print": |
371 buildAll(false) // pass duration | 282 if len(args) != 1 { |
283 fmt.Println("ERROR: filename expected") | |
284 } else { | |
285 build(args[0], os.Stdout, builtins(), globals()) | |
286 } | |
372 case "var": | 287 case "var": |
373 if len(args) == 0 { | 288 fmt.Println(Var(args)) |
374 log.Println("ERROR: filename expected") | 289 case "lorem": |
375 return | 290 fmt.Println(Lorem(args)) |
376 } | 291 case "dateparse": |
377 if vars, _, err := md(args[0], globals()); err == nil { | 292 fmt.Println(DateParse(args)) |
378 if len(args) > 1 { | 293 case "datefmt": |
379 for _, a := range args[1:] { | 294 fmt.Println(DateFmt(args)) |
380 fmt.Println(vars[a]) | |
381 } | |
382 } else { | |
383 for k, v := range vars { | |
384 fmt.Println(k + ":" + v) | |
385 } | |
386 } | |
387 } else { | |
388 log.Println("ERROR:", err) | |
389 } | |
390 default: | 295 default: |
391 err := run(path.Join(ZSDIR, cmd), args, Vars{}, os.Stdout) | 296 err := run(path.Join(ZSDIR, cmd), args, Vars{}, os.Stdout) |
392 if err != nil { | 297 if err != nil { |
393 log.Println("ERROR:", err) | 298 log.Println("ERROR:", err) |
394 } | 299 } |