comparison zs.go @ 18:ae3116ea938b draft

started migration to go templates
author zaitsev.serge
date Sat, 29 Aug 2015 16:46:05 +0000
parents 0214b1b5f5eb
children 802b96e67bae
comparison
equal deleted inserted replaced
17:0214b1b5f5eb 18:ae3116ea938b
9 "os" 9 "os"
10 "os/exec" 10 "os/exec"
11 "path" 11 "path"
12 "path/filepath" 12 "path/filepath"
13 "strings" 13 "strings"
14 "text/template"
14 "time" 15 "time"
15 16
16 "github.com/eknkc/amber" 17 "github.com/eknkc/amber"
17 "github.com/russross/blackfriday" 18 "github.com/russross/blackfriday"
18 "github.com/yosssi/gcss" 19 "github.com/yosssi/gcss"
22 ZSDIR = ".zs" 23 ZSDIR = ".zs"
23 PUBDIR = ".pub" 24 PUBDIR = ".pub"
24 ) 25 )
25 26
26 type Vars map[string]string 27 type Vars map[string]string
27
28 type EvalFn func(args []string, vars Vars) (string, error)
29 28
30 // Splits a string in exactly two parts by delimiter 29 // Splits a string in exactly two parts by delimiter
31 // If no delimiter is found - the second string is be empty 30 // If no delimiter is found - the second string is be empty
32 func split2(s, delim string) (string, string) { 31 func split2(s, delim string) (string, string) {
33 parts := strings.SplitN(s, delim, 2) 32 parts := strings.SplitN(s, delim, 2)
64 v["url"] = v["url"][2:] 63 v["url"] = v["url"][2:]
65 } 64 }
66 return v, body, nil 65 return v, body, nil
67 } 66 }
68 67
69 func render(s string, vars Vars, eval EvalFn) (string, error) { 68 // Use standard Go templates
70 delim_open := "{{" 69 func render(s string, funcs template.FuncMap, vars Vars) (string, error) {
71 delim_close := "}}" 70 f := template.FuncMap{}
72 71 for k, v := range funcs {
73 out := bytes.NewBuffer(nil) 72 f[k] = v
74 for { 73 }
75 if from := strings.Index(s, delim_open); from == -1 { 74 for k, v := range vars {
76 out.WriteString(s) 75 f[k] = varFunc(v)
77 return out.String(), nil 76 }
78 } else { 77 tmpl, err := template.New("").Funcs(f).Parse(s)
79 if to := strings.Index(s, delim_close); to == -1 { 78 if err != nil {
80 return "", fmt.Errorf("Close delim not found") 79 return "", err
81 } else { 80 }
82 out.WriteString(s[:from]) 81 out := &bytes.Buffer{}
83 cmd := s[from+len(delim_open) : to] 82 if err := tmpl.Execute(out, vars); err != nil {
84 s = s[to+len(delim_close):] 83 return "", err
85 m := strings.Fields(cmd) 84 }
86 if len(m) == 1 { 85 return string(out.Bytes()), nil
87 if v, ok := vars[m[0]]; ok {
88 out.WriteString(v)
89 continue
90 }
91 }
92 if res, err := eval(m, vars); err == nil {
93 out.WriteString(res)
94 } else {
95 log.Println(err) // silent
96 }
97 }
98 }
99 }
100 } 86 }
101 87
102 // Converts zs markdown variables into environment variables 88 // Converts zs markdown variables into environment variables
103 func env(vars Vars) []string { 89 func env(vars Vars) []string {
104 env := []string{"ZS=" + os.Args[0], "ZS_OUTDIR=" + PUBDIR} 90 env := []string{"ZS=" + os.Args[0], "ZS_OUTDIR=" + PUBDIR}
130 return err 116 return err
131 } 117 }
132 return nil 118 return nil
133 } 119 }
134 120
121 // Expands macro: either replacing it with the variable value, or
122 // running the plugin command and replacing it with the command's output
135 func eval(cmd []string, vars Vars) (string, error) { 123 func eval(cmd []string, vars Vars) (string, error) {
136 outbuf := bytes.NewBuffer(nil) 124 outbuf := bytes.NewBuffer(nil)
137 err := run(path.Join(ZSDIR, cmd[0]), cmd[1:], vars, outbuf) 125 err := run(path.Join(ZSDIR, cmd[0]), cmd[1:], vars, outbuf)
138 if err != nil { 126 if err != nil {
139 if _, ok := err.(*exec.ExitError); ok { 127 if _, ok := err.(*exec.ExitError); ok {
147 } 135 }
148 } 136 }
149 return outbuf.String(), nil 137 return outbuf.String(), nil
150 } 138 }
151 139
152 func buildMarkdown(path string) error { 140 // Renders markdown with the given layout into html expanding all the macros
141 func buildMarkdown(path string, funcs template.FuncMap, vars Vars) error {
153 v, body, err := md(path) 142 v, body, err := md(path)
154 if err != nil { 143 if err != nil {
155 return err 144 return err
156 } 145 }
157 content, err := render(body, v, eval) 146 content, err := render(body, funcs, v)
158 if err != nil { 147 if err != nil {
159 return err 148 return err
160 } 149 }
161 v["content"] = string(blackfriday.MarkdownBasic([]byte(content))) 150 v["content"] = string(blackfriday.MarkdownBasic([]byte(content)))
162 return buildPlain(filepath.Join(ZSDIR, v["layout"]), v) 151 if strings.HasSuffix(v["layout"], ".amber") {
163 } 152 return buildAmber(filepath.Join(ZSDIR, v["layout"]), funcs, v)
164 153 } else {
165 func buildPlain(path string, vars Vars) error { 154 return buildPlain(filepath.Join(ZSDIR, v["layout"]), funcs, v)
155 }
156 }
157
158 // Renders text file expanding all variable macros inside it
159 func buildPlain(path string, funcs template.FuncMap, vars Vars) error {
166 b, err := ioutil.ReadFile(path) 160 b, err := ioutil.ReadFile(path)
167 if err != nil { 161 if err != nil {
168 return err 162 return err
169 } 163 }
170 content, err := render(string(b), vars, eval) 164 content, err := render(string(b), funcs, vars)
171 if err != nil { 165 if err != nil {
172 return err 166 return err
173 } 167 }
174 output := filepath.Join(PUBDIR, path) 168 output := filepath.Join(PUBDIR, path)
175 if s, ok := vars["output"]; ok { 169 if s, ok := vars["output"]; ok {
180 return err 174 return err
181 } 175 }
182 return nil 176 return nil
183 } 177 }
184 178
185 func buildGCSS(path string) error { 179 // Renders .amber file into .html
186 f, err := os.Open(path) 180 func buildAmber(path string, funcs template.FuncMap, vars Vars) error {
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() 181 a := amber.New()
206 err := a.ParseFile(path) 182 err := a.ParseFile(path)
207 if err != nil { 183 if err != nil {
208 return err 184 return err
209 } 185 }
219 } 195 }
220 defer f.Close() 196 defer f.Close()
221 return t.Execute(f, vars) 197 return t.Execute(f, vars)
222 } 198 }
223 199
200 // Compiles .gcss into .css
201 func buildGCSS(path string) error {
202 f, err := os.Open(path)
203 if err != nil {
204 return err
205 }
206 s := strings.TrimSuffix(path, ".gcss") + ".css"
207 css, err := os.Create(filepath.Join(PUBDIR, s))
208 if err != nil {
209 return err
210 }
211
212 defer f.Close()
213 defer css.Close()
214
215 _, err = gcss.Compile(css, f)
216 return err
217 }
218
219 // Copies file from working directory into public directory
224 func copyFile(path string) (err error) { 220 func copyFile(path string) (err error) {
225 var in, out *os.File 221 var in, out *os.File
226 if in, err = os.Open(path); err == nil { 222 if in, err = os.Open(path); err == nil {
227 defer in.Close() 223 defer in.Close()
228 if out, err = os.Create(filepath.Join(PUBDIR, path)); err == nil { 224 if out, err = os.Create(filepath.Join(PUBDIR, path)); err == nil {
231 } 227 }
232 } 228 }
233 return err 229 return err
234 } 230 }
235 231
232 func varFunc(s string) func() string {
233 return func() string {
234 return s
235 }
236 }
237
238 func pluginFunc(cmd string) func() string {
239 return func() string {
240 return "Not implemented yet"
241 }
242 }
243
244 func createFuncs() template.FuncMap {
245 // Builtin functions
246 funcs := template.FuncMap{}
247 // Plugin functions
248 files, _ := ioutil.ReadDir(ZSDIR)
249 for _, f := range files {
250 if !f.IsDir() {
251 name := f.Name()
252 if !strings.HasSuffix(name, ".html") && !strings.HasSuffix(name, ".amber") {
253 funcs[strings.TrimSuffix(name, filepath.Ext(name))] = pluginFunc(name)
254 }
255 }
256 }
257 return funcs
258 }
259
236 func buildAll(once bool) { 260 func buildAll(once bool) {
237 lastModified := time.Unix(0, 0) 261 lastModified := time.Unix(0, 0)
238 modified := false 262 modified := false
263 // Convert env variables into zs global variables
264 globals := Vars{}
265 for _, e := range os.Environ() {
266 pair := strings.Split(e, "=")
267 if strings.HasPrefix(pair[0], "ZS_") {
268 globals[strings.ToLower(pair[0][3:])] = pair[1]
269 }
270 }
239 for { 271 for {
240 os.Mkdir(PUBDIR, 0755) 272 os.Mkdir(PUBDIR, 0755)
273 funcs := createFuncs()
241 err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error { 274 err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
242 // ignore hidden files and directories 275 // ignore hidden files and directories
243 if filepath.Base(path)[0] == '.' || strings.HasPrefix(path, ".") { 276 if filepath.Base(path)[0] == '.' || strings.HasPrefix(path, ".") {
244 return nil 277 return nil
245 } 278 }
248 os.Mkdir(filepath.Join(PUBDIR, path), 0755) 281 os.Mkdir(filepath.Join(PUBDIR, path), 0755)
249 return nil 282 return nil
250 } else if info.ModTime().After(lastModified) { 283 } else if info.ModTime().After(lastModified) {
251 if !modified { 284 if !modified {
252 // About to be modified, so run pre-build hook 285 // About to be modified, so run pre-build hook
286 // FIXME on windows it might not work well
253 run(filepath.Join(ZSDIR, "pre"), []string{}, nil, nil) 287 run(filepath.Join(ZSDIR, "pre"), []string{}, nil, nil)
254 modified = true 288 modified = true
255 } 289 }
256 ext := filepath.Ext(path) 290 ext := filepath.Ext(path)
257 if ext == ".md" || ext == ".mkd" { 291 if ext == ".md" || ext == ".mkd" {
258 log.Println("md: ", path) 292 log.Println("md: ", path)
259 return buildMarkdown(path) 293 return buildMarkdown(path, funcs, globals)
260 } else if ext == ".html" || ext == ".xml" { 294 } else if ext == ".html" || ext == ".xml" {
261 log.Println("html: ", path) 295 log.Println("html: ", path)
262 return buildPlain(path, Vars{}) 296 return buildPlain(path, funcs, globals)
263 } else if ext == ".amber" { 297 } else if ext == ".amber" {
264 log.Println("html: ", path) 298 log.Println("html: ", path)
265 return buildAmber(path, Vars{}) 299 return buildAmber(path, funcs, globals)
266 } else if ext == ".gcss" { 300 } else if ext == ".gcss" {
267 log.Println("css: ", path) 301 log.Println("css: ", path)
268 return buildGCSS(path) 302 return buildGCSS(path)
269 } else { 303 } else {
270 log.Println("raw: ", path) 304 log.Println("raw: ", path)
276 if err != nil { 310 if err != nil {
277 log.Println("ERROR:", err) 311 log.Println("ERROR:", err)
278 } 312 }
279 if modified { 313 if modified {
280 // Something was modified, so post-build hook 314 // Something was modified, so post-build hook
315 // FIXME on windows it might not work well
281 run(filepath.Join(ZSDIR, "post"), []string{}, nil, nil) 316 run(filepath.Join(ZSDIR, "post"), []string{}, nil, nil)
282 modified = false 317 modified = false
283 } 318 }
284 lastModified = time.Now() 319 lastModified = time.Now()
285 if once { 320 if once {