refactor(interactive): simplify prompting functions and naming

This commit is contained in:
Valentin Maerten
2025-12-14 17:17:31 +01:00
parent 7f4d5a5e4a
commit fa92cc14e5
3 changed files with 34 additions and 40 deletions

View File

@@ -70,7 +70,7 @@ type (
fuzzyModel *fuzzy.Model fuzzyModel *fuzzy.Model
fuzzyModelOnce sync.Once fuzzyModelOnce sync.Once
promptedVars *ast.Vars // vars collected via interactive prompts promptedVars *ast.Vars // vars collected via interactive prompts
concurrencySemaphore chan struct{} concurrencySemaphore chan struct{}
taskCallCount map[string]*int32 taskCallCount map[string]*int32
mkdirMutexMap map[string]*sync.Mutex mkdirMutexMap map[string]*sync.Mutex

View File

@@ -9,7 +9,20 @@ import (
"github.com/go-task/task/v3/taskfile/ast" "github.com/go-task/task/v3/taskfile/ast"
) )
func (e *Executor) collectAllRequiredVars(calls []*Call) ([]*ast.VarsWithValidation, error) { // promptDepsVars traverses the dependency tree, collects all missing required
// variables, and prompts for them upfront. This is used for deps which execute
// in parallel, so all prompts must happen before execution to avoid interleaving.
// Prompted values are stored in e.promptedVars for injection into task calls.
func (e *Executor) promptDepsVars(calls []*Call) error {
if !e.Interactive {
return nil
}
if !e.AssumeTerm && !term.IsTerminal() {
return nil
}
// Collect all missing vars from the dependency tree
visited := make(map[string]bool) visited := make(map[string]bool)
varsMap := make(map[string]*ast.VarsWithValidation) varsMap := make(map[string]*ast.VarsWithValidation)
@@ -52,36 +65,24 @@ func (e *Executor) collectAllRequiredVars(calls []*Call) ([]*ast.VarsWithValidat
for _, call := range calls { for _, call := range calls {
if err := collect(call); err != nil { if err := collect(call); err != nil {
return nil, err return err
} }
} }
result := make([]*ast.VarsWithValidation, 0, len(varsMap)) if len(varsMap) == 0 {
for _, v := range varsMap { return nil
result = append(result, v)
}
return result, nil
}
func (e *Executor) promptForAllVars(vars []*ast.VarsWithValidation) (*ast.Vars, error) {
if len(vars) == 0 || !e.Interactive {
return nil, nil
}
if !e.AssumeTerm && !term.IsTerminal() {
return nil, nil
} }
// Prompt for all collected vars
prompter := &prompt.Prompter{ prompter := &prompt.Prompter{
Stdin: e.Stdin, Stdin: e.Stdin,
Stdout: e.Stdout, Stdout: e.Stdout,
Stderr: e.Stderr, Stderr: e.Stderr,
} }
result := ast.NewVars() e.promptedVars = ast.NewVars()
for _, v := range vars { for _, v := range varsMap {
var value string var value string
var err error var err error
@@ -93,21 +94,21 @@ func (e *Executor) promptForAllVars(vars []*ast.VarsWithValidation) (*ast.Vars,
if err != nil { if err != nil {
if errors.Is(err, prompt.ErrCancelled) { if errors.Is(err, prompt.ErrCancelled) {
return nil, &errors.TaskCancelledByUserError{TaskName: "interactive prompt"} return &errors.TaskCancelledByUserError{TaskName: "interactive prompt"}
} }
return nil, err return err
} }
result.Set(v.Name, ast.Var{Value: value}) e.promptedVars.Set(v.Name, ast.Var{Value: value})
} }
return result, nil return nil
} }
// promptForMissingVars prompts for any required vars that are missing from the task. // promptTaskVars prompts for any missing required vars from a single task.
// It updates call.Vars with the prompted values and stores them in e.promptedVars for reuse. // Used for sequential task calls (cmds) where we can prompt just-in-time.
// Returns true if any vars were prompted (caller should recompile the task). // Returns true if any vars were prompted (caller should recompile the task).
func (e *Executor) promptForMissingVars(t *ast.Task, call *Call) (bool, error) { func (e *Executor) promptTaskVars(t *ast.Task, call *Call) (bool, error) {
if !e.Interactive || t.Requires == nil || len(t.Requires.Vars) == 0 { if !e.Interactive || t.Requires == nil || len(t.Requires.Vars) == 0 {
return false, nil return false, nil
} }
@@ -120,7 +121,7 @@ func (e *Executor) promptForMissingVars(t *ast.Task, call *Call) (bool, error) {
var missing []*ast.VarsWithValidation var missing []*ast.VarsWithValidation
for _, v := range t.Requires.Vars { for _, v := range t.Requires.Vars {
if _, ok := t.Vars.Get(v.Name); !ok { if _, ok := t.Vars.Get(v.Name); !ok {
// Also check if we already prompted for this var // Skip if already prompted
if e.promptedVars != nil { if e.promptedVars != nil {
if _, ok := e.promptedVars.Get(v.Name); ok { if _, ok := e.promptedVars.Get(v.Name); ok {
continue continue
@@ -157,13 +158,13 @@ func (e *Executor) promptForMissingVars(t *ast.Task, call *Call) (bool, error) {
return false, err return false, err
} }
// Add to call.Vars so it's available for recompilation // Add to call.Vars for recompilation
if call.Vars == nil { if call.Vars == nil {
call.Vars = ast.NewVars() call.Vars = ast.NewVars()
} }
call.Vars.Set(v.Name, ast.Var{Value: value}) call.Vars.Set(v.Name, ast.Var{Value: value})
// Store in promptedVars for reuse by other tasks // Cache for reuse by other tasks
if e.promptedVars == nil { if e.promptedVars == nil {
e.promptedVars = ast.NewVars() e.promptedVars = ast.NewVars()
} }

13
task.go
View File

@@ -73,15 +73,8 @@ func (e *Executor) Run(ctx context.Context, calls ...*Call) error {
return nil return nil
} }
// Collect all required vars upfront and prompt for them all at once // Prompt for all required vars from deps upfront (parallel execution)
requiredVars, err := e.collectAllRequiredVars(calls) if err := e.promptDepsVars(calls); err != nil {
if err != nil {
return err
}
// Prompt for all missing vars and store on executor
e.promptedVars, err = e.promptForAllVars(requiredVars)
if err != nil {
return err return err
} }
@@ -155,7 +148,7 @@ func (e *Executor) RunTask(ctx context.Context, call *Call) error {
} }
// Prompt for missing required vars (just-in-time for sequential task calls) // Prompt for missing required vars (just-in-time for sequential task calls)
prompted, err := e.promptForMissingVars(t, call) prompted, err := e.promptTaskVars(t, call)
if err != nil { if err != nil {
return err return err
} }