source: code/trunk/zs.go@ 6

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

added tests for eval command runner

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