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

action: added uid #1040

Merged
merged 1 commit into from
Sep 15, 2024
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
37 changes: 37 additions & 0 deletions action.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package carapace

import (
"fmt"
"net/url"
"os"
"regexp"
"runtime"
Expand Down Expand Up @@ -462,6 +463,42 @@ func (a Action) Unless(condition func(c Context) bool) Action {
})
}

// Uid TODO experimental
func (a Action) Uid(scheme, host string, opts ...string) Action {
return ActionCallback(func(c Context) Action {
if length := len(opts); length%2 != 0 {
return ActionMessage("invalid amount of arguments [Uid]: %v", length)
}

invoked := a.Invoke(c)
for index, v := range invoked.action.rawValues {
uid := url.URL{
Scheme: scheme,
Host: host,
Path: v.Value,
}
if len(opts) > 0 {
values := uid.Query()
for i := 0; i < len(opts); i += 2 {
if opts[i+1] != "" { // implicitly skip empty values
values.Set(opts[i], opts[i+1])
}
}
uid.RawQuery = values.Encode()
}
invoked.action.rawValues[index].Uid = uid.String()
}
return invoked.ToA()
})
}

// UidF TODO experimental
func (a Action) UidF(f func(s string) (*url.URL, error)) Action {
return ActionCallback(func(c Context) Action {
return a.Invoke(c).UidF(f).ToA()
})
}

// Usage sets the usage.
func (a Action) Usage(usage string, args ...interface{}) Action {
return a.UsageF(func() string {
Expand Down
51 changes: 44 additions & 7 deletions action_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/carapace-sh/carapace/internal/assert"
"github.com/carapace-sh/carapace/internal/common"
"github.com/carapace-sh/carapace/internal/uid"
"github.com/carapace-sh/carapace/pkg/style"
)

Expand Down Expand Up @@ -122,7 +123,14 @@ func TestActionDirectories(t *testing.T) {
"internal/", style.Of(style.Blue, style.Bold),
"pkg/", style.Of(style.Blue, style.Bold),
"third_party/", style.Of(style.Blue, style.Bold),
).NoSpace('/').Tag("directories").Invoke(Context{}),
).NoSpace('/').Tag("directories").Invoke(Context{}).UidF(uid.Map(
"example/", "file://"+wd("")+"/example/",
"example-nonposix/", "file://"+wd("")+"/example-nonposix/",
"docs/", "file://"+wd("")+"/docs/",
"internal/", "file://"+wd("")+"/internal/",
"pkg/", "file://"+wd("")+"/pkg/",
"third_party/", "file://"+wd("")+"/third_party/",
)),
ActionDirectories().Invoke(Context{Value: ""}).Filter("vendor/"),
)

Expand All @@ -134,22 +142,34 @@ func TestActionDirectories(t *testing.T) {
"internal/", style.Of(style.Blue, style.Bold),
"pkg/", style.Of(style.Blue, style.Bold),
"third_party/", style.Of(style.Blue, style.Bold),
).NoSpace('/').Tag("directories").Invoke(Context{}).Prefix("./"),
).NoSpace('/').Tag("directories").Invoke(Context{}).Prefix("./").UidF(uid.Map(
"./example/", "file://"+wd("")+"/example/",
"./example-nonposix/", "file://"+wd("")+"/example-nonposix/",
"./docs/", "file://"+wd("")+"/docs/",
"./internal/", "file://"+wd("")+"/internal/",
"./pkg/", "file://"+wd("")+"/pkg/",
"./third_party/", "file://"+wd("")+"/third_party/",
)),
ActionDirectories().Invoke(Context{Value: "./"}).Filter("./vendor/"),
)

assertEqual(t,
ActionStyledValues(
"_test/", style.Of(style.Blue, style.Bold),
"cmd/", style.Of(style.Blue, style.Bold),
).NoSpace('/').Tag("directories").Invoke(Context{}).Prefix("example/"),
).NoSpace('/').Tag("directories").Invoke(Context{}).Prefix("example/").UidF(uid.Map(
"example/_test/", "file://"+wd("")+"/example/_test/",
"example/cmd/", "file://"+wd("")+"/example/cmd/",
)),
ActionDirectories().Invoke(Context{Value: "example/"}),
)

assertEqual(t,
ActionStyledValues(
"cmd/", style.Of(style.Blue, style.Bold),
).NoSpace('/').Tag("directories").Invoke(Context{}).Prefix("example/"),
).NoSpace('/').Tag("directories").Invoke(Context{}).Prefix("example/").UidF(uid.Map(
"example/cmd/", "file://"+wd("")+"/example/cmd/",
)),
ActionDirectories().Invoke(Context{Value: "example/cm"}),
)
}
Expand All @@ -164,7 +184,15 @@ func TestActionFiles(t *testing.T) {
"internal/", style.Of(style.Blue, style.Bold),
"pkg/", style.Of(style.Blue, style.Bold),
"third_party/", style.Of(style.Blue, style.Bold),
).NoSpace('/').Tag("files").Invoke(Context{}),
).NoSpace('/').Tag("files").Invoke(Context{}).UidF(uid.Map(
"README.md", "file://"+wd("")+"/README.md",
"example/", "file://"+wd("")+"/example/",
"example-nonposix/", "file://"+wd("")+"/example-nonposix/",
"docs/", "file://"+wd("")+"/docs/",
"internal/", "file://"+wd("")+"/internal/",
"pkg/", "file://"+wd("")+"/pkg/",
"third_party/", "file://"+wd("")+"/third_party/",
)),
ActionFiles(".md").Invoke(Context{Value: ""}).Filter("vendor/"),
)

Expand All @@ -175,7 +203,13 @@ func TestActionFiles(t *testing.T) {
"cmd/", style.Of(style.Blue, style.Bold),
"main.go", style.Default,
"main_test.go", style.Default,
).NoSpace('/').Tag("files").Invoke(Context{}).Prefix("example/"),
).NoSpace('/').Tag("files").Invoke(Context{}).Prefix("example/").UidF(uid.Map(
"example/README.md", "file://"+wd("example")+"/README.md",
"example/_test/", "file://"+wd("example")+"/_test/",
"example/cmd/", "file://"+wd("example")+"/cmd/",
"example/main.go", "file://"+wd("example")+"/main.go",
"example/main_test.go", "file://"+wd("example")+"/main_test.go",
)),
ActionFiles().Invoke(Context{Value: "example/"}).Filter("example/example"),
)
}
Expand All @@ -197,7 +231,10 @@ func TestActionFilesChdir(t *testing.T) {
ActionStyledValues(
"action.go", style.Default,
"snippet.go", style.Default,
).NoSpace('/').Tag("files").Invoke(Context{}).Prefix("elvish/"),
).NoSpace('/').Tag("files").Invoke(Context{}).Prefix("elvish/").UidF(uid.Map(
"elvish/action.go", "file://"+wd("internal/shell")+"/elvish/action.go",
"elvish/snippet.go", "file://"+wd("internal/shell")+"/elvish/snippet.go",
)),
ActionFiles().Chdir("internal/shell").Invoke(Context{Value: "elvish/"}),
)

Expand Down
58 changes: 47 additions & 11 deletions defaultActions.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
"net/url"
"os"
"os/exec"
"strings"
Expand All @@ -14,6 +15,7 @@ import (
"github.com/carapace-sh/carapace/internal/env"
"github.com/carapace-sh/carapace/internal/export"
"github.com/carapace-sh/carapace/internal/man"
"github.com/carapace-sh/carapace/internal/uid"
"github.com/carapace-sh/carapace/pkg/match"
"github.com/carapace-sh/carapace/pkg/style"
"github.com/carapace-sh/carapace/third_party/github.com/acarl005/stripansi"
Expand Down Expand Up @@ -132,18 +134,34 @@ func ActionExecute(cmd *cobra.Command) Action {

// ActionDirectories completes directories.
func ActionDirectories() Action {
return actionPath([]string{""}, true).
MultiParts("/").
StyleF(style.ForPath).
Tag("directories")
return ActionCallback(func(c Context) Action {
return actionPath([]string{""}, true).
MultiParts("/").
StyleF(style.ForPath).
UidF(func(s string) (*url.URL, error) { // TODO duplicated from ActionFiles
abs, err := c.Abs(s)
if err != nil {
return nil, err
}
return url.Parse("file://" + abs)
})
}).Tag("directories")
}

// ActionFiles completes files with optional suffix filtering.
func ActionFiles(suffix ...string) Action {
return actionPath(suffix, false).
MultiParts("/").
StyleF(style.ForPath).
Tag("files")
return ActionCallback(func(c Context) Action {
return actionPath(suffix, false).
MultiParts("/").
StyleF(style.ForPath).
UidF(func(s string) (*url.URL, error) {
abs, err := c.Abs(s)
if err != nil {
return nil, err
}
return url.Parse("file://" + abs)
})
}).Tag("files")
}

// ActionValues completes arbitrary keywords (values).
Expand Down Expand Up @@ -438,7 +456,10 @@ func ActionExecutables(dirs ...string) Action {
for i := len(dirs) - 1; i >= 0; i-- {
batch = append(batch, actionDirectoryExecutables(dirs[i], c.Value, manDescriptions))
}
return batch.ToA()
return batch.ToA().
UidF(func(s string) (*url.URL, error) {
return &url.URL{Scheme: "cmd", Host: s}, nil
})
}).Tag("executables")
}

Expand All @@ -458,7 +479,9 @@ func actionDirectoryExecutables(dir string, prefix string, manDescriptions map[s
}
}
}
return ActionStyledValuesDescribed(vals...)
return ActionStyledValuesDescribed(vals...).UidF(func(s string) (*url.URL, error) {
return url.Parse(fmt.Sprintf("file://%v/%v", dir, s)) // TODO trim slash suffix from dir | backslash path possible? (windows)
})
}
return ActionValues()
})
Expand Down Expand Up @@ -524,7 +547,20 @@ func ActionCommands(cmd *cobra.Command) Action {
}
}
}
return batch.ToA()
return batch.ToA().UidF(func(s string) (*url.URL, error) {
uid := uid.Command(cmd)
if subCommand, _, err := cmd.Find([]string{s}); err == nil {
s = subCommand.Name() // alias -> actual name
}

switch uid.Path {
case "":
uid.Path = s
default:
uid.Path = uid.Path + "/" + s
}
return uid, nil
})
})
}

Expand Down
19 changes: 17 additions & 2 deletions defaultActions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"strings"
"testing"

"github.com/carapace-sh/carapace/internal/uid"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -33,13 +34,27 @@ func TestActionImport(t *testing.T) {
}

func TestActionFlags(t *testing.T) {
cmd := &cobra.Command{}
cmd := &cobra.Command{Use: "actionFlags"}
cmd.Flags().BoolP("alpha", "a", false, "")
cmd.Flags().BoolP("beta", "b", false, "")

cmd.Flag("alpha").Changed = true
a := actionFlags(cmd).Invoke(Context{Value: "-a"})
assertEqual(t, ActionValuesDescribed("b", "", "h", "help for this command").Tag("shorthand flags").NoSpace('b', 'h').Invoke(Context{}).Prefix("-a"), a)
assertEqual(
t,
ActionValuesDescribed(
"b", "",
"h", "help for actionFlags",
).Tag("shorthand flags").
NoSpace('b', 'h').
Invoke(Context{}).
Prefix("-a").
UidF(uid.Map(
"-ab", "cmd://actionFlags?flag=beta",
"-ah", "cmd://actionFlags?flag=help",
)),
a,
)
}

func TestActionExecCommandEnv(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions internal/common/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type RawValue struct {
Description string `json:"description,omitempty"`
Style string `json:"style,omitempty"`
Tag string `json:"tag,omitempty"`
Uid string `json:"uid,omitempty"`
}

// TrimmedDescription returns the trimmed description.
Expand Down
5 changes: 5 additions & 0 deletions internal/env/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

const (
CARAPACE_COVERDIR = "CARAPACE_COVERDIR" // coverage directory for sandbox tests
CARAPACE_EXPERIMENTAL = "CARAPACE_EXPERIMENTAL" // enable experimental features
CARAPACE_HIDDEN = "CARAPACE_HIDDEN" // show hidden commands/flags
CARAPACE_LENIENT = "CARAPACE_LENIENT" // allow unknown flags
CARAPACE_LOG = "CARAPACE_LOG" // enable logging
Expand All @@ -25,6 +26,10 @@ func ColorDisabled() bool {
return os.Getenv(NO_COLOR) != "" || os.Getenv(CLICOLOR) == "0"
}

func Experimental() bool {
return os.Getenv(CARAPACE_EXPERIMENTAL) != ""
}

func Lenient() bool {
return os.Getenv(CARAPACE_LENIENT) != ""
}
Expand Down
4 changes: 2 additions & 2 deletions internal/log/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package log

import (
"fmt"
"io/ioutil"
"io"
"log"
"os"

Expand All @@ -11,7 +11,7 @@ import (
"github.com/carapace-sh/carapace/pkg/ps"
)

var LOG = log.New(ioutil.Discard, "", log.Flags())
var LOG = log.New(io.Discard, "", log.Flags())

func init() {
if !env.Log() {
Expand Down
9 changes: 9 additions & 0 deletions internal/shell/shell.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package shell

import (
"fmt"
"os/exec"
"sort"
"strings"

Expand Down Expand Up @@ -90,6 +91,14 @@ func Value(shell string, value string, meta common.Meta, values common.RawValues
}

sort.Sort(common.ByDisplay(filtered))
if env.Experimental() {
if _, err := exec.LookPath("tabdance"); err == nil {
return f(value, meta, filtered)
}
}
for index := range filtered {
filtered[index].Uid = ""
}
return f(value, meta, filtered)
}
return ""
Expand Down
Loading