getopt-generics-0.6: test/System/Console/GetOpt/GenericsSpec.hs
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE ScopedTypeVariables #-}
module System.Console.GetOpt.GenericsSpec where
import Control.Exception
import Data.Foldable (forM_)
import Data.List
import Data.Typeable
import qualified GHC.Generics as GHC
import System.Environment
import System.Exit
import System.IO
import System.IO.Silently
import Test.Hspec
import Test.Hspec.Expectations.Contrib
import System.Console.GetOpt.Generics
spec :: Spec
spec = do
part1
part2
part3
part4
part5
part6
data Foo
= Foo {
bar :: Maybe Int,
baz :: String,
bool :: Bool
}
deriving (GHC.Generic, Show, Eq)
instance Generic Foo
instance HasDatatypeInfo Foo
data NotAllowed = NotAllowed
deriving (GHC.Generic, Show, Eq)
instance Generic NotAllowed
instance HasDatatypeInfo NotAllowed
part1 :: Spec
part1 = do
describe "getArguments" $ do
it "parses command line arguments" $ do
withArgs (words "--bar 4 --baz foo") $
getArguments `shouldReturn` Foo (Just 4) "foo" False
it "allows optional arguments" $ do
withArgs (words "--baz foo") $
getArguments `shouldReturn` Foo Nothing "foo" False
it "allows boolean flags" $ do
withArgs (words "--bool --baz foo") $
getArguments `shouldReturn` Foo Nothing "foo" True
context "with invalid arguments" $ do
it "doesn't execute the action" $ do
let main = withArgs (words "--invalid") $ do
_ :: Foo <- getArguments
throwIO (ErrorCall "action")
main `shouldThrow` (== ExitFailure 1)
it "prints out an error" $ do
output <- hCapture_ [stderr] $ handle (\ (_ :: SomeException) -> return ()) $
withArgs (words "--no-such-option") $ do
_ :: Foo <- getArguments
return ()
output `shouldContain` "unrecognized"
output `shouldContain` "--no-such-option"
it "prints errors for missing options" $ do
output <- hCapture_ [stderr] $ handle (\ (_ :: SomeException) -> return ()) $
withArgs [] $ do
_ :: Foo <- getArguments
return ()
output `shouldContain` "missing option: --baz=string"
it "prints out an error for unparseable options" $ do
output <- hCapture_ [stderr] $ handle (\ (_ :: SomeException) -> return ()) $
withArgs (words "--bar foo --baz huhu") $ do
_ :: Foo <- getArguments
return ()
output `shouldContain` "cannot parse as integer (optional): foo"
it "complains about invalid overwritten options" $ do
output <- hCapture_ [stderr] $ handle (\ (_ :: SomeException) -> return ()) $
withArgs (words "--bar foo --baz huhu --bar 12") $ do
_ :: Foo <- getArguments
return ()
output `shouldContain` "cannot parse as integer (optional): foo"
context "--help" $ do
it "implements --help" $ do
output <- capture_ $ withArgs ["--help"] $
handle (\ (_ :: SomeException) -> return ()) $ do
_ :: Foo <- getArguments
return ()
mapM_ (output `shouldContain`) $
"--bar=integer" : "optional" :
"--baz=string" :
"--bool" :
[]
it "throws ExitSuccess" $ do
withArgs ["--help"] (getArguments :: IO Foo)
`shouldThrow` (== ExitSuccess)
it "contains help message about --help" $ do
output <- capture_ $ withArgs ["--help"] $
handle (\ (_ :: SomeException) -> return ()) $ do
_ :: Foo <- getArguments
return ()
output `shouldContain` "show help and exit"
it "does not contain trailing spaces" $ do
output <- capture_ $ withArgs ["--help"] $
handle (\ (_ :: SomeException) -> return ()) $ do
_ :: Foo <- getArguments
return ()
forM_ (lines output) $ \ line ->
line `shouldSatisfy` (not . (" " `isSuffixOf`))
it "throws an exception when the options datatype is not allowed" $ do
output <- hCapture_ [stderr] $
withArgs ["--help"] $
handle (\ (_ :: SomeException) -> return ()) $ do
_ :: NotAllowed <- getArguments
return ()
output `shouldContain` "doesn't support constructors without field labels"
describe "parseArguments" $ do
it "allows to overwrite String options" $ do
parseArguments "header" [] (words "--baz one --baz two")
`shouldBe` Success (Foo Nothing "two" False)
data ListOptions
= ListOptions {
multiple :: [Int]
}
deriving (GHC.Generic, Show, Eq)
instance Generic ListOptions
instance HasDatatypeInfo ListOptions
part2 :: Spec
part2 = do
describe "getArguments" $ do
it "allows to interpret multiple uses of the same option as lists" $ do
withArgs (words "--multiple 23 --multiple 42") $ do
getArguments `shouldReturn` ListOptions [23, 42]
it "complains about invalid list arguments" $ do
output <- hCapture_ [stderr] $
withArgs (words "--multiple foo --multiple 13") $
handle (\ (_ :: SomeException) -> return ()) $ do
_ :: ListOptions <- getArguments
return ()
output `shouldContain` "cannot parse as integer (multiple possible): foo"
data CamelCaseOptions
= CamelCaseOptions {
camelCase :: String
}
deriving (GHC.Generic, Show, Eq)
instance Generic CamelCaseOptions
instance HasDatatypeInfo CamelCaseOptions
part3 :: Spec
part3 = do
describe "getArguments" $ do
it "turns camelCase selectors to lowercase and seperates with a dash" $ do
withArgs (words "--camel-case foo") $ do
getArguments `shouldReturn` CamelCaseOptions "foo"
describe "parseArguments" $ do
it "help does not contain camelCase flags" $ do
let OutputAndExit output :: Result CamelCaseOptions
= parseArguments "header" [] ["--help"]
output `shouldNotContain` "camelCase"
output `shouldContain` "camel-case"
it "error messages don't contain camelCase flags" $ do
let Errors errs :: Result CamelCaseOptions
= parseArguments "header" [] ["--bla"]
show errs `shouldNotContain` "camelCase"
show errs `shouldContain` "camel-case"
context "AddShortOption" $ do
it "allows modifiers for short options" $ do
parseArguments "header" [AddShortOption "camel-case" 'x'] (words "-x foo")
`shouldBe` Success (CamelCaseOptions "foo")
it "allows modifiers in camelCase" $ do
parseArguments "header" [AddShortOption "camelCase" 'x'] (words "-x foo")
`shouldBe` Success (CamelCaseOptions "foo")
let parse :: [String] -> Result CamelCaseOptions
parse = parseArguments "header" [AddShortOption "camelCase" 'x']
it "includes the short option in the help" $ do
let OutputAndExit output = parse ["--help"]
output `shouldContain` "-x string"
context "RenameOption" $ do
it "allows to rename options" $ do
parseArguments "header" [RenameOption "camelCase" "bla"] (words "--bla foo")
`shouldBe` Success (CamelCaseOptions "foo")
let parse = parseArguments "header"
[RenameOption "camelCase" "foo", RenameOption "camelCase" "bar"]
it "allows to shadow earlier modifiers with later modifiers" $ do
parse (words "--bar foo")
`shouldBe` Success (CamelCaseOptions "foo")
let Errors errs = parse (words "--foo foo")
show errs `shouldContain` "unknown argument: foo"
it "contains renamed options in error messages" $ do
let Errors errs = parse []
show errs `shouldNotContain` "camelCase"
show errs `shouldContain` "camel-case"
it "allows to address fields in Modifiers in slugified form" $ do
parseArguments "header" [RenameOption "camel-case" "foo"] (words "--foo bar")
`shouldBe` Success (CamelCaseOptions "bar")
data WithUnderscore
= WithUnderscore {
_withUnderscore :: String
}
deriving (GHC.Generic, Show, Eq)
instance Generic WithUnderscore
instance HasDatatypeInfo WithUnderscore
part4 :: Spec
part4 = do
describe "parseArguments" $ do
it "ignores leading underscores in field names" $ do
parseArguments "header" [] (words "--with-underscore foo")
`shouldBe` Success (WithUnderscore "foo")
data WithPositionalArguments
= WithPositionalArguments {
positionalArguments :: [String],
someFlag :: Bool
}
deriving (GHC.Generic, Show, Eq)
instance Generic WithPositionalArguments
instance HasDatatypeInfo WithPositionalArguments
part5 :: Spec
part5 = do
describe "parseArguments" $ do
context "UseForPositionalArguments" $ do
it "allows positionalArguments" $ do
parseArguments "header"
[UseForPositionalArguments "positionalArguments"]
(words "foo bar --some-flag")
`shouldBe` Success (WithPositionalArguments ["foo", "bar"] True)
it "disallows to specify the option used for positional arguments" $ do
parseArguments "header"
[UseForPositionalArguments "positionalArguments"]
(words "--positional-arguments foo")
`shouldBe`
(Errors ["unrecognized option `--positional-arguments'\n"]
:: Result WithPositionalArguments)
it "complains about fields that don't have type [String]" $ do
parseArguments "header"
[UseForPositionalArguments "someFlag"]
(words "doesn't matter")
`shouldBe`
(Errors ["UseForPositionalArguments can only be used for fields of type [String] not Bool"]
:: Result WithPositionalArguments)
data CustomFields
= CustomFields {
custom :: Custom,
customList :: [Custom],
customMaybe :: Maybe Custom
}
deriving (GHC.Generic, Show, Eq)
instance Generic CustomFields
instance HasDatatypeInfo CustomFields
data Custom
= CFoo
| CBar
| CBaz
deriving (Show, Eq, Typeable)
instance Option Custom where
argumentType Proxy = "custom"
parseArgument x = case x of
"foo" -> Just CFoo
"bar" -> Just CBar
"baz" -> Just CBaz
_ -> Nothing
part6 :: Spec
part6 = do
describe "parseArguments" $ do
context "CustomFields" $ do
it "allows easy implementation of custom field types" $ do
parseArguments "header" []
(words "--custom foo --custom-list bar --custom-maybe baz")
`shouldBe`
Success (CustomFields CFoo [CBar] (Just CBaz))