mirror of
https://github.com/go-task/task.git
synced 2026-07-01 08:34:19 +00:00
feat(completion): unify shell wrappers behind task __complete
This commit is contained in:
171
internal/complete/engine.go
Normal file
171
internal/complete/engine.go
Normal file
@@ -0,0 +1,171 @@
|
||||
package complete
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/go-task/task/v3"
|
||||
"github.com/go-task/task/v3/internal/templater"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
// Complete is the single entry point used by `task __complete`. e may be nil
|
||||
// when the Taskfile failed to load; flag completion still works in that case.
|
||||
func Complete(e *task.Executor, fs *pflag.FlagSet, args []string) ([]Suggestion, Directive) {
|
||||
knownTasks := taskNames(e)
|
||||
ctx := parseContext(args, knownTasks, fs)
|
||||
|
||||
if ctx.afterDash {
|
||||
return nil, DirectiveDefault
|
||||
}
|
||||
|
||||
if ctx.prev != "" {
|
||||
if flag := matchFlagName(fs, ctx.prev); flag != nil && flagTakesValue(flag) {
|
||||
return completeFlagValue(flag.Name, ctx.toComplete)
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(ctx.toComplete, "-") {
|
||||
if eqIdx := strings.Index(ctx.toComplete, "="); eqIdx != -1 {
|
||||
flagWord := ctx.toComplete[:eqIdx]
|
||||
partial := ctx.toComplete[eqIdx+1:]
|
||||
if f := matchFlagName(fs, flagWord); f != nil && flagTakesValue(f) {
|
||||
return completeFlagValue(f.Name, partial)
|
||||
}
|
||||
}
|
||||
return listFlags(fs), DirectiveNoFileComp
|
||||
}
|
||||
|
||||
if ctx.taskName != "" && e != nil && e.Taskfile != nil {
|
||||
return completeTaskVars(e, ctx.taskName, ctx.toComplete)
|
||||
}
|
||||
|
||||
return completeTaskNames(e), DirectiveNoFileComp
|
||||
}
|
||||
|
||||
func taskNames(e *task.Executor) []string {
|
||||
if e == nil || e.Taskfile == nil {
|
||||
return nil
|
||||
}
|
||||
var out []string
|
||||
for t := range e.Taskfile.Tasks.Values(nil) {
|
||||
if t.Internal {
|
||||
continue
|
||||
}
|
||||
out = append(out, strings.TrimSuffix(t.Task, ":"))
|
||||
for _, alias := range t.Aliases {
|
||||
out = append(out, strings.TrimSuffix(alias, ":"))
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func completeTaskNames(e *task.Executor) []Suggestion {
|
||||
if e == nil || e.Taskfile == nil {
|
||||
return nil
|
||||
}
|
||||
tasks, err := e.GetTaskList(task.FilterOutInternal)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
out := make([]Suggestion, 0, len(tasks))
|
||||
for _, t := range tasks {
|
||||
out = append(out, Suggestion{
|
||||
Value: strings.TrimSuffix(t.Task, ":"),
|
||||
Description: t.Desc,
|
||||
})
|
||||
for _, alias := range t.Aliases {
|
||||
out = append(out, Suggestion{
|
||||
Value: strings.TrimSuffix(alias, ":"),
|
||||
Description: t.Desc,
|
||||
})
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func completeFlagValue(flagName, toComplete string) ([]Suggestion, Directive) {
|
||||
if dir, ok := flagDirective[flagName]; ok {
|
||||
switch dir {
|
||||
case DirectiveFilterFileExt:
|
||||
suggs := make([]Suggestion, 0, len(taskfileExtensions))
|
||||
for _, ext := range taskfileExtensions {
|
||||
suggs = append(suggs, Suggestion{Value: ext})
|
||||
}
|
||||
return suggs, DirectiveFilterFileExt
|
||||
case DirectiveFilterDirs:
|
||||
return nil, DirectiveFilterDirs
|
||||
default:
|
||||
return nil, DirectiveDefault
|
||||
}
|
||||
}
|
||||
|
||||
if values, ok := flagEnums[flagName]; ok {
|
||||
out := make([]Suggestion, 0, len(values))
|
||||
for _, v := range values {
|
||||
out = append(out, Suggestion{Value: v})
|
||||
}
|
||||
_ = toComplete
|
||||
return out, DirectiveNoFileComp
|
||||
}
|
||||
|
||||
return nil, DirectiveDefault
|
||||
}
|
||||
|
||||
func completeTaskVars(e *task.Executor, taskName, toComplete string) ([]Suggestion, Directive) {
|
||||
compiled, err := e.FastCompiledTask(&task.Call{Task: taskName})
|
||||
if err != nil || compiled == nil || compiled.Requires == nil {
|
||||
return nil, DirectiveNoFileComp
|
||||
}
|
||||
|
||||
cache := &templater.Cache{Vars: compiled.Vars}
|
||||
out := make([]Suggestion, 0, 8)
|
||||
for _, v := range compiled.Requires.Vars {
|
||||
if v == nil || v.Name == "" {
|
||||
continue
|
||||
}
|
||||
values := enumValues(v.Enum, cache)
|
||||
if len(values) == 0 {
|
||||
out = append(out, Suggestion{Value: v.Name + "="})
|
||||
continue
|
||||
}
|
||||
for _, val := range values {
|
||||
out = append(out, Suggestion{Value: v.Name + "=" + val})
|
||||
}
|
||||
}
|
||||
_ = toComplete
|
||||
if len(out) == 0 {
|
||||
return nil, DirectiveNoFileComp
|
||||
}
|
||||
return out, DirectiveNoSpace | DirectiveNoFileComp
|
||||
}
|
||||
|
||||
func enumValues(enum *ast.Enum, cache *templater.Cache) []string {
|
||||
if enum == nil {
|
||||
return nil
|
||||
}
|
||||
if len(enum.Value) > 0 {
|
||||
return enum.Value
|
||||
}
|
||||
if enum.Ref == "" {
|
||||
return nil
|
||||
}
|
||||
resolved := templater.ResolveRef(enum.Ref, cache)
|
||||
if cache.Err() != nil {
|
||||
return nil
|
||||
}
|
||||
arr, ok := resolved.([]any)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
out := make([]string, 0, len(arr))
|
||||
for _, item := range arr {
|
||||
s, ok := item.(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
out = append(out, s)
|
||||
}
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user