F# / Argu - How to display help for two level command tree without throwing exception

Viewed 262

I have a two-level F# / Argu command tree. Its abbreviated version looks like that:

[<CliPrefix(CliPrefix.Dash)>]
type RunContGenArgs =
    | [<Unique>] [<EqualsAssignment>] [<AltCommandLine("-ql")>] MaxQueueLength of int

with
    interface IArgParserTemplate with
        member this.Usage =
            match this with
            | MaxQueueLength _ -> "max queue length."
and
    [<CliPrefix(CliPrefix.None)>]
    ContGenArguments =
        | [<Unique>] [<AltCommandLine("run")>]      RunContGen of ParseResults<RunContGenArgs>
    with
        interface IArgParserTemplate with
            member this.Usage =
                match this with
                | RunContGen _ -> "run Continuous Generation."

which, I then use as follows:

[<EntryPoint>]
let main argv =
    let parser = ArgumentParser.Create<ContGenArguments>(programName = "ContGen.exe")
    let results = parser.Parse argv

    match results.GetAllResults() |> ContGenTask.tryCreate with
    | Some task -> task.run()
    | None ->
        printfn "%s" (parser.PrintUsage())
        -1

If I run the command like ContGen.exe run help, then it correctly displays help for the command run. However, it then crashes with ugly exception:

Unhandled Exception: Argu.ArguParseException: USAGE: ContGen.exe runcontgen [help] [-maxqueuelength=<int>]
OPTIONS:
    -maxqueuelength, -ql=<int>
                          max queue length.
    help                  display this list of options.
   at Argu.ExceptionExiter.Argu-IExiter-Exit[a](String msg, ErrorCode errorCode) in C:\Users\eirik.tsarpalis\devel\public\Argu\src\Argu\Types.fs:line 62
   at Argu.ArgumentParser\`1.Parse(FSharpOption\`1 inputs, FSharpOption\`1 configurationReader, FSharpOption\`1 ignoreMissing, FSharpOption\`1 ignoreUnrecognized, FSharpOption\`1 raiseOnUsage) in C:\Users\eirik.tsarpalis\devel\public\Argu\src\Argu\ArgumentParser.fs:line 180
   at Program.main(String[] argv) in C:\GitHub\ClmFSharp\Clm\ContGen\Program.fs:line 8

If I change let results = parser.Parse argv into let results = parser.Parse(argv, raiseOnUsage = false), then it does not crash but does not display any help message. And then since command run can run without any second level argument, the program just keeps going instead of displaying help and quitting.

However, I need ContGen.exe run help just display help message and then quit. How can I achieve that? Thanks.

1 Answers

This is a somewhat peculiar behavior of Argu; you need to provide your own exiter to avoid the exception being thrown there.

Something along these lines:

type NonThrowingExiter() =
    interface IExiter with
        member __.Name = "Exiter" // I don't know what this is used for; I have never seen it appear anywhere
        member __.Exit (msg, code) =
            if code = ErrorCode.HelpText then
                printfn "%s" msg
                exit 0
            else
                printfn "%s" msg // Maybe have code to color the console output red here
                exit 1

To use this, create your `ArgumentParser` like this:

let argumentParser =
    Argu.ArgumentParser.Create<ContGenArguments>(helpTextMessage = "Help requested",
                                                 errorHandler = NonThrowingExiter())

(You don't actually need to create a class for this, of course; an object expression will do it just as well.)

Related