source: code/trunk/zs.go@ 33

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

removed funcs, ext and utils

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