From 876bb33de20ffa85db0cabdd4b033696ac584280 Mon Sep 17 00:00:00 2001 From: Valentin Maerten Date: Sun, 23 Nov 2025 21:12:25 +0100 Subject: [PATCH] refactor: lazy load fuzzy model & ability to disable it --- completion/fish/task.fish | 7 +++--- completion/ps/task.ps1 | 1 + completion/zsh/_task | 1 + executor.go | 14 ++++++++++++ internal/flags/flags.go | 3 +++ setup.go | 33 ++++++++++++++------------- task.go | 10 ++++++-- taskrc/ast/taskrc.go | 12 ++++++---- website/src/docs/reference/cli.md | 10 ++++++++ website/src/docs/reference/config.md | 12 ++++++++++ website/src/public/schema-taskrc.json | 4 ++++ 11 files changed, 81 insertions(+), 26 deletions(-) diff --git a/completion/fish/task.fish b/completion/fish/task.fish index e8640fe4..ebc72531 100644 --- a/completion/fish/task.fish +++ b/completion/fish/task.fish @@ -36,9 +36,10 @@ end complete -c $GO_TASK_PROGNAME -d 'Runs the specified task(s). Falls back to the "default" task if no task name was specified, or lists all tasks if an unknown task name was specified.' -xa "(__task_get_tasks)" -complete -c $GO_TASK_PROGNAME -s c -l color -d 'colored output (default true)' -complete -c $GO_TASK_PROGNAME -s d -l dir -d 'sets directory of execution' -complete -c $GO_TASK_PROGNAME -l dry -d 'compiles and prints tasks in the order that they would be run, without executing them' +complete -c $GO_TASK_PROGNAME -s c -l color -d 'colored output (default true)' +complete -c $GO_TASK_PROGNAME -s d -l dir -d 'sets directory of execution' +complete -c $GO_TASK_PROGNAME -l disable-fuzzy -d 'disables fuzzy matching for task names' +complete -c $GO_TASK_PROGNAME -l dry -d 'compiles and prints tasks in the order that they would be run, without executing them' complete -c $GO_TASK_PROGNAME -s f -l force -d 'forces execution even when the task is up-to-date' complete -c $GO_TASK_PROGNAME -s h -l help -d 'shows Task usage' complete -c $GO_TASK_PROGNAME -s i -l init -d 'creates a new Taskfile.yml in the current folder' diff --git a/completion/ps/task.ps1 b/completion/ps/task.ps1 index 2a389e8a..340abe26 100644 --- a/completion/ps/task.ps1 +++ b/completion/ps/task.ps1 @@ -8,6 +8,7 @@ Register-ArgumentCompleter -CommandName task -ScriptBlock { [CompletionResult]::new('--list-all ', '--list-all ', [CompletionResultType]::ParameterName, 'list all tasks'), [CompletionResult]::new('--color ', '--color', [CompletionResultType]::ParameterName, '--color'), [CompletionResult]::new('--concurrency=', '--concurrency=', [CompletionResultType]::ParameterName, 'concurrency'), + [CompletionResult]::new('--disable-fuzzy ', '--disable-fuzzy', [CompletionResultType]::ParameterName, '--disable-fuzzy'), [CompletionResult]::new('--interval=', '--interval=', [CompletionResultType]::ParameterName, 'interval'), [CompletionResult]::new('--output=interleaved ', '--output=interleaved', [CompletionResultType]::ParameterName, '--output='), [CompletionResult]::new('--output=group ', '--output=group', [CompletionResultType]::ParameterName, '--output='), diff --git a/completion/zsh/_task b/completion/zsh/_task index 7fe50aed..aad4888b 100755 --- a/completion/zsh/_task +++ b/completion/zsh/_task @@ -42,6 +42,7 @@ _task() { '(-f --force)'{-f,--force}'[run even if task is up-to-date]' \ '(-c --color)'{-c,--color}'[colored output]' \ '(-d --dir)'{-d,--dir}'[dir to run in]:execution dir:_dirs' \ + '(--disable-fuzzy)--disable-fuzzy[disables fuzzy matching for task names]' \ '(--dry)--dry[dry-run mode, compile and print tasks only]' \ '(-o --output)'{-o,--output}'[set output style]:style:(interleaved group prefixed)' \ '(--output-group-begin)--output-group-begin[message template before grouped output]:template text: ' \ diff --git a/executor.go b/executor.go index 6ecf910a..84dd2e39 100644 --- a/executor.go +++ b/executor.go @@ -39,6 +39,7 @@ type ( Watch bool Verbose bool Silent bool + DisableFuzzy bool AssumeYes bool AssumeTerm bool // Used for testing Dry bool @@ -296,6 +297,19 @@ func (o *silentOption) ApplyToExecutor(e *Executor) { e.Silent = o.silent } +// WithDisableFuzzy tells the [Executor] to disable fuzzy matching for task names. +func WithDisableFuzzy(disableFuzzy bool) ExecutorOption { + return &disableFuzzyOption{disableFuzzy} +} + +type disableFuzzyOption struct { + disableFuzzy bool +} + +func (o *disableFuzzyOption) ApplyToExecutor(e *Executor) { + e.DisableFuzzy = o.disableFuzzy +} + // WithAssumeYes tells the [Executor] to assume "yes" for all prompts. func WithAssumeYes(assumeYes bool) ExecutorOption { return &assumeYesOption{assumeYes} diff --git a/internal/flags/flags.go b/internal/flags/flags.go index 5787e17a..b1ef0e36 100644 --- a/internal/flags/flags.go +++ b/internal/flags/flags.go @@ -58,6 +58,7 @@ var ( Watch bool Verbose bool Silent bool + DisableFuzzy bool AssumeYes bool Dry bool Summary bool @@ -123,6 +124,7 @@ func init() { pflag.BoolVarP(&Watch, "watch", "w", false, "Enables watch of the given task.") pflag.BoolVarP(&Verbose, "verbose", "v", getConfig(config, func() *bool { return config.Verbose }, false), "Enables verbose mode.") pflag.BoolVarP(&Silent, "silent", "s", false, "Disables echoing.") + pflag.BoolVar(&DisableFuzzy, "disable-fuzzy", getConfig(config, func() *bool { return config.DisableFuzzy }, false), "Disables fuzzy matching for task names.") pflag.BoolVarP(&AssumeYes, "yes", "y", false, "Assume \"yes\" as answer to all prompts.") pflag.BoolVarP(&Parallel, "parallel", "p", false, "Executes tasks provided on command line in parallel.") pflag.BoolVarP(&Dry, "dry", "n", false, "Compiles and prints tasks in the order that they would be run, without executing them.") @@ -243,6 +245,7 @@ func (o *flagsOption) ApplyToExecutor(e *task.Executor) { task.WithWatch(Watch), task.WithVerbose(Verbose), task.WithSilent(Silent), + task.WithDisableFuzzy(DisableFuzzy), task.WithAssumeYes(AssumeYes), task.WithDry(Dry || Status), task.WithSummary(Summary), diff --git a/setup.go b/setup.go index 14ffc3b3..df236f5e 100644 --- a/setup.go +++ b/setup.go @@ -5,10 +5,12 @@ import ( "fmt" "os" "path/filepath" + "slices" "strings" "sync" "github.com/Masterminds/semver/v3" + "github.com/sajari/fuzzy" "github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/internal/env" @@ -34,7 +36,6 @@ func (e *Executor) Setup() error { if err := e.readTaskfile(node); err != nil { return err } - e.setupFuzzyModel() e.setupStdFiles() if err := e.setupOutput(); err != nil { return err @@ -105,21 +106,21 @@ func (e *Executor) setupFuzzyModel() { if e.Taskfile == nil { return } - // - //model := fuzzy.NewModel() - //model.SetThreshold(1) // because we want to build grammar based on every task name - // - //var words []string - //for name, task := range e.Taskfile.Tasks.All(nil) { - // if task.Internal { - // continue - // } - // words = append(words, name) - // words = slices.Concat(words, task.Aliases) - //} - // - //model.Train(words) - //e.fuzzyModel = model + + model := fuzzy.NewModel() + model.SetThreshold(1) // because we want to build grammar based on every task name + + var words []string + for name, task := range e.Taskfile.Tasks.All(nil) { + if task.Internal { + continue + } + words = append(words, name) + words = slices.Concat(words, task.Aliases) + } + + model.Train(words) + e.fuzzyModel = model } func (e *Executor) setupTempDir() error { diff --git a/task.go b/task.go index 0afe2215..be7c7676 100644 --- a/task.go +++ b/task.go @@ -452,8 +452,14 @@ func (e *Executor) GetTask(call *Call) (*ast.Task, error) { // If we found no tasks if len(aliasedTasks) == 0 { didYouMean := "" - if e.fuzzyModel != nil { - didYouMean = e.fuzzyModel.SpellCheck(call.Task) + if !e.DisableFuzzy { + // Lazy initialization of fuzzy model + if e.fuzzyModel == nil { + e.setupFuzzyModel() + } + if e.fuzzyModel != nil { + didYouMean = e.fuzzyModel.SpellCheck(call.Task) + } } return nil, &errors.TaskNotFoundError{ TaskName: call.Task, diff --git a/taskrc/ast/taskrc.go b/taskrc/ast/taskrc.go index 9410ed17..e625616f 100644 --- a/taskrc/ast/taskrc.go +++ b/taskrc/ast/taskrc.go @@ -9,11 +9,12 @@ import ( ) type TaskRC struct { - Version *semver.Version `yaml:"version"` - Verbose *bool `yaml:"verbose"` - Concurrency *int `yaml:"concurrency"` - Remote Remote `yaml:"remote"` - Experiments map[string]int `yaml:"experiments"` + Version *semver.Version `yaml:"version"` + Verbose *bool `yaml:"verbose"` + DisableFuzzy *bool `yaml:"disable-fuzzy"` + Concurrency *int `yaml:"concurrency"` + Remote Remote `yaml:"remote"` + Experiments map[string]int `yaml:"experiments"` } type Remote struct { @@ -44,5 +45,6 @@ func (t *TaskRC) Merge(other *TaskRC) { t.Remote.CacheExpiry = cmp.Or(other.Remote.CacheExpiry, t.Remote.CacheExpiry) t.Verbose = cmp.Or(other.Verbose, t.Verbose) + t.DisableFuzzy = cmp.Or(other.DisableFuzzy, t.DisableFuzzy) t.Concurrency = cmp.Or(other.Concurrency, t.Concurrency) } diff --git a/website/src/docs/reference/cli.md b/website/src/docs/reference/cli.md index 848f492c..6756592e 100644 --- a/website/src/docs/reference/cli.md +++ b/website/src/docs/reference/cli.md @@ -108,6 +108,16 @@ Disable command echoing. task deploy --silent ``` +#### `--disable-fuzzy` + +Disable fuzzy matching for task names. When enabled, Task will not suggest similar task names when you mistype a task name. + +```bash +task buidl --disable-fuzzy +# Output: Task "buidl" does not exist +# (without "Did you mean 'build'?" suggestion) +``` + ### Execution Control #### `-f, --force` diff --git a/website/src/docs/reference/config.md b/website/src/docs/reference/config.md index 73bb1525..e4ec78a8 100644 --- a/website/src/docs/reference/config.md +++ b/website/src/docs/reference/config.md @@ -91,6 +91,17 @@ experiments: verbose: true ``` +### `disable-fuzzy` + +- **Type**: `boolean` +- **Default**: `false` +- **Description**: Disable fuzzy matching for task names. When enabled, Task will not suggest similar task names when you mistype a task name. +- **CLI equivalent**: [`--disable-fuzzy`](./cli.md#--disable-fuzzy) + +```yaml +disable-fuzzy: true +``` + ### `concurrency` - **Type**: `integer` @@ -109,6 +120,7 @@ Here's a complete example of a `.taskrc.yml` file with all available options: ```yaml # Global settings verbose: true +disable-fuzzy: false concurrency: 2 # Enable experimental features diff --git a/website/src/public/schema-taskrc.json b/website/src/public/schema-taskrc.json index ac101acf..813a1ebb 100644 --- a/website/src/public/schema-taskrc.json +++ b/website/src/public/schema-taskrc.json @@ -50,6 +50,10 @@ "type": "boolean", "description": "Enable verbose output" }, + "disable-fuzzy": { + "type": "boolean", + "description": "Disable fuzzy matching for task names" + }, "concurrency": { "type": "integer", "description": "Number of concurrent tasks to run",