source: code/trunk/zs.go@ 10

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

simplified build process, added pre and post build hooks

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