-
Notifications
You must be signed in to change notification settings - Fork 15
Add additional generic components and helpers #224
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 3 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
7424524
Add additional generic components and helpers
anujc25 5a47482
Fix linter errors
anujc25 978c5bd
Add additional argument helpers
anujc25 45f3974
Combine color and string utils under stringutils package
anujc25 b6d6e7a
Fix linter errors
anujc25 26ad5bc
Address review comments
anujc25 fc53753
Address review comments
anujc25 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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,152 @@ | ||
| // 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)" | ||
| ) | ||
anujc25 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| 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 Argument(name string, val *string) Arg { | ||
| return Arg{ | ||
| Name: name, | ||
| Arity: 1, | ||
| Set: func(_ *cobra.Command, args []string, offset int) error { | ||
| *val = args[offset] | ||
| return nil | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| func OptionalArgument(name string, val *string) Arg { | ||
| arg := Argument(name, val) | ||
| arg.Optional = true | ||
| return arg | ||
| } | ||
|
|
||
| func RemainingArguments(name string, values *[]string) Arg { | ||
| return Arg{ | ||
| Name: name, | ||
| Arity: -1, | ||
| Set: func(_ *cobra.Command, args []string, offset int) error { | ||
| *values = args[offset:] | ||
| return nil | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| func OptionalRemainingArguments(name string, values *[]string) Arg { | ||
| arg := RemainingArguments(name, values) | ||
| arg.Optional = true | ||
| return arg | ||
| } | ||
|
|
||
| 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 hidden or 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 hidden or 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 hidden or 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 { | ||
anujc25 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.