pax_global_header00006660000000000000000000000064145251706720014523gustar00rootroot0000000000000052 comment=b59327a44e1018402798b4cca269d90de20b2831 kongplete-0.4.0/000077500000000000000000000000001452517067200135145ustar00rootroot00000000000000kongplete-0.4.0/.bindown.yaml000066400000000000000000000361121452517067200161210ustar00rootroot00000000000000systems: - darwin/amd64 - darwin/arm64 - linux/amd64 - windows/amd64 dependencies: gh: template: origin#gh vars: version: 2.29.0 gofumpt: template: origin#gofumpt vars: version: 0.5.0 golangci-lint: template: origin#golangci-lint vars: version: 1.54.2 goreleaser: template: origin#goreleaser vars: version: 1.18.2 handcrafted: template: origin#handcrafted vars: version: 0.0.0 jq: template: origin#jq vars: version: "1.6" semver-next: template: origin#semver-next vars: version: 2.0.0 semver-prev: template: origin#semver-prev vars: version: 0.0.1 shellcheck: template: origin#shellcheck vars: version: 0.9.0 shfmt: template: origin#shfmt vars: version: 3.6.0 templates: origin#gh: homepage: https://github.com/cli/cli description: GitHub’s official command line tool url: https://github.com/cli/cli/releases/download/v{{.version}}/gh_{{.version}}_{{.os}}_{{.arch}}{{.urlSuffix}} archive_path: gh_{{.version}}_{{.os}}_{{.arch}}/bin/gh{{.archivePathSuffix}} bin: gh vars: archivePathSuffix: "" urlSuffix: .zip overrides: - matcher: os: - windows dependency: archive_path: bin/gh{{.archivePathSuffix}} vars: archivePathSuffix: .exe - matcher: os: - linux dependency: vars: urlSuffix: .tar.gz - matcher: os: - darwin version: - < 2.28.0 dependency: vars: urlSuffix: .tar.gz substitutions: os: darwin: macOS systems: - darwin/amd64 - darwin/arm64 - linux/386 - linux/amd64 - linux/arm64 - windows/386 - windows/amd64 - windows/arm64 required_vars: - version origin#gofumpt: url: https://github.com/mvdan/gofumpt/releases/download/v{{.version}}/gofumpt_v{{.version}}_{{.os}}_{{.arch}}{{.suffix}} archive_path: gofumpt_v{{.version}}_{{.os}}_{{.arch}}{{.suffix}} bin: gofumpt{{.suffix}} vars: suffix: "" overrides: - matcher: os: - windows dependency: vars: suffix: .exe systems: - darwin/amd64 - darwin/arm64 - linux/386 - linux/amd64 - linux/arm - linux/arm64 - windows/386 - windows/amd64 required_vars: - version origin#golangci-lint: url: https://github.com/golangci/golangci-lint/releases/download/v{{.version}}/golangci-lint-{{.version}}-{{.os}}-{{.arch}}{{.urlsuffix}} archive_path: golangci-lint-{{.version}}-{{.os}}-{{.arch}}/golangci-lint{{.archivepathsuffix}} bin: golangci-lint link: true vars: archivepathsuffix: "" urlsuffix: .tar.gz overrides: - matcher: os: - windows dependency: vars: archivepathsuffix: .exe urlsuffix: .zip systems: - darwin/amd64 - darwin/arm64 - linux/386 - linux/amd64 - linux/arm64 - windows/386 - windows/amd64 - freebsd/386 - freebsd/amd64 - linux/mips64 - linux/mips64le - linux/s390x - linux/ppc64le required_vars: - version origin#goreleaser: homepage: https://github.com/goreleaser/goreleaser description: Deliver Go binaries as fast and easily as possible url: https://github.com/goreleaser/goreleaser/releases/download/v{{.version}}/goreleaser_{{.os}}_{{.arch}}{{.urlSuffix}} archive_path: goreleaser{{.archivePathSuffix}} bin: goreleaser vars: archivePathSuffix: "" urlSuffix: .tar.gz overrides: - matcher: os: - windows dependency: vars: archivePathSuffix: .exe urlSuffix: .zip substitutions: arch: "386": i386 amd64: x86_64 os: windows: Windows substitutions: arch: "386": i386 amd64: x86_64 os: darwin: Darwin linux: Linux systems: - darwin/amd64 - darwin/arm64 - linux/386 - linux/amd64 - linux/arm64 - linux/ppc64 - windows/386 - windows/amd64 - windows/arm64 required_vars: - version origin#handcrafted: homepage: https://github.com/willabides/handcrafted description: lists non-generated go files in a package url: https://github.com/WillAbides/handcrafted/releases/download/v{{.version}}/handcrafted_{{.version}}_{{.os}}_{{.arch}}{{.urlSuffix}} archive_path: handcrafted{{.archivePathSuffix}} bin: handcrafted vars: archivePathSuffix: "" urlSuffix: .tar.gz overrides: - matcher: os: - windows dependency: vars: archivePathSuffix: .exe systems: - darwin/amd64 - darwin/arm64 - linux/386 - linux/amd64 - linux/arm64 - windows/386 - windows/amd64 - windows/arm64 required_vars: - version origin#jq: homepage: https://github.com/stedolan/jq description: Command-line JSON processor url: https://github.com/stedolan/jq/releases/download/jq-{{.version}}/jq-{{.os}}{{.arch}}{{.extension}} archive_path: jq-{{.os}}{{.arch}}{{.extension}} bin: jq vars: extension: "" overrides: - matcher: arch: - amd64 - arm64 os: - darwin dependency: url: https://github.com/stedolan/jq/releases/download/jq-1.6/jq-osx-amd64 archive_path: jq-osx-amd64 - matcher: os: - windows dependency: vars: extension: .exe substitutions: arch: "386": "32" amd64: "64" os: windows: win systems: - linux/386 - linux/amd64 - darwin/amd64 - darwin/arm64 - windows/386 - windows/amd64 required_vars: - version origin#semver-next: homepage: https://github.com/WillAbides/semver-next url: https://github.com/WillAbides/semver-next/releases/download/v{{.version}}/semver-next_{{.version}}_{{.os}}_{{.arch}}{{.urlSuffix}} archive_path: semver-next{{.archivePathSuffix}} bin: semver-next vars: archivePathSuffix: "" urlSuffix: .tar.gz overrides: - matcher: os: - windows dependency: vars: archivePathSuffix: .exe systems: - darwin/amd64 - darwin/arm64 - linux/386 - linux/amd64 - linux/arm64 - windows/386 - windows/amd64 - windows/arm64 required_vars: - version origin#semver-prev: homepage: https://github.com/willabides/semver-prev url: https://github.com/WillAbides/semver-prev/releases/download/v{{.version}}/semver-prev_{{.version}}_{{.os}}_{{.arch}}{{.urlSuffix}} archive_path: semver-prev{{.archivePathSuffix}} bin: semver-prev vars: archivePathSuffix: "" urlSuffix: .tar.gz overrides: - matcher: os: - windows dependency: vars: archivePathSuffix: .exe systems: - darwin/amd64 - darwin/arm64 - linux/386 - linux/amd64 - linux/arm64 - windows/386 - windows/amd64 - windows/arm64 required_vars: - version origin#shellcheck: url: https://github.com/koalaman/shellcheck/releases/download/v{{.version}}/shellcheck-v{{.version}}.{{.os}}.{{.arch}}.tar.xz archive_path: shellcheck-v{{.version}}/shellcheck bin: shellcheck overrides: - matcher: os: - windows dependency: url: https://github.com/koalaman/shellcheck/releases/download/v{{.version}}/shellcheck-v{{.version}}.zip archive_path: shellcheck.exe - matcher: arch: - arm64 os: - darwin dependency: vars: arch: amd64 substitutions: arch: amd64: x86_64 systems: - darwin/amd64 - darwin/arm64 - linux/amd64 - windows/amd64 required_vars: - version origin#shfmt: homepage: https://github.com/mvdan/sh description: A shell parser, formatter, and interpreter with bash support; includes shfmt url: https://github.com/mvdan/sh/releases/download/v{{.version}}/shfmt_v{{.version}}_{{.os}}_{{.arch}}{{.urlSuffix}} archive_path: shfmt_v{{.version}}_{{.os}}_{{.arch}}{{.urlSuffix}} bin: shfmt vars: archivePathSuffix: "" urlSuffix: "" overrides: - matcher: os: - windows dependency: vars: urlSuffix: .exe systems: - darwin/amd64 - darwin/arm64 - linux/386 - linux/amd64 - linux/arm64 - windows/386 - windows/amd64 required_vars: - version template_sources: origin: https://raw.githubusercontent.com/WillAbides/bindown-templates/master/bindown.yml url_checksums: https://github.com/WillAbides/handcrafted/releases/download/v0.0.0/handcrafted_0.0.0_darwin_amd64.tar.gz: df5dbf9c8b282d8209a8baddfe3410c5b3ace87bdce808fce0a0d49356c9ff4d https://github.com/WillAbides/handcrafted/releases/download/v0.0.0/handcrafted_0.0.0_darwin_arm64.tar.gz: c03133084f87e064f9801d4b2a9739be755fcee5875382f4da0fc10cd8306dfb https://github.com/WillAbides/handcrafted/releases/download/v0.0.0/handcrafted_0.0.0_linux_amd64.tar.gz: 1a7885a9854d2455dce1be3bc19f2d61a61ebdc99e2a98e4969ab1965c2a64ad https://github.com/WillAbides/handcrafted/releases/download/v0.0.0/handcrafted_0.0.0_windows_amd64.tar.gz: 5ce8cddc9bdbd19adde3104397d698ecca7eb8ad2ac540cc709a15821f9b2609 https://github.com/WillAbides/semver-next/releases/download/v2.0.0/semver-next_2.0.0_darwin_amd64.tar.gz: 2bb0e3a6fda58689c7c983c33f131b44298484ea42c95455f711b16e3a93d033 https://github.com/WillAbides/semver-next/releases/download/v2.0.0/semver-next_2.0.0_darwin_arm64.tar.gz: d86b2a7e0cb21cd56b9168913994b4470be2c1a0d724d91c85d91aa3e8c63755 https://github.com/WillAbides/semver-next/releases/download/v2.0.0/semver-next_2.0.0_linux_amd64.tar.gz: 336beb1eb6ce340a6dfbb61b7f3ffd8af821414112479a38f9b10db03dc7a6ea https://github.com/WillAbides/semver-next/releases/download/v2.0.0/semver-next_2.0.0_windows_amd64.tar.gz: 2991941f5ab181a426789a8c3a33d999086ed21dba8a8ae7690f59b3c2dbb274 https://github.com/WillAbides/semver-prev/releases/download/v0.0.1/semver-prev_0.0.1_darwin_amd64.tar.gz: de2902df5f99e6db91b143199446ccf9c7bdacd37980fca55f71e2da35211d0f https://github.com/WillAbides/semver-prev/releases/download/v0.0.1/semver-prev_0.0.1_darwin_arm64.tar.gz: 367eaccc368bec6119d47cc78d0ad743cc8709dc1900e8f170c5314418c3a14e https://github.com/WillAbides/semver-prev/releases/download/v0.0.1/semver-prev_0.0.1_linux_amd64.tar.gz: 1c1ab5926e9548659bdc1786cf1da9f62f80ae5314c5ccdf71123767e4f4da1d https://github.com/WillAbides/semver-prev/releases/download/v0.0.1/semver-prev_0.0.1_windows_amd64.tar.gz: 31bc3527ad74cdfda9d5868287141f44e72e8fbd1709c4f6af05205f9eb8bb8b https://github.com/cli/cli/releases/download/v2.29.0/gh_2.29.0_linux_amd64.tar.gz: 9fe05f43a11a7bf8eacf731422452d1997e6708d4160ef0efcb13c103320390e https://github.com/cli/cli/releases/download/v2.29.0/gh_2.29.0_macOS_amd64.zip: e116d0f9c310450482cdcd7f4d2d1c7c4cab8d4f025a340260ce3f15329c5145 https://github.com/cli/cli/releases/download/v2.29.0/gh_2.29.0_macOS_arm64.zip: 38ca9a355376abd1475362cf8b3cacf2a757198fe5fe70349cb1767666abacc6 https://github.com/cli/cli/releases/download/v2.29.0/gh_2.29.0_windows_amd64.zip: 031eb343ebff6f8cc712d58d79267ee00b0c61b37d6698927161daae895044c6 https://github.com/golangci/golangci-lint/releases/download/v1.54.2/golangci-lint-1.54.2-darwin-amd64.tar.gz: 925c4097eae9e035b0b052a66d0a149f861e2ab611a4e677c7ffd2d4e05b9b89 https://github.com/golangci/golangci-lint/releases/download/v1.54.2/golangci-lint-1.54.2-darwin-arm64.tar.gz: 7b33fb1be2f26b7e3d1f3c10ce9b2b5ce6d13bb1d8468a4b2ba794f05b4445e1 https://github.com/golangci/golangci-lint/releases/download/v1.54.2/golangci-lint-1.54.2-linux-amd64.tar.gz: 17c9ca05253efe833d47f38caf670aad2202b5e6515879a99873fabd4c7452b3 https://github.com/golangci/golangci-lint/releases/download/v1.54.2/golangci-lint-1.54.2-windows-amd64.zip: ce17d122f3f93e0a9e52009d2c03cc1c1a1ae28338c2702a1f53eccd10a1afa3 https://github.com/goreleaser/goreleaser/releases/download/v1.18.2/goreleaser_Darwin_arm64.tar.gz: 7eec9f4d0b86b2c9c9f6af1770a11315998bd4d4617633b0a73eeb036e97393e https://github.com/goreleaser/goreleaser/releases/download/v1.18.2/goreleaser_Darwin_x86_64.tar.gz: 95338eed333347152e23837b68a8c6ce0c62b9f5abb68bd5b4b08178766400b9 https://github.com/goreleaser/goreleaser/releases/download/v1.18.2/goreleaser_Linux_x86_64.tar.gz: 811e0c63e347f78f3c8612a19ca8eeb564eb45f0265ce3f38aec39c8fdbcfa10 https://github.com/goreleaser/goreleaser/releases/download/v1.18.2/goreleaser_Windows_x86_64.zip: 4b67f9a0159dc4f6a19fdea46eda506d58efe9e9d01aebc6ee39c9e9c14f9715 https://github.com/koalaman/shellcheck/releases/download/v0.9.0/shellcheck-v0.9.0.darwin.x86_64.tar.xz: 7d3730694707605d6e60cec4efcb79a0632d61babc035aa16cda1b897536acf5 https://github.com/koalaman/shellcheck/releases/download/v0.9.0/shellcheck-v0.9.0.linux.x86_64.tar.xz: 700324c6dd0ebea0117591c6cc9d7350d9c7c5c287acbad7630fa17b1d4d9e2f https://github.com/koalaman/shellcheck/releases/download/v0.9.0/shellcheck-v0.9.0.zip: ae58191b1ea4ffd9e5b15da9134146e636440302ce3e2f46863e8d71c8be1bbb https://github.com/mvdan/gofumpt/releases/download/v0.5.0/gofumpt_v0.5.0_darwin_amd64: 870f05a23541aad3d20d208a3ea17606169a240f608ac1cf987426198c14b2ed https://github.com/mvdan/gofumpt/releases/download/v0.5.0/gofumpt_v0.5.0_darwin_arm64: f2df95d5fad8498ad8eeb0be8abdb8bb8d05e8130b332cb69751dfd090fabac4 https://github.com/mvdan/gofumpt/releases/download/v0.5.0/gofumpt_v0.5.0_linux_amd64: 759c6ab56bfbf62cafb35944aef1e0104a117e0aebfe44816fd79ef4b28521e4 https://github.com/mvdan/gofumpt/releases/download/v0.5.0/gofumpt_v0.5.0_windows_amd64.exe: c9ca0a8a95c2ead0a009a349d5a326e385f5f15a96b084e11c4a7c1cb86b694b https://github.com/mvdan/sh/releases/download/v3.6.0/shfmt_v3.6.0_darwin_amd64: b8c9c025b498e2816b62f0b717f6032e9ab49e725a45b8205f52f66318f17185 https://github.com/mvdan/sh/releases/download/v3.6.0/shfmt_v3.6.0_darwin_arm64: 633f242246ee0a866c5f5df25cbf61b6af0d5e143555aca32950059cf13d91e0 https://github.com/mvdan/sh/releases/download/v3.6.0/shfmt_v3.6.0_linux_amd64: 5741a02a641de7e56b8da170e71a97e58050d66a3cf485fb268d6a5a8bb74afb https://github.com/mvdan/sh/releases/download/v3.6.0/shfmt_v3.6.0_windows_amd64.exe: 18122d910ba434be366588f37c302c309cde4ca5403f93285254a3cf96839d01 https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64: af986793a515d500ab2d35f8d2aecd656e764504b789b66d7e1a0b727a124c44 https://github.com/stedolan/jq/releases/download/jq-1.6/jq-osx-amd64: 5c0a0a3ea600f302ee458b30317425dd9632d1ad8882259fcaf4e9b868b2b1ef https://github.com/stedolan/jq/releases/download/jq-1.6/jq-win64.exe: a51d36968dcbdeabb3142c6f5cf9b401a65dc3a095f3144bd0c118d5bb192753 kongplete-0.4.0/.github/000077500000000000000000000000001452517067200150545ustar00rootroot00000000000000kongplete-0.4.0/.github/workflows/000077500000000000000000000000001452517067200171115ustar00rootroot00000000000000kongplete-0.4.0/.github/workflows/ci.yml000066400000000000000000000011541452517067200202300ustar00rootroot00000000000000name: ci on: [ push, workflow_dispatch ] jobs: cibuild: name: cibuild runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 - uses: WillAbides/setup-go-faster@v1 id: setup-go with: go-version: '1.21.x' - uses: actions/cache@v2 with: path: | ${{ steps.setup-go.outputs.GOCACHE }} ${{ steps.setup-go.outputs.GOMODCACHE }} key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: ${{ runner.os }}-go- - run: script/generate --check - run: script/test - run: script/lint kongplete-0.4.0/.gitignore000066400000000000000000000000271452517067200155030ustar00rootroot00000000000000/bin/ /tmp/ /.bindown/ kongplete-0.4.0/.golangci.yml000066400000000000000000000017541452517067200161070ustar00rootroot00000000000000# configure golangci-lint # see https://github.com/golangci/golangci-lint/blob/master/.golangci.example.yml issues: exclude-use-default: false exclude-rules: - path: _test\.go linters: - dupl - gosec - goconst - text: "hugeParam: a is heavy" linters: - gocritic linters: enable: - gosec - unconvert - gocyclo - goconst - goimports - gocritic - gofumpt - revive linters-settings: gocritic: enabled-tags: - style - diagnostic - performance errcheck: # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`; # default is false: such cases aren't reported by default. check-blank: true govet: # report about shadowed variables check-shadowing: true maligned: # print struct with more effective memory layout or not, false by default suggest-new: true revive: rules: - name: package-comments disabled: true kongplete-0.4.0/LICENSE000066400000000000000000000020531452517067200145210ustar00rootroot00000000000000MIT License Copyright (c) 2020 WillAbides Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. kongplete-0.4.0/README.md000066400000000000000000000034631452517067200150010ustar00rootroot00000000000000# kongplete You kongplete me. kongplete lets you generate shell completions for your command-line programs using [github.com/alecthomas/kong](https://github.com/alecthomas/kong) and [github.com/posener/complete](https://github.com/posener/complete). ## Examples ```golang // This example is adapted from the shell example in github.com/alecthomas/kong package main import ( "fmt" "os" "github.com/alecthomas/kong" "github.com/posener/complete" "github.com/willabides/kongplete" ) var shellCli struct { Rm struct { User string `help:"Run as user." short:"u" default:"default"` Force bool `help:"Force removal." short:"f"` Recursive bool `help:"Recursively remove files." short:"r"` Hidden string `help:"A hidden flag" hidden:""` Paths []string `arg:"" help:"Paths to remove." type:"path" name:"path" predictor:"file"` } `cmd:"" help:"Remove files."` Ls struct { Paths []string `arg:"" optional:"" help:"Paths to list." type:"path" predictor:"file"` } `cmd:"" help:"List paths."` Hidden struct{} `cmd:"" help:"A hidden command" hidden:""` Debug bool `help:"Debug mode."` InstallCompletions kongplete.InstallCompletions `cmd:"" help:"install shell completions"` } func main() { // Create a kong parser as usual, but don't run Parse quite yet. parser := kong.Must(&shellCli, kong.Name("shell"), kong.Description("A shell-like example app."), kong.UsageOnError(), ) // Run kongplete.Complete to handle completion requests kongplete.Complete(parser, kongplete.WithPredictor("file", complete.PredictFiles("*")), ) // Proceed as normal after kongplete.Complete. ctx, err := parser.Parse(os.Args[1:]) parser.FatalIfErrorf(err) switch ctx.Command() { case "rm ": fmt.Println(shellCli.Rm.Paths, shellCli.Rm.Force, shellCli.Rm.Recursive) case "ls", "hidden": } } ``` kongplete-0.4.0/completion_install.go000066400000000000000000000036051452517067200177460ustar00rootroot00000000000000package kongplete import ( "fmt" "io" "os" "path/filepath" "github.com/alecthomas/kong" "github.com/riywo/loginshell" ) // InstallCompletions is a kong command for installing or uninstalling shell completions type InstallCompletions struct { Uninstall bool } // BeforeApply installs completion into the users shell. func (c *InstallCompletions) BeforeApply(ctx *kong.Context) error { err := installCompletionFromContext(ctx) if err != nil { return err } ctx.Exit(0) return nil } var shellInstall = map[string]string{ "bash": "complete -C ${bin} ${cmd}\n", "zsh": `autoload -U +X bashcompinit && bashcompinit complete -C ${bin} ${cmd} `, "fish": `function __complete_${cmd} set -lx COMP_LINE (commandline -cp) test -z (commandline -ct) and set COMP_LINE "$COMP_LINE " ${bin} end complete -f -c ${cmd} -a "(__complete_${cmd})" `, } // installCompletionFromContext writes shell completion for the given command. func installCompletionFromContext(ctx *kong.Context) error { shell, err := loginshell.Shell() if err != nil { return fmt.Errorf("couldn't determine user's shell: %w", err) } bin, err := os.Executable() if err != nil { return fmt.Errorf("couldn't find absolute path to ourselves: %w", err) } bin, err = filepath.Abs(bin) if err != nil { return fmt.Errorf("couldn't find absolute path to ourselves: %w", err) } w := ctx.Stdout cmd := ctx.Model.Name return installCompletion(w, shell, cmd, bin) } // installCompletion writes shell completion for a command. func installCompletion(w io.Writer, shell, cmd, bin string) error { script, ok := shellInstall[filepath.Base(shell)] if !ok { return fmt.Errorf("unsupported shell %s", shell) } vars := map[string]string{"cmd": cmd, "bin": bin} fragment := os.Expand(script, func(s string) string { v, ok := vars[s] if !ok { return "$" + s } return v }) _, err := fmt.Fprint(w, fragment) return err } kongplete-0.4.0/completion_install_test.go000066400000000000000000000014121452517067200207770ustar00rootroot00000000000000package kongplete import ( "strings" "testing" "github.com/stretchr/testify/require" ) func TestInstallCompletion(t *testing.T) { tests := map[string]string{ "zsh": "autoload -U +X bashcompinit && bashcompinit\ncomplete -C /usr/bin/docker docker\n", "bash": "complete -C /usr/bin/docker docker\n", "fish": `function __complete_docker set -lx COMP_LINE (commandline -cp) test -z (commandline -ct) and set COMP_LINE "$COMP_LINE " /usr/bin/docker end complete -f -c docker -a "(__complete_docker)" `, } for shell, fragment := range tests { t.Run(shell, func(t *testing.T) { w := &strings.Builder{} err := installCompletion(w, shell, "docker", "/usr/bin/docker") require.NoError(t, err) require.Equal(t, fragment, w.String()) }) } } kongplete-0.4.0/doc.go000066400000000000000000000002571452517067200146140ustar00rootroot00000000000000/* Package kongplete lets you generate shell completions for your command-line programs using github.com/alecthomas/kong and github.com/posener/complete. */ package kongplete kongplete-0.4.0/example_test.go000066400000000000000000000030471452517067200165410ustar00rootroot00000000000000// This example is adapted from the shell example in github.com/alecthomas/kong package kongplete_test import ( "fmt" "os" "github.com/alecthomas/kong" "github.com/posener/complete" "github.com/willabides/kongplete" ) var shellCli struct { Rm struct { User string `help:"Run as user." short:"u" default:"default"` Force bool `help:"Force removal." short:"f"` Recursive bool `help:"Recursively remove files." short:"r"` Hidden string `help:"A hidden flag" hidden:""` Paths []string `arg:"" help:"Paths to remove." type:"path" name:"path" predictor:"file"` } `cmd:"" help:"Remove files."` Ls struct { Paths []string `arg:"" optional:"" help:"Paths to list." type:"path" predictor:"file"` } `cmd:"" help:"List paths."` Hidden struct{} `cmd:"" help:"A hidden command" hidden:""` Debug bool `help:"Debug mode."` InstallCompletions kongplete.InstallCompletions `cmd:"" help:"install shell completions"` } func Example() { // Create a kong parser as usual, but don't run Parse quite yet. parser := kong.Must(&shellCli, kong.Name("shell"), kong.Description("A shell-like example app."), kong.UsageOnError(), ) // Run kongplete.Complete to handle completion requests kongplete.Complete(parser, kongplete.WithPredictor("file", complete.PredictFiles("*")), ) // Proceed as normal after kongplete.Complete. ctx, err := parser.Parse(os.Args[1:]) parser.FatalIfErrorf(err) switch ctx.Command() { case "rm ": fmt.Println(shellCli.Rm.Paths, shellCli.Rm.Force, shellCli.Rm.Recursive) case "ls", "hidden": } } kongplete-0.4.0/go.mod000066400000000000000000000007431452517067200146260ustar00rootroot00000000000000module github.com/willabides/kongplete go 1.17 require ( github.com/alecthomas/kong v0.8.1 github.com/posener/complete v1.2.3 github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab github.com/stretchr/testify v1.8.4 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) kongplete-0.4.0/go.sum000066400000000000000000000064111452517067200146510ustar00rootroot00000000000000github.com/alecthomas/assert/v2 v2.1.0 h1:tbredtNcQnoSd3QBhQWI7QZ3XHOVkw1Moklp2ojoH/0= github.com/alecthomas/assert/v2 v2.1.0/go.mod h1:b/+1DI2Q6NckYi+3mXyH3wFb8qG37K/DuK80n7WefXA= github.com/alecthomas/kong v0.8.1 h1:acZdn3m4lLRobeh3Zi2S2EpnXTd1mOL6U7xVml+vfkY= github.com/alecthomas/kong v0.8.1/go.mod h1:n1iCIO2xS46oE8ZfYCNDqdR0b0wZNrXAIAqro/2132U= github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE= github.com/alecthomas/repr v0.1.0/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= 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/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab h1:ZjX6I48eZSFetPb41dHudEyVr5v953N15TsNZXlkcWY= github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab/go.mod h1:/PfPXh0EntGc3QAAyUaviy4S9tzy4Zp0e2ilq4voC6E= 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/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 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= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= kongplete-0.4.0/internal/000077500000000000000000000000001452517067200153305ustar00rootroot00000000000000kongplete-0.4.0/internal/positionalpredictor/000077500000000000000000000000001452517067200214255ustar00rootroot00000000000000kongplete-0.4.0/internal/positionalpredictor/positional.go000066400000000000000000000042211452517067200241340ustar00rootroot00000000000000package positionalpredictor import ( "strings" "github.com/posener/complete" ) // PositionalPredictor is a predictor for positional arguments type PositionalPredictor struct { Predictors []complete.Predictor ArgFlags []string BoolFlags []string IsCumulative bool } // Predict implements complete.Predict func (p *PositionalPredictor) Predict(a complete.Args) []string { predictor := p.predictor(a) if predictor == nil { return []string{} } return predictor.Predict(a) } func (p *PositionalPredictor) predictor(a complete.Args) complete.Predictor { position := p.predictorIndex(a) complete.Log("predicting positional argument(%d)", position) if p.IsCumulative && position >= len(p.Predictors) { return p.Predictors[len(p.Predictors)-1] } if position < 0 || position > len(p.Predictors)-1 { return nil } return p.Predictors[position] } // predictorIndex returns the index in predictors to use. Returns -1 if no predictor should be used. func (p *PositionalPredictor) predictorIndex(a complete.Args) int { idx := 0 for i := 0; i < len(a.Completed); i++ { if !p.nonPredictorPos(a, i) { idx++ } } return idx } // nonPredictorPos returns true if the value at this position is either a flag or a flag's argument func (p *PositionalPredictor) nonPredictorPos(a complete.Args, pos int) bool { if pos < 0 || pos > len(a.All)-1 { return false } val := a.All[pos] if p.valIsFlag(val) { return true } if pos == 0 { return false } prev := a.All[pos-1] return p.nextValueIsFlagArg(prev) } // valIsFlag returns true if the value matches a flag from the configuration func (p *PositionalPredictor) valIsFlag(val string) bool { val = strings.Split(val, "=")[0] for _, flag := range p.BoolFlags { if flag == val { return true } } for _, flag := range p.ArgFlags { if flag == val { return true } } return false } // nextValueIsFlagArg returns true if the value matches an ArgFlag and doesn't contain an equal sign. func (p *PositionalPredictor) nextValueIsFlagArg(val string) bool { if strings.Contains(val, "=") { return false } for _, flag := range p.ArgFlags { if flag == val { return true } } return false } kongplete-0.4.0/internal/positionalpredictor/positional_test.go000066400000000000000000000065631452517067200252060ustar00rootroot00000000000000package positionalpredictor import ( "strings" "testing" "unicode" "github.com/posener/complete" "github.com/stretchr/testify/assert" ) func TestPositionalPredictor_position(t *testing.T) { posPredictor := &PositionalPredictor{ BoolFlags: []string{"--mybool", "-b"}, ArgFlags: []string{"--myarg", "-a"}, } for args, want := range map[string]int{ ``: 0, `foo`: 0, `foo `: 1, `-b foo `: 1, `-a foo `: 0, `-a=omg foo `: 1, `--myarg omg foo `: 1, `--myarg=omg foo `: 1, `foo bar`: 1, `foo bar `: 2, } { t.Run(args, func(t *testing.T) { got := posPredictor.predictorIndex(newArgs("foo " + args)) assert.Equal(t, want, got) }) } } func TestPositionalPredictor_predictor(t *testing.T) { predictor1 := complete.PredictSet("1") predictor2 := complete.PredictSet("2") posPredictor := &PositionalPredictor{ Predictors: []complete.Predictor{predictor1, predictor2}, } for args, want := range map[string]complete.Predictor{ ``: predictor1, `foo`: predictor1, `foo `: predictor2, `foo bar`: predictor2, `foo bar `: nil, } { t.Run(args, func(t *testing.T) { got := posPredictor.predictor(newArgs("app " + args)) assert.Equal(t, want, got) }) } } func TestPositionalPredictor_cumulativepredictor(t *testing.T) { predictor1 := complete.PredictSet("1") predictor2 := complete.PredictSet("2") posPredictor := &PositionalPredictor{ Predictors: []complete.Predictor{predictor1, predictor2}, IsCumulative: true, } for args, want := range map[string]complete.Predictor{ ``: predictor1, `foo`: predictor1, `foo `: predictor2, `foo bar`: predictor2, `foo bar `: predictor2, `foo bar baz `: predictor2, } { t.Run(args, func(t *testing.T) { got := posPredictor.predictor(newArgs("app " + args)) assert.Equal(t, want, got) }) } } // The code below is taken from https://github.com/posener/complete/blob/f6dd29e97e24f8cb51a8d4050781ce2b238776a4/args.go // to assist in tests. func newArgs(line string) complete.Args { var ( all []string completed []string ) parts := splitFields(line) if len(parts) > 0 { all = parts[1:] completed = removeLast(parts[1:]) } return complete.Args{ All: all, Completed: completed, Last: last(parts), LastCompleted: last(completed), } } // splitFields returns a list of fields from the given command line. // If the last character is space, it appends an empty field in the end // indicating that the field before it was completed. // If the last field is of the form "a=b", it splits it to two fields: "a", "b", // So it can be completed. func splitFields(line string) []string { parts := strings.Fields(line) // Add empty field if the last field was completed. if len(line) > 0 && unicode.IsSpace(rune(line[len(line)-1])) { parts = append(parts, "") } // Treat the last field if it is of the form "a=b" parts = splitLastEqual(parts) return parts } func splitLastEqual(line []string) []string { if len(line) == 0 { return line } parts := strings.Split(line[len(line)-1], "=") return append(line[:len(line)-1], parts...) } func removeLast(a []string) []string { if len(a) > 0 { return a[:len(a)-1] } return a } func last(args []string) string { if len(args) == 0 { return "" } return args[len(args)-1] } kongplete-0.4.0/kongplete.go000066400000000000000000000137501452517067200160410ustar00rootroot00000000000000package kongplete import ( "fmt" "github.com/alecthomas/kong" "github.com/posener/complete" "github.com/willabides/kongplete/internal/positionalpredictor" ) const predictorTag = "predictor" type options struct { predictors map[string]complete.Predictor exitFunc func(code int) errorHandler func(error) } // Option is a configuration option for running Complete type Option func(*options) // WithPredictor use the named predictor func WithPredictor(name string, predictor complete.Predictor) Option { return func(o *options) { if o.predictors == nil { o.predictors = map[string]complete.Predictor{} } o.predictors[name] = predictor } } // WithPredictors use these predictors func WithPredictors(predictors map[string]complete.Predictor) Option { return func(o *options) { for k, v := range predictors { WithPredictor(k, v)(o) } } } // WithExitFunc the exit command that is run after completions func WithExitFunc(exitFunc func(code int)) Option { return func(o *options) { o.exitFunc = exitFunc } } // WithErrorHandler handle errors with completions func WithErrorHandler(handler func(error)) Option { return func(o *options) { o.errorHandler = handler } } func buildOptions(opt ...Option) *options { opts := &options{ predictors: map[string]complete.Predictor{}, } for _, o := range opt { o(opts) } return opts } // Command returns a completion Command for a kong parser func Command(parser *kong.Kong, opt ...Option) (complete.Command, error) { opts := buildOptions(opt...) if parser == nil || parser.Model == nil { return complete.Command{}, nil } command, err := nodeCommand(parser.Model.Node, opts.predictors) if err != nil { return complete.Command{}, err } return *command, err } // Complete runs completion for a kong parser func Complete(parser *kong.Kong, opt ...Option) { if parser == nil { return } opts := buildOptions(opt...) errHandler := opts.errorHandler if errHandler == nil { errHandler = func(err error) { parser.Errorf("error running command completion: %v", err) } } exitFunc := opts.exitFunc if exitFunc == nil { exitFunc = parser.Exit } cmd, err := Command(parser, opt...) if err != nil { errHandler(err) exitFunc(1) } cmp := complete.New(parser.Model.Name, cmd) cmp.Out = parser.Stdout done := cmp.Complete() if done { exitFunc(0) } } func nodeCommand(node *kong.Node, predictors map[string]complete.Predictor) (*complete.Command, error) { if node == nil { return nil, nil } cmd := complete.Command{ Sub: complete.Commands{}, GlobalFlags: complete.Flags{}, } for _, child := range node.Children { if child == nil || child.Hidden { continue } childCmd, err := nodeCommand(child, predictors) if err != nil { return nil, err } if childCmd != nil { cmd.Sub[child.Name] = *childCmd } } for _, flag := range node.Flags { if flag == nil || flag.Hidden { continue } predictor, err := flagPredictor(flag, predictors) if err != nil { return nil, err } for _, f := range flagNamesWithHyphens(flag) { cmd.GlobalFlags[f] = predictor } } boolFlags, nonBoolFlags := boolAndNonBoolFlags(node.Flags) isCumulative := false if len(node.Positional) > 0 && node.Positional[len(node.Positional)-1].IsCumulative() { isCumulative = true } pps, err := positionalPredictors(node.Positional, predictors) if err != nil { return nil, err } cmd.Args = &positionalpredictor.PositionalPredictor{ Predictors: pps, ArgFlags: flagNamesWithHyphens(nonBoolFlags...), BoolFlags: flagNamesWithHyphens(boolFlags...), IsCumulative: isCumulative, } return &cmd, nil } func flagNamesWithHyphens(flags ...*kong.Flag) []string { names := make([]string, 0, len(flags)*2) if flags == nil { return names } for _, flag := range flags { names = append(names, "--"+flag.Name) if flag.Short != 0 { names = append(names, "-"+string(flag.Short)) } } return names } // boolAndNonBoolFlags divides a list of flags into boolean and non-boolean flags func boolAndNonBoolFlags(flags []*kong.Flag) (boolFlags, nonBoolFlags []*kong.Flag) { boolFlags = make([]*kong.Flag, 0, len(flags)) nonBoolFlags = make([]*kong.Flag, 0, len(flags)) for _, flag := range flags { switch flag.Value.IsBool() { case true: boolFlags = append(boolFlags, flag) case false: nonBoolFlags = append(nonBoolFlags, flag) } } return boolFlags, nonBoolFlags } // kongTag interface for *kong.kongTag type kongTag interface { Has(string) bool Get(string) string } func tagPredictor(tag kongTag, predictors map[string]complete.Predictor) (complete.Predictor, error) { if tag == nil { return nil, nil } if !tag.Has(predictorTag) { return nil, nil } if predictors == nil { predictors = map[string]complete.Predictor{} } predictorName := tag.Get(predictorTag) predictor, ok := predictors[predictorName] if !ok { return nil, fmt.Errorf("no predictor with name %q", predictorName) } return predictor, nil } func valuePredictor(value *kong.Value, predictors map[string]complete.Predictor) (complete.Predictor, error) { if value == nil { return nil, nil } predictor, err := tagPredictor(value.Tag, predictors) if err != nil { return nil, err } if predictor != nil { return predictor, nil } switch { case value.IsBool(): return complete.PredictNothing, nil case value.Enum != "": enumVals := make([]string, 0, len(value.EnumMap())) for enumVal := range value.EnumMap() { enumVals = append(enumVals, enumVal) } return complete.PredictSet(enumVals...), nil default: return complete.PredictAnything, nil } } func positionalPredictors(args []*kong.Positional, predictors map[string]complete.Predictor) ([]complete.Predictor, error) { res := make([]complete.Predictor, len(args)) var err error for i, arg := range args { res[i], err = valuePredictor(arg, predictors) if err != nil { return nil, err } } return res, nil } func flagPredictor(flag *kong.Flag, predictors map[string]complete.Predictor) (complete.Predictor, error) { return valuePredictor(flag.Value, predictors) } kongplete-0.4.0/kongplete_test.go000066400000000000000000000134171452517067200171000ustar00rootroot00000000000000package kongplete import ( "bytes" "os" "strconv" "strings" "testing" "github.com/alecthomas/kong" "github.com/posener/complete" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) const ( envLine = "COMP_LINE" envPoint = "COMP_POINT" ) func TestComplete(t *testing.T) { type embed struct { Lion string } predictors := map[string]complete.Predictor{ "things": complete.PredictSet("thing1", "thing2"), "otherthings": complete.PredictSet("otherthing1", "otherthing2"), } var cli struct { Foo struct { Embedded embed `kong:"embed"` Bar string `kong:"predictor=things"` Baz bool Qux bool `kong:"hidden"` Rabbit struct{} `kong:"cmd"` Duck struct{} `kong:"cmd"` } `kong:"cmd"` Bar struct { Tiger string `kong:"arg,predictor=things"` Bear string `kong:"arg,predictor=otherthings"` OMG string `kong:"required,enum='oh,my,gizzles'"` Number int `kong:"required,short=n,enum='1,2,3'"` BooFlag bool `kong:"name=boofl,short=b"` } `kong:"cmd"` Baz struct{} `kong:"cmd,hidden"` Pos struct { Cumulative []string `kong:"arg,predictor=things"` } `kong:"cmd"` } for _, td := range []completeTest{ { parser: kong.Must(&cli), want: []string{"thing1", "thing2"}, line: "myApp pos thing1 ", }, { parser: kong.Must(&cli), want: []string{"foo", "bar", "pos"}, line: "myApp ", }, { parser: kong.Must(&cli), want: []string{"foo"}, line: "myApp foo", }, { parser: kong.Must(&cli), want: []string{"rabbit", "duck"}, line: "myApp foo ", }, { parser: kong.Must(&cli), want: []string{"rabbit"}, line: "myApp foo r", }, { parser: kong.Must(&cli), want: []string{"--bar", "--baz", "--lion", "--help", "-h"}, line: "myApp foo -", }, { parser: kong.Must(&cli), want: []string{}, line: "myApp foo --lion ", }, { parser: kong.Must(&cli), want: []string{"rabbit", "duck"}, line: "myApp foo --baz ", }, { parser: kong.Must(&cli), want: []string{"--bar", "--baz", "--lion", "--help", "-h"}, line: "myApp foo --baz -", }, { parser: kong.Must(&cli), want: []string{"thing1", "thing2"}, line: "myApp foo --bar ", }, { parser: kong.Must(&cli), want: []string{"thing1", "thing2"}, line: "myApp bar ", }, { parser: kong.Must(&cli), want: []string{"thing1", "thing2"}, line: "myApp bar thing", }, { parser: kong.Must(&cli), want: []string{"otherthing1", "otherthing2"}, line: "myApp bar thing1 ", }, { parser: kong.Must(&cli), want: []string{"oh", "my", "gizzles"}, line: "myApp bar --omg ", }, { parser: kong.Must(&cli), want: []string{"-n", "--number", "--omg", "--help", "-h", "--boofl", "-b"}, line: "myApp bar -", }, { parser: kong.Must(&cli), want: []string{"thing1", "thing2"}, line: "myApp bar -b ", }, { parser: kong.Must(&cli), want: []string{"-n", "--number", "--omg", "--help", "-h", "--boofl", "-b"}, line: "myApp bar -b thing1 -", }, { parser: kong.Must(&cli), want: []string{"oh", "my", "gizzles"}, line: "myApp bar -b thing1 --omg ", }, { parser: kong.Must(&cli), want: []string{"otherthing1", "otherthing2"}, line: "myApp bar -b thing1 --omg gizzles ", }, } { name := td.name if name == "" { name = td.line } t.Run(name, func(t *testing.T) { options := []Option{WithPredictors(predictors)} got := runComplete(t, td.parser, td.line, options) assert.ElementsMatch(t, td.want, got) }) } } func Test_tagPredictor(t *testing.T) { t.Run("nil", func(t *testing.T) { got, err := tagPredictor(nil, nil) assert.NoError(t, err) assert.Nil(t, got) }) t.Run("no predictor tag", func(t *testing.T) { got, err := tagPredictor(testTag{}, nil) assert.NoError(t, err) assert.Nil(t, got) }) t.Run("missing predictor", func(t *testing.T) { got, err := tagPredictor(testTag{predictorTag: "foo"}, nil) assert.Error(t, err) assert.Equal(t, `no predictor with name "foo"`, err.Error()) assert.Nil(t, got) }) t.Run("existing predictor", func(t *testing.T) { got, err := tagPredictor(testTag{predictorTag: "foo"}, map[string]complete.Predictor{"foo": complete.PredictAnything}) assert.NoError(t, err) assert.NotNil(t, got) }) } type testTag map[string]string func (t testTag) Has(k string) bool { _, ok := t[k] return ok } func (t testTag) Get(k string) string { return t[k] } type completeTest struct { name string parser *kong.Kong want []string line string } func setLineAndPoint(t *testing.T, line string) func() { t.Helper() origLine, hasOrigLine := os.LookupEnv(envLine) origPoint, hasOrigPoint := os.LookupEnv(envPoint) require.NoError(t, os.Setenv(envLine, line)) require.NoError(t, os.Setenv(envPoint, strconv.Itoa(len(line)))) return func() { t.Helper() require.NoError(t, os.Unsetenv(envLine)) require.NoError(t, os.Unsetenv(envPoint)) if hasOrigLine { require.NoError(t, os.Setenv(envLine, origLine)) } if hasOrigPoint { require.NoError(t, os.Setenv(envPoint, origPoint)) } } } func runComplete(t *testing.T, parser *kong.Kong, line string, options []Option) []string { t.Helper() options = append(options, WithErrorHandler(func(err error) { t.Helper() assert.NoError(t, err) }), WithExitFunc(func(code int) { t.Helper() assert.Equal(t, 0, code) }), ) cleanup := setLineAndPoint(t, line) defer cleanup() var buf bytes.Buffer if parser != nil { parser.Stdout = &buf } Complete(parser, options...) return parseOutput(buf.String()) } func parseOutput(output string) []string { lines := strings.Split(output, "\n") options := []string{} for _, l := range lines { if l != "" { options = append(options, l) } } return options } kongplete-0.4.0/script/000077500000000000000000000000001452517067200150205ustar00rootroot00000000000000kongplete-0.4.0/script/bindown000077500000000000000000000003441452517067200164070ustar00rootroot00000000000000#!/bin/sh set -e CDPATH="" cd -- "$(dirname -- "$0")/.." mkdir -p bin [ -f bin/bindown ] || sh -c "$( curl -sfL https://github.com/WillAbides/bindown/releases/download/v4.6.2/bootstrap-bindown.sh )" exec bin/bindown "$@" kongplete-0.4.0/script/fmt000077500000000000000000000004561452517067200155410ustar00rootroot00000000000000#!/bin/sh #/ script/fmt formats go code and shell scripts. set -e CDPATH="" cd -- "$(dirname -- "$0")/.." script/bindown -q install gofumpt shfmt handcrafted git ls-files -o -c --exclude-standard '*.go' | bin/handcrafted | xargs bin/gofumpt -w -extra bin/shfmt -ci -i 2 -ci -sr -w -s ./script kongplete-0.4.0/script/generate000077500000000000000000000010721452517067200165400ustar00rootroot00000000000000#!/bin/sh set -e CDPATH="" cd -- "$(dirname -- "$0")/.." if [ "$1" = "--check" ]; then [ -z "$(git status --porcelain)" ] || { git status echo 1>&2 "Running 'script/generate --check' requires a clean git working tree. Please commit or stash changes and try again." exit 1 } script/generate [ -z "$(git status --porcelain)" ] || { git status echo 1>&2 "script/generate resulted in changes. Please commit changes (or 'git reset --hard HEAD' if you aren't ready to commit changes)." git diff exit 1 } exit 0 fi go generate ./... kongplete-0.4.0/script/lint000077500000000000000000000002431452517067200157130ustar00rootroot00000000000000#!/bin/sh set -e CDPATH="" cd -- "$(dirname -- "$0")/.." script/bindown -q install golangci-lint shellcheck bin/golangci-lint run ./... bin/shellcheck script/* kongplete-0.4.0/script/test000077500000000000000000000001421452517067200157220ustar00rootroot00000000000000#!/bin/sh set -e CDPATH="" cd -- "$(dirname -- "$0")/.." go test -race -covermode=atomic ./...