More refactoring into nice little directories. data/types.go is because go is a wimp that can't handle cycles.
parent
8ae845d014
commit
f2b9edeb05
@ -0,0 +1,220 @@ |
|||||||
|
package builder |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"os/exec" |
||||||
|
"log" |
||||||
|
"io" |
||||||
|
"os" |
||||||
|
"time" |
||||||
|
"io/fs" |
||||||
|
"path/filepath" |
||||||
|
"errors" |
||||||
|
"github.com/fsnotify/fsnotify" |
||||||
|
"bufio" |
||||||
|
"regexp" |
||||||
|
"strconv" |
||||||
|
"lcthw.dev/go/ttarpit/config" |
||||||
|
"lcthw.dev/go/ttarpit/data" |
||||||
|
) |
||||||
|
|
||||||
|
type ErrorHandler func (data.ErrInfo)
|
||||||
|
|
||||||
|
type Builder struct { |
||||||
|
settings config.Config
|
||||||
|
OnError ErrorHandler |
||||||
|
|
||||||
|
BuildRunning bool |
||||||
|
} |
||||||
|
|
||||||
|
func New(settings config.Config, handler ErrorHandler) (*Builder) { |
||||||
|
builder := new(Builder) |
||||||
|
|
||||||
|
builder.settings = settings |
||||||
|
builder.OnError = handler |
||||||
|
|
||||||
|
return builder |
||||||
|
} |
||||||
|
|
||||||
|
func (build *Builder) ParseErrInfo(line string, reg *regexp.Regexp) (data.ErrInfo, bool) { |
||||||
|
var info data.ErrInfo |
||||||
|
|
||||||
|
matches := reg.FindStringSubmatch(line) |
||||||
|
if matches == nil { |
||||||
|
return info, false |
||||||
|
} |
||||||
|
|
||||||
|
et_index := reg.SubexpIndex("ErrType") |
||||||
|
if et_index != -1 { |
||||||
|
info.ErrType = matches[et_index] |
||||||
|
} |
||||||
|
|
||||||
|
// BUG: use reflect
|
||||||
|
info.File = matches[reg.SubexpIndex("File")] |
||||||
|
|
||||||
|
info.Line, _ = strconv.Atoi(matches[reg.SubexpIndex("Line")]) |
||||||
|
|
||||||
|
info.Col, _ = strconv.Atoi(matches[reg.SubexpIndex("Col")]) |
||||||
|
|
||||||
|
info.Message = matches[reg.SubexpIndex("Message")] |
||||||
|
|
||||||
|
return info, true |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
func (build *Builder) LaunchLogger(in io.Reader, out io.Writer, err error) { |
||||||
|
if err != nil { log.Fatal(err) } |
||||||
|
|
||||||
|
go func() { |
||||||
|
scan := bufio.NewScanner(in) |
||||||
|
|
||||||
|
for scan.Scan() { |
||||||
|
for _, reg := range build.settings.TriggerRegex { |
||||||
|
line := scan.Text() |
||||||
|
|
||||||
|
errinfo, ok := build.ParseErrInfo(line, reg) |
||||||
|
|
||||||
|
if ok { |
||||||
|
build.OnError(errinfo) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}() |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
func (build *Builder) LaunchProcess(proc *config.Process) { |
||||||
|
proc.ExecCmd = exec.Command(proc.Command, proc.Args...) |
||||||
|
if errors.Is(proc.ExecCmd.Err, exec.ErrDot) { |
||||||
|
proc.ExecCmd.Err = nil |
||||||
|
} |
||||||
|
|
||||||
|
fmt.Println("STARTING", proc.Command) |
||||||
|
|
||||||
|
stderr, err := proc.ExecCmd.StderrPipe(); |
||||||
|
build.LaunchLogger(stderr, os.Stdout, err) |
||||||
|
|
||||||
|
stdout, err := proc.ExecCmd.StdoutPipe(); |
||||||
|
build.LaunchLogger(stdout, os.Stdout, err) |
||||||
|
|
||||||
|
err = proc.ExecCmd.Start() |
||||||
|
if err != nil { |
||||||
|
log.Fatalf("FAIL %s %s err=%v", proc.Command, proc.Args, err) |
||||||
|
} |
||||||
|
|
||||||
|
fmt.Println("WAITING for", proc.Command) |
||||||
|
|
||||||
|
proc.ExecCmd.Wait() |
||||||
|
fmt.Println("PROCESS", proc.Command, "EXITED") |
||||||
|
} |
||||||
|
|
||||||
|
func (build *Builder) MatchesPath(fp string) bool { |
||||||
|
fp = filepath.ToSlash(fp) |
||||||
|
|
||||||
|
for _, reg := range build.settings.IncludeRegex { |
||||||
|
matches := reg.MatchString(fp) |
||||||
|
|
||||||
|
if matches { |
||||||
|
return true |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
func (build *Builder) BeginBuild() bool { |
||||||
|
if !build.BuildRunning { |
||||||
|
build.BuildRunning = true |
||||||
|
return true |
||||||
|
} else { |
||||||
|
return false |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (build *Builder) EndBuild() { |
||||||
|
build.BuildRunning = false |
||||||
|
} |
||||||
|
|
||||||
|
func (build *Builder) RunBuild() { |
||||||
|
if build.BeginBuild() { |
||||||
|
defer build.EndBuild()
|
||||||
|
|
||||||
|
fmt.Println("CONFIG:", build.settings.ConfigPath) |
||||||
|
fmt.Println("COMMANDs:", build.settings.Processes) |
||||||
|
|
||||||
|
for name, proc := range build.settings.Processes { |
||||||
|
fmt.Println("PROCESS:", name) |
||||||
|
|
||||||
|
build.LaunchProcess(&proc) |
||||||
|
} |
||||||
|
|
||||||
|
time.Sleep(1000 * time.Millisecond) |
||||||
|
} else { |
||||||
|
fmt.Println("!!!! BUILD SKIP, already running") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (build *Builder) AddWatchDir(watcher *fsnotify.Watcher, name string) error { |
||||||
|
return filepath.WalkDir(name, |
||||||
|
func(path string, d fs.DirEntry, err error) error { |
||||||
|
if err != nil { |
||||||
|
log.Printf("WATCH ERROR! walking=%s path=%s: err=%v", name, path, err) |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if d.IsDir() { |
||||||
|
log.Println("WATCHING: ", path) |
||||||
|
err = watcher.Add(path) |
||||||
|
if err != nil { |
||||||
|
log.Printf("failed to watch %s", path) |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
func (build *Builder) WatchDir() { |
||||||
|
watcher, err := fsnotify.NewWatcher() |
||||||
|
if err != nil { |
||||||
|
log.Fatal("Failed to start fsnotify", err) |
||||||
|
} |
||||||
|
defer watcher.Close() |
||||||
|
|
||||||
|
go func() { |
||||||
|
for { |
||||||
|
select { |
||||||
|
case event, ok := <-watcher.Events: |
||||||
|
if !ok { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
if event.Has(fsnotify.Create) { |
||||||
|
log.Println("---> CREATE", event.Name) |
||||||
|
build.AddWatchDir(watcher, event.Name) |
||||||
|
} else if event.Has(fsnotify.Write) { |
||||||
|
// check if match then do thing
|
||||||
|
if build.MatchesPath(event.Name) { |
||||||
|
go build.RunBuild() |
||||||
|
} |
||||||
|
} else { |
||||||
|
log.Println("event:", event) |
||||||
|
} |
||||||
|
case err, ok := <-watcher.Errors: |
||||||
|
if !ok { |
||||||
|
return |
||||||
|
} |
||||||
|
log.Println("error: ", err) |
||||||
|
} |
||||||
|
} |
||||||
|
}() |
||||||
|
|
||||||
|
err = build.AddWatchDir(watcher, ".") |
||||||
|
|
||||||
|
if err != nil { |
||||||
|
log.Fatal(err, "Failed to watch .") |
||||||
|
} |
||||||
|
|
||||||
|
<-make(chan struct{}) |
||||||
|
} |
||||||
@ -0,0 +1,10 @@ |
|||||||
|
package data |
||||||
|
|
||||||
|
type ErrInfo struct { |
||||||
|
File string |
||||||
|
Line int |
||||||
|
Col int |
||||||
|
ErrType string |
||||||
|
Message string |
||||||
|
} |
||||||
|
|
||||||
@ -0,0 +1,40 @@ |
|||||||
|
package game |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"lcthw.dev/go/ttarpit/config" |
||||||
|
"lcthw.dev/go/ttarpit/data" |
||||||
|
) |
||||||
|
|
||||||
|
type Game struct { |
||||||
|
settings config.Config
|
||||||
|
|
||||||
|
HP int |
||||||
|
Errors int |
||||||
|
} |
||||||
|
|
||||||
|
func New(settings config.Config) *Game { |
||||||
|
game := new(Game) |
||||||
|
|
||||||
|
game.settings = settings |
||||||
|
game.HP = settings.StartingHP |
||||||
|
|
||||||
|
return game |
||||||
|
} |
||||||
|
|
||||||
|
func (game *Game) TakeHit(errinfo data.ErrInfo) { |
||||||
|
fmt.Println("!!!!!!!!!!!!!!!!!", errinfo) |
||||||
|
game.Errors++ |
||||||
|
game.HP-- |
||||||
|
|
||||||
|
fmt.Println("============== PROCESS EXIT") |
||||||
|
fmt.Printf("==== HP: %d Errors: %d =====\n", |
||||||
|
game.HP, game.Errors) |
||||||
|
|
||||||
|
if game.HP <= 0 { |
||||||
|
fmt.Println("!!!!!! YOU DIED !!!!!!!") |
||||||
|
game.HP = game.settings.StartingHP |
||||||
|
} |
||||||
|
|
||||||
|
fmt.Println("===========================") |
||||||
|
} |
||||||
@ -1,249 +1,20 @@ |
|||||||
package main |
package main |
||||||
|
|
||||||
import ( |
import ( |
||||||
"fmt" |
"lcthw.dev/go/ttarpit/builder" |
||||||
"os/exec" |
|
||||||
"log" |
|
||||||
"io" |
|
||||||
"os" |
|
||||||
"time" |
|
||||||
"io/fs" |
|
||||||
"path/filepath" |
|
||||||
"errors" |
|
||||||
"lcthw.dev/go/ttarpit/config" |
"lcthw.dev/go/ttarpit/config" |
||||||
"github.com/fsnotify/fsnotify" |
"lcthw.dev/go/ttarpit/game" |
||||||
"bufio" |
"lcthw.dev/go/ttarpit/data" |
||||||
"regexp" |
|
||||||
"strconv" |
|
||||||
) |
) |
||||||
|
|
||||||
type ErrInfo struct { |
|
||||||
File string |
|
||||||
Line int |
|
||||||
Col int |
|
||||||
ErrType string |
|
||||||
Message string |
|
||||||
} |
|
||||||
|
|
||||||
type Game struct { |
|
||||||
settings config.Config
|
|
||||||
|
|
||||||
BuildRunning bool |
|
||||||
HP int |
|
||||||
Errors int |
|
||||||
} |
|
||||||
|
|
||||||
func MakeGame(settings config.Config) *Game { |
|
||||||
game := new(Game) |
|
||||||
|
|
||||||
game.settings = settings |
|
||||||
game.HP = settings.StartingHP |
|
||||||
|
|
||||||
return game |
|
||||||
} |
|
||||||
|
|
||||||
func (game *Game) ParseErrInfo(line string, reg *regexp.Regexp) (ErrInfo, bool) { |
|
||||||
var info ErrInfo |
|
||||||
|
|
||||||
matches := reg.FindStringSubmatch(line) |
|
||||||
if matches == nil { |
|
||||||
return info, false |
|
||||||
} |
|
||||||
|
|
||||||
et_index := reg.SubexpIndex("ErrType") |
|
||||||
if et_index != -1 { |
|
||||||
info.ErrType = matches[et_index] |
|
||||||
} |
|
||||||
|
|
||||||
// BUG: use reflect
|
|
||||||
info.File = matches[reg.SubexpIndex("File")] |
|
||||||
|
|
||||||
info.Line, _ = strconv.Atoi(matches[reg.SubexpIndex("Line")]) |
|
||||||
|
|
||||||
info.Col, _ = strconv.Atoi(matches[reg.SubexpIndex("Col")]) |
|
||||||
|
|
||||||
info.Message = matches[reg.SubexpIndex("Message")] |
|
||||||
|
|
||||||
return info, true |
|
||||||
} |
|
||||||
|
|
||||||
func (game *Game) TakeHit(errinfo ErrInfo) { |
|
||||||
fmt.Println("!!!!!!!!!!!!!!!!!", errinfo) |
|
||||||
game.Errors++ |
|
||||||
game.HP-- |
|
||||||
} |
|
||||||
|
|
||||||
func (game *Game) LaunchLogger(in io.Reader, out io.Writer, err error) { |
|
||||||
if err != nil { log.Fatal(err) } |
|
||||||
|
|
||||||
go func() { |
|
||||||
scan := bufio.NewScanner(in) |
|
||||||
|
|
||||||
for scan.Scan() { |
|
||||||
for _, reg := range game.settings.TriggerRegex { |
|
||||||
line := scan.Text() |
|
||||||
|
|
||||||
errinfo, ok := game.ParseErrInfo(line, reg) |
|
||||||
|
|
||||||
if ok { |
|
||||||
game.TakeHit(errinfo) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
}() |
|
||||||
} |
|
||||||
|
|
||||||
func (game *Game) LaunchProcess(proc *config.Process) { |
|
||||||
proc.ExecCmd = exec.Command(proc.Command, proc.Args...) |
|
||||||
if errors.Is(proc.ExecCmd.Err, exec.ErrDot) { |
|
||||||
proc.ExecCmd.Err = nil |
|
||||||
} |
|
||||||
|
|
||||||
fmt.Println("STARTING", proc.Command) |
|
||||||
|
|
||||||
stderr, err := proc.ExecCmd.StderrPipe(); |
|
||||||
game.LaunchLogger(stderr, os.Stdout, err) |
|
||||||
|
|
||||||
stdout, err := proc.ExecCmd.StdoutPipe(); |
|
||||||
game.LaunchLogger(stdout, os.Stdout, err) |
|
||||||
|
|
||||||
err = proc.ExecCmd.Start() |
|
||||||
if err != nil { |
|
||||||
log.Fatalf("FAIL %s %s err=%v", proc.Command, proc.Args, err) |
|
||||||
} |
|
||||||
|
|
||||||
fmt.Println("WAITING for", proc.Command) |
|
||||||
|
|
||||||
proc.ExecCmd.Wait() |
|
||||||
fmt.Println("PROCESS", proc.Command, "EXITED") |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
func (game *Game) MatchesPath(fp string) bool { |
|
||||||
fp = filepath.ToSlash(fp) |
|
||||||
|
|
||||||
for _, reg := range game.settings.IncludeRegex { |
|
||||||
matches := reg.MatchString(fp) |
|
||||||
|
|
||||||
if matches { |
|
||||||
return true |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return false |
|
||||||
} |
|
||||||
|
|
||||||
func (game *Game) BeginBuild() bool { |
|
||||||
if !game.BuildRunning { |
|
||||||
game.BuildRunning = true |
|
||||||
return true |
|
||||||
} else { |
|
||||||
return false |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (game *Game) EndBuild() { |
|
||||||
game.BuildRunning = false |
|
||||||
} |
|
||||||
|
|
||||||
func (game *Game) RunBuild() { |
|
||||||
if game.BeginBuild() { |
|
||||||
defer game.EndBuild()
|
|
||||||
|
|
||||||
fmt.Println("CONFIG:", game.settings.ConfigPath) |
|
||||||
fmt.Println("COMMANDs:", game.settings.Processes) |
|
||||||
|
|
||||||
for name, proc := range game.settings.Processes { |
|
||||||
fmt.Println("PROCESS:", name) |
|
||||||
|
|
||||||
game.LaunchProcess(&proc) |
|
||||||
fmt.Println("============== PROCESS EXIT") |
|
||||||
fmt.Printf("==== HP: %d Errors: %d =====\n", |
|
||||||
game.HP, game.Errors) |
|
||||||
|
|
||||||
if game.HP <= 0 { |
|
||||||
fmt.Println("!!!!!! YOU DIED !!!!!!!") |
|
||||||
game.HP = game.settings.StartingHP |
|
||||||
} |
|
||||||
|
|
||||||
fmt.Println("===========================") |
|
||||||
} |
|
||||||
|
|
||||||
time.Sleep(1000 * time.Millisecond) |
|
||||||
} else { |
|
||||||
fmt.Println("!!!! BUILD SKIP, already running") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (game *Game) AddWatchDir(watcher *fsnotify.Watcher, name string) error { |
|
||||||
return filepath.WalkDir(name, |
|
||||||
func(path string, d fs.DirEntry, err error) error { |
|
||||||
if err != nil { |
|
||||||
log.Printf("WATCH ERROR! walking=%s path=%s: err=%v", name, path, err) |
|
||||||
return err |
|
||||||
} |
|
||||||
|
|
||||||
if d.IsDir() { |
|
||||||
log.Println("WATCHING: ", path) |
|
||||||
err = watcher.Add(path) |
|
||||||
if err != nil { |
|
||||||
log.Printf("failed to watch %s", path) |
|
||||||
return err |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return nil |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
func (game *Game) WatchDir() { |
|
||||||
watcher, err := fsnotify.NewWatcher() |
|
||||||
if err != nil { |
|
||||||
log.Fatal("Failed to start fsnotify", err) |
|
||||||
} |
|
||||||
defer watcher.Close() |
|
||||||
|
|
||||||
go func() { |
|
||||||
for { |
|
||||||
select { |
|
||||||
case event, ok := <-watcher.Events: |
|
||||||
if !ok { |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
if event.Has(fsnotify.Create) { |
|
||||||
log.Println("---> CREATE", event.Name) |
|
||||||
game.AddWatchDir(watcher, event.Name) |
|
||||||
} else if event.Has(fsnotify.Write) { |
|
||||||
// check if match then do thing
|
|
||||||
if game.MatchesPath(event.Name) { |
|
||||||
go game.RunBuild() |
|
||||||
} |
|
||||||
} else { |
|
||||||
log.Println("event:", event) |
|
||||||
} |
|
||||||
case err, ok := <-watcher.Errors: |
|
||||||
if !ok { |
|
||||||
return |
|
||||||
} |
|
||||||
log.Println("error: ", err) |
|
||||||
} |
|
||||||
} |
|
||||||
}() |
|
||||||
|
|
||||||
err = game.AddWatchDir(watcher, ".") |
|
||||||
|
|
||||||
if err != nil { |
|
||||||
log.Fatal(err, "Failed to watch .") |
|
||||||
} |
|
||||||
|
|
||||||
<-make(chan struct{}) |
|
||||||
} |
|
||||||
|
|
||||||
func main() { |
func main() { |
||||||
settings := config.Load() |
settings := config.Load() |
||||||
|
|
||||||
game := MakeGame(settings) |
game := game.New(settings) |
||||||
|
|
||||||
|
build := builder.New(settings, func (errinfo data.ErrInfo) { |
||||||
|
game.TakeHit(errinfo) |
||||||
|
}) |
||||||
|
|
||||||
game.WatchDir() |
build.WatchDir() |
||||||
} |
} |
||||||
|
|||||||
Loading…
Reference in new issue