@@ -11,6 +11,7 @@ import (
1111 "os/exec"
1212 "strings"
1313 "sync"
14+ "time"
1415
1516 "github.com/alecthomas/chroma/v2/quick"
1617 "github.com/charmbracelet/lipgloss"
@@ -104,15 +105,14 @@ func (r *Resolver) RunTask(ctx context.Context, name string) error {
104105 return err
105106 }
106107
107- pipeline := executor .NewPipeline (slog .Default (), steps )
108+ currentPipeline := executor .NewPipeline (slog .Default (), steps )
108109
109110 notWatching := rt .Watch == nil || rt .Watch .Enabled == false
110111
111112 if notWatching {
112- return pipeline .Start (ctx )
113+ return currentPipeline .Start (ctx )
113114 }
114115
115- var wg sync.WaitGroup
116116 watch , err := watcher .NewWatcher (ctx , watcher.WatcherArgs {
117117 WatchDirs : rt .Watch .Dirs ,
118118 IgnoreDirs : rt .Watch .IgnoreDirs ,
@@ -126,57 +126,63 @@ func (r *Resolver) RunTask(ctx context.Context, name string) error {
126126 return err
127127 }
128128
129- wg .Add (1 )
130- go func () {
131- defer wg .Done ()
132- <- ctx .Done ()
133- // ctx.Logger().Info("fwatcher is closing ...")
134- watch .Close ()
135- }()
136-
137- // executors := []executor.Executor{pipeline}
138- //
139- // if rt.Watch.SSE != nil && rt.Watch.SSE.Addr != "" {
140- // executors = append(executors, executor.NewSSEExecutor(executor.SSEExecutorArgs{Addr: rt.Watch.SSE.Addr}))
141- // }
142-
143- wg .Add (1 )
144- go func () {
145- defer wg .Done ()
146- if err := pipeline .Start (ctx ); err != nil {
147- slog .Error ("starting command" , "err" , err )
129+ go watch .Watch (ctx )
130+ defer watch .Close ()
131+
132+ var pMu sync.Mutex
133+
134+ run := func () {
135+ pMu .Lock ()
136+ defer pMu .Unlock ()
137+
138+ if currentPipeline != nil {
139+ currentPipeline .Stop ()
148140 }
149- slog .Debug ("final executor start finished" )
150- }()
151-
152- wg .Add (1 )
153- go func () {
154- defer wg .Done ()
155- <- ctx .Done ()
156- pipeline .Stop ()
157- slog .Debug ("2. context cancelled" )
158- }()
159-
160- wg .Add (1 )
161- go func () {
162- defer wg .Done ()
163- watch .Watch (ctx )
164- slog .Debug ("3. watcher closed" )
165- }()
141+
142+ currentPipeline = executor .NewPipeline (slog .Default (), steps )
143+ go func () {
144+ if err := currentPipeline .Start (ctx ); err != nil && ! errors .Is (err , context .Canceled ) {
145+ // slog.Error("pipeline finished with error", "err", err)
146+ }
147+ }()
148+ }
149+
150+ run ()
151+ // // initial run
152+ // go func() {
153+ // if err := currentPipeline.Start(ctx); err != nil && !errors.Is(err, context.Canceled) {
154+ // // slog.Error("pipeline finished with error", "err", err)
155+ // }
156+ // }()
166157
167158 counter := 0
168- for ev := range watch . GetEvents () {
169- slog . Debug ( "received" , "event" , ev )
170- counter += 1
171- slog . Info ( fmt . Sprintf ( "[RELOADING (%d)] due changes in %s" , counter , ev . Name ))
159+ debounceDuration := 300 * time . Millisecond
160+ timer := time . NewTimer ( debounceDuration )
161+ if ! timer . Stop () {
162+ <- timer . C
172163 }
173164
174- // if err := watch.WatchAndExecute(ctx, executors); err != nil {
175- // return err
176- // }
177- //
178- wg .Wait ()
179- return nil
165+ for {
166+ select {
167+ case <- ctx .Done ():
168+ pMu .Lock ()
169+ if currentPipeline != nil {
170+ currentPipeline .Stop ()
171+ }
172+ pMu .Unlock ()
173+ return nil
174+ case ev , ok := <- watch .GetEvents ():
175+ if ! ok {
176+ return nil
177+ }
178+ slog .Debug ("received" , "event" , ev )
179+ timer .Reset (debounceDuration )
180+ case <- timer .C :
181+ counter += 1
182+ slog .Info (fmt .Sprintf ("[RELOADING (%d)]" , counter ))
183+ run ()
184+ }
185+ }
180186}
181187
182188func parseCommands (commands []any ) ([]* Command , error ) {
@@ -311,8 +317,8 @@ func printCommand(w *writer.LogWriter, prefix, lang, cmd string) {
311317 hlCode .WriteString (cmdStr )
312318 }
313319
314- // INFO: 2 for spaces around prefix
315- longestLen := longestLineLen (cmd ) + len (prefix ) + 2
320+ // Use display width so unicode prefixes like "≫" don't skew the box layout.
321+ longestLen := longestLineWidth (cmd ) + prefixDisplayWidth (prefix )
316322
317323 if width > 0 && longestLen >= width - 2 {
318324 s = s .Width (width - 2 )
@@ -323,12 +329,12 @@ func printCommand(w *writer.LogWriter, prefix, lang, cmd string) {
323329 fmt .Fprintf (w , "\r \033 [K%s%s\n " , padString (s .Render (hlCode .String ()), prefix ), s .UnsetBorderStyle ())
324330}
325331
326- func longestLineLen (str string ) int {
332+ func longestLineWidth (str string ) int {
327333 sp := strings .Split (str , "\n " )
328- l := len (sp [0 ])
334+ l := lipgloss . Width (sp [0 ])
329335 for i := 1 ; i < len (sp ); i ++ {
330- if len (sp [i ]) > l {
331- l = len (sp [i ])
336+ if lipgloss . Width (sp [i ]) > l {
337+ l = lipgloss . Width (sp [i ])
332338 }
333339 }
334340
@@ -337,17 +343,26 @@ func longestLineLen(str string) int {
337343
338344func padString (str string , withPrefix string ) string {
339345 sp := strings .Split (str , "\n " )
346+ indent := strings .Repeat (" " , prefixDisplayWidth (withPrefix ))
340347 for i := range sp {
341348 if i == 0 {
342349 sp [i ] = fmt .Sprintf ("%s %s" , writer .GetStyledPrefix (withPrefix ), sp [i ])
343350 continue
344351 }
345- sp [i ] = fmt . Sprintf ( "%s %s" , strings . Repeat ( " " , len ( withPrefix ) + 2 ), sp [i ])
352+ sp [i ] = indent + sp [i ]
346353 }
347354
348355 return strings .Join (sp , "\n " )
349356}
350357
358+ func prefixDisplayWidth (prefix string ) int {
359+ if prefix == "" {
360+ return 0
361+ }
362+
363+ return lipgloss .Width ("[" + prefix + "] " )
364+ }
365+
351366type createCommandGroupArgs struct {
352367 Stdout * writer.LogWriter
353368 Stderr * writer.LogWriter
@@ -384,6 +399,10 @@ func (r *Resolver) createSteps(task *ResolvedTask, args createCommandGroupArgs)
384399 }
385400
386401 if cmd .IsRunTarget {
402+ if cycle := appendCycle (taskTrail , cmd .Text ); cycle != nil {
403+ return nil , errors .New ("Circular Task Dependency" ).KV ("cycle" , strings .Join (cycle , " -> " ))
404+ }
405+
387406 rt , err := r .GetTask (cmd .Text )
388407 if err != nil {
389408 return nil , err
@@ -448,3 +467,17 @@ func (r *Resolver) createSteps(task *ResolvedTask, args createCommandGroupArgs)
448467 slog .Debug ("created command groups" , "len" , len (steps ))
449468 return steps , nil
450469}
470+
471+ func appendCycle (taskTrail []string , next string ) []string {
472+ for i := range taskTrail {
473+ if taskTrail [i ] != next {
474+ continue
475+ }
476+
477+ cycle := append ([]string {}, taskTrail [i :]... )
478+ cycle = append (cycle , next )
479+ return cycle
480+ }
481+
482+ return nil
483+ }
0 commit comments