packages feed

getopt-generics 0.5 → 0.6

raw patch · 9 files changed

+522/−21 lines, 9 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ System.Console.GetOpt.Generics: AddOptionHelp :: String -> String -> Modifier
+ System.Console.GetOpt.Generics: Proxy :: Proxy
+ System.Console.GetOpt.Generics: class (SingI [[*]] (Code a), All [*] (SingI [*]) (Code a)) => Generic a
+ System.Console.GetOpt.Generics: class HasDatatypeInfo a
+ System.Console.GetOpt.Generics: data Proxy (t :: k) :: k -> *

Files

+ examples/Example.hs view
@@ -0,0 +1,28 @@+{-# LANGUAGE DeriveGeneric #-}++module Example where++import qualified GHC.Generics+import           System.Console.GetOpt.Generics++-- we specify a data type that maps to the commandline arguments++data Foo+  = Foo {+      bar :: String+    , baz :: Maybe String+    , qux :: Int+    }+    deriving (Eq, Show, GHC.Generics.Generic)++instance Generic Foo+instance HasDatatypeInfo Foo++-- give it go++main :: IO ()+main = do+  myFoo <- getArguments+  print $ bar myFoo+  print $ baz myFoo+  print $ qux myFoo
getopt-generics.cabal view
@@ -1,13 +1,13 @@--- This file has been generated from package.yaml by Cabalize.+-- This file has been generated from package.yaml by hpack. ----- see: https://github.com/sol/cabalize+-- see: https://github.com/sol/hpack  name:           getopt-generics-version:        0.5+version:        0.6 synopsis:       Simple command line argument parsing-description:-  "getopt-generics" tries to make it very simple to create command line argument parsers. An introductory example can be found in the <https://github.com/zalora/getopt-generics#getopt-generics README>.+description:    "getopt-generics" tries to make it very simple to create command line argument parsers. An introductory example can be found in the <https://github.com/zalora/getopt-generics#getopt-generics README>. category:       Console, System+bug-reports:    https://github.com/zalora/getopt-generics/issues author:         Linh Nguyen, Sönke Hahn maintainer:     linh.nguyen@zalora.com, soenke.hahn@zalora.com copyright:      Zalora South East Asia Pte Ltd@@ -40,6 +40,17 @@   type: exitcode-stdio-1.0   hs-source-dirs: src, test, examples   main-is: Spec.hs+  other-modules:+      System.Console.GetOpt.Generics.Internal+      System.Console.GetOpt.Generics.Modifier+      System.Console.GetOpt.Generics.Result+      System.Console.GetOpt.Generics+      ExamplesSpec+      System.Console.GetOpt.Generics.InternalSpec+      System.Console.GetOpt.Generics.ModifierSpec+      System.Console.GetOpt.Generics.ResultSpec+      System.Console.GetOpt.GenericsSpec+      Example   build-depends:       base == 4.*     , base-compat >= 0.7
src/System/Console/GetOpt/Generics.hs view
@@ -33,6 +33,10 @@   deriveShortOptions,   -- * Available Field Types   Option(..),+  -- * Re-exports from "Generics.SOP"+  Generic,+  HasDatatypeInfo,+  Proxy(..)  ) where  import           Prelude ()@@ -167,7 +171,7 @@       (mkShortOptions modifiers name)       [mkLongOption modifiers name]       _toOption-      ""+      (getHelpText modifiers name)  toOptDescr :: NS OptDescrE xs -> Maybe (OptDescr (NS FieldState xs)) toOptDescr (Z (OptDescrE (Just a))) = Just $ fmap Z a
src/System/Console/GetOpt/Generics/Modifier.hs view
@@ -10,6 +10,7 @@   mkLongOption,   hasPositionalArgumentsField,   isPositionalArgumentsField,+  getHelpText,    deriveShortOptions, @@ -39,37 +40,45 @@     -- ^ @UseForPositionalArguments fieldName@ fills the field addressed by     --   @fieldName@ with the positional arguments (i.e. arguments that don't     --   correspond to a flag). The field has to have type @['String']@.+  | AddOptionHelp String String+    -- ^ @AddOptionHelp fieldName helpText@ adds a help text for the option+    --   @fieldName@.   deriving (Show, Eq, Ord)  data Modifiers = Modifiers {   _shortOptions :: [(String, [Char])],   _renamings :: [(String, String)],-  positionalArgumentsField :: Maybe String+  positionalArgumentsField :: Maybe String,+  helpTexts :: [(String, String)]  }  deriving (Show, Eq, Ord)  mkModifiers :: [Modifier] -> Result Modifiers-mkModifiers = foldM inner (Modifiers [] [] Nothing)+mkModifiers = foldM inner (Modifiers [] [] Nothing [])   where     inner :: Modifiers -> Modifier -> Result Modifiers-    inner (Modifiers shorts renamings args) (AddShortOption option short) = do-      normalized <- normalizeFieldName option-      return $ Modifiers-        (insertWith (++) normalized [short] shorts)-        renamings args-    inner (Modifiers shorts renamings args) (RenameOption from to) = do-      fromNormalized <- normalizeFieldName from-      return $ Modifiers shorts (insert fromNormalized to renamings) args-    inner (Modifiers shorts renamings _) (UseForPositionalArguments option) = do-      normalized <- normalizeFieldName option-      return $ Modifiers shorts renamings (Just normalized)+    inner (Modifiers shorts renamings args help) modifier = case modifier of+      (AddShortOption option short) ->  do+        normalized <- normalizeFieldName option+        return $ Modifiers+          (insertWith (++) normalized [short] shorts)+          renamings args help+      (RenameOption from to) -> do+        fromNormalized <- normalizeFieldName from+        return $ Modifiers shorts (insert fromNormalized to renamings) args help+      (UseForPositionalArguments option) -> do+        normalized <- normalizeFieldName option+        return $ Modifiers shorts renamings (Just normalized) help+      (AddOptionHelp option helpText) -> do+        normalized <- normalizeFieldName option+        return $ Modifiers shorts renamings args (insert normalized helpText help)  mkShortOptions :: Modifiers -> String -> [Char]-mkShortOptions (Modifiers shortMap _ _) option =+mkShortOptions (Modifiers shortMap _ _ _) option =     fromMaybe [] (lookup option shortMap)  mkLongOption :: Modifiers -> String -> String-mkLongOption (Modifiers _ renamings _) option =+mkLongOption (Modifiers _ renamings _ _) option =   fromMaybe option (lookup option renamings)  hasPositionalArgumentsField :: Modifiers -> Bool@@ -78,6 +87,9 @@ isPositionalArgumentsField :: Modifiers -> String -> Bool isPositionalArgumentsField modifiers field =   Just field == positionalArgumentsField modifiers++getHelpText :: Modifiers -> String -> String+getHelpText modifiers field = fromMaybe "" (lookup field (helpTexts modifiers))  -- * deriving Modifiers 
+ test/ExamplesSpec.hs view
@@ -0,0 +1,10 @@++module ExamplesSpec where++import           Test.Hspec++import           Example   ()+import           Readme    ()++spec :: Spec+spec = return ()
+ test/System/Console/GetOpt/Generics/InternalSpec.hs view
@@ -0,0 +1,45 @@++module System.Console.GetOpt.Generics.InternalSpec where++import           Data.Char+import           Test.Hspec+import           Test.QuickCheck hiding (Success)++import           System.Console.GetOpt.Generics.Internal+import           System.Console.GetOpt.Generics.Result++spec :: Spec+spec = do+  describe "normalizeFieldName" $ do+    it "is idempotent" $ do+      property $ \ x ->+        (normalizeFieldName =<< normalizeFieldName x)+          `shouldBe` normalizeFieldName x++    it "contains only valid characters" $ do+      property $ \ x ->+        case normalizeFieldName x of+          Success s -> s `shouldSatisfy`+            (\ s -> all (\ c -> c `elem` ['a' .. 'z'] || c == '-') s)+          _ -> return ()++    it "complains on field names without alphabetic characters" $ do+      property $+        forAll (listOf (elements "-_$/|\\#~_[]")) $ \ s ->+        normalizeFieldName s `shouldBe`+          Errors [("unsupported field name: " ++ s)]++    it "replaces camelCase with dashes" $+      property $+        \ prefix suffix ->+        (fmap (not . null) (normalizeFieldName prefix) == Success True) ==>+        forAll upperCaseChar $ \ upper ->+        counterexample (prefix ++ [upper] ++ suffix) $+        normalizeFieldName (prefix ++ [upper] ++ suffix)+          `shouldBe` do+            normalizedPrefix <- normalizeFieldName prefix+            normalizedRest <- normalizeFieldName (toLower upper : suffix)+            return $ normalizedPrefix ++ "-" ++ normalizedRest++upperCaseChar :: Gen Char+upperCaseChar = elements ['A' .. 'Z']
+ test/System/Console/GetOpt/Generics/ModifierSpec.hs view
@@ -0,0 +1,59 @@+{-# LANGUAGE DeriveGeneric #-}++module System.Console.GetOpt.Generics.ModifierSpec where++import           Data.Char+import           Data.Proxy+import qualified GHC.Generics+import           Test.Hspec+import           Test.QuickCheck                         hiding (Result)++import           System.Console.GetOpt.Generics+import           System.Console.GetOpt.Generics.Modifier++spec :: Spec+spec = do+  describe "deriveShortOptions" $ do+    it "includes modifiers for short options" $ do+      deriveShortOptions (Proxy :: Proxy Foo) `shouldBe` [AddShortOption "bar" 'b']++    it "doesn't include modifiers for short options in case of overlaps" $ do+      deriveShortOptions (Proxy :: Proxy Overlap) `shouldBe` []++  describe "mkShortModifiers" $ do+    it "returns only lower-case ascii alpha characters as short options" $ do+      property $ \ strings ->+        mkShortModifiers strings `shouldSatisfy` all (\ (AddShortOption _ c) ->+          isLower c && isAscii c && isAlpha c)++  describe "insertWith" $ do+    it "combines existing values with the given function" $ do+      insertWith (++) (1 :: Integer) "bar" [(1, "foo")]+        `shouldBe` [(1, "foobar")]++  context "when used with Modifiers" $ do+    describe "parseArguments" $ do+      it "allows to specify a flag specific help" $ do+        let OutputAndExit output =+              parseArguments "header" [AddOptionHelp "bar" "bar help text"]+                (words "--help") :: Result Foo+        output `shouldContain` "--bar=string  bar help text"++data Foo+  = Foo {+    bar :: String+  }+  deriving (GHC.Generics.Generic)++instance Generic Foo+instance HasDatatypeInfo Foo++data Overlap+  = Overlap {+    foo :: String,+    fooo :: String+  }+  deriving (GHC.Generics.Generic)++instance Generic Overlap+instance HasDatatypeInfo Overlap
+ test/System/Console/GetOpt/Generics/ResultSpec.hs view
@@ -0,0 +1,15 @@+{-# LANGUAGE ScopedTypeVariables #-}++module System.Console.GetOpt.Generics.ResultSpec where++import           Test.Hspec++import           System.Console.GetOpt.Generics.Result++spec :: Spec+spec = do+  describe "Result" $ do+    context ">>" $ do+      it "collects errors" $ do+        (Errors ["foo"] >> Errors ["bar"] :: Result ())+          `shouldBe` Errors ["foo", "bar"]
+ test/System/Console/GetOpt/GenericsSpec.hs view
@@ -0,0 +1,317 @@+{-# 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))