diff --git a/cmd/carapace/cmd/root.go b/cmd/carapace/cmd/root.go index 6bd4fb198e..998d1f46e0 100644 --- a/cmd/carapace/cmd/root.go +++ b/cmd/carapace/cmd/root.go @@ -17,7 +17,7 @@ import ( ) var rootCmd = &cobra.Command{ - Use: "carapace [flags] [COMPLETER] [bash|elvish|fish|oil|powershell|tcsh|xonsh|zsh]", + Use: "carapace [flags] [COMPLETER] [bash|elvish|fish|nushell|oil|powershell|tcsh|xonsh|zsh]", Long: "multi-shell multi-command argument completer", Example: fmt.Sprintf(` Single completer: bash: source <(carapace chmod bash) @@ -45,11 +45,23 @@ var rootCmd = &cobra.Command{ bash: source <(carapace --bridge vault/posener) elvish: eval (carapace --bridge vault/posener|slurp) fish: carapace --bridge vault/posener | source + nushell: carapace --bridge vault/posener | save vault.nu ; nu -c 'source vault.nu' oil: source <(carapace --bridge vault/posener) powershell: carapace --bridge vault/posener | Out-String | Invoke-Expression tcsh: eval `+"`"+`carapace --bridge vault/posener`+"`"+` xonsh: exec($(carapace --bridge vault/posener)) zsh: source <(carapace --bridge vault/posener) + + Spec completion: + bash: source <(carapace --spec example.yaml) + elvish: eval (carapace --spec example.yaml|slurp) + fish: carapace --spec example.yaml | source + oil: source <(carapace --spec example.yaml) + nushell: carapace --spec example.yaml | save example.nu ; nu -c 'source example.nu' + powershell: carapace --spec example.yaml | Out-String | Invoke-Expression + tcsh: eval `+"`"+`carapace --spec example.yaml`+"`"+` + xonsh: exec($(carapace --spec example.yaml)) + zsh: source <(carapace --spec example.yaml) Style: set: carapace --style 'carapace.Value=bold,magenta' @@ -71,6 +83,10 @@ var rootCmd = &cobra.Command{ bridgeCompletion(splitted[0], splitted[1], args[2:]...) } } + case "--spec": + if len(args) > 1 { + specCompletion(args[1], args[2:]...) + } case "-h": cmd.Help() case "--help": diff --git a/cmd/carapace/cmd/spec.go b/cmd/carapace/cmd/spec.go new file mode 100644 index 0000000000..589b67e4a6 --- /dev/null +++ b/cmd/carapace/cmd/spec.go @@ -0,0 +1,70 @@ +package cmd + +import ( + "bytes" + "fmt" + "github.com/rsteube/carapace-spec" + "github.com/spf13/cobra" + "gopkg.in/yaml.v3" + "io" + "os" + "path/filepath" + "strings" +) + +func loadSpec(path string) (string, *cobra.Command, error) { + abs, err := filepath.Abs(path) + if err != nil { + return "", nil, err + } + + content, err := os.ReadFile(abs) + if err != nil { + return "", nil, err + } + + var specCmd spec.Command + if err := yaml.Unmarshal(content, &specCmd); err != nil { + return "", nil, err + } + return abs, specCmd.ToCobra(), nil +} + +func specCompletion(path string, args ...string) { + old := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + outC := make(chan string) + // copy the output in a separate goroutine so printing can't block indefinitely + go func() { + var buf bytes.Buffer + io.Copy(&buf, r) + outC <- buf.String() + }() + + abs, cmd, err := loadSpec(path) + if err != nil { + return /// TODO handle error + } + + a := []string{"_carapace"} + a = append(a, args...) + cmd.SetArgs(a) + cmd.Execute() + + w.Close() + out := <-outC + os.Stdout = old + + executable, err := os.Executable() + if err != nil { + panic(err.Error()) // TODO exit with error message + } + + executableName := filepath.Base(executable) + patched := strings.Replace(string(out), fmt.Sprintf("%v _carapace", executableName), fmt.Sprintf("%v --spec %v", executableName, abs), -1) // general callback + patched = strings.Replace(patched, fmt.Sprintf("'%v', '_carapace'", executableName), fmt.Sprintf("'%v', '--spec', '%v'", executableName, abs), -1) // xonsh callback + fmt.Print(patched) + +} diff --git a/completers/carapace_completer/cmd/root.go b/completers/carapace_completer/cmd/root.go index 57cc7e4467..aa42d6ed77 100644 --- a/completers/carapace_completer/cmd/root.go +++ b/completers/carapace_completer/cmd/root.go @@ -34,6 +34,7 @@ func flagCmd() *cobra.Command { cmd.Flags().String("bridge", "", "generic completion bridge") cmd.Flags().BoolP("help", "h", false, "help for carapace") cmd.Flags().Bool("list", false, "list completers") + cmd.Flags().String("spec", "", "spec completion") cmd.Flags().StringSlice("style", []string{}, "set style") cmd.Flags().BoolP("version", "v", false, "version for carapace") @@ -53,6 +54,7 @@ func flagCmd() *cobra.Command { return carapace.ActionValues() } }), + "spec": carapace.ActionFiles(".yaml"), "style": carapace.ActionStyleConfig(), }) return cmd diff --git a/go.mod b/go.mod index 67521e5eff..e06e4846c6 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/mitchellh/go-ps v1.0.0 github.com/pelletier/go-toml v1.9.5 github.com/rsteube/carapace v0.20.1 + github.com/rsteube/carapace-spec v0.0.4 github.com/spf13/cobra v1.4.0 github.com/spf13/pflag v1.0.5 golang.org/x/sys v0.0.0-20211205182925-97ca703d548d diff --git a/go.sum b/go.sum index f5c548470c..e2a7991ca3 100644 --- a/go.sum +++ b/go.sum @@ -15,6 +15,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rsteube/carapace v0.20.1 h1:ZnPtEZQGi4vSoco7yLexTvmx9X82jUaOcDScpl3v14M= github.com/rsteube/carapace v0.20.1/go.mod h1:GgiwpPVhucHNOv0AmtIkxhiEFkCMP5BBRauyQLP0mFY= +github.com/rsteube/carapace-spec v0.0.4 h1:ZXqtUGw+Qp+hu3RVIXrp2Xv7hmZa6nxFZnfkyzxeC3k= +github.com/rsteube/carapace-spec v0.0.4/go.mod h1:u6Qe+U4w4/yD1MWRV9k83WuCkqq4Z/rAjzj8gz7zS9M= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=