1
0
mirror of https://github.com/go-task/task.git synced 2025-01-04 03:48:02 +02:00

feat: suggest the most similar task name when a given task does not exist

This commit is contained in:
Max Pushkarov 2022-10-02 18:49:38 +03:00
parent d2061ec898
commit 3e5ee2332a
7 changed files with 48 additions and 6 deletions

View File

@ -13,11 +13,20 @@ var (
)
type taskNotFoundError struct {
taskName string
taskName string
didYouMean string
}
func (err *taskNotFoundError) Error() string {
return fmt.Sprintf(`task: Task %q not found`, err.taskName)
if err.didYouMean != "" {
return fmt.Sprintf(
`task: Task %q does not exist. Did you mean %q?`,
err.taskName,
err.didYouMean,
)
}
return fmt.Sprintf(`task: Task %q does not exist`, err.taskName)
}
type taskInternalError struct {

1
go.mod
View File

@ -7,6 +7,7 @@ require (
github.com/mattn/go-zglob v0.0.3
github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/radovskyb/watcher v1.0.7
github.com/sajari/fuzzy v1.0.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.0
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c

2
go.sum
View File

@ -26,6 +26,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/radovskyb/watcher v1.0.7 h1:AYePLih6dpmS32vlHfhCeli8127LzkIgwJGcwwe8tUE=
github.com/radovskyb/watcher v1.0.7/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg=
github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
github.com/sajari/fuzzy v1.0.0 h1:+FmwVvJErsd0d0hAPlj4CxqxUtQY/fOoY0DwX4ykpRY=
github.com/sajari/fuzzy v1.0.0/go.mod h1:OjYR6KxoWOe9+dOlXeiCJd4dIbED4Oo8wpS89o0pwOo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=

View File

@ -78,7 +78,7 @@ func (e *Executor) tasksWithDesc() (tasks []*taskfile.Task) {
return
}
// PrintTaskNames prints only the task names in a Taskfile.
// ListTaskNames prints only the task names in a Taskfile.
// Only tasks with a non-empty description are printed if allTasks is false.
// Otherwise, all task names are printed.
func (e *Executor) ListTaskNames(allTasks bool) {

View File

@ -17,6 +17,8 @@ import (
"github.com/go-task/task/v3/internal/output"
"github.com/go-task/task/v3/taskfile"
"github.com/go-task/task/v3/taskfile/read"
"github.com/sajari/fuzzy"
)
func (e *Executor) Setup() error {
@ -28,6 +30,8 @@ func (e *Executor) Setup() error {
return err
}
e.setupFuzzyModel()
v, err := e.Taskfile.ParsedVersion()
if err != nil {
return err
@ -85,6 +89,21 @@ func (e *Executor) readTaskfile() error {
return err
}
func (e *Executor) setupFuzzyModel() {
if e.Taskfile != nil {
model := fuzzy.NewModel()
model.SetThreshold(1) // because we want to build grammar based on every task name
var words []string
for taskName := range e.Taskfile.Tasks {
words = append(words, taskName)
}
model.Train(words)
e.fuzzyModel = model
}
}
func (e *Executor) setupTempDir() error {
if e.TempDir != "" {
return nil

11
task.go
View File

@ -16,6 +16,7 @@ import (
"github.com/go-task/task/v3/internal/templater"
"github.com/go-task/task/v3/taskfile"
"github.com/sajari/fuzzy"
"golang.org/x/sync/errgroup"
)
@ -51,7 +52,8 @@ type Executor struct {
Output output.Output
OutputStyle taskfile.Output
taskvars *taskfile.Vars
taskvars *taskfile.Vars
fuzzyModel *fuzzy.Model
concurrencySemaphore chan struct{}
taskCallCount map[string]*int32
@ -68,7 +70,12 @@ func (e *Executor) Run(ctx context.Context, calls ...taskfile.Call) error {
if !ok {
// FIXME: move to the main package
e.ListTasksWithDesc()
return &taskNotFoundError{taskName: c.Task}
didYouMean := ""
if e.fuzzyModel != nil {
didYouMean = e.fuzzyModel.SpellCheck(c.Task)
}
return &taskNotFoundError{taskName: c.Task, didYouMean: didYouMean}
}
if t.Internal {
e.ListTasksWithDesc()

View File

@ -24,7 +24,11 @@ func (e *Executor) FastCompiledTask(call taskfile.Call) (*taskfile.Task, error)
func (e *Executor) compiledTask(call taskfile.Call, evaluateShVars bool) (*taskfile.Task, error) {
origTask, ok := e.Taskfile.Tasks[call.Task]
if !ok {
return nil, &taskNotFoundError{call.Task}
didYouMean := ""
if e.fuzzyModel != nil {
didYouMean = e.fuzzyModel.SpellCheck(call.Task)
}
return nil, &taskNotFoundError{taskName: call.Task, didYouMean: didYouMean}
}
var vars *taskfile.Vars