source: code/trunk/zs.go@ 24

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

fixed output file names in html pages, fixed amber function bindings, replaced print command with build, fixed plugin functions, implemented zs and exec functions

File size: 6.5 KB
Line 
1package main
2
3import (
4 "bytes"
5 "fmt"
6 "io"
7 "io/ioutil"
8 "log"
9 "os"
10 "path"
11 "path/filepath"
12 "strings"
13 "text/template"
14 "time"
15
16 "github.com/eknkc/amber"
17 "github.com/russross/blackfriday"
18 "github.com/yosssi/gcss"
19)
20
21const (
22 ZSDIR = ".zs"
23 PUBDIR = ".pub"
24)
25
26type Vars map[string]string
27type Funcs template.FuncMap
28
29// Parses markdown content. Returns parsed header variables and content
30func md(path string, globals Vars) (Vars, string, error) {
31 b, err := ioutil.ReadFile(path)
32 if err != nil {
33 return nil, "", err
34 }
35 s := string(b)
36 url := path[:len(path)-len(filepath.Ext(path))] + ".html"
37 v := Vars{
38 "file": path,
39 "url": url,
40 "output": filepath.Join(PUBDIR, url),
41 }
42 if _, err := os.Stat(filepath.Join(ZSDIR, "layout.amber")); err == nil {
43 v["layout"] = "layout.amber"
44 } else {
45 v["layout"] = "layout.html"
46 }
47
48 if info, err := os.Stat(path); err == nil {
49 v["date"] = info.ModTime().Format("02-01-2006")
50 }
51 for name, value := range globals {
52 v[name] = value
53 }
54 if strings.Index(s, "\n\n") == -1 {
55 return v, s, nil
56 }
57 header, body := split2(s, "\n\n")
58 for _, line := range strings.Split(header, "\n") {
59 key, value := split2(line, ":")
60 v[strings.ToLower(strings.TrimSpace(key))] = strings.TrimSpace(value)
61 }
62 if strings.HasPrefix(v["url"], "./") {
63 v["url"] = v["url"][2:]
64 }
65 return v, body, nil
66}
67
68// Use standard Go templates
69func render(s string, funcs Funcs, vars Vars) (string, error) {
70 f := Funcs{}
71 for k, v := range funcs {
72 f[k] = v
73 }
74 for k, v := range vars {
75 f[k] = varFunc(v)
76 }
77 // Plugin functions
78 files, _ := ioutil.ReadDir(ZSDIR)
79 for _, file := range files {
80 if !file.IsDir() {
81 name := file.Name()
82 if !strings.HasSuffix(name, ".html") && !strings.HasSuffix(name, ".amber") {
83 f[strings.TrimSuffix(name, filepath.Ext(name))] = pluginFunc(name, vars)
84 }
85 }
86 }
87
88 tmpl, err := template.New("").Funcs(template.FuncMap(f)).Parse(s)
89 if err != nil {
90 return "", err
91 }
92 out := &bytes.Buffer{}
93 if err := tmpl.Execute(out, vars); err != nil {
94 return "", err
95 }
96 return string(out.Bytes()), nil
97}
98
99// Renders markdown with the given layout into html expanding all the macros
100func buildMarkdown(path string, w io.Writer, funcs Funcs, vars Vars) error {
101 v, body, err := md(path, vars)
102 if err != nil {
103 return err
104 }
105 content, err := render(body, funcs, v)
106 if err != nil {
107 return err
108 }
109 v["content"] = string(blackfriday.MarkdownBasic([]byte(content)))
110 if w == nil {
111 out, err := os.Create(filepath.Join(PUBDIR, renameExt(path, "", ".html")))
112 if err != nil {
113 return err
114 }
115 defer out.Close()
116 w = out
117 }
118 if strings.HasSuffix(v["layout"], ".amber") {
119 return buildAmber(filepath.Join(ZSDIR, v["layout"]), w, funcs, v)
120 } else {
121 return buildHTML(filepath.Join(ZSDIR, v["layout"]), w, funcs, v)
122 }
123}
124
125// Renders text file expanding all variable macros inside it
126func buildHTML(path string, w io.Writer, funcs Funcs, vars Vars) error {
127 b, err := ioutil.ReadFile(path)
128 if err != nil {
129 return err
130 }
131 content, err := render(string(b), funcs, vars)
132 if err != nil {
133 return err
134 }
135 output := filepath.Join(PUBDIR, path)
136 if s, ok := vars["output"]; ok {
137 output = s
138 }
139 err = ioutil.WriteFile(output, []byte(content), 0666)
140 if err != nil {
141 return err
142 }
143 return nil
144}
145
146// Renders .amber file into .html
147func buildAmber(path string, w io.Writer, funcs Funcs, vars Vars) error {
148 a := amber.New()
149 err := a.ParseFile(path)
150 if err != nil {
151 return err
152 }
153 t, err := a.Compile()
154 if err != nil {
155 return err
156 }
157 if w == nil {
158 f, err := os.Create(filepath.Join(PUBDIR, renameExt(path, ".amber", ".html")))
159 if err != nil {
160 return err
161 }
162 defer f.Close()
163 w = f
164 }
165 return t.Execute(w, vars)
166}
167
168// Compiles .gcss into .css
169func buildGCSS(path string, w io.Writer) error {
170 f, err := os.Open(path)
171 if err != nil {
172 return err
173 }
174 defer f.Close()
175
176 if w == nil {
177 s := strings.TrimSuffix(path, ".gcss") + ".css"
178 css, err := os.Create(filepath.Join(PUBDIR, s))
179 if err != nil {
180 return err
181 }
182 defer css.Close()
183 w = css
184 }
185 _, err = gcss.Compile(w, f)
186 return err
187}
188
189// Copies file as is from path to writer
190func buildRaw(path string, w io.Writer) error {
191 in, err := os.Open(path)
192 if err != nil {
193 return err
194 }
195 defer in.Close()
196 if w == nil {
197 if out, err := os.Create(filepath.Join(PUBDIR, path)); err != nil {
198 return err
199 } else {
200 defer out.Close()
201 w = out
202 }
203 }
204 _, err = io.Copy(w, in)
205 return err
206}
207
208func build(path string, w io.Writer, funcs Funcs, vars Vars) error {
209 ext := filepath.Ext(path)
210 if ext == ".md" || ext == ".mkd" {
211 return buildMarkdown(path, w, funcs, vars)
212 } else if ext == ".html" || ext == ".xml" {
213 return buildHTML(path, w, funcs, vars)
214 } else if ext == ".amber" {
215 return buildAmber(path, w, funcs, vars)
216 } else if ext == ".gcss" {
217 return buildGCSS(path, w)
218 } else {
219 return buildRaw(path, w)
220 }
221}
222
223func buildAll(watch bool) {
224 lastModified := time.Unix(0, 0)
225 modified := false
226
227 vars := globals()
228 for {
229 os.Mkdir(PUBDIR, 0755)
230 funcs := builtins()
231 err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
232 // ignore hidden files and directories
233 if filepath.Base(path)[0] == '.' || strings.HasPrefix(path, ".") {
234 return nil
235 }
236
237 if info.IsDir() {
238 os.Mkdir(filepath.Join(PUBDIR, path), 0755)
239 return nil
240 } else if info.ModTime().After(lastModified) {
241 if !modified {
242 // About to be modified, so run pre-build hook
243 // FIXME on windows it might not work well
244 run(filepath.Join(ZSDIR, "pre"), []string{}, nil, nil)
245 modified = true
246 }
247 log.Println("build: ", path)
248 return build(path, nil, funcs, vars)
249 }
250 return nil
251 })
252 if err != nil {
253 log.Println("ERROR:", err)
254 }
255 if modified {
256 // Something was modified, so post-build hook
257 // FIXME on windows it might not work well
258 run(filepath.Join(ZSDIR, "post"), []string{}, nil, nil)
259 modified = false
260 }
261 if !watch {
262 break
263 }
264 lastModified = time.Now()
265 time.Sleep(1 * time.Second)
266 }
267}
268
269func main() {
270 if len(os.Args) == 1 {
271 fmt.Println(os.Args[0], "<command> [args]")
272 return
273 }
274 cmd := os.Args[1]
275 args := os.Args[2:]
276 switch cmd {
277 case "build":
278 buildAll(false)
279 case "watch":
280 buildAll(true)
281 case "print":
282 if len(args) != 1 {
283 fmt.Println("ERROR: filename expected")
284 } else {
285 build(args[0], os.Stdout, builtins(), globals())
286 }
287 case "var":
288 fmt.Println(Var(args))
289 case "lorem":
290 fmt.Println(Lorem(args))
291 case "dateparse":
292 fmt.Println(DateParse(args))
293 case "datefmt":
294 fmt.Println(DateFmt(args))
295 default:
296 err := run(path.Join(ZSDIR, cmd), args, Vars{}, os.Stdout)
297 if err != nil {
298 log.Println("ERROR:", err)
299 }
300 }
301}
Note: See TracBrowser for help on using the repository browser.