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
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 }
[24]135 output := filepath.Join(PUBDIR, path)
[15]136 if s, ok := vars["output"]; ok {
137 output = s
138 }
139 err = ioutil.WriteFile(output, []byte(content), 0666)
[1]140 if err != nil {
141 return err
142 }
143 return nil
144}
145
[19]146// Renders .amber file into .html
[24]147func buildAmber(path string, w io.Writer, funcs Funcs, vars Vars) error {
[18]148 a := amber.New()
[24]149 err := a.ParseFile(path)
[18]150 if err != nil {
151 return err
152 }
153 t, err := a.Compile()
154 if err != nil {
155 return err
156 }
[24]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
[18]164 }
[24]165 return t.Execute(w, vars)
[18]166}
167
[19]168// Compiles .gcss into .css
[24]169func buildGCSS(path string, w io.Writer) error {
[19]170 f, err := os.Open(path)
171 if err != nil {
172 return err
173 }
174 defer f.Close()
175
[24]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
[1]181 }
[24]182 defer css.Close()
183 w = css
[1]184 }
[24]185 _, err = gcss.Compile(w, f)
[8]186 return err
[1]187}
188
[24]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
[19]194 }
[24]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
[19]202 }
203 }
[24]204 _, err = io.Copy(w, in)
205 return err
[19]206}
207
[24]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)
[21]220 }
221}
222
[24]223func buildAll(watch bool) {
[20]224 lastModified := time.Unix(0, 0)
225 modified := false
226
227 vars := globals()
[1]228 for {
229 os.Mkdir(PUBDIR, 0755)
[24]230 funcs := builtins()
[1]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
[8]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
[19]243 // FIXME on windows it might not work well
[8]244 run(filepath.Join(ZSDIR, "pre"), []string{}, nil, nil)
245 modified = true
246 }
[24]247 log.Println("build: ", path)
248 return build(path, nil, funcs, vars)
[1]249 }
250 return nil
251 })
252 if err != nil {
253 log.Println("ERROR:", err)
254 }
[8]255 if modified {
256 // Something was modified, so post-build hook
[19]257 // FIXME on windows it might not work well
[8]258 run(filepath.Join(ZSDIR, "post"), []string{}, nil, nil)
259 modified = false
260 }
[24]261 if !watch {
[1]262 break
263 }
[24]264 lastModified = time.Now()
[1]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":
[24]278 buildAll(false)
279 case "watch":
[1]280 buildAll(true)
[24]281 case "print":
282 if len(args) != 1 {
283 fmt.Println("ERROR: filename expected")
[1]284 } else {
[24]285 build(args[0], os.Stdout, builtins(), globals())
[1]286 }
[24]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))
[1]295 default:
[18]296 err := run(path.Join(ZSDIR, cmd), args, Vars{}, os.Stdout)
[5]297 if err != nil {
[21]298 log.Println("ERROR:", err)
[1]299 }
300 }
301}
Note: See TracBrowser for help on using the repository browser.