Mercurial > yakumo_izuru > aya
comparison zs.go @ 0:45a9e1fac18e draft
initial commit
| author | zaitsev.serge |
|---|---|
| date | Fri, 05 Dec 2014 16:59:56 +0000 |
| parents | |
| children | fd79b3a90bef |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:45a9e1fac18e |
|---|---|
| 1 package main | |
| 2 | |
| 3 import ( | |
| 4 "bytes" | |
| 5 "fmt" | |
| 6 "io" | |
| 7 "io/ioutil" | |
| 8 "log" | |
| 9 "os" | |
| 10 "os/exec" | |
| 11 "path" | |
| 12 "path/filepath" | |
| 13 "strings" | |
| 14 "time" | |
| 15 | |
| 16 "github.com/russross/blackfriday" | |
| 17 ) | |
| 18 | |
| 19 const ( | |
| 20 ZSDIR = ".zs" | |
| 21 PUBDIR = ".pub" | |
| 22 ) | |
| 23 | |
| 24 func split2(s, delim string) (string, string) { | |
| 25 parts := strings.SplitN(s, delim, 2) | |
| 26 if len(parts) == 2 { | |
| 27 return parts[0], parts[1] | |
| 28 } else { | |
| 29 return parts[0], "" | |
| 30 } | |
| 31 } | |
| 32 | |
| 33 func md(s string) (map[string]string, string) { | |
| 34 v := map[string]string{} | |
| 35 // FIXME: if no header? | |
| 36 header, body := split2(s, "\n\n") | |
| 37 for _, line := range strings.Split(header, "\n") { | |
| 38 key, value := split2(line, ":") | |
| 39 v[strings.ToLower(strings.TrimSpace(key))] = strings.TrimSpace(value) | |
| 40 } | |
| 41 return v, body | |
| 42 } | |
| 43 | |
| 44 type EvalFn func(args []string, vars map[string]string) (string, error) | |
| 45 | |
| 46 func render(s string, vars map[string]string, eval EvalFn) (string, error) { | |
| 47 b := []byte(s) | |
| 48 delim_open := []byte("{{") | |
| 49 delim_close := []byte("}}") | |
| 50 | |
| 51 out := bytes.NewBuffer(nil) | |
| 52 for { | |
| 53 if from := bytes.Index(b, delim_open); from == -1 { | |
| 54 out.Write(b) | |
| 55 return out.String(), nil | |
| 56 } else { | |
| 57 to := bytes.Index(b, delim_close) | |
| 58 if to == -1 { | |
| 59 return "", fmt.Errorf("Close delim not found") | |
| 60 } else { | |
| 61 out.Write(b[:from]) | |
| 62 cmd := b[from+len(delim_open) : to] | |
| 63 b = b[to+len(delim_close):] | |
| 64 m := strings.Fields(string(cmd)) | |
| 65 if len(m) == 1 { | |
| 66 if v, ok := vars[m[0]]; ok { | |
| 67 out.Write([]byte(v)) | |
| 68 continue | |
| 69 } | |
| 70 } | |
| 71 if res, err := eval(m, vars); err == nil { | |
| 72 out.Write([]byte(res)) | |
| 73 } else { | |
| 74 log.Println(err) // silent | |
| 75 } | |
| 76 } | |
| 77 } | |
| 78 } | |
| 79 } | |
| 80 | |
| 81 func eval(cmd []string, vars map[string]string) (string, error) { | |
| 82 var outbuf, errbuf bytes.Buffer | |
| 83 c := exec.Command(path.Join(ZSDIR, cmd[0]), cmd[1:]...) | |
| 84 env := []string{"ZS=" + os.Args[0]} | |
| 85 for k, v := range vars { | |
| 86 env = append(env, "ZS_"+strings.ToUpper(k)+"="+v) | |
| 87 } | |
| 88 c.Env = append(c.Env, env...) | |
| 89 c.Stdout = &outbuf | |
| 90 c.Stderr = &errbuf | |
| 91 if err := c.Run(); err != nil { | |
| 92 log.Println(err) | |
| 93 c := exec.Command(path.Join(cmd[0]), cmd[1:]...) | |
| 94 c.Env = append(c.Env, env...) | |
| 95 c.Stdout = &outbuf | |
| 96 c.Stderr = &errbuf | |
| 97 if err := c.Run(); err != nil { | |
| 98 return "", err | |
| 99 } | |
| 100 } | |
| 101 if errbuf.Len() > 0 { | |
| 102 log.Println(errbuf.String()) | |
| 103 } | |
| 104 return outbuf.String(), nil | |
| 105 } | |
| 106 | |
| 107 func buildMarkdown(path string) error { | |
| 108 b, err := ioutil.ReadFile(path) | |
| 109 if err != nil { | |
| 110 return err | |
| 111 } | |
| 112 v, body := md(string(b)) | |
| 113 defaultVars(v, path) | |
| 114 content, err := render(body, v, eval) | |
| 115 if err != nil { | |
| 116 return err | |
| 117 } | |
| 118 v["content"] = string(blackfriday.MarkdownBasic([]byte(content))) | |
| 119 b, err = ioutil.ReadFile(filepath.Join(ZSDIR, v["layout"])) | |
| 120 if err != nil { | |
| 121 return err | |
| 122 } | |
| 123 content, err = render(string(b), v, eval) | |
| 124 if err != nil { | |
| 125 return err | |
| 126 } | |
| 127 err = ioutil.WriteFile(v["output"], []byte(content), 0666) | |
| 128 if err != nil { | |
| 129 return err | |
| 130 } | |
| 131 return nil | |
| 132 } | |
| 133 | |
| 134 func defaultVars(vars map[string]string, path string) { | |
| 135 if _, ok := vars["file"]; !ok { | |
| 136 vars["file"] = path | |
| 137 } | |
| 138 if _, ok := vars["url"]; !ok { | |
| 139 vars["url"] = path[:len(path)-len(filepath.Ext(path))] + ".html" | |
| 140 if strings.HasPrefix(vars["url"], "./") { | |
| 141 vars["url"] = vars["url"][2:] | |
| 142 } | |
| 143 } | |
| 144 if _, ok := vars["outdir"]; !ok { | |
| 145 vars["outdir"] = PUBDIR | |
| 146 } | |
| 147 if _, ok := vars["output"]; !ok { | |
| 148 vars["output"] = filepath.Join(PUBDIR, vars["url"]) | |
| 149 } | |
| 150 if _, ok := vars["layout"]; !ok { | |
| 151 vars["layout"] = "index.html" | |
| 152 } | |
| 153 } | |
| 154 | |
| 155 func copyFile(path string) error { | |
| 156 if in, err := os.Open(path); err != nil { | |
| 157 return err | |
| 158 } else { | |
| 159 defer in.Close() | |
| 160 if stat, err := in.Stat(); err != nil { | |
| 161 return err | |
| 162 } else { | |
| 163 // Directory? | |
| 164 if stat.Mode().IsDir() { | |
| 165 os.Mkdir(filepath.Join(PUBDIR, path), 0755) | |
| 166 return nil | |
| 167 } | |
| 168 if !stat.Mode().IsRegular() { | |
| 169 return nil | |
| 170 } | |
| 171 } | |
| 172 if out, err := os.Create(filepath.Join(PUBDIR, path)); err != nil { | |
| 173 return err | |
| 174 } else { | |
| 175 defer out.Close() | |
| 176 _, err = io.Copy(out, in) | |
| 177 return err | |
| 178 } | |
| 179 } | |
| 180 } | |
| 181 | |
| 182 func buildAll(once bool) { | |
| 183 lastModified := time.Unix(0, 0) | |
| 184 for { | |
| 185 os.Mkdir(PUBDIR, 0755) | |
| 186 err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error { | |
| 187 // ignore hidden files and directories | |
| 188 if filepath.Base(path)[0] == '.' || strings.HasPrefix(path, ".") { | |
| 189 return nil | |
| 190 } | |
| 191 | |
| 192 if info.ModTime().After(lastModified) { | |
| 193 ext := filepath.Ext(path) | |
| 194 if ext == ".md" || ext == "mkd" { | |
| 195 log.Println("mkd: ", path) | |
| 196 return buildMarkdown(path) | |
| 197 } else { | |
| 198 log.Println("raw: ", path) | |
| 199 return copyFile(path) | |
| 200 } | |
| 201 } | |
| 202 return nil | |
| 203 }) | |
| 204 if err != nil { | |
| 205 log.Println("ERROR:", err) | |
| 206 } | |
| 207 lastModified = time.Now() | |
| 208 if once { | |
| 209 break | |
| 210 } | |
| 211 time.Sleep(1 * time.Second) | |
| 212 } | |
| 213 } | |
| 214 | |
| 215 func main() { | |
| 216 if len(os.Args) == 1 { | |
| 217 fmt.Println(os.Args[0], "<command> [args]") | |
| 218 return | |
| 219 } | |
| 220 cmd := os.Args[1] | |
| 221 args := os.Args[2:] | |
| 222 switch cmd { | |
| 223 case "build": | |
| 224 buildAll(true) | |
| 225 case "watch": | |
| 226 buildAll(false) // pass duration | |
| 227 case "var": | |
| 228 if len(args) == 0 { | |
| 229 // print error | |
| 230 return | |
| 231 } | |
| 232 if b, err := ioutil.ReadFile(args[0]); err == nil { | |
| 233 vars, _ := md(string(b)) | |
| 234 defaultVars(vars, args[0]) | |
| 235 if len(args) > 1 { | |
| 236 for _, a := range args[1:] { | |
| 237 fmt.Println(vars[a]) | |
| 238 } | |
| 239 } else { | |
| 240 for k, v := range vars { | |
| 241 fmt.Println(k + ":" + v) | |
| 242 } | |
| 243 } | |
| 244 } else { | |
| 245 // print error | |
| 246 } | |
| 247 default: | |
| 248 cmd := exec.Command(path.Join(ZSDIR, cmd), args...) | |
| 249 cmd.Env = append(cmd.Env, "ZS="+os.Args[0]) | |
| 250 cmd.Stdout = os.Stdout | |
| 251 cmd.Stderr = os.Stderr | |
| 252 if err := cmd.Run(); err != nil { | |
| 253 log.Println(err) | |
| 254 } | |
| 255 } | |
| 256 } |
