source: code/trunk/zs.go@ 2

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

initial commit

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