packages feed

getopt-generics 0.10.0.1 → 0.11

raw patch · 59 files changed

+1831/−1439 lines, 59 filesdep +safedep −directorydep −processdep −temporaryPVP ok

version bump matches the API change (PVP)

Dependencies added: safe

Dependencies removed: directory, process, temporary

API changes (from Hackage documentation)

- System.Console.GetOpt.Generics: Proxy :: Proxy
- System.Console.GetOpt.Generics: class Typeable a => Option a where _toOption = ReqArg parseAsFieldState (argumentType (Proxy :: Proxy a)) _emptyOption modifiers flagName = Unset ("missing option: --" ++ mkLongOption modifiers flagName ++ "=" ++ argumentType (Proxy :: Proxy a)) _accumulate _ x = x
- System.Console.GetOpt.Generics: class SingI (ArgumentTypes main) => SimpleCLI main where type family ArgumentTypes main :: [*]
- System.Console.GetOpt.Generics: class SingI (a :: k)
- System.Console.GetOpt.Generics: data Proxy (t :: k) :: k -> *
- System.Console.GetOpt.Generics: deriveShortOptions :: (HasDatatypeInfo a, SingI (Code a)) => Proxy a -> [Modifier]
- System.Console.GetOpt.Generics: simpleCLI :: (SimpleCLI main, All Option (ArgumentTypes main)) => main -> IO ()
+ System.Console.GetOpt.Generics: class Argument a
+ System.Console.GetOpt.Generics: class HasArguments a where argumentsParser = const . genericParser
+ System.Console.GetOpt.Generics: class WithCli main
+ System.Console.GetOpt.Generics: withCli :: WithCli main => main -> IO ()
+ System.Console.GetOpt.Generics: withCliModified :: WithCli main => [Modifier] -> main -> IO ()
+ WithCli: AddOptionHelp :: String -> String -> Modifier
+ WithCli: AddShortOption :: String -> Char -> Modifier
+ WithCli: AddVersionFlag :: String -> Modifier
+ WithCli: Proxy :: Proxy
+ WithCli: RenameOption :: String -> String -> Modifier
+ WithCli: RenameOptions :: (String -> Maybe String) -> Modifier
+ WithCli: UseForPositionalArguments :: String -> String -> Modifier
+ WithCli: argumentType :: Argument a => Proxy a -> String
+ WithCli: argumentsParser :: HasArguments a => Modifiers -> Maybe String -> Result (Parser Unnormalized a)
+ WithCli: atomicArgumentsParser :: Argument a => Modifiers -> Maybe String -> Result (Parser Unnormalized a)
+ WithCli: class Argument a
+ WithCli: class (SingI [[*]] (Code a), All [*] (SingI [*]) (Code a)) => Generic a where type family Code a :: [[*]]
+ WithCli: class HasArguments a where argumentsParser = const . genericParser
+ WithCli: class HasDatatypeInfo a
+ WithCli: class Typeable (a :: k)
+ WithCli: class WithCli main
+ WithCli: data Modifier
+ WithCli: data Proxy (t :: k) :: k -> *
+ WithCli: instance (WithCli.HasArguments.HasArguments a, WithCli.WithCli rest) => WithCli.WithCli (a -> rest)
+ WithCli: instance WithCli.WithCli (GHC.Types.IO ())
+ WithCli: parseArgument :: Argument a => String -> Maybe a
+ WithCli: withCli :: WithCli main => main -> IO ()
+ WithCli: withCliModified :: WithCli main => [Modifier] -> main -> IO ()
- System.Console.GetOpt.Generics: argumentType :: Option a => Proxy a -> String
+ System.Console.GetOpt.Generics: argumentType :: Argument a => Proxy a -> String
- System.Console.GetOpt.Generics: getArguments :: (Generic a, HasDatatypeInfo a, All2 Option (Code a)) => IO a
+ System.Console.GetOpt.Generics: getArguments :: (Generic a, HasDatatypeInfo a, All2 HasArguments (Code a)) => IO a
- System.Console.GetOpt.Generics: modifiedGetArguments :: (Generic a, HasDatatypeInfo a, All2 Option (Code a)) => [Modifier] -> IO a
+ System.Console.GetOpt.Generics: modifiedGetArguments :: (Generic a, HasDatatypeInfo a, All2 HasArguments (Code a)) => [Modifier] -> IO a
- System.Console.GetOpt.Generics: parseArgument :: Option a => String -> Maybe a
+ System.Console.GetOpt.Generics: parseArgument :: Argument a => String -> Maybe a
- System.Console.GetOpt.Generics: parseArguments :: (Generic a, HasDatatypeInfo a, All2 Option (Code a)) => String -> [Modifier] -> [String] -> Result a
+ System.Console.GetOpt.Generics: parseArguments :: (Generic a, HasDatatypeInfo a, All2 HasArguments (Code a)) => String -> [Modifier] -> [String] -> Result a

Files

+ README.md view
@@ -0,0 +1,49 @@+# getopt-generics++## Status++This library is experimental.++## Usage++`getopt-generics` tries to make it very simple to create command line+interfaces. Here's an example:++<!--- ### Start "docs/Simple.hs" "module Simple where\n\n" (MarkDown Haskell) ### -->++``` haskell+import WithCli++main :: IO ()+main = withCli run++run :: String -> Int -> Bool -> IO ()+run s i b = print (s, i, b)+```++<!--- ### End ### -->++This is how the program behaves in a shell:++<!--- ### Start "docs/Simple.shell-protocol" "" (MarkDown Shell) ### -->++``` shell+$ program foo 42 true+("foo",42,True)+$ program --help+program [OPTIONS] STRING INTEGER BOOL+  -h  --help  show help and exit+$ program foo 42 bar+cannot parse as BOOL: bar+# exit-code 1+$ program+missing argument of type STRING+missing argument of type INTEGER+missing argument of type BOOL+# exit-code 1+$ program foo 42 yes bar+unknown argument: bar+# exit-code 1+```++<!--- ### End ### -->
+ docs/CustomOption.hs view
@@ -0,0 +1,21 @@+{-# LANGUAGE DeriveDataTypeable #-}++module CustomOption where++import WithCli++data File = File FilePath+  deriving (Show, Typeable)++instance Argument File where+  argumentType Proxy = "custom-file-type"+  parseArgument f = Just (File f)++instance HasArguments File where+  argumentsParser = atomicArgumentsParser++main :: IO ()+main = withCli run++run :: File -> IO ()+run = print
+ docs/CustomOption.shell-protocol view
@@ -0,0 +1,5 @@+$ program --help+program [OPTIONS] custom-file-type+  -h  --help  show help and exit+$ program some/file+File "some/file"
+ docs/CustomOptionRecord.hs view
@@ -0,0 +1,33 @@+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE TypeFamilies #-}++module CustomOptionRecord where++import qualified GHC.Generics+import           WithCli++data File = File FilePath+  deriving (Show, Typeable)++instance Argument File where+  argumentType Proxy = "custom-file-type"+  parseArgument f = Just (File f)++data Options+  = Options {+    file :: File+  }+  deriving (Show, GHC.Generics.Generic)++instance Generic Options+instance HasDatatypeInfo Options+instance HasArguments Options+instance HasArguments File where+  argumentsParser = atomicArgumentsParser++main :: IO ()+main = withCli run++run :: Options -> IO ()+run = print
+ docs/CustomOptionRecord.shell-protocol view
@@ -0,0 +1,6 @@+$ program --file some/file+Options {file = File "some/file"}+$ program --help+program [OPTIONS]+      --file=custom-file-type+  -h  --help                   show help and exit
− docs/CustomOptionsExample.bash-protocol
@@ -1,5 +0,0 @@-$ program some/file-File "some/file"-$ program --help-program [OPTIONS] custom-file-type-  -h  --help  show help and exit
− docs/CustomOptionsExample.hs
@@ -1,15 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}--import           Data.Typeable-import           System.Console.GetOpt.Generics--data File = File FilePath-  deriving (Show, Typeable)--instance Option File where-  argumentType Proxy = "custom-file-type"-  parseArgument f = Just (File f)--main :: IO ()-main = simpleCLI $ \ file -> do-  print (file :: File)
− docs/Example.hs
@@ -1,26 +0,0 @@-{-# LANGUAGE DeriveGeneric #-}--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
+ docs/RecordType.hs view
@@ -0,0 +1,26 @@+{-# LANGUAGE DeriveGeneric #-}++module RecordType where++import qualified GHC.Generics+import           System.Console.GetOpt.Generics++-- All you have to do is to define a type and derive some instances:++data Options+  = Options {+    port :: Int,+    daemonize :: Bool,+    config :: Maybe FilePath+  }+  deriving (Show, GHC.Generics.Generic)++instance Generic Options+instance HasDatatypeInfo Options++-- Then you can use `getArguments` to create a command-line argument parser:++main :: IO ()+main = do+  options <- getArguments+  print (options :: Options)
+ docs/RecordType.shell-protocol view
@@ -0,0 +1,16 @@+$ program --port 8080 --config some/path+Options {port = 8080, daemonize = False, config = Just "some/path"}+$ program  --port 8080 --daemonize+Options {port = 8080, daemonize = True, config = Nothing}+$ program --port foo+cannot parse as INTEGER: foo+# exit-code 1+$ program+missing option: --port=INTEGER+# exit-code 1+$ program --help+program [OPTIONS]+      --port=INTEGER+      --daemonize+      --config=STRING (optional)+  -h  --help                      show help and exit
− docs/RecordTypeExample.bash-protocol
@@ -1,14 +0,0 @@-$ program --port 8080 --config some/path-Options {port = 8080, daemonize = False, config = Just "some/path"}-$ program  --port 8080 --daemonize-Options {port = 8080, daemonize = True, config = Nothing}-$ program --port foo-cannot parse as INTEGER: foo-$ program-missing option: --port=INTEGER-$ program --help-program [OPTIONS]-      --port=INTEGER-      --daemonize-      --config=STRING (optional)-  -h  --help                      show help and exit
− docs/RecordTypeExample.hs
@@ -1,24 +0,0 @@-{-# LANGUAGE DeriveGeneric #-}--import GHC.Generics-import System.Console.GetOpt.Generics---- All you have to do is to define a type and derive some instances:--data Options-  = Options {-    port :: Int,-    daemonize :: Bool,-    config :: Maybe FilePath-  }-  deriving (Show, GHC.Generics.Generic)--instance System.Console.GetOpt.Generics.Generic Options-instance HasDatatypeInfo Options---- Then you can use `getArguments` to create a command-line argument parser:--main :: IO ()-main = do-  options <- getArguments-  print (options :: Options)
+ docs/Simple.hs view
@@ -0,0 +1,9 @@+module Simple where++import WithCli++main :: IO ()+main = withCli run++run :: String -> Int -> Bool -> IO ()+run s i b = print (s, i, b)
+ docs/Simple.shell-protocol view
@@ -0,0 +1,16 @@+$ program foo 42 true+("foo",42,True)+$ program --help+program [OPTIONS] STRING INTEGER BOOL+  -h  --help  show help and exit+$ program foo 42 bar+cannot parse as BOOL: bar+# exit-code 1+$ program+missing argument of type STRING+missing argument of type INTEGER+missing argument of type BOOL+# exit-code 1+$ program foo 42 yes bar+unknown argument: bar+# exit-code 1
− docs/SimpleExample.bash-protocol
@@ -1,7 +0,0 @@-$ program foo 42 true-("foo",42,True)-$ program foo 42 bar-cannot parse as BOOL: bar-$ program --help-program [OPTIONS] STRING INTEGER BOOL-  -h  --help  show help and exit
− docs/SimpleExample.hs
@@ -1,7 +0,0 @@-import           System.Console.GetOpt.Generics--main :: IO ()-main = simpleCLI myMain--myMain :: String -> Int -> Bool -> IO ()-myMain s i b = print (s, i, b)
+ docs/SimpleRecord.hs view
@@ -0,0 +1,24 @@+{-# LANGUAGE DeriveGeneric #-}++module SimpleRecord where++import qualified GHC.Generics+import           System.Console.GetOpt.Generics++data Options+  = Options {+    port :: Int,+    daemonize :: Bool,+    config :: Maybe FilePath+  }+  deriving (Show, GHC.Generics.Generic)++instance Generic Options+instance HasDatatypeInfo Options+instance HasArguments Options++main :: IO ()+main = withCli run++run :: Options -> IO ()+run = print
+ docs/SimpleRecord.shell-protocol view
@@ -0,0 +1,16 @@+$ program --port 8080 --config some/path+Options {port = 8080, daemonize = False, config = Just "some/path"}+$ program  --port 8080 --daemonize+Options {port = 8080, daemonize = True, config = Nothing}+$ program --port foo+cannot parse as INTEGER: foo+# exit-code 1+$ program+missing option: --port=INTEGER+# exit-code 1+$ program --help+program [OPTIONS]+      --port=INTEGER+      --daemonize+      --config=STRING (optional)+  -h  --help                      show help and exit
+ docs/Test01.hs view
@@ -0,0 +1,10 @@++module Test01 where++import WithCli++main :: IO ()+main = withCli run++run :: Int -> Bool -> IO ()+run = curry print
+ docs/Test01.shell-protocol view
@@ -0,0 +1,7 @@+$ program 42+missing argument of type BOOL+# exit-code 1+$ program+missing argument of type INTEGER+missing argument of type BOOL+# exit-code 1
+ docs/Test02.hs view
@@ -0,0 +1,26 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE TypeFamilies #-}++module Test02 where++import qualified GHC.Generics+import           WithCli++data Options+  = Options {+    port :: Int,+    daemonize :: Bool,+    config :: Maybe FilePath,+    args :: [String]+  }+  deriving (Show, GHC.Generics.Generic)++instance Generic Options+instance HasDatatypeInfo Options+instance HasArguments Options++main :: IO ()+main = withCli run++run :: Options -> IO ()+run = print
+ docs/Test02.shell-protocol view
@@ -0,0 +1,21 @@+$ program --port 8080 --config some/path+Options {port = 8080, daemonize = False, config = Just "some/path", args = []}+$ program --port 8080 --config some/path --foo true+unrecognized option `--foo'+unknown argument: true+# exit-code 1+$ program  --port 8080 --daemonize --config some+Options {port = 8080, daemonize = True, config = Just "some", args = []}+$ program --port foo+cannot parse as INTEGER: foo+# exit-code 1+$ program+missing option: --port=INTEGER+# exit-code 1+$ program --help+program [OPTIONS]+      --port=INTEGER+      --daemonize+      --config=STRING (optional)+      --args=STRING (multiple possible)+  -h  --help                             show help and exit
+ docs/Test03.hs view
@@ -0,0 +1,35 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE FlexibleInstances #-}++module Test03 where++import qualified GHC.Generics+import           WithCli++main :: IO ()+main = withCli run++run :: (A, B) -> IO ()+run options = do+  print (options :: (A, B))++data A+  = A {+    aa :: String+  }+  deriving (Show, GHC.Generics.Generic)++instance Generic A+instance HasDatatypeInfo A+instance HasArguments A++data B+  = B {+    bb :: String+  }+  deriving (Show, GHC.Generics.Generic)++instance Generic B+instance HasDatatypeInfo B+instance HasArguments B
+ docs/Test03.shell-protocol view
@@ -0,0 +1,7 @@+$ program --aa foo --bb bar+(A {aa = "foo"},B {bb = "bar"})+$ program --help+program [OPTIONS]+      --aa=STRING+      --bb=STRING+  -h  --help       show help and exit
+ docs/Test04.hs view
@@ -0,0 +1,34 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveGeneric #-}++module Test04 where++import qualified GHC.Generics+import           WithCli++main :: IO ()+main = withCli run++run :: A -> B -> IO ()+run a b = do+  print (a, b)++data A+  = A {+    aa :: String+  }+  deriving (Show, GHC.Generics.Generic)++instance Generic A+instance HasDatatypeInfo A+instance HasArguments A++data B+  = B {+    bb :: String+  }+  deriving (Show, GHC.Generics.Generic)++instance Generic B+instance HasDatatypeInfo B+instance HasArguments B
+ docs/Test04.shell-protocol view
@@ -0,0 +1,7 @@+$ program --aa foo --bb bar+(A {aa = "foo"},B {bb = "bar"})+$ program --help+program [OPTIONS]+      --aa=STRING+      --bb=STRING+  -h  --help       show help and exit
getopt-generics.cabal view
@@ -3,7 +3,7 @@ -- see: https://github.com/sol/hpack  name:           getopt-generics-version:        0.10.0.1+version:        0.11 synopsis:       Create command line interfaces with ease description:    Create command line interfaces with ease category:       Console, System@@ -18,13 +18,25 @@ cabal-version:  >= 1.10  extra-source-files:-    docs/CustomOptionsExample.bash-protocol-    docs/CustomOptionsExample.hs-    docs/Example.hs-    docs/RecordTypeExample.bash-protocol-    docs/RecordTypeExample.hs-    docs/SimpleExample.bash-protocol-    docs/SimpleExample.hs+    docs/CustomOption.hs+    docs/CustomOption.shell-protocol+    docs/CustomOptionRecord.hs+    docs/CustomOptionRecord.shell-protocol+    docs/RecordType.hs+    docs/RecordType.shell-protocol+    docs/Simple.hs+    docs/Simple.shell-protocol+    docs/SimpleRecord.hs+    docs/SimpleRecord.shell-protocol+    docs/Test01.hs+    docs/Test01.shell-protocol+    docs/Test02.hs+    docs/Test02.shell-protocol+    docs/Test03.hs+    docs/Test03.shell-protocol+    docs/Test04.hs+    docs/Test04.shell-protocol+    README.md  source-repository head   type: git@@ -41,45 +53,17 @@     , generics-sop     , tagged   exposed-modules:+      WithCli       System.Console.GetOpt.Generics   other-modules:-      System.Console.GetOpt.Generics.FieldString-      System.Console.GetOpt.Generics.GetArguments       System.Console.GetOpt.Generics.Modifier-      System.Console.GetOpt.Generics.Result-      System.Console.GetOpt.Generics.Simple-  default-language: Haskell2010--test-suite examples-  type: exitcode-stdio-1.0-  main-is: Examples.hs-  hs-source-dirs:-      test-  ghc-options: -Wall -fno-warn-name-shadowing -threaded -O0-  build-depends:-      base == 4.*-    , base-compat >= 0.8-    , base-orphans-    , generics-sop-    , tagged-    , hspec >= 2.1.8-    , temporary-    , directory-    , filepath-    , process-    , silently-  other-modules:-      BashProtocol-      ModifiersSpec-      ModifiersSpec.RenameOptionsSpec-      ModifiersSpec.UseForPositionalArgumentsSpec-      Spec-      System.Console.GetOpt.Generics.FieldStringSpec-      System.Console.GetOpt.Generics.ModifierSpec-      System.Console.GetOpt.Generics.ResultSpec-      System.Console.GetOpt.Generics.SimpleSpec-      System.Console.GetOpt.GenericsSpec-      Util+      System.Console.GetOpt.Generics.Modifier.Types+      WithCli.Argument+      WithCli.Flag+      WithCli.HasArguments+      WithCli.Normalize+      WithCli.Parser+      WithCli.Result   default-language: Haskell2010  test-suite spec@@ -88,6 +72,7 @@   hs-source-dirs:       src     , test+    , docs   ghc-options: -Wall -fno-warn-name-shadowing -threaded -O0   build-depends:       base == 4.*@@ -98,22 +83,40 @@     , hspec >= 2.1.8     , QuickCheck     , silently+    , filepath+    , safe   other-modules:       System.Console.GetOpt.Generics-      System.Console.GetOpt.Generics.FieldString-      System.Console.GetOpt.Generics.GetArguments       System.Console.GetOpt.Generics.Modifier-      System.Console.GetOpt.Generics.Result-      System.Console.GetOpt.Generics.Simple-      BashProtocol-      Examples+      System.Console.GetOpt.Generics.Modifier.Types+      WithCli+      WithCli.Argument+      WithCli.Flag+      WithCli.HasArguments+      WithCli.Normalize+      WithCli.Parser+      WithCli.Result+      ExamplesSpec       ModifiersSpec       ModifiersSpec.RenameOptionsSpec       ModifiersSpec.UseForPositionalArgumentsSpec-      System.Console.GetOpt.Generics.FieldStringSpec+      ShellProtocol       System.Console.GetOpt.Generics.ModifierSpec-      System.Console.GetOpt.Generics.ResultSpec-      System.Console.GetOpt.Generics.SimpleSpec       System.Console.GetOpt.GenericsSpec       Util+      WithCli.ArgumentSpec+      WithCli.HasArgumentsSpec+      WithCli.NormalizeSpec+      WithCli.ParserSpec+      WithCli.ResultSpec+      WithCliSpec+      CustomOption+      CustomOptionRecord+      RecordType+      Simple+      SimpleRecord+      Test01+      Test02+      Test03+      Test04   default-language: Haskell2010
src/System/Console/GetOpt/Generics.hs view
@@ -1,31 +1,123 @@+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE ViewPatterns #-}  module System.Console.GetOpt.Generics (+  -- * Simple IO API+  withCli,+  WithCli(),+  HasArguments,+  WithCli.Argument(argumentType, parseArgument),+  -- * Customizing the CLI+  withCliModified,+  Modifier(..),   -- * IO API-  simpleCLI,-  SimpleCLI,-  ArgumentTypes,   getArguments,   modifiedGetArguments,   -- * Pure API   parseArguments,   Result(..),-  -- * Customizing the CLI-  Modifier(..),-  deriveShortOptions,-  -- * Available Field Types-  Option(argumentType, parseArgument),   -- * Re-exports from "Generics.SOP"-  Generic,+  Generics.SOP.Generic,   HasDatatypeInfo,   Code,   All2,-  SingI,-  Proxy(..),  ) where  import           Generics.SOP+import           System.Environment -import           System.Console.GetOpt.Generics.GetArguments+import           WithCli+import           WithCli.Parser+import           WithCli.HasArguments+import           WithCli.Result import           System.Console.GetOpt.Generics.Modifier-import           System.Console.GetOpt.Generics.Result-import           System.Console.GetOpt.Generics.Simple++-- | Parses command line arguments (gotten from 'withArgs') and returns the+--   parsed value. This function should be enough for simple use-cases.+--+--   Throws the same exceptions as 'withCli'.+--+-- Here's an example:++-- ### Start "docs/RecordType.hs" "" Haddock ###++-- |+-- >  {-# LANGUAGE DeriveGeneric #-}+-- >+-- >  module RecordType where+-- >+-- >  import qualified GHC.Generics+-- >  import           System.Console.GetOpt.Generics+-- >+-- >  -- All you have to do is to define a type and derive some instances:+-- >+-- >  data Options+-- >    = Options {+-- >      port :: Int,+-- >      daemonize :: Bool,+-- >      config :: Maybe FilePath+-- >    }+-- >    deriving (Show, GHC.Generics.Generic)+-- >+-- >  instance Generic Options+-- >  instance HasDatatypeInfo Options+-- >+-- >  -- Then you can use `getArguments` to create a command-line argument parser:+-- >+-- >  main :: IO ()+-- >  main = do+-- >    options <- getArguments+-- >    print (options :: Options)++-- ### End ###++-- | And this is how the above program behaves:++-- ### Start "docs/RecordType.shell-protocol" "" Haddock ###++-- |+-- >  $ program --port 8080 --config some/path+-- >  Options {port = 8080, daemonize = False, config = Just "some/path"}+-- >  $ program  --port 8080 --daemonize+-- >  Options {port = 8080, daemonize = True, config = Nothing}+-- >  $ program --port foo+-- >  cannot parse as INTEGER: foo+-- >  # exit-code 1+-- >  $ program+-- >  missing option: --port=INTEGER+-- >  # exit-code 1+-- >  $ program --help+-- >  program [OPTIONS]+-- >        --port=INTEGER+-- >        --daemonize+-- >        --config=STRING (optional)+-- >    -h  --help                      show help and exit++-- ### End ###++getArguments :: forall a . (Generic a, HasDatatypeInfo a, All2 HasArguments (Code a)) =>+  IO a+getArguments = modifiedGetArguments []++-- | Like 'getArguments` but allows you to pass in 'Modifier's.+modifiedGetArguments :: forall a . (Generic a, HasDatatypeInfo a, All2 HasArguments (Code a)) =>+  [Modifier] -> IO a+modifiedGetArguments modifiers = do+  args <- getArgs+  progName <- getProgName+  handleResult $ parseArguments progName modifiers args++-- | Pure variant of 'modifiedGetArguments'.+--+--   Does not throw any exceptions.+parseArguments :: forall a . (Generic a, HasDatatypeInfo a, All2 HasArguments (Code a)) =>+     String -- ^ Name of the program (e.g. from 'getProgName').+  -> [Modifier] -- ^ List of 'Modifier's to manually tweak the command line interface.+  -> [String] -- ^ List of command line arguments to parse (e.g. from 'getArgs').+  -> Result a+parseArguments progName mods args = do+  modifiers <- mkModifiers mods+  parser <- genericParser modifiers+  runParser progName modifiers+    (normalizeParser (applyModifiers modifiers parser)) args
− src/System/Console/GetOpt/Generics/FieldString.hs
@@ -1,48 +0,0 @@--module System.Console.GetOpt.Generics.FieldString (-  FieldString,-  mkFieldString,-  normalized,-  matches,-  renameUnnormalized,- ) where--import           Data.Char--data FieldString = FieldString String-  deriving (Show)--normalized :: FieldString -> String-normalized (FieldString s) = normalize s--mkFieldString :: String -> FieldString-mkFieldString = FieldString--matches :: String -> FieldString -> Bool-matches s field =-  normalize s == normalized field--renameUnnormalized :: (String -> Maybe String) -> (FieldString -> FieldString)-renameUnnormalized f input@(FieldString unnormalized) =-  maybe input FieldString (f unnormalized)--normalize :: String -> String-normalize s =-  if all (not . isAllowedChar) s-    then s-    else-      slugify $-      dropWhile (== '-') $-      filter isAllowedChar $-      map (\ c -> if c == '_' then '-' else c) $-      s-  where-    slugify (a : r)-      | isUpper a = slugify (toLower a : r)-    slugify (a : b : r)-      | isUpper b = a : '-' : slugify (toLower b : r)-      | otherwise = a : slugify (b : r)-    slugify x = x--isAllowedChar :: Char -> Bool-isAllowedChar c = (isAscii c && isAlphaNum c) || (c `elem` "-_")
− src/System/Console/GetOpt/Generics/GetArguments.hs
@@ -1,505 +0,0 @@-{-# LANGUAGE ConstraintKinds       #-}-{-# LANGUAGE DataKinds             #-}-{-# LANGUAGE DeriveDataTypeable    #-}-{-# LANGUAGE DeriveFunctor         #-}-{-# LANGUAGE DeriveGeneric         #-}-{-# LANGUAGE FlexibleContexts      #-}-{-# LANGUAGE FlexibleInstances     #-}-{-# LANGUAGE GADTs                 #-}-{-# LANGUAGE InstanceSigs          #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE OverlappingInstances  #-}-{-# LANGUAGE RankNTypes            #-}-{-# LANGUAGE ScopedTypeVariables   #-}-{-# LANGUAGE TypeFamilies          #-}-{-# LANGUAGE TypeOperators         #-}-{-# LANGUAGE TypeSynonymInstances  #-}-{-# LANGUAGE ViewPatterns          #-}--{-# OPTIONS_GHC -fno-warn-deprecated-flags #-}-{-# OPTIONS_GHC -fno-warn-unrecognised-pragmas #-}---- | @getopt-generics@ tries to make it very simple to create command line--- argument parsers.-module System.Console.GetOpt.Generics.GetArguments where--import           Data.Orphans ()-import           Prelude ()-import           Prelude.Compat--import           Data.Char-import           Data.List.Compat-import           Data.Maybe-import           Data.Proxy-import           Data.Typeable-import           Generics.SOP-import           System.Console.GetOpt-import           System.Environment-import           Text.Read.Compat--import           System.Console.GetOpt.Generics.FieldString-import           System.Console.GetOpt.Generics.Modifier-import           System.Console.GetOpt.Generics.Result---- | Parses command line arguments (gotten from 'withArgs') and returns the---   parsed value. This function should be enough for simple use-cases.------   Throws the same exceptions as 'simpleCLI'.------ Here's an example:---- ### Start "docs/RecordTypeExample.hs" Haddock ###---- |--- >  {-# LANGUAGE DeriveGeneric #-}--- >--- >  import GHC.Generics--- >  import System.Console.GetOpt.Generics--- >--- >  -- All you have to do is to define a type and derive some instances:--- >--- >  data Options--- >    = Options {--- >      port :: Int,--- >      daemonize :: Bool,--- >      config :: Maybe FilePath--- >    }--- >    deriving (Show, GHC.Generics.Generic)--- >--- >  instance System.Console.GetOpt.Generics.Generic Options--- >  instance HasDatatypeInfo Options--- >--- >  -- Then you can use `getArguments` to create a command-line argument parser:--- >--- >  main :: IO ()--- >  main = do--- >    options <- getArguments--- >    print (options :: Options)---- ### End ###---- | And this is how the above program behaves:---- ### Start "docs/RecordTypeExample.bash-protocol" Haddock ###---- |--- >  $ program --port 8080 --config some/path--- >  Options {port = 8080, daemonize = False, config = Just "some/path"}--- >  $ program  --port 8080 --daemonize--- >  Options {port = 8080, daemonize = True, config = Nothing}--- >  $ program --port foo--- >  cannot parse as INTEGER: foo--- >  $ program--- >  missing option: --port=INTEGER--- >  $ program --help--- >  program [OPTIONS]--- >        --port=INTEGER--- >        --daemonize--- >        --config=STRING (optional)--- >    -h  --help                      show help and exit---- ### End ###--getArguments :: forall a . (Generic a, HasDatatypeInfo a, All2 Option (Code a)) =>-  IO a-getArguments = modifiedGetArguments []---- | Like 'getArguments` but allows you to pass in 'Modifier's.-modifiedGetArguments :: forall a . (Generic a, HasDatatypeInfo a, All2 Option (Code a)) =>-  [Modifier] -> IO a-modifiedGetArguments modifiers = do-  args <- getArgs-  progName <- getProgName-  handleResult $ parseArguments progName modifiers args---- | Pure variant of 'modifiedGetArguments'.------   Does not throw any exceptions.-parseArguments :: forall a . (Generic a, HasDatatypeInfo a, All2 Option (Code a)) =>-     String -- ^ Name of the program (e.g. from 'getProgName').-  -> [Modifier] -- ^ List of 'Modifier's to manually tweak the command line interface.-  -> [String] -- ^ List of command line arguments to parse (e.g. from 'getArgs').-  -> Result a-parseArguments progName modifiersList args = do-    let modifiers = mkModifiers modifiersList-    case datatypeInfo (Proxy :: Proxy a) of-      ADT typeName _ (constructorInfo :* Nil) ->-        case constructorInfo of-          (Record _ fields) -> processFields progName modifiers args-            (hliftA (Comp . Selector) fields)-          Constructor{} ->-            processFields progName modifiers args (hpure (Comp NoSelector))-          Infix{} ->-            err typeName "infix constructors"-      ADT typeName _ Nil ->-        err typeName "empty data types"-      ADT typeName _ (_ :* _ :* _) ->-        err typeName "sum types"-      Newtype _ _ (Record _ fields) ->-        processFields progName modifiers args-          (hliftA (Comp . Selector) fields)-      Newtype typeName _ (Constructor _) ->-        err typeName "constructors without field labels"-  where-    err typeName message =-      errors ["getopt-generics doesn't support " ++ message ++-              " (" ++ typeName ++ ")."]--data Field a-  = NoSelector-  | Selector a--processFields :: forall a xs .-  (Generic a, Code a ~ '[xs], SingI xs, All Option xs) =>-  String -> Modifiers -> [String] -> NP (Field :.: FieldInfo) xs -> Result a-processFields progName modifiers args fields = do-    initialFieldStates <- mkInitialFieldStates modifiers fields--    showOutputInfo--    let (options, arguments, parseErrors) =-          getOpt Permute (mkOptDescrs modifiers fields) args--    reportGetOptErrors parseErrors--    withPositionalArguments <- fillInPositionalArguments arguments $-      project options initialFieldStates--    to . SOP . Z <$> collectResult withPositionalArguments-  where-    showOutputInfo :: Result ()-    showOutputInfo = outputInfo progName modifiers args fields--    reportGetOptErrors :: [String] -> Result ()-    reportGetOptErrors parseErrors = case parseErrors of-      [] -> pure ()-      errs -> errors errs---- Creates a list of NS where every element corresponds to one field. To be--- used by 'getOpt'.-mkOptDescrs :: forall xs . (SingI xs, All Option xs) =>-  Modifiers -> NP (Field :.: FieldInfo) xs -> [OptDescr (NS FieldState xs)]-mkOptDescrs modifiers =-  mapMaybe toOptDescr . apInjs_NP . hcliftA (Proxy :: Proxy Option) (mkOptDescr modifiers)--newtype OptDescrE a = OptDescrE (Maybe (OptDescr (FieldState a)))--mkOptDescr :: forall a . Option a => Modifiers -> (Field :.: FieldInfo) a -> OptDescrE a-mkOptDescr _modifiers (Comp NoSelector) = OptDescrE Nothing-mkOptDescr modifiers (Comp (Selector (FieldInfo (mkFieldString -> name)))) = OptDescrE $-  if isPositionalArgumentsField modifiers name-    then Nothing-    else Just $ Option-      (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-toOptDescr (Z (OptDescrE Nothing)) = Nothing-toOptDescr (S a) = fmap (fmap S) (toOptDescr a)---- Initializes an NP of empty fields to be filled in later.--- Contains only default values.-mkInitialFieldStates :: forall xs . (SingI xs, All Option xs) =>-  Modifiers -> NP (Field :.: FieldInfo) xs -> Result (NP FieldState xs)-mkInitialFieldStates modifiers fields = case (sing :: Sing xs, fields) of-  (SNil, Nil) -> return Nil-  (SCons, Comp (Selector (FieldInfo (mkFieldString -> name))) :* r) ->-    (:*) <$> inner name <*> mkInitialFieldStates modifiers r-  (SCons, Comp NoSelector :* r) ->-    (:*) <$> Success PositionalArgument <*> mkInitialFieldStates modifiers r-  _ -> uninhabited "mkInitialFieldStates"-- where-  inner :: forall x . Option x => FieldString -> Result (FieldState x)-  inner name = if isPositionalArgumentsField modifiers name-    then case cast (id :: FieldState x -> FieldState x) of-      (Just id' :: Maybe (FieldState [String] -> FieldState x)) ->-        Success $ id' PositionalArguments-      Nothing -> errors-        ["UseForPositionalArguments can only be used " ++-         "for fields of type [String] not " ++-         show (typeOf (impossible "mkInitialFieldStates" :: x))]-    else return $ _emptyOption modifiers name---- * showing output information--data OutputInfoFlag-  = HelpFlag-  | VersionFlag String-  deriving (Eq, Ord)---- Outputs the help or version information if the corresponding flags are given.-outputInfo :: (SingI xs, All Option xs) =>-  String -> Modifiers -> [String] -> NP (Field :.: FieldInfo) xs -> Result ()-outputInfo progName modifiers args fields =-    case (\ (a, b, c) -> (sort a, b, c)) (getOpt Permute options args) of-      ([], _, _) -> return ()-        -- no help or version flag given-      (HelpFlag : _, _, _) -> outputAndExit $-        usageInfo header $-          toOptDescrUnit (mkOptDescrs modifiers fields) ++-          toOptDescrUnit options-      (VersionFlag version : _, _, _) -> outputAndExit $-        progName ++ " version " ++ version ++ "\n"-  where-    options :: [OptDescr OutputInfoFlag]-    options = helpOption : maybeToList versionOption--    helpOption :: OptDescr OutputInfoFlag-    helpOption = Option ['h'] ["help"] (NoArg HelpFlag) "show help and exit"--    versionOption :: Maybe (OptDescr OutputInfoFlag)-    versionOption = case getVersion modifiers of-      Just version -> Just $ Option [] ["version"] (NoArg (VersionFlag version)) "show version and exit"-      Nothing -> Nothing--    toOptDescrUnit :: [OptDescr a] -> [OptDescr ()]-    toOptDescrUnit = map (fmap (const ()))--    header :: String-    header = unwords $-      progName :-      "[OPTIONS]" :-      positionalArgumentHelp fields ++-      maybe [] (\ t -> ["[" ++ t ++ "]"])-        (getPositionalArgumentType modifiers) ++-      []--positionalArgumentHelp :: (All Option xs) => NP (Field :.: FieldInfo) xs -> [String]-positionalArgumentHelp (p@(Comp NoSelector) :* r) =-  argumentType (toProxy p) : positionalArgumentHelp r-positionalArgumentHelp (_ :* r) = positionalArgumentHelp r-positionalArgumentHelp Nil = []---- Fills in the positional arguments in the NP that already contains the flag--- values. Fills in FieldErrors in case of--- - parse errors and--- - missing positional arguments.--- The returned Either contains errors in case of too many positional arguments.-fillInPositionalArguments :: (All Option xs) =>-  [String] -> NP FieldState xs -> Result (NP FieldState xs)-fillInPositionalArguments args inputFieldStates = do-    let (result, errs) = inner (Just args) inputFieldStates-    either errors return errs-    Success result-  where-    inner :: All Option xs =>-      Maybe [String] -> NP FieldState xs -> (NP FieldState xs, Either [String] ())-    inner arguments fields = case (arguments, fields) of-      (Just arguments, PositionalArguments :* r) ->-        FieldSuccess arguments `cons` inner Nothing r-      (Nothing, PositionalArguments :* r) ->-        FieldErrors ["UseForPositionalArguments can only be used once"] `cons` inner Nothing r--      (Just (argument : arguments), PositionalArgument :* r) ->-        case parseArgumentEither argument of-          Right a -> FieldSuccess a `cons` inner (Just arguments) r-          Left err -> FieldErrors [err] `cons` inner (Just arguments) r-      (Just [], p@PositionalArgument :* r) ->-        FieldErrors ["missing argument of type " ++ argumentType (toProxy p)]-          `cons` inner (Just []) r-      (Nothing, PositionalArgument :* _) ->-        impossible "fillInPositionalArguments"--      (arguments, a :* r) -> a `cons` inner arguments r-      (Just [], Nil) -> (Nil, Right ())-      (Nothing, Nil) -> (Nil, Right ())-      (Just arguments@(_ : _), Nil) ->-        (Nil, Left (map (\ arg -> "unknown argument: " ++ arg) arguments))--    cons :: FieldState x -> (NP FieldState xs, r) -> (NP FieldState (x ': xs), r)-    cons fieldState (arguments, r) = (fieldState :* arguments, r)---- Collects all FieldStates into a Result NP. If any errors are contained they--- will be accumulated.-collectResult :: (SingI xs) => NP FieldState xs -> Result (NP I xs)-collectResult input =-    hsequence $ hliftA inner input-  where-    inner :: FieldState x -> Result x-    inner s = case s of-      FieldSuccess v -> Success v-      FieldErrors errs -> errors errs-      Unset err -> errors [err]-      PositionalArguments -> impossible "collectResult"-      PositionalArgument -> impossible "collectResult"---- * helper functions for NS and NP--project :: (SingI xs, All Option xs) =>-  [NS FieldState xs] -> NP FieldState xs -> NP FieldState xs-project sums start =-    foldl' inner start sums-  where-    inner :: (All Option xs) =>-      NP FieldState xs -> NS FieldState xs -> NP FieldState xs-    inner (a :* r) (Z b) = combine a b :* r-    inner (a :* r) (S rSum) = a :* inner r rSum-    inner Nil _ = uninhabited "project"--impossible :: String -> a-impossible name = error ("System.Console.GetOpt.Generics." ++ name ++ ": This should never happen!")--uninhabited :: String -> a-uninhabited = impossible--toProxy :: f a -> Proxy a-toProxy = const Proxy---- * possible field types--data FieldState a where-  Unset :: String -> FieldState a-  FieldErrors :: [String] -> FieldState a-  FieldSuccess :: a -> FieldState a-  PositionalArguments :: FieldState [String]-  PositionalArgument :: FieldState a-  deriving (Typeable)---- | Type class for all allowed field types.------   If you want to use custom field types you should implement an---   @instance Option YourCustomType@ containing implementations of---   'argumentType' and 'parseArgument' (the minimal complete definition).------ Here's an example:---- ### Start "docs/CustomOptionsExample.hs" Haddock ###---- |--- >  {-# LANGUAGE DeriveDataTypeable #-}--- >--- >  import           Data.Typeable--- >  import           System.Console.GetOpt.Generics--- >--- >  data File = File FilePath--- >    deriving (Show, Typeable)--- >--- >  instance Option File where--- >    argumentType Proxy = "custom-file-type"--- >    parseArgument f = Just (File f)--- >--- >  main :: IO ()--- >  main = simpleCLI $ \ file -> do--- >    print (file :: File)---- ### End ###---- | This would give you:---- ### Start "docs/CustomOptionsExample.bash-protocol" Haddock ###---- |--- >  $ program some/file--- >  File "some/file"--- >  $ program --help--- >  program [OPTIONS] custom-file-type--- >    -h  --help  show help and exit---- ### End ###--class Typeable a => Option a where-  {-# MINIMAL argumentType, parseArgument #-}-  -- | Name of the argument type, e.g. "bool" or "integer".-  argumentType :: Proxy a -> String--  -- | Parses a 'String' into an argument. Returns 'Nothing' on parse errors.-  parseArgument :: String -> Maybe a--  -- | This is meant to be an internal function.-  _toOption :: ArgDescr (FieldState a)-  _toOption = ReqArg parseAsFieldState (argumentType (Proxy :: Proxy a))--  -- | This is meant to be an internal function.-  _emptyOption :: Modifiers -> FieldString -> FieldState a-  _emptyOption modifiers flagName = Unset-    ("missing option: --" ++ mkLongOption modifiers flagName ++-     "=" ++ argumentType (Proxy :: Proxy a))--  -- | This is meant to be an internal function.-  _accumulate :: a -> a -> a-  _accumulate _ x = x--parseArgumentEither :: forall a . Option a => String -> Either String a-parseArgumentEither s =-  maybe-    (Left ("cannot parse as " ++ argumentType (Proxy :: Proxy a) ++ ": " ++ s))-    Right-    (parseArgument s)--parseAsFieldState :: forall a . Option a => String -> FieldState a-parseAsFieldState s = either-  (\ err -> FieldErrors [err])-  FieldSuccess-  (parseArgumentEither s)--combine :: Option a => FieldState a -> FieldState a -> FieldState a-combine _ (Unset _) = impossible "combine"-combine _ PositionalArguments = impossible "combine"-combine _ PositionalArgument = impossible "combine"-combine (FieldErrors e) (FieldErrors f) = FieldErrors (e ++ f)-combine (FieldErrors e) _ = FieldErrors e-combine (Unset _) x = x-combine (FieldSuccess _) (FieldErrors e) = FieldErrors e-combine (FieldSuccess a) (FieldSuccess b) = FieldSuccess (_accumulate a b)-combine PositionalArguments _ = PositionalArguments-combine PositionalArgument _ = PositionalArgument--instance Option a => Option [a] where-  argumentType Proxy = argumentType (Proxy :: Proxy a) ++ " (multiple possible)"-  parseArgument x = case parseArgument x of-    Just (x :: a) -> Just [x]-    Nothing -> Nothing-  _emptyOption _ _ = FieldSuccess []-  _accumulate = (++)--instance Option a => Option (Maybe a) where-  argumentType Proxy = argumentType (Proxy :: Proxy a) ++ " (optional)"-  parseArgument x = case parseArgument x of-    Just (x :: a) -> Just (Just x)-    Nothing -> Nothing-  _emptyOption _ _ = FieldSuccess Nothing--instance Option Bool where-  argumentType _ = "BOOL"--  parseArgument :: String -> Maybe Bool-  parseArgument s-    | map toLower s `elem` ["true", "yes", "on"] = Just True-    | map toLower s `elem` ["false", "no", "off"] = Just False-    | otherwise = case readMaybe s of-      Just (n :: Integer) -> Just (n > 0)-      Nothing -> Nothing--  _toOption = NoArg (FieldSuccess True)-  _emptyOption _ _ = FieldSuccess False--instance Option String where-  argumentType Proxy = "STRING"-  parseArgument = Just--instance Option Int where-  argumentType _ = "INTEGER"-  parseArgument = readMaybe--instance Option Integer where-  argumentType _ = "INTEGER"-  parseArgument = readMaybe--readNumber :: (RealFloat n, Read n) => String -> Maybe n-readNumber s = case readMaybe s of-  Just n -> Just n-  Nothing-    | "." `isPrefixOf` s -> readMaybe ("0" ++ s)-    | otherwise -> Nothing--instance Option Float where-  argumentType _ = "NUMBER"-  parseArgument = readNumber--instance Option Double where-  argumentType _ = "NUMBER"-  parseArgument = readNumber
src/System/Console/GetOpt/Generics/Modifier.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GADTs            #-}+{-# LANGUAGE LambdaCase       #-} {-# LANGUAGE TupleSections    #-} {-# LANGUAGE ViewPatterns     #-} @@ -7,30 +8,31 @@   Modifier(..),   Modifiers,   mkModifiers,-  mkShortOptions,-  mkLongOption,-  hasPositionalArgumentsField,   isPositionalArgumentsField,   getPositionalArgumentType,-  getHelpText,   getVersion, -  deriveShortOptions,+  applyModifiers,+  applyModifiersLong,    -- exported for testing-  mkShortModifiers,   insertWith,  ) where  import           Prelude () import           Prelude.Compat +import           Control.Arrow+import           Control.Monad import           Data.Char-import           Data.List (find, foldl')+import           Data.List (foldl') import           Data.Maybe-import           Generics.SOP+import           System.Console.GetOpt -import           System.Console.GetOpt.Generics.FieldString+import           System.Console.GetOpt.Generics.Modifier.Types+import           WithCli.Parser+import           WithCli.Normalize+import           WithCli.Result  -- | 'Modifier's can be used to customize the command line parser. data Modifier@@ -59,116 +61,34 @@   | AddVersionFlag String     -- ^ @AddVersionFlag version@ adds a @--version@ flag. -data Modifiers = Modifiers {-  _shortOptions :: [(String, [Char])],-  _renaming :: FieldString -> FieldString,-  positionalArgumentsField :: [(String, String)],-  helpTexts :: [(String, String)],-  version :: Maybe String- }--mkModifiers :: [Modifier] -> Modifiers-mkModifiers = foldl' inner empty+mkModifiers :: [Modifier] -> Result Modifiers+mkModifiers = foldM inner empty   where     empty :: Modifiers-    empty = Modifiers [] id [] [] Nothing+    empty = Modifiers [] id Nothing [] Nothing -    inner :: Modifiers -> Modifier -> Modifiers+    inner :: Modifiers -> Modifier -> Result Modifiers     inner (Modifiers shorts renaming args help version) modifier = case modifier of       (AddShortOption option short) ->-        Modifiers (insertWith (++) option [short] shorts) renaming args help version+        return $ Modifiers (insertWith (++) option [short] shorts) renaming args help version       (RenameOption from to) ->-        let newRenaming :: FieldString -> FieldString+        let newRenaming :: String -> String             newRenaming option = if from `matches` option-              then mkFieldString to+              then to               else option-        in Modifiers shorts (renaming . newRenaming) args help version+        in return $ Modifiers shorts (renaming . newRenaming) args help version       (RenameOptions newRenaming) ->-        Modifiers shorts (renaming `combineRenamings` newRenaming) args help version-      (UseForPositionalArguments option typ) ->-        Modifiers shorts renaming ((option, map toUpper typ) : args) help version+        return $ Modifiers shorts (renaming `combineRenamings` newRenaming) args help version+      (UseForPositionalArguments option typ) -> case args of+        Nothing -> return $ Modifiers shorts renaming (Just (option, map toUpper typ)) help version+        Just _ -> Errors ["UseForPositionalArguments can only be used once"]       (AddOptionHelp option helpText) ->-        Modifiers shorts renaming args (insert option helpText help) version+        return $ Modifiers shorts renaming args (insert option helpText help) version       (AddVersionFlag v) ->-        Modifiers shorts renaming args help (Just v)--    combineRenamings :: (FieldString -> FieldString) -> (String -> Maybe String)-      -> FieldString -> FieldString-    combineRenamings old new fieldString =-      (old . renameUnnormalized new) fieldString--lookupMatching :: [(String, a)] -> FieldString -> Maybe a-lookupMatching list option = fmap snd $ find (\ (from, _) -> from `matches` option) list--mkShortOptions :: Modifiers -> FieldString -> [Char]-mkShortOptions (Modifiers shortMap _ _ _ _) option = fromMaybe [] (lookupMatching shortMap option)--mkLongOption :: Modifiers -> FieldString -> String-mkLongOption (Modifiers _ renaming _ _ _) option =-  normalized (renaming option)--hasPositionalArgumentsField :: Modifiers -> Bool-hasPositionalArgumentsField = not . null . positionalArgumentsField--isPositionalArgumentsField :: Modifiers -> FieldString -> Bool-isPositionalArgumentsField modifiers field =-  any (`matches` field) (map fst (positionalArgumentsField modifiers))--getPositionalArgumentType :: Modifiers -> Maybe String-getPositionalArgumentType = fmap snd . listToMaybe . positionalArgumentsField--getHelpText :: Modifiers -> FieldString -> String-getHelpText modifiers field = fromMaybe "" $ lookupMatching (helpTexts modifiers) field--getVersion :: Modifiers -> Maybe String-getVersion modifiers = version modifiers---- * deriving Modifiers---- | Derives 'AddShortOption's for all fields of the datatype that start with a---   unique character.-deriveShortOptions :: (HasDatatypeInfo a, SingI (Code a)) =>-  Proxy a -> [Modifier]-deriveShortOptions proxy =-  mkShortModifiers (flags proxy)--flags :: (SingI (Code a), HasDatatypeInfo a) =>-  Proxy a -> [String]-flags proxy = case datatypeInfo proxy of-    ADT _ _ ci -> fromNPConstructorInfo ci-    Newtype _ _ ci -> fromConstructorInfo ci-  where-    fromNPConstructorInfo :: NP ConstructorInfo xs -> [String]-    fromNPConstructorInfo Nil = []-    fromNPConstructorInfo (a :* r) =-      fromConstructorInfo a ++ fromNPConstructorInfo r--    fromConstructorInfo :: ConstructorInfo x -> [String]-    fromConstructorInfo (Constructor _) = []-    fromConstructorInfo (Infix _ _ _) = []-    fromConstructorInfo (Record _ fields) =-      fromFields fields--    fromFields :: NP FieldInfo xs -> [String]-    fromFields (FieldInfo name :* r) = name : fromFields r-    fromFields Nil = []+        return $ Modifiers shorts renaming args help (Just v) -mkShortModifiers :: [String] -> [Modifier]-mkShortModifiers fields =-    let withShorts = mapMaybe (\ field -> (field, ) <$> toShort field) fields-        allShorts = map snd withShorts-        isUnique c = case filter (== c) allShorts of-          [_] -> True-          _ -> False-    in (flip mapMaybe) withShorts $ \ (field, short) ->-          if isUnique short-            then Just (AddShortOption field short)-            else Nothing-  where-    toShort :: String -> Maybe Char-    toShort s = case dropWhile (\ c -> not (isAscii c && isAlpha c)) s of-      [] -> Nothing-      (a : _) -> Just (toLower a)+    combineRenamings :: (a -> a) -> (a -> Maybe a) -> (a -> a)+    combineRenamings old new x = old (fromMaybe x (new x))  -- * list utils to replace Data.Map @@ -185,3 +105,33 @@   if a == key     then (key, value) : r     else (a, b) : insert key value r++-- * transforming Parsers++applyModifiers :: Modifiers -> Parser Unnormalized a -> Parser Unnormalized a+applyModifiers modifiers =+  addShortOptions >>>+  renameOptions+  where+    addShortOptions = modParserOptions $ map $+      \ option ->+        case filter (\ (needle, _) -> needle `elem` longs option) (shortOptions modifiers) of+          [] -> option+          (concat . map snd -> newShorts) ->+            foldl' (flip addShort) option newShorts+    renameOptions =+      modParserOptions $ map $ modLongs $ renaming modifiers++applyModifiersLong :: Modifiers -> String -> String+applyModifiersLong modifiers long = (renaming modifiers) long++longs :: OptDescr a -> [String]+longs (Option _ ls _ _) = ls++addShort :: Char -> OptDescr a -> OptDescr a+addShort short (Option shorts longs argDescrs help) =+  Option (shorts ++ [short]) longs argDescrs help++modLongs :: (String -> String) -> OptDescr a -> OptDescr a+modLongs f (Option shorts longs descrs help) =+  Option shorts (map f longs) descrs help
+ src/System/Console/GetOpt/Generics/Modifier/Types.hs view
@@ -0,0 +1,20 @@++module System.Console.GetOpt.Generics.Modifier.Types where++data Modifiers = Modifiers {+  shortOptions :: [(String, [Char])],+  renaming :: String -> String,+  positionalArgumentsField :: Maybe (String, String),+  _helpTexts :: [(String, String)],+  version :: Maybe String+ }++getVersion :: Modifiers -> Maybe String+getVersion modifiers = version modifiers++isPositionalArgumentsField :: Modifiers -> String -> Bool+isPositionalArgumentsField modifiers field =+  maybe False ((field ==) . fst) (positionalArgumentsField modifiers)++getPositionalArgumentType :: Modifiers -> Maybe String+getPositionalArgumentType = fmap snd . positionalArgumentsField
− src/System/Console/GetOpt/Generics/Result.hs
@@ -1,83 +0,0 @@-{-# LANGUAGE DeriveFunctor #-}--module System.Console.GetOpt.Generics.Result (-  Result(..),-  errors,-  outputAndExit,-  handleResult,- ) where--import           Prelude ()-import           Prelude.Compat--import           Data.List-import           System.Exit-import           System.IO---- | Type to wrap results from the pure parsing functions.-data Result a-  = Success a-    -- ^ The CLI was used correctly and a value of type @a@ was-    --   successfully constructed.-  | Errors [String]-    -- ^ The CLI was used incorrectly. The 'Result' contains a list of error-    --   messages.-    ---    --   It can also happen that the data type you're trying to use isn't-    --   supported. See the-    --   <https://github.com/zalora/getopt-generics#getopt-generics README> for-    --   details.-  | OutputAndExit String-    -- ^ The CLI was used with @--help@. The 'Result' contains the help message.-  deriving (Show, Eq, Ord, Functor)--errors :: [String] -> Result a-errors = Errors . map removeTrailingNewline--outputAndExit :: String -> Result a-outputAndExit = OutputAndExit . stripTrailingSpaces--instance Applicative Result where-  pure = Success-  OutputAndExit message <*> _ = OutputAndExit message-  _ <*> OutputAndExit message = OutputAndExit message-  Success f <*> Success x = Success (f x)-  Errors a <*> Errors b = Errors (a ++ b)-  Errors errs <*> Success _ = Errors errs-  Success _ <*> Errors errs = Errors errs--instance Monad Result where-  return = pure-  Success a >>= b = b a-  Errors errs >>= _ = Errors errs-  OutputAndExit message >>= _ = OutputAndExit message--  (>>) = (*>)--handleResult :: Result a -> IO a-handleResult result = case result of-  Success a -> return a-  OutputAndExit message -> do-    putStr message-    exitWith ExitSuccess-  Errors errs -> do-    mapM_ (hPutStr stderr . addNewlineIfMissing) errs-    exitWith $ ExitFailure 1--addNewlineIfMissing :: String -> String-addNewlineIfMissing s-  | "\n" `isSuffixOf` s = s-  | otherwise = s ++ "\n"--removeTrailingNewline :: String -> String-removeTrailingNewline s-  | "\n" `isSuffixOf` s = init s-  | otherwise = s--stripTrailingSpaces :: String -> String-stripTrailingSpaces = reverse . inner . dropWhile (== ' ') . reverse-  where-    inner s = case s of-      ('\n' : ' ' : r) -> inner ('\n' : r)-      (a : r) -> a : inner r-      [] -> []
− src/System/Console/GetOpt/Generics/Simple.hs
@@ -1,96 +0,0 @@-{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE UndecidableInstances #-}--{-# OPTIONS_GHC -fno-warn-unrecognised-pragmas #-}--module System.Console.GetOpt.Generics.Simple where--import           Generics.SOP-import           System.Environment--import           System.Console.GetOpt.Generics.GetArguments-import           System.Console.GetOpt.Generics.Modifier-import           System.Console.GetOpt.Generics.Result--class SingI (ArgumentTypes main) => SimpleCLI main where-  {-# MINIMAL _initialFieldStates, _run #-}-  type ArgumentTypes main :: [*]-  _initialFieldStates :: Proxy main -> NP FieldState (ArgumentTypes main)-  _run :: NP I (ArgumentTypes main) -> main -> IO ()---- | 'simpleCLI' converts an IO operation into a program with a proper CLI.---   Retrieves command line arguments through 'withArgs'.---   @main@ (the given IO operation) can have arbitrarily many parameters---   provided all parameters have an instance for 'Option'.------   May throw the following exceptions:------   - @'ExitFailure' 1@ in case of invalid options. Error messages are written---     to @stderr@.---   - @'ExitSuccess'@ in case @--help@ is given. (@'ExitSuccess'@ behaves like---     a normal exception, except that -- if uncaught -- the process will exit---     with exit-code @0@.) Help output is written to @stdout@.------   Example:---- ### Start "docs/SimpleExample.hs" Haddock ###---- |--- >  import           System.Console.GetOpt.Generics--- >--- >  main :: IO ()--- >  main = simpleCLI myMain--- >--- >  myMain :: String -> Int -> Bool -> IO ()--- >  myMain s i b = print (s, i, b)---- ### End ###---- | Using the above program in bash:---- ### Start "docs/SimpleExample.bash-protocol" Haddock ###---- |--- >  $ program foo 42 true--- >  ("foo",42,True)--- >  $ program foo 42 bar--- >  cannot parse as BOOL: bar--- >  $ program --help--- >  program [OPTIONS] STRING INTEGER BOOL--- >    -h  --help  show help and exit---- ### End ###--simpleCLI :: forall main . (SimpleCLI main, All Option (ArgumentTypes main)) =>-  main -> IO ()-simpleCLI main = do-  args <- getArgs-  progName <- getProgName-  let result = do-        outputInfo progName (mkModifiers []) args-          (hliftA (const $ Comp NoSelector) (_initialFieldStates (Proxy :: Proxy main)))-        filledIn <- fillInPositionalArguments args (_initialFieldStates (Proxy :: Proxy main))-        collectResult filledIn-  f <- handleResult result-  _run f main--instance SimpleCLI (IO ()) where-  type ArgumentTypes (IO ()) = '[]-  _initialFieldStates Proxy = Nil-  _run Nil = id-  _run _ = impossible "_run"--instance (Option a, SimpleCLI rest) =>-  SimpleCLI (a -> rest) where-    type ArgumentTypes (a -> rest) = a ': ArgumentTypes rest-    _initialFieldStates Proxy = PositionalArgument :* _initialFieldStates (Proxy :: Proxy rest)-    _run (I a :* r) main = _run r (main a)-    _run _ _ = impossible "_run"
+ src/WithCli.hs view
@@ -0,0 +1,118 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DefaultSignatures #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}++module WithCli (+  withCli,+  WithCli(),+  HasArguments(argumentsParser),+  atomicArgumentsParser,+  Argument(argumentType, parseArgument),+  -- * Modifiers+  withCliModified,+  Modifier(..),+  -- * Useful Re-exports+  SOP.Generic,+  SOP.HasDatatypeInfo,+  SOP.Code,+  SOP.All2,+  Typeable,+  Proxy(..),+  ) where++-- todo: add pure api with withCli++import           Data.Proxy+import           Data.Typeable+import qualified Generics.SOP as SOP+import           System.Environment++import           System.Console.GetOpt.Generics.Modifier+import           WithCli.Argument+import           WithCli.HasArguments+import           WithCli.Parser+import           WithCli.Result++-- | 'withCli' converts an IO operation into a program with a proper CLI.+--   Retrieves command line arguments through 'withArgs'.+--   @main@ (the given IO operation) can have arbitrarily many parameters+--   provided all parameters have instances for 'HasArguments'.+--+--   May throw the following exceptions:+--+--   - @'ExitFailure' 1@ in case of invalid options. Error messages are written+--     to @stderr@.+--   - @'ExitSuccess'@ in case @--help@ is given. (@'ExitSuccess'@ behaves like+--     a normal exception, except that -- if uncaught -- the process will exit+--     with exit-code @0@.) Help output is written to @stdout@.+--+--   Example:++-- ### Start "docs/Simple.hs" "module Simple where\n\n" Haddock ###++-- |+-- >  import WithCli+-- >+-- >  main :: IO ()+-- >  main = withCli run+-- >+-- >  run :: String -> Int -> Bool -> IO ()+-- >  run s i b = print (s, i, b)++-- ### End ###++-- | Using the above program in a shell:++-- ### Start "docs/Simple.shell-protocol" "" Haddock ###++-- |+-- >  $ program foo 42 true+-- >  ("foo",42,True)+-- >  $ program --help+-- >  program [OPTIONS] STRING INTEGER BOOL+-- >    -h  --help  show help and exit+-- >  $ program foo 42 bar+-- >  cannot parse as BOOL: bar+-- >  # exit-code 1+-- >  $ program+-- >  missing argument of type STRING+-- >  missing argument of type INTEGER+-- >  missing argument of type BOOL+-- >  # exit-code 1+-- >  $ program foo 42 yes bar+-- >  unknown argument: bar+-- >  # exit-code 1++-- ### End ###++withCli :: forall main . WithCli main => main -> IO ()+withCli = withCliModified []++-- | This is a variant of 'withCli' that allows to tweak the generated+--   command line interface by providing a list of 'Modifier's.+withCliModified :: forall main . WithCli main => [Modifier] -> main -> IO ()+withCliModified mods main = do+  args <- getArgs+  modifiers <- handleResult (mkModifiers mods)+  _run modifiers (return $ emptyParser ()) (\ () -> main) args++-- | Everything that can be used as a @main@ function with 'withCli' needs to+--   have an instance of 'WithCli'. You shouldn't need to implement your own+--   instances.+class WithCli main where+  _run :: Modifiers -> Result (Parser Unnormalized a) -> (a -> main) -> [String] -> IO ()++instance WithCli (IO ()) where+  _run modifiers mkParser mkMain args = do+    progName <- getProgName+    parser <- handleResult mkParser+    a <- handleResult $ runParser progName modifiers+      (normalizeParser (applyModifiers modifiers parser)) args+    mkMain a++instance (HasArguments a, WithCli rest) => WithCli (a -> rest) where+  _run modifiers fa mkMain args =+    _run modifiers (combine fa (argumentsParser modifiers Nothing)) (\ (a, r) -> mkMain a r) args
+ src/WithCli/Argument.hs view
@@ -0,0 +1,96 @@+{-# LANGUAGE DefaultSignatures #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE InstanceSigs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverlappingInstances  #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeSynonymInstances #-}++{-# OPTIONS_GHC -fno-warn-deprecated-flags #-}++module WithCli.Argument where++import           Data.Orphans ()+import           Prelude ()+import           Prelude.Compat++import           Data.List+import           Data.Proxy+import           Text.Read++-- | 'Argument' is a typeclass for things that can be parsed as atomic values from+--   single command line arguments, e.g. strings (and filenames) and numbers.+--+--   Occasionally you might want to declare your own instance for additional+--   type safety and for providing a more informative command argument type.+--   Here's an example:++-- ### Start "docs/CustomOption.hs" "module CustomOption where\n\n" Haddock ###++-- |+-- >  {-# LANGUAGE DeriveDataTypeable #-}+-- >+-- >  import WithCli+-- >+-- >  data File = File FilePath+-- >    deriving (Show, Typeable)+-- >+-- >  instance Argument File where+-- >    argumentType Proxy = "custom-file-type"+-- >    parseArgument f = Just (File f)+-- >+-- >  instance HasArguments File where+-- >    argumentsParser = atomicArgumentsParser+-- >+-- >  main :: IO ()+-- >  main = withCli run+-- >+-- >  run :: File -> IO ()+-- >  run = print++-- ### End ###++-- | And this is how the above program behaves:++-- ### Start "docs/CustomOption.shell-protocol" "" Haddock ###++-- |+-- >  $ program --help+-- >  program [OPTIONS] custom-file-type+-- >    -h  --help  show help and exit+-- >  $ program some/file+-- >  File "some/file"++-- ### End ###++class Argument a where+  argumentType :: Proxy a -> String+  parseArgument :: String -> Maybe a++instance Argument String where+  argumentType Proxy = "STRING"+  parseArgument = Just++instance Argument Int where+  argumentType _ = "INTEGER"+  parseArgument = readMaybe++instance Argument Integer where+  argumentType _ = "INTEGER"+  parseArgument = readMaybe++instance Argument Float where+  argumentType _ = "NUMBER"+  parseArgument = readFloat++instance Argument Double where+  argumentType _ = "NUMBER"+  parseArgument = readFloat++readFloat :: (RealFloat n, Read n) => String -> Maybe n+readFloat s = case readMaybe s of+  Just n -> Just n+  Nothing+    | "." `isPrefixOf` s -> readMaybe ("0" ++ s)+    | otherwise -> Nothing
+ src/WithCli/Flag.hs view
@@ -0,0 +1,45 @@+{-# LANGUAGE DeriveFunctor #-}++module WithCli.Flag where++import           Prelude ()+import           Prelude.Compat++import           Data.Monoid+import           System.Console.GetOpt++data Flag a+  = Help+  | Version String+  | NoHelp a+  deriving (Functor)++instance Monoid a => Monoid (Flag a) where+  mappend a b = case (a, b) of+    (Help, _) -> Help+    (_, Help) -> Help+    (Version s, _) -> Version s+    (_, Version s) -> Version s+    (NoHelp a, NoHelp b) -> NoHelp (a <> b)+  mempty = NoHelp mempty++foldFlags :: [Flag a] -> Flag [a]+foldFlags flags = mconcat $ map (fmap pure) flags++helpOption :: OptDescr (Flag a)+helpOption =+  Option ['h'] ["help"] (NoArg Help) "show help and exit"++versionOption :: String -> OptDescr (Flag a)+versionOption version =+  Option ['v'] ["version"] (NoArg (Version version)) "show version and exit"++usage :: String -> [String] -> [OptDescr ()] -> String+usage progName fields options = usageInfo header options+  where+    header :: String+    header = unwords $+      progName :+      "[OPTIONS]" :+       fields +++      []
+ src/WithCli/HasArguments.hs view
@@ -0,0 +1,320 @@+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DefaultSignatures #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE InstanceSigs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverlappingInstances #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE TypeSynonymInstances #-}+{-# LANGUAGE ViewPatterns #-}++{-# OPTIONS_GHC -fno-warn-deprecated-flags #-}++module WithCli.HasArguments where++import           Data.Orphans ()+import           Prelude ()+import           Prelude.Compat++import           Data.Char+import           Data.List.Compat+import           Data.Proxy+import           Data.Traversable+import           Generics.SOP as SOP+import           System.Console.GetOpt+import           Text.Read++import           System.Console.GetOpt.Generics.Modifier+import           WithCli.Parser+import           WithCli.Normalize+import           WithCli.Argument+import           WithCli.Result++parseArgumentResult :: forall a . Argument a => Maybe String -> String -> Result a+parseArgumentResult mMsg s = case parseArgument s of+  Just x -> return x+  Nothing -> parseError (argumentType (Proxy :: Proxy a)) mMsg s++parseError :: String -> Maybe String -> String -> Result a+parseError typ mMsg s = Errors $ pure $+  "cannot parse as " ++ typ +++  maybe "" (\ msg -> " (" ++ msg ++ ")") mMsg +++  ": " ++ s++-- | Everything that can be used as an argument to your @main@ function+--   (see 'withCli') needs to have a 'HasArguments' instance.+--+--   'HasArguments' also allows to conjure up instances for record types+--   to create more complex command line interfaces. Here's an example:++-- ### Start "docs/SimpleRecord.hs" "module SimpleRecord where\n\n" Haddock ###++-- |+-- >  {-# LANGUAGE DeriveGeneric #-}+-- >+-- >  import qualified GHC.Generics+-- >  import           System.Console.GetOpt.Generics+-- >+-- >  data Options+-- >    = Options {+-- >      port :: Int,+-- >      daemonize :: Bool,+-- >      config :: Maybe FilePath+-- >    }+-- >    deriving (Show, GHC.Generics.Generic)+-- >+-- >  instance Generic Options+-- >  instance HasDatatypeInfo Options+-- >  instance HasArguments Options+-- >+-- >  main :: IO ()+-- >  main = withCli run+-- >+-- >  run :: Options -> IO ()+-- >  run = print++-- ### End ###++-- | In a shell this program behaves like this:++-- ### Start "docs/SimpleRecord.shell-protocol" "" Haddock ###++-- |+-- >  $ program --port 8080 --config some/path+-- >  Options {port = 8080, daemonize = False, config = Just "some/path"}+-- >  $ program  --port 8080 --daemonize+-- >  Options {port = 8080, daemonize = True, config = Nothing}+-- >  $ program --port foo+-- >  cannot parse as INTEGER: foo+-- >  # exit-code 1+-- >  $ program+-- >  missing option: --port=INTEGER+-- >  # exit-code 1+-- >  $ program --help+-- >  program [OPTIONS]+-- >        --port=INTEGER+-- >        --daemonize+-- >        --config=STRING (optional)+-- >    -h  --help                      show help and exit++-- ### End ###++class HasArguments a where+  argumentsParser :: Modifiers -> Maybe String -> Result (Parser Unnormalized a)+  default argumentsParser ::+    (SOP.Generic a, SOP.HasDatatypeInfo a, All2 HasArguments (Code a)) =>+    Modifiers ->+    Maybe String -> Result (Parser Unnormalized a)+  argumentsParser = const . genericParser++-- * atomic HasArguments++instance HasArguments Int where+  argumentsParser = atomicArgumentsParser++instance HasArguments Bool where+  argumentsParser = wrapForPositionalArguments "Bool" (const boolParser)++instance HasArguments String where+  argumentsParser = atomicArgumentsParser++instance HasArguments Float where+  argumentsParser = atomicArgumentsParser++instance HasArguments Double where+  argumentsParser = atomicArgumentsParser++instance (HasArguments a, HasArguments b) => HasArguments (a, b)++instance (HasArguments a, HasArguments b, HasArguments c) => HasArguments (a, b, c)++wrapForPositionalArguments :: String -> (Modifiers -> Maybe String -> Result a) -> (Modifiers -> Maybe String -> Result a)+wrapForPositionalArguments typ wrapped modifiers (Just field) =+  if isPositionalArgumentsField modifiers field+    then Errors ["UseForPositionalArguments can only be used for fields of type [String] not " ++ typ]+    else wrapped modifiers (Just field)+wrapForPositionalArguments _ wrapped modifiers Nothing = wrapped modifiers Nothing++instance Argument a => HasArguments (Maybe a) where+  argumentsParser _ = maybeParser++instance Argument a => HasArguments [a] where+  argumentsParser modifiers (Just field) =+    return $ if isPositionalArgumentsField modifiers field+      then positionalArgumentsParser+      else listParser (Just field)+  argumentsParser _ Nothing =+    return $ listParser Nothing++-- | Useful for implementing your own instances of 'HasArguments' on top+--   of a custom 'Argument' instance.+atomicArgumentsParser :: forall a . Argument a =>+  Modifiers ->+  Maybe String -> Result (Parser Unnormalized a)+atomicArgumentsParser =+  wrapForPositionalArguments typ inner+  where+    typ = argumentType (Proxy :: Proxy a)++    inner modifiers mLong = return $ case mLong of+      Nothing -> withoutLongOption+      Just long -> withLongOption modifiers long++    withoutLongOption = Parser {+      parserDefault = Nothing,+      parserOptions = [],+      parserNonOptions =+        [NonOptionsParser typ (\ (s : r) -> fmap ((, r) . const . Just) $ parseArgumentResult Nothing s)],+      parserConvert = \ case+        Just a -> return a+        Nothing -> Errors $ pure $+          "missing argument of type " ++ typ+    }++    withLongOption modifiers long = Parser {+      parserDefault = Left (),+      parserOptions = pure $+        Option [] [long]+          (fmap (fmap (const . Right)) $+            ReqArg (parseArgumentResult Nothing) typ)+          "",+      parserNonOptions = [],+      parserConvert = \ case+        Right a -> return a+        Left () -> Errors $ pure $+          "missing option: --" ++ normalize (applyModifiersLong modifiers long) ++ "=" ++ typ+    }++listParser :: forall a . Argument a =>+  Maybe String -> Parser Unnormalized [a]+listParser mLong = case mLong of+  Nothing -> positionalArgumentsParser+  Just long -> Parser {+    parserDefault = [],+    parserOptions = pure $+      Option [] [long]+        (ReqArg+          (\ s -> fmap (\ a -> (++ [a])) (parseArgumentResult (Just "multiple possible") s))+          (argumentType (Proxy :: Proxy a) ++ " (multiple possible)"))+        "",+    parserNonOptions = [],+    parserConvert = return+  }++positionalArgumentsParser :: forall a . Argument a =>+  Parser Unnormalized [a]+positionalArgumentsParser = Parser {+  parserDefault = [],+  parserOptions = [],+  parserNonOptions = [NonOptionsParser (argumentType (Proxy :: Proxy a)) parse],+  parserConvert = return+}+  where+    parse :: [String] -> Result ([a] -> [a], [String])+    parse args = do+      mods <- forM args $ \ arg ->+        case parseArgument arg of+          Just a -> return (a :)+          Nothing -> parseError (argumentType (Proxy :: Proxy a)) Nothing arg+      return (foldl' (.) id mods, [])++maybeParser :: forall a . Argument a =>+  Maybe String -> Result (Parser Unnormalized (Maybe a))+maybeParser mLong = case mLong of+  Nothing -> Errors ["cannot use Maybes for positional arguments"]+  Just long -> return $ Parser {+    parserDefault = Nothing,+    parserOptions = pure $+      Option [] [long]+        (ReqArg+          (\ s -> fmap (\ a -> (const (Just a))) (parseArgumentResult (Just "optional") s))+          (argumentType (Proxy :: Proxy a) ++ " (optional)"))+        "",+    parserNonOptions = [],+    parserConvert = return+  }++boolParser :: Maybe String -> Result (Parser Unnormalized Bool)+boolParser mLong = return $ case mLong of+  Nothing -> Parser {+    parserDefault = Nothing,+    parserOptions = [],+    parserNonOptions = pure $+      (NonOptionsParser "BOOL" (\ (s : r) -> (, r) <$> maybe (parseError "BOOL" Nothing s) (return . const . Just) (parseBool s))),+    parserConvert = \ case+      Just x -> return x+      Nothing -> Errors $ pure $+        "missing argument of type BOOL"+  }+  Just long -> Parser {+    parserDefault = False,+    parserOptions = pure $+      Option [] [long]+        (NoArg (return (const True)))+        "",+    parserNonOptions = [],+    parserConvert = return+  }++parseBool :: String -> Maybe Bool+parseBool s+  | map toLower s `elem` ["true", "yes", "on"] = Just True+  | map toLower s `elem` ["false", "no", "off"] = Just False+  | otherwise = case readMaybe s of+    Just (n :: Integer) -> Just (n > 0)+    Nothing -> Nothing++-- * generic HasArguments++genericParser :: forall a .+  (Generic a, HasDatatypeInfo a, All2 HasArguments (Code a)) =>+  Modifiers ->+  Result (Parser Unnormalized a)+genericParser modifiers = fmap (fmap to) $ case datatypeInfo (Proxy :: Proxy a) of+  ADT _ typeName (constructorInfo :* Nil) ->+    case constructorInfo of+      (Record _ fields) ->+        fmap (fmap (SOP . Z)) (fieldsParser modifiers fields)+      Constructor{} ->+        fmap (fmap (SOP . Z)) (noSelectorsParser modifiers shape)+      Infix{} ->+        err typeName "infix constructors"+  ADT _ typeName Nil ->+    err typeName "empty data types"+  ADT _ typeName (_ :* _ :* _) ->+    err typeName "sum types"+  Newtype _ _ (Record _ fields) ->+    fmap (fmap (SOP . Z)) (fieldsParser modifiers fields)+  Newtype _ typeName (Constructor _) ->+    err typeName "constructors without field labels"+  where+    err typeName message = Errors $ pure $+      "getopt-generics doesn't support " ++ message +++      " (" ++ typeName ++ ")."++fieldsParser :: All HasArguments xs =>+  Modifiers -> NP FieldInfo xs -> Result (Parser Unnormalized (NP I xs))+fieldsParser modifiers = \ case+  Nil -> return $ emptyParser Nil+  FieldInfo fieldName :* rest ->+    fmap (fmap (\ (a, r) -> a :* r)) $+      combine (fmap (fmap I) $ (argumentsParser modifiers (Just fieldName))) (fieldsParser modifiers rest)++noSelectorsParser :: All HasArguments xs =>+  Modifiers -> Shape xs -> Result (Parser Unnormalized (NP I xs))+noSelectorsParser modifiers = \ case+  ShapeNil -> return $ emptyParser Nil+  ShapeCons rest -> +    fmap (fmap (\ (a, r) -> a :* r)) $+      combine (fmap (fmap I) $ (argumentsParser modifiers Nothing)) (noSelectorsParser modifiers rest)
+ src/WithCli/Normalize.hs view
@@ -0,0 +1,32 @@++module WithCli.Normalize (+  normalize,+  matches,+ ) where++import           Data.Char++matches :: String -> String -> Bool+matches a b =+  normalize a == normalize b++normalize :: String -> String+normalize s =+  if all (not . isAllowedChar) s+    then s+    else+      slugify $+      dropWhile (== '-') $+      filter isAllowedChar $+      map (\ c -> if c == '_' then '-' else c) $+      s+  where+    slugify (a : r)+      | isUpper a = slugify (toLower a : r)+    slugify (a : b : r)+      | isUpper b = a : '-' : slugify (toLower b : r)+      | otherwise = a : slugify (b : r)+    slugify x = x++isAllowedChar :: Char -> Bool+isAllowedChar c = (isAscii c && isAlphaNum c) || (c `elem` "-_")
+ src/WithCli/Parser.hs view
@@ -0,0 +1,135 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}++module WithCli.Parser where++import           Data.Orphans ()+import           Prelude ()+import           Prelude.Compat++import           Control.Arrow+import           Control.Monad+import           System.Console.GetOpt as Base++import           System.Console.GetOpt.Generics.Modifier.Types+import           WithCli.Flag+import           WithCli.Normalize+import           WithCli.Result++data NonOptionsParser uninitialized =+  NonOptionsParser {+    nonOptionsType :: String,+    nonOptionsParser :: [String] -> Result (uninitialized -> uninitialized, [String])+  }++combineNonOptionsParser :: [NonOptionsParser u] -> [NonOptionsParser v]+  -> [NonOptionsParser (u, v)]+combineNonOptionsParser a b =+  map (modMod first) a +++  map (modMod second) b+  where+    modMod :: ((a -> a) -> (b -> b)) -> NonOptionsParser a -> NonOptionsParser b+    modMod f (NonOptionsParser field parser) =+      NonOptionsParser field (fmap (fmap (first f)) parser)++data Parser phase a where+  Parser :: {+    parserDefault :: uninitialized,+    parserOptions :: [OptDescr (Result (uninitialized -> uninitialized))],+    parserNonOptions :: [NonOptionsParser uninitialized],+    parserConvert :: uninitialized -> Result a+  } -> Parser phase a++instance Functor (Parser phase) where+  fmap f (Parser def options nonOptions convert) =+    Parser def options nonOptions (fmap f . convert)++-- phases:+data Unnormalized+data Normalized++emptyParser :: a -> Parser phase a+emptyParser a = Parser {+  parserDefault = a,+  parserOptions = [],+  parserNonOptions = [],+  parserConvert = return+}++normalizeParser :: Parser Unnormalized a -> Parser Normalized a+normalizeParser (Parser d options nonOptions convert) =+  Parser d (map (mapLongOptions normalize) options) nonOptions convert+  where+    mapLongOptions :: (String -> String) -> OptDescr a -> OptDescr a+    mapLongOptions f (Option shorts longs argDescr help) =+      Option shorts (map f longs) argDescr help++modParserOptions :: (forall x . [OptDescr (Result x)] -> [OptDescr (Result x)])+  -> Parser Unnormalized a -> Parser Unnormalized a+modParserOptions f (Parser def options nonOptions convert) =+  Parser def (f options) nonOptions convert++combine :: forall a b phase .+  Result (Parser phase a) -> Result (Parser phase b)+  -> Result (Parser phase (a, b))+combine a b = inner <$> a <*> b+  where+    inner :: Parser phase a -> Parser phase b -> Parser phase (a, b)+    inner (Parser defaultA optionsA nonOptionsA convertA) (Parser defaultB optionsB nonOptionsB convertB) =+      Parser {+        parserDefault = (defaultA, defaultB),+        parserOptions =+          map (fmap (fmap first)) optionsA ++ map (fmap (fmap second)) optionsB,+        parserNonOptions = combineNonOptionsParser nonOptionsA nonOptionsB,+        parserConvert =+          \ (u, v) -> (,) <$> (convertA u) <*> (convertB v)+      }++fillInOptions :: [Result (u -> u)] -> u -> Result u+fillInOptions [] u = return u+fillInOptions (option : options) u = do+  f <- option+  fillInOptions options (f u)++fillInNonOptions :: [[String] -> Result (u -> u, [String])] -> [String] -> u+  -> Result u+fillInNonOptions (parser : parsers) nonOptions@(_ : _) u = do+  (p, rest) <- parser nonOptions+  fillInNonOptions parsers rest (p u)+fillInNonOptions [] [] u =+  return u+fillInNonOptions [] nonOptions _ =+  Errors (map ("unknown argument: " ++) nonOptions)+fillInNonOptions _ [] u = return u++runParser :: String -> Modifiers -> Parser Normalized a -> [String] -> Result a+runParser progName modifiers Parser{..} args = do+  let versionOptions = maybe []+        (\ v -> pure $ versionOption (progName ++ " version " ++ v))+        (getVersion modifiers)+      options = map (fmap NoHelp) parserOptions ++ [helpOption] ++ versionOptions+      (flags, nonOptions, errs) =+        Base.getOpt Base.Permute options args+  case foldFlags flags of+    Help -> OutputAndExit $+      let fields = case getPositionalArgumentType modifiers of+            Nothing -> map nonOptionsType parserNonOptions+            Just typ -> ["[" ++ typ ++ "]"]+      in usage progName fields (map void options)+    Version msg -> OutputAndExit msg+    NoHelp innerFlags ->+      reportErrors errs *>+      (fillInOptions innerFlags parserDefault >>=+       fillInNonOptions (map nonOptionsParser parserNonOptions) nonOptions >>=+       parserConvert)+  where+    reportErrors :: [String] -> Result ()+    reportErrors = \ case+      [] -> return ()+      errs -> Errors errs
+ src/WithCli/Result.hs view
@@ -0,0 +1,78 @@+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE ImplicitParams #-}+{-# LANGUAGE ViewPatterns #-}++module WithCli.Result (+  Result(..),+  handleResult,+  sanitize,+ ) where++import           Prelude ()+import           Prelude.Compat++import           Control.Arrow+import           Data.List.Compat+import           System.Exit+import           System.IO++-- | Type to wrap results from the pure parsing functions.+data Result a+  = Success a+    -- ^ The CLI was used correctly and a value of type @a@ was+    --   successfully constructed.+  | Errors [String]+    -- ^ The CLI was used incorrectly. The 'Result' contains a list of error+    --   messages.+    --+    --   It can also happen that the data type you're trying to use isn't+    --   supported. See the+    --   <https://github.com/zalora/getopt-generics#getopt-generics README> for+    --   details.+  | OutputAndExit String+    -- ^ The CLI was used with @--help@. The 'Result' contains the help message.+  deriving (Show, Eq, Ord, Functor)++instance Applicative Result where+  pure = Success+  OutputAndExit message <*> _ = OutputAndExit message+  _ <*> OutputAndExit message = OutputAndExit message+  Success f <*> Success x = Success (f x)+  Errors a <*> Errors b = Errors (a ++ b)+  Errors errs <*> Success _ = Errors errs+  Success _ <*> Errors errs = Errors errs++instance Monad Result where+  return = pure+  Success a >>= b = b a+  Errors errs >>= _ = Errors errs+  OutputAndExit message >>= _ = OutputAndExit message++  (>>) = (*>)++handleResult :: Result a -> IO a+handleResult result = case result of+  Success a -> return a+  OutputAndExit message -> do+    putStr $ sanitize message+    exitWith ExitSuccess+  Errors errs -> do+    hPutStr stderr $ sanitize $ intercalate "\n" errs+    exitWith $ ExitFailure 1++sanitize :: String -> String+sanitize =+  lines >>>+  map stripTrailingSpaces >>>+  filter (not . null) >>>+  map (++ "\n") >>>+  concat++stripTrailingSpaces :: String -> String+stripTrailingSpaces =+  reverse . inner . dropWhile (`elem` [' ', '\n']) . reverse+  where+    inner s = case s of+      ('\n' : ' ' : r) -> inner ('\n' : r)+      (a : r) -> a : inner r+      [] -> []
− test/BashProtocol.hs
@@ -1,44 +0,0 @@--module BashProtocol (testBashProtocol) where--import           Control.Exception-import           Control.Monad-import           Data.List-import           System.Environment.Compat-import           System.FilePath-import           System.IO-import           System.IO.Silently-import           System.Process-import           Test.Hspec--testBashProtocol :: FilePath -> String -> IO ()-testBashProtocol program bashProtocol = do-  assert (takeFileName program == "program") (return ())-  let protocol = parseProtocol bashProtocol-  testProtocol (takeDirectory program) protocol--data Protocol-  = Protocol {-    _command :: String,-    _expected :: [String]-  }-  deriving (Show)--parseProtocol :: String -> [Protocol]-parseProtocol = inner . lines-  where-    inner :: [String] -> [Protocol]-    inner [] = []-    inner (('$' : ' ' : command) : rest) =-      let (expected, next) = span (not . ("$ " `isPrefixOf`)) rest in-      Protocol command expected : inner next-    inner lines = error ("parseProtocol: cannot parse: " ++ show lines)--testProtocol :: FilePath -> [Protocol] -> IO ()-testProtocol programDir protocol = do-  path <- getEnv "PATH"-  setEnv "PATH" (programDir ++ ":" ++ path)-  forM_ protocol $ \ (Protocol command expected) -> do-    hPutStrLn stderr ("running: " ++ command)-    output <- hCapture_ [stdout, stderr] $ system command-    output `shouldBe` unlines expected
− test/Examples.hs
@@ -1,57 +0,0 @@--import           Prelude ()-import           Prelude.Compat--import           Control.Monad-import           Data.List-import           System.Directory-import           System.Exit.Compat-import           System.FilePath-import           System.IO.Temp-import           System.Process-import           Test.Hspec--import           BashProtocol--main :: IO ()-main = hspec spec--spec :: Spec-spec = do-  files <- runIO $-    map ("docs" </>) <$>-    filter ((".hs" ==) . takeExtension) <$>-    sort <$>-    getDirectoryContents "docs"--  forM_ files $ \ file -> do-    describe "docs directory ghc" $ do-      it ("compile and run " ++ file) $ do-        withSystemTempDirectory "getopt-generics-test" $ \ tempDir -> do-          compile tempDir file >>= execute file--compile :: FilePath -> FilePath -> IO FilePath-compile tempDir file = do-  let program = tempDir </> "program"-  callCommandCompat $ "cabal exec -- ghc " ++ file ++-    " -outputdir " ++ (tempDir </> "build") ++-    " -o " ++ program ++-    " -isrc -O0" ++-    " -Wall -Werror -fno-warn-unused-binds -fno-warn-name-shadowing"-  return program--execute :: FilePath -> FilePath -> IO ()-execute haskellFile program = do-  let bashProtocolFile = dropExtension haskellFile <.> "bash-protocol"-  exists <- doesFileExist bashProtocolFile-  if not exists-    then callCommandCompat $ program ++ " --help"-    else do-      testBashProtocol program =<< readFile bashProtocolFile--callCommandCompat :: String -> IO ()-callCommandCompat command = do-  exitCode <- system command-  case exitCode of-    ExitSuccess -> return ()-    failure@ExitFailure{} -> exitWith failure
+ test/ExamplesSpec.hs view
@@ -0,0 +1,51 @@++module ExamplesSpec where++import           Prelude ()+import           Prelude.Compat++import           Control.Monad+import           System.FilePath+import           Test.Hspec++import           ShellProtocol++import qualified CustomOption+import qualified CustomOptionRecord+import qualified RecordType+import qualified Simple+import qualified SimpleRecord+import qualified Test01+import qualified Test02+import qualified Test03+import qualified Test04++examples :: [(IO (), String)]+examples =+  (Test01.main, "Test01") :+  (Test02.main, "Test02") :+  (Test03.main, "Test03") :+  (Test04.main, "Test04") :++  (Simple.main, "Simple") :+  (SimpleRecord.main, "SimpleRecord") :+  (RecordType.main, "RecordType") :+  (SimpleRecord.main, "SimpleRecord") :+  (CustomOption.main, "CustomOption") :+  (CustomOptionRecord.main, "CustomOptionRecord") :+  []++main :: IO ()+main = hspec spec++spec :: Spec+spec = do+  describe "shell protocols" $ do+    forM_ examples $ \ (program, name) ->+      it name $ do+        test program name++test :: IO () -> String -> IO ()+test program name = do+  protocol <- readFile ("docs" </> name <.> "shell-protocol")+  testShellProtocol program protocol
test/ModifiersSpec.hs view
@@ -13,7 +13,7 @@ spec = do   describe "AddShortOption" $ do     it "allows modifiers for short options" $ do-      modsParse [AddShortOption "camel-case" 'x'] "-x foo"+      modsParse [AddShortOption "camelCase" 'x'] "-x foo"         `shouldBe` Success (CamelCaseOptions "foo")      it "allows modifiers in camelCase" $ do@@ -39,19 +39,17 @@        it "disregards the earlier renaming" $ do         let Errors errs = parse' "--foo foo"-        errs `shouldContain` ["unrecognized option `--foo'"]+        errs `shouldContain` ["unrecognized option `--foo'\n"]      it "contains renamed options in error messages" $ do       let Errors errs = modsParse             [RenameOption "camelCase" "foo"]             "" :: Result CamelCaseOptions+      -- _ <- error $ show errs       show errs `shouldNotContain` "camelCase"+      show errs `shouldNotContain` "camel-case"       show errs `shouldContain` "foo" -    it "allows to address fields in Modifiers in slugified form" $ do-      modsParse [RenameOption "camel-case" "foo"] "--foo bar"-        `shouldBe` Success (CamelCaseOptions "bar")-     it "" $ do       modsParse [RenameOption "bar" "one", RenameOption "baz" "two"] "--one 1 --two foo"         `shouldBe` Success (Foo (Just 1) "foo" False)@@ -59,7 +57,7 @@   describe "AddVersionFlag" $ do     it "implements --version" $ do       let OutputAndExit output = modsParse [AddVersionFlag "1.0.0"] "--version" :: Result Foo-      output `shouldBe` "prog-name version 1.0.0\n"+      output `shouldBe` "prog-name version 1.0.0"      it "--help takes precedence over --version" $ do       let OutputAndExit output = modsParse [AddVersionFlag "1.0.0"] "--version --help" :: Result Foo
test/ModifiersSpec/UseForPositionalArgumentsSpec.hs view
@@ -1,9 +1,11 @@ {-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE ScopedTypeVariables #-}  module ModifiersSpec.UseForPositionalArgumentsSpec where  import           Data.List import qualified GHC.Generics as GHC+import           System.Environment import           Test.Hspec  import           System.Console.GetOpt.Generics@@ -43,7 +45,7 @@       [UseForPositionalArguments "positionalArguments" "type"]       "--positional-arguments foo"         `shouldBe`-      (Errors ["unrecognized option `--positional-arguments'"]+      (Errors ["unrecognized option `--positional-arguments'\n"]         :: Result WithPositionalArguments)    it "complains about fields that don't have type [String]" $ do@@ -67,3 +69,14 @@           []     (modsParse modifiers [] :: Result WithMultiplePositionalArguments)       `shouldBe` Errors ["UseForPositionalArguments can only be used once"]++  context "when used without selector" $ do+    it "automatically uses positional arguments for [Int]" $ do+      withArgs (words "1 2 3") $+        withCli $ \ (xs :: [Int]) -> do+          xs `shouldBe` [1, 2, 3]++    it "automatically uses positional arguments for [String]" $ do+      withArgs (words "foo bar") $+        withCli $ \ (xs :: [String]) -> do+          xs `shouldBe` (words "foo bar")
+ test/ShellProtocol.hs view
@@ -0,0 +1,50 @@+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE ViewPatterns #-}++module ShellProtocol (testShellProtocol) where++import           Control.Exception+import           Control.Monad+import           Data.List+import           System.Environment+import           System.Exit+import           System.IO+import           System.IO.Silently+import           Test.Hspec++testShellProtocol :: IO () -> String -> IO ()+testShellProtocol program shellProtocol = do+  let protocol = parseProtocol shellProtocol+  testProtocol program protocol++data Protocol+  = Protocol {+    _args :: [String],+    _expected :: [String]+  }+  deriving (Show)++parseProtocol :: String -> [Protocol]+parseProtocol = inner . lines+  where+    inner :: [String] -> [Protocol]+    inner [] = []+    inner ((words -> "$" : "program" : args) : rest) =+      let (expected, next) = span (not . ("$ " `isPrefixOf`)) rest in+      Protocol args expected : inner next+    inner lines = error ("parseProtocol: cannot parse: " ++ show lines)++testProtocol :: IO () -> [Protocol] -> IO ()+testProtocol program protocol = do+  forM_ protocol $ \ (Protocol args expected) -> do+    output <- hCapture_ [stdout, stderr] $+      handle (\ (e :: ExitCode) -> printExitCode e) $+      withArgs args $+      withProgName "program" $+      program+    output `shouldBe` unlines expected++printExitCode :: ExitCode -> IO ()+printExitCode e = case e of+  ExitFailure n -> hPutStrLn stderr ("# exit-code " ++ show n)+  ExitSuccess -> return ()
− test/System/Console/GetOpt/Generics/FieldStringSpec.hs
@@ -1,79 +0,0 @@-{-# LANGUAGE ViewPatterns #-}--module System.Console.GetOpt.Generics.FieldStringSpec where--import           Data.Char-import           Test.Hspec-import           Test.QuickCheck hiding (Success)--import           System.Console.GetOpt.Generics.FieldString--normalize :: String -> String-normalize = normalized . mkFieldString--isValidInputChar :: Char -> Bool-isValidInputChar c = c `elem` ['A' .. 'Z'] ++ ['a' .. 'z'] ++ ['0' .. '9'] ++ "-_"--isAllowedOutputChar :: Char -> Bool-isAllowedOutputChar c = c `elem` ['a' .. 'z'] ++ ['0' .. '9'] ++ "-"--upperCaseChar :: Gen Char-upperCaseChar = elements ['A' .. 'Z']--spec :: Spec-spec = do-  describe "normalized" $ do-    it "is idempotent" $ do-      property $ \ x -> do-        let once = normalize x-            twice = normalize once-        once `shouldBe` twice--    it "replaces underscores with dashes" $ do-      normalize "foo_bar" `shouldBe` "foo-bar"--    it "doesn't modify digits" $ do-      normalize "foo2bar" `shouldBe` "foo2bar"--    it "when there's one valid character it returns only dashes and lower case characters" $ do-      property $ \ x ->-        any isValidInputChar x ==>-        normalize x `shouldSatisfy`-          (\ s -> all isAllowedOutputChar s)--    it "when there are no valid characters it returns its input" $ do-      property $ forAll (listOf (arbitrary `suchThat` (not . isValidInputChar))) $ \ x ->-        normalize x `shouldBe` x--    it "replaces camelCase with dashes" $ do-      let isValidPrefixChar c = c `elem` ['A' .. 'Z'] ++ ['a' .. 'z']-      property $-        \ prefix suffix ->-        (any isValidPrefixChar prefix) ==>-        forAll upperCaseChar $ \ upper ->-        counterexample (prefix ++ [upper] ++ suffix) $-        normalize (prefix ++ [upper] ++ suffix)-          `shouldBe`-            normalize prefix ++ "-" ++ normalize (toLower upper : suffix)--  describe "matches" $ do-    it "matches normalized strings" $ do-      property $ \ s ->-        normalize s `matches` mkFieldString s--    it "matches unnormalized strings" $ do-      property $ \ s ->-        s `matches` mkFieldString s--  describe "renameUnnormalized" $ do-    it "allows to rename the unnormalized field names" $ do-      let f "camelCaseFoo" = Just "caseFoo"-          f _ = Nothing-      normalized (renameUnnormalized f (mkFieldString "camelCaseFoo")) `shouldBe`-        "case-foo"--    it "doesn't allow to rename normalized field names" $ do-      let f "camel-case-foo" = Just "case-foo"-          f _ = Nothing-      normalized (renameUnnormalized f (mkFieldString "camelCaseFoo")) `shouldBe`-        "camel-case-foo"
test/System/Console/GetOpt/Generics/ModifierSpec.hs view
@@ -2,34 +2,15 @@  module System.Console.GetOpt.Generics.ModifierSpec where -import           Data.Char-import           Data.Proxy import qualified GHC.Generics import           Generics.SOP import           Test.Hspec-import           Test.QuickCheck hiding (Result(..)) -import           System.Console.GetOpt.Generics.GetArguments import           System.Console.GetOpt.Generics.Modifier-import           System.Console.GetOpt.Generics.Result+import           Util  spec :: Spec spec = do-  describe "deriveShortOptions" $ do-    it "includes modifiers for short options" $ do-      let [AddShortOption long short] = deriveShortOptions (Proxy :: Proxy Foo)-      (long, short) `shouldBe` ("bar", 'b')--    it "doesn't include modifiers for short options in case of overlaps" $ do-      null (deriveShortOptions (Proxy :: Proxy Overlap))--  describe "mkShortModifiers" $ do-    it "returns only lower-case ascii alpha characters as short options" $ do-      property $ \ strings ->-        all-          (\ (AddShortOption _ c) -> isLower c && isAscii c && isAlpha c)-          (mkShortModifiers strings)-   describe "insertWith" $ do     it "combines existing values with the given function" $ do       insertWith (++) (1 :: Integer) "bar" [(1, "foo")]@@ -37,16 +18,8 @@    describe "getVersion" $ do     it "returns the version" $ do-      let modifiers = mkModifiers [AddVersionFlag "1.0.0"]+      let modifiers = unsafeModifiers [AddVersionFlag "1.0.0"]       getVersion modifiers `shouldBe` Just "1.0.0"--  context "when used with Modifiers" $ do-    describe "parseArguments" $ do-      it "allows to specify a flag specific help" $ do-        let OutputAndExit output =-              parseArguments "prog-name" [AddOptionHelp "bar" "bar help text"]-                (words "--help") :: Result Foo-        output `shouldContain` "--bar=STRING  bar help text"  data Foo   = Foo {
− test/System/Console/GetOpt/Generics/ResultSpec.hs
@@ -1,66 +0,0 @@-{-# LANGUAGE ScopedTypeVariables #-}--module System.Console.GetOpt.Generics.ResultSpec where--import           Control.Exception-import           Data.List-import           System.Exit-import           System.IO-import           System.IO.Silently-import           Test.Hspec-import           Test.QuickCheck hiding (Result(..))--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"]--  describe "errors" $ do-    it "removes trailing newlines" $ do-      (errors ["foo\n", "bar", "baz\n"] :: Result ()) `shouldBe`-        Errors ["foo", "bar", "baz"]--  describe "outputAndExit" $ do-    it "removes trailing spaces" $ do-      (outputAndExit "foo \nbar" :: Result ()) `shouldBe`-        OutputAndExit "foo\nbar"--    it "removes trailing spaces at the end" $ do-      (outputAndExit "foo " :: Result ()) `shouldBe`-        OutputAndExit "foo"--    it "quickcheck" $ do-      property $ \ s ->-        let OutputAndExit output = outputAndExit s-        in output `shouldSatisfy` (not . (" \n" `isInfixOf`))--    it "only strips spaces" $ do-      property $ \ s ->-        let OutputAndExit output = outputAndExit s-        in-          filter (/= ' ') output-            `shouldBe`-          filter (/= ' ') s--  describe "handleResult" $ do-    it "appends '\\n' at the end of error messages if missing" $ do-      output <- hCapture_ [stderr] $ do-        handle (\ (_ :: ExitCode) -> return ()) $ do-          _ <- handleResult (Errors ["foo", "bar\n", "baz"])-          return ()-      output `shouldBe` "foo\nbar\nbaz\n"--    context "OutputAndExit" $ do-      it "throws ExitSuccess" $ do-        handleResult (OutputAndExit "foo")-          `shouldThrow` (== ExitSuccess)--    context "Errors" $ do-      it "throws an ExitFailure" $ do-        handleResult (Errors ["foo"])-          `shouldThrow` (== ExitFailure 1)
− test/System/Console/GetOpt/Generics/SimpleSpec.hs
@@ -1,64 +0,0 @@--module System.Console.GetOpt.Generics.SimpleSpec where--import           System.Environment-import           System.Exit-import           System.IO-import           System.IO.Silently-import           Test.Hspec--import           System.Console.GetOpt.Generics.Simple--spec :: Spec-spec = do-  describe "simpleCLI" $ do-    context "no arguments" $ do-      it "executes the operation in case of no command line arguments" $ do-        let main :: IO ()-            main = putStrLn "success"-        (capture_ $ withArgs [] $ simpleCLI main)-          `shouldReturn` "success\n"--      it "produces nice error messages" $ do-        let main :: IO ()-            main = putStrLn "success"-        output <- hCapture_ [stderr] (withArgs ["foo"] (simpleCLI main) `shouldThrow` (== ExitFailure 1))-        output `shouldBe` "unknown argument: foo\n"--    context "1 argument" $ do-      it "creates CLIs for unary IO operations" $ do-        let main :: String -> IO ()-            main s = putStrLn ("success: " ++ s)-        (capture_ $ withArgs ["foo"] $ simpleCLI main)-          `shouldReturn` "success: foo\n"--      it "parses Ints" $ do-        let main :: Int -> IO ()-            main n = putStrLn ("success: " ++ show n)-        (capture_ $ withArgs ["12"] $ simpleCLI main)-          `shouldReturn` "success: 12\n"--      it "error parsing" $ do-        let main :: Int -> IO ()-            main n = putStrLn ("error: " ++ show n)-        output <- hCapture_ [stderr] (withArgs (words "12 foo") (simpleCLI main) `shouldThrow` (== ExitFailure 1))-        output `shouldBe` "unknown argument: foo\n"--      it "offers --help" $ do-        let main :: Int -> IO ()-            main n = putStrLn ("error: " ++ show n)-        output <- capture_ (withArgs ["--help"] (simpleCLI main) `shouldThrow` (== ExitSuccess))-        output `shouldContain` "[OPTIONS] INTEGER"-        output `shouldContain` "-h  --help  show help and exit"--    it "works for IO operations with 2 arguments" $ do-      let main :: Int -> Bool -> IO ()-          main n b = putStrLn ("success: " ++ show (n, b)) -      (capture_ $ withArgs (words "42 yes") $ simpleCLI main)-        `shouldReturn` "success: (42,True)\n"--    it "works for IO operations with 3 arguments" $ do-      let main :: String -> Int -> Bool -> IO ()-          main s n b = putStrLn ("success: " ++ show (s, n, b))-      (capture_ $ withArgs (words "foo 42 yes") $ simpleCLI main)-        `shouldReturn` "success: (\"foo\",42,True)\n"
test/System/Console/GetOpt/GenericsSpec.hs view
@@ -8,16 +8,19 @@ import           Prelude () import           Prelude.Compat +import           Control.Exception import           Data.Foldable (forM_) import           Data.List (isPrefixOf, isSuffixOf)-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.QuickCheck hiding (Result(..))  import           System.Console.GetOpt.Generics import           Util+import           WithCli.Result  spec :: Spec spec = do@@ -26,7 +29,6 @@   part3   part4   part5-  part6  data Foo   = Foo {@@ -63,11 +65,15 @@       parse "--bool --baz foo" `shouldBe`         Success (Foo Nothing "foo" True) +    it "allows to overwrite String options" $ do+      parse "--baz one --baz two"+        `shouldBe` Success (Foo Nothing "two" False)+     context "with invalid arguments" $ do       it "prints out an error" $ do         let Errors messages = parse "--no-such-option" :: Result Foo         messages `shouldBe`-          ["unrecognized option `--no-such-option'",+          ["unrecognized option `--no-such-option'\n",            "missing option: --baz=STRING"]        it "prints errors for missing options" $ do@@ -101,8 +107,11 @@         output `shouldContain` "show help and exit"        it "does not contain trailing spaces" $ do-        let OutputAndExit output = parse "--help" :: Result Foo-        forM_ (lines output) $ \ line ->+        output <-+          hCapture_ [stdout] $+          handle (\ ExitSuccess -> return ()) $+          handleResult $ ((parse "--help" :: Result Foo) >> return ())+        forM_ (lines output) $ \ line -> do           line `shouldSatisfy` (not . (" " `isSuffixOf`))        it "complains when the options datatype is not allowed" $ do@@ -113,11 +122,6 @@         let OutputAndExit output = parse "--help" :: Result Foo         output `shouldSatisfy` ("prog-name [OPTIONS]\n" `isPrefixOf`) -  describe "parseArguments" $ do-    it "allows to overwrite String options" $ do-      parse "--baz one --baz two"-        `shouldBe` Success (Foo Nothing "two" False)- data ListOptions   = ListOptions {     multiple :: [Int]@@ -182,39 +186,6 @@       parse "--with-underscore foo"         `shouldBe` Success (WithUnderscore "foo") -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--part5 :: Spec-part5 = do-  describe "parseArguments" $ do-    context "CustomFields" $ do-      it "allows easy implementation of custom field types" $ do-        parse "--custom foo --custom-list bar --custom-maybe baz"-          `shouldBe` Success (CustomFields CFoo [CBar] (Just CBaz))- data WithoutSelectors   = WithoutSelectors String Bool Int   deriving (Eq, Show, GHC.Generic)@@ -222,8 +193,8 @@ instance Generic WithoutSelectors instance HasDatatypeInfo WithoutSelectors -part6 :: Spec-part6 = do+part5 :: Spec+part5 = do   describe "parseArguments" $ do     context "WithoutSelectors" $ do       it "populates fields without selectors from positional arguments" $ do@@ -248,43 +219,3 @@       it "allows to use tuples" $ do         (parse "42 bar" :: Result (Int, String))           `shouldBe` Success (42, "bar")--  describe "Option.Bool" $ do-    describe "parseArgument" $ do-      forM_ ["true", "True", "tRue", "TRUE", "yes", "yEs", "on", "oN"] $ \ true ->-        it ("parses '" ++ true ++ "' as True") $ do-          parseArgument true `shouldBe` Just True--      forM_ ["false", "False", "falSE", "FALSE", "no", "nO", "off", "ofF"] $ \ false ->-        it ("parses '" ++ false ++ "' as False") $ do-          parseArgument false `shouldBe` Just False--      it "parses every positive integer as true" $ do-        property $ \ (n :: Int) ->-          n > 0 ==>-          parseArgument (show n) `shouldBe` Just True--      it "parses every non-positive integer as false" $ do-        property $ \ (n :: Int) ->-          n <= 0 ==>-          parseArgument (show n) `shouldBe` Just False--      it "doesn't parse 'foo'" $ do-        parseArgument "foo" `shouldBe` (Nothing :: Maybe Bool)--  describe "Option.Double" $ do-    it "parses doubles" $ do-      parseArgument "1.2" `shouldBe` Just (1.2 :: Double)--    it "renders as NUMBER in help and error output" $ do-      argumentType (Proxy :: Proxy Double) `shouldBe` "NUMBER"--    it "parses doubles that start with a dot" $ do-      parseArgument ".4" `shouldBe` Just (0.4 :: Double)--  describe "Option.Float" $ do-    it "parses floats" $ do-      parseArgument "1.2" `shouldBe` Just (1.2 :: Float)--    it "renders as NUMBER in help and error output" $ do-      argumentType (Proxy :: Proxy Float) `shouldBe` "NUMBER"
test/Util.hs view
@@ -3,11 +3,18 @@ module Util where  import           System.Console.GetOpt.Generics+import           System.Console.GetOpt.Generics.Modifier -parse :: (Generic a, HasDatatypeInfo a, All2 Option (Code a)) =>+parse :: (Generic a, HasDatatypeInfo a, All2 HasArguments (Code a)) =>   String -> Result a parse = modsParse [] -modsParse :: (Generic a, HasDatatypeInfo a, All2 Option (Code a)) =>+modsParse :: (Generic a, HasDatatypeInfo a, All2 HasArguments (Code a)) =>   [Modifier] -> String -> Result a modsParse modifiers = parseArguments "prog-name" modifiers . words++unsafeModifiers :: [Modifier] -> Modifiers+unsafeModifiers mods = case mkModifiers mods of+  Success x -> x+  Errors errs -> error ("unsafeModifiers: " ++ show errs)+  OutputAndExit msg -> error ("unsafeModifiers: " ++ show msg)
+ test/WithCli/ArgumentSpec.hs view
@@ -0,0 +1,26 @@++module WithCli.ArgumentSpec where++import           Data.Proxy+import           Test.Hspec++import           WithCli.Argument++spec :: Spec+spec = do+  describe "Option.Double" $ do+    it "parses doubles" $ do+      parseArgument "1.2" `shouldBe` Just (1.2 :: Double)++    it "renders as NUMBER in help and error output" $ do+      argumentType (Proxy :: Proxy Double) `shouldBe` "NUMBER"++    it "parses doubles that start with a dot" $ do+      parseArgument ".4" `shouldBe` Just (0.4 :: Double)++  describe "Option.Float" $ do+    it "parses floats" $ do+      parseArgument "1.2" `shouldBe` Just (1.2 :: Float)++    it "renders as NUMBER in help and error output" $ do+      argumentType (Proxy :: Proxy Float) `shouldBe` "NUMBER"
+ test/WithCli/HasArgumentsSpec.hs view
@@ -0,0 +1,34 @@+{-# LANGUAGE ScopedTypeVariables #-}++module WithCli.HasArgumentsSpec where++import           Control.Monad+import           Test.Hspec+import           Test.QuickCheck++import           WithCli.HasArguments++spec :: Spec+spec = do+  describe "parseBool" $ do+    forM_ ["true", "True", "tRue", "TRUE", "yes", "yEs", "on", "oN"] $ \ true ->+      it ("parses '" ++ true ++ "' as True") $ do+        parseBool true `shouldBe` Just True++    forM_ ["false", "False", "falSE", "FALSE", "no", "nO", "off", "ofF"] $ \ false ->+      it ("parses '" ++ false ++ "' as False") $ do+        parseBool false `shouldBe` Just False++    it "parses every positive integer as true" $ do+      property $ \ (n :: Int) ->+        n > 0 ==>+        parseBool (show n) `shouldBe` Just True++    it "parses every non-positive integer as false" $ do+      property $ \ (n :: Int) ->+        n <= 0 ==>+        parseBool (show n) `shouldBe` Just False++    it "doesn't parse 'foo'" $ do+      parseBool "foo" `shouldBe` (Nothing :: Maybe Bool)+
+ test/WithCli/NormalizeSpec.hs view
@@ -0,0 +1,63 @@+{-# LANGUAGE ViewPatterns #-}++module WithCli.NormalizeSpec where++import           Data.Char+import           Test.Hspec+import           Test.QuickCheck hiding (Success)++import           WithCli.Normalize++isValidInputChar :: Char -> Bool+isValidInputChar c = c `elem` ['A' .. 'Z'] ++ ['a' .. 'z'] ++ ['0' .. '9'] ++ "-_"++isAllowedOutputChar :: Char -> Bool+isAllowedOutputChar c = c `elem` ['a' .. 'z'] ++ ['0' .. '9'] ++ "-"++upperCaseChar :: Gen Char+upperCaseChar = elements ['A' .. 'Z']++spec :: Spec+spec = do+  describe "normalize" $ do+    it "is idempotent" $ do+      property $ \ x -> do+        let once = normalize x+            twice = normalize once+        once `shouldBe` twice++    it "replaces underscores with dashes" $ do+      normalize "foo_bar" `shouldBe` "foo-bar"++    it "doesn't modify digits" $ do+      normalize "foo2bar" `shouldBe` "foo2bar"++    it "when there's one valid character it returns only dashes and lower case characters" $ do+      property $ \ x ->+        any isValidInputChar x ==>+        normalize x `shouldSatisfy`+          (\ s -> all isAllowedOutputChar s)++    it "when there are no valid characters it returns its input" $ do+      property $ forAll (listOf (arbitrary `suchThat` (not . isValidInputChar))) $ \ x ->+        normalize x `shouldBe` x++    it "replaces camelCase with dashes" $ do+      let isValidPrefixChar c = c `elem` ['A' .. 'Z'] ++ ['a' .. 'z']+      property $+        \ prefix suffix ->+        (any isValidPrefixChar prefix) ==>+        forAll upperCaseChar $ \ upper ->+        counterexample (prefix ++ [upper] ++ suffix) $+        normalize (prefix ++ [upper] ++ suffix)+          `shouldBe`+            normalize prefix ++ "-" ++ normalize (toLower upper : suffix)++  describe "matches" $ do+    it "matches normalized strings" $ do+      property $ \ s ->+        normalize s `matches` s++    it "matches unnormalized strings" $ do+      property $ \ s ->+        s `matches` s
+ test/WithCli/ParserSpec.hs view
@@ -0,0 +1,29 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE TupleSections #-}++module WithCli.ParserSpec where++import           Prelude ()+import           Prelude.Compat++import           Test.Hspec++import           WithCli.Parser+import           WithCli.Result+import           Util++spec :: Spec+spec = do+  describe "runParser" $ do+    it "foo" $ do+      let fa :: Parser phase Int+          fa = Parser {+            parserDefault = Nothing,+            parserOptions = [],+            parserNonOptions =+              (NonOptionsParser "type" (\ (s : r) -> (, r) <$> Success (const $ Just $ read s))) :+              [],+            parserConvert = \ (Just x) -> return x+          }+      let i = runParser "program" (unsafeModifiers []) fa ["42"]+      i `shouldBe` Success 42
+ test/WithCli/ResultSpec.hs view
@@ -0,0 +1,55 @@+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE ViewPatterns #-}++module WithCli.ResultSpec where++import           Data.Char+import           Data.List+import           Safe+import           System.Exit+import           Test.Hspec+import           Test.QuickCheck hiding (Result(..))++import           WithCli.Result++spec :: Spec+spec = do+  describe "Result" $ do+    context ">>" $ do+      it "collects errors" $ do+        (Errors ["foo"] >> Errors ["bar"] :: Result ())+          `shouldBe` Errors ["foo", "bar"]++  describe "handleResult" $ do+    context "OutputAndExit" $ do+      it "throws ExitSuccess" $ do+        handleResult (OutputAndExit "foo")+          `shouldThrow` (== ExitSuccess)++    context "Errors" $ do+      it "throws an ExitFailure" $ do+        handleResult (Errors ["foo"])+          `shouldThrow` (== ExitFailure 1)++  describe "sanitize" $ do+    it "removes empty lines" $ do+      property $ \ (unlines -> s) -> do+        sanitize s `shouldNotContain` "\n\n"++    it "adds a newline at the end if missing" $ do+      property $ \ (unlines -> s) ->+        not (null (sanitize s)) ==>+        lastMay (sanitize s) `shouldBe` Just '\n'++    it "only strips spaces" $ do+      property $ \ (unlines -> s) -> do+        counterexample s $ do+          let expected = case s of+                "" -> ""+                x | lastMay x == Just '\n' -> x+                x -> x ++ "\n"+          filter (not . isSpace) (sanitize s) `shouldBe` filter (not . isSpace) expected++    it "removes trailing spaces" $ do+      property $ \ (unlines -> s) -> do+        sanitize s `shouldSatisfy` (not . (" \n" `isInfixOf`))
+ test/WithCliSpec.hs view
@@ -0,0 +1,45 @@++module WithCliSpec where++import           System.Environment+import           System.Exit+import           System.IO+import           System.IO.Silently+import           Test.Hspec++import           WithCli++spec :: Spec+spec = do+  describe "withCli" $ do+    context "no arguments" $ do+      it "executes the operation in case of no command line arguments" $ do+        let main :: IO ()+            main = putStrLn "success"+        (capture_ $ withArgs [] $ withCli main)+          `shouldReturn` "success\n"++      it "produces nice error messages" $ do+        let main :: IO ()+            main = putStrLn "success"+        output <- hCapture_ [stderr] (withArgs ["foo"] (withCli main) `shouldThrow` (== ExitFailure 1))+        output `shouldBe` "unknown argument: foo\n"++    context "1 argument" $ do+      it "parses Ints" $ do+        let main :: Int -> IO ()+            main n = putStrLn ("success: " ++ show n)+        (capture_ $ withArgs ["12"] $ withCli main)+          `shouldReturn` "success: 12\n"++      it "error parsing" $ do+        let main :: Int -> IO ()+            main n = putStrLn ("error: " ++ show n)+        output <- hCapture_ [stderr] (withArgs (words "12 foo") (withCli main) `shouldThrow` (== ExitFailure 1))+        output `shouldBe` "unknown argument: foo\n"++      it "handle Maybes as positional arguments with a proper error message" $ do+        let main :: Maybe Int -> IO ()+            main = error "main"+        output <- hCapture_ [stderr] (withCli main `shouldThrow` (== ExitFailure 1))+        output `shouldBe` "cannot use Maybes for positional arguments\n"