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 |
||||
|
||||
import ( |
||||
"fmt" |
||||
"os/exec" |
||||
"log" |
||||
"io" |
||||
"os" |
||||
"time" |
||||
"io/fs" |
||||
"path/filepath" |
||||
"errors" |
||||
"lcthw.dev/go/ttarpit/builder" |
||||
"lcthw.dev/go/ttarpit/config" |
||||
"github.com/fsnotify/fsnotify" |
||||
"bufio" |
||||
"regexp" |
||||
"strconv" |
||||
"lcthw.dev/go/ttarpit/game" |
||||
"lcthw.dev/go/ttarpit/data" |
||||
) |
||||
|
||||
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() |
||||
func main() { |
||||
settings := config.Load() |
||||
|
||||
errinfo, ok := game.ParseErrInfo(line, reg) |
||||
game := game.New(settings) |
||||
|
||||
if ok { |
||||
build := builder.New(settings, func (errinfo data.ErrInfo) { |
||||
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() { |
||||
settings := config.Load() |
||||
|
||||
game := MakeGame(settings) |
||||
|
||||
game.WatchDir() |
||||
build.WatchDir() |
||||
} |
||||
|
||||
Loading…
Reference in new issue