Skip to content

Commit

Permalink
forward parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
rsteube committed Jan 21, 2023
1 parent 8381259 commit aa1d32c
Show file tree
Hide file tree
Showing 19 changed files with 382 additions and 385 deletions.
3 changes: 3 additions & 0 deletions action_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ func TestActionDirectories(t *testing.T) {
assertEqual(t,
ActionStyledValues(
"example/", style.Of("fg-default", "bg-default", style.Blue, style.Bold),
"example-nonposix/", style.Of("fg-default", "bg-default", style.Blue, style.Bold),
"docs/", style.Of("fg-default", "bg-default", style.Blue, style.Bold),
"internal/", style.Of("fg-default", "bg-default", style.Blue, style.Bold),
"pkg/", style.Of("fg-default", "bg-default", style.Blue, style.Bold),
Expand All @@ -128,6 +129,7 @@ func TestActionDirectories(t *testing.T) {
assertEqual(t,
ActionStyledValues(
"example/", style.Of("fg-default", "bg-default", style.Blue, style.Bold),
"example-nonposix/", style.Of("fg-default", "bg-default", style.Blue, style.Bold),
"docs/", style.Of("fg-default", "bg-default", style.Blue, style.Bold),
"internal/", style.Of("fg-default", "bg-default", style.Blue, style.Bold),
"pkg/", style.Of("fg-default", "bg-default", style.Blue, style.Bold),
Expand Down Expand Up @@ -157,6 +159,7 @@ func TestActionFiles(t *testing.T) {
ActionStyledValues(
"README.md", style.Of("fg-default", "bg-default"),
"example/", style.Of("fg-default", "bg-default", style.Blue, style.Bold),
"example-nonposix/", style.Of("fg-default", "bg-default", style.Blue, style.Bold),
"docs/", style.Of("fg-default", "bg-default", style.Blue, style.Bold),
"internal/", style.Of("fg-default", "bg-default", style.Blue, style.Bold),
"pkg/", style.Of("fg-default", "bg-default", style.Blue, style.Bold),
Expand Down
8 changes: 6 additions & 2 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,17 @@ func addCompletionCommand(cmd *cobra.Command) {
Use: "_carapace",
Hidden: true,
Run: func(cmd *cobra.Command, args []string) {
logger.PrintArgs(os.Args)
logger.Printf("%#v", os.Args)

if len(args) > 2 && strings.HasPrefix(args[2], "_") {
cmd.Hidden = false
}

if s, err := complete(cmd, args); err != nil {
if !cmd.HasParent() {
panic("missing parent command") // TODO this should never happen
}

if s, err := complete(cmd.Parent(), args); err != nil {
fmt.Fprintln(io.MultiWriter(cmd.OutOrStderr(), logger.Writer()), err.Error())
} else {
fmt.Fprintln(io.MultiWriter(cmd.OutOrStdout(), logger.Writer()), s)
Expand Down
90 changes: 3 additions & 87 deletions complete.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
package carapace

import (
"strings"

"github.com/rsteube/carapace/internal/common"
"github.com/rsteube/carapace/internal/config"
"github.com/rsteube/carapace/internal/pflagfork"
"github.com/rsteube/carapace/pkg/ps"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)

func complete(cmd *cobra.Command, args []string) (string, error) {
Expand All @@ -17,86 +11,8 @@ func complete(cmd *cobra.Command, args []string) (string, error) {
return Gen(cmd).Snippet(ps.DetermineShell())
case 1:
return Gen(cmd).Snippet(args[0])
default:
action, context := traverse(cmd, args[2:])
return action.Invoke(context).value(args[0], args[len(args)-1]), nil
}

shell := args[0]
current := args[len(args)-1]
previous := args[len(args)-2]

if err := config.Load(); err != nil {
return ActionMessage("failed to load config: "+err.Error()).Invoke(Context{CallbackValue: current}).value(shell, current), nil
}

targetCmd, targetArgs, err := findTarget(cmd, args)
if err != nil {
return ActionMessage(err.Error()).Invoke(Context{CallbackValue: current}).value(shell, current), nil
}

context := NewContext(append(targetArgs, current))

// TODO needs more cleanup and tests
var targetAction Action
if flag := lookupFlag(targetCmd, previous); !targetCmd.DisableFlagParsing && flag != nil && flag.NoOptDefVal == "" && !common.IsDash(targetCmd) { // previous arg is a flag and needs a value
targetAction = storage.getFlag(targetCmd, flag.Name)
} else if !targetCmd.DisableFlagParsing && strings.HasPrefix(current, "-") && !common.IsDash(targetCmd) { // assume flag
if strings.Contains(current, "=") { // complete value for optarg flag
if flag := lookupFlag(targetCmd, current); flag != nil && flag.NoOptDefVal != "" {
a := storage.getFlag(targetCmd, flag.Name)
splitted := strings.SplitN(current, "=", 2)
context.CallbackValue = splitted[1]
current = strings.Replace(current, "=", opts.OptArgDelimiter, 1) // revert (potentially) overridden optarg divider for `.value()` invocation below
targetAction = a.Invoke(context).Prefix(splitted[0] + opts.OptArgDelimiter).ToA() // prefix with (potentially) overridden optarg delimiter
}
} else { // complete flagnames
targetAction = actionFlags(targetCmd)
}
} else {
if len(context.Args) > 0 {
context.Args = context.Args[:len(context.Args)-1] // current word being completed is a positional so remove it from context.Args
}

if common.IsDash(targetCmd) {
dashArgs := targetArgs[targetCmd.ArgsLenAtDash() : len(targetArgs)-1]
context.Args = dashArgs
targetAction = findAction(targetCmd, dashArgs)
} else {
targetAction = findAction(targetCmd, targetArgs)
if targetCmd.HasAvailableSubCommands() && len(targetArgs) <= 1 {
subcommandA := actionSubcommands(targetCmd).Invoke(context)
targetAction = targetAction.Invoke(context).Merge(subcommandA).ToA()
}
}
}
return targetAction.Invoke(context).value(shell, current), nil
}

func findAction(targetCmd *cobra.Command, targetArgs []string) Action {
// TODO handle Action not found
if len(targetArgs) == 0 {
return storage.getPositional(targetCmd, 0)
}
return storage.getPositional(targetCmd, len(targetArgs)-1)
}

func findTarget(cmd *cobra.Command, args []string) (*cobra.Command, []string, error) {
origArg := []string{}
if len(args) > 2 {
origArg = args[2:]
}
return common.TraverseLenient(cmd, origArg)
}

func lookupFlag(cmd *cobra.Command, arg string) (flag *pflag.Flag) {
nameOrShorthand := strings.TrimLeft(strings.SplitN(arg, "=", 2)[0], "-")

if strings.HasPrefix(arg, "--") {
flag = cmd.Flags().Lookup(nameOrShorthand)
} else if strings.HasPrefix(arg, "-") && len(nameOrShorthand) > 0 {
if (pflagfork.FlagSet{FlagSet: cmd.Flags()}).IsPosix() {
flag = cmd.Flags().ShorthandLookup(string(nameOrShorthand[len(nameOrShorthand)-1]))
} else {
flag = cmd.Flags().ShorthandLookup(nameOrShorthand)
}
}
return
}
38 changes: 38 additions & 0 deletions example-nonposix/cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package cmd

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

var rootCmd = &cobra.Command{
Use: "example-nonposix",
Short: "nonposix examples",
Run: func(cmd *cobra.Command, args []string) {},
}

func Execute() error {
return rootCmd.Execute()
}
func init() {
carapace.Gen(rootCmd).Standalone()

rootCmd.Flags().BoolN("bool-long", "bool-short", false, "BoolN")
rootCmd.Flags().StringS("delim-colon", "delim-colon", "", "OptargDelimiter ':'")
rootCmd.Flags().StringS("delim-slash", "delim-slash", "", "OptargDelimiter '/'")
rootCmd.Flags().CountN("count", "c", "CountN")

rootCmd.Flag("delim-colon").NoOptDefVal = " "
rootCmd.Flag("delim-colon").OptargDelimiter = ':'
rootCmd.Flag("delim-slash").NoOptDefVal = " "
rootCmd.Flag("delim-slash").OptargDelimiter = '/'

carapace.Gen(rootCmd).FlagCompletion(carapace.ActionMap{
"delim-colon": carapace.ActionValues("d1", "d2", "d3"),
"delim-slash": carapace.ActionValues("d1", "d2", "d3"),
})

carapace.Gen(rootCmd).PositionalCompletion(
carapace.ActionValues("p1", "positional1"),
)
}
32 changes: 32 additions & 0 deletions example-nonposix/cmd/root_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package cmd

import (
"testing"

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

func TestRoot(t *testing.T) {
sandbox.Run(t, "github.com/rsteube/carapace/example-nonposix")(func(s *sandbox.Sandbox) {
s.Run("-delim-colon:").
Expect(carapace.ActionValues("d1", "d2", "d3").
Prefix("-delim-colon:").
Usage("OptargDelimiter ':'"))

s.Run("-delim-colon", "").
Expect(carapace.ActionValues("p1", "positional1"))

s.Run("-delim-slash/").
Expect(carapace.ActionValues("d1", "d2", "d3").
Prefix("-delim-slash/").
Usage("OptargDelimiter '/'"))

s.Run("-c").
Expect(carapace.ActionValuesDescribed(
"-c", "CountN",
"-count", "CountN").
NoSpace('.').
Tag("flags"))
})
}
12 changes: 12 additions & 0 deletions example-nonposix/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module github.com/rsteube/carapace/example-nonposix

go 1.15

require (
github.com/rsteube/carapace v0.31.1
github.com/spf13/cobra v1.6.1
)

replace github.com/rsteube/carapace => ../

replace github.com/spf13/pflag => github.com/rsteube/carapace-pflag v0.0.6
10 changes: 10 additions & 0 deletions example-nonposix/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/rsteube/carapace-pflag v0.0.6 h1:8qloiXqoRdrX6sU1e+sIq9fYrl8Htrm0V/YY+UUjvVU=
github.com/rsteube/carapace-pflag v0.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
9 changes: 9 additions & 0 deletions example-nonposix/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package main

import (
"github.com/rsteube/carapace/example-nonposix/cmd"
)

func main() {
_ = cmd.Execute()
}
3 changes: 3 additions & 0 deletions go.work
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
go 1.19

use ./example-nonposix
4 changes: 4 additions & 0 deletions go.work.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
github.com/rsteube/carapace-pflag v0.0.4 h1:Onb0cLNLxg1xJr2EsMlBldAI5KkybrvZ89b5cRElZXI=
github.com/rsteube/carapace-pflag v0.0.4/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/rsteube/carapace-pflag v0.0.5 h1:QQC0KnthHMayHsX7B7DxqOkr0B6JSIM0glB+KrSTruU=
github.com/rsteube/carapace-pflag v0.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
3 changes: 1 addition & 2 deletions internal/common/dash.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,5 @@ import "github.com/spf13/cobra"
//
// example action positional1 -- dash1 dash2
func IsDash(cmd *cobra.Command) bool {
// TODO add test
return cmd.ArgsLenAtDash() != -1 && (cmd.ArgsLenAtDash() != len(cmd.Flags().Args()))
return cmd.ArgsLenAtDash() != -1
}
Loading

0 comments on commit aa1d32c

Please sign in to comment.