From f2b9edeb05b3736eea3cf493add4f659c172a2e2 Mon Sep 17 00:00:00 2001 From: "Zed A. Shaw" Date: Sat, 27 Dec 2025 11:57:08 -0500 Subject: [PATCH] More refactoring into nice little directories. data/types.go is because go is a wimp that can't handle cycles. --- builder/build.go | 220 +++++++++++++++++++++++++++++++++++++++++ data/types.go | 10 ++ game/engine.go | 40 ++++++++ main.go | 247 ++--------------------------------------------- 4 files changed, 279 insertions(+), 238 deletions(-) create mode 100644 builder/build.go create mode 100644 data/types.go create mode 100644 game/engine.go diff --git a/builder/build.go b/builder/build.go new file mode 100644 index 0000000..c00756d --- /dev/null +++ b/builder/build.go @@ -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{}) +} diff --git a/data/types.go b/data/types.go new file mode 100644 index 0000000..261c4a9 --- /dev/null +++ b/data/types.go @@ -0,0 +1,10 @@ +package data + +type ErrInfo struct { + File string + Line int + Col int + ErrType string + Message string +} + diff --git a/game/engine.go b/game/engine.go new file mode 100644 index 0000000..ed74de0 --- /dev/null +++ b/game/engine.go @@ -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("===========================") +} diff --git a/main.go b/main.go index 9c773d3..19f8b7a 100644 --- a/main.go +++ b/main.go @@ -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() - - 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() { 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() }