├── .github └── workflows │ └── release.yml ├── .gitignore ├── Makefile ├── README.md ├── go.mod ├── go.sum └── main.go /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | release: 10 | name: Release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@master 15 | - name: Setup Go 16 | uses: actions/setup-go@v3 17 | with: 18 | go-version: 1.21.x 19 | - name: Cross build 20 | run: make cross 21 | - name: Create Release 22 | id: create_release 23 | uses: actions/create-release@master 24 | env: 25 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 26 | with: 27 | tag_name: ${{ github.ref }} 28 | release_name: Release ${{ github.ref }} 29 | - name: Upload 30 | run: make upload 31 | env: 32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | gotr 2 | gotr.exe 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BIN := gotr 2 | VERSION := $$(make -s show-version) 3 | CURRENT_REVISION := $(shell git rev-parse --short HEAD) 4 | BUILD_LDFLAGS := "-s -w -X main.revision=$(CURRENT_REVISION)" 5 | GOBIN ?= $(shell go env GOPATH)/bin 6 | export GO111MODULE=on 7 | 8 | .PHONY: all 9 | all: clean build 10 | 11 | .PHONY: build 12 | build: 13 | go build -ldflags=$(BUILD_LDFLAGS) -o $(BIN) . 14 | 15 | .PHONY: install 16 | install: 17 | go install -ldflags=$(BUILD_LDFLAGS) . 18 | 19 | .PHONY: show-version 20 | show-version: $(GOBIN)/gobump 21 | @gobump show -r . 22 | 23 | $(GOBIN)/gobump: 24 | go install github.com/x-motemen/gobump/cmd/gobump@latest 25 | 26 | .PHONY: cross 27 | cross: $(GOBIN)/goxz 28 | goxz -n $(BIN) -pv=v$(VERSION) -build-ldflags=$(BUILD_LDFLAGS) . 29 | 30 | $(GOBIN)/goxz: 31 | go install github.com/Songmu/goxz/cmd/goxz@latest 32 | 33 | .PHONY: test 34 | test: build 35 | go test -v ./... 36 | 37 | .PHONY: clean 38 | clean: 39 | rm -rf $(BIN) goxz 40 | go clean 41 | 42 | .PHONY: bump 43 | bump: $(GOBIN)/gobump 44 | ifneq ($(shell git status --porcelain),) 45 | $(error git workspace is dirty) 46 | endif 47 | ifneq ($(shell git rev-parse --abbrev-ref HEAD),master) 48 | $(error current branch is not master) 49 | endif 50 | @gobump up -w . 51 | git commit -am "bump up version to $(VERSION)" 52 | git tag "v$(VERSION)" 53 | git push origin master 54 | git push origin "refs/tags/v$(VERSION)" 55 | 56 | .PHONY: upload 57 | upload: $(GOBIN)/ghr 58 | ghr "v$(VERSION)" goxz 59 | 60 | $(GOBIN)/ghr: 61 | go install github.com/tcnksm/ghr@latest 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gotr 2 | 3 | [entr](http://entrproject.org/) clone in golang 4 | 5 | ## License 6 | 7 | MIT 8 | 9 | ## Author 10 | 11 | Yasuhiro Matsumoto (a.k.a. mattn) 12 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mattn/gotr 2 | 3 | go 1.23.1 4 | 5 | require gopkg.in/fsnotify.v1 v1.4.7 6 | 7 | require ( 8 | github.com/fsnotify/fsnotify v1.8.0 // indirect 9 | golang.org/x/sys v0.27.0 // indirect 10 | ) 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= 2 | github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= 3 | golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= 4 | golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 5 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= 6 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 7 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "flag" 6 | "fmt" 7 | "os" 8 | "os/exec" 9 | "path/filepath" 10 | "runtime" 11 | "sync" 12 | 13 | "gopkg.in/fsnotify.v1" 14 | ) 15 | 16 | const name = "gotr" 17 | 18 | const version = "0.0.1" 19 | 20 | var revision = "HEAD" 21 | 22 | var ( 23 | m sync.Mutex 24 | c = flag.Bool("c", false, "clear") 25 | r = flag.Bool("r", false, "restart") 26 | ) 27 | 28 | func cls() { 29 | var cmd *exec.Cmd 30 | if runtime.GOOS == "windows" { 31 | cmd = exec.Command("cmd", "/c", "cls") 32 | } else { 33 | cmd = exec.Command("clear") 34 | } 35 | cmd.Stdout = os.Stdout 36 | cmd.Run() 37 | } 38 | 39 | type gotr struct { 40 | cmd *exec.Cmd 41 | going bool 42 | } 43 | 44 | func (g *gotr) run(n string) { 45 | m.Lock() 46 | if g.going { 47 | return 48 | } 49 | g.going = true 50 | m.Unlock() 51 | 52 | if *r && g.cmd != nil { 53 | if g.cmd.ProcessState == nil || !g.cmd.ProcessState.Exited() { 54 | if g.cmd.Process != nil { 55 | if err := g.cmd.Process.Signal(os.Interrupt); err != nil { 56 | g.cmd.Process.Kill() 57 | } 58 | g.cmd.Process.Wait() 59 | } 60 | } 61 | } 62 | g.cmd = exec.Command(flag.Arg(0)) 63 | args := make([]string, flag.NArg()) 64 | for i := 0; i < len(args); i++ { 65 | args[i] = flag.Arg(i) 66 | if args[i] == "/_" { 67 | args[i] = n 68 | } 69 | } 70 | g.cmd.Args = args 71 | g.cmd.Stdout = os.Stdout 72 | g.cmd.Stderr = os.Stderr 73 | if *c { 74 | cls() 75 | } 76 | if err := g.cmd.Start(); err != nil { 77 | fmt.Fprintln(os.Stderr, os.Args[0], err) 78 | } 79 | 80 | m.Lock() 81 | g.going = false 82 | m.Unlock() 83 | } 84 | 85 | func main() { 86 | flag.Parse() 87 | 88 | w, err := fsnotify.NewWatcher() 89 | if err != nil { 90 | fmt.Fprintln(os.Stderr, os.Args[0], err) 91 | os.Exit(1) 92 | } 93 | 94 | files := map[string]bool{} 95 | 96 | go func() { 97 | scanner := bufio.NewScanner(os.Stdin) 98 | for scanner.Scan() { 99 | n := scanner.Text() 100 | m.Lock() 101 | if _, ok := files[n]; !ok { 102 | if fn, err := filepath.Abs(n); err == nil { 103 | // handle long name 104 | files[fn] = true 105 | w.Add(n) 106 | } 107 | } 108 | m.Unlock() 109 | } 110 | }() 111 | 112 | app := new(gotr) 113 | for { 114 | select { 115 | case ev := <-w.Events: 116 | if ev.Op == fsnotify.Create || ev.Op == fsnotify.Rename { 117 | w.Add(ev.Name) 118 | } 119 | fn, err := filepath.Abs(ev.Name) 120 | if err != nil { 121 | continue 122 | } 123 | m.Lock() 124 | // check as long name 125 | if _, ok := files[fn]; !ok { 126 | m.Unlock() 127 | continue 128 | } 129 | m.Unlock() 130 | go app.run(ev.Name) 131 | case err := <-w.Errors: 132 | fmt.Fprintln(os.Stderr, os.Args[0], err) 133 | } 134 | } 135 | } 136 | --------------------------------------------------------------------------------