The following is an informal proposal for a new error handling method in the standard flag package. I'm posting it here for a preliminary discussion. If there's any interest in it I'll prepare a more thorough proposal and submit it correctly
I am calling this method KeepOnError. KeepOnError is similar to ContinueOnError but unlike ContinueOnError it will retain unrecognised arguments in the argument list and will not return an error. I find this useful for building flag hierarchies, particularly for programs that have modes of operation. To help illustrate what I mean I will use the following examples: program -verbose execute -cpu=2 myfile or: program -verbose summarise -lang=en myfile In these examples, 'execute' and 'summarise' are two different modes to the same program. The -verbose flag is common to both modes. The following is a sketch showing how I might implement the above examples using ContinueOnError: flgs := flag NewFlagSet("program", flag.ContinueOnError) var verbose bool flgs.BoolVar(&verbose, "verbose", false, "verbose output") err := flag.Parse(args) if err != nil { if errors.Is(err, flag.ErrHelp) { fmt.Println("modes: EXECUTE, SUMMARISE") return nil } return err } args = flgs.Args() if verbose { setVerbose() } var mode string if len(args) > 0 { mode = strings.ToUpper(args[0]) } switch mode { default: return errors.New("unrecognised mode") case "EXECUTE": execute(args[1:]) case "SUMMARISE": summarise(args[1:]) } Assuming the execute() and summarise() functions create their own flags then that will more-or-less implement the above examples. Now, say I want to give the program a default mode to save the user spelling it out every time. With some small changes to the above implementation I could then write: program myfile or: program -verbose myfile But I couldn't write: program -cpu=2 myfile This is because -cpu is not recognised by the top-level flag set. And even when using ContinueOnError and ignoring the returned error, the Parse() function will swallow the -cpu flag. The proposed KeepOnError will retain the -cpu argument in the Args() list. This allows the argument to be passed on to the default function. With KeepOnError we could even mix the order of the top-level arguments with the mode specific arguments. For example: program -cpu=2 -verbose myfile As a user of this example "program" I would find this level of freedom more convenient and user-friendly. I don't need to think about the ordering of arguments. All I need to remember is that the program has a -cpu flag and a -verbose flag. My proposed implementation of KeepOnError is very simple. The patch is given below. The diff has been made against the 1.24.0 version of the flag package. The existing flag package tests all pass. I've not yet written any new tests for KeepOnError but I'm happy to do so. I am aware that there are well supported third-party libraries that perhaps allow this level of control but I think this simple change to the standard library would be very helpful. Is there anything I've missed or is there maybe a good way of doing this already using the standard library? diff -urN a/flag.go b/flag.go --- a/flag.go 2025-02-10 23:33:55.000000000 +0000 +++ b/flag.go 2025-02-28 08:22:26.843882403 +0000 @@ -100,6 +100,9 @@ // but no such flag is defined. var ErrHelp = errors.New("flag: help requested") +// errKeep is returned by parseOne() when an argument is not recognised +var errKeep = errors.New("keep error") + // errParse is returned by Set if a flag's value fails to parse, such as with an invalid integer for Int. // It then gets wrapped through failf to provide more information. var errParse = errors.New("parse error") @@ -379,6 +382,7 @@ ContinueOnError ErrorHandling = iota // Return a descriptive error. ExitOnError // Call os.Exit(2) or for -h/-help Exit(0). PanicOnError // Call panic with a descriptive error. + KeepOnError // Keep any arguments that aren't handled and continue parsing. ) // A FlagSet represents a set of defined flags. The zero value of a FlagSet @@ -1112,6 +1116,9 @@ f.usage() return false, ErrHelp } + if f.errorHandling == KeepOnError { + return false, errKeep + } return false, f.failf("flag provided but not defined: -%s", name) } @@ -1153,7 +1160,20 @@ func (f *FlagSet) Parse(arguments []string) error { f.parsed = true f.args = arguments + + // arg and keep are used by the KeepOnError handler + var arg string + var keep []string + + defer func() { + f.args = append(keep, f.args...) + }() + for { + if len(f.args) > 0 { + arg = f.args[0] + } + seen, err := f.parseOne() if seen { continue @@ -1161,6 +1181,7 @@ if err == nil { break } + switch f.errorHandling { case ContinueOnError: return err @@ -1171,8 +1192,15 @@ os.Exit(2) case PanicOnError: panic(err) + case KeepOnError: + if errors.Is(err, errKeep) { + keep = append(keep, arg) + } else { + return err + } } } + return nil } -- You received this message because you are subscribed to the Google Groups "golang-nuts" group. To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscr...@googlegroups.com. To view this discussion visit https://groups.google.com/d/msgid/golang-nuts/6ae5687f-948c-4e6d-b392-cac6ceeb3a7dn%40googlegroups.com.