getopt-generics 0.9 → 0.10
raw patch · 10 files changed
+278/−297 lines, 10 filesdep +directorydep +filepathdep +processdep −hspec-expectationsdep −markdown-unlitPVP ok
version bump matches the API change (PVP)
Dependencies added: directory, filepath, process, temporary
Dependencies removed: hspec-expectations, markdown-unlit
API changes (from Hackage documentation)
- System.Console.GetOpt.Generics: _accumulate :: Option a => a -> a -> a
- System.Console.GetOpt.Generics: _emptyOption :: Option a => Modifiers -> FieldString -> FieldState a
- System.Console.GetOpt.Generics: _toOption :: Option a => ArgDescr (FieldState a)
Files
- README.md +0/−104
- examples/Example.hs +0/−28
- examples/Readme.lhs +0/−104
- getopt-generics.cabal +38/−13
- src/System/Console/GetOpt/Generics.hs +3/−5
- src/System/Console/GetOpt/Generics/GetArguments.hs +96/−14
- src/System/Console/GetOpt/Generics/Simple.hs +40/−19
- test/BashProtocol.hs +44/−0
- test/Examples.hs +57/−0
- test/ExamplesSpec.hs +0/−10
− README.md
@@ -1,104 +0,0 @@-# getopt-generics--## Status--This library is experimental.--## Usage--`getopt-generics` tries to make it very simple to create executables that parse-command line options. All you have to do is to define a type and derive some-instances:--~~~ {.haskell}-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE DeriveGeneric #-}--module Readme where--import Data.Typeable-import GHC.Generics-import System.Console.GetOpt.Generics-import System.Environment--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:--~~~ {.haskell}-main :: IO ()-main = do- options <- getArguments- print (options :: Options)-~~~--This program has--- a non-optional `--port` flag with an integer argument,-- a boolean flag `--daemonize`,-- an optional flag `--config` expecting a file argument and-- a generic `--help` option.--Here's in example of the program above in bash:-``` bash-$ 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-not an integer: foo-$ program-missing option: --port=int-$ program --help-program- --port=integer- --daemonize- --config=string (optional)-```--## Constraints--There are some constraints that the defined datatype has to fulfill:-- * It has to have only one constructor,- * that constructor has to have field selectors (i.e. use record syntax) and- * all fields have to be of a type that has an instance for `Option`.--(Types declared with `newtype` are allowed with the same constraints.)--## Using Custom Field Types--It is possible to use custom field types by providing an instance for `Option`.-Here's an example:--~~~ {.haskell}-data File = File FilePath- deriving (Show, Typeable)--instance Option File where- argumentType Proxy = "file"- parseArgument f = Just (File f)--data FileOptions- = FileOptions {- file :: File- }- deriving (Show, GHC.Generics.Generic)--instance System.Console.GetOpt.Generics.Generic FileOptions-instance HasDatatypeInfo FileOptions---- Returns: FileOptions {file = File "some/file"}-getFileOptions :: IO FileOptions-getFileOptions = withArgs (words "--file some/file") getArguments-~~~
− examples/Example.hs
@@ -1,28 +0,0 @@-{-# LANGUAGE DeriveGeneric #-}--module Example where--import qualified GHC.Generics-import System.Console.GetOpt.Generics---- we specify a data type that maps to the commandline arguments--data Foo- = Foo {- bar :: String- , baz :: Maybe String- , qux :: Int- }- deriving (Eq, Show, GHC.Generics.Generic)--instance Generic Foo-instance HasDatatypeInfo Foo---- give it go--main :: IO ()-main = do- myFoo <- getArguments- print $ bar myFoo- print $ baz myFoo- print $ qux myFoo
− examples/Readme.lhs
@@ -1,104 +0,0 @@-# getopt-generics--## Status--This library is experimental.--## Usage--`getopt-generics` tries to make it very simple to create executables that parse-command line options. All you have to do is to define a type and derive some-instances:--~~~ {.haskell}-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE DeriveGeneric #-}--module Readme where--import Data.Typeable-import GHC.Generics-import System.Console.GetOpt.Generics-import System.Environment--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:--~~~ {.haskell}-main :: IO ()-main = do- options <- getArguments- print (options :: Options)-~~~--This program has--- a non-optional `--port` flag with an integer argument,-- a boolean flag `--daemonize`,-- an optional flag `--config` expecting a file argument and-- a generic `--help` option.--Here's in example of the program above in bash:-``` bash-$ 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-not an integer: foo-$ program-missing option: --port=int-$ program --help-program- --port=integer- --daemonize- --config=string (optional)-```--## Constraints--There are some constraints that the defined datatype has to fulfill:-- * It has to have only one constructor,- * that constructor has to have field selectors (i.e. use record syntax) and- * all fields have to be of a type that has an instance for `Option`.--(Types declared with `newtype` are allowed with the same constraints.)--## Using Custom Field Types--It is possible to use custom field types by providing an instance for `Option`.-Here's an example:--~~~ {.haskell}-data File = File FilePath- deriving (Show, Typeable)--instance Option File where- argumentType Proxy = "file"- parseArgument f = Just (File f)--data FileOptions- = FileOptions {- file :: File- }- deriving (Show, GHC.Generics.Generic)--instance System.Console.GetOpt.Generics.Generic FileOptions-instance HasDatatypeInfo FileOptions---- Returns: FileOptions {file = File "some/file"}-getFileOptions :: IO FileOptions-getFileOptions = withArgs (words "--file some/file") getArguments-~~~
getopt-generics.cabal view
@@ -3,9 +3,9 @@ -- see: https://github.com/sol/hpack name: getopt-generics-version: 0.9-synopsis: Simple command line argument parsing-description: "getopt-generics" tries to make it very simple to create command line argument parsers. An introductory example can be found in the <https://github.com/zalora/getopt-generics#getopt-generics README>.+version: 0.10+synopsis: Create command line interfaces with ease+description: Create command line interfaces with ease category: Console, System homepage: https://github.com/zalora/getopt-generics#readme bug-reports: https://github.com/zalora/getopt-generics/issues@@ -17,9 +17,6 @@ build-type: Simple cabal-version: >= 1.10 -extra-source-files:- README.md- source-repository head type: git location: https://github.com/zalora/getopt-generics@@ -44,14 +41,45 @@ 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+ default-language: Haskell2010+ test-suite spec type: exitcode-stdio-1.0 main-is: Spec.hs hs-source-dirs: src , test- , examples- ghc-options: -Wall -fno-warn-name-shadowing -threaded -O0 -pgmL markdown-unlit+ ghc-options: -Wall -fno-warn-name-shadowing -threaded -O0 build-depends: base == 4.* , base-compat >= 0.8@@ -59,8 +87,6 @@ , generics-sop , tagged , hspec >= 2.1.8- , hspec-expectations- , markdown-unlit , QuickCheck , silently other-modules:@@ -70,7 +96,8 @@ System.Console.GetOpt.Generics.Modifier System.Console.GetOpt.Generics.Result System.Console.GetOpt.Generics.Simple- ExamplesSpec+ BashProtocol+ Examples ModifiersSpec ModifiersSpec.RenameOptionsSpec ModifiersSpec.UseForPositionalArgumentsSpec@@ -80,6 +107,4 @@ System.Console.GetOpt.Generics.SimpleSpec System.Console.GetOpt.GenericsSpec Util- Example- Readme default-language: Haskell2010
src/System/Console/GetOpt/Generics.hs view
@@ -2,6 +2,8 @@ module System.Console.GetOpt.Generics ( -- * IO API simpleCLI,+ SimpleCLI,+ ArgumentTypes, getArguments, modifiedGetArguments, -- * Pure API@@ -11,11 +13,7 @@ Modifier(..), deriveShortOptions, -- * Available Field Types- -- fixme: hide methods?- Option(..),- -- * SimpleCLI class- SimpleCLI,- ArgumentTypes,+ Option(argumentType, parseArgument), -- * Re-exports from "Generics.SOP" Generic, HasDatatypeInfo,
src/System/Console/GetOpt/Generics/GetArguments.hs view
@@ -20,10 +20,8 @@ {-# OPTIONS_GHC -fno-warn-unrecognised-pragmas #-} -- | @getopt-generics@ tries to make it very simple to create command line--- argument parsers. An introductory example can be found in the--- <https://github.com/zalora/getopt-generics#getopt-generics README>.--module System.Console.GetOpt.Generics.GetArguments where+-- argument parsers.+module System.Console.GetOpt.Generics.GetArguments where import Data.Orphans () import Prelude ()@@ -46,13 +44,62 @@ -- | Parses command line arguments (gotten from 'withArgs') and returns the -- parsed value. This function should be enough for simple use-cases. ----- May throw the following exceptions:+-- Throws the same exceptions as 'simpleCLI'. ----- - @'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@.+-- 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 []@@ -65,7 +112,7 @@ progName <- getProgName handleResult $ parseArguments progName modifiers args --- | Pure variant of 'getArguments'.+-- | Pure variant of 'modifiedGetArguments'. -- -- Does not throw any exceptions. parseArguments :: forall a . (Generic a, HasDatatypeInfo a, All2 Option (Code a)) =>@@ -316,9 +363,44 @@ -- -- 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). For--- an example see the--- <https://github.com/zalora/getopt-generics#getopt-generics README>.+-- '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".
src/System/Console/GetOpt/Generics/Simple.hs view
@@ -26,28 +26,49 @@ _initialFieldStates :: Proxy main -> NP FieldState (ArgumentTypes main) _run :: NP I (ArgumentTypes main) -> main -> IO () --- | 'simpleCLI' converts an IO operation into a program with a nice CLI. @main@ can have--- arbitrarily many parameters provided all parameters have an instance for 'Option'.------ Example:------ Given a file @myProgram.hs@:+-- | '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'. ----- > main :: IO ()--- > main = simpleCLI myMain--- >--- > myMain :: String -> Int -> Bool -> IO ()--- > myMain s i b = print (s, i, b)+-- May throw the following exceptions: ----- you get:+-- - @'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@. ----- > $ runhaskell myProgram.hs foo 42 true--- > ("foo",42,True)--- > $ runhaskell myProgram.hs foo 42 bar--- > cannot parse as BOOL: bar--- > $ runhaskell myProgram.hs --help--- > myProgram.hs [OPTIONS] STRING INTEGER BOOL--- > -h --help show help and exit+-- 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
+ test/BashProtocol.hs view
@@ -0,0 +1,44 @@++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 view
@@ -0,0 +1,57 @@++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
@@ -1,10 +0,0 @@--module ExamplesSpec where--import Test.Hspec--import Example ()-import Readme ()--spec :: Spec-spec = return ()