source: code/trunk/zs.go@ 5

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

render uses strings, not bytes; added helpers for env and run

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