-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add additional generic components and helpers
- Loading branch information
Showing
13 changed files
with
1,233 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
// Copyright 2025 VMware, Inc. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package command | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/spf13/cobra" | ||
) | ||
|
||
const ( | ||
NameArgumentName = "name" | ||
NamesArgumentName = "name(s)" | ||
) | ||
|
||
var ErrIgnoreArg = fmt.Errorf("ignore argument") | ||
|
||
type Arg struct { | ||
Name string | ||
Arity int | ||
Optional bool | ||
Set func(cmd *cobra.Command, args []string, offset int) error | ||
} | ||
|
||
func Args(cmd *cobra.Command, argDefs ...Arg) { | ||
cmd.Args = func(cmd *cobra.Command, args []string) error { | ||
offset := 0 | ||
|
||
for _, argDef := range argDefs { | ||
arity := argDef.Arity | ||
if arity == -1 { | ||
// consume all remaining args | ||
arity = len(args) - offset | ||
} | ||
if len(args)-offset < arity { | ||
if argDef.Optional { | ||
continue | ||
} | ||
// TODO create a better message saying what is missing | ||
return fmt.Errorf("missing required argument(s)") | ||
} | ||
|
||
if err := argDef.Set(cmd, args, offset); err != nil { | ||
if err == ErrIgnoreArg { | ||
continue | ||
} | ||
return err | ||
} | ||
|
||
offset += arity | ||
} | ||
|
||
// no additional args | ||
return cobra.NoArgs(cmd, args[offset:]) | ||
} | ||
|
||
addArgsToUseString(cmd, argDefs) | ||
} | ||
|
||
func NameArg(name *string) Arg { | ||
return Arg{ | ||
Name: NameArgumentName, | ||
Arity: 1, | ||
Set: func(_ *cobra.Command, args []string, offset int) error { | ||
*name = args[offset] | ||
return nil | ||
}, | ||
} | ||
} | ||
|
||
func OptionalNameArg(name *string) Arg { | ||
arg := NameArg(name) | ||
arg.Optional = true | ||
return arg | ||
} | ||
|
||
func NamesArg(names *[]string) Arg { | ||
return Arg{ | ||
Name: NamesArgumentName, | ||
Arity: -1, | ||
Set: func(_ *cobra.Command, args []string, offset int) error { | ||
*names = args[offset:] | ||
return nil | ||
}, | ||
} | ||
} | ||
|
||
func BareDoubleDashArgs(values *[]string) Arg { | ||
return Arg{ | ||
Arity: -1, | ||
Set: func(cmd *cobra.Command, args []string, _ int) error { | ||
if cmd.ArgsLenAtDash() == -1 { | ||
return nil | ||
} | ||
*values = args[cmd.ArgsLenAtDash():] | ||
return nil | ||
}, | ||
} | ||
} | ||
|
||
// addArgsToUseString automatically adds the argument names to the Use field of the command | ||
func addArgsToUseString(cmd *cobra.Command, argDefs []Arg) { | ||
for i := range argDefs { | ||
name := argDefs[i].Name | ||
if name == "" { | ||
continue | ||
} | ||
|
||
if argDefs[i].Optional { | ||
name = fmt.Sprintf("[%s]", name) | ||
} else { | ||
name = fmt.Sprintf("<%s>", name) | ||
} | ||
|
||
cmd.Use += " " + name | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// Copyright 2025 VMware, Inc. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package command | ||
|
||
var SilentError = &silentError{} | ||
|
||
type silentError struct { | ||
err error | ||
} | ||
|
||
func (e *silentError) Error() string { | ||
if e.err == nil { | ||
return "" | ||
} | ||
return e.err.Error() | ||
} | ||
|
||
func (e *silentError) Unwrap() error { | ||
return e.err | ||
} | ||
|
||
func (e *silentError) Is(err error) bool { | ||
_, ok := err.(*silentError) | ||
return ok | ||
} | ||
|
||
func SilenceError(err error) error { | ||
return &silentError{err: err} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
// Copyright 2025 VMware, Inc. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package command | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"testing" | ||
) | ||
|
||
func TestSilenceError(t *testing.T) { | ||
err := fmt.Errorf("test error") | ||
silentErr := SilenceError(err) | ||
|
||
if errors.Is(err, SilentError) { | ||
t.Errorf("expected error to not be silent, got %#v", err) | ||
} | ||
if !errors.Is(silentErr, SilentError) { | ||
t.Errorf("expected error to be silent, got %#v", err) | ||
} | ||
if expected, actual := err, errors.Unwrap(silentErr); expected != actual { | ||
t.Errorf("errors expected to match, expected %v, actually %v", expected, actual) | ||
} | ||
if expected, actual := err.Error(), silentErr.Error(); expected != actual { | ||
t.Errorf("errors expected to match, expected %q, actually %q", expected, actual) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
// Copyright 2025 VMware, Inc. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package command | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/spf13/cobra" | ||
) | ||
|
||
func Sequence(items ...func(cmd *cobra.Command, args []string) error) func(*cobra.Command, []string) error { | ||
return func(cmd *cobra.Command, args []string) error { | ||
for i := range items { | ||
if err := items[i](cmd, args); err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} | ||
} | ||
|
||
func Visit(cmd *cobra.Command, f func(c *cobra.Command) error) error { | ||
err := f(cmd) | ||
if err != nil { | ||
return err | ||
} | ||
for _, c := range cmd.Commands() { | ||
err := Visit(c, f) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
type commandKey struct{} | ||
|
||
func WithCommand(ctx context.Context, cmd *cobra.Command) context.Context { | ||
return context.WithValue(ctx, commandKey{}, cmd) | ||
} | ||
|
||
func CommandFromContext(ctx context.Context) *cobra.Command { | ||
if cmd, ok := ctx.Value(commandKey{}).(*cobra.Command); ok { | ||
return cmd | ||
} | ||
return nil | ||
} |
Oops, something went wrong.