Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

export macros #1832

Merged
merged 1 commit into from
Dec 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions cmd/carapace/cmd/action/completer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package action

import (
"encoding/json"

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

func ActionCompleters() carapace.Action {
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
return carapace.ActionExecCommand("carapace", "--list", "--format", "json")(func(output []byte) carapace.Action {
var completers []struct {
Name string
Description string
Spec string
Overlay string
}
if err := json.Unmarshal(output, &completers); err != nil {
return carapace.ActionMessage(err.Error())
}

vals := make([]string, 0, len(completers))
for _, completer := range completers {
s := style.Default
if completer.Spec != "" {
s = style.Blue
}
if completer.Overlay != "" {
s = style.Of(s, style.Underlined)
}

vals = append(vals, completer.Name, completer.Description, s)
}
return carapace.ActionStyledValuesDescribed(vals...)
})
})
}
23 changes: 23 additions & 0 deletions cmd/carapace/cmd/codegen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package cmd

import (
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
)

var codegenCmd = &cobra.Command{
Use: "--scrape [spec]",
Short: "",
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
scrape(args[0])
},
}

func init() {
carapace.Gen(codegenCmd).Standalone()

carapace.Gen(codegenCmd).PositionalCompletion(
carapace.ActionFiles(".yaml"),
)
}
2 changes: 2 additions & 0 deletions cmd/carapace/cmd/completers/completers.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ var (
)

func Names() []string {
names = append(names, "carapace") // TODO add here or in generate?

unique := make(map[string]string)
for _, name := range names {
unique[name] = name
Expand Down
163 changes: 163 additions & 0 deletions cmd/carapace/cmd/invoke.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package cmd

import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"strings"

"github.com/rsteube/carapace"
"github.com/rsteube/carapace-bin/cmd/carapace/cmd/action"
"github.com/rsteube/carapace-bin/cmd/carapace/cmd/completers"
"github.com/rsteube/carapace-bridge/pkg/actions/bridge"
"github.com/spf13/cobra"
)

var invokeCmd = &cobra.Command{
Use: "invoke",
Short: "",
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
if overlayPath, err := overlayPath(args[0]); err == nil && len(args) > 2 { // and arg[1] is a known shell
cmd := &cobra.Command{
DisableFlagParsing: true,
CompletionOptions: cobra.CompletionOptions{
DisableDefaultCmd: true,
},
}

// TODO yuck
command := args[0]
shell := args[1]
args[0] = "_carapace"
args[1] = "export"
os.Args[1] = "_carapace"
os.Args[2] = "export"
os.Setenv("CARAPACE_LENIENT", "1")

carapace.Gen(cmd).PositionalAnyCompletion(
carapace.ActionCallback(func(c carapace.Context) carapace.Action {
batch := carapace.Batch()
specPath, err := completers.SpecPath(command)
if err != nil {
batch = append(batch, carapace.ActionImport([]byte(invokeCompleter(command))))
} else {
out, err := specCompletion(specPath, args[1:]...)
if err != nil {
return carapace.ActionMessage(err.Error())
}

batch = append(batch, carapace.ActionImport([]byte(out)))
}

batch = append(batch, overlayCompletion(overlayPath, args[1:]...))
return batch.ToA()
}),
)

cmd.SetArgs(append([]string{"_carapace", shell}, args[2:]...))
cmd.Execute()
} else {
if specPath, err := completers.SpecPath(args[0]); err == nil {
out, err := specCompletion(specPath, args[1:]...)
if err != nil {
fmt.Fprintln(cmd.ErrOrStderr(), err.Error())
return
}

// TODO revert the patching from specCompletion to use the integrated version for overlay to work (should move this somewhere else - best in specCompletion)
// TODO only patch completion script
out = strings.Replace(out, fmt.Sprintf("--spec '%v'", specPath), args[0], -1)
out = strings.Replace(out, fmt.Sprintf("'--spec', '%v'", specPath), fmt.Sprintf("'%v'", args[0]), -1) // xonsh callback
fmt.Fprint(cmd.OutOrStdout(), out)
} else {
fmt.Print(invokeCompleter(args[0]))
}
}
},
}

func init() {
carapace.Gen(invokeCmd).Standalone()
invokeCmd.Flags().SetInterspersed(false)

carapace.Gen(invokeCmd).PositionalCompletion(
action.ActionCompleters(),
bridge.ActionCarapaceBin("_carapace", "export", "", "_carapace").Shift(1).
Filter("macro", "style"),
carapace.ActionCallback(func(c carapace.Context) carapace.Action {
switch c.Args[1] {
case "bash",
"bash-ble",
"elvish",
"export",
"fish",
"ion",
"nushell",
"oil",
"powershell",
"tcsh",
"xonsh",
"zsh":
return carapace.ActionValues(c.Args[0])
default:
return carapace.ActionValues()
}
}),
)

carapace.Gen(invokeCmd).PositionalAnyCompletion(
carapace.ActionCallback(func(c carapace.Context) carapace.Action {
switch c.Args[1] {
case "bash",
"bash-ble",
"elvish",
"export",
"fish",
"ion",
"nushell",
"oil",
"powershell",
"tcsh",
"xonsh",
"zsh":
return bridge.ActionCarapaceBin(c.Args[0]).Shift(3)
default:
return carapace.ActionValues()
}
}),
)
}

func invokeCompleter(completer string) 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()
}()

os.Args[1] = "_carapace"
executeCompleter(completer)

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 %v", executableName, completer), -1) // general callback
patched = strings.Replace(patched, fmt.Sprintf("'%v', '_carapace'", executableName), fmt.Sprintf("'%v', '%v'", executableName, completer), -1) // xonsh callback
return patched

}
44 changes: 44 additions & 0 deletions cmd/carapace/cmd/invoke_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package cmd

import (
"testing"

"github.com/rsteube/carapace"
"github.com/rsteube/carapace/pkg/sandbox"
"github.com/rsteube/carapace/pkg/style"
)

func TestInvokeFlags(t *testing.T) {
sandbox.Package(t, "github.com/rsteube/carapace-bin/cmd/carapace")(func(s *sandbox.Sandbox) {
s.Run("tail", "export", "tail", "--fo").
Expect(carapace.ActionStyledValuesDescribed(
"--follow", "output appended data as the file grows", style.Yellow,
).Tag("flags").
NoSpace('.').
Usage("carapace [flags] [COMPLETER] [bash|elvish|fish|nushell|oil|powershell|tcsh|xonsh|zsh]")) // TODO fix usage

s.Run("tail", "export", "tail", "--follow=").
Expect(carapace.ActionValues(
"name",
"descriptor",
).Prefix("--follow=").
Usage("output appended data as the file grows"))
})
}

func TestInvokePositional(t *testing.T) {
sandbox.Package(t, "github.com/rsteube/carapace-bin/cmd/carapace")(func(s *sandbox.Sandbox) {
s.Run("git", "export", "git", "checko").
Expect(carapace.Batch(
carapace.ActionValuesDescribed(
"checkout", "Switch branches or restore working tree files",
).Style(style.Blue).
Tag("main commands"),
carapace.ActionValuesDescribed(
"checkout-index", "Copy files from the index to the working tree",
).Style(style.Of(style.Dim, style.Yellow)).
Tag("low-level manipulator commands"),
).ToA().
Usage("carapace [flags] [COMPLETER] [bash|elvish|fish|nushell|oil|powershell|tcsh|xonsh|zsh]")) // TODO fix usage
})
}
75 changes: 75 additions & 0 deletions cmd/carapace/cmd/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package cmd

import (
"encoding/json"
"fmt"
"strconv"

"github.com/rsteube/carapace"
"github.com/rsteube/carapace-bin/cmd/carapace/cmd/completers"
"github.com/rsteube/carapace/pkg/style"
"github.com/spf13/cobra"
)

var listCmd = &cobra.Command{
Use: "--list",
Short: "",
Run: func(cmd *cobra.Command, args []string) {
switch cmd.Flag("format").Value.String() {
case "json":
printCompletersJson()
default:
printCompleters()
}
},
}

func init() {
carapace.Gen(listCmd).Standalone()

listCmd.Flags().String("format", "plain", "output format")

carapace.Gen(listCmd).FlagCompletion(carapace.ActionMap{
"format": carapace.ActionValues("plain", "json").StyleF(func(s string, sc style.Context) string {
return style.ForPathExt("."+s, sc)
}),
})

}
func printCompleters() {
maxlen := 0
for _, name := range completers.Names() {
if len := len(name); len > maxlen {
maxlen = len
}
}

for _, name := range completers.Names() {
fmt.Printf("%-"+strconv.Itoa(maxlen)+"v %v\n", name, completers.Description(name))
}
}

func printCompletersJson() {
// TODO move to completers package
type _completer struct {
Name string
Description string
Spec string `json:",omitempty"`
Overlay string `json:",omitempty"`
}

_completers := make([]_completer, 0)
for _, name := range completers.Names() {
specPath, _ := completers.SpecPath(name) // TODO handle error (log?)
overlayPath, _ := completers.OverlayPath(name) // TODO handle error (log?)
_completers = append(_completers, _completer{
Name: name,
Description: completers.Description(name),
Spec: specPath,
Overlay: overlayPath,
})
}
if m, err := json.Marshal(_completers); err == nil { // TODO handle error (log?)
fmt.Println(string(m))
}
}
Loading