Command Line Subcommands in F#

Here’s the translation of the Go code to F# with explanations in Markdown format suitable for Hugo:

Our first program demonstrates how to create command-line subcommands in F#. This is similar to tools like git where different subcommands (e.g., git commit, git push) have their own set of flags.

open System
open Argu

type FooArgs =
    | [<AltCommandLine("-e")>] Enable
    | [<AltCommandLine("-n")>] Name of string
with
    interface IArgParserTemplate with
        member s.Usage =
            match s with
            | Enable -> "enable the foo feature"
            | Name _ -> "specify a name for foo"

type BarArgs =
    | [<AltCommandLine("-l")>] Level of int
with
    interface IArgParserTemplate with
        member s.Usage =
            match s with
            | Level _ -> "specify the level for bar"

type CLIArguments =
    | [<CliPrefix(CliPrefix.None)>] Foo of ParseResults<FooArgs>
    | [<CliPrefix(CliPrefix.None)>] Bar of ParseResults<BarArgs>
with
    interface IArgParserTemplate with
        member s.Usage =
            match s with
            | Foo _ -> "execute foo subcommand"
            | Bar _ -> "execute bar subcommand"

[<EntryPoint>]
let main argv =
    let parser = ArgumentParser.Create<CLIArguments>()
    try
        let results = parser.ParseCommandLine(inputs = argv, raiseOnUsage = true)
        match results.GetSubCommand() with
        | Foo fooArgs ->
            printfn "subcommand 'foo'"
            printfn "  enable: %b" (fooArgs.Contains Enable)
            printfn "  name: %s" (fooArgs.GetResult(Name, defaultValue = ""))
            printfn "  tail: %A" fooArgs.UnparsedCliArgs
        | Bar barArgs ->
            printfn "subcommand 'bar'"
            printfn "  level: %d" (barArgs.GetResult(Level, defaultValue = 0))
            printfn "  tail: %A" barArgs.UnparsedCliArgs
    with
    | :? ArguParseException as ex ->
        printfn "%s" ex.Message
    0

In this F# version, we use the Argu library to handle command-line parsing. Argu is a declarative CLI argument parser that supports subcommands out of the box.

We define separate argument types for each subcommand (FooArgs and BarArgs), and then combine them into a main CLIArguments type.

The main function sets up the argument parser and handles the subcommands. It prints out the parsed arguments similar to the original example.

To run the program, compile it and use the following commands:

$ dotnet run -- foo --enable --name=joe a1 a2
subcommand 'foo'
  enable: true
  name: joe
  tail: [|"a1"; "a2"|]

$ dotnet run -- bar --level 8 a1
subcommand 'bar'
  level: 8
  tail: [|"a1"|]

$ dotnet run -- bar --enable a1
ERROR: Unrecognized command line argument: --enable

Usage: 
    bar [--level <int>]

This F# implementation provides similar functionality to the original example, with strong typing and built-in help generation provided by the Argu library.