Skip to content

Commit

Permalink
feat(clif): add Evaluate (#211)
Browse files Browse the repository at this point in the history
  • Loading branch information
plastikfan committed Oct 20, 2023
1 parent 1693a03 commit 49d5493
Show file tree
Hide file tree
Showing 11 changed files with 765 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,5 @@ coverage
ginkgo.report

src/assistant/internal/l10n/out/translate.en-US.json

.DS_Store
2 changes: 2 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"beezledub",
"bindnative",
"bodyclose",
"clif",
"cogen",
"colors",
"Comparables",
Expand Down Expand Up @@ -36,6 +37,7 @@
"ipmask",
"ipnet",
"linters",
"magick",
"memfs",
"nakedret",
"nolint",
Expand Down
Binary file removed main
Binary file not shown.
68 changes: 68 additions & 0 deletions public-cobrass-clif-api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package cobrass

import (
"github.com/snivilised/cobrass/src/clif"
)

type (
// ThirdPartyFlagName raw name of a flag, ie without the leading --/-
ThirdPartyFlagName = clif.ThirdPartyFlagName

// ThirdPartyOptionValue the string value of an option. Since this option
// is being delegated to a third party command, it does not have to be
// of a particular native go type and can be composed from a go type
// using the value's String() method.
ThirdPartyOptionValue = clif.ThirdPartyOptionValue

// PresentFlagsCollection represents the set of third party flags
// presented by the user on the command line.
// (NB: Cobra does not currently have a mechanism to collect third
// party flags, by convention, anything that follows " -- "), therefore
// we need to collect and handle these flags/options explicitly,
// which is less than ideal.
// A difference between PresentFlagsCollection and ThirdPartyCommandLine
// is that switch flags have a true/false option value in PresentFlagsCollection
// but not in ThirdPartyCommandLine.
PresentFlagsCollection = clif.PresentFlagsCollection

// ThirdPartyPresentFlags (see PresentFlagsCollection)
ThirdPartyPresentFlags = clif.ThirdPartyPresentFlags

// KnownByCollection collection maps a full flag name to the
// short name it is also known by. If a flag does not
// have a short name, it should be mapped to the empty
// string.
KnownByCollection = clif.KnownByCollection

// ThirdPartyFlagKnownBy (see KnownByCollection).
ThirdPartyFlagKnownBy = clif.ThirdPartyFlagKnownBy

// ThirdPartyCommandLine represents the collection of flags
// used to invoke a third party command. This collection
// represents the raw flags used for the invocation in
// the order required by the third party command. It also means
// that this collection contains the leading --/- not just
// the names of the flags and options.
// For example, to invoke the magick command we may want to
// compose this collection with:
// magick --strip --interlace plane --gaussian-blur 0.05
// and in this case, the list would be defined as a string slice:
// []string{"--strip", "--interlace", "plane", "--gaussian-blur", "0.05"}
ThirdPartyCommandLine = clif.ThirdPartyCommandLine

// ExternalThirdParty base struct for cli applications using the
// entry paradigm that need to delegate an invocation to an
// external third party command.
ExternalThirdParty = clif.ExternalThirdParty
)

var (
// Evaluate merges the secondary command line with the present flags.
// The flags that occur in present take precedence over those in
// secondary. There is a slight complication caused by the fact that
// a flag in the present set may be in the secondary set but in the opposite
// form; eg a flag may be in its short from in present but in long form
// in secondary. This is resolved by the knownBy set. The present set
// contains flags in their bare long form (bare as in without dash prefix).
Evaluate = clif.Evaluate
)
13 changes: 13 additions & 0 deletions src/clif/clif-suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package clif_test

import (
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func TestClif(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Clif Suite")
}
201 changes: 201 additions & 0 deletions src/clif/evaluate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
package clif

import (
"strings"

"github.com/samber/lo"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
)

var booleans = []string{"true", "false"}

type (
tokenInput struct {
token string
lead string
bare string
optionValue string
existingCL ThirdPartyCommandLine
presentFlags PresentFlagsCollection
knownBy KnownByCollection
}

handleTokenResult struct {
doConcatenate bool
}

concatIfFunc func(input *tokenInput) *handleTokenResult
concatenateResult struct {
commandLine ThirdPartyCommandLine
handleResult handleTokenResult
}
)

func (i *tokenInput) yoke(nextIndex int, secondaryCL ThirdPartyCommandLine) int {
const (
unaryIncrement = 1
pairIncrement = 2
)

handleAsPair := false

if nextIndex < len(secondaryCL) {
next := secondaryCL[nextIndex]
nextLead, nextBare := split(next)

if strings.HasPrefix(i.lead, "-") && !strings.HasPrefix(nextLead, "-") {
i.optionValue = nextBare
handleAsPair = true
}
}

return lo.Ternary(handleAsPair, pairIncrement, unaryIncrement)
}

func (i *tokenInput) concatIf(concatFunc concatIfFunc) *concatenateResult {
handleResult := concatFunc(i)

if handleResult.doConcatenate {
i.existingCL = append(i.existingCL, i.token)

if i.optionValue != "" {
i.existingCL = append(i.existingCL, i.optionValue)
}
}

return &concatenateResult{
commandLine: i.existingCL,
handleResult: *handleResult,
}
}

func concatenate(input *tokenInput) *handleTokenResult {
var (
result = &handleTokenResult{}
)

if input.lead == "" {
result.doConcatenate = true

return result
}

if _, found := input.presentFlags[input.bare]; found {
return result
}

aka := input.knownBy[input.bare]
_, found := input.presentFlags[aka]
result.doConcatenate = !found

return result
}

// Evaluate merges the secondary command line with the present flags.
// The flags that occur in present take precedence over those in
// secondary. There is a slight complication caused by the fact that
// a flag in the present set may be in the secondary set but in the opposite
// form; eg a flag may be in its short from in present but in long form
// in secondary. This is resolved by the knownBy set. The present set
// contains flags in their bare long form.
func Evaluate(presentFlags PresentFlagsCollection,
knownBy KnownByCollection,
secondaryCL ThirdPartyCommandLine,
) ThirdPartyCommandLine {
result := &concatenateResult{}
bilateralKnownBy := composeBilateral(knownBy)

result.commandLine = spreadFlags(presentFlags)

if len(secondaryCL) == 0 {
return result.commandLine
}

if len(secondaryCL) == 1 {
token := secondaryCL[0]
lead, bare := split(token)

input := &tokenInput{
token: token,
lead: lead,
bare: bare,
existingCL: result.commandLine,
presentFlags: presentFlags,
knownBy: bilateralKnownBy,
}
result = input.concatIf(concatenate)

return result.commandLine
}

for t, n := 0, 1; t < len(secondaryCL); {
token := secondaryCL[t]
lead, bare := split(token)

input := &tokenInput{
token: token,
lead: lead,
bare: bare,
existingCL: result.commandLine,
presentFlags: presentFlags,
knownBy: bilateralKnownBy,
}
increment := input.yoke(n, secondaryCL)
result = input.concatIf(concatenate)

t += increment
n += increment
}

return result.commandLine
}

func split(token string) (string, string) { //nolint:gocritic // pedant
var (
lead string
bare = token
)

if strings.HasPrefix(token, "--") {
lead = "--"
bare = token[2:]
} else if strings.HasPrefix(token, "-") {
lead = "-"
bare = token[1:]
}

return lead, bare
}

func spreadFlags(presentFlags PresentFlagsCollection) ThirdPartyCommandLine {
commandLine := ThirdPartyCommandLine{}

for _, flag := range presentFlags.Keys() {
option := presentFlags[flag]
dash := lo.Ternary(len(flag) == 1, "-", "--")
prefixed := dash + flag
withOption := !slices.Contains(booleans, option)

commandLine = append(commandLine, prefixed)

if withOption {
commandLine = append(commandLine, option)
}
}

return commandLine
}

func composeBilateral(knownBy KnownByCollection) KnownByCollection {
const twice = 2
bilateral := make(KnownByCollection, len(knownBy)*twice)

maps.Copy(bilateral, knownBy)

for long, short := range knownBy {
bilateral[short] = long
}

return bilateral
}
Loading

0 comments on commit 49d5493

Please sign in to comment.