Skip to content

Commit

Permalink
Merge pull request #1847 from rsteube/env-positional
Browse files Browse the repository at this point in the history
env: fix positional completion
  • Loading branch information
rsteube authored Sep 19, 2023
2 parents 7178e78 + edf7242 commit 904159a
Show file tree
Hide file tree
Showing 13 changed files with 562 additions and 0 deletions.
49 changes: 49 additions & 0 deletions completers/env_completer/cmd/root.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package cmd

import (
"strings"

"github.com/rsteube/carapace"
"github.com/rsteube/carapace-bin/pkg/actions/env"
"github.com/rsteube/carapace-bin/pkg/actions/os"
"github.com/rsteube/carapace-bin/pkg/actions/ps"
"github.com/rsteube/carapace-bridge/pkg/actions/bridge"
"github.com/rsteube/carapace/pkg/style"
"github.com/spf13/cobra"
)

Expand All @@ -19,6 +24,7 @@ func Execute() error {
}
func init() {
carapace.Gen(rootCmd).Standalone()
rootCmd.Flags().SetInterspersed(false)

rootCmd.Flags().String("block-signal", "", "block delivery of SIG signal(s) to COMMAND")
rootCmd.Flags().StringP("chdir", "C", "", "change working directory to DIR")
Expand All @@ -33,11 +39,54 @@ func init() {
rootCmd.Flags().StringP("unset", "u", "", "remove variable from the environment")
rootCmd.Flags().Bool("version", false, "output version information and exit")

rootCmd.Flag("block-signal").NoOptDefVal = " "
rootCmd.Flag("default-signal").NoOptDefVal = " "
rootCmd.Flag("ignore-signal").NoOptDefVal = " "

carapace.Gen(rootCmd).FlagCompletion(carapace.ActionMap{
"block-signal": ps.ActionKillSignals(),
"chdir": carapace.ActionDirectories(),
"default-signal": ps.ActionKillSignals(),
"ignore-signal": ps.ActionKillSignals(),
"unset": os.ActionEnvironmentVariables(),
})

carapace.Gen(rootCmd).PositionalAnyCompletion(
carapace.ActionCallback(func(c carapace.Context) carapace.Action {
for index, arg := range c.Args {
if strings.Contains(arg, "=") {
splitted := strings.SplitN(arg, "=", 2)
c.Setenv(splitted[0], splitted[1])
} else {
return bridge.ActionCarapaceBin().Shift(index).Invoke(c).ToA()
}
}

return carapace.Batch(
carapace.ActionMultiPartsN("=", 2, func(c carapace.Context) carapace.Action {
switch len(c.Parts) {
case 0:
return carapace.Batch(
carapace.ActionCallback(func(c carapace.Context) carapace.Action {
alreadySet := make([]string, 0)
for _, e := range c.Env {
alreadySet = append(alreadySet, strings.SplitN(e, "=", 2)[0])
}
a := env.ActionKnownEnvironmentVariables().Filter(alreadySet...).Suffix("=")
if !strings.Contains(c.Value, "_") {
return a.MultiParts("_") // only do multipart completion for first underscore
}
return a
}),
os.ActionEnvironmentVariables().Style(style.Blue).Suffix("="),
).ToA()
default:
return env.ActionEnvironmentVariableValues(c.Parts[0])
}
}),
carapace.ActionExecutables(),
carapace.ActionFiles(),
).ToA()
}),
)
}
63 changes: 63 additions & 0 deletions pkg/actions/env/aws.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package env

import (
"os"

"github.com/rsteube/carapace"
"github.com/rsteube/carapace-bin/pkg/actions/tools/aws"
"github.com/rsteube/carapace-bridge/pkg/actions/bridge"
"github.com/rsteube/carapace/pkg/style"
)

func init() {
_bool := carapace.ActionValues("true", "false").StyleF(style.ForKeyword)
knownVariables["aws"] = variables{
Condition: checkPath("aws"),
Names: map[string]string{
"AWS_ACCESS_KEY_ID": "Specifies an AWS access key associated with an IAM account",
"AWS_CA_BUNDLE": "Specifies the path to a certificate bundle to use for HTTPS certificate validation",
"AWS_CLI_AUTO_PROMPT": "Enables the auto-prompt for the AWS CLI version 2",
"AWS_CLI_FILE_ENCODING": "Specifies the encoding used for text files",
"AWS_CONFIG_FILE": "Specifies the location of the file that the AWS CLI uses to store configuration profiles",
"AWS_DATA_PATH": "A list of additional directories to check outside of the built-in search path of ~/.aws/models",
"AWS_DEFAULT_OUTPUT": "Specifies the output format to use",
"AWS_DEFAULT_REGION": "The Default region name",
"AWS_EC2_METADATA_DISABLED": "Disables the use of the Amazon EC2 instance metadata service (IMDS)",
"AWS_ENDPOINT_URL": "Specifies the endpoint that is used for all service requests",
// "AWS_ENDPOINT_URL_<SERVICE>": "pecifies a custom endpoint that is used for a specific service, where <SERVICE> is replaced with the AWS service identifier. For example, Amazon DynamoDB has a serviceId of DynamoDB. For this service, the endpoint URL environment variable is AWS_ENDPOINT_URL_DYNAMODB.
"AWS_IGNORE_CONFIGURED_ENDPOINT_URLS": "If enabled, the AWS CLI ignores all custom endpoint configurations",
"AWS_MAX_ATTEMPTS": "Specifies a value of maximum retry attempts the AWS CLI retry handler uses",
"AWS_METADATA_SERVICE_NUM_ATTEMPTS": "retry multiple times before giving up",
"AWS_METADATA_SERVICE_TIMEOUT": "The number of seconds before a connection to the instance metadata service should time out",
"AWS_PAGER": "Specifies the pager program used for output",
"AWS_PROFILE": "Specifies the name of the AWS CLI profile with the credentials and options to use",
"AWS_REGION": "The AWS SDK compatible environment variable that specifies the AWS Region to send the request to",
"AWS_RETRY_MODE": "Specifies which retry mode AWS CLI uses",
"AWS_ROLE_ARN": "Specifies the Amazon Resource Name (ARN) of an IAM role",
"AWS_ROLE_SESSION_NAME": "Specifies the name to attach to the role session",
"AWS_SECRET_ACCESS_KEY": "Specifies the secret key associated with the access key",
"AWS_SHARED_CREDENTIALS_FILE": "Specifies the location of the file that the AWS CLI uses to store access keys",
"AWS_USE_FIPS_ENDPOINT": "Federal Information Processing Standard (FIPS) endoint",
"AWS_WEB_IDENTITY_TOKEN_FILE": "Specifies the path to a file that contains an OAuth 2.0 access token",
},
Values: map[string]carapace.Action{
"AWS_CA_BUNDLE": carapace.ActionFiles(),
"AWS_CLI_AUTO_PROMPT": carapace.ActionValuesDescribed(
"on", "full auto-prompt mode each time you attempt to run an aws command",
"on-partial", "partial auto-prompt mode",
).StyleF(style.ForKeyword),
"AWS_CONFIG_FILE": carapace.ActionFiles(),
"AWS_DATA_PATH": carapace.ActionDirectories().List(string(os.PathListSeparator)),
"AWS_DEFAULT_OUTPUT": aws.ActionOutputFormats(),
"AWS_DEFAULT_REGION": aws.ActionRegions(),
"AWS_EC2_METADATA_DISABLED": _bool,
"AWS_IGNORE_CONFIGURED_ENDPOINT_URLS": _bool,
"AWS_PAGER": bridge.ActionCarapaceBin().Split(),
"AWS_PROFILE": aws.ActionProfiles(),
"AWS_REGION": aws.ActionRegions(),
"AWS_RETRY_MODE": carapace.ActionValues("legacy", "standard", "adaptive"),
"AWS_SHARED_CREDENTIALS_FILE": carapace.ActionFiles(),
"AWS_WEB_IDENTITY_TOKEN_FILE": carapace.ActionFiles(),
},
}
}
25 changes: 25 additions & 0 deletions pkg/actions/env/carapace.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package env

import "github.com/rsteube/carapace"

func init() {
knownVariables["carapace"] = variables{
Names: map[string]string{
"CARAPACE_COVERDIR": "coverage directory for sandbox tests",
"CARAPACE_HIDDEN": "show hidden commands/flags",
"CARAPACE_LENIENT": "allow unknown flags",
"CARAPACE_LOG": "enable logging",
"CARAPACE_MATCH": "match case insensitive",
"CARAPACE_SANDBOX": "mock context for sandbox tests",
"CARAPACE_ZSH_HASH_DIRS": "zsh hash directories",
},
Values: map[string]carapace.Action{
"CARAPACE_COVERDIR": carapace.ActionDirectories(),
"CARAPACE_HIDDEN": carapace.ActionValues("0", "1"),
"CARAPACE_LENIENT": carapace.ActionValues("0", "1"),
"CARAPACE_LOG": carapace.ActionValues("0", "1"),
"CARAPACE_MATCH": carapace.ActionValues("0", "1"),
},
}

}
21 changes: 21 additions & 0 deletions pkg/actions/env/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package env

import (
_os "os"

"github.com/rsteube/carapace"
)

func init() {
knownVariables["common"] = variables{
Names: map[string]string{
"HTTP_PROXY": "http proxy server",
"HTTPS_PROXY": "https proxy server",
"PATH": "A list of directories to be searched when executing commands",
},
Values: map[string]carapace.Action{
"PATH": carapace.ActionDirectories().List(string(_os.PathListSeparator)).NoSpace(),
},
}

}
28 changes: 28 additions & 0 deletions pkg/actions/env/common_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package env

import (
"github.com/rsteube/carapace"
"github.com/rsteube/carapace-bin/pkg/actions/os"
)

func init() {
knownVariables["common_unix"] = variables{
Names: map[string]string{
"USER": "The current logged in user",
"HOME": "The home directory of the current user",
"EDITOR": "The default file editor to be used",
"SHELL": "The path of the current user’s shell, such as bash or zsh",
"LOGNAME": "The name of the current user",
"LANG": "The current locales settings",
"TERM": "The current terminal emulation",
"MAIL": "Location of where the current user’s mail is stored",
},
Values: map[string]carapace.Action{
"HOME": carapace.ActionDirectories(),
"LANG": os.ActionLanguages(),
"LOGNAME": os.ActionUsers(),
"USER": os.ActionUsers(),
},
}

}
35 changes: 35 additions & 0 deletions pkg/actions/env/docker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package env

import (
"github.com/rsteube/carapace"
"github.com/rsteube/carapace/pkg/style"
)

func init() {
knownVariables["docker"] = variables{
Condition: checkPath("docker"),
Names: map[string]string{
"DOCKER_API_VERSION": "Override the negotiated API version to use for debugging",
"DOCKER_CERT_PATH": "Location of your authentication keys",
"DOCKER_CONFIG": "The location of your client configuration files",
"DOCKER_CONTENT_TRUST_SERVER": "The URL of the Notary server to use",
"DOCKER_CONTENT_TRUST": "When set Docker uses notary to sign and verify images",
"DOCKER_CONTEXT": "Name of the docker context to use",
"DOCKER_DEFAULT_PLATFORM": "Default platform for commands that take the --platform flag",
"DOCKER_HIDE_LEGACY_COMMANDS": "When set, Docker hides \"legacy\" top-level commands",
"DOCKER_HOST": "Daemon socket to connect to",
"DOCKER_TLS_VERIFY": "When set Docker uses TLS and verifies the remote",
"BUILDKIT_PROGRESS": "Set type of progress output",
},
Values: map[string]carapace.Action{
"DOCKER_CERT_PATH": carapace.ActionDirectories(),
"DOCKER_CONFIG": carapace.ActionFiles(),
"DOCKER_HIDE_LEGACY_COMMANDS": carapace.ActionStyledValuesDescribed(
"0", "show", style.Carapace.KeywordNegative,
"1", "hide", style.Carapace.KeywordPositive,
),
"BUILDKIT_PROGRESS": carapace.ActionValues("auto", "plain", "tty"),
},
}

}
49 changes: 49 additions & 0 deletions pkg/actions/env/env.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package env

import (
"os/exec"

"github.com/rsteube/carapace"
)

type variables struct {
Condition func(c carapace.Context) bool
Names map[string]string
Values map[string]carapace.Action
}

func checkPath(s string) func(c carapace.Context) bool {
return func(c carapace.Context) bool {
_, err := exec.LookPath(s) // TODO copy function to carapace as this needs to use carapace.Context$Env
return err == nil
}
}

var knownVariables = map[string]variables{}

func ActionKnownEnvironmentVariables() carapace.Action {
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
vals := make([]string, 0)
for _, v := range knownVariables {
if v.Condition != nil && !v.Condition(c) {
continue
}

for name, description := range v.Names {
vals = append(vals, name, description)
}
}
return carapace.ActionValuesDescribed(vals...)
}).Tag("known environment variables")
}

func ActionEnvironmentVariableValues(s string) carapace.Action {
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
for _, v := range knownVariables {
if action, ok := v.Values[s]; ok {
return action
}
}
return carapace.ActionFiles()
})
}
13 changes: 13 additions & 0 deletions pkg/actions/env/env_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package env

import "testing"

func TestKnownVariables(t *testing.T) {
for k, v := range knownVariables {
for name := range v.Values {
if _, ok := v.Names[name]; !ok {
t.Errorf("variables %#v is unknown in %#v", name, k)
}
}
}
}
Loading

0 comments on commit 904159a

Please sign in to comment.