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 }