Skip to content
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

Add additional generic components and helpers #224

Merged
merged 7 commits into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 152 additions & 0 deletions command/arguments.go
anujc25 marked this conversation as resolved.
Show resolved Hide resolved
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.
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
}
}
30 changes: 30 additions & 0 deletions command/errors.go
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}
}
28 changes: 28 additions & 0 deletions command/errors_test.go
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)
}
}
48 changes: 48 additions & 0 deletions command/extend.go
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.
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
}
Loading
Loading