Skip to content

Commit

Permalink
Custom help (#50)
Browse files Browse the repository at this point in the history
* Finishes addressing issue #29

Adds a Settings struct for creating parsers/commands with
NewParserWithSettings or NewCommandWithSettings.

Settings
	HelpDisabled -	defaults false, set true to not generate a help
			argument for parser/command
	HelpSname -	short name for the parser/command help argument.
			Can be left empty
	HelpLname -	long name for the parser/command help argument.
			Leaving empty forces use of -h/--help when
			HelpDisabled is false
	NoExitOnHelp -	defaults false, set true to not exit on help
			argument being parsed

Should resolve all outstanding help argument issues.

* Finishing Issue #29

Fixed merge conflict error due to check function return type change confusion.

* Updated Settings Object to multiple function calls

Added functions:
	DisableHelp
	SetHelp
	ExitOnHelp

Added Command.exitOnHelp

Added more tests to increase code coverage

* Updated no help example
  • Loading branch information
densestvoid committed Jan 29, 2020
1 parent 48862cb commit 6876ef2
Show file tree
Hide file tree
Showing 8 changed files with 292 additions and 19 deletions.
31 changes: 28 additions & 3 deletions argparse.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type Command struct {
happened bool
parent *Command
HelpFunc func(c *Command, msg interface{}) string
exitOnHelp bool
}

// GetName exposes Command's name field
Expand Down Expand Up @@ -109,7 +110,8 @@ func NewParser(name string, description string) *Parser {
p.args = make([]*arg, 0)
p.commands = make([]*Command, 0)

p.help()
p.help("h", "help")
p.exitOnHelp = true
p.HelpFunc = (*Command).Usage

return p
Expand All @@ -128,8 +130,7 @@ func (o *Command) NewCommand(name string, description string) *Command {
c.description = description
c.parsed = false
c.parent = o

c.help()
c.exitOnHelp = o.exitOnHelp

if o.commands == nil {
o.commands = make([]*Command, 0)
Expand All @@ -140,6 +141,30 @@ func (o *Command) NewCommand(name string, description string) *Command {
return c
}

// DisableHelp removes any help arguments from the commands list of arguments
// This prevents prevents help from being parsed or invoked from the argument list
func (o *Parser) DisableHelp() {
for i, arg := range o.args {
if _, ok := arg.result.(*help); ok {
o.args = append(o.args[:i], o.args[i+1:]...)
}
}
}

// ExitOnHelp sets the exitOnHelp variable of Parser
func (o *Command) ExitOnHelp(b bool) {
o.exitOnHelp = b
for _, c := range o.commands {
c.ExitOnHelp(b)
}
}

// SetHelp removes the previous help argument, and creates a new one with the desired sname/lname
func (o *Parser) SetHelp(sname, lname string) {
o.DisableHelp()
o.help(sname, lname)
}

// Flag Creates new flag type of argument, which is boolean value showing if argument was provided or not.
// Takes short name, long name and pointer to options (optional).
// Short name must be single character, but can be omitted by giving empty string.
Expand Down
4 changes: 3 additions & 1 deletion argparse_examples_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package argparse

import "fmt"
import (
"fmt"
)

func ExampleCommand_Help() {
parser := NewParser("parser", "")
Expand Down
185 changes: 184 additions & 1 deletion argparse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2282,9 +2282,192 @@ func TestUsageStringer(t *testing.T) {
}
}

func TestNewParserHelpFuncDefault(t *testing.T) {
func TestParserHelpFuncDefault(t *testing.T) {
parser := NewParser("parser", "")
if parser.HelpFunc == nil || parser.Help(nil) != parser.Usage(nil) {
t.Errorf("HelpFunc should default to Usage function")
}
}

func TestCommandHelpFuncDefault(t *testing.T) {
parser := NewParser("parser", "")
command := parser.NewCommand("command", "")
if command.HelpFunc != nil {
t.Errorf("HelpFunc should default to Usage function")
}
}

func TestCommandHelpFuncDefaultToParent(t *testing.T) {
parser := NewParser("parser", "")
command := parser.NewCommand("command", "")

parser.HelpFunc = func(c *Command, msg interface{}) string {
return "testing"
}

if command.Help(nil) == command.Usage(nil) || command.Help(nil) != parser.Help(nil) {
t.Errorf("command HelpFunc should default to parent function")
}
}

func TestParserExitOnHelpTrue(t *testing.T) {
exited := false
exit = func(n int) {
exited = true
}

parser := NewParser("parser", "")

print = func(...interface{}) (int, error) {
return 0, nil
}

if err := parser.Parse([]string{"parser", "-h"}); err == nil {
if !exited {
t.Errorf("Parsing help should have invoked os.Exit")
}
} else {
t.Error(err)
}
}

func TestParserExitOnHelpFalse(t *testing.T) {
exited := false
exit = func(n int) {
exited = true
}

parser := NewParser("parser", "")
parser.ExitOnHelp(false)

print = func(...interface{}) (int, error) {
return 0, nil
}

if err := parser.Parse([]string{"parser", "-h"}); exited {
t.Errorf("Parsing help should not have invoked os.Exit")
} else if err != nil {
t.Error(err)
}
}

func TestParserDisableHelp(t *testing.T) {
parser := NewParser("parser", "")
parser.DisableHelp()
if len(parser.args) > 0 {
t.Errorf("Parser should not have any arguments")
}

print = func(...interface{}) (int, error) {
return 0, nil
}

if err := parser.Parse([]string{"parser", "-h"}); err == nil {
t.Errorf("Parsing should fail, help argument shouldn't exist")
}
}

func TestParserSetHelp(t *testing.T) {
sname, lname := "x", "xyz"
parser := NewParser("parser", "")
parser.SetHelp(sname, lname)
if len(parser.args) != 1 {
t.Errorf("Parser should have one argument:\n%s", parser.Help(nil))
}
arg := parser.args[0]
if _, ok := arg.result.(*help); !ok {
t.Errorf("Argument should be %T, is %T", help{}, arg.result)
}
if arg.sname != sname {
t.Errorf("Argument short name should be %s, is %s", sname, arg.sname)
}
if arg.lname != lname {
t.Errorf("Argument long name should be %s, is %s", lname, arg.lname)
}
}

func TestCommandExitOnHelpTrue(t *testing.T) {
exited := false
exit = func(n int) {
exited = true
}

parser := NewParser("parser", "")
parser.NewCommand("command", "")

print = func(...interface{}) (int, error) {
return 0, nil
}

if err := parser.Parse([]string{"parser", "command", "-h"}); exited {
if err != nil {
t.Error(err)
}
} else {
t.Errorf("Parsing help should have invoked os.Exit")
}
}

func TestCommandExitOnHelpFalse(t *testing.T) {
exited := false
exit = func(n int) {
exited = true
}

parser := NewParser("parser", "")
parser.NewCommand("command", "")
parser.ExitOnHelp(false)

print = func(...interface{}) (int, error) {
return 0, nil
}

if err := parser.Parse([]string{"parser", "command", "-h"}); exited {
t.Error("Parsing help should not have exited")
} else if err != nil {
t.Error(err)
}
}

func TestCommandDisableHelp(t *testing.T) {
parser := NewParser("parser", "")
parser.NewCommand("command", "")
parser.DisableHelp()
if len(parser.args) > 0 {
t.Errorf("Parser should not have any arguments")
}

print = func(...interface{}) (int, error) {
return 0, nil
}

if err := parser.Parse([]string{"parser", "command", "-h"}); err == nil {
t.Errorf("Parsing should fail, help argument shouldn't exist")
}
}

func TestCommandHelpInheritance(t *testing.T) {
parser := NewParser("parser", "")
command := parser.NewCommand("command", "")
parser.ExitOnHelp(false)

if command.exitOnHelp != false {
t.Errorf("Command should inherit exitOnHelp from parent, even after creation")
}
}

func TestCommandHelpSetSnameOnly(t *testing.T) {
parser := NewParser("parser", "")
parser.SetHelp("q", "")

arg := parser.args[0]

_, ok := arg.result.(*help)
if !ok {
t.Error("Argument should be of help type")
}

if arg.sname != "h" || arg.lname != "help" {
t.Error("Help arugment names should have defaulted")
}
}
20 changes: 10 additions & 10 deletions argument.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,6 @@ func (o *arg) checkShortName(argument string) (int, error) {
// For shorthand argument - 0 if there is no occurrences, or count of occurrences.
// Shorthand argument with parametr, mast be the only or last in the argument string.
func (o *arg) check(argument string) (int, error) {
// Shortcut to showing help
if argument == "-h" || argument == "--help" {
helpText := o.parent.Help(nil)
fmt.Print(helpText)
os.Exit(0)
}

rez := o.checkLongName(argument)
if rez > 0 {
return rez, nil
Expand Down Expand Up @@ -334,13 +327,20 @@ func (o *arg) parseFileList(args []string) error {
return nil
}

// To overwrite while testing
// Possibly extend to allow user overriding
var exit func(int) = os.Exit
var print func(...interface{}) (int, error) = fmt.Println

func (o *arg) parseSomeType(args []string, argCount int) error {
var err error
switch o.result.(type) {
case *help:
helpText := o.parent.Help(nil)
fmt.Print(helpText)
os.Exit(0)
print(o.parent.Help(nil))
if o.parent.exitOnHelp {
exit(0)
}
//data of bool type is for Flag argument
case *bool:
err = o.parseBool(args)
case *int:
Expand Down
12 changes: 8 additions & 4 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ import (
"fmt"
)

func (o *Command) help() {
func (o *Command) help(sname, lname string) {
result := &help{}

if lname == "" {
sname, lname = "h", "help"
}

a := &arg{
result: &result,
sname: "h",
lname: "help",
result: result,
sname: sname,
lname: lname,
size: 1,
opts: &Options{Help: "Print help information"},
unique: true,
Expand Down
19 changes: 19 additions & 0 deletions examples/help-argument-names/help-argument-names.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package main

import (
"fmt"

"github.com/akamensky/argparse"
)

func main() {
// Create new parser object
parser := argparse.NewParser("help", "Demonstrates changing the help argument names")
parser.SetHelp("e", "example")
// Create string flag
parser.String("s", "string", &argparse.Options{Required: false, Help: "String argument example"})
// Create string flag
parser.Int("i", "int", &argparse.Options{Required: false, Help: "Integer argument example"})
// Use the help function
fmt.Print(parser.Parse([]string{"parser", "-e"}))
}
20 changes: 20 additions & 0 deletions examples/help-no-exit/help-no-exit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package main

import (
"fmt"

"github.com/akamensky/argparse"
)

func main() {
// Create new parser object
parser := argparse.NewParser("help", "Demonstrates changing the help argument names")
parser.ExitOnHelp(false)
// Create string flag
parser.String("s", "string", &argparse.Options{Required: false, Help: "String argument example"})
// Create string flag
parser.Int("i", "int", &argparse.Options{Required: false, Help: "Integer argument example"})
// Use the help function
fmt.Println(parser.Parse([]string{"parser", "-h"}))
fmt.Println("Didn't exit, still printing")
}
20 changes: 20 additions & 0 deletions examples/no-help/no-help.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package main

import (
"fmt"

"github.com/akamensky/argparse"
)

func main() {
// Create new parser object
parser := argparse.NewParser("help", "Demonstrates disabing the help arguments")
parser.DisableHelp()
// Create string flag
parser.String("s", "string", &argparse.Options{Required: false, Help: "String argument example"})
// Create string flag
parser.Int("i", "int", &argparse.Options{Required: false, Help: "Integer argument example"})

// parsing for -h fails
fmt.Println(parser.Parse([]string{"parser", "-h", "--help", "-s", "testing", "-i", "5"}))
}

0 comments on commit 6876ef2

Please sign in to comment.