Skip to content

Commit

Permalink
feat: completion command
Browse files Browse the repository at this point in the history
  • Loading branch information
pd93 committed May 15, 2023
1 parent 65c923e commit 0674612
Show file tree
Hide file tree
Showing 16 changed files with 347 additions and 6 deletions.
17 changes: 16 additions & 1 deletion Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,13 @@ tasks:
- go install -v ./cmd/task

generate:
desc: Run all generation tasks
aliases: [g, gen]
deps: [generate:mocks, generate:fixtures]

generate:mocks:
desc: Runs Go generate to create mocks
aliases: [gen, g]
aliases: [g:mocks, gen:mocks]
deps: [install:mockery]
sources:
- "internal/fingerprint/checker.go"
Expand All @@ -38,6 +43,16 @@ tasks:
- "{{.BIN}}/mockery --dir ./internal/fingerprint --name SourcesCheckable"
- "{{.BIN}}/mockery --dir ./internal/fingerprint --name StatusCheckable"

generate:fixtures:
desc: Generates fixtures
aliases: [g:fixtures, gen:fixtures]
sources:
- "internal/completion/templates/*"
generates:
- "internal/completion/testdata/**/*.golden"
cmds:
- go test ./internal/completion -update

install:mockery:
desc: Installs mockgen; a tool to generate mock files
vars:
Expand Down
12 changes: 12 additions & 0 deletions cmd/task/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/go-task/task/v3"
"github.com/go-task/task/v3/args"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/completion"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/sort"
ver "github.com/go-task/task/v3/internal/version"
Expand Down Expand Up @@ -47,6 +48,7 @@ var flags struct {
version bool
help bool
init bool
completion string
list bool
listAll bool
listJson bool
Expand Down Expand Up @@ -104,6 +106,7 @@ func run() error {
pflag.BoolVar(&flags.version, "version", false, "Show Task version.")
pflag.BoolVarP(&flags.help, "help", "h", false, "Shows Task usage.")
pflag.BoolVarP(&flags.init, "init", "i", false, "Creates a new Taskfile.yml in the current folder.")
pflag.StringVar(&flags.completion, "completion", "", "Generates shell completion script.")
pflag.BoolVarP(&flags.list, "list", "l", false, "Lists tasks with description of current Taskfile.")
pflag.BoolVarP(&flags.listAll, "list-all", "a", false, "Lists tasks with or without a description.")
pflag.BoolVarP(&flags.listJson, "json", "j", false, "Formats task list as JSON.")
Expand Down Expand Up @@ -226,6 +229,15 @@ func run() error {
return err
}

if flags.completion != "" {
script, err := completion.Compile(flags.completion, e.Taskfile.Tasks)
if err != nil {
return err
}
fmt.Println(script)
return nil
}

if listOptions.ShouldListTasks() {
foundTasks, err := e.ListTasks(listOptions)
if err != nil {
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/radovskyb/watcher v1.0.7
github.com/sajari/fuzzy v1.0.0
github.com/sebdah/goldie/v2 v2.5.3
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.2
golang.org/x/exp v0.0.0-20230212135524-a684f29349b6
Expand All @@ -24,6 +25,7 @@ require (
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sergi/go-diff v1.0.0 // indirect
github.com/stretchr/objx v0.5.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/term v0.3.0 // indirect
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,25 @@ github.com/mattn/go-zglob v0.0.4 h1:LQi2iOm0/fGgu80AioIJ/1j9w9Oh+9DZ39J4VAGzHQM=
github.com/mattn/go-zglob v0.0.4/go.mod h1:MxxjyoXXnMxfIpxTK2GAkw1w8glPsQILx3N5wrKakiY=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/sajari/fuzzy v1.0.0 h1:+FmwVvJErsd0d0hAPlj4CxqxUtQY/fOoY0DwX4ykpRY=
github.com/sajari/fuzzy v1.0.0/go.mod h1:OjYR6KxoWOe9+dOlXeiCJd4dIbED4Oo8wpS89o0pwOo=
github.com/sebdah/goldie/v2 v2.5.3 h1:9ES/mNN+HNUbNWpVAlrzuZ7jE+Nrczbj8uFRjM7624Y=
github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
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=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
Expand Down
71 changes: 71 additions & 0 deletions internal/completion/completion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package completion

import (
"bytes"
"embed"
"fmt"
"os"
"path/filepath"
"text/template"

"github.com/spf13/pflag"

"github.com/go-task/task/v3/internal/templater"
"github.com/go-task/task/v3/taskfile"
)

type TemplateValues struct {
Entrypoint string
Flags []*pflag.Flag
Tasks []*taskfile.Task
}

//go:embed templates/*
var templates embed.FS

func Compile(completion string, tasks taskfile.Tasks) (string, error) {
// Get the file extension for the selected shell
var ext string
switch completion {
case "bash":
ext = "bash"
case "fish":
ext = "fish"
case "powershell":
ext = "ps1"
case "zsh":
ext = "zsh"
default:
return "", fmt.Errorf("unknown completion shell: %s", completion)
}

// Load the template
templateName := fmt.Sprintf("task.tpl.%s", ext)
tpl, err := template.New(templateName).
Funcs(templater.TemplateFuncs).
ParseFS(templates, filepath.Join("templates", templateName))
if err != nil {
return "", err
}

values := TemplateValues{
Entrypoint: os.Args[0],
Flags: getFlagNames(),
Tasks: tasks.Values(),
}

var buf bytes.Buffer
if err := tpl.Execute(&buf, values); err != nil {
return "", err
}

return buf.String(), nil
}

func getFlagNames() []*pflag.Flag {
var flags []*pflag.Flag
pflag.VisitAll(func(flag *pflag.Flag) {
flags = append(flags, flag)
})
return flags
}
72 changes: 72 additions & 0 deletions internal/completion/completion_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package completion

import (
"os"
"testing"

"github.com/sebdah/goldie/v2"
"github.com/spf13/pflag"
"github.com/stretchr/testify/require"

"github.com/go-task/task/v3/internal/orderedmap"
"github.com/go-task/task/v3/taskfile"
)

var (
tasks = taskfile.Tasks{
OrderedMap: orderedmap.FromMapWithOrder(
map[string]*taskfile.Task{
"foo": {
Task: "foo",
Cmds: []*taskfile.Cmd{
{Cmd: "echo foo"},
},
Desc: "Prints foo",
Aliases: []string{"f"},
},
"bar": {
Task: "bar",
Cmds: []*taskfile.Cmd{
{Cmd: "echo bar"},
},
Desc: "Prints bar",
Aliases: []string{"b"},
},
},
[]string{"foo", "bar"},
),
}
flags struct {
foo string
bar int
noDesc bool
}
)

func init() {
os.Args[0] = "task"
pflag.StringVarP(&flags.foo, "foo", "f", "default", "A regular flag")
pflag.IntVar(&flags.bar, "bar", 99, "A flag with no short variant")
pflag.BoolVar(&flags.noDesc, "no-desc", true, "")
}

func TestCompile(t *testing.T) {
tests := []struct {
shell string
}{
{shell: "bash"},
{shell: "fish"},
{shell: "powershell"},
{shell: "zsh"},
}

for _, tt := range tests {
t.Run(tt.shell, func(t *testing.T) {
completion, err := Compile(tt.shell, tasks)
require.NoError(t, err)

g := goldie.New(t, goldie.WithTestNameForDir(true))
g.Assert(t, tt.shell, []byte(completion))
})
}
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
55 changes: 55 additions & 0 deletions internal/completion/testdata/TestCompile/bash.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# vim: set tabstop=2 shiftwidth=2 expandtab:

_GO_TASK_COMPLETION_LIST_OPTION='--list-all'

function _task()
{
local cur prev words cword
_init_completion -n : || return

# Check for `--` within command-line and quit or strip suffix.
local i
for i in "${!words[@]}"; do
if [ "${words[$i]}" == "--" ]; then
# Do not complete words following `--` passed to CLI_ARGS.
[ $cword -gt $i ] && return
# Remove the words following `--` to not put --list in CLI_ARGS.
words=( "${words[@]:0:$i}" )
break
fi
done

# Handle special arguments of options.
case "$prev" in
-d|--dir)
_filedir -d
return $?
;;
-t|--taskfile)
_filedir yaml || return $?
_filedir yml
return $?
;;
-o|--output)
COMPREPLY=( $( compgen -W "interleaved group prefixed" -- $cur ) )
return 0
;;
esac

# Handle normal options.
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "$(_parse_help $1)" -- $cur ) )
return 0
;;
esac

# Prepare task name completions.
local tasks=( $( "${words[@]}" --silent $_GO_TASK_COMPLETION_LIST_OPTION 2> /dev/null ) )
COMPREPLY=( $( compgen -W "${tasks[*]}" -- "$cur" ) )

# Post-process because task names might contain colons.
__ltrim_colon_completions "$cur"
}

complete -F _task task
37 changes: 37 additions & 0 deletions internal/completion/testdata/TestCompile/fish.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
set GO_TASK_PROGNAME task

function __task_get_tasks --description "Prints all available tasks with their description"
# Read the list of tasks (and potential errors)
$GO_TASK_PROGNAME --list-all 2>&1 | read -lz rawOutput

# Return on non-zero exit code (for cases when there is no Taskfile found or etc.)
if test $status -ne 0
return
end

# Grab names and descriptions (if any) of the tasks
set -l output (echo $rawOutput | sed -e '1d; s/\* \(.*\):\s*\(.*\)\s*(aliases.*/\1\t\2/' -e 's/\* \(.*\):\s*\(.*\)/\1\t\2/'| string split0)
if test $output
echo $output
end
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 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'
complete -c $GO_TASK_PROGNAME -s l -l list -d 'lists tasks with description of current Taskfile'
complete -c $GO_TASK_PROGNAME -s o -l output -d 'sets output style: [interleaved|group|prefixed]' -xa "interleaved group prefixed"
complete -c $GO_TASK_PROGNAME -s p -l parallel -d 'executes tasks provided on command line in parallel'
complete -c $GO_TASK_PROGNAME -s s -l silent -d 'disables echoing'
complete -c $GO_TASK_PROGNAME -l status -d 'exits with non-zero exit code if any of the given tasks is not up-to-date'
complete -c $GO_TASK_PROGNAME -l summary -d 'show summary about a task'
complete -c $GO_TASK_PROGNAME -s t -l taskfile -d 'choose which Taskfile to run. Defaults to "Taskfile.yml"'
complete -c $GO_TASK_PROGNAME -s v -l verbose -d 'enables verbose mode'
complete -c $GO_TASK_PROGNAME -l version -d 'show Task version'
complete -c $GO_TASK_PROGNAME -s w -l watch -d 'enables watch of the given task'
8 changes: 8 additions & 0 deletions internal/completion/testdata/TestCompile/powershell.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
$scriptBlock = {
param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters )
$reg = "\* ($commandName.+?):"
$listOutput = $(task --list-all)
$listOutput | Select-String $reg -AllMatches | ForEach-Object { $_.Matches.Groups[1].Value }
}

Register-ArgumentCompleter -CommandName task -ScriptBlock $scriptBlock

0 comments on commit 0674612

Please sign in to comment.