├── LICENSE ├── README.md ├── cmd └── lime │ └── main.go ├── lime.png ├── main.go ├── server └── server.go ├── vendor.conf ├── vendor └── github.com │ └── codegangsta │ └── gin │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── lib │ ├── builder.go │ ├── config.go │ ├── proxy.go │ └── runner.go │ └── wercker.yml └── wercker.yml /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Shintaro Kaneko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lime 2 | 3 | [![wercker status](https://app.wercker.com/status/5ae8f488a3136a826b480a6bbf33138a/s/master "wercker status")](https://app.wercker.com/project/bykey/5ae8f488a3136a826b480a6bbf33138a) 4 | 5 | `lime` is a simple command line utility for live-reloading Go applications. 6 | Just run `lime` in your app directory and your web app will be served with 7 | `lime` as a proxy. `lime` will automatically recompile your code when it 8 | detects a change. Your app will be restarted the next time it receives an 9 | HTTP request. 10 | 11 | `lime` adheres to the "silence is golden" principle, so it will only complain 12 | if there was a compiler error or if you succesfully compile after an error. 13 | 14 | ![lime](http://i0.wp.com/www.kaneshin.co/wp-content/uploads/2016/02/65y1806uDp.gif?resize=634%2C424) 15 | 16 | ## Installation 17 | 18 | ```shell 19 | go get github.com/kaneshin/lime 20 | ``` 21 | 22 | Make sure that `lime` was installed correctly: 23 | 24 | ```shell 25 | lime -h 26 | ``` 27 | 28 | ## Usage 29 | 30 | ```shell 31 | cd /path/to/app 32 | lime 33 | ``` 34 | 35 | ### Example 36 | 37 | ```shell 38 | lime -bin=/tmp/bin -ignore-pattern="(\\.git)" -path=./app -immediate=true ./app -version 39 | ``` 40 | 41 | ### Options 42 | 43 | | option | description | 44 | | :----- | :---------- | 45 | | port | port for the proxy server | 46 | | app-port | port for the Go web server | 47 | | bin | locates built binary | 48 | | ignore-pattern | pattern to ignore | 49 | | build-pattern | pattern to build | 50 | | run-pattern | pattern to run | 51 | | path, -t "." | watch directory | 52 | | immediate, -i | run the server immediately after it's built | 53 | | godep, -g | use godep when building | 54 | 55 | 56 | ## License 57 | 58 | [The MIT License (MIT)](http://kaneshin.mit-license.org/) 59 | 60 | 61 | ## Author 62 | 63 | Shintaro Kaneko 64 | -------------------------------------------------------------------------------- /cmd/lime/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "net/http/httptest" 7 | "regexp" 8 | "strings" 9 | 10 | "github.com/codegangsta/cli" 11 | "github.com/codegangsta/gin/lib" 12 | 13 | "log" 14 | "os" 15 | "os/exec" 16 | "os/signal" 17 | "path/filepath" 18 | "strconv" 19 | "syscall" 20 | "time" 21 | ) 22 | 23 | var ( 24 | buildTime = time.Now() 25 | logger = struct { 26 | info *log.Logger 27 | warn *log.Logger 28 | }{ 29 | log.New(os.Stdout, "[lime-INFO] ", log.Ldate|log.Ltime), 30 | log.New(os.Stdout, "[lime-WARN] ", log.Ldate|log.Ltime), 31 | } 32 | immediate = false 33 | verbose = false 34 | ) 35 | 36 | func main() { 37 | app := cli.NewApp() 38 | app.Name = "lime" 39 | app.Usage = "A live reload utility for Go applications." 40 | app.Version = "1.0.0" 41 | app.Action = mainAction 42 | app.Flags = []cli.Flag{ 43 | cli.IntFlag{ 44 | Name: "port", 45 | Usage: "port for the proxy server", 46 | }, 47 | cli.IntFlag{ 48 | Name: "app-port", 49 | Usage: "port for the Go web server", 50 | }, 51 | cli.StringFlag{ 52 | Name: "bin,b", 53 | Value: "./lime-bin", 54 | Usage: "path of generated binary file", 55 | }, 56 | cli.StringFlag{ 57 | Name: "ignore-pattern", 58 | Usage: "pattern to ignore", 59 | }, 60 | cli.StringFlag{ 61 | Name: "build-pattern", 62 | Value: "(\\.go)", 63 | Usage: "pattern to build", 64 | }, 65 | cli.StringFlag{ 66 | Name: "run-pattern", 67 | Value: "(\\.html|\\.css|\\.js)", 68 | Usage: "pattern to run", 69 | }, 70 | cli.StringFlag{ 71 | Name: "path,t", 72 | Value: ".", 73 | Usage: "path to watch files from", 74 | }, 75 | cli.BoolFlag{ 76 | Name: "immediate,i", 77 | Usage: "run the server immediately after it's built", 78 | }, 79 | cli.BoolFlag{ 80 | Name: "verbose", 81 | Usage: "show verbose output", 82 | }, 83 | cli.BoolFlag{ 84 | Name: "godep,g", 85 | Usage: "use godep when building", 86 | }, 87 | } 88 | 89 | app.Run(os.Args) 90 | } 91 | 92 | var ( 93 | ipat *regexp.Regexp 94 | bpat *regexp.Regexp 95 | rpat *regexp.Regexp 96 | ) 97 | 98 | func mainAction(c *cli.Context) { 99 | // TODO: New feature to check update binary is 100 | // logger.info.Printf("Skipping lime update check.\n") 101 | immediate = c.GlobalBool("immediate") 102 | verbose = c.GlobalBool("verbose") 103 | 104 | wd, err := os.Getwd() 105 | if err != nil { 106 | logger.warn.Fatal(err) 107 | } 108 | 109 | bp := wd 110 | args := c.Args() 111 | if len(args) > 0 { 112 | bp = args[0] 113 | } 114 | builder := gin.NewBuilder(bp, c.GlobalString("bin"), c.GlobalBool("godep")) 115 | var runner gin.Runner 116 | if len(args) < 2 { 117 | runner = gin.NewRunner(builder.Binary()) 118 | } else { 119 | runner = gin.NewRunner(builder.Binary(), args[1:]...) 120 | } 121 | runner.SetWriter(os.Stdout) 122 | proxy := gin.NewProxy(builder, runner) 123 | f := func(w http.ResponseWriter, r *http.Request) { 124 | runner.Kill() 125 | runner.Run() 126 | } 127 | server := httptest.NewServer(http.HandlerFunc(f)) 128 | logger.info.Printf("Starting lime server at: %s\n", server.URL) 129 | 130 | if port := c.GlobalInt("port"); port > 0 { 131 | appPort := c.GlobalInt("app-port") 132 | if appPort == 0 { 133 | appPort = port + 1 134 | } 135 | config := &gin.Config{ 136 | Port: port, 137 | ProxyTo: "http://localhost:" + strconv.Itoa(appPort), 138 | } 139 | 140 | if err := proxy.Run(config); err != nil { 141 | logger.warn.Fatal(err) 142 | } 143 | 144 | logger.info.Printf("listening on port %d\n", port) 145 | } 146 | 147 | shutdown(runner) 148 | 149 | // build right now 150 | build(builder, runner) 151 | 152 | // scan for changes 153 | if p := c.GlobalString("ignore-pattern"); len(p) > 0 { 154 | ipat = regexp.MustCompile(p) 155 | } 156 | if p := c.GlobalString("build-pattern"); len(p) > 0 { 157 | bpat = regexp.MustCompile(p) 158 | } 159 | if p := c.GlobalString("run-pattern"); len(p) > 0 { 160 | rpat = regexp.MustCompile(p) 161 | } 162 | 163 | targets := strings.Split(c.GlobalString("path"), ",") 164 | for { 165 | for _, target := range targets { 166 | scanChanges(filepath.Clean(filepath.Join(wd, target)), func(path string) error { 167 | ext := filepath.Ext(path) 168 | switch { 169 | case bpat != nil && bpat.MatchString(ext): 170 | logger.info.Printf("Detected file changes:\n %s", path) 171 | runner.Kill() 172 | build(builder, runner) 173 | case rpat != nil && rpat.MatchString(ext): 174 | logger.info.Printf("Detected file changes:\n %s", path) 175 | runner.Kill() 176 | runner.Run() 177 | default: 178 | return nil 179 | } 180 | buildTime = time.Now() 181 | return errors.New("done") 182 | }) 183 | } 184 | time.Sleep(500 * time.Millisecond) 185 | } 186 | } 187 | 188 | func build(builder gin.Builder, runner gin.Runner) { 189 | st := time.Now() 190 | logger.info.Println("Build started") 191 | if err := builder.Build(); err != nil { 192 | logger.info.Println("ERROR! Build failed") 193 | logger.info.Println(builder.Errors()) 194 | re := regexp.MustCompile("cannot find package \".*\"") 195 | matches := re.FindAllStringSubmatch(builder.Errors(), -1) 196 | goget(matches) 197 | } else { 198 | et := time.Now() 199 | logger.info.Println("Build Successful") 200 | if verbose { 201 | logger.info.Printf("Build time: %v\n", et.Sub(st)) 202 | } 203 | if immediate { 204 | runner.Run() 205 | } 206 | } 207 | 208 | time.Sleep(100 * time.Millisecond) 209 | } 210 | 211 | func goget(packs [][]string) { 212 | goPath, err := exec.LookPath("go") 213 | if err != nil { 214 | logger.warn.Fatalf("Go executable not found in PATH.") 215 | } 216 | for _, pack := range packs { 217 | for _, p := range pack { 218 | rep := strings.Replace(strings.Replace(p, "cannot find package ", "", -1), `"`, "", -1) 219 | args := []string{"get", "-u", rep} 220 | cmd := exec.Command(goPath, args...) 221 | logger.info.Printf("go get -u %s\n", rep) 222 | cmd.CombinedOutput() 223 | } 224 | } 225 | } 226 | 227 | type scanCallback func(path string) error 228 | 229 | func scanChanges(watchPath string, cb scanCallback) { 230 | filepath.Walk(watchPath, func(path string, info os.FileInfo, err error) error { 231 | if err != nil { 232 | logger.warn.Fatal(err) 233 | return nil 234 | } 235 | 236 | if ipat != nil && ipat.MatchString(path) { 237 | return filepath.SkipDir 238 | } 239 | 240 | if info.ModTime().After(buildTime) { 241 | return cb(path) 242 | } 243 | 244 | return nil 245 | }) 246 | } 247 | 248 | func shutdown(runner gin.Runner) { 249 | c := make(chan os.Signal, 2) 250 | signal.Notify(c, os.Interrupt, syscall.SIGTERM) 251 | go func() { 252 | s := <-c 253 | logger.info.Println("Got signal: ", s) 254 | err := runner.Kill() 255 | if err != nil { 256 | logger.info.Print("Error killing: ", err) 257 | } 258 | os.Exit(1) 259 | }() 260 | } 261 | -------------------------------------------------------------------------------- /lime.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaneshin/lime/80e6eb47bb4b09b0cb2c79960ce1d2bb2eab3cdb/lime.png -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "net/http/httptest" 7 | "regexp" 8 | "strings" 9 | 10 | "github.com/codegangsta/cli" 11 | "github.com/codegangsta/gin/lib" 12 | 13 | "log" 14 | "os" 15 | "os/exec" 16 | "os/signal" 17 | "path/filepath" 18 | "strconv" 19 | "syscall" 20 | "time" 21 | ) 22 | 23 | var ( 24 | buildTime = time.Now() 25 | logger = struct { 26 | info *log.Logger 27 | warn *log.Logger 28 | }{ 29 | log.New(os.Stdout, "[lime-INFO] ", log.Ldate|log.Ltime), 30 | log.New(os.Stdout, "[lime-WARN] ", log.Ldate|log.Ltime), 31 | } 32 | immediate = false 33 | verbose = false 34 | ) 35 | 36 | func main() { 37 | app := cli.NewApp() 38 | app.Name = "lime" 39 | app.Usage = "A live reload utility for Go applications." 40 | app.Version = "1.0.0" 41 | app.Action = mainAction 42 | app.Flags = []cli.Flag{ 43 | cli.IntFlag{ 44 | Name: "port", 45 | Usage: "port for the proxy server", 46 | }, 47 | cli.IntFlag{ 48 | Name: "app-port", 49 | Usage: "port for the Go web server", 50 | }, 51 | cli.StringFlag{ 52 | Name: "bin,b", 53 | Value: "./lime-bin", 54 | Usage: "path of generated binary file", 55 | }, 56 | cli.StringFlag{ 57 | Name: "ignore-pattern", 58 | Usage: "pattern to ignore", 59 | }, 60 | cli.StringFlag{ 61 | Name: "build-pattern", 62 | Value: "(\\.go)", 63 | Usage: "pattern to build", 64 | }, 65 | cli.StringFlag{ 66 | Name: "run-pattern", 67 | Value: "(\\.html|\\.css|\\.js)", 68 | Usage: "pattern to run", 69 | }, 70 | cli.StringFlag{ 71 | Name: "path,t", 72 | Value: ".", 73 | Usage: "path to watch files from", 74 | }, 75 | cli.BoolFlag{ 76 | Name: "immediate,i", 77 | Usage: "run the server immediately after it's built", 78 | }, 79 | cli.BoolFlag{ 80 | Name: "verbose", 81 | Usage: "show verbose output", 82 | }, 83 | cli.BoolFlag{ 84 | Name: "godep,g", 85 | Usage: "use godep when building", 86 | }, 87 | } 88 | 89 | app.Run(os.Args) 90 | } 91 | 92 | var ( 93 | ipat *regexp.Regexp 94 | bpat *regexp.Regexp 95 | rpat *regexp.Regexp 96 | ) 97 | 98 | func mainAction(c *cli.Context) { 99 | // TODO: New feature to check update binary is 100 | // logger.info.Printf("Skipping lime update check.\n") 101 | immediate = c.GlobalBool("immediate") 102 | verbose = c.GlobalBool("verbose") 103 | 104 | wd, err := os.Getwd() 105 | if err != nil { 106 | logger.warn.Fatal(err) 107 | } 108 | 109 | bp := wd 110 | args := c.Args() 111 | if len(args) > 0 { 112 | bp = args[0] 113 | } 114 | builder := gin.NewBuilder(bp, c.GlobalString("bin"), c.GlobalBool("godep")) 115 | var runner gin.Runner 116 | if len(args) < 2 { 117 | runner = gin.NewRunner(builder.Binary()) 118 | } else { 119 | runner = gin.NewRunner(builder.Binary(), args[1:]...) 120 | } 121 | runner.SetWriter(os.Stdout) 122 | proxy := gin.NewProxy(builder, runner) 123 | f := func(w http.ResponseWriter, r *http.Request) { 124 | runner.Kill() 125 | runner.Run() 126 | } 127 | server := httptest.NewServer(http.HandlerFunc(f)) 128 | logger.info.Printf("Starting lime server at: %s\n", server.URL) 129 | 130 | if port := c.GlobalInt("port"); port > 0 { 131 | appPort := c.GlobalInt("app-port") 132 | if appPort == 0 { 133 | appPort = port + 1 134 | } 135 | config := &gin.Config{ 136 | Port: port, 137 | ProxyTo: "http://localhost:" + strconv.Itoa(appPort), 138 | } 139 | 140 | if err := proxy.Run(config); err != nil { 141 | logger.warn.Fatal(err) 142 | } 143 | 144 | logger.info.Printf("listening on port %d\n", port) 145 | } 146 | 147 | shutdown(runner) 148 | 149 | // build right now 150 | build(builder, runner) 151 | 152 | // scan for changes 153 | if p := c.GlobalString("ignore-pattern"); len(p) > 0 { 154 | ipat = regexp.MustCompile(p) 155 | } 156 | if p := c.GlobalString("build-pattern"); len(p) > 0 { 157 | bpat = regexp.MustCompile(p) 158 | } 159 | if p := c.GlobalString("run-pattern"); len(p) > 0 { 160 | rpat = regexp.MustCompile(p) 161 | } 162 | 163 | targets := strings.Split(c.GlobalString("path"), ",") 164 | for { 165 | for _, target := range targets { 166 | scanChanges(filepath.Clean(filepath.Join(wd, target)), func(path string) error { 167 | ext := filepath.Ext(path) 168 | switch { 169 | case bpat != nil && bpat.MatchString(ext): 170 | logger.info.Printf("Detected file changes:\n %s", path) 171 | runner.Kill() 172 | build(builder, runner) 173 | case rpat != nil && rpat.MatchString(ext): 174 | logger.info.Printf("Detected file changes:\n %s", path) 175 | runner.Kill() 176 | runner.Run() 177 | default: 178 | return nil 179 | } 180 | buildTime = time.Now() 181 | return errors.New("done") 182 | }) 183 | } 184 | time.Sleep(500 * time.Millisecond) 185 | } 186 | } 187 | 188 | func build(builder gin.Builder, runner gin.Runner) { 189 | st := time.Now() 190 | logger.info.Println("Build started") 191 | if err := builder.Build(); err != nil { 192 | logger.info.Println("ERROR! Build failed") 193 | logger.info.Println(builder.Errors()) 194 | re := regexp.MustCompile("cannot find package \".*\"") 195 | matches := re.FindAllStringSubmatch(builder.Errors(), -1) 196 | goget(matches) 197 | } else { 198 | et := time.Now() 199 | logger.info.Println("Build Successful") 200 | if verbose { 201 | logger.info.Printf("Build time: %v\n", et.Sub(st)) 202 | } 203 | if immediate { 204 | runner.Run() 205 | } 206 | } 207 | 208 | time.Sleep(100 * time.Millisecond) 209 | } 210 | 211 | func goget(packs [][]string) { 212 | goPath, err := exec.LookPath("go") 213 | if err != nil { 214 | logger.warn.Fatalf("Go executable not found in PATH.") 215 | } 216 | for _, pack := range packs { 217 | for _, p := range pack { 218 | rep := strings.Replace(strings.Replace(p, "cannot find package ", "", -1), `"`, "", -1) 219 | args := []string{"get", "-u", rep} 220 | cmd := exec.Command(goPath, args...) 221 | logger.info.Printf("go get -u %s\n", rep) 222 | cmd.CombinedOutput() 223 | } 224 | } 225 | } 226 | 227 | type scanCallback func(path string) error 228 | 229 | func scanChanges(watchPath string, cb scanCallback) { 230 | filepath.Walk(watchPath, func(path string, info os.FileInfo, err error) error { 231 | if err != nil { 232 | logger.warn.Fatal(err) 233 | return nil 234 | } 235 | 236 | if ipat != nil && ipat.MatchString(path) { 237 | return filepath.SkipDir 238 | } 239 | 240 | if info.ModTime().After(buildTime) { 241 | return cb(path) 242 | } 243 | 244 | return nil 245 | }) 246 | } 247 | 248 | func shutdown(runner gin.Runner) { 249 | c := make(chan os.Signal, 2) 250 | signal.Notify(c, os.Interrupt, syscall.SIGTERM) 251 | go func() { 252 | s := <-c 253 | logger.info.Println("Got signal: ", s) 254 | err := runner.Kill() 255 | if err != nil { 256 | logger.info.Print("Error killing: ", err) 257 | } 258 | os.Exit(1) 259 | }() 260 | } 261 | -------------------------------------------------------------------------------- /server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | func main() { 9 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 10 | fmt.Fprintf(w, "Hello world!\n") 11 | }) 12 | http.ListenAndServe(":8181", nil) 13 | } 14 | -------------------------------------------------------------------------------- /vendor.conf: -------------------------------------------------------------------------------- 1 | github.com/codegangsta/gin 3691f64132455125ba1d3ebdaf050a63de4e299f 2 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/gin/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | 24 | lib/test_fixtures/build_success/build_success 25 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/gin/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Jeremy Saenz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/gin/README.md: -------------------------------------------------------------------------------- 1 | gin [![wercker status](https://app.wercker.com/status/f413ccbd85cfc4a58a37f03dd7aaa87e "wercker status")](https://app.wercker.com/project/bykey/f413ccbd85cfc4a58a37f03dd7aaa87e) 2 | ======== 3 | 4 | `gin` is a simple command line utility for live-reloading Go web applications. 5 | Just run `gin` in your app directory and your web app will be served with 6 | `gin` as a proxy. `gin` will automatically recompile your code when it 7 | detects a change. Your app will be restarted the next time it receives an 8 | HTTP request. 9 | 10 | `gin` adheres to the "silence is golden" principle, so it will only complain 11 | if there was a compiler error or if you succesfully compile after an error. 12 | 13 | ## Installation 14 | 15 | Assuming you have a working Go environment and `GOPATH/bin` is in your 16 | `PATH`, `gin` is a breeze to install: 17 | 18 | ```shell 19 | go get github.com/codegangsta/gin 20 | ``` 21 | 22 | Then verify that `gin` was installed correctly: 23 | 24 | ```shell 25 | gin -h 26 | ``` 27 | 28 | ## Supporting Gin in Your Web app 29 | `gin` assumes that your web app binds itself to the `PORT` environment 30 | variable so it can properly proxy requests to your app. Web frameworks 31 | like [Martini](http://github.com/codegangsta/martini) do this out of 32 | the box. 33 | 34 | ## Using flags? 35 | When you normally start your server with [flags](https://godoc.org/flag) 36 | if you want to override any of them when running `gin` we suggest you 37 | instead use [github.com/namsral/flag](https://github.com/namsral/flag) 38 | as explained in [this post](http://stackoverflow.com/questions/24873883/organizing-environment-variables-golang/28160665#28160665) 39 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/gin/lib/builder.go: -------------------------------------------------------------------------------- 1 | package gin 2 | 3 | import ( 4 | "fmt" 5 | "os/exec" 6 | "runtime" 7 | "strings" 8 | ) 9 | 10 | type Builder interface { 11 | Build() error 12 | Binary() string 13 | Errors() string 14 | } 15 | 16 | type builder struct { 17 | dir string 18 | binary string 19 | errors string 20 | useGodep bool 21 | } 22 | 23 | func NewBuilder(dir string, bin string, useGodep bool) Builder { 24 | if len(bin) == 0 { 25 | bin = "bin" 26 | } 27 | 28 | // does not work on Windows without the ".exe" extension 29 | if runtime.GOOS == "windows" { 30 | if !strings.HasSuffix(bin, ".exe") { // check if it already has the .exe extension 31 | bin += ".exe" 32 | } 33 | } 34 | 35 | return &builder{dir: dir, binary: bin, useGodep: useGodep} 36 | } 37 | 38 | func (b *builder) Binary() string { 39 | return b.binary 40 | } 41 | 42 | func (b *builder) Errors() string { 43 | return b.errors 44 | } 45 | 46 | func (b *builder) Build() error { 47 | var command *exec.Cmd 48 | if b.useGodep { 49 | command = exec.Command("godep", "go", "build", "-o", b.binary) 50 | } else { 51 | command = exec.Command("go", "build", "-o", b.binary) 52 | } 53 | command.Dir = b.dir 54 | 55 | output, err := command.CombinedOutput() 56 | 57 | if command.ProcessState.Success() { 58 | b.errors = "" 59 | } else { 60 | b.errors = string(output) 61 | } 62 | 63 | if len(b.errors) > 0 { 64 | return fmt.Errorf(b.errors) 65 | } 66 | 67 | return err 68 | } 69 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/gin/lib/config.go: -------------------------------------------------------------------------------- 1 | package gin 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | type Config struct { 10 | Laddr string `json:"laddr"` 11 | Port int `json:"port"` 12 | ProxyTo string `json:"proxy_to"` 13 | } 14 | 15 | func LoadConfig(path string) (*Config, error) { 16 | configFile, err := os.Open(path) 17 | 18 | if err != nil { 19 | return nil, fmt.Errorf("Unable to read configuration file %s", path) 20 | } 21 | 22 | config := new(Config) 23 | 24 | decoder := json.NewDecoder(configFile) 25 | err = decoder.Decode(&config) 26 | if err != nil { 27 | return nil, fmt.Errorf("Unable to parse configuration file %s", path) 28 | } 29 | 30 | return config, nil 31 | } 32 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/gin/lib/proxy.go: -------------------------------------------------------------------------------- 1 | package gin 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net" 7 | "net/http" 8 | "net/http/httputil" 9 | "net/url" 10 | "strings" 11 | ) 12 | 13 | type Proxy struct { 14 | listener net.Listener 15 | proxy *httputil.ReverseProxy 16 | builder Builder 17 | runner Runner 18 | to *url.URL 19 | } 20 | 21 | func NewProxy(builder Builder, runner Runner) *Proxy { 22 | return &Proxy{ 23 | builder: builder, 24 | runner: runner, 25 | } 26 | } 27 | 28 | func (p *Proxy) Run(config *Config) error { 29 | 30 | // create our reverse proxy 31 | url, err := url.Parse(config.ProxyTo) 32 | if err != nil { 33 | return err 34 | } 35 | p.proxy = httputil.NewSingleHostReverseProxy(url) 36 | p.to = url 37 | 38 | p.listener, err = net.Listen("tcp", fmt.Sprintf("%s:%d", config.Laddr, config.Port)) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | go http.Serve(p.listener, http.HandlerFunc(p.defaultHandler)) 44 | return nil 45 | } 46 | 47 | func (p *Proxy) Close() error { 48 | return p.listener.Close() 49 | } 50 | 51 | func (p *Proxy) defaultHandler(res http.ResponseWriter, req *http.Request) { 52 | errors := p.builder.Errors() 53 | if len(errors) > 0 { 54 | res.Write([]byte(errors)) 55 | } else { 56 | p.runner.Run() 57 | if strings.ToLower(req.Header.Get("Upgrade")) == "websocket" || strings.ToLower(req.Header.Get("Accept")) == "text/event-stream" { 58 | proxyWebsocket(res, req, p.to) 59 | } else { 60 | p.proxy.ServeHTTP(res, req) 61 | } 62 | } 63 | } 64 | 65 | func proxyWebsocket(w http.ResponseWriter, r *http.Request, host *url.URL) { 66 | d, err := net.Dial("tcp", host.Host) 67 | if err != nil { 68 | http.Error(w, "Error contacting backend server.", 500) 69 | fmt.Errorf("Error dialing websocket backend %s: %v", host, err) 70 | return 71 | } 72 | hj, ok := w.(http.Hijacker) 73 | if !ok { 74 | http.Error(w, "Not a hijacker?", 500) 75 | return 76 | } 77 | nc, _, err := hj.Hijack() 78 | if err != nil { 79 | fmt.Errorf("Hijack error: %v", err) 80 | return 81 | } 82 | defer nc.Close() 83 | defer d.Close() 84 | 85 | err = r.Write(d) 86 | if err != nil { 87 | fmt.Errorf("Error copying request to target: %v", err) 88 | return 89 | } 90 | 91 | errc := make(chan error, 2) 92 | cp := func(dst io.Writer, src io.Reader) { 93 | _, err := io.Copy(dst, src) 94 | errc <- err 95 | } 96 | go cp(d, nc) 97 | go cp(nc, d) 98 | <-errc 99 | } 100 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/gin/lib/runner.go: -------------------------------------------------------------------------------- 1 | package gin 2 | 3 | import ( 4 | "io" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | "os/exec" 9 | "runtime" 10 | "time" 11 | ) 12 | 13 | type Runner interface { 14 | Run() (*exec.Cmd, error) 15 | Info() (os.FileInfo, error) 16 | SetWriter(io.Writer) 17 | Kill() error 18 | } 19 | 20 | type runner struct { 21 | bin string 22 | args []string 23 | writer io.Writer 24 | command *exec.Cmd 25 | starttime time.Time 26 | } 27 | 28 | func NewRunner(bin string, args ...string) Runner { 29 | return &runner{ 30 | bin: bin, 31 | args: args, 32 | writer: ioutil.Discard, 33 | starttime: time.Now(), 34 | } 35 | } 36 | 37 | func (r *runner) Run() (*exec.Cmd, error) { 38 | if r.needsRefresh() { 39 | r.Kill() 40 | } 41 | 42 | if r.command == nil || r.Exited() { 43 | err := r.runBin() 44 | time.Sleep(250 * time.Millisecond) 45 | return r.command, err 46 | } else { 47 | return r.command, nil 48 | } 49 | 50 | } 51 | 52 | func (r *runner) Info() (os.FileInfo, error) { 53 | return os.Stat(r.bin) 54 | } 55 | 56 | func (r *runner) SetWriter(writer io.Writer) { 57 | r.writer = writer 58 | } 59 | 60 | func (r *runner) Kill() error { 61 | if r.command != nil && r.command.Process != nil { 62 | done := make(chan error) 63 | go func() { 64 | r.command.Wait() 65 | close(done) 66 | }() 67 | 68 | //Trying a "soft" kill first 69 | if runtime.GOOS == "windows" { 70 | if err := r.command.Process.Kill(); err != nil { 71 | return err 72 | } 73 | } else if err := r.command.Process.Signal(os.Interrupt); err != nil { 74 | return err 75 | } 76 | 77 | //Wait for our process to die before we return or hard kill after 3 sec 78 | select { 79 | case <-time.After(3 * time.Second): 80 | if err := r.command.Process.Kill(); err != nil { 81 | log.Println("failed to kill: ", err) 82 | } 83 | case <-done: 84 | } 85 | r.command = nil 86 | } 87 | 88 | return nil 89 | } 90 | 91 | func (r *runner) Exited() bool { 92 | return r.command != nil && r.command.ProcessState != nil && r.command.ProcessState.Exited() 93 | } 94 | 95 | func (r *runner) runBin() error { 96 | r.command = exec.Command(r.bin, r.args...) 97 | stdout, err := r.command.StdoutPipe() 98 | if err != nil { 99 | return err 100 | } 101 | stderr, err := r.command.StderrPipe() 102 | if err != nil { 103 | return err 104 | } 105 | 106 | err = r.command.Start() 107 | if err != nil { 108 | return err 109 | } 110 | 111 | r.starttime = time.Now() 112 | 113 | go io.Copy(r.writer, stdout) 114 | go io.Copy(r.writer, stderr) 115 | go r.command.Wait() 116 | 117 | return nil 118 | } 119 | 120 | func (r *runner) needsRefresh() bool { 121 | info, err := r.Info() 122 | if err != nil { 123 | return false 124 | } else { 125 | return info.ModTime().After(r.starttime) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/gin/wercker.yml: -------------------------------------------------------------------------------- 1 | box: wercker/golang@1.1.1 2 | -------------------------------------------------------------------------------- /wercker.yml: -------------------------------------------------------------------------------- 1 | box: google/golang 2 | 3 | # Build definition 4 | build: 5 | # The steps that will be executed on build 6 | steps: 7 | 8 | # Sets the go workspace and places you package 9 | # at the right place in the workspace tree 10 | - setup-go-workspace 11 | 12 | # golint step! 13 | - wercker/golint 14 | 15 | # goget 16 | - script: 17 | name: go get 18 | code: | 19 | cd $WERCKER_SOURCE_DIR 20 | go get -d ./... 21 | 22 | # Build the project 23 | - script: 24 | name: go build 25 | code: | 26 | go build ./... 27 | 28 | # Test the project 29 | - script: 30 | name: go test 31 | code: | 32 | go test ./... 33 | 34 | --------------------------------------------------------------------------------