Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ require (
github.com/docker/go-units v0.5.0 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
Expand Down
15 changes: 11 additions & 4 deletions integration/execute_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,15 @@ import (
"path/filepath"
)

type RunConfig struct {
Print func(string)
Start func(*exec.Cmd)
Directory string
Arguments []string
}

// Build and then run a go program.
func BuildThenRun(funcPrint func(string), funcStart func(*exec.Cmd), directory string, args ...string) error {
func BuildThenRun(config RunConfig) error {

// Get the old working directory
workDir, err := os.Getwd()
Expand All @@ -17,12 +24,12 @@ func BuildThenRun(funcPrint func(string), funcStart func(*exec.Cmd), directory s
}

// Change directory to the file
if err := os.Chdir(directory); err != nil {
if err := os.Chdir(config.Directory); err != nil {
return err
}

// Build the program
if err := ExecCmdWithFuncStart(funcPrint, func(c *exec.Cmd) {}, "go", "build", "-o", "program.exe"); err != nil {
if err := ExecCmdWithFuncStart(config.Print, func(c *exec.Cmd) {}, "go", "build", "-o", "program.exe"); err != nil {
return err
}

Expand All @@ -32,7 +39,7 @@ func BuildThenRun(funcPrint func(string), funcStart func(*exec.Cmd), directory s
}

// Execute and return the process
if err := ExecCmdWithFuncStart(funcPrint, funcStart, filepath.Join(directory, "program.exe"), args...); err != nil {
if err := ExecCmdWithFuncStart(config.Print, config.Start, filepath.Join(config.Directory, "program.exe"), config.Arguments...); err != nil {
return err
}

Expand Down
138 changes: 138 additions & 0 deletions integration/watch_directory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package integration

import (
"errors"
"fmt"
"log"
"os"
"path/filepath"
"slices"
"sync"
"time"

"github.com/Liphium/magic/mconfig"
"github.com/fsnotify/fsnotify"
)

// exclusions has to be a list of non-relative paths
func WatchDirectory(dir string, listener func(), exclusions ...string) error {
// Create new watcher.
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}

// Start listening for events.
go func() {
for {
select {
case _, ok := <-watcher.Events:
if !ok {
return
}
listener()
case err, ok := <-watcher.Errors:
if !ok {
log.Println("error not okay")
return
}
log.Println("watch error:", err)
}
}
}()

// Start watching all of the directories recursively
for i, exclusion := range exclusions {
exclusions[i] = filepath.Clean(exclusion)
}
return startWatchingRecursive(watcher, dir, exclusions)
}

// Helper function that calls itself recursively adding all directories to the watcher
func startWatchingRecursive(watcher *fsnotify.Watcher, dir string, cleanedExclusions []string) error {
entries, err := os.ReadDir(dir)
if err != nil {
return fmt.Errorf("couldn't read directory %s: %s", dir, err)
}

if mconfig.VerboseLogging {
log.Printf("Watching %s...", dir)
}

if err := watcher.Add(dir); err != nil {
return fmt.Errorf("couldn't watch directory %s: %s", dir, err)
}

for _, entry := range entries {
entryPath := filepath.Clean(filepath.Join(dir, entry.Name()))

// If it's a directory and not an exclusion, watch it as well
if entry.IsDir() && !slices.ContainsFunc(cleanedExclusions, func(path string) bool {
return filepath.Clean(path) == entryPath
}) {
if err := startWatchingRecursive(watcher, entryPath, cleanedExclusions); err != nil {
return err
}
}
}
return nil
}

type WatchContext[J any, C any] struct {
Print func(string) // Called for prints.
Error func(error) // Called when an error happens.
Start func(currentContext C, lastJob *J, retrievalChannel chan J) error // Gets called to start the process.
Stop func(J) error // Gets called to stop a job.
RetrievalChannel chan J // The channel a new job gets passed through (once ready)
}

// Helper function for handling watching properly. Returns a function that can be called by multiple goroutines. None of the functions in context can be nil.
func HandleWatching[J any, C any](context WatchContext[J, C], startContext C) func(C, string) {
var debounceTimer *time.Timer

// Create a waiting boolean for making sure we're not waiting for rebuilding twice
waitMutex := &sync.Mutex{}
waiting := false

listener := func(ctx C, message string) {
if debounceTimer != nil {
debounceTimer.Stop()
}

// Create new timer for 500ms
debounceTimer = time.AfterFunc(500*time.Millisecond, func() {
waitMutex.Lock()
defer waitMutex.Unlock()
if waiting {
if mconfig.VerboseLogging {
context.Print("Changes detected, but already trying rebuild.")
}
return
}
waiting = true

// Print what the user wants us to say when the change is the one being accepted
context.Print(message)

// Wait for the previous job to be cancellable and cancel it
job, ok := <-context.RetrievalChannel
if !ok {
context.Error(fmt.Errorf("couldn't get previous process"))
return
}
if err := context.Stop(job); err != nil && !errors.Is(err, os.ErrProcessDone) {
context.Error(fmt.Errorf("couldn't kill previous process: %w", err))
return
}

// Start the new job
if err := context.Start(ctx, &job, context.RetrievalChannel); err != nil {
context.Error(err)
}
})
}

// Start the first job
context.Start(startContext, nil, context.RetrievalChannel)
return listener
}
2 changes: 1 addition & 1 deletion mcli/mcli.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ func RunCli() {
}

if err := cmd.Run(context.Background(), os.Args); err != nil {
log.Fatalln("ERROR:", err)
log.Println("ERROR:", err)
}
}
8 changes: 4 additions & 4 deletions mcli/shutdown/shutdown.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ func Hooks() map[string]func(os.Signal) {

// Listen waits for provided OS signals.
// It will wait for any signal if no signals provided.
func Listen(signals ...os.Signal) {
DefaultShutdown.Listen(signals...)
func Listen() {
DefaultShutdown.Listen()
}

// Remove cancels hook by identificator (key).
Expand Down Expand Up @@ -142,9 +142,9 @@ func (s *Shutdown) Hooks() map[string]func(os.Signal) {

// Listen waits for provided OS signals.
// It will wait for any signal if no signals provided.
func (s *Shutdown) Listen(signals ...os.Signal) {
func (s *Shutdown) Listen() {
ch := make(chan os.Signal, 1)
signal.Notify(ch, signals...)
signal.Notify(ch)
sig := <-ch
var wg sync.WaitGroup
for _, fn := range s.Hooks() {
Expand Down
Loading
Loading