From c8593842eeb2a3eae74af7747afc62fa406a5b54 Mon Sep 17 00:00:00 2001 From: Trevor Brown Date: Wed, 18 Dec 2024 11:17:46 -0500 Subject: [PATCH] feat(golang-rewrite): self-contained completion code * Add `asdf completion` command * Move completion files to `cli/completions` * Add completions for Bash, Zsh, Fish, Elvish, and Nushell * Update Zsh completion code to work with new completion install method --- cli/cli.go | 49 ++++ {completions => cli/completions}/asdf.bash | 0 cli/completions/asdf.elv | 148 +++++++++++ {completions => cli/completions}/asdf.fish | 0 cli/completions/asdf.nu | 250 ++++++++++++++++++ completions/_asdf => cli/completions/asdf.zsh | 5 + scripts/lint.bash | 6 +- 7 files changed, 455 insertions(+), 3 deletions(-) rename {completions => cli/completions}/asdf.bash (100%) create mode 100644 cli/completions/asdf.elv rename {completions => cli/completions}/asdf.fish (100%) create mode 100644 cli/completions/asdf.nu rename completions/_asdf => cli/completions/asdf.zsh (99%) diff --git a/cli/cli.go b/cli/cli.go index 3195a45c..0a2ccb91 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -2,6 +2,7 @@ package cli import ( + _ "embed" "errors" "fmt" "io" @@ -71,6 +72,13 @@ func Execute(version string) { return extensionCommand(logger, args) }, }, + { + Name: "completion", + Action: func(cCtx *cli.Context) error { + shell := cCtx.Args().Get(0) + return completionCommand(logger, shell) + }, + }, { Name: "current", Flags: []cli.Flag{ @@ -307,6 +315,47 @@ func Execute(version string) { } } +//go:embed completions/asdf.bash +var bashCompletions string + +//go:embed completions/asdf.zsh +var zshCompletions string + +//go:embed completions/asdf.fish +var fishCompletions string + +//go:embed completions/asdf.nu +var nuCompletions string + +//go:embed completions/asdf.elv +var elvishCompletions string + +func completionCommand(l *log.Logger, shell string) error { + switch shell { + case "bash": + fmt.Print(bashCompletions) + return nil + case "zsh": + fmt.Print(zshCompletions) + return nil + case "fish": + fmt.Print(fishCompletions) + return nil + case "nushell": + fmt.Print(nuCompletions) + return nil + case "elvish": + fmt.Print(elvishCompletions) + return nil + default: + fmtString := `No completions available for shell with name %s +Completions are available for: bash, zsh, fish, nushell, elvish` + msg := fmt.Sprintf(fmtString, shell) + l.Print(msg) + return errors.New(msg) + } +} + // This function is a whole mess and needs to be refactored func currentCommand(logger *log.Logger, tool string, noHeader bool) error { conf, err := config.LoadConfig() diff --git a/completions/asdf.bash b/cli/completions/asdf.bash similarity index 100% rename from completions/asdf.bash rename to cli/completions/asdf.bash diff --git a/cli/completions/asdf.elv b/cli/completions/asdf.elv new file mode 100644 index 00000000..e145ad28 --- /dev/null +++ b/cli/completions/asdf.elv @@ -0,0 +1,148 @@ +# Setup argument completions +fn arg-completer {|@argz| + set argz = $argz[1..-1] # strip 'asdf' and trailing empty string + var num = (count $argz) + if (== $num 0) { + # list all subcommands + find $asdf_dir'/lib/commands' -name 'command-*' | each {|cmd| + put (re:replace '.*/command-(.*)\.bash' '${1}' $cmd) + } + put 'plugin' + } else { + if (match $argz 'current') { + # asdf current + asdf plugin-list + } elif (match $argz 'env') { + # asdf env + ls-shims + } elif (match $argz 'env' '.*') { + # asdf env [util] + ls-executables + } elif (match $argz 'exec') { + # asdf exec + ls-shims + } elif (match $argz 'global') { + # asdf global + asdf plugin-list + } elif (match $argz 'global' '.*') { + # asdf global + ls-installed-versions $argz[-1] + } elif (match $argz 'install') { + # asdf install + asdf plugin-list + } elif (match $argz 'install' '.*') { + # asdf install + ls-all-versions $argz[-1] + } elif (match $argz 'install' '.*' '.*') { + # asdf install [--keep-download] + put '--keep-download' + } elif (match $argz 'latest') { + # asdf latest + asdf plugin-list + } elif (match $argz 'latest' '.*') { + # asdf latest [] + ls-all-versions $argz[-1] + } elif (match $argz 'list-all') { + # asdf list all + asdf plugin-list + } elif (match $argz 'list-all' '.*') { + # asdf list all [] + ls-all-versions $argz[-1] + } elif (match $argz 'list') { + # asdf list + asdf plugin-list + } elif (match $argz 'list' '.*') { + # asdf list [] + ls-installed-versions $argz[-1] + } elif (match $argz 'local') { + # asdf local [-p|--parent] + asdf plugin-list + put '-p' + put '--parent' + } elif (match $argz 'local' '(-p|(--parent))') { + # asdf local [-p|--parent] + asdf plugin-list + } elif (match $argz 'local' '.*') { + # asdf local [-p|--parent] + # asdf local + ls-installed-versions $argz[-1] + put '-p' + put '--parent' + } elif (match $argz 'local' '(-p|(--parent))' '.*') { + # asdf local [-p|--parent] + ls-installed-versions $argz[-1] + } elif (match $argz 'local' '.*' '(-p|(--parent))') { + # asdf local [-p|--parent] + ls-installed-versions $argz[-2] + } elif (match $argz 'local' '.*' '.*') { + # asdf local [-p|--parent] + put '-p' + put '--parent' + } elif (or (match $argz 'plugin-add') (match $argz 'plugin' 'add')) { + # asdf plugin add + asdf plugin-list-all | each {|line| + put (re:replace '([^\s]+)\s+.*' '${1}' $line) + } + } elif (or (match $argz 'plugin-list') (match $argz 'plugin' 'list')) { + # asdf plugin list + put '--urls' + put '--refs' + put 'all' + } elif (or (match $argz 'plugin-push') (match $argz 'plugin' 'push')) { + # asdf plugin push + asdf plugin-list + } elif (or (match $argz 'plugin-remove') (match $argz 'plugin' 'remove')) { + # asdf plugin remove + asdf plugin-list + } elif (and (>= (count $argz) 3) (match $argz[..3] 'plugin-test' '.*' '.*')) { + # asdf plugin-test [--asdf-tool-version ] [--asdf-plugin-gitref ] [test-command*] + put '--asdf-plugin-gitref' + put '--asdf-tool-version' + ls-executables + ls-shims + } elif (and (>= (count $argz) 4) (match $argz[..4] 'plugin' 'test' '.*' '.*')) { + # asdf plugin test [--asdf-tool-version ] [--asdf-plugin-gitref ] [test-command*] + put '--asdf-plugin-gitref' + put '--asdf-tool-version' + ls-executables + ls-shims + } elif (or (match $argz 'plugin-update') (match $argz 'plugin' 'update')) { + # asdf plugin update + asdf plugin-list + put '--all' + } elif (match $argz 'plugin') { + # list plugin-* subcommands + find $asdf_dir'/lib/commands' -name 'command-plugin-*' | each {|cmd| + put (re:replace '.*/command-plugin-(.*)\.bash' '${1}' $cmd) + } + } elif (match $argz 'reshim') { + # asdf reshim + asdf plugin-list + } elif (match $argz 'reshim' '.*') { + # asdf reshim + ls-installed-versions $argz[-1] + } elif (match $argz 'shim-versions') { + # asdf shim-versions + ls-shims + } elif (match $argz 'uninstall') { + # asdf uninstall + asdf plugin-list + } elif (match $argz 'uninstall' '.*') { + # asdf uninstall + ls-installed-versions $argz[-1] + } elif (match $argz 'update') { + if (== $num 1) { + # asdf update + put '--head' + } + } elif (match $argz 'where') { + # asdf where + asdf plugin-list + } elif (match $argz 'where' '.*') { + # asdf where [] + ls-installed-versions $argz[-1] + } elif (match $argz 'which') { + ls-shims + } + } +} diff --git a/completions/asdf.fish b/cli/completions/asdf.fish similarity index 100% rename from completions/asdf.fish rename to cli/completions/asdf.fish diff --git a/cli/completions/asdf.nu b/cli/completions/asdf.nu new file mode 100644 index 00000000..63e48286 --- /dev/null +++ b/cli/completions/asdf.nu @@ -0,0 +1,250 @@ +module asdf { + + def "complete asdf sub-commands" [] { + [ + "plugin", + "list", + "install", + "uninstall", + "current", + "where", + "which", + "local", + "global", + "shell", + "latest", + "help", + "exec", + "env", + "info", + "reshim", + "shim-version", + "update" + ] + } + + def "complete asdf installed" [] { + ^asdf plugin list | lines | each { |line| $line | str trim } + } + + + def "complete asdf plugin sub-commands" [] { + [ + "list", + "list all", + "add", + "remove", + "update" + ] + } + + def "complete asdf installed plugins" [] { + ^asdf plugin list | lines | each { |line| + $line | str trim + } + } + + def "complete asdf plugin versions all" [context: string] { + let plugin = $context | str trim | split words | last + ^asdf list all $plugin + | lines + | each { |line| $line | str trim } + | prepend "latest" + } + + def "complete asdf plugin versions installed" [context: string] { + let plugin = $context | str trim | split words | last + let versions = ^asdf list $plugin + | lines + | each { |line| $line | str trim } + | each { |version| if ($version | str starts-with "*") {{value: ($version | str substring 1..), description: "current version"}} else {{value: $version, description: ""}} } + + let latest = ^asdf latest $plugin | str trim + + if ($versions | get value | any {|el| $el == $latest}) { + $versions | prepend {value: "latest", description: $"alias to ($latest)"} + } else { + $versions + } + } + + # ASDF version manager + export extern main [ + subcommand?: string@"complete asdf sub-commands" + ] + + # Manage plugins + export extern "asdf plugin" [ + subcommand?: string@"complete asdf plugin sub-commands" + ] + + # List installed plugins + export def "asdf plugin list" [ + --urls # Show urls + --refs # Show refs + ] { + + let params = [ + {name: 'urls', enabled: $urls, flag: '--urls', + template: '\s+?(?P(?:http[s]?|git).+\.git|/.+)'} + {name: 'refs', enabled: $refs, flag: '--refs', + template: '\s+?(?P\w+)\s+(?P\w+)'} + ] + + let template = '(?P.+)' + ( + $params | + where enabled | + get --ignore-errors template | + str join '' | + str trim + ) + + let flags = ($params | where enabled | get --ignore-errors flag | default '' ) + + ^asdf plugin list ...$flags | lines | parse -r $template | str trim + } + + # list all available plugins + export def "asdf plugin list all" [] { + let template = '(?P.+)\s+?(?P[*]?)(?P(?:git|http|https).+)' + let is_installed = { |it| $it.installed == '*' } + + ^asdf plugin list all | + lines | + parse -r $template | + str trim | + update installed $is_installed | + sort-by name + } + + # Add a plugin + export extern "asdf plugin add" [ + name: string # Name of the plugin + git_url?: string # Git url of the plugin + ] + + # Remove an installed plugin and their package versions + export extern "asdf plugin remove" [ + name: string@"complete asdf installed plugins" # Name of the plugin + ] + + # Update a plugin + export extern "asdf plugin update" [ + name: string@"complete asdf installed plugins" # Name of the plugin + git_ref?: string # Git ref to update the plugin + ] + + # Update all plugins to the latest commit + export extern "asdf plugin update --all" [] + + # install a package version + export extern "asdf install" [ + name?: string@"complete asdf installed plugins" # Name of the package + version?: string@"complete asdf plugin versions all" # Version of the package or latest + ] + + + # Remove an installed package version + export extern "asdf uninstall" [ + name: string@"complete asdf installed" # Name of the package + version: string@"complete asdf plugin versions installed" # Version of the package + ] + + # Display current version + export extern "asdf current" [ + name?: string@"complete asdf installed" # Name of installed version of a package + ] + + # Display path of an executable + export extern "asdf which" [ + command: string # Name of command + ] + + # Display install path for an installled package version + export extern "asdf where" [ + name: string@"complete asdf installed" # Name of installed package + version?: string@"complete asdf plugin versions installed" # Version of installed package + ] + + # Set the package local version + export extern "asdf local" [ + name: string@"complete asdf installed" # Name of the package + version?: string@"complete asdf plugin versions installed" # Version of the package or latest + ] + + # Set the package global version + export extern "asdf global" [ + name: string@"complete asdf installed" # Name of the package + version?: string@"complete asdf plugin versions installed" # Version of the package or latest + ] + + # Set the package to version in the current shell + export extern "asdf shell" [ + name: string@"complete asdf installed" # Name of the package + version?: string@"complete asdf plugin versions installed" # Version of the package or latest + ] + + # Show latest stable version of a package + export extern "asdf latest" [ + name: string@"complete asdf installed" # Name of the package + version?: string@"complete asdf plugin versions installed" # Filter latest stable version from this version + ] + + # Show latest stable version for all installed packages + export extern "asdf latest --all" [] + + # List installed package versions + export extern "asdf list" [ + name?: string@"complete asdf installed" # Name of the package + version?: string@"complete asdf plugin versions installed" # Filter the version + ] + + # List all available package versions + export def "asdf list all" [ + name: string@"complete asdf installed" # Name of the package + version?: string@"complete asdf plugin versions installed"="" # Filter the version + ] { + ^asdf list all $name $version | lines | parse "{version}" | str trim + } + + # Show documentation for plugin + export extern "asdf help" [ + name: string@"complete asdf installed" # Name of the plugin + version?: string@"complete asdf plugin versions installed" # Version of the plugin + ] + + # Execute a command shim for the current version + export extern "asdf exec" [ + command: string # Name of the command + ...args: any # Arguments to pass to the command + ] + + # Run util (default: env) inside the environment used for command shim execution + export extern "asdf env" [ + command?: string # Name of the command + util?: string = 'env' # Name of util to run + ] + + # Show information about OS, Shell and asdf Debug + export extern "asdf info" [] + + # Recreate shims for version package + export extern "asdf reshim" [ + name?: string@"complete asdf installed" # Name of the package + version?: string@"complete asdf plugin versions installed" # Version of the package + ] + + # List the plugins and versions that provide a command + export extern "asdf shim-version" [ + command: string # Name of the command + ] + + # Update asdf to the latest version on the stable branch + export extern "asdf update" [] + + # Update asdf to the latest version on the main branch + export extern "asdf update --head" [] + +} + +use asdf * diff --git a/completions/_asdf b/cli/completions/asdf.zsh similarity index 99% rename from completions/_asdf rename to cli/completions/asdf.zsh index f6573354..c88accef 100755 --- a/completions/_asdf +++ b/cli/completions/asdf.zsh @@ -1,4 +1,5 @@ #compdef asdf +compdef _asdf asdf #description tool to manage versions of multiple runtimes local curcontext="$curcontext" state state_descr line subcmd @@ -90,6 +91,7 @@ _asdf__installed_versions_of_plus_system() { compadd -a versions } +_asdf() { local -i IntermediateCount=0 @@ -117,6 +119,8 @@ _asdf__dash_commands() { subcmd="${subcmd}-${words[2+IntermediateCount]}" fi } + + case "$subcmd" in (plugin|shim|list) _asdf__dash_commands @@ -220,3 +224,4 @@ case "$subcmd" in (( CURRENT == 3 )) && compadd -- --head ;; esac +} diff --git a/scripts/lint.bash b/scripts/lint.bash index 6ebfe809..904fbcda 100755 --- a/scripts/lint.bash +++ b/scripts/lint.bash @@ -31,7 +31,7 @@ run_shfmt_stylecheck() { print.info "Checking .bash with shfmt" shfmt --language-dialect bash --indent 2 "${shfmt_flag}" \ - completions/*.bash \ + cli/completions/*.bash \ bin/asdf \ bin/private/asdf-exec \ lib/utils.bash \ @@ -55,7 +55,7 @@ run_shellcheck_linter() { print.info "Checking .bash files with Shellcheck" shellcheck --shell bash --external-sources \ - completions/*.bash \ + cli/completions/*.bash \ bin/asdf \ bin/private/asdf-exec \ lib/utils.bash \ @@ -123,7 +123,7 @@ run_fish_linter() { printf "%s\n" "[WARNING] fish_indent not found. Skipping .fish files." else print.info "Checking .fish files with fish_indent" - fish_indent "${flag}" ./**/*.fish + fish_indent "${flag}" ./cli/completions/asdf.fish fi }