Add typed command input bindings
This commit is contained in:
50
command.go
50
command.go
@@ -3,6 +3,7 @@ package commandkit
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// Command is an opaque command declaration. Use NewCommand or Group to create
|
||||
@@ -49,9 +50,12 @@ type Flag[I any] struct {
|
||||
}
|
||||
|
||||
type flagBinding[I any] struct {
|
||||
meta FlagMeta
|
||||
prepare func([]string) (func(*I), error)
|
||||
valid bool
|
||||
meta FlagMeta
|
||||
defaultText string
|
||||
repeated bool
|
||||
boolean bool
|
||||
prepare func([]string) (func(*I), error)
|
||||
valid bool
|
||||
}
|
||||
|
||||
// Argument represents a typed binding for a positional command argument.
|
||||
@@ -91,7 +95,7 @@ type CommandSpec[I any] struct {
|
||||
type commandPayload interface {
|
||||
validate() error
|
||||
hasRun() bool
|
||||
newInput() any
|
||||
prepare([][]string, []string, bool) (func() any, error)
|
||||
run(Invocation, any) Outcome
|
||||
}
|
||||
|
||||
@@ -107,12 +111,15 @@ func (c typedCommand[I]) hasRun() bool {
|
||||
return c.spec.Run != nil
|
||||
}
|
||||
|
||||
func (c typedCommand[I]) newInput() any {
|
||||
var input I
|
||||
if c.spec.NewInput != nil {
|
||||
input = c.spec.NewInput()
|
||||
func (c typedCommand[I]) prepare(flagValues [][]string, operands []string, enforceRequired bool) (func() any, error) {
|
||||
prepared, err := prepareInput(c.spec, flagValues, operands, enforceRequired)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &input
|
||||
return func() any {
|
||||
input := prepared.build()
|
||||
return &input
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c typedCommand[I]) run(invocation Invocation, input any) Outcome {
|
||||
@@ -222,8 +229,8 @@ func validateBindings[I any](flags []Flag[I], arguments []Argument[I]) error {
|
||||
shortNames := make(map[string]struct{}, len(flags))
|
||||
for _, flag := range flags {
|
||||
binding := flag.binding
|
||||
if binding.meta.Name == "" || strings.ContainsAny(binding.meta.Name, " \t\r\n") || strings.HasPrefix(binding.meta.Name, "-") {
|
||||
return fmt.Errorf("flag name %q must be a nonempty token without leading dashes", binding.meta.Name)
|
||||
if !validFlagName(binding.meta.Name) {
|
||||
return fmt.Errorf("flag name %q must use lowercase hyphen-separated words", binding.meta.Name)
|
||||
}
|
||||
if binding.meta.Name == "help" {
|
||||
return fmt.Errorf("flag name %q is reserved for help", binding.meta.Name)
|
||||
@@ -263,3 +270,24 @@ func validateBindings[I any](flags []Flag[I], arguments []Argument[I]) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validFlagName(name string) bool {
|
||||
if name == "" || name[0] == '-' || name[len(name)-1] == '-' {
|
||||
return false
|
||||
}
|
||||
segmentHasCharacter := false
|
||||
for _, character := range name {
|
||||
if character == '-' {
|
||||
if !segmentHasCharacter {
|
||||
return false
|
||||
}
|
||||
segmentHasCharacter = false
|
||||
continue
|
||||
}
|
||||
if !unicode.IsLower(character) && !unicode.IsDigit(character) {
|
||||
return false
|
||||
}
|
||||
segmentHasCharacter = true
|
||||
}
|
||||
return segmentHasCharacter
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user