source: code/trunk/zs.go@ 23

Last change on this file since 23 was 23, checked in by zaitsev.serge, 10 years ago

restored the original amber since my issue has been fixed

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