Repository: rsteube/carapace
Pull request #697 (2023-01-21 23:50:02)

sandbox: added Command
- fixed PreRun



Patch:
diff --git a/carapace.go b/carapace.go
index 175a8ba..ee78f49 100644
--- a/carapace.go
+++ b/carapace.go
@@ -32,12 +32,16 @@ func Gen(cmd *cobra.Command) *Carapace {
 
 // PreRun sets a function to be run before completion (use on rootCmd).
 func (c Carapace) PreRun(f func(cmd *cobra.Command, args []string)) {
-	if completionCmd, _, err := c.cmd.Find([]string{"_carapace"}); err == nil {
-		completionCmd.PreRun = func(cmd *cobra.Command, args []string) {
-			if len(args) > 2 { // skip script generation
-				f(c.cmd, args[2:])
-			}
+	if entry := storage.get(c.cmd); entry.prerun != nil {
+		_f := entry.prerun
+		entry.prerun = func(cmd *cobra.Command, args []string) {
+			// TODO yuck - probably best to append to a slice in storage
+			_f(cmd, args)
+			f(cmd, args)
+
 		}
+	} else {
+		entry.prerun = f
 	}
 }
 
diff --git a/pkg/sandbox/sandbox.go b/pkg/sandbox/sandbox.go
index f52e746..2a635fc 100644
--- a/pkg/sandbox/sandbox.go
+++ b/pkg/sandbox/sandbox.go
@@ -17,7 +17,7 @@ import (
 
 type Sandbox struct {
 	t    *testing.T
-	f    func() *cobra.Command
+	cmdF func() *cobra.Command
 	env  map[string]string
 	keep bool
 	mock *common.Mock
@@ -29,9 +29,9 @@ func newSandbox(t *testing.T, f func() *cobra.Command) Sandbox {
 		t.Fatal("failed to create sandbox dir: " + err.Error())
 	}
 	return Sandbox{
-		t:   t,
-		f:   f,
-		env: make(map[string]string),
+		t:    t,
+		cmdF: f,
+		env:  make(map[string]string),
 		mock: &common.Mock{
 			Dir:     tempDir,
 			Replies: make(map[string]string),
@@ -100,8 +100,8 @@ func (r reply) With(s string) {
 }
 
 // Run invokes `go run` on given package for sandbox tests.
-func Run(t *testing.T, pkg string) (f func(func(s *Sandbox))) {
-	cmdF := func() *cobra.Command {
+func Package(t *testing.T, pkg string) (f func(func(s *Sandbox))) {
+	return Command(t, func() *cobra.Command {
 		cmd := &cobra.Command{
 			Use:                "integration",
 			Run:                func(cmd *cobra.Command, args []string) {},
@@ -122,8 +122,11 @@ func Run(t *testing.T, pkg string) (f func(func(s *Sandbox))) {
 			}),
 		)
 		return cmd
-	}
+	})
+}
 
+// Command executes the command returned by given function for sandbox tests.
+func Command(t *testing.T, cmdF func() *cobra.Command) (f func(func(s *Sandbox))) {
 	return func(f func(s *Sandbox)) {
 		s := newSandbox(t, cmdF)
 		defer s.remove()
@@ -137,7 +140,6 @@ func (s *Sandbox) Run(args ...string) run {
 	for key, value := range s.env {
 		c.Setenv(key, value)
 	}
-	// TODO actually invoke it here instead of Expect
 	m, _ := json.Marshal(args)
 
 	return run{
@@ -146,9 +148,12 @@ func (s *Sandbox) Run(args ...string) run {
 		s.mock.Dir,
 		c,
 		carapace.ActionCallback(func(c carapace.Context) carapace.Action {
-			b, _ := json.Marshal(s.mock)
+			b, err := json.Marshal(s.mock)
+			if err != nil {
+				return carapace.ActionMessage(err.Error())
+			}
 			c.Setenv("CARAPACE_SANDBOX", string(b))
-			return carapace.ActionExecute(s.f()).Invoke(c).ToA()
+			return carapace.ActionExecute(s.cmdF()).Invoke(c).ToA()
 		}),
 	}
 }
diff --git a/storage.go b/storage.go
index 66334be..9ccd3b6 100644
--- a/storage.go
+++ b/storage.go
@@ -19,6 +19,7 @@ type entry struct {
 	dash          []Action
 	dashAny       Action
 	preinvoke     func(cmd *cobra.Command, flag *pflag.Flag, action Action) Action
+	prerun        func(cmd *cobra.Command, args []string)
 }
 
 type _storage map[*cobra.Command]*entry
@@ -48,6 +49,12 @@ func (s _storage) getFlag(cmd *cobra.Command, name string) Action {
 	}
 }
 
+func (s _storage) preRun(cmd *cobra.Command, args []string) {
+	if entry := s.get(cmd); entry.prerun != nil {
+		entry.prerun(cmd, args)
+	}
+}
+
 func (s _storage) preinvoke(cmd *cobra.Command, flag *pflag.Flag, action Action) Action {
 	// TODO yuck - clean this up
 	entry := s.get(cmd)
diff --git a/traverse.go b/traverse.go
index f6a7f56..299be8e 100644
--- a/traverse.go
+++ b/traverse.go
@@ -175,8 +175,6 @@ func subcommand(cmd *cobra.Command, arg string) *cobra.Command {
 }
 
 func preRun(cmd *cobra.Command, args []string) {
-	if subCmd := subcommand(cmd, "_carapace"); subCmd != nil && subCmd.PreRun != nil {
-		logger.Printf("executing PreRun for %#v with args %#v", cmd.Name(), args)
-		subCmd.PreRun(cmd, args)
-	}
+	logger.Printf("executing PreRun for %#v with args %#v", cmd.Name(), args)
+	storage.preRun(cmd, args)
 }


Test patch:
diff --git a/example-nonposix/cmd/root_test.go b/example-nonposix/cmd/root_test.go
index 0270e74..9ad89b4 100644
--- a/example-nonposix/cmd/root_test.go
+++ b/example-nonposix/cmd/root_test.go
@@ -8,7 +8,7 @@ import (
 )
 
 func TestRoot(t *testing.T) {
-	sandbox.Run(t, "github.com/rsteube/carapace/example-nonposix")(func(s *sandbox.Sandbox) {
+	sandbox.Package(t, "github.com/rsteube/carapace/example-nonposix")(func(s *sandbox.Sandbox) {
 		s.Run("-delim-colon:").
 			Expect(carapace.ActionValues("d1", "d2", "d3").
 				Prefix("-delim-colon:").
diff --git a/example/cmd/action_test.go b/example/cmd/action_test.go
index 25abdfa..7f2f47f 100644
--- a/example/cmd/action_test.go
+++ b/example/cmd/action_test.go
@@ -9,7 +9,7 @@ import (
 )
 
 func TestAction(t *testing.T) {
-	sandbox.Run(t, "github.com/rsteube/carapace/example")(func(s *sandbox.Sandbox) {
+	sandbox.Package(t, "github.com/rsteube/carapace/example")(func(s *sandbox.Sandbox) {
 		s.Files(
 			"dirA/file1.txt", "",
 			"dirA/file2.png", "",
@@ -165,7 +165,7 @@ func TestAction(t *testing.T) {
 }
 
 func TestUnknownFlag(t *testing.T) {
-	sandbox.Run(t, "github.com/rsteube/carapace/example")(func(s *sandbox.Sandbox) {
+	sandbox.Package(t, "github.com/rsteube/carapace/example")(func(s *sandbox.Sandbox) {
 		s.Run("action", "--unknown", "").
 			Expect(carapace.ActionMessage("unknown flag: --unknown").NoSpace())
 
diff --git a/example/cmd/modifier_test.go b/example/cmd/modifier_test.go
index 7582927..1621d47 100644
--- a/example/cmd/modifier_test.go
+++ b/example/cmd/modifier_test.go
@@ -8,7 +8,7 @@ import (
 )
 
 func TestModifier(t *testing.T) {
-	sandbox.Run(t, "github.com/rsteube/carapace/example")(func(s *sandbox.Sandbox) {
+	sandbox.Package(t, "github.com/rsteube/carapace/example")(func(s *sandbox.Sandbox) {
 		s.Run("modifier", "--timeout", "1s:").
 			Expect(carapace.ActionValues("within timeout").
 				Prefix("1s:").
diff --git a/example/cmd/multiparts_test.go b/example/cmd/multiparts_test.go
index 20b5920..b24b9d1 100644
--- a/example/cmd/multiparts_test.go
+++ b/example/cmd/multiparts_test.go
@@ -9,7 +9,7 @@ import (
 )
 
 func TestMultiparts(t *testing.T) {
-	sandbox.Run(t, "github.com/rsteube/carapace/example")(func(s *sandbox.Sandbox) {
+	sandbox.Package(t, "github.com/rsteube/carapace/example")(func(s *sandbox.Sandbox) {
 		s.Files(
 			"dirA/file1.txt", "",
 			"dirA/file2.png", "",
diff --git a/example/cmd/root_test.go b/example/cmd/root_test.go
index 241459f..eb8a3fd 100644
--- a/example/cmd/root_test.go
+++ b/example/cmd/root_test.go
@@ -57,7 +57,7 @@ func TestZsh(t *testing.T) {
 }
 
 func TestRoot(t *testing.T) {
-	sandbox.Run(t, "github.com/rsteube/carapace/example")(func(s *sandbox.Sandbox) {
+	sandbox.Package(t, "github.com/rsteube/carapace/example")(func(s *sandbox.Sandbox) {
 		s.Run("").
 			Expect(carapace.Batch(
 				carapace.ActionValuesDescribed(
diff --git a/pkg/sandbox/sandbox_test.go b/pkg/sandbox/sandbox_test.go
new file mode 100644
index 0000000..7e6112a
--- /dev/null
+++ b/pkg/sandbox/sandbox_test.go
@@ -0,0 +1,52 @@
+package sandbox
+
+import (
+	"testing"
+
+	"github.com/rsteube/carapace"
+	"github.com/rsteube/carapace/pkg/style"
+	"github.com/spf13/cobra"
+)
+
+func TestPreRun(t *testing.T) {
+	Command(t, func() *cobra.Command {
+		rootCmd := &cobra.Command{}
+		carapace.Gen(rootCmd).PreRun(func(cmd *cobra.Command, args []string) {
+			cmd.Flags().Bool("root", false, "root flag")
+		})
+
+		subCmd := &cobra.Command{
+			Use: "sub",
+		}
+		carapace.Gen(subCmd).PreRun(func(cmd *cobra.Command, args []string) {
+			cmd.Flags().String("sub", "", "sub flag")
+
+			carapace.Gen(cmd).FlagCompletion(carapace.ActionMap{
+				"sub": carapace.ActionValues(cmd.Parent().Flag("root").Value.String()),
+			})
+		})
+		rootCmd.AddCommand(subCmd)
+
+		return rootCmd
+	})(func(s *Sandbox) {
+		s.Run("--root").
+			Expect(carapace.ActionValuesDescribed(
+				"--root", "root flag").
+				NoSpace('.').
+				Tag("flags"))
+
+		s.Run("--root", "sub", "--").
+			Expect(carapace.ActionStyledValuesDescribed(
+				"--sub", "sub flag", style.Blue).
+				NoSpace('.').
+				Tag("flags"))
+
+		s.Run("--root", "sub", "--sub", "").
+			Expect(carapace.ActionValues("true").
+				Usage("sub flag"))
+
+		s.Run("sub", "--sub", "").
+			Expect(carapace.ActionValues("false").
+				Usage("sub flag"))
+	})
+}