Skip to content

Commit 6876ef2

Browse files
authored
Custom help (akamensky#50)
* Finishes addressing issue akamensky#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 akamensky#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
1 parent 48862cb commit 6876ef2

File tree

8 files changed

+292
-19
lines changed

8 files changed

+292
-19
lines changed

argparse.go

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ type Command struct {
2323
happened bool
2424
parent *Command
2525
HelpFunc func(c *Command, msg interface{}) string
26+
exitOnHelp bool
2627
}
2728

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

112-
p.help()
113+
p.help("h", "help")
114+
p.exitOnHelp = true
113115
p.HelpFunc = (*Command).Usage
114116

115117
return p
@@ -128,8 +130,7 @@ func (o *Command) NewCommand(name string, description string) *Command {
128130
c.description = description
129131
c.parsed = false
130132
c.parent = o
131-
132-
c.help()
133+
c.exitOnHelp = o.exitOnHelp
133134

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

144+
// DisableHelp removes any help arguments from the commands list of arguments
145+
// This prevents prevents help from being parsed or invoked from the argument list
146+
func (o *Parser) DisableHelp() {
147+
for i, arg := range o.args {
148+
if _, ok := arg.result.(*help); ok {
149+
o.args = append(o.args[:i], o.args[i+1:]...)
150+
}
151+
}
152+
}
153+
154+
// ExitOnHelp sets the exitOnHelp variable of Parser
155+
func (o *Command) ExitOnHelp(b bool) {
156+
o.exitOnHelp = b
157+
for _, c := range o.commands {
158+
c.ExitOnHelp(b)
159+
}
160+
}
161+
162+
// SetHelp removes the previous help argument, and creates a new one with the desired sname/lname
163+
func (o *Parser) SetHelp(sname, lname string) {
164+
o.DisableHelp()
165+
o.help(sname, lname)
166+
}
167+
143168
// Flag Creates new flag type of argument, which is boolean value showing if argument was provided or not.
144169
// Takes short name, long name and pointer to options (optional).
145170
// Short name must be single character, but can be omitted by giving empty string.

argparse_examples_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package argparse
22

3-
import "fmt"
3+
import (
4+
"fmt"
5+
)
46

57
func ExampleCommand_Help() {
68
parser := NewParser("parser", "")

argparse_test.go

Lines changed: 184 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2282,9 +2282,192 @@ func TestUsageStringer(t *testing.T) {
22822282
}
22832283
}
22842284

2285-
func TestNewParserHelpFuncDefault(t *testing.T) {
2285+
func TestParserHelpFuncDefault(t *testing.T) {
22862286
parser := NewParser("parser", "")
22872287
if parser.HelpFunc == nil || parser.Help(nil) != parser.Usage(nil) {
22882288
t.Errorf("HelpFunc should default to Usage function")
22892289
}
22902290
}
2291+
2292+
func TestCommandHelpFuncDefault(t *testing.T) {
2293+
parser := NewParser("parser", "")
2294+
command := parser.NewCommand("command", "")
2295+
if command.HelpFunc != nil {
2296+
t.Errorf("HelpFunc should default to Usage function")
2297+
}
2298+
}
2299+
2300+
func TestCommandHelpFuncDefaultToParent(t *testing.T) {
2301+
parser := NewParser("parser", "")
2302+
command := parser.NewCommand("command", "")
2303+
2304+
parser.HelpFunc = func(c *Command, msg interface{}) string {
2305+
return "testing"
2306+
}
2307+
2308+
if command.Help(nil) == command.Usage(nil) || command.Help(nil) != parser.Help(nil) {
2309+
t.Errorf("command HelpFunc should default to parent function")
2310+
}
2311+
}
2312+
2313+
func TestParserExitOnHelpTrue(t *testing.T) {
2314+
exited := false
2315+
exit = func(n int) {
2316+
exited = true
2317+
}
2318+
2319+
parser := NewParser("parser", "")
2320+
2321+
print = func(...interface{}) (int, error) {
2322+
return 0, nil
2323+
}
2324+
2325+
if err := parser.Parse([]string{"parser", "-h"}); err == nil {
2326+
if !exited {
2327+
t.Errorf("Parsing help should have invoked os.Exit")
2328+
}
2329+
} else {
2330+
t.Error(err)
2331+
}
2332+
}
2333+
2334+
func TestParserExitOnHelpFalse(t *testing.T) {
2335+
exited := false
2336+
exit = func(n int) {
2337+
exited = true
2338+
}
2339+
2340+
parser := NewParser("parser", "")
2341+
parser.ExitOnHelp(false)
2342+
2343+
print = func(...interface{}) (int, error) {
2344+
return 0, nil
2345+
}
2346+
2347+
if err := parser.Parse([]string{"parser", "-h"}); exited {
2348+
t.Errorf("Parsing help should not have invoked os.Exit")
2349+
} else if err != nil {
2350+
t.Error(err)
2351+
}
2352+
}
2353+
2354+
func TestParserDisableHelp(t *testing.T) {
2355+
parser := NewParser("parser", "")
2356+
parser.DisableHelp()
2357+
if len(parser.args) > 0 {
2358+
t.Errorf("Parser should not have any arguments")
2359+
}
2360+
2361+
print = func(...interface{}) (int, error) {
2362+
return 0, nil
2363+
}
2364+
2365+
if err := parser.Parse([]string{"parser", "-h"}); err == nil {
2366+
t.Errorf("Parsing should fail, help argument shouldn't exist")
2367+
}
2368+
}
2369+
2370+
func TestParserSetHelp(t *testing.T) {
2371+
sname, lname := "x", "xyz"
2372+
parser := NewParser("parser", "")
2373+
parser.SetHelp(sname, lname)
2374+
if len(parser.args) != 1 {
2375+
t.Errorf("Parser should have one argument:\n%s", parser.Help(nil))
2376+
}
2377+
arg := parser.args[0]
2378+
if _, ok := arg.result.(*help); !ok {
2379+
t.Errorf("Argument should be %T, is %T", help{}, arg.result)
2380+
}
2381+
if arg.sname != sname {
2382+
t.Errorf("Argument short name should be %s, is %s", sname, arg.sname)
2383+
}
2384+
if arg.lname != lname {
2385+
t.Errorf("Argument long name should be %s, is %s", lname, arg.lname)
2386+
}
2387+
}
2388+
2389+
func TestCommandExitOnHelpTrue(t *testing.T) {
2390+
exited := false
2391+
exit = func(n int) {
2392+
exited = true
2393+
}
2394+
2395+
parser := NewParser("parser", "")
2396+
parser.NewCommand("command", "")
2397+
2398+
print = func(...interface{}) (int, error) {
2399+
return 0, nil
2400+
}
2401+
2402+
if err := parser.Parse([]string{"parser", "command", "-h"}); exited {
2403+
if err != nil {
2404+
t.Error(err)
2405+
}
2406+
} else {
2407+
t.Errorf("Parsing help should have invoked os.Exit")
2408+
}
2409+
}
2410+
2411+
func TestCommandExitOnHelpFalse(t *testing.T) {
2412+
exited := false
2413+
exit = func(n int) {
2414+
exited = true
2415+
}
2416+
2417+
parser := NewParser("parser", "")
2418+
parser.NewCommand("command", "")
2419+
parser.ExitOnHelp(false)
2420+
2421+
print = func(...interface{}) (int, error) {
2422+
return 0, nil
2423+
}
2424+
2425+
if err := parser.Parse([]string{"parser", "command", "-h"}); exited {
2426+
t.Error("Parsing help should not have exited")
2427+
} else if err != nil {
2428+
t.Error(err)
2429+
}
2430+
}
2431+
2432+
func TestCommandDisableHelp(t *testing.T) {
2433+
parser := NewParser("parser", "")
2434+
parser.NewCommand("command", "")
2435+
parser.DisableHelp()
2436+
if len(parser.args) > 0 {
2437+
t.Errorf("Parser should not have any arguments")
2438+
}
2439+
2440+
print = func(...interface{}) (int, error) {
2441+
return 0, nil
2442+
}
2443+
2444+
if err := parser.Parse([]string{"parser", "command", "-h"}); err == nil {
2445+
t.Errorf("Parsing should fail, help argument shouldn't exist")
2446+
}
2447+
}
2448+
2449+
func TestCommandHelpInheritance(t *testing.T) {
2450+
parser := NewParser("parser", "")
2451+
command := parser.NewCommand("command", "")
2452+
parser.ExitOnHelp(false)
2453+
2454+
if command.exitOnHelp != false {
2455+
t.Errorf("Command should inherit exitOnHelp from parent, even after creation")
2456+
}
2457+
}
2458+
2459+
func TestCommandHelpSetSnameOnly(t *testing.T) {
2460+
parser := NewParser("parser", "")
2461+
parser.SetHelp("q", "")
2462+
2463+
arg := parser.args[0]
2464+
2465+
_, ok := arg.result.(*help)
2466+
if !ok {
2467+
t.Error("Argument should be of help type")
2468+
}
2469+
2470+
if arg.sname != "h" || arg.lname != "help" {
2471+
t.Error("Help arugment names should have defaulted")
2472+
}
2473+
}

argument.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,6 @@ func (o *arg) checkShortName(argument string) (int, error) {
9898
// For shorthand argument - 0 if there is no occurrences, or count of occurrences.
9999
// Shorthand argument with parametr, mast be the only or last in the argument string.
100100
func (o *arg) check(argument string) (int, error) {
101-
// Shortcut to showing help
102-
if argument == "-h" || argument == "--help" {
103-
helpText := o.parent.Help(nil)
104-
fmt.Print(helpText)
105-
os.Exit(0)
106-
}
107-
108101
rez := o.checkLongName(argument)
109102
if rez > 0 {
110103
return rez, nil
@@ -334,13 +327,20 @@ func (o *arg) parseFileList(args []string) error {
334327
return nil
335328
}
336329

330+
// To overwrite while testing
331+
// Possibly extend to allow user overriding
332+
var exit func(int) = os.Exit
333+
var print func(...interface{}) (int, error) = fmt.Println
334+
337335
func (o *arg) parseSomeType(args []string, argCount int) error {
338336
var err error
339337
switch o.result.(type) {
340338
case *help:
341-
helpText := o.parent.Help(nil)
342-
fmt.Print(helpText)
343-
os.Exit(0)
339+
print(o.parent.Help(nil))
340+
if o.parent.exitOnHelp {
341+
exit(0)
342+
}
343+
//data of bool type is for Flag argument
344344
case *bool:
345345
err = o.parseBool(args)
346346
case *int:

command.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,17 @@ import (
44
"fmt"
55
)
66

7-
func (o *Command) help() {
7+
func (o *Command) help(sname, lname string) {
88
result := &help{}
99

10+
if lname == "" {
11+
sname, lname = "h", "help"
12+
}
13+
1014
a := &arg{
11-
result: &result,
12-
sname: "h",
13-
lname: "help",
15+
result: result,
16+
sname: sname,
17+
lname: lname,
1418
size: 1,
1519
opts: &Options{Help: "Print help information"},
1620
unique: true,
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/akamensky/argparse"
7+
)
8+
9+
func main() {
10+
// Create new parser object
11+
parser := argparse.NewParser("help", "Demonstrates changing the help argument names")
12+
parser.SetHelp("e", "example")
13+
// Create string flag
14+
parser.String("s", "string", &argparse.Options{Required: false, Help: "String argument example"})
15+
// Create string flag
16+
parser.Int("i", "int", &argparse.Options{Required: false, Help: "Integer argument example"})
17+
// Use the help function
18+
fmt.Print(parser.Parse([]string{"parser", "-e"}))
19+
}

examples/help-no-exit/help-no-exit.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/akamensky/argparse"
7+
)
8+
9+
func main() {
10+
// Create new parser object
11+
parser := argparse.NewParser("help", "Demonstrates changing the help argument names")
12+
parser.ExitOnHelp(false)
13+
// Create string flag
14+
parser.String("s", "string", &argparse.Options{Required: false, Help: "String argument example"})
15+
// Create string flag
16+
parser.Int("i", "int", &argparse.Options{Required: false, Help: "Integer argument example"})
17+
// Use the help function
18+
fmt.Println(parser.Parse([]string{"parser", "-h"}))
19+
fmt.Println("Didn't exit, still printing")
20+
}

examples/no-help/no-help.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/akamensky/argparse"
7+
)
8+
9+
func main() {
10+
// Create new parser object
11+
parser := argparse.NewParser("help", "Demonstrates disabing the help arguments")
12+
parser.DisableHelp()
13+
// Create string flag
14+
parser.String("s", "string", &argparse.Options{Required: false, Help: "String argument example"})
15+
// Create string flag
16+
parser.Int("i", "int", &argparse.Options{Required: false, Help: "Integer argument example"})
17+
18+
// parsing for -h fails
19+
fmt.Println(parser.Parse([]string{"parser", "-h", "--help", "-s", "testing", "-i", "5"}))
20+
}

0 commit comments

Comments
 (0)