forked from smarty-archives/scantest
/
main.go
197 lines (174 loc) · 4.56 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
package main
import (
"bytes"
"flag"
"fmt"
"io"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"time"
)
func main() {
command := flag.String("command", deriveDefaultCommand(), "The command (with arguments) to run when a .go file is saved.")
flag.Parse()
working, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
scanner := &Scanner{working: working}
runner := &Runner{working: working, command: *command}
for {
if scanner.Scan() {
runner.Run()
}
}
}
// deriveDefaultCommand determines what to present as the default value of the
// command flag, in case the user does not provide one. It first looks for a
// Makefile in the current directory. If that doesn't exist it looks for the
// Makefile provided by this project, which serves as a working generic example
// that should fit a variety of use cases. If that doesn't exist for whatever
// reason (say, if the scantest binary wasn't built from source on the current
// machine) then it defaults to 'go test'.
func deriveDefaultCommand() string {
var defaultCommand string
if current, err := os.Getwd(); err == nil {
if _, err := os.Stat(filepath.Join(current, "Makefile")); err == nil {
defaultCommand = "make"
}
}
if defaultCommand == "" {
if _, file, _, ok := runtime.Caller(0); ok {
backupMakefile := filepath.Join(filepath.Dir(file), "Makefile")
if _, err := os.Stat(backupMakefile); err == nil {
defaultCommand = backupMakefile
}
}
}
if defaultCommand == "" {
defaultCommand = "go test"
}
return defaultCommand
}
////////////////////////////////////////////////////////////////////////////
type Scanner struct {
state int64
working string
}
func (this *Scanner) Scan() bool {
time.Sleep(time.Millisecond * 250)
newState := this.checksum()
defer func() { this.state = newState }()
write(".")
return newState != this.state
}
func (this *Scanner) checksum() int64 {
var sum int64 = 0
err := filepath.Walk(this.working, func(path string, info os.FileInfo, err error) error {
if info.IsDir() {
sum++
} else if info.Name() == "generated_by_gunit_test.go" {
return nil
} else if strings.HasSuffix(info.Name(), ".go") || info.Name() == "Makefile" {
sum += info.Size() + info.ModTime().Unix()
}
return nil
})
if err != nil {
log.Fatal(err)
}
return sum
}
////////////////////////////////////////////////////////////////////////////
type Runner struct {
command string
working string
}
func (this *Runner) Run() {
message := fmt.Sprintln(" Executing:", this.command)
write(clearScreen)
writeln()
write(strings.Repeat("=", len(message)))
writeln()
write(message)
write(strings.Repeat("=", len(message)))
writeln()
output, success := this.run()
if success {
write(greenColor)
} else {
write(redColor)
}
write(string(output))
writeln()
write(strings.Repeat("-", len(message)))
writeln()
write(resetColor)
}
func writeln() {
write("\n")
}
func write(a ...interface{}) {
_, _ = fmt.Fprint(os.Stdout, a...)
_ = os.Stdout.Sync()
}
func (this *Runner) run() (output []byte, success bool) {
command := exec.Command("bash", "-c", this.command)
command.Dir = this.working
buffer := bytes.NewBufferString("")
writer := io.MultiWriter(buffer, os.Stdout)
command.Stdout = writer
command.Stderr = writer
now := time.Now()
err := command.Run()
fmt.Println(Round(time.Since(now), time.Millisecond))
if err != nil {
_, _ = io.WriteString(writer, err.Error())
}
return buffer.Bytes(), command.ProcessState.Success()
}
// Round rounds a duration to a precision, in a more human-readable way than time.Round.
// GoLang-Nuts thread:
//
// https://groups.google.com/d/msg/golang-nuts/OWHmTBu16nA/RQb4TvXUg1EJ
//
// Wise, a word which here means unhelpful, guidance from Commander Pike:
//
// https://groups.google.com/d/msg/golang-nuts/OWHmTBu16nA/zoGNwDVKIqAJ
//
// Answer satisfying the original asker:
//
// https://groups.google.com/d/msg/golang-nuts/OWHmTBu16nA/wnrz0tNXzngJ
//
// Answer implementation on the Go Playground:
//
// http://play.golang.org/p/QHocTHl8iR
func Round(duration, precision time.Duration) time.Duration {
if precision <= 0 {
return duration
}
negative := duration < 0
if negative {
duration = -duration
}
if m := duration % precision; m+m < precision {
duration = duration - m
} else {
duration = duration + precision - m
}
if negative {
return -duration
}
return duration
}
////////////////////////////////////////////////////////////////////////////
var (
clearScreen = "\033[2J\033[H" // clear the screen and put the cursor at top-left
greenColor = "\033[32m"
redColor = "\033[31m"
resetColor = "\033[0m"
)