Repository: rsteube/carapace
Pull request #799 (2023-07-10 09:10:06)

support FlagSet.interspersed
related https://github.com/rsteube/carapace-bin/pull/1720


Patch:
diff --git a/example/cmd/interspersed.go b/example/cmd/interspersed.go
new file mode 100644
index 0000000..6091b97
--- /dev/null
+++ b/example/cmd/interspersed.go
@@ -0,0 +1,37 @@
+package cmd
+
+import (
+	"fmt"
+
+	"github.com/rsteube/carapace"
+	"github.com/spf13/cobra"
+)
+
+var interspersedCmd = &cobra.Command{
+	Use:   "interspersed",
+	Short: "interspersed example",
+	Run: func(cmd *cobra.Command, args []string) {
+		fmt.Fprintf(cmd.OutOrStdout(), "#%v", args)
+	},
+}
+
+func init() {
+	carapace.Gen(interspersedCmd).Standalone()
+
+	interspersedCmd.Flags().BoolP("bool", "b", false, "bool flag")
+	interspersedCmd.Flags().StringP("string", "s", "", "string flag")
+
+	interspersedCmd.Flags().SetInterspersed(false)
+
+	rootCmd.AddCommand(interspersedCmd)
+
+	carapace.Gen(interspersedCmd).PositionalCompletion(
+		carapace.ActionValues("p1", "positional1"),
+		carapace.ActionValues("p2", "positional2"),
+	)
+
+	carapace.Gen(interspersedCmd).DashCompletion(
+		carapace.ActionValues("d1", "dash1"),
+		carapace.ActionValues("d2", "dash2"),
+	)
+}
diff --git a/internal/pflagfork/flagset.go b/internal/pflagfork/flagset.go
index fa12972..e6d4bde 100644
--- a/internal/pflagfork/flagset.go
+++ b/internal/pflagfork/flagset.go
@@ -12,6 +12,13 @@ type FlagSet struct {
 	*pflag.FlagSet
 }
 
+func (f FlagSet) IsInterspersed() bool {
+	if fv := reflect.ValueOf(f.FlagSet).Elem().FieldByName("interspersed"); fv.IsValid() {
+		return fv.Bool()
+	}
+	return false
+}
+
 func (f FlagSet) IsPosix() bool {
 	if method := reflect.ValueOf(f.FlagSet).MethodByName("IsPosix"); method.IsValid() {
 		if values := method.Call([]reflect.Value{}); len(values) == 1 && values[0].Kind() == reflect.Bool {
diff --git a/traverse.go b/traverse.go
index c7a8653..f689e76 100644
--- a/traverse.go
+++ b/traverse.go
@@ -44,9 +44,10 @@ func traverse(c *cobra.Command, args []string) (Action, Context) {
 		c.FParseErrWhitelist.UnknownFlags = true
 	}
 
-	inArgs := []string{} // args consumed by current command
-	var inFlag *_inFlag  // last encountered flag that still expects arguments
-	c.LocalFlags()       // TODO force  c.mergePersistentFlags() which is missing from c.Flags()
+	inArgs := []string{}        // args consumed by current command
+	inPositionals := []string{} // positionals consumed by current command
+	var inFlag *_inFlag         // last encountered flag that still expects arguments
+	c.LocalFlags()              // TODO force  c.mergePersistentFlags() which is missing from c.Flags()
 	fs := pflagfork.FlagSet{FlagSet: c.Flags()}
 
 	context := NewContext(args...)
@@ -71,7 +72,7 @@ loop:
 			break loop
 
 		// flag
-		case !c.DisableFlagParsing && strings.HasPrefix(arg, "-"):
+		case !c.DisableFlagParsing && strings.HasPrefix(arg, "-") && (fs.IsInterspersed() || len(inPositionals) == 0):
 			LOG.Printf("arg %#v is a flag\n", arg)
 			inArgs = append(inArgs, arg)
 			inFlag = &_inFlag{
@@ -106,6 +107,7 @@ loop:
 		default:
 			LOG.Printf("arg %#v is a positional\n", arg)
 			inArgs = append(inArgs, arg)
+			inPositionals = append(inPositionals, arg)
 		}
 	}
 
@@ -157,7 +159,7 @@ loop:
 		return storage.getFlag(c, inFlag.Name), context
 
 	// flag
-	case !c.DisableFlagParsing && strings.HasPrefix(context.Value, "-"):
+	case !c.DisableFlagParsing && strings.HasPrefix(context.Value, "-") && (fs.IsInterspersed() || len(inPositionals) == 0):
 		if f := fs.LookupArg(context.Value); f != nil && f.IsOptarg() && strings.Contains(context.Value, string(f.OptargDelimiter())) {
 			LOG.Printf("completing optional flag argument for arg %#v\n", context.Value)
 			prefix, optarg := f.Split(context.Value)


Test patch:
diff --git a/example/cmd/interspersed_test.go b/example/cmd/interspersed_test.go
new file mode 100644
index 0000000..39ff444
--- /dev/null
+++ b/example/cmd/interspersed_test.go
@@ -0,0 +1,37 @@
+package cmd
+
+import (
+	"testing"
+
+	"github.com/rsteube/carapace"
+	"github.com/rsteube/carapace/pkg/sandbox"
+	"github.com/rsteube/carapace/pkg/style"
+)
+
+func TestInterspersed(t *testing.T) {
+	sandbox.Package(t, "github.com/rsteube/carapace/example")(func(s *sandbox.Sandbox) {
+		s.Run("interspersed", "--s").
+			Expect(carapace.ActionValuesDescribed(
+				"--string", "string flag",
+			).
+				StyleR(&style.Carapace.FlagArg).
+				NoSpace('.').
+				Tag("flags"))
+
+		s.Run("interspersed", "--bool", "--s").
+			Expect(carapace.ActionValuesDescribed(
+				"--string", "string flag",
+			).
+				StyleR(&style.Carapace.FlagArg).
+				NoSpace('.').
+				Tag("flags"))
+
+		s.Run("interspersed", "--bool", "").
+			Expect(carapace.ActionValues(
+				"p1", "positional1",
+			))
+
+		s.Run("interspersed", "--bool", "p1", "-").
+			Expect(carapace.ActionValues())
+	})
+}
diff --git a/example/cmd/root_test.go b/example/cmd/root_test.go
index 19bb1de..7315262 100644
--- a/example/cmd/root_test.go
+++ b/example/cmd/root_test.go
@@ -74,6 +74,7 @@ func TestRoot(t *testing.T) {
 				carapace.ActionValuesDescribed(
 					"completion", "Generate the autocompletion script for the specified shell",
 					"help", "Help about any command",
+					"interspersed", "interspersed example",
 					"multiparts", "multiparts example",
 					"special", "",
 				).Tag("other commands"),