Browse Source

Initial import

Wirecog, LLC 2 years ago
commit
6f140f36ae
4 changed files with 270 additions and 0 deletions
  1. 6
    0
      .gitignore
  2. 29
    0
      LICENSE
  3. 57
    0
      Readme.md
  4. 178
    0
      kick.go

+ 6
- 0
.gitignore View File

@@ -0,0 +1,6 @@
1
+*~
2
+*.swp
3
+*.swo
4
+*.tmp
5
+*.out
6
+.DS_Store

+ 29
- 0
LICENSE View File

@@ -0,0 +1,29 @@
1
+The Isomorphic Go Project
2
+Copyright (c) Wirecog, LLC. All rights reserved.
3
+
4
+Redistribution and use in source and binary forms, with or without
5
+modification, are permitted provided that the following conditions are
6
+met:
7
+
8
+   * Redistributions of source code must retain the above copyright
9
+notice, this list of conditions and the following disclaimer.
10
+   * Redistributions in binary form must reproduce the above
11
+copyright notice, this list of conditions and the following disclaimer
12
+in the documentation and/or other materials provided with the
13
+distribution.
14
+   * Neither the name of Wirecog, LLC. nor the names of its contributors
15
+may be used to endorse or promote products derived from
16
+this software without specific prior written permission.
17
+
18
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
+

+ 57
- 0
Readme.md View File

@@ -0,0 +1,57 @@
1
+<p align="center"><a href="http://isomorphicgo.org" target="_blank"><img src="https://github.com/isomorphicgo/isogoapp/blob/master/static/images/isomorphic_go_logo.png"></a></p>
2
+
3
+# Kick
4
+
5
+A lightweight mechanism to provide an *instant kickstart* to a Go web server instance, upon the modification of a Go source file within a particular project directory (including any subdirectories).
6
+
7
+An *instant kickstart* consists of a recompilation of the Go code and a restart of the web server instance.
8
+
9
+Kick comes with the ability to take both the `go` and `gopherjs` commands into consideration when performing the *instant kickstart*.
10
+
11
+## Installation
12
+
13
+Before installing Kick, it is recommended, to install the barebones [isogoapp](https://github.com/isomorphicgo/isogoapp) first – since it will provide you an example of how to use kick.
14
+
15
+### Get Kick
16
+`go get -u github.com/isomorphicgo/kick`
17
+
18
+## Usage
19
+
20
+### Getting Help
21
+
22
+Issue the `help` flag to get help on using the kick command:
23
+
24
+`kick --help`
25
+
26
+### Running Kick
27
+
28
+Example (using GopherJS):
29
+
30
+`kick --appPath=$ISOGO_APP_ROOT --mainSourceFile=isogoapp.go --gopherjsAppPath=$ISOGO_APP_ROOT/client`
31
+
32
+The `appPath` flag specifies the project directory where the Go application resides.
33
+
34
+The `mainSourceFile` flag specifies the Go source file that implements the main function.
35
+
36
+The `gopherjsAppPath` flag specifies the directory to the GopherJS client-side application. This flag is optional.
37
+
38
+If your Go project is not using GopherJS, you can feel free to omit the `gopherjsAppPath` flag.
39
+
40
+### Verify That Kick Is Functioning
41
+
42
+Assuming that you've installed the [isogoapp](https://github.com/isomorphicgo/isogo), and you have issued the kick command to run the web server instance:
43
+
44
+Access the [test page](http://localhost:8080) for the `isogoapp` using your web browser.
45
+
46
+Open up the `client.go` source file in the `$ISOGO_APP_ROOT/client` directory. Change the message that is passed to the `SetInnerHTML()` function call.
47
+
48
+Refresh your web browser. You should see your change reflected.
49
+
50
+In the command line prompt where you issued the kick command, take note of the "Recompiling and Restarting" message. This is kick's way of telling you that an *instant kickstart* was performed.
51
+
52
+
53
+## The Isomorphic Go Project
54
+More information on the benefits of Isomorphic Go applications can be found at the [Isomorphic Go Website](http://isomorphicgo.org).
55
+
56
+## License
57
+Kick is licensed under the BSD License. Read the [LICENSE](https://github.com/isomorphicgo/kick/blob/master/LICENSE) file for more information.

+ 178
- 0
kick.go View File

@@ -0,0 +1,178 @@
1
+package main
2
+
3
+import (
4
+	"flag"
5
+	"fmt"
6
+	"log"
7
+	"os"
8
+	"os/exec"
9
+	"os/signal"
10
+	"path/filepath"
11
+	"syscall"
12
+
13
+	"github.com/fsnotify/fsnotify"
14
+)
15
+
16
+var appPath string
17
+var mainSourceFile string
18
+var gopherjsAppPath string
19
+
20
+func start() *exec.Cmd {
21
+
22
+	if gopherjsAppPath != "" {
23
+
24
+		cwd, err := os.Getwd()
25
+		if err != nil {
26
+			log.Fatal("Encountered an error while attempting to get the cwd: ", err)
27
+		} else {
28
+			os.Chdir(gopherjsAppPath)
29
+			gjsCommand := exec.Command("gopherjs", "build")
30
+			gjsCommand.Start()
31
+			os.Chdir(cwd)
32
+		}
33
+	}
34
+
35
+	cmd := exec.Command("go", "run", appPath+"/"+mainSourceFile)
36
+	cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
37
+	cmd.Start()
38
+	return cmd
39
+
40
+}
41
+
42
+func stop(cmd *exec.Cmd) {
43
+	pgid, err := syscall.Getpgid(cmd.Process.Pid)
44
+	if err == nil {
45
+		syscall.Kill(-pgid, 15)
46
+	}
47
+}
48
+
49
+func restart(cmd *exec.Cmd) *exec.Cmd {
50
+	var newCommand *exec.Cmd
51
+	stop(cmd)
52
+	newCommand = start()
53
+	return newCommand
54
+
55
+}
56
+
57
+func initializeWatcher(shouldRestart chan bool, dirList []string) {
58
+
59
+	watcher, err := fsnotify.NewWatcher()
60
+	if err != nil {
61
+		log.Fatal(err)
62
+	}
63
+	defer watcher.Close()
64
+
65
+	done := make(chan bool)
66
+	go func() {
67
+		for {
68
+			select {
69
+			case event := <-watcher.Events:
70
+
71
+				if event.Op == fsnotify.Write || event.Op == fsnotify.Rename {
72
+					if filepath.Ext(event.Name) == ".go" {
73
+						shouldRestart <- true
74
+					}
75
+				}
76
+
77
+			case err := <-watcher.Errors:
78
+				if err != nil {
79
+					log.Println("error:", err)
80
+				}
81
+			}
82
+		}
83
+	}()
84
+
85
+	err = watcher.Add(appPath)
86
+	if err != nil {
87
+		log.Fatal(err)
88
+	}
89
+	// watch subdirectories also
90
+	for _, element := range dirList {
91
+		watcher.Add(element)
92
+	}
93
+
94
+	<-done
95
+
96
+}
97
+
98
+func pathExists(path string) (bool, error) {
99
+	_, err := os.Stat(path)
100
+	if err == nil {
101
+		return true, nil
102
+	}
103
+	if os.IsNotExist(err) {
104
+		return false, nil
105
+	}
106
+	return true, err
107
+}
108
+
109
+func main() {
110
+
111
+	flag.StringVar(&appPath, "appPath", "", "The path to your Go project")
112
+	flag.StringVar(&mainSourceFile, "mainSourceFile", "", "The Go source file with the main func()")
113
+	flag.StringVar(&gopherjsAppPath, "gopherjsAppPath", "", "The path to your GopherJS project (optional)")
114
+	flag.Parse()
115
+
116
+	// Exit if no appPath is supplied
117
+	if appPath == "" {
118
+		fmt.Println("You must supply the appPath parameter")
119
+		os.Exit(1)
120
+	}
121
+
122
+	if appPathExists, appPathErr := pathExists(appPath); appPathExists != true || appPathErr != nil {
123
+		fmt.Println("The path you specified to your Go application project does not exist.")
124
+		os.Exit(1)
125
+	}
126
+
127
+	if mainSourceFile == "" {
128
+		fmt.Println("You must supply the mainSourceFile parameter")
129
+		os.Exit(1)
130
+	}
131
+
132
+	if sourceFileExists, sourceFilePathErr := pathExists(appPath + "/" + mainSourceFile); sourceFileExists != true || sourceFilePathErr != nil {
133
+		fmt.Println("The path to the main source file you provided does not exist.")
134
+		os.Exit(1)
135
+	}
136
+
137
+	if gopherjsAppPath != "" {
138
+		if gopherjsFileExists, gopherjsFileErr := pathExists(gopherjsAppPath); gopherjsFileExists != true || gopherjsFileErr != nil {
139
+			fmt.Println("The path you specified to the GopherJS application project does not exist.")
140
+			os.Exit(1)
141
+		}
142
+	}
143
+
144
+	dirList := []string{}
145
+	filepath.Walk(appPath, func(path string, f os.FileInfo, err error) error {
146
+
147
+		if f.IsDir() == true {
148
+			dirList = append(dirList, path)
149
+		}
150
+		return nil
151
+	})
152
+
153
+	shouldRestart := make(chan bool, 1)
154
+
155
+	go initializeWatcher(shouldRestart, dirList)
156
+
157
+	interrupt := make(chan os.Signal, 1)
158
+	signal.Notify(interrupt, os.Interrupt)
159
+
160
+	cmd := start()
161
+
162
+	for {
163
+
164
+		select {
165
+
166
+		case <-interrupt:
167
+			stop(cmd)
168
+			os.Exit(0)
169
+
170
+		case <-shouldRestart:
171
+			fmt.Println("Recompiling and Restarting")
172
+			cmd = restart(cmd)
173
+
174
+		}
175
+
176
+	}
177
+
178
+}