Command Line Subcommands in Haskell

Here’s the translation of the Go code to Haskell, along with explanations in Markdown format suitable for Hugo:

Our program demonstrates how to create command-line subcommands in Haskell. We’ll use the optparse-applicative library, which provides a powerful and flexible way to parse command-line options.

First, let’s import the necessary modules:

import Options.Applicative
import Data.Semigroup ((<>))
import System.Environment (getArgs)

Now, let’s define our data types for the subcommands and their options:

data FooOptions = FooOptions
  { fooEnable :: Bool
  , fooName   :: String
  }

data BarOptions = BarOptions
  { barLevel :: Int
  }

data Command
  = Foo FooOptions
  | Bar BarOptions

Next, we’ll define the parsers for our subcommands:

fooOptions :: Parser FooOptions
fooOptions = FooOptions
  <$> switch
      ( long "enable"
     <> help "enable" )
  <*> strOption
      ( long "name"
     <> metavar "NAME"
     <> help "name" )

barOptions :: Parser BarOptions
barOptions = BarOptions
  <$> option auto
      ( long "level"
     <> metavar "LEVEL"
     <> help "level"
     <> value 0 )

commandParser :: Parser Command
commandParser = subparser
  ( command "foo"
    (info (Foo <$> fooOptions)
          (progDesc "Foo subcommand"))
 <> command "bar"
    (info (Bar <$> barOptions)
          (progDesc "Bar subcommand"))
  )

Now, let’s define our main function:

main :: IO ()
main = do
  cmd <- execParser opts
  case cmd of
    Foo (FooOptions enable name) -> do
      putStrLn "subcommand 'foo'"
      putStrLn $ "  enable: " ++ show enable
      putStrLn $ "  name: " ++ name
    Bar (BarOptions level) -> do
      putStrLn "subcommand 'bar'"
      putStrLn $ "  level: " ++ show level
  where
    opts = info (commandParser <**> helper)
      ( fullDesc
     <> progDesc "Program with subcommands"
     <> header "command-line-subcommands - an example CLI program" )

To run the program, compile it with GHC:

$ ghc -o command-line-subcommands command-line-subcommands.hs

Now you can invoke the subcommands:

$ ./command-line-subcommands foo --enable --name=joe
subcommand 'foo'
  enable: True
  name: joe

$ ./command-line-subcommands bar --level 8
subcommand 'bar'
  level: 8

If you try to use flags from one subcommand with another, you’ll get an error:

$ ./command-line-subcommands bar --enable
Invalid option `--enable'

Usage: command-line-subcommands bar [--level LEVEL]
  Bar subcommand

Available options:
  --level LEVEL           level
  -h,--help               Show this help text

This Haskell implementation provides a type-safe and declarative way to define command-line subcommands. The optparse-applicative library offers many more features for complex command-line interfaces, including support for positional arguments, which we haven’t demonstrated here.