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

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 {
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