Skip to content

Commit

Permalink
Add GetRequiredArg helper
Browse files Browse the repository at this point in the history
  • Loading branch information
DavidGamba committed Dec 29, 2023
1 parent c83ca56 commit f6f46c4
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 1 deletion.
1 change: 1 addition & 0 deletions api.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type programTree struct {
Name string
Description string
SynopsisArgs []help.SynopsisArg
SynopsisArgsIdx int // idx for the GetRequiredArg helper
ChildCommands map[string]*programTree
ChildOptions map[string]*option.Option
UnknownOptions []*option.Option // Track unknown options in order in case they need to be passed to the remaining array.
Expand Down
31 changes: 31 additions & 0 deletions helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package getoptions

import (
"fmt"

"github.com/DavidGamba/go-getoptions/text"
)

// GetRequiredArg - Get the next argument from the args list and error if it doesn't exist.
// By default the error will include the HelpSynopsis section but it can be overriden with the list of sections or getoptions.HelpNone.
//
// If the arguments have been named with `opt.HelpSynopsisArg` then the error will include the argument name.
func (gopt *GetOpt) GetRequiredArg(args []string, sections ...HelpSection) (string, []string, error) {
if len(args) < 1 {
if len(gopt.programTree.SynopsisArgs) > gopt.programTree.SynopsisArgsIdx {
argName := gopt.programTree.SynopsisArgs[gopt.programTree.SynopsisArgsIdx].Arg
fmt.Fprintf(Writer, text.ErrorMissingRequiredNamedArgument+"\n", argName)
} else {
fmt.Fprintf(Writer, "%s\n", text.ErrorMissingRequiredArgument)
}
if sections != nil {
fmt.Fprintf(Writer, "%s", gopt.Help(sections...))
} else {
fmt.Fprintf(Writer, "%s", gopt.Help(HelpSynopsis))
}
gopt.programTree.SynopsisArgsIdx++
return "", args, ErrorHelpCalled
}
gopt.programTree.SynopsisArgsIdx++
return args[0], args[1:], nil
}
193 changes: 193 additions & 0 deletions public_api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"os"
"path/filepath"
"reflect"
"testing"
"time"
Expand All @@ -14,6 +15,10 @@ import (
"github.com/DavidGamba/go-getoptions/text"
)

func executable(msg string) string {
return fmt.Sprintf(msg, filepath.Base(os.Args[0]))
}

// Test helper to compare two string outputs and find the first difference
func firstDiff(got, expected string) string {
same := ""
Expand Down Expand Up @@ -3786,3 +3791,191 @@ func TestSetCalled(t *testing.T) {
}
})
}

func TestGetRequiredArg(t *testing.T) {
fn := func(ctx context.Context, opt *getoptions.GetOpt, args []string) error {
_, args, err := opt.GetRequiredArg(args)
if err != nil {
return err
}
_, args, err = opt.GetRequiredArg(args)
if err != nil {
return err
}
_, _, err = opt.GetRequiredArg(args)
if err != nil {
return err
}
return nil
}
t.Run("no args", func(t *testing.T) {
helpBuf := new(bytes.Buffer)
getoptions.Writer = helpBuf
opt := getoptions.New()
opt.SetCommandFn(fn)
opt.HelpCommand("help")
remaining, err := opt.Parse([]string{})
if err != nil {
t.Errorf("Unexpected error: %s", err)
}
err = opt.Dispatch(context.Background(), remaining)
if !errors.Is(err, getoptions.ErrorHelpCalled) {
t.Errorf("Unexpected error: %s", err)
}

expected := executable(`ERROR: Missing required argument
SYNOPSIS:
%s [--help] [<args>]
`)
if helpBuf.String() != expected {
t.Errorf("Wrong output:\n%s\n", firstDiff(helpBuf.String(), expected))
}
})
t.Run("no args", func(t *testing.T) {
helpBuf := new(bytes.Buffer)
getoptions.Writer = helpBuf
opt := getoptions.New()
opt.SetCommandFn(fn)
opt.HelpCommand("help")
remaining, err := opt.Parse([]string{"arg1", "arg2"})
if err != nil {
t.Errorf("Unexpected error: %s", err)
}
err = opt.Dispatch(context.Background(), remaining)
if !errors.Is(err, getoptions.ErrorHelpCalled) {
t.Errorf("Unexpected error: %s", err)
}

expected := executable(`ERROR: Missing required argument
SYNOPSIS:
%s [--help] [<args>]
`)
if helpBuf.String() != expected {
t.Errorf("Wrong output:\n%s\n", firstDiff(helpBuf.String(), expected))
}
})
t.Run("no named args", func(t *testing.T) {
helpBuf := new(bytes.Buffer)
getoptions.Writer = helpBuf
opt := getoptions.New()
opt.SetCommandFn(fn)
opt.HelpSynopsisArg("<arg1>", "arg1 desc")
opt.HelpSynopsisArg("<arg2>", "arg2 desc")
opt.HelpSynopsisArg("<arg3>", "arg3 desc")
opt.HelpCommand("help")
remaining, err := opt.Parse([]string{})
if err != nil {
t.Errorf("Unexpected error: %s", err)
}
err = opt.Dispatch(context.Background(), remaining)
if !errors.Is(err, getoptions.ErrorHelpCalled) {
t.Errorf("Unexpected error: %s", err)
}

expected := executable(`ERROR: Missing <arg1>
SYNOPSIS:
%s [--help] <arg1> <arg2> <arg3>
`)
if helpBuf.String() != expected {
t.Errorf("Wrong output:\n%s\n", firstDiff(helpBuf.String(), expected))
}
})
t.Run("no named args", func(t *testing.T) {
helpBuf := new(bytes.Buffer)
getoptions.Writer = helpBuf
opt := getoptions.New()
opt.SetCommandFn(fn)
opt.HelpSynopsisArg("<arg1>", "arg1 desc")
opt.HelpSynopsisArg("<arg2>", "arg2 desc")
opt.HelpSynopsisArg("<arg3>", "arg3 desc")
opt.HelpCommand("help")
remaining, err := opt.Parse([]string{"arg1", "arg2"})
if err != nil {
t.Errorf("Unexpected error: %s", err)
}
err = opt.Dispatch(context.Background(), remaining)
if !errors.Is(err, getoptions.ErrorHelpCalled) {
t.Errorf("Unexpected error: %s", err)
}

expected := executable(`ERROR: Missing <arg3>
SYNOPSIS:
%s [--help] <arg1> <arg2> <arg3>
`)
if helpBuf.String() != expected {
t.Errorf("Wrong output:\n%s\n", firstDiff(helpBuf.String(), expected))
}
})
t.Run("help section", func(t *testing.T) {
fn := func(ctx context.Context, opt *getoptions.GetOpt, args []string) error {
_, _, err := opt.GetRequiredArg(args, getoptions.HelpNone)
if err != nil {
return err
}
return nil
}
helpBuf := new(bytes.Buffer)
getoptions.Writer = helpBuf
opt := getoptions.New()
opt.SetCommandFn(fn)
opt.HelpSynopsisArg("<arg1>", "arg1 desc")
opt.HelpSynopsisArg("<arg2>", "arg2 desc")
opt.HelpSynopsisArg("<arg3>", "arg3 desc")
opt.HelpCommand("help")
remaining, err := opt.Parse([]string{})
if err != nil {
t.Errorf("Unexpected error: %s", err)
}
err = opt.Dispatch(context.Background(), remaining)
if !errors.Is(err, getoptions.ErrorHelpCalled) {
t.Errorf("Unexpected error: %s", err)
}

expected := `ERROR: Missing <arg1>
`
if helpBuf.String() != expected {
t.Errorf("Wrong output:\n%s\n", firstDiff(helpBuf.String(), expected))
}
})
t.Run("help section", func(t *testing.T) {
fn := func(ctx context.Context, opt *getoptions.GetOpt, args []string) error {
_, _, err := opt.GetRequiredArg(args, getoptions.HelpName, getoptions.HelpSynopsis)
if err != nil {
return err
}
return nil
}
helpBuf := new(bytes.Buffer)
getoptions.Writer = helpBuf
opt := getoptions.New()
opt.SetCommandFn(fn)
opt.HelpSynopsisArg("<arg1>", "arg1 desc")
opt.HelpSynopsisArg("<arg2>", "arg2 desc")
opt.HelpSynopsisArg("<arg3>", "arg3 desc")
opt.HelpCommand("help")
remaining, err := opt.Parse([]string{})
if err != nil {
t.Errorf("Unexpected error: %s", err)
}
err = opt.Dispatch(context.Background(), remaining)
if !errors.Is(err, getoptions.ErrorHelpCalled) {
t.Errorf("Unexpected error: %s", err)
}

expected := executable(`ERROR: Missing <arg1>
NAME:
%s
SYNOPSIS:
%[1]s [--help] <arg1> <arg2> <arg3>
`)
if helpBuf.String() != expected {
t.Errorf("Wrong output:\n%s\n", firstDiff(helpBuf.String(), expected))
}
})
}
4 changes: 4 additions & 0 deletions text/variables.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ var ErrorAmbiguousArgument = "Ambiguous option '%s', matches %v!"
// It has a string placeholder '%s' for the name of the missing option.
var ErrorMissingRequiredOption = "Missing required parameter '%s'"

var ErrorMissingRequiredArgument = "ERROR: Missing required argument"

var ErrorMissingRequiredNamedArgument = "ERROR: Missing %s"

// ErrorArgumentIsNotKeyValue holds the text for Map type options where the argument is not of key=value type.
// It has a string placeholder '%s' for the name of the option missing the argument.
var ErrorArgumentIsNotKeyValue = "Argument error for option '%s': Should be of type 'key=value'!"
Expand Down
3 changes: 2 additions & 1 deletion user_help.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ type HelpSection int

// Help Output Types
const (
helpDefaultName HelpSection = iota
HelpNone HelpSection = iota
helpDefaultName
HelpName
HelpSynopsis
HelpCommandList
Expand Down

0 comments on commit f6f46c4

Please sign in to comment.