Skip to content

Commit

Permalink
Merge pull request sipin#25 from sipin/fsnotify
Browse files Browse the repository at this point in the history
Fsnotify
  • Loading branch information
chenyukang committed Sep 1, 2014
2 parents 4a15c0f + 9f8cc43 commit bb6c759
Show file tree
Hide file tree
Showing 4 changed files with 224 additions and 93 deletions.
22 changes: 4 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,28 +291,15 @@ Here is a simple example of [gorazor templates](https://github.com/sipin/gorazor

## How to auto re-generate when gohtml file changes?

We may add `gorazor watch` cmd after Go 1.3 which has official [fsnotify](https://docs.google.com/document/d/1xl_aRcCbksFRmCKtoyRQG9L7j6DIdMZtrkFAoi5EXaA/edit) support.
We use `https://gopkg.in/fsnotify.v1` to implement watching option.

Currently, we are using below scripts to handle this issue on mac:
Useage: `gorazor -watch input_dir output_dir` to re-generate gohtml file changes,

* gorazor_watch.sh
```bash
#!/bin/bash

gorazor tpl src/tpl
watchmedo shell-command --patterns="*.gohtml" --recursive --command='python gorazor.py ${watch_src_path}'
new/modify -> generate corresponding Go file, make new directory if necessary
remove -> remove corresponding Go file or directory
```
* gorazor.py
```python
import sys, os

path = sys.argv[1]
os.system("gorazor " + path + " " + path.replace("/tpl/", "/src/tpl/")[:-4])
```

* [watchmedo](https://github.com/gorakhargosh/watchdog)

# Credits
The very [first version](https://github.com/sipin/gorazor/releases/tag/vash) of GoRazor is essentially a hack of razor's port in javascript: [vash](https://github.com/kirbysayshi/vash), thus requires node's to run.
Expand All @@ -321,7 +308,6 @@ GoRazor has been though several rounds of refactoring and it has completely rewr
# Todo
* Add tools, like monitor template changes and auto re-generate
* Add default html widgets
* Add more usage examples
* Generate more function overloads, like accept additional buffer parameter for write
49 changes: 29 additions & 20 deletions gorazor.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,37 +11,46 @@ import (
)

func Usage() {
fmt.Fprintf(os.Stderr, "usage: gorazor <input dir or file> <output dir or file> [-debug]\n")
fmt.Fprintf(os.Stderr, "usage: gorazor [-debug] [-watch] <input dir or file> <output dir or file>\n")
flag.PrintDefaults()
os.Exit(2)
os.Exit(1)
}

func main() {
flag.Usage = Usage
isDebug := flag.Bool("debug", false, "use debug mode")
isWatch := flag.Bool("watch", false, "use watch mode")

flag.Parse()

options := gorazor.Option{}

if flag.NArg() == 3 && flag.Arg(2) == "-debug" {
options["Debug"] = true
if *isDebug {
options["Debug"] = *isDebug
}
if *isWatch {
options["Watch"] = *isWatch
}

if len(flag.Args()) != 2 {
flag.Usage()
}
if flag.NArg() >= 2 {
arg1, arg2 := flag.Arg(0), flag.Arg(1)
stat, err := os.Stat(arg1)

input, output := flag.Arg(0), flag.Arg(1)
stat, err := os.Stat(input)
if err != nil {
fmt.Println(err)
os.Exit(1)
}

if stat.IsDir() {
fmt.Printf("Gorazor processing dir: %s -> %s\n", input, output)
err := gorazor.GenFolder(input, output, options)
if err != nil {
fmt.Println(err)
os.Exit(2)
}
if stat.IsDir() {
fmt.Printf("Gorazor processing dir: %s -> %s\n", arg1, arg2)
err := gorazor.GenFolder(arg1, arg2, options)
if err != nil {
fmt.Println(err)
}
} else if stat.Mode().IsRegular() {
fmt.Printf("Gorazor processing file: %s -> %s\n", arg1, arg2)
gorazor.GenFile(arg1, arg2, options)
} else {
flag.Usage()
}
} else if stat.Mode().IsRegular() {
fmt.Printf("Gorazor processing file: %s -> %s\n", input, output)
gorazor.GenFile(input, output, options)
}
}
92 changes: 92 additions & 0 deletions gorazor/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package gorazor

//------------------------------ API ------------------------------

import (
"errors"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
)

const (
go_extension = ".go"
gz_extension = ".gohtml"
)

// Generate from input to output file,
// gofmt will trigger an error if it fails.
func GenFile(input string, output string, options Option) error {
outdir := filepath.Dir(output)
if !exists(outdir) {
os.MkdirAll(outdir, 0775)
}
return generate(input, output, options)
}

// Generate from directory to directory, Find all the files with extension
// of .gohtml and generate it into target dir.
func GenFolder(indir string, outdir string, options Option) (err error) {
if !exists(indir) {
return errors.New("Input directory does not exsits")
} else {
if err != nil {
return err
}
}
//Make it
if !exists(outdir) {
os.MkdirAll(outdir, 0775)
}

incdir_abs, _ := filepath.Abs(indir)
outdir_abs, _ := filepath.Abs(outdir)

paths := []string{}

visit := func(path string, info os.FileInfo, err error) error {
if !info.IsDir() {
//Just do file with exstension .gohtml
if !strings.HasSuffix(path, gz_extension) {
return nil
}
filename := filepath.Base(path)
if strings.HasPrefix(filename, ".#") {
return nil
}
paths = append(paths, path)
}
return nil
}

fun := func(path string, res chan<- string) {
//adjust with the abs path, so that we keep the same directory hierarchy
input, _ := filepath.Abs(path)
output := strings.Replace(input, incdir_abs, outdir_abs, 1)
output = strings.Replace(output, gz_extension, go_extension, -1)
err := GenFile(path, output, options)
if err != nil {
res <- fmt.Sprintf("%s -> %s", path, output)
os.Exit(2)
}
res <- fmt.Sprintf("%s -> %s", path, output)
}

err = filepath.Walk(indir, visit)
runtime.GOMAXPROCS(runtime.NumCPU())
result := make(chan string, len(paths))

for w := 0; w < len(paths); w++ {
go fun(paths[w], result)
}
for i := 0; i < len(paths); i++ {
<-result
}

if options["Watch"] != nil {
watchDir(incdir_abs, outdir_abs, options)
}
return
}
154 changes: 99 additions & 55 deletions gorazor/gogen.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
package gorazor

import (
"errors"
"fmt"
"go/parser"
"go/token"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"

"gopkg.in/fsnotify.v1"
)

var GorazorNamespace = `"github.com/sipin/gorazor/gorazor"`
Expand Down Expand Up @@ -418,79 +419,122 @@ func generate(path string, output string, Options Option) error {
return err
}

//------------------------------ API ------------------------------
const (
go_extension = ".go"
gz_extension = ".gohtml"
)
func watchDir(input, output string, options Option) error {
log.Println("Watching dir:", input, output)
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
defer watcher.Close()

done := make(chan bool)

// Generate from input to output file,
// gofmt will trigger an error if it fails.
func GenFile(input string, output string, options Option) error {
outdir := filepath.Dir(output)
if !exists(outdir) {
os.MkdirAll(outdir, 0775)
output_path := func(path string) string {
res := strings.Replace(path, input, output, 1)
return res
}
return generate(input, output, options)
}

// Generate from directory to directory, Find all the files with extension
// of .gohtml and generate it into target dir.
func GenFolder(indir string, outdir string, options Option) (err error) {
if !exists(indir) {
return errors.New("Input directory does not exsits")
} else {
if err != nil {
return err
gen := func(filename string) error {
outpath := output_path(filename)
outpath = strings.Replace(outpath, ".gohtml", ".go", 1)
outdir := filepath.Dir(outpath)
if !exists(outdir) {
os.MkdirAll(outdir, 0775)
}
}
//Make it
if !exists(outdir) {
os.MkdirAll(outdir, 0775)
err := GenFile(filename, outpath, options)
if err == nil {
log.Printf("%s -> %s\n", filename, outpath)
}
return err
}

incdir_abs, _ := filepath.Abs(indir)
outdir_abs, _ := filepath.Abs(outdir)

paths := []string{}

visit := func(path string, info os.FileInfo, err error) error {
visit_gen := func(path string, info os.FileInfo, err error) error {
if !info.IsDir() {
//Just do file with exstension .gohtml
if !strings.HasSuffix(path, gz_extension) {
if !strings.HasSuffix(path, ".gohtml") {
return nil
}
filename := filepath.Base(path)
if strings.HasPrefix(filename, ".#") {
return nil
}
paths = append(paths, path)
err := gen(path)
if err != nil {
return err
}
}
return nil
}

fun := func(path string, res chan<- string) {
//adjust with the abs path, so that we keep the same directory hierarchy
input, _ := filepath.Abs(path)
output := strings.Replace(input, incdir_abs, outdir_abs, 1)
output = strings.Replace(output, gz_extension, go_extension, -1)
err := GenFile(path, output, options)
if err != nil {
res <- fmt.Sprintf("%s -> %s", path, output)
os.Exit(2)
go func() {
for {
select {
case event := <-watcher.Events:
filename := event.Name
if filename == "" {
//should be a bug for fsnotify
continue
}
if event.Op&fsnotify.Remove != fsnotify.Remove &&
(event.Op&fsnotify.Write == fsnotify.Write ||
event.Op&fsnotify.Create == fsnotify.Create) {
stat, err := os.Stat(filename)
if err != nil {
continue
}
if stat.IsDir() {
log.Println("add dir:", filename)
watcher.Add(filename)
output := output_path(filename)
log.Println("mkdir:", output)
if !exists(output) {
os.MkdirAll(output, 0755)
err = filepath.Walk(filename, visit_gen)
if err != nil {
done <- true
}
}
continue
}
if !strings.HasPrefix(filepath.Base(filename), ".#") &&
strings.HasSuffix(filename, ".gohtml") {
gen(filename)
}
} else if event.Op&fsnotify.Remove == fsnotify.Remove ||
event.Op&fsnotify.Rename == fsnotify.Rename {
output := output_path(filename)
if exists(output) {
//shoud be dir
watcher.Remove(filename)
os.RemoveAll(output)
log.Println("remove dir:", output)
} else if strings.HasSuffix(output, ".gohtml") {
output = strings.Replace(output, ".gohtml", ".go", 1)
if exists(output) {
os.Remove(output)
log.Println("removing file:", output)
}
}
}
case err := <-watcher.Errors:
log.Println("error:", err)
continue
}
}
res <- fmt.Sprintf("%s -> %s", path, output)
}
}()

err = filepath.Walk(indir, visit)
runtime.GOMAXPROCS(runtime.NumCPU())
result := make(chan string, len(paths))

for w := 0; w < len(paths); w++ {
go fun(paths[w], result)
visit := func(path string, info os.FileInfo, err error) error {
if info.IsDir() {
watcher.Add(path)
}
return nil
}
for i := 0; i < len(paths); i++ {
<-result

err = filepath.Walk(input, visit)
err = watcher.Add(input)
if err != nil {
log.Fatal(err)
}
return
<-done
return nil
}

0 comments on commit bb6c759

Please sign in to comment.