source: code/trunk/zs.go@ 25

Last change on this file since 25 was 25, 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.7 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))
[1]307 default:
[18]308 err := run(path.Join(ZSDIR, cmd), args, Vars{}, os.Stdout)
[5]309 if err != nil {
[21]310 log.Println("ERROR:", err)
[1]311 }
312 }
313}
Note: See TracBrowser for help on using the repository browser.