packages feed

envparse 0.3.2 → 0.3.3

raw patch · 7 files changed

+190/−69 lines, 7 files

Files

CHANGELOG.markdown view
@@ -1,3 +1,9 @@+0.3.3+=====++  * Removed the unnecessary `AsEmpty` and `AsUnset` constraints on the error type+    in the `flag` and `switch` parsers.+ 0.3.2 ===== 
envparse.cabal view
@@ -1,5 +1,5 @@ name:                envparse-version:             0.3.2+version:             0.3.3 synopsis:            Parse environment variables description:   Here's a simple example of a program that uses @envparse@'s parser:@@ -59,7 +59,8 @@   CHANGELOG.markdown   example/Main.hs   example/CustomError.hs-  example/Generic.hs+  example/Generic0.hs+  example/Generic1.hs  source-repository head   type:     git@@ -68,7 +69,7 @@ source-repository this   type:     git   location: https://github.com/supki/envparse-  tag:      0.3.2+  tag:      0.3.3  library   default-language:
− example/Generic.hs
@@ -1,46 +0,0 @@-{-# LANGUAGE CPP #-}-#if __GLASGOW_HASKELL__ < 710-module Main (main) where--main :: IO ()-main =-  return ()-#else-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE NamedFieldPuns #-}-{-# LANGUAGE TypeOperators #-}--- | Greetings for $NAME------ @--- % GENERIC_NAME=a5579150 runhaskell -isrc example/Generic.hs--- Hello, a5579150!--- % GENERIC_NAME=a5579150 GENERIC_QUIET=1 runhaskell -isrc example/Generic.hs--- %--- @-module Main (main) where--import Control.Monad (unless)-import Env-import Env.Generic---data Hello = Hello-  { name  :: String ? "Target for the greeting"-  , quiet :: Bool   ? "Whether to actually print the greeting"-  } deriving (Show, Eq, Generic)--instance Record Error Hello--main :: IO ()-main = do-  Hello {name=Help n, quiet=Help q} <- hello-  unless q $-    putStrLn ("Hello, " ++ n ++ "!")--hello :: IO Hello-hello =-  Env.parse (header "envparse example") (prefixed "GENERIC_" record)-#endif
+ example/Generic0.hs view
@@ -0,0 +1,30 @@+{-# LANGUAGE CPP #-}+#if __GLASGOW_HASKELL__ < 710+module Main (main) where++main :: IO ()+main =+  return ()+#else+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE MultiParamTypeClasses #-}+module Main (main) where++import Env+import Env.Generic+++data Hello = Hello+  { name  :: String+  , count :: Int+  , quiet :: Bool+  } deriving (Show, Eq, Generic)++instance Record Error Hello++main :: IO ()+main = do+  hello <- Env.parse (header "envparse example") record+  print (hello :: Hello)+#endif
+ example/Generic1.hs view
@@ -0,0 +1,31 @@+{-# LANGUAGE CPP #-}+#if __GLASGOW_HASKELL__ < 710+module Main (main) where++main :: IO ()+main =+  return ()+#else+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TypeOperators #-}+module Main (main) where++import Env+import Env.Generic+++data Hello = Hello+  { name  :: String ? "Whom shoud I greet?"+  , count :: Int    ? "How many times to greet them?"+  , quiet :: Bool   ? "Should I be quiet instead?"+  } deriving (Show, Eq, Generic)++instance Record Error Hello++main :: IO ()+main = do+  hello <- Env.parse (header "envparse example") record+  print (hello :: Hello)+#endif
src/Env/Generic.hs view
@@ -9,6 +9,85 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-}+-- | Using the 'G.Generic' facility, this module can derive 'Env.Parser's automatically.+--+-- If you have a simple record:+--+-- @+-- {-\# LANGUAGE DeriveGeneric #-}+-- {-\# LANGUAGE MultiParamTypeClasses #-}+--+-- import Env+-- import Env.Generic+--+-- data Hello = Hello+--   { name  :: String+--   , count :: Int+--   , quiet :: Bool+--   } deriving (Show, Eq, Generic)+--+-- instance Record Error Hello+--+-- main :: IO ()+-- main = do+--   hello <- Env.parse (header "envparse example") 'record'+--   print (hello :: Hello)+-- @+--+-- The generic implementation of the 'record' method translates named fields to field parsers:+--+-- @+-- % NAME=bob COUNT=3 runhaskell -isrc example/Generic0.hs+-- Hello {name = "bob", count = 3, quiet = False}+-- @+--+-- If you want to adorn the ugly default help message, augment the fields with descriptions:+--+-- @+-- {-\# LANGUAGE DataKinds #-}+-- {-\# LANGUAGE DeriveGeneric #-}+-- {-\# LANGUAGE MultiParamTypeClasses #-}+-- {-\# LANGUAGE TypeOperators #-}+--+-- import Env+-- import Env.Generic+--+-- data Hello = Hello+--   { name  :: String ? __"Whom shoud I greet?"__+--   , count :: Int    ? __"How many times to greet them?"__+--   , quiet :: Bool   ? __"Should I be quiet instead?"__+--   } deriving (Show, Eq, Generic)+--+-- instance Record Error Hello+--+-- main :: IO ()+-- main = do+--   hello <- Env.parse (header "envparse example") record+--   print (hello :: Hello)+-- @+--+-- @+-- % runhaskell -isrc example/Generic1.hs+-- envparse example+--+-- Available environment variables:+--+--   COUNT                  __How many times to greet them?__+--   NAME                   __Whom shoud I greet?__+--   QUIET                  __Should I be quiet instead?__+--+-- Parsing errors:+--+--   COUNT is unset+--   NAME is unset+-- @+--+-- Note that this has an effect of wrapping the values in the 'Help' constructor:+--+-- @+-- % NAME=bob COUNT=3 QUIET='YES' runhaskell -isrc example/Generic1.hs+-- Hello {name = Help {unHelp = "bob"}, count = Help {unHelp = 3}, quiet = Help {unHelp = True}}+-- @ module Env.Generic   ( Record(..)   , Field(..)@@ -32,33 +111,38 @@ import qualified Env  +-- | Given a @Record e a@ instance, a value of the type @a@ can be parsed from the environment.+-- If the parsing fails, a value of an error type @e@ is returned.+--+-- The 'record' method has a default implementation for any type that has a 'G.Generic' instance. If you+-- need to choose a concrete type for @e@, the default error type 'Env.Error' is a good candidate. Otherwise,+-- the features you'll use in your parsers will naturally guide GHC to compute the set of required+-- constraints on @e@. class Record e a where   record :: Env.Parser e a   default record :: (r ~ G.Rep a, G.Generic a, GRecord e r) => Env.Parser e a   record =     fmap G.to (gr State {statePrefix="", stateCon="", stateVar=""}) --- | Generic parsing state. data State = State-  { statePrefix :: String -- ^ All variables' names have this prefix.-  , stateCon    :: String -- ^ Constructor currently being processed.-  , stateVar    :: String -- ^ Variable name to use for the next component.+  { statePrefix :: String -- All the variables' names have this prefix.+  , stateCon    :: String -- The constructor currently being processed.+  , stateVar    :: String -- The variable name to use for the next component.   } deriving (Show, Eq)  class GRecord e f where   gr :: State -> Env.Parser e (f a) --- | We are not interested in any metadata of the type constructor definition. instance GRecord e a => GRecord e (G.D1 c a) where   gr =     fmap G.M1 . gr --- | Constant values are converted to 'Env.Parser's using their 'Field' instance.+-- Constant values are converted to 'Env.Parser's using their 'Field' instance. instance (Env.AsUnset e, Field e a) => GRecord e (G.K1 i a) where   gr State {stateVar} =     fmap G.K1 (field stateVar Nothing) --- | Constructor's name is used as a prefix to try to remove from+-- Constructor's name is used as a prefix to try to remove from -- selectors when building environment variable names. instance (G.Constructor c, GRecord e a) => GRecord e (G.C1 c a) where   gr state =@@ -66,17 +150,14 @@    where     con = G.conName (G.M1 Proxy :: G.M1 t c Proxy b) --- | Products are converted to products of parsers. instance (GRecord e f, GRecord e g) => GRecord e (f G.:*: g) where   gr x =     liftA2 (G.:*:) (gr x) (gr x) --- | Sums are converted to sums of parsers. instance (GRecord e f, GRecord e g) => GRecord e (f G.:+: g) where   gr x =     fmap G.L1 (gr x) <|> fmap G.R1 (gr x) --- | Record selectors' names determine suffixes of environment variables' names. instance (G.Selector c, Type c ~ 'Record, GRecord e a) => GRecord e (G.S1 c a) where   gr state@State {statePrefix, stateCon} =     fmap G.M1 (gr state {stateVar=statePrefix ++ suffix})@@ -88,7 +169,6 @@         y <- List.stripPrefix (map Char.toLower stateCon) sel         camelTo2 y <$ guard (not (List.null y)) --- | Stolen from Aeson and adapted. camelTo2 :: String -> String camelTo2 = map Char.toUpper . go2 . go1  where@@ -100,14 +180,21 @@   go2 (l:u:xs) | Char.isLower l && Char.isUpper u = l : '_' : u : go2 xs   go2 (x:xs) = x : go2 xs --- | Decide whether the constructor is a record. type family Type x :: ConType where   Type G.NoSelector = 'Plain   Type x = 'Record --- | Constructor can be either a plain thing or a record. data ConType = Plain | Record +-- | Given a @Field e a@ instance, a value of the type @a@ can be parsed from an environment variable.+-- If the parsing fails, a value of an error type @e@ is returned.+--+-- The 'field' method has a default implementation for any type that has a 'Read' instance. If you+-- need to choose a concrete type for @e@, the default error type 'Env.Error' is a good candidate. Otherwise,+-- the features you'll use in your parsers will naturally guide GHC to compute the set of required+-- constraints on @e@.+--+-- The annotated instances do not use the default implementation. class Field e a where   field :: String -> Maybe String -> Env.Parser e a   default field :: (Env.AsUnset e, Env.AsUnread e, Read a) => String -> Maybe String -> Env.Parser e a@@ -142,10 +229,12 @@  instance (Env.AsUnset e, Env.AsUnread e) => Field e Double +-- | Uses the 'String' value verbatim. instance Env.AsUnset e => Field e String where   field name help =     Env.var Env.str name (foldMap Env.help help) +-- | Expects a single-character 'String' value. instance (Env.AsUnset e, Env.AsUnread e) => Field e Char where   field name help =     Env.var reader name (foldMap Env.help help)@@ -154,14 +243,22 @@       [c] -> pure c       str -> Left (Env.unread str) -instance (Env.AsUnset e, Env.AsEmpty e) => Field e Bool where+-- | Any set and non-empty value parses to a 'True'; otherwise, it's a 'False'. This parser+-- never fails.+instance Field e Bool where   field name help =     Env.switch name (foldMap Env.help help) --- | Variable tagged with its help message.+-- | A field annotation.+--+-- If you annotate a record field with a 'Symbol' literal (that is, a statically known type level string)+-- the derivation machinery will use the literal in the help message.+--+-- Please remember that the values of the annotated fields are wrapped in the 'Help' constructor. newtype a ? tag = Help { unHelp :: a }     deriving (Show, Eq, Functor, Foldable, Traversable) +-- | Augments the underlying field parser with the help message. instance (G.KnownSymbol tag, Field e a) => Field e (a ? tag) where   field name _ =     fmap Help (field name (pure (G.symbolVal (Proxy :: Proxy tag))))
src/Env/Parse.hs view
@@ -107,13 +107,15 @@ -- -- /Note:/ this parser never fails. flag-  :: forall e a. (Error.AsUnset e, Error.AsEmpty e)-  => a -- ^ default value+  :: a -- ^ default value   -> a -- ^ active value   -> String -> Mod Flag a -> Parser e a flag f t n (Mod g) = Parser . liftAlt $ VarF   { varfName    = n-  , varfReader  = \name -> Right . either (const f) (const t) . (nonempty :: Reader e String) <=< lookupVar name+  , varfReader  = \name env ->+      pure $ case (nonempty :: Reader Error.Error String) =<< lookupVar name env of+        Left  _ -> f+        Right _ -> t   , varfHelp    = flagHelp   , varfDef     = Just f   , varfHelpDef = Nothing@@ -123,8 +125,8 @@  -- | A simple boolean 'flag' ----- /Note:/ the same caveats apply.-switch :: (Error.AsUnset e, Error.AsEmpty e) => String -> Mod Flag Bool -> Parser e Bool+-- /Note:/ this parser never fails.+switch :: String -> Mod Flag Bool -> Parser e Bool switch = flag False True  -- | The trivial reader