packages feed

getopt-generics 0.1.1 → 0.2

raw patch · 4 files changed

+322/−95 lines, 4 filesdep +base-compatdep +hspec-expectationsdep +taggeddep −getopt-genericsdep −safePVP ok

version bump matches the API change (PVP)

Dependencies added: base-compat, hspec-expectations, tagged

Dependencies removed: getopt-generics, safe

API changes (from Hackage documentation)

- System.Console.GetOpt.Generics: accumulate :: Option a => a -> a -> a
- System.Console.GetOpt.Generics: emptyOption :: Option a => String -> FieldState a
- System.Console.GetOpt.Generics: toOption :: Option a => ArgDescr (FieldState a)
- System.Console.GetOpt.Generics: withArguments :: (Generic a, HasDatatypeInfo a, All2 Option (Code a)) => (a -> IO ()) -> IO ()
+ System.Console.GetOpt.Generics: AddShortOption :: String -> Char -> Modifier
+ System.Console.GetOpt.Generics: Errors :: [String] -> Result a
+ System.Console.GetOpt.Generics: OutputAndExit :: String -> Result a
+ System.Console.GetOpt.Generics: RenameOption :: String -> String -> Modifier
+ System.Console.GetOpt.Generics: Success :: a -> Result a
+ System.Console.GetOpt.Generics: _accumulate :: Option a => a -> a -> a
+ System.Console.GetOpt.Generics: _emptyOption :: Option a => String -> FieldState a
+ System.Console.GetOpt.Generics: _toOption :: Option a => ArgDescr (FieldState a)
+ System.Console.GetOpt.Generics: argumentType :: Option a => Proxy a -> String
+ System.Console.GetOpt.Generics: data Modifier
+ System.Console.GetOpt.Generics: data Result a
+ System.Console.GetOpt.Generics: deriveShortOptions :: (HasDatatypeInfo a, SingI (Code a)) => Proxy a -> [Modifier]
+ System.Console.GetOpt.Generics: getArguments :: (Generic a, HasDatatypeInfo a, All2 Option (Code a)) => IO a
+ System.Console.GetOpt.Generics: instance Eq a => Eq (Result a)
+ System.Console.GetOpt.Generics: instance Option [Int]
+ System.Console.GetOpt.Generics: instance Ord a => Ord (Result a)
+ System.Console.GetOpt.Generics: instance Show a => Show (Result a)
+ System.Console.GetOpt.Generics: parseArgument :: Option 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: class Option a where accumulate _ x = x
+ System.Console.GetOpt.Generics: class Option a where _toOption = ReqArg parseAsFieldState (argumentType (Proxy :: Proxy a)) _emptyOption flagName = Unset ("missing option: --" ++ flagName ++ "=" ++ argumentType (Proxy :: Proxy a)) _accumulate _ x = x

Files

getopt-generics.cabal view
@@ -1,49 +1,48 @@+-- This file has been generated from package.yaml by Cabalize. name: getopt-generics-version: 0.1.1-category: Console, System+version: 0.2 synopsis: Simple command line argument parsing description:-  "getopt-generics" tries to make it very simple to create command line argument-  parsers. Documentation can be found in the-  <https://github.com/zalora/getopt-generics#getopt-generics README>.-license: BSD3-license-file: LICENSE+  "getopt-generics" tries to make it very simple to create command line argument parsers. An introductory example can be found in the <https://github.com/zalora/getopt-generics#getopt-generics README>.+category: Console, System author: Linh Nguyen, Sönke Hahn-copyright: Zalora South East Asia Pte Ltd maintainer: linh.nguyen@zalora.com, soenke.hahn@zalora.com--cabal-version: >= 1.8+copyright: Zalora South East Asia Pte Ltd+license: BSD3+license-file: LICENSE build-type: Simple+cabal-version: >= 1.10 +source-repository head+  type: git+  location: https://github.com/zalora/getopt-generics+ library-  hs-source-dirs:-    src-  ghc-options:-    -Wall -fno-warn-name-shadowing+  hs-source-dirs: src   exposed-modules:-    System.Console.GetOpt.Generics+      System.Console.GetOpt.Generics+  other-modules:+      System.Console.GetOpt.Generics.Internal+      System.Console.GetOpt.Generics.Modifier   build-depends:       base == 4.*+    , base-compat >= 0.7     , generics-sop-    , safe+    , tagged+  ghc-options: -Wall -fno-warn-name-shadowing+  default-language: Haskell2010  test-suite spec-  type:-    exitcode-stdio-1.0-  hs-source-dirs:-    test, examples-  ghc-options:-    -Wall -threaded -fno-warn-name-shadowing -O0 -pgmL markdown-unlit-  main-is:-    Spec.hs+  type: exitcode-stdio-1.0+  hs-source-dirs: src, test, examples+  main-is: Spec.hs   build-depends:       base == 4.*-    , getopt-generics+    , base-compat >= 0.7     , generics-sop-     , hspec+    , hspec-expectations     , silently--source-repository head-  type:     git-  location: git://github.com/zalora/getopt-generics+    , tagged+  ghc-options: -Wall -fno-warn-name-shadowing -threaded -O0 -pgmL markdown-unlit+  default-language: Haskell2010
src/System/Console/GetOpt/Generics.hs view
@@ -4,48 +4,97 @@ {-# LANGUAGE DeriveGeneric         #-} {-# LANGUAGE FlexibleContexts      #-} {-# LANGUAGE FlexibleInstances     #-}+{-# LANGUAGE GADTs                 #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE RankNTypes            #-} {-# LANGUAGE ScopedTypeVariables   #-} {-# LANGUAGE TypeFamilies          #-} {-# LANGUAGE TypeSynonymInstances  #-} --- | "getopt-generics" tries to make it very simple to create command line--- argument parsers. Documentation can be found in the+{-# 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 (-  withArguments,+  -- * IO API+  getArguments,+  -- * Pure API+  parseArguments,+  Result(..),+  -- * Customizing the CLI+  Modifier(..),+  deriveShortOptions,+  -- * Available Field Types   Option(..),  ) where -import           Control.Applicative+import           Prelude ()+import           Prelude.Compat++import           Data.Char import           Data.List-import           Data.Monoid (Monoid, mempty) import           Generics.SOP-import           Safe-import           System.Console.GetOpt+import           System.Console.GetOpt.Compat import           System.Environment import           System.Exit import           System.IO+import           Text.Read.Compat -withArguments :: forall a . (Generic a, HasDatatypeInfo a, All2 Option (Code a)) =>-  (a -> IO ()) -> IO ()-withArguments action = do+import           System.Console.GetOpt.Generics.Modifier+import           System.Console.GetOpt.Generics.Internal++-- | 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:+--+--   - @'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@.+getArguments :: forall a . (Generic a, HasDatatypeInfo a, All2 Option (Code a)) =>+  IO a+getArguments = do   args <- getArgs-  case parseArgs args of-    Right (Right a) -> action a-    Left noAction -> noAction-    Right (Left errs) -> do+  progName <- getProgName+  case parseArguments progName [] args of+    Success a -> return a+    OutputAndExit message -> do+      putStrLn message+      exitWith ExitSuccess+    Errors errs -> do       mapM_ (hPutStrLn stderr) errs       exitWith $ ExitFailure 1 -parseArgs :: forall a . (Generic a, HasDatatypeInfo a, All2 Option (Code a)) =>-  [String] -> Either (IO ()) (Either [String] a)-parseArgs args = case datatypeInfo (Proxy :: Proxy a) of+-- | 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)++-- | Pure variant of 'getArguments'. Also allows to declare 'Modifier's.+--+--   Does not throw any exceptions.+parseArguments :: forall a . (Generic a, HasDatatypeInfo a, All2 Option (Code a)) =>+  String -> [Modifier] -> [String] -> Result a+parseArguments header modifiers args = case normalizedDatatypeInfo (Proxy :: Proxy a) of     ADT typeName _ (constructorInfo :* Nil) ->       case constructorInfo of-        (Record _ fields) -> processFields args fields+        (Record _ fields) -> processFields header modifiers args fields         Constructor{} ->           err typeName "constructors without field labels"         Infix{} ->@@ -55,19 +104,19 @@     ADT typeName _ (_ :* _ :* _) ->       err typeName "sum-types"     Newtype _ _ (Record _ fields) ->-      processFields args fields+      processFields header modifiers args fields     Newtype typeName _ (Constructor _) ->       err typeName "constructors without field labels"   where     err typeName message =-      Right $ Left ["getopt-generics doesn't support " ++ message ++ " (" ++ typeName ++ ")."]+      Errors ["getopt-generics doesn't support " ++ message ++ " (" ++ typeName ++ ")."]  processFields :: forall a xs . (Generic a, Code a ~ '[xs], SingI xs, All Option xs) =>-  [String] -> NP FieldInfo xs -> Either (IO ()) (Either [String] a)-processFields args fields =-  helpWrapper args fields $+  String -> [Modifier] -> [String] -> NP FieldInfo xs -> Result a+processFields header modifiers args fields =+  helpWrapper header modifiers args fields $   fmap (to . SOP . Z) $-  case getOpt Permute (mkOptDescrs fields) args of+  case getOpt Permute (mkOptDescrs modifiers fields) args of     (options, arguments, parseErrors) ->       let result :: Either [String] (NP I xs) =             collectErrors $ project options (mkEmptyArguments fields)@@ -86,14 +135,19 @@     ignoreRight = either id (const mempty)  mkOptDescrs :: forall xs . All Option xs =>-  NP FieldInfo xs -> [OptDescr (NS FieldState xs)]-mkOptDescrs fields =-  map toOptDescr $ sumList $ npMap mkOptDescr fields+  [Modifier] -> NP FieldInfo xs -> [OptDescr (NS FieldState xs)]+mkOptDescrs modifiers fields =+  map toOptDescr $ sumList $ npMap (mkOptDescr modifiers) fields  newtype OptDescrE a = OptDescrE (OptDescr (FieldState a)) -mkOptDescr :: forall a . Option a => FieldInfo a -> OptDescrE a-mkOptDescr (FieldInfo name) = OptDescrE $ Option [] [name] toOption ""+mkOptDescr :: forall a . Option a => [Modifier] -> FieldInfo a -> OptDescrE a+mkOptDescr modifiers (FieldInfo name) = OptDescrE $+  Option+    (mkShortOptions modifiers name)+    (mkLongOptions modifiers name)+    _toOption+    ""  toOptDescr :: NS OptDescrE xs -> OptDescr (NS FieldState xs) toOptDescr (Z (OptDescrE a)) = fmap Z a@@ -104,35 +158,49 @@ mkEmptyArguments fields = case (sing :: Sing xs, fields) of   (SNil, Nil) -> Nil   (SCons, FieldInfo name :* r) ->-    emptyOption name :* mkEmptyArguments r+    _emptyOption name :* mkEmptyArguments r   _ -> uninhabited "mkEmpty"   -- * showing help? +data HelpFlag = HelpFlag+ helpWrapper :: (All Option xs) =>-  [String] -> NP FieldInfo xs -> a -> Either (IO ()) a-helpWrapper args fields a =+  String -> [Modifier] -> [String] -> NP FieldInfo xs -> Either [String] a -> Result a+helpWrapper header modifiers args fields result =     case getOpt Permute [helpOption] args of-      ([], _, _) -> Right a-      (() : _, _, _) -> Left $ do-        progName <- getProgName-        let header = progName-        putStrLn (usageInfo header (mkOptDescrs fields))+      ([], _, _) -> case result of+        -- no help flag given+        Left errs -> Errors errs+        Right a -> Success a+      (HelpFlag : _, _, _) -> OutputAndExit $+        stripTrailingSpaces $+        usageInfo header $+          toOptDescrUnit (mkOptDescrs modifiers fields) +++          toOptDescrUnit [helpOption]   where-    helpOption = Option ['h'] ["help"] (NoArg ()) "show help and exit"+    helpOption :: OptDescr HelpFlag+    helpOption = Option ['h'] ["help"] (NoArg HelpFlag) "show help and exit" +    toOptDescrUnit :: [OptDescr a] -> [OptDescr ()]+    toOptDescrUnit = map (fmap (const ())) +stripTrailingSpaces :: String -> String+stripTrailingSpaces = unlines . map stripLines . lines+  where+    stripLines = reverse . dropWhile isSpace . reverse+ -- * helper functions for NS and NP  collectErrors :: NP FieldState xs -> Either [String] (NP I xs) collectErrors np = case np of   Nil -> Right Nil   (a :* r) -> case (a, collectErrors r) of-    (Success a, Right r) -> Right (I a :* r)+    (FieldSuccess a, Right r) -> Right (I a :* r)     (ParseErrors errs, r) -> Left (errs ++ either id (const []) r)     (Unset err, r) -> Left (err : either id (const []) r)-    (Success _, Left errs) -> Left errs+    (FieldSuccess _, Left errs) -> Left errs  npMap :: (All Option xs) => (forall a . Option a => f a -> g a) -> NP f xs -> NP g xs npMap _ Nil = Nil@@ -164,49 +232,89 @@ data FieldState a   = Unset String   | ParseErrors [String]-  | Success a+  | FieldSuccess a   deriving (Functor) +-- | Type class for all allowed field types.+--+--   Implementing custom instances to allow different types is possible. In the+--   easiest case you just implement 'argumentType' and 'parseArgument' (the+--   minimal complete definition).+--+--   (Unfortunately implementing instances for lists or 'Maybe's of custom types+--   is not very straightforward.) class Option a where-  toOption :: ArgDescr (FieldState a)-  emptyOption :: String -> FieldState a-  accumulate :: a -> a -> a-  accumulate _ x = x+  {-# 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 :: String -> FieldState a+  _emptyOption flagName = Unset+    ("missing option: --" ++ flagName ++ "=" ++ argumentType (Proxy :: Proxy a))++  -- | This is meant to be an internal function.+  _accumulate :: a -> a -> a+  _accumulate _ x = x++parseAsFieldState :: forall a . Option a => String -> FieldState a+parseAsFieldState s = case parseArgument s of+  Just a -> FieldSuccess a+  Nothing -> ParseErrors $ pure $+    "cannot parse as " ++ argumentType (Proxy :: Proxy a) ++ ": " ++ s+ combine :: Option a => FieldState a -> FieldState a -> FieldState a combine _ (Unset _) = impossible "combine" combine (ParseErrors e) (ParseErrors f) = ParseErrors (e ++ f) combine (ParseErrors e) _ = ParseErrors e combine (Unset _) x = x-combine (Success _) (ParseErrors e) = ParseErrors e-combine (Success a) (Success b) = Success (accumulate a b)+combine (FieldSuccess _) (ParseErrors e) = ParseErrors e+combine (FieldSuccess a) (FieldSuccess b) = FieldSuccess (_accumulate a b)  instance Option Bool where-  toOption = NoArg (Success True)-  emptyOption _ = Success False+  argumentType _ = "bool"+  parseArgument = impossible "Option.Bool.parseArguments" +  _toOption = NoArg (FieldSuccess True)+  _emptyOption _ = FieldSuccess False+ instance Option String where-  toOption = ReqArg Success "string"-  emptyOption flagName = Unset-    ("missing option: --" ++ flagName ++ "=string")+  argumentType _ = "string"+  parseArgument = Just  instance Option (Maybe String) where-  toOption = ReqArg (Success . Just) "string (optional)"-  emptyOption _ = Success Nothing+  argumentType _ = "string (optional)"+  parseArgument = Just . Just+  _emptyOption _ = FieldSuccess Nothing  instance Option [String] where-  toOption = ReqArg (Success . pure) "strings (multiple possible)"-  emptyOption _ = Success []-  accumulate = (++)--parseInt :: String -> FieldState Int-parseInt s = maybe (ParseErrors ["not an integer: " ++ s]) Success $ readMay s+  argumentType _ = "string (multiple possible)"+  parseArgument = Just . pure+  _emptyOption _ = FieldSuccess []+  _accumulate = (++)  instance Option Int where-  toOption = ReqArg parseInt "integer"-  emptyOption flagName = Unset-    ("missing option: --" ++ flagName ++ "=int")+  argumentType _ = "integer"+  parseArgument = readMaybe  instance Option (Maybe Int) where-  toOption = ReqArg (fmap Just . parseInt) "integer (optional)"-  emptyOption _ = Success Nothing+  argumentType _ = "integer (optional)"+  parseArgument s = case readMaybe s of+    Just i -> Just (Just i)+    Nothing -> Nothing+  _emptyOption _ = FieldSuccess Nothing++instance Option [Int] where+  argumentType _ = "integer (multiple possible)"+  parseArgument s = case readMaybe s of+    Just a -> Just [a]+    Nothing -> Nothing+  _emptyOption _ = FieldSuccess []+  _accumulate = (++)
+ src/System/Console/GetOpt/Generics/Internal.hs view
@@ -0,0 +1,31 @@+{-# LANGUAGE ConstraintKinds     #-}+{-# LANGUAGE GADTs               #-}+{-# LANGUAGE RankNTypes          #-}+{-# LANGUAGE TypeFamilies        #-}++module System.Console.GetOpt.Generics.Internal where++import           Data.Char+import           Generics.SOP++normalizedDatatypeInfo :: (HasDatatypeInfo a, Code a ~ xss, SingI xss) =>+  Proxy a -> DatatypeInfo xss+normalizedDatatypeInfo p = mapFieldInfo (\ (FieldInfo s) -> FieldInfo (slugify s)) (datatypeInfo p)++mapFieldInfo :: (SingI xss) =>+  (forall b . FieldInfo b -> FieldInfo b) -> DatatypeInfo xss -> DatatypeInfo xss+mapFieldInfo f info = case info of+    (ADT mod name constructors) -> ADT mod name (hliftA (mapSingleCons f) constructors)+    (Newtype mod name constructor) -> Newtype mod name (mapSingleCons f constructor)+  where+    mapSingleCons :: (forall b . FieldInfo b -> FieldInfo b) -> ConstructorInfo xs -> ConstructorInfo xs+    mapSingleCons f c = case c of+      (Record name fields) -> Record name (hliftA f fields)+      cons@Infix{} -> cons+      cons@Constructor{} -> cons++slugify :: String -> String+slugify [] = []+slugify (x : xs)+  | isUpper x = '-' : toLower x : slugify xs+  | otherwise = x : slugify xs
+ src/System/Console/GetOpt/Generics/Modifier.hs view
@@ -0,0 +1,89 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GADTs            #-}++module System.Console.GetOpt.Generics.Modifier (+  Modifier(..),+  deriveShortOptions,+  mkShortOptions,+  mkLongOptions,+ ) where++import           Data.List+import           Data.Maybe+import           Generics.SOP++import           System.Console.GetOpt.Generics.Internal++-- | 'Modifier's can be used to customize the command line parser.+data Modifier+  = AddShortOption String Char+    -- ^ @AddShortOption fieldName c@ adds the 'Char' @c@ as a short option for+    --   the field addressed by @fieldName@.+  | RenameOption String String+    -- ^ @RenameOption fieldName customName@ renames the option generated+    --   through the @fieldName@ by @customName@.+  deriving (Show, Eq, Ord)++-- | 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 normalizedDatatypeInfo 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 = []++mkShortModifiers :: [String] -> [Modifier]+mkShortModifiers fields =+    mapMaybe inner fields+  where+    inner :: String -> Maybe Modifier+    inner field@(short : _) =+      case filter ([short] `isPrefixOf`) fields of+        [_] -> Just $ AddShortOption field short+        _ -> Nothing+    inner [] = Nothing++mkShortOptions :: [Modifier] -> String -> [Char]+mkShortOptions modifiers option =+    mapMaybe inner modifiers+  where+    inner :: Modifier -> Maybe Char+    inner (AddShortOption modifierOption short)+      | matchesField modifierOption option+        = Just short+      | otherwise+        = Nothing+    inner _ = Nothing++mkLongOptions :: [Modifier] -> String -> [String]+mkLongOptions modifiers option =+    inner (reverse modifiers)+  where+    inner (RenameOption renameOption newName : _)+      | renameOption `matchesField` option = [newName]+    inner [] = [option]+    inner (_ : r) = inner r++matchesField :: String -> String -> Bool+matchesField modifierOption option =+  modifierOption == option || slugify modifierOption == option