mirror of
https://github.com/go-task/task.git
synced 2026-08-29 10:08:27 +02:00
refactor(completion): tighten the engine after a cleanup pass
`--temp-dir` was missing from the flag-to-directive map, so it fell back to plain file completion while `--dir` and `--remote-cache-dir` offered directories. The rest is dead weight: `listTasks` re-defaulted a sorter `NewExecutor` already sets and scanned descriptions for templates even with `--no-descriptions`; `detectTaskName` had a `--` branch `Complete` returns before reaching; the two flag-value branches built the same suggestion slice twice. `os.Args[2:]` is now sliced in one place, `complete.Words()`, instead of three, and the test helpers reuse `slicesext.Convert`.
This commit is contained in:
@@ -62,7 +62,7 @@ func run() error {
|
||||
// Dispatched before flag validation: the args after __complete are the
|
||||
// user's command line, not Task's own flags.
|
||||
if complete.IsActive() {
|
||||
return runComplete(os.Args[2:])
|
||||
return runComplete(complete.Words())
|
||||
}
|
||||
|
||||
log := &logger.Logger{
|
||||
|
||||
@@ -10,6 +10,11 @@ func IsActive() bool {
|
||||
return len(os.Args) >= 2 && os.Args[1] == CommandName
|
||||
}
|
||||
|
||||
// Words returns the command line being completed: the args after __complete.
|
||||
func Words() []string {
|
||||
return os.Args[2:]
|
||||
}
|
||||
|
||||
// Directive mirrors cobra's ShellCompDirective bitfield, emitted as `:<n>`.
|
||||
type Directive int
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/go-task/task/v3"
|
||||
"github.com/go-task/task/v3/internal/complete"
|
||||
"github.com/go-task/task/v3/internal/slicesext"
|
||||
)
|
||||
|
||||
func newTestFlagSet() *pflag.FlagSet {
|
||||
@@ -23,6 +24,7 @@ func newTestFlagSet() *pflag.FlagSet {
|
||||
fs.BoolVarP(&b, "verbose", "v", false, "Verbose mode")
|
||||
fs.StringVarP(&s, "taskfile", "t", "", "Taskfile path")
|
||||
fs.StringVarP(&s, "dir", "d", "", "Run dir")
|
||||
fs.StringVar(&s, "temp-dir", "", "Temp dir")
|
||||
fs.StringVarP(&s, "output", "o", "", "Output style")
|
||||
fs.StringVar(&s, "sort", "", "Sort order")
|
||||
fs.StringVar(&s, "cacert", "", "CA cert path")
|
||||
@@ -282,6 +284,14 @@ func TestComplete_PathFlag_Dir(t *testing.T) {
|
||||
require.Equal(t, complete.DirectiveFilterDirs, dir)
|
||||
}
|
||||
|
||||
func TestComplete_PathFlag_TempDir(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
suggs, dir := complete.Complete(setupExecutor(t), newTestFlagSet(), []string{"--temp-dir", ""}, complete.Options{})
|
||||
require.Empty(t, suggs)
|
||||
require.Equal(t, complete.DirectiveFilterDirs, dir)
|
||||
}
|
||||
|
||||
func TestComplete_PathFlag_Cacert(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -435,17 +445,9 @@ func TestWrite_EmptyWithDirective(t *testing.T) {
|
||||
}
|
||||
|
||||
func values(suggs []complete.Suggestion) []string {
|
||||
out := make([]string, 0, len(suggs))
|
||||
for _, s := range suggs {
|
||||
out = append(out, s.Value)
|
||||
}
|
||||
return out
|
||||
return slicesext.Convert(suggs, func(s complete.Suggestion) string { return s.Value })
|
||||
}
|
||||
|
||||
func descriptions(suggs []complete.Suggestion) []string {
|
||||
out := make([]string, 0, len(suggs))
|
||||
for _, s := range suggs {
|
||||
out = append(out, s.Description)
|
||||
}
|
||||
return out
|
||||
return slicesext.Convert(suggs, func(s complete.Suggestion) string { return s.Description })
|
||||
}
|
||||
|
||||
@@ -56,9 +56,6 @@ func detectTaskName(args []string, knownTasks []string, fs *pflag.FlagSet) strin
|
||||
skipNext = false
|
||||
continue
|
||||
}
|
||||
if w == "--" {
|
||||
return taskName
|
||||
}
|
||||
if strings.HasPrefix(w, "-") {
|
||||
if !strings.Contains(w, "=") {
|
||||
if f := matchFlagName(fs, w); f != nil && flagTakesValue(f) {
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"github.com/go-task/task/v3"
|
||||
"github.com/go-task/task/v3/internal/refs"
|
||||
"github.com/go-task/task/v3/internal/slicesext"
|
||||
"github.com/go-task/task/v3/internal/sort"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
@@ -123,22 +122,17 @@ func completeTaskNames(e *task.Executor, opts Options) ([]Suggestion, Directive)
|
||||
// GetTaskList compiles every task, on every keystroke, and a description is the
|
||||
// only compiled field read: worth its cost only when one holds a template.
|
||||
func listTasks(e *task.Executor, opts Options) []*ast.Task {
|
||||
sorter := e.TaskSorter
|
||||
if sorter == nil {
|
||||
sorter = sort.AlphaNumericWithRootTasksFirst
|
||||
}
|
||||
|
||||
out := make([]*ast.Task, 0, e.Taskfile.Tasks.Len())
|
||||
templated := false
|
||||
for t := range e.Taskfile.Tasks.Values(sorter) {
|
||||
for t := range e.Taskfile.Tasks.Values(e.TaskSorter) {
|
||||
if t.Internal {
|
||||
continue
|
||||
}
|
||||
templated = templated || strings.Contains(t.Desc, "{{")
|
||||
templated = templated || (!opts.NoDescriptions && strings.Contains(t.Desc, "{{"))
|
||||
out = append(out, t)
|
||||
}
|
||||
|
||||
if !opts.NoDescriptions && templated {
|
||||
if templated {
|
||||
// The uncompiled tasks keep one broken task from emptying the list.
|
||||
if compiled, err := e.GetTaskList(task.FilterOutInternal); err == nil {
|
||||
return compiled
|
||||
@@ -160,24 +154,24 @@ func completeFlagValue(flagName, prefix string) ([]Suggestion, Directive) {
|
||||
// An absent key yields DirectiveDefault, falling through to the enums.
|
||||
switch flagDirective[flagName] {
|
||||
case DirectiveFilterFileExt:
|
||||
exts := slicesext.Convert(taskfileExtensions, func(ext string) Suggestion {
|
||||
return Suggestion{Value: ext}
|
||||
})
|
||||
return exts, DirectiveFilterFileExt
|
||||
return suggest("", taskfileExtensions), DirectiveFilterFileExt
|
||||
case DirectiveFilterDirs:
|
||||
return nil, DirectiveFilterDirs
|
||||
}
|
||||
|
||||
if values, ok := flagEnums[flagName]; ok {
|
||||
out := slicesext.Convert(values, func(v string) Suggestion {
|
||||
return Suggestion{Value: prefix + v}
|
||||
})
|
||||
return out, DirectiveNoFileComp
|
||||
return suggest(prefix, values), DirectiveNoFileComp
|
||||
}
|
||||
|
||||
return nil, DirectiveDefault
|
||||
}
|
||||
|
||||
func suggest(prefix string, values []string) []Suggestion {
|
||||
return slicesext.Convert(values, func(v string) Suggestion {
|
||||
return Suggestion{Value: prefix + v}
|
||||
})
|
||||
}
|
||||
|
||||
func completeTaskVars(e *task.Executor, taskName string) ([]Suggestion, Directive) {
|
||||
compiled, err := e.FastCompiledTask(&task.Call{Task: taskName})
|
||||
if err != nil || compiled == nil || compiled.Requires == nil {
|
||||
|
||||
@@ -23,6 +23,7 @@ var flagDirective = map[string]Directive{
|
||||
"taskfile": DirectiveFilterFileExt,
|
||||
"dir": DirectiveFilterDirs,
|
||||
"remote-cache-dir": DirectiveFilterDirs,
|
||||
"temp-dir": DirectiveFilterDirs,
|
||||
}
|
||||
|
||||
var taskfileExtensions = []string{"yml", "yaml"}
|
||||
|
||||
@@ -181,7 +181,7 @@ func init() {
|
||||
// flags deciding which Taskfile is loaded must still reach the engine.
|
||||
// ContinueOnError keeps what was parsed and prints nothing.
|
||||
if complete.IsActive() {
|
||||
_, words := complete.ParseOptions(os.Args[2:])
|
||||
_, words := complete.ParseOptions(complete.Words())
|
||||
pflag.CommandLine.Init(pflag.CommandLine.Name(), pflag.ContinueOnError)
|
||||
pflag.CommandLine.ParseErrorsAllowlist.UnknownFlags = true
|
||||
_ = pflag.CommandLine.Parse(words)
|
||||
|
||||
Reference in New Issue
Block a user