source: code/trunk/zs.go@ 18

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

added amber and gcss compilers

File size: 7.0 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
[18]16 "github.com/eknkc/amber"
[1]17 "github.com/russross/blackfriday"
[18]18 "github.com/yosssi/gcss"
[1]19)
20
21const (
22 ZSDIR = ".zs"
23 PUBDIR = ".pub"
24)
25
[18]26type Vars map[string]string
[4]27
[18]28type EvalFn func(args []string, vars Vars) (string, error)
29
30// Splits a string in exactly two parts by delimiter
31// If no delimiter is found - the second string is be empty
[1]32func split2(s, delim string) (string, string) {
33 parts := strings.SplitN(s, delim, 2)
34 if len(parts) == 2 {
35 return parts[0], parts[1]
36 } else {
37 return parts[0], ""
38 }
39}
40
[18]41// Parses markdown content. Returns parsed header variables and content
42func md(path string) (Vars, string, error) {
43 b, err := ioutil.ReadFile(path)
44 if err != nil {
45 return nil, "", err
46 }
47 s := string(b)
[7]48 url := path[:len(path)-len(filepath.Ext(path))] + ".html"
[18]49 v := Vars{
[7]50 "file": path,
51 "url": url,
52 "output": filepath.Join(PUBDIR, url),
53 "layout": "index.html",
54 }
[3]55 if strings.Index(s, "\n\n") == -1 {
[18]56 return Vars{}, s, nil
[3]57 }
[1]58 header, body := split2(s, "\n\n")
59 for _, line := range strings.Split(header, "\n") {
60 key, value := split2(line, ":")
61 v[strings.ToLower(strings.TrimSpace(key))] = strings.TrimSpace(value)
62 }
[7]63 if strings.HasPrefix(v["url"], "./") {
64 v["url"] = v["url"][2:]
65 }
[18]66 return v, body, nil
[1]67}
68
[18]69func render(s string, vars Vars, eval EvalFn) (string, error) {
[5]70 delim_open := "{{"
71 delim_close := "}}"
[1]72
73 out := bytes.NewBuffer(nil)
74 for {
[5]75 if from := strings.Index(s, delim_open); from == -1 {
76 out.WriteString(s)
[1]77 return out.String(), nil
78 } else {
[5]79 if to := strings.Index(s, delim_close); to == -1 {
[1]80 return "", fmt.Errorf("Close delim not found")
81 } else {
[5]82 out.WriteString(s[:from])
83 cmd := s[from+len(delim_open) : to]
84 s = s[to+len(delim_close):]
85 m := strings.Fields(cmd)
[1]86 if len(m) == 1 {
87 if v, ok := vars[m[0]]; ok {
[5]88 out.WriteString(v)
[1]89 continue
90 }
91 }
92 if res, err := eval(m, vars); err == nil {
[5]93 out.WriteString(res)
[1]94 } else {
95 log.Println(err) // silent
96 }
97 }
98 }
99 }
100}
101
[18]102// Converts zs markdown variables into environment variables
103func env(vars Vars) []string {
[8]104 env := []string{"ZS=" + os.Args[0], "ZS_OUTDIR=" + PUBDIR}
[5]105 env = append(env, os.Environ()...)
[8]106 if vars != nil {
107 for k, v := range vars {
108 env = append(env, "ZS_"+strings.ToUpper(k)+"="+v)
109 }
[1]110 }
[5]111 return env
112}
113
[18]114// Runs command with given arguments and variables, intercepts stderr and
115// redirects stdout into the given writer
116func run(cmd string, args []string, vars Vars, output io.Writer) error {
[5]117 var errbuf bytes.Buffer
118 c := exec.Command(cmd, args...)
119 c.Env = env(vars)
120 c.Stdout = output
[1]121 c.Stderr = &errbuf
[5]122
123 err := c.Run()
124
125 if errbuf.Len() > 0 {
126 log.Println(errbuf.String())
127 }
128
129 if err != nil {
130 return err
131 }
132 return nil
133}
134
[18]135func eval(cmd []string, vars Vars) (string, error) {
[5]136 outbuf := bytes.NewBuffer(nil)
137 err := run(path.Join(ZSDIR, cmd[0]), cmd[1:], vars, outbuf)
138 if err != nil {
[6]139 if _, ok := err.(*exec.ExitError); ok {
140 return "", err
141 }
[5]142 outbuf = bytes.NewBuffer(nil)
143 err := run(cmd[0], cmd[1:], vars, outbuf)
[6]144 // Return exit errors, but ignore if the command was not found
145 if _, ok := err.(*exec.ExitError); ok {
[1]146 return "", err
147 }
148 }
149 return outbuf.String(), nil
150}
151
152func buildMarkdown(path string) error {
[18]153 v, body, err := md(path)
[1]154 if err != nil {
155 return err
156 }
157 content, err := render(body, v, eval)
158 if err != nil {
159 return err
160 }
161 v["content"] = string(blackfriday.MarkdownBasic([]byte(content)))
[15]162 return buildPlain(filepath.Join(ZSDIR, v["layout"]), v)
163}
164
[18]165func buildPlain(path string, vars Vars) error {
[15]166 b, err := ioutil.ReadFile(path)
[1]167 if err != nil {
168 return err
169 }
[15]170 content, err := render(string(b), vars, eval)
[1]171 if err != nil {
172 return err
173 }
[15]174 output := filepath.Join(PUBDIR, path)
175 if s, ok := vars["output"]; ok {
176 output = s
177 }
178 err = ioutil.WriteFile(output, []byte(content), 0666)
[1]179 if err != nil {
180 return err
181 }
182 return nil
183}
184
[18]185func buildGCSS(path string) error {
186 f, err := os.Open(path)
187 if err != nil {
188 return err
189 }
190 s := strings.TrimSuffix(path, ".gcss") + ".css"
191 log.Println(s)
192 css, err := os.Create(filepath.Join(PUBDIR, s))
193 if err != nil {
194 return err
195 }
196
197 defer f.Close()
198 defer css.Close()
199
200 _, err = gcss.Compile(css, f)
201 return err
202}
203
204func buildAmber(path string, vars Vars) error {
205 a := amber.New()
206 err := a.ParseFile(path)
207 if err != nil {
208 return err
209 }
210 t, err := a.Compile()
211 if err != nil {
212 return err
213 }
214 //amber.FuncMap = amber.FuncMap
215 s := strings.TrimSuffix(path, ".amber") + ".html"
216 f, err := os.Create(filepath.Join(PUBDIR, s))
217 if err != nil {
218 return err
219 }
220 defer f.Close()
221 return t.Execute(f, vars)
222}
223
[8]224func copyFile(path string) (err error) {
225 var in, out *os.File
226 if in, err = os.Open(path); err == nil {
[1]227 defer in.Close()
[8]228 if out, err = os.Create(filepath.Join(PUBDIR, path)); err == nil {
[1]229 defer out.Close()
230 _, err = io.Copy(out, in)
231 }
232 }
[8]233 return err
[1]234}
235
236func buildAll(once bool) {
237 lastModified := time.Unix(0, 0)
[8]238 modified := false
[1]239 for {
240 os.Mkdir(PUBDIR, 0755)
241 err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
242 // ignore hidden files and directories
243 if filepath.Base(path)[0] == '.' || strings.HasPrefix(path, ".") {
244 return nil
245 }
246
[8]247 if info.IsDir() {
248 os.Mkdir(filepath.Join(PUBDIR, path), 0755)
249 return nil
250 } else if info.ModTime().After(lastModified) {
251 if !modified {
252 // About to be modified, so run pre-build hook
253 run(filepath.Join(ZSDIR, "pre"), []string{}, nil, nil)
254 modified = true
255 }
[1]256 ext := filepath.Ext(path)
[14]257 if ext == ".md" || ext == ".mkd" {
[18]258 log.Println("md: ", path)
[1]259 return buildMarkdown(path)
[16]260 } else if ext == ".html" || ext == ".xml" {
[18]261 log.Println("html: ", path)
262 return buildPlain(path, Vars{})
263 } else if ext == ".amber" {
264 log.Println("html: ", path)
265 return buildAmber(path, Vars{})
266 } else if ext == ".gcss" {
267 log.Println("css: ", path)
268 return buildGCSS(path)
[1]269 } else {
270 log.Println("raw: ", path)
271 return copyFile(path)
272 }
273 }
274 return nil
275 })
276 if err != nil {
277 log.Println("ERROR:", err)
278 }
[8]279 if modified {
280 // Something was modified, so post-build hook
281 run(filepath.Join(ZSDIR, "post"), []string{}, nil, nil)
282 modified = false
283 }
[1]284 lastModified = time.Now()
285 if once {
286 break
287 }
288 time.Sleep(1 * time.Second)
289 }
290}
291
292func main() {
293 if len(os.Args) == 1 {
294 fmt.Println(os.Args[0], "<command> [args]")
295 return
296 }
297 cmd := os.Args[1]
298 args := os.Args[2:]
299 switch cmd {
300 case "build":
301 buildAll(true)
302 case "watch":
303 buildAll(false) // pass duration
304 case "var":
305 if len(args) == 0 {
[4]306 log.Println("ERROR: filename expected")
[1]307 return
308 }
[18]309 if vars, _, err := md(args[0]); err == nil {
[1]310 if len(args) > 1 {
311 for _, a := range args[1:] {
312 fmt.Println(vars[a])
313 }
314 } else {
315 for k, v := range vars {
316 fmt.Println(k + ":" + v)
317 }
318 }
319 } else {
[4]320 log.Println(err)
[1]321 }
322 default:
[18]323 err := run(path.Join(ZSDIR, cmd), args, Vars{}, os.Stdout)
[5]324 if err != nil {
[1]325 log.Println(err)
326 }
327 }
328}
Note: See TracBrowser for help on using the repository browser.