Changeset 24 in code
- Timestamp:
- Aug 30, 2015, 12:20:35 PM (10 years ago)
- Location:
- trunk
- Files:
-
- 2 added
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/zs.go
r23 r24 8 8 "log" 9 9 "os" 10 "os/exec"11 10 "path" 12 11 "path/filepath" … … 26 25 27 26 type Vars map[string]string 28 29 // Splits a string in exactly two parts by delimiter 30 // If no delimiter is found - the second string is be empty 31 func split2(s, delim string) (string, string) { 32 parts := strings.SplitN(s, delim, 2) 33 if len(parts) == 2 { 34 return parts[0], parts[1] 35 } else { 36 return parts[0], "" 37 } 38 } 27 type Funcs template.FuncMap 39 28 40 29 // Parses markdown content. Returns parsed header variables and content … … 78 67 79 68 // Use standard Go templates 80 func render(s string, funcs template.FuncMap, vars Vars) (string, error) {81 f := template.FuncMap{}69 func render(s string, funcs Funcs, vars Vars) (string, error) { 70 f := Funcs{} 82 71 for k, v := range funcs { 83 72 f[k] = v … … 86 75 f[k] = varFunc(v) 87 76 } 88 tmpl, err := template.New("").Funcs(f).Parse(s) 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) 89 89 if err != nil { 90 90 return "", err … … 97 97 } 98 98 99 // Converts zs markdown variables into environment variables100 func env(vars Vars) []string {101 env := []string{"ZS=" + os.Args[0], "ZS_OUTDIR=" + PUBDIR}102 env = append(env, os.Environ()...)103 if vars != nil {104 for k, v := range vars {105 env = append(env, "ZS_"+strings.ToUpper(k)+"="+v)106 }107 }108 return env109 }110 111 // Runs command with given arguments and variables, intercepts stderr and112 // redirects stdout into the given writer113 func run(cmd string, args []string, vars Vars, output io.Writer) error {114 var errbuf bytes.Buffer115 c := exec.Command(cmd, args...)116 c.Env = env(vars)117 c.Stdout = output118 c.Stderr = &errbuf119 120 err := c.Run()121 122 if errbuf.Len() > 0 {123 log.Println("ERROR:", errbuf.String())124 }125 126 if err != nil {127 return err128 }129 return nil130 }131 132 // Expands macro: either replacing it with the variable value, or133 // running the plugin command and replacing it with the command's output134 func eval(cmd []string, vars Vars) (string, error) {135 outbuf := bytes.NewBuffer(nil)136 err := run(path.Join(ZSDIR, cmd[0]), cmd[1:], vars, outbuf)137 if err != nil {138 if _, ok := err.(*exec.ExitError); ok {139 return "", err140 }141 outbuf = bytes.NewBuffer(nil)142 err := run(cmd[0], cmd[1:], vars, outbuf)143 // Return exit errors, but ignore if the command was not found144 if _, ok := err.(*exec.ExitError); ok {145 return "", err146 }147 }148 return outbuf.String(), nil149 }150 151 99 // Renders markdown with the given layout into html expanding all the macros 152 func buildMarkdown(path string, funcs template.FuncMap, vars Vars) error {100 func buildMarkdown(path string, w io.Writer, funcs Funcs, vars Vars) error { 153 101 v, body, err := md(path, vars) 154 102 if err != nil { … … 160 108 } 161 109 v["content"] = string(blackfriday.MarkdownBasic([]byte(content))) 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 } 162 118 if strings.HasSuffix(v["layout"], ".amber") { 163 return buildAmber(filepath.Join(ZSDIR, v["layout"]), 164 renameExt(path, "", ".html"), funcs, v) 119 return buildAmber(filepath.Join(ZSDIR, v["layout"]), w, funcs, v) 165 120 } else { 166 return buildPlain(filepath.Join(ZSDIR, v["layout"]), 167 renameExt(path, "", ".html"), funcs, v) 121 return buildHTML(filepath.Join(ZSDIR, v["layout"]), w, funcs, v) 168 122 } 169 123 } 170 124 171 125 // Renders text file expanding all variable macros inside it 172 func build Plain(in, out string, funcs template.FuncMap, vars Vars) error {173 b, err := ioutil.ReadFile( in)126 func buildHTML(path string, w io.Writer, funcs Funcs, vars Vars) error { 127 b, err := ioutil.ReadFile(path) 174 128 if err != nil { 175 129 return err … … 179 133 return err 180 134 } 181 output := filepath.Join(PUBDIR, out)135 output := filepath.Join(PUBDIR, path) 182 136 if s, ok := vars["output"]; ok { 183 137 output = s … … 191 145 192 146 // Renders .amber file into .html 193 func buildAmber( in, out string, funcs template.FuncMap, vars Vars) error {147 func buildAmber(path string, w io.Writer, funcs Funcs, vars Vars) error { 194 148 a := amber.New() 195 err := a.ParseFile( in)149 err := a.ParseFile(path) 196 150 if err != nil { 197 151 return err … … 201 155 return err 202 156 } 203 //amber.FuncMap = amber.FuncMap 204 f, err := os.Create(filepath.Join(PUBDIR, out)) 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 164 } 165 return t.Execute(w, vars) 166 } 167 168 // Compiles .gcss into .css 169 func buildGCSS(path string, w io.Writer) error { 170 f, err := os.Open(path) 205 171 if err != nil { 206 172 return err 207 173 } 208 174 defer f.Close() 209 return t.Execute(f, vars) 210 } 211 212 // Compiles .gcss into .css 213 func buildGCSS(path string) error { 214 f, err := os.Open(path) 215 if err != nil { 216 return err 217 } 218 s := strings.TrimSuffix(path, ".gcss") + ".css" 219 css, err := os.Create(filepath.Join(PUBDIR, s)) 220 if err != nil { 221 return err 222 } 223 224 defer f.Close() 225 defer css.Close() 226 227 _, err = gcss.Compile(css, f) 175 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 181 } 182 defer css.Close() 183 w = css 184 } 185 _, err = gcss.Compile(w, f) 228 186 return err 229 187 } 230 188 231 // Copies file from working directory into public directory 232 func copyFile(path string) (err error) { 233 var in, out *os.File 234 if in, err = os.Open(path); err == nil { 235 defer in.Close() 236 if out, err = os.Create(filepath.Join(PUBDIR, path)); err == nil { 189 // Copies file as is from path to writer 190 func buildRaw(path string, w io.Writer) error { 191 in, err := os.Open(path) 192 if err != nil { 193 return err 194 } 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 { 237 200 defer out.Close() 238 _, err = io.Copy(out, in) 239 } 240 } 201 w = out 202 } 203 } 204 _, err = io.Copy(w, in) 241 205 return err 242 206 } 243 207 244 func varFunc(s string) func() string { 245 return func() string { 246 return s 247 } 248 } 249 250 func pluginFunc(cmd string) func() string { 251 return func() string { 252 return "Not implemented yet" 253 } 254 } 255 256 func createFuncs() template.FuncMap { 257 // Builtin functions 258 funcs := template.FuncMap{ 259 "exec": func(s ...string) string { 260 // Run external command with arguments 261 return "" 262 }, 263 "zs": func(args ...string) string { 264 // Run zs with arguments 265 return "" 266 }, 267 } 268 // Plugin functions 269 files, _ := ioutil.ReadDir(ZSDIR) 270 for _, f := range files { 271 if !f.IsDir() { 272 name := f.Name() 273 if !strings.HasSuffix(name, ".html") && !strings.HasSuffix(name, ".amber") { 274 funcs[strings.TrimSuffix(name, filepath.Ext(name))] = pluginFunc(name) 275 } 276 } 277 } 278 return funcs 279 } 280 281 func renameExt(path, from, to string) string { 282 if from == "" { 283 from = filepath.Ext(path) 284 } 285 return strings.TrimSuffix(path, from) + to 286 } 287 288 func globals() Vars { 289 vars := Vars{} 290 for _, e := range os.Environ() { 291 pair := strings.Split(e, "=") 292 if strings.HasPrefix(pair[0], "ZS_") { 293 vars[strings.ToLower(pair[0][3:])] = pair[1] 294 } 295 } 296 return vars 297 } 298 299 func buildAll(once bool) { 208 func 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) 220 } 221 } 222 223 func buildAll(watch bool) { 300 224 lastModified := time.Unix(0, 0) 301 225 modified := false … … 304 228 for { 305 229 os.Mkdir(PUBDIR, 0755) 306 funcs := createFuncs()230 funcs := builtins() 307 231 err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error { 308 232 // ignore hidden files and directories … … 321 245 modified = true 322 246 } 323 ext := filepath.Ext(path) 324 if ext == ".md" || ext == ".mkd" { 325 log.Println("md: ", path) 326 return buildMarkdown(path, funcs, vars) 327 } else if ext == ".html" || ext == ".xml" { 328 log.Println("html: ", path) 329 return buildPlain(path, path, funcs, vars) 330 } else if ext == ".amber" { 331 log.Println("html: ", path) 332 return buildAmber(path, renameExt(path, ".amber", ".html"), funcs, vars) 333 } else if ext == ".gcss" { 334 log.Println("css: ", path) 335 return buildGCSS(path) 336 } else { 337 log.Println("raw: ", path) 338 return copyFile(path) 339 } 247 log.Println("build: ", path) 248 return build(path, nil, funcs, vars) 340 249 } 341 250 return nil … … 350 259 modified = false 351 260 } 261 if !watch { 262 break 263 } 352 264 lastModified = time.Now() 353 if once {354 break355 }356 265 time.Sleep(1 * time.Second) 357 266 } … … 367 276 switch cmd { 368 277 case "build": 278 buildAll(false) 279 case "watch": 369 280 buildAll(true) 370 case "watch": 371 buildAll(false) // pass duration 281 case "print": 282 if len(args) != 1 { 283 fmt.Println("ERROR: filename expected") 284 } else { 285 build(args[0], os.Stdout, builtins(), globals()) 286 } 372 287 case "var": 373 if len(args) == 0 { 374 log.Println("ERROR: filename expected") 375 return 376 } 377 if vars, _, err := md(args[0], globals()); err == nil { 378 if len(args) > 1 { 379 for _, a := range args[1:] { 380 fmt.Println(vars[a]) 381 } 382 } else { 383 for k, v := range vars { 384 fmt.Println(k + ":" + v) 385 } 386 } 387 } else { 388 log.Println("ERROR:", err) 389 } 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)) 390 295 default: 391 296 err := run(path.Join(ZSDIR, cmd), args, Vars{}, os.Stdout) -
trunk/zs_test.go
r20 r24 7 7 "log" 8 8 "os" 9 "os/exec"10 9 "strings" 11 10 "testing" 12 "text/template"13 11 ) 14 12 … … 79 77 func TestRender(t *testing.T) { 80 78 vars := map[string]string{"foo": "bar"} 81 funcs := template.FuncMap{79 funcs := Funcs{ 82 80 "greet": func(s ...string) string { 83 81 if len(s) == 0 { … … 139 137 } 140 138 141 func TestEvalCommand(t *testing.T) {142 s, err := eval([]string{"echo", "hello"}, map[string]string{})143 if err != nil {144 t.Error(err)145 }146 if s != "hello\n" {147 t.Error(s)148 }149 _, err = eval([]string{"cat", "bogus/file"}, map[string]string{})150 if _, ok := err.(*exec.ExitError); !ok {151 t.Error("expected ExitError")152 }153 _, err = eval([]string{"missing command"}, map[string]string{})154 if err != nil {155 t.Error("missing command should be ignored")156 }157 }158 159 139 func TestHelperProcess(*testing.T) { 160 140 if os.Getenv("ZS_HELPER") != "1" {
Note:
See TracChangeset
for help on using the changeset viewer.