1

Using cmdargs, is there a convenient way to print an error message and exit if a mandatory argument is missing? E.g. right now I have something like:

foo = Foo{script = def &= args &= typ "SCRIPT"}

main = do
   scriptName <- script <$> cmdArgs foo
   -- ...

If I run this program and don't pass the SCRIPT argument, scriptName is simply an empty string. Do I really have to manually check for and handle that using something like Control.Monad.Except?

Peter
  • 2,919
  • 1
  • 16
  • 35
  • In the [documentation](http://hackage.haskell.org/package/cmdargs-0.10.20/docs/System-Console-CmdArgs-Implicit.html#v:opt) it says: ''Note that all flags in CmdArgs are optional, and if omitted will use their default value.". You might want to use something like `when (null scriptName) (error "missing script name")` or similar. – jpmarinier Dec 28 '20 at 20:59
  • I don't know whether cmdargs has special support for this check or not, but one thing I am confident of: no matter what solution you end up with, you should use `Maybe String` instead of `String`, so that you can differentiate between "argument not supplied" and "argument supplied, and that argument was an empty string". – Daniel Wagner Dec 28 '20 at 21:06

1 Answers1

0

A partial and therefore somewhat unsatisfying solution.

{-# LANGUAGE DeriveDataTypeable #-}

module Main where

import Control.Monad
import System.Console.CmdArgs
import System.Console.CmdArgs.Explicit
import System.IO

helpAndExit :: (Data a) => String -> a -> IO ()
helpAndExit msg args =
    do
    let cm = cmdArgsMode args
    let ht = msg ++ "\n\n"
             ++ show cm
    cmdArgsApply (CmdArgs{cmdArgsValue=args,
                          cmdArgsHelp = Just ht,
                          cmdArgsVersion = Nothing,
                          cmdArgsVerbosity = Nothing } )
    return ()


data FooArgs = FooArgs{script :: String, thing :: String }
           deriving (Show, Data, Typeable)

fooArgs = cmdArgsMode $ FooArgs{
                    script = def &= typ "SCRIPT" &= help "Script",
                    thing = def &= help "Thing"
                }


main :: IO()
main = do
    ca <- cmdArgsRun fooArgs
    let scriptName = script ca
    when (null $ scriptName)
         (helpAndExit "scriptName is mandatory" ca)
    putStrLn scriptName

This is only a partial solution because parameter specific help is lost. With --help:

$ stack run -- --help
The fooargs program

fooargs [OPTIONS]

Common flags:
  -s --script=SCRIPT  Script
  -t --thing=ITEM     Thing
  -? --help           Display help message
  -V --version        Print version information

When not passing a mandatory parameter:

scriptName is mandatory

fooargs [OPTIONS]

Common flags:
  -s --script=ITEM
  -t --thing=ITEM
  -? --help         Display help message
  -V --version      Print version information

This loss of help info for --script and --thing is possibly because CmdArgs.cmdArgsPrivate is not initialised, which also causes a compiler warning. Or it could be related to this caveat in the doco

Values created with annotations are not pure - the first time they are computed they will include the annotations, but subsequently they will not.

It is a shame to post a partial answer, and I solved my problem another way, but since the question has gone unanswered for a few years, perhaps other people can improve this one. It does seem like an obvious use case to me.

Adam Burke
  • 724
  • 1
  • 7
  • 21