Skip to content

Commit

Permalink
Merge pull request #1832 from rsteube/export-macros
Browse files Browse the repository at this point in the history
export macros
  • Loading branch information
rsteube authored Dec 22, 2023
2 parents 07933c4 + 7ed9fd6 commit a947f03
Show file tree
Hide file tree
Showing 22 changed files with 767 additions and 520 deletions.
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

0 comments on commit a947f03

Please sign in to comment.