source: code/trunk/zs.go@ 28

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

passing globals when running a plugin

File size: 6.8 KB
RevLine 
[1]1package main
2
3import (
4 "bytes"
5 "fmt"
6 "io"
7 "io/ioutil"
8 "log"
9 "os"
10 "path"
11 "path/filepath"
12 "strings"
[19]13 "text/template"
[1]14 "time"
15
[23]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
[24]27type Funcs template.FuncMap
[4]28
[18]29// Parses markdown content. Returns parsed header variables and content
[20]30func md(path string, globals Vars) (Vars, string, error) {
[18]31 b, err := ioutil.ReadFile(path)
32 if err != nil {
33 return nil, "", err
34 }
35 s := string(b)
[7]36 url := path[:len(path)-len(filepath.Ext(path))] + ".html"
[18]37 v := Vars{
[7]38 "file": path,
39 "url": url,
40 "output": filepath.Join(PUBDIR, url),
41 }
[21]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
[20]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 }
[3]54 if strings.Index(s, "\n\n") == -1 {
[20]55 return v, s, nil
[3]56 }
[1]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 }
[7]62 if strings.HasPrefix(v["url"], "./") {
63 v["url"] = v["url"][2:]
64 }
[18]65 return v, body, nil
[1]66}
67
[19]68// Use standard Go templates
[24]69func render(s string, funcs Funcs, vars Vars) (string, error) {
70 f := Funcs{}
[19]71 for k, v := range funcs {
72 f[k] = v
[1]73 }
[19]74 for k, v := range vars {
75 f[k] = varFunc(v)
76 }
[24]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)
[19]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
[1]97}
98
[19]99// Renders markdown with the given layout into html expanding all the macros
[24]100func buildMarkdown(path string, w io.Writer, funcs Funcs, vars Vars) error {
[20]101 v, body, err := md(path, vars)
[1]102 if err != nil {
103 return err
104 }
[19]105 content, err := render(body, funcs, v)
[1]106 if err != nil {
107 return err
108 }
109 v["content"] = string(blackfriday.MarkdownBasic([]byte(content)))
[24]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 }
[19]118 if strings.HasSuffix(v["layout"], ".amber") {
[24]119 return buildAmber(filepath.Join(ZSDIR, v["layout"]), w, funcs, v)
[19]120 } else {
[24]121 return buildHTML(filepath.Join(ZSDIR, v["layout"]), w, funcs, v)
[19]122 }
[15]123}
124
[19]125// Renders text file expanding all variable macros inside it
[24]126func buildHTML(path string, w io.Writer, funcs Funcs, vars Vars) error {
127 b, err := ioutil.ReadFile(path)
[1]128 if err != nil {
129 return err
130 }
[19]131 content, err := render(string(b), funcs, vars)
[1]132 if err != nil {
133 return err
134 }
[25]135 if w == nil {
136 f, err := os.Create(filepath.Join(PUBDIR, path))
137 if err != nil {
138 return err
139 }
140 defer f.Close()
141 w = f
[15]142 }
[25]143 _, err = io.WriteString(w, content)
144 return err
[1]145}
146
[19]147// Renders .amber file into .html
[24]148func buildAmber(path string, w io.Writer, funcs Funcs, vars Vars) error {
[18]149 a := amber.New()
[24]150 err := a.ParseFile(path)
[18]151 if err != nil {
152 return err
153 }
[25]154
155 data := map[string]interface{}{}
156 for k, v := range vars {
157 data[k] = v
158 }
159 for k, v := range funcs {
160 data[k] = v
161 }
162
[18]163 t, err := a.Compile()
164 if err != nil {
165 return err
166 }
[24]167 if w == nil {
168 f, err := os.Create(filepath.Join(PUBDIR, renameExt(path, ".amber", ".html")))
169 if err != nil {
170 return err
171 }
172 defer f.Close()
173 w = f
[18]174 }
[25]175 return t.Execute(w, data)
[18]176}
177
[19]178// Compiles .gcss into .css
[24]179func buildGCSS(path string, w io.Writer) error {
[19]180 f, err := os.Open(path)
181 if err != nil {
182 return err
183 }
184 defer f.Close()
185
[24]186 if w == nil {
187 s := strings.TrimSuffix(path, ".gcss") + ".css"
188 css, err := os.Create(filepath.Join(PUBDIR, s))
189 if err != nil {
190 return err
[1]191 }
[24]192 defer css.Close()
193 w = css
[1]194 }
[24]195 _, err = gcss.Compile(w, f)
[8]196 return err
[1]197}
198
[24]199// Copies file as is from path to writer
200func buildRaw(path string, w io.Writer) error {
201 in, err := os.Open(path)
202 if err != nil {
203 return err
[19]204 }
[24]205 defer in.Close()
206 if w == nil {
207 if out, err := os.Create(filepath.Join(PUBDIR, path)); err != nil {
208 return err
209 } else {
210 defer out.Close()
211 w = out
[19]212 }
213 }
[24]214 _, err = io.Copy(w, in)
215 return err
[19]216}
217
[24]218func build(path string, w io.Writer, funcs Funcs, vars Vars) error {
219 ext := filepath.Ext(path)
220 if ext == ".md" || ext == ".mkd" {
221 return buildMarkdown(path, w, funcs, vars)
222 } else if ext == ".html" || ext == ".xml" {
223 return buildHTML(path, w, funcs, vars)
224 } else if ext == ".amber" {
225 return buildAmber(path, w, funcs, vars)
226 } else if ext == ".gcss" {
227 return buildGCSS(path, w)
228 } else {
229 return buildRaw(path, w)
[21]230 }
231}
232
[24]233func buildAll(watch bool) {
[20]234 lastModified := time.Unix(0, 0)
235 modified := false
236
237 vars := globals()
[1]238 for {
239 os.Mkdir(PUBDIR, 0755)
[24]240 funcs := builtins()
[1]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
[19]253 // FIXME on windows it might not work well
[8]254 run(filepath.Join(ZSDIR, "pre"), []string{}, nil, nil)
255 modified = true
256 }
[24]257 log.Println("build: ", path)
258 return build(path, nil, funcs, vars)
[1]259 }
260 return nil
261 })
262 if err != nil {
263 log.Println("ERROR:", err)
264 }
[8]265 if modified {
266 // Something was modified, so post-build hook
[19]267 // FIXME on windows it might not work well
[8]268 run(filepath.Join(ZSDIR, "post"), []string{}, nil, nil)
269 modified = false
270 }
[24]271 if !watch {
[1]272 break
273 }
[24]274 lastModified = time.Now()
[1]275 time.Sleep(1 * time.Second)
276 }
277}
278
279func main() {
280 if len(os.Args) == 1 {
281 fmt.Println(os.Args[0], "<command> [args]")
282 return
283 }
284 cmd := os.Args[1]
285 args := os.Args[2:]
286 switch cmd {
287 case "build":
[25]288 if len(args) == 0 {
289 buildAll(false)
290 } else if len(args) == 1 {
291 if err := build(args[0], os.Stdout, builtins(), globals()); err != nil {
292 fmt.Println("ERROR: " + err.Error())
293 }
294 } else {
295 fmt.Println("ERROR: too many arguments")
296 }
[24]297 case "watch":
[1]298 buildAll(true)
[24]299 case "var":
300 fmt.Println(Var(args))
301 case "lorem":
302 fmt.Println(Lorem(args))
303 case "dateparse":
304 fmt.Println(DateParse(args))
305 case "datefmt":
306 fmt.Println(DateFmt(args))
[26]307 case "wc":
308 fmt.Println(WordCount(args))
309 case "timetoread":
310 fmt.Println(TimeToRead(args))
[1]311 default:
[28]312 err := run(path.Join(ZSDIR, cmd), args, globals(), os.Stdout)
[5]313 if err != nil {
[21]314 log.Println("ERROR:", err)
[1]315 }
316 }
317}
Note: See TracBrowser for help on using the repository browser.