envparse 0.1.0 → 0.2.0
raw patch · 7 files changed
+134/−43 lines, 7 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Env: parseTest :: Parser a -> [(String, String)] -> Maybe a
+ Env: parseOr :: (String -> IO a) -> Mod Info b -> Parser b -> IO (Either a b)
+ Env: parsePure :: Mod Info a -> Parser a -> [(String, String)] -> Either String a
+ Env: prefixed :: String -> Parser a -> Parser a
Files
- CHANGELOG.markdown +13/−0
- envparse.cabal +30/−3
- example/Main.hs +2/−2
- src/Env.hs +44/−12
- src/Env/Free.hs +19/−5
- src/Env/Help.hs +9/−10
- src/Env/Parse.hs +17/−11
+ CHANGELOG.markdown view
@@ -0,0 +1,13 @@+0.2.0+=====++ * Added `prefixed`.++ * Added `parseOr`, a slightly generalized version of `parse`.++ * Renamed `parseTest` to `parsePure`++0.1.0+=====++ * Initial release.
envparse.cabal view
@@ -1,8 +1,8 @@ name: envparse-version: 0.1.0+version: 0.2.0 synopsis: Parse environment variables description:- Here's a simple example+ Here's a simple example of a program that uses @envparse@'s parser: . @ module Main (main) where@@ -23,7 +23,28 @@   unless quiet $   putStrLn (\"Hello, \" ++ name ++ \"!\") @-homepage: http://example.com/+ .+ The @NAME@ environment variable is mandatory and contains the name of the person to+ greet. @QUIET@, on the other hand, is an optional boolean flag, false by default, that+ decides whether the greeting should be silent.+ .+ If the @NAME@ variable is undefined in the environment then running the program will+ result in the following help text:+ .+ @+ envparse example+  + Available environment variables:+  +   NAME Target for the greeting+   QUIET Whether to actually print the+   greeting+  + Parsing errors:+  +   NAME is missing+ @+homepage: https://supki.github.io/envparse license: BSD2 license-file: LICENSE author: Matvey Aksenov@@ -34,11 +55,17 @@ cabal-version: >= 1.10 extra-source-files: README.markdown+ CHANGELOG.markdown example/Main.hs source-repository head type: git location: https://github.com/supki/envparse++source-repository this+ type: git+ location: https://github.com/supki/envparse+ tag: 0.2.0 library default-language:
example/Main.hs view
@@ -24,5 +24,5 @@ hello :: IO Hello hello = Env.parse (header "envparse example") $- Hello <$> var (str <=< nonempty) "NAME" (help "Target for the greeting")- <*> switch "QUIET" (help "Whether to actually print the greeting")+ Hello <$> var (str <=< nonempty) "NAME" (help "Target for the greeting")+ <*> switch "QUIET" (help "Whether to actually print the greeting")
src/Env.hs view
@@ -1,5 +1,5 @@ {-# LANGUAGE Safe #-}--- | Here's a simple example+-- | Here's a simple example of a program that uses @envparse@'s parser: -- -- @ -- module Main (main) where@@ -10,7 +10,7 @@ -- data Hello = Hello { name :: String, quiet :: Bool } -- -- hello :: IO Hello--- hello = 'Env.parse' ('header' \"envparse example\") $+-- hello = Env.'parse' ('header' \"envparse example\") $ -- Hello \<$\> 'var' ('str' <=< 'nonempty') \"NAME\" ('help' \"Target for the greeting\") -- \<*\> 'switch' \"QUIET\" ('help' \"Whether to actually print the greeting\") --@@ -20,14 +20,37 @@ -- unless quiet $ -- putStrLn (\"Hello, \" ++ name ++ \"!\") -- @+--+-- The @NAME@ environment variable is mandatory and contains the name of the person to+-- greet. @QUIET@, on the other hand, is an optional boolean flag, false by default, that+-- decides whether the greeting should be silent.+--+-- If the @NAME@ variable is undefined in the environment then running the program will+-- result in the following help text:+--+-- @+-- envparse example+--+-- Available environment variables:+--+-- NAME Target for the greeting+-- QUIET Whether to actually print the+-- greeting+--+-- Parsing errors:+--+-- NAME is missing+-- @ module Env ( parse+ , parseOr , Parser , Mod , Info , header , desc , footer+ , prefixed , var , Var , Reader@@ -50,7 +73,7 @@ , asum -- * Testing -- $testing- , parseTest+ , parsePure ) where import Control.Applicative@@ -72,22 +95,31 @@ -- what you want them to do --- | Parse the environment+-- | Parse the environment or die ----- Prints the help text and exits with @EXIT_FAILURE@ if it encounters a parse error+-- Prints the help text and exits with @EXIT_FAILURE@ on encountering a parse error. -- -- @--- >>> parse ('header' \"env-parse 0.1.0\") ('var' 'str' \"USER\" ('def' \"nobody\"))+-- >>> parse ('header' \"env-parse 0.2.0\") ('var' 'str' \"USER\" ('def' \"nobody\")) -- @ parse :: Mod Info a -> Parser a -> IO a-parse i p = either (die . helpDoc i p) return . static p =<< getEnvironment+parse m = fmap (either (\_ -> error "absurd") id) . parseOr die m +-- | Try to parse the environment+--+-- Use this if simply dying on failure (the behavior of 'parse') is inadequate for your needs.+parseOr :: (String -> IO a) -> Mod Info b -> Parser b -> IO (Either a b)+parseOr f m p = traverseLeft f . parsePure m p =<< getEnvironment+ die :: String -> IO a die m = do IO.hPutStrLn IO.stderr m; exitFailure --- | Parse a static environment-parseTest :: Parser a -> [(String, String)] -> Maybe a-parseTest p = hush . static p+-- | Try to parse a pure environment+parsePure :: Mod Info a -> Parser a -> [(String, String)] -> Either String a+parsePure (Mod f) p = mapLeft (helpDoc (f defaultInfo) p) . static p -hush :: Either a b -> Maybe b-hush = either (const Nothing) Just+mapLeft :: (a -> b) -> Either a t -> Either b t+mapLeft f = either (Left . f) Right++traverseLeft :: Applicative f => (a -> f b) -> Either a t -> f (Either b t)+traverseLeft f = either (fmap Left . f) (pure . Right)
src/Env/Free.hs view
@@ -1,12 +1,15 @@ {-# LANGUAGE GADTs #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-}--- @Alt F@ is the free 'Alternative' functor on @F@+-- | @Alt F@ is the free 'Alternative' functor on @F@ module Env.Free ( Alt(..) , liftAlt , runAlt , foldAlt+ , hoistAlt+ -- * Debug+ , inspect ) where import Control.Applicative (Applicative(..), Alternative(..))@@ -18,14 +21,22 @@ Pure :: a -> Alt f a Ap :: Alt f (a -> b) -> Alt f a -> Alt f b Alt :: Alt f a -> Alt f a -> Alt f a- Fun :: f a -> Alt f a+ Lift :: f a -> Alt f a +-- | Print the free structure+inspect :: Alt f a -> String+inspect Nope = "Nope"+inspect (Pure _) = "Pure _"+inspect (Ap f x) = concat ["(", inspect f, ") <*> (", inspect x, ")"]+inspect (Alt x y) = concat ["(", inspect x, ") <|> (", inspect y, ")"]+inspect (Lift _) = "Lift _"+ instance Functor f => Functor (Alt f) where fmap _ Nope = Nope fmap f (Pure a) = Pure (f a) fmap f (Ap a v) = Ap (fmap (f .) a) v fmap f (Alt a b) = Alt (fmap f a) (fmap f b)- fmap f (Fun a) = Fun (fmap f a)+ fmap f (Lift a) = Lift (fmap f a) instance Functor f => Applicative (Alt f) where pure = Pure@@ -37,7 +48,7 @@ liftAlt :: f a -> Alt f a-liftAlt = Fun+liftAlt = Lift runAlt :: forall f g a. Alternative g => (forall x. f x -> g x) -> Alt f a -> g a runAlt u = go where@@ -46,10 +57,13 @@ go (Pure a) = pure a go (Ap f x) = go f <*> go x go (Alt s t) = go s <|> go t- go (Fun x) = u x+ go (Lift x) = u x foldAlt :: Monoid p => (forall a. f a -> p) -> Alt f b -> p foldAlt f = unMon . runAlt (Mon . f)++hoistAlt :: forall f g b. Functor g => (forall a. f a -> g a) -> Alt f b -> Alt g b+hoistAlt nat = runAlt (Lift . nat) -- | The 'Alternative' functor induced by the 'Monoid'
src/Env/Help.hs view
@@ -12,16 +12,15 @@ import Env.Parse -helpDoc :: Mod Info a -> Parser a -> [Error] -> String-helpDoc (Mod f) p fs = intercalate "\n\n" . catMaybes $- [ infoHeader- , fmap (intercalate "\n" . splitWords 50) infoDesc- , Just "Available environment variables:"- , Just (intercalate "\n" (helpParserDoc p))- , fmap (intercalate "\n" . splitWords 50) infoFooter- ] ++ map Just (helpFailuresDoc fs)- where- Info { infoHeader, infoDesc, infoFooter } = f defaultInfo+helpDoc :: Info a -> Parser b -> [Error] -> String+helpDoc Info { infoHeader, infoDesc, infoFooter } p fs =+ intercalate "\n\n" . catMaybes $+ [ infoHeader+ , fmap (intercalate "\n" . splitWords 50) infoDesc+ , Just "Available environment variables:"+ , Just (intercalate "\n" (helpParserDoc p))+ , fmap (intercalate "\n" . splitWords 50) infoFooter+ ] ++ map Just (helpFailuresDoc fs) helpParserDoc :: Parser a -> [String] helpParserDoc = concat . Map.elems . foldAlt (\v -> Map.singleton (varfName v) (helpVarfDoc v)) . unParser
src/Env/Parse.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE ViewPatterns #-} module Env.Parse ( Parser(..) , VarF(..)@@ -11,6 +12,7 @@ , header , desc , footer+ , prefixed , var , Var(..) , defaultVar@@ -37,12 +39,11 @@ import Env.Val -static :: Parser a -> [(String, String)] -> Either [Error] a-static (Parser p) xs = toEither (runAlt go p)+static :: Parser b -> [(String, String)] -> Either [Error] b+static (Parser p) (Map.fromList -> env) =+ toEither (runAlt go p) where- go v = maybe id (\d x -> x <|> pure d) (varfDef v) (fromEither (readVar v =<< lookupVar v ys))-- ys = Map.fromList xs+ go v = maybe id (\d x -> x <|> pure d) (varfDef v) (fromEither (readVar v =<< lookupVar v env)) lookupVar :: VarF a -> Map String String -> Either [Error] String lookupVar v = note [ENoExistError (varfName v)] . Map.lookup (varfName v)@@ -69,7 +70,12 @@ empty = Parser empty Parser f <|> Parser x = Parser (f <|> x) +-- | The string to prepend to the name of every declared environment variable+prefixed :: String -> Parser a -> Parser a+prefixed pre =+ Parser . hoistAlt (\v -> v { varfName = pre ++ varfName v }) . unParser + data Error = ParseError String String | ENoExistError String@@ -151,16 +157,16 @@ -- | Parser's metadata data Info a = Info- { infoHeader :: Maybe String- , infoDesc :: Maybe String- , infoFooter :: Maybe String+ { infoHeader :: Maybe String+ , infoDesc :: Maybe String+ , infoFooter :: Maybe String } defaultInfo :: Info a defaultInfo = Info- { infoHeader = Nothing- , infoDesc = Nothing- , infoFooter = Nothing+ { infoHeader = Nothing+ , infoDesc = Nothing+ , infoFooter = Nothing } -- | A help text header (it usually includes an application name and version)