From 2c6fce530081f4374f23b59821e938c7877c0f4e Mon Sep 17 00:00:00 2001 From: Matvey Aksenov Date: Sat, 27 Feb 2016 20:01:11 +0000 Subject: [PATCH 01/15] Implement extensible errors. --- LICENSE | 2 +- default.nix | 2 +- envparse.cabal | 1 + shell.nix | 7 +++--- src/Env.hs | 21 ++++++++--------- src/Env/Error.hs | 59 ++++++++++++++++++++++++++++++++++++++++++++++++ src/Env/Help.hs | 19 +++++++++------- src/Env/Parse.hs | 58 +++++++++++++++++++++++------------------------ test/EnvSpec.hs | 24 +++++++++++--------- 9 files changed, 126 insertions(+), 67 deletions(-) create mode 100644 src/Env/Error.hs diff --git a/LICENSE b/LICENSE index 8d89b35..a92c1cb 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2014-2015, Matvey Aksenov +Copyright (c) 2014-2016, Matvey Aksenov All rights reserved. diff --git a/default.nix b/default.nix index 3827f09..b0ddb8f 100644 --- a/default.nix +++ b/default.nix @@ -1,2 +1,2 @@ -{ nixpkgs ? import {}, compiler ? "ghc7101" }: +{ nixpkgs ? import {}, compiler ? "ghc7102" }: nixpkgs.pkgs.haskell.packages.${compiler}.callPackage ./package.nix {} diff --git a/envparse.cabal b/envparse.cabal index e03d129..408a47c 100644 --- a/envparse.cabal +++ b/envparse.cabal @@ -77,6 +77,7 @@ library src exposed-modules: Env + Env.Error other-modules: Env.Free Env.Help diff --git a/shell.nix b/shell.nix index 5da8beb..3709213 100644 --- a/shell.nix +++ b/shell.nix @@ -1,7 +1,7 @@ -{ nixpkgs ? import {}, compiler ? "ghc7101" }: let +{ nixpkgs ? import {}, compiler ? "ghc7102" }: let inherit (nixpkgs) pkgs; ghc = pkgs.haskell.packages.${compiler}.ghcWithPackages(ps: [ - ps.hdevtools ps.doctest ps.hspec-discover + ps.hdevtools ps.doctest ps.hspec-discover ps.hlint ps.ghc-mod ]); cabal-install = pkgs.haskell.packages.${compiler}.cabal-install; pkg = (import ./default.nix { inherit nixpkgs compiler; }); @@ -11,7 +11,6 @@ in buildInputs = [ ghc cabal-install ] ++ pkg.env.buildInputs; shellHook = '' ${pkg.env.shellHook} - export IN_WHICH_NIX_SHELL=${name} - cabal --no-require-sandbox configure --package-db=$NIX_GHC_LIBDIR/package.conf.d --enable-tests + cabal configure --package-db=$NIX_GHC_LIBDIR/package.conf.d --enable-tests ''; } diff --git a/src/Env.hs b/src/Env.hs index 0fb0b0a..a2b495e 100644 --- a/src/Env.hs +++ b/src/Env.hs @@ -66,6 +66,8 @@ module Env , HasHelp , help , helpDoc + , Error + , AsUnset -- * Re-exports -- $re-exports , pure, (<$>), (<*>), (*>), (<*), optional @@ -82,15 +84,17 @@ import Control.Applicative import Control.Monad ((>=>), (<=<)) import Data.Foldable (asum) #if __GLASGOW_HASKELL__ < 710 -import Data.Monoid (Monoid(..)) -#endif +import Data.Monoid (Monoid(..), (<>)) +#else import Data.Monoid ((<>)) +#endif import System.Environment (getEnvironment) import System.Exit (exitFailure) import qualified System.IO as IO import Env.Help (helpInfo, helpDoc) import Env.Parse +import Env.Error (Error, AsUnset) -- $re-exports -- External functions that may be useful to the consumer of the library @@ -107,24 +111,17 @@ import Env.Parse -- @ -- >>> parse ('header' \"env-parse 0.2.0\") ('var' 'str' \"USER\" ('def' \"nobody\")) -- @ -parse :: Mod Info a -> Parser a -> IO a +parse :: Mod Info a -> Parser Error a -> IO a 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 +parseOr :: (String -> IO a) -> Mod Info b -> Parser Error b -> IO (Either a b) +parseOr f (Mod g) p = traverseLeft (f . helpInfo (g defaultInfo) p) . parsePure p =<< getEnvironment die :: String -> IO a die m = do IO.hPutStrLn IO.stderr m; exitFailure --- | Try to parse a pure environment -parsePure :: Mod Info a -> Parser a -> [(String, String)] -> Either String a -parsePure (Mod f) p = mapLeft (helpInfo (f defaultInfo) p) . static p - -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) diff --git a/src/Env/Error.hs b/src/Env/Error.hs new file mode 100644 index 0000000..170d3d8 --- /dev/null +++ b/src/Env/Error.hs @@ -0,0 +1,59 @@ +module Env.Error + ( Error(..) + , Unset(..) + , AsUnset(..) + , Empty(..) + , AsEmpty(..) + , Invalid(..) + , AsInvalid(..) + ) where + + +data Error + = UnsetError Unset + | EmptyError Empty + | InvalidError Invalid + deriving (Show, Eq) + +instance AsUnset Error where + unset = + UnsetError . Unset + +instance AsEmpty Error where + empty = + EmptyError . Empty + +instance AsInvalid Error where + invalid val = + InvalidError . Invalid val + + +newtype Unset = Unset { unUnset :: String } + deriving (Show, Eq) + +class AsUnset e where + unset :: String -> e + +instance AsUnset Unset where + unset = Unset + + +newtype Empty = Empty { unEmpty :: String } + deriving (Show, Eq) + +class AsEmpty e where + empty :: String -> e + +instance AsEmpty Empty where + empty = Empty + + +data Invalid + = Invalid String String + deriving (Show, Eq) + +class AsInvalid e where + invalid :: String -> String -> e + +instance AsInvalid Invalid where + invalid = Invalid diff --git a/src/Env/Help.hs b/src/Env/Help.hs index 2740f8d..f0d7bf2 100644 --- a/src/Env/Help.hs +++ b/src/Env/Help.hs @@ -11,9 +11,10 @@ import Data.Ord (comparing) import Env.Free import Env.Parse +import Env.Error (Error(..), Unset(..), Empty(..), Invalid(..)) -helpInfo :: Info a -> Parser b -> [Error] -> String +helpInfo :: Info a -> Parser e b -> [Error] -> String helpInfo Info { infoHeader, infoDesc, infoFooter } p errors = List.intercalate "\n\n" $ catMaybes [ infoHeader @@ -23,14 +24,14 @@ helpInfo Info { infoHeader, infoDesc, infoFooter } p errors = ] ++ helpErrors errors -- | A pretty-printed list of recognized environment variables suitable for usage messages. -helpDoc :: Parser a -> String +helpDoc :: Parser e a -> String helpDoc p = List.intercalate "\n" ("Available environment variables:\n" : helpParserDoc p) -helpParserDoc :: Parser a -> [String] +helpParserDoc :: Parser e a -> [String] helpParserDoc = concat . Map.elems . foldAlt (\v -> Map.singleton (varfName v) (helpVarfDoc v)) . unParser -helpVarfDoc :: VarF a -> [String] +helpVarfDoc :: VarF e a -> [String] helpVarfDoc VarF { varfName, varfHelp, varfHelpDef } = case varfHelp of Nothing -> [indent 2 varfName] @@ -51,12 +52,14 @@ helpErrors fs = ] helpError :: Error -> String -helpError (ParseError n e) = " " ++ n ++ " cannot be parsed: " ++ e -helpError (ENoExistError n) = " " ++ n ++ " is unset" +helpError (UnsetError (Unset n)) = " " ++ n ++ " is unset" +helpError (EmptyError (Empty n)) = " " ++ n ++ " is empty" +helpError (InvalidError (Invalid n val)) = " " ++ n ++ " has an invalid value " ++ val varName :: Error -> String -varName (ParseError n _) = n -varName (ENoExistError n) = n +varName (UnsetError (Unset n)) = n +varName (EmptyError (Empty n)) = n +varName (InvalidError (Invalid n _)) = n splitWords :: Int -> String -> [String] splitWords n = go [] 0 . words diff --git a/src/Env/Parse.hs b/src/Env/Parse.hs index a46e345..a331144 100644 --- a/src/Env/Parse.hs +++ b/src/Env/Parse.hs @@ -1,12 +1,12 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ViewPatterns #-} module Env.Parse ( Parser(..) , VarF(..) - , static - , Error(..) + , parsePure , Mod(..) , Info(..) , defaultInfo @@ -39,20 +39,22 @@ import Data.Monoid (Monoid(..)) import Data.String (IsString(..)) import Env.Free +import qualified Env.Error as Error import Env.Val -static :: Parser b -> [(String, String)] -> Either [Error] b -static (Parser p) (Map.fromList -> env) = +-- | Try to parse a pure environment +parsePure :: Error.AsUnset e => Parser e b -> [(String, String)] -> Either [e] b +parsePure (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 env)) -lookupVar :: VarF a -> Map String String -> Either [Error] String -lookupVar v = note [ENoExistError (varfName v)] . Map.lookup (varfName v) +lookupVar :: Error.AsUnset e => VarF e a -> Map String String -> Either [e] String +lookupVar v = note [Error.unset (varfName v)] . Map.lookup (varfName v) -readVar :: VarF a -> String -> Either [Error] a -readVar v = mapLeft (pure . ParseError (varfName v)) . varfReader v +readVar :: VarF e a -> String -> Either [e] a +readVar v = mapLeft (pure . ($ varfName v)) . varfReader v note :: a -> Maybe b -> Either a b note a = maybe (Left a) Right @@ -62,45 +64,40 @@ mapLeft f = either (Left . f) Right -- | An environment parser -newtype Parser a = Parser { unParser :: Alt VarF a } +newtype Parser e a = Parser { unParser :: Alt (VarF e) a } deriving (Functor) -instance Applicative Parser where +instance Applicative (Parser e) where pure = Parser . pure Parser f <*> Parser x = Parser (f <*> x) -instance Alternative Parser where +instance Alternative (Parser e) where 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 :: String -> Parser e a -> Parser e a prefixed pre = Parser . hoistAlt (\v -> v { varfName = pre ++ varfName v }) . unParser -data Error - = ParseError String String - | ENoExistError String - deriving (Show, Eq) - -data VarF a = VarF +data VarF e a = VarF { varfName :: String - , varfReader :: Reader a + , varfReader :: Reader e a , varfHelp :: Maybe String , varfDef :: Maybe a , varfHelpDef :: Maybe String } deriving (Functor) -- | An environment variable's value parser. Use @(<=<)@ and @(>=>)@ to combine these -type Reader a = String -> Either String a +type Reader e a = String -> Either (String -> e) a -- | Parse a particular variable from the environment -- -- @ -- >>> var 'str' \"EDITOR\" ('def' \"vim\" <> 'helpDef' show) -- @ -var :: Reader a -> String -> Mod Var a -> Parser a +var :: Reader e a -> String -> Mod Var a -> Parser e a var r n (Mod f) = Parser . liftAlt $ VarF { varfName = n , varfReader = r @@ -116,12 +113,13 @@ var r n (Mod f) = Parser . liftAlt $ VarF -- -- /Note:/ this parser never fails. flag - :: a -- ^ default value + :: forall e a. Error.AsEmpty e + => a -- ^ default value -> a -- ^ active value - -> String -> Mod Flag a -> Parser a + -> String -> Mod Flag a -> Parser e a flag f t n (Mod g) = Parser . liftAlt $ VarF { varfName = n - , varfReader = Right . either (const f) (const t) . (nonempty :: Reader String) + , varfReader = Right . either (const f) (const t) . (nonempty :: Reader e String) , varfHelp = flagHelp , varfDef = Just f , varfHelpDef = Nothing @@ -132,20 +130,20 @@ flag f t n (Mod g) = Parser . liftAlt $ VarF -- | A simple boolean 'flag' -- -- /Note:/ the same caveats apply. -switch :: String -> Mod Flag Bool -> Parser Bool +switch :: Error.AsEmpty e => String -> Mod Flag Bool -> Parser e Bool switch = flag False True -- | The trivial reader -str :: IsString s => Reader s +str :: IsString s => Reader e s str = Right . fromString -- | The reader that accepts only non-empty strings -nonempty :: IsString s => Reader s -nonempty = fmap fromString . go where go [] = Left "a non-empty string is expected"; go xs = Right xs +nonempty :: (Error.AsEmpty e, IsString s) => Reader e s +nonempty = fmap fromString . go where go [] = Left Error.empty; go xs = Right xs -- | The reader that uses the 'Read' instance of the type -auto :: Read a => Reader a -auto = \s -> case reads s of [(v, "")] -> Right v; _ -> Left (show s ++ " is an invalid value") +auto :: (Error.AsInvalid e, Read a) => Reader e a +auto = \s -> case reads s of [(v, "")] -> Right v; _ -> Left (Error.invalid (show s)) {-# ANN auto "HLint: ignore Redundant lambda" #-} diff --git a/test/EnvSpec.hs b/test/EnvSpec.hs index b4fe103..f39ccd7 100644 --- a/test/EnvSpec.hs +++ b/test/EnvSpec.hs @@ -2,13 +2,14 @@ {-# OPTIONS_GHC -fno-warn-type-defaults #-} module EnvSpec (spec) where -import Control.Applicative -import Control.Monad -import Prelude hiding (pi) -import Test.Hspec -import Text.Read (readMaybe) +import Control.Applicative +import Control.Monad +import Prelude hiding (pi) +import Test.Hspec +import Text.Read (readMaybe) -import Env +import Env +import qualified Env.Error as Error default (Integer, Double, String) @@ -66,7 +67,7 @@ spec = context "modifiers" $ do it "the latter modifier overwrites the former" $ - p (var (\_ -> Left "nope") "never" (def 4 <> def 7)) `shouldBe` Just 7 + p (var (\_ -> Left (Error.invalid "nope")) "never" (def 4 <> def 7)) `shouldBe` Just 7 it "‘prefixed’ modifier changes the names of the variables" $ p (prefixed "spec_" (var str "foo" mempty)) `shouldBe` Just "totally-not-bar" @@ -77,11 +78,12 @@ spec = Just "zygohistomorphic" -greaterThan5 :: Reader Int -greaterThan5 s = note "fail" (do v <- readMaybe s; guard (v > 5); return v) +greaterThan5 :: Error.AsInvalid e => Reader e Int +greaterThan5 s = + note (Error.invalid "fail") (do v <- readMaybe s; guard (v > 5); return v) -p :: Parser a -> Maybe a -p x = hush (parsePure mempty x fancyEnv) +p :: Parser Error a -> Maybe a +p x = hush (parsePure x fancyEnv) fancyEnv :: [(String, String)] fancyEnv = From eab9d5381ffbf9fdc261e0993d91a4c716a80dfb Mon Sep 17 00:00:00 2001 From: Matvey Aksenov Date: Sat, 27 Feb 2016 21:15:15 +0000 Subject: [PATCH 02/15] Drop newtypes for errors. --- src/Env/Error.hs | 40 ++++++---------------------------------- src/Env/Help.hs | 14 +++++++------- 2 files changed, 13 insertions(+), 41 deletions(-) diff --git a/src/Env/Error.hs b/src/Env/Error.hs index 170d3d8..907475b 100644 --- a/src/Env/Error.hs +++ b/src/Env/Error.hs @@ -1,59 +1,31 @@ module Env.Error ( Error(..) - , Unset(..) , AsUnset(..) - , Empty(..) , AsEmpty(..) - , Invalid(..) , AsInvalid(..) ) where data Error - = UnsetError Unset - | EmptyError Empty - | InvalidError Invalid + = UnsetError String + | EmptyError String + | InvalidError String String deriving (Show, Eq) instance AsUnset Error where - unset = - UnsetError . Unset + unset = UnsetError instance AsEmpty Error where - empty = - EmptyError . Empty + empty = EmptyError instance AsInvalid Error where - invalid val = - InvalidError . Invalid val - - -newtype Unset = Unset { unUnset :: String } - deriving (Show, Eq) + invalid = InvalidError class AsUnset e where unset :: String -> e -instance AsUnset Unset where - unset = Unset - - -newtype Empty = Empty { unEmpty :: String } - deriving (Show, Eq) - class AsEmpty e where empty :: String -> e -instance AsEmpty Empty where - empty = Empty - - -data Invalid - = Invalid String String - deriving (Show, Eq) - class AsInvalid e where invalid :: String -> String -> e - -instance AsInvalid Invalid where - invalid = Invalid diff --git a/src/Env/Help.hs b/src/Env/Help.hs index f0d7bf2..cd16004 100644 --- a/src/Env/Help.hs +++ b/src/Env/Help.hs @@ -11,7 +11,7 @@ import Data.Ord (comparing) import Env.Free import Env.Parse -import Env.Error (Error(..), Unset(..), Empty(..), Invalid(..)) +import Env.Error (Error(..)) helpInfo :: Info a -> Parser e b -> [Error] -> String @@ -52,14 +52,14 @@ helpErrors fs = ] helpError :: Error -> String -helpError (UnsetError (Unset n)) = " " ++ n ++ " is unset" -helpError (EmptyError (Empty n)) = " " ++ n ++ " is empty" -helpError (InvalidError (Invalid n val)) = " " ++ n ++ " has an invalid value " ++ val +helpError (UnsetError n) = " " ++ n ++ " is unset" +helpError (EmptyError n) = " " ++ n ++ " is empty" +helpError (InvalidError n val) = " " ++ n ++ " has an invalid value " ++ val varName :: Error -> String -varName (UnsetError (Unset n)) = n -varName (EmptyError (Empty n)) = n -varName (InvalidError (Invalid n _)) = n +varName (UnsetError n) = n +varName (EmptyError n) = n +varName (InvalidError n _) = n splitWords :: Int -> String -> [String] splitWords n = go [] 0 . words From 9c707c18d150645ba7d8f5a14e1cf162625f7044 Mon Sep 17 00:00:00 2001 From: Matvey Aksenov Date: Sat, 27 Feb 2016 21:24:42 +0000 Subject: [PATCH 03/15] Refactoring. --- src/Env/Error.hs | 12 ++++++------ src/Env/Help.hs | 25 ++++++++++++++----------- src/Env/Parse.hs | 20 ++++++++++++-------- 3 files changed, 32 insertions(+), 25 deletions(-) diff --git a/src/Env/Error.hs b/src/Env/Error.hs index 907475b..8f27062 100644 --- a/src/Env/Error.hs +++ b/src/Env/Error.hs @@ -7,9 +7,9 @@ module Env.Error data Error - = UnsetError String - | EmptyError String - | InvalidError String String + = UnsetError + | EmptyError + | InvalidError String deriving (Show, Eq) instance AsUnset Error where @@ -22,10 +22,10 @@ instance AsInvalid Error where invalid = InvalidError class AsUnset e where - unset :: String -> e + unset :: e class AsEmpty e where - empty :: String -> e + empty :: e class AsInvalid e where - invalid :: String -> String -> e + invalid :: String -> e diff --git a/src/Env/Help.hs b/src/Env/Help.hs index cd16004..f13c319 100644 --- a/src/Env/Help.hs +++ b/src/Env/Help.hs @@ -14,7 +14,7 @@ import Env.Parse import Env.Error (Error(..)) -helpInfo :: Info a -> Parser e b -> [Error] -> String +helpInfo :: Info a -> Parser e b -> [(String, Error)] -> String helpInfo Info { infoHeader, infoDesc, infoFooter } p errors = List.intercalate "\n\n" $ catMaybes [ infoHeader @@ -44,22 +44,25 @@ helpVarfDoc VarF { varfName, varfHelp, varfHelpDef } = where k = length varfName t = maybe h (\s -> h ++ " (default: " ++ s ++")") varfHelpDef -helpErrors :: [Error] -> [String] +helpErrors :: [(String, Error)] -> [String] helpErrors [] = [] helpErrors fs = [ "Parsing errors:" - , List.intercalate "\n" (map helpError (List.sortBy (comparing varName) fs)) + , List.intercalate "\n" (map (uncurry helpError) (List.sortBy (comparing varName) fs)) ] -helpError :: Error -> String -helpError (UnsetError n) = " " ++ n ++ " is unset" -helpError (EmptyError n) = " " ++ n ++ " is empty" -helpError (InvalidError n val) = " " ++ n ++ " has an invalid value " ++ val +helpError :: String -> Error -> String +helpError n err = + case err of + UnsetError -> + " " ++ n ++ " is unset" + EmptyError -> + " " ++ n ++ " is empty" + InvalidError val -> + " " ++ n ++ " has an invalid value " ++ val -varName :: Error -> String -varName (UnsetError n) = n -varName (EmptyError n) = n -varName (InvalidError n _) = n +varName :: (String, Error) -> String +varName (n, _) = n splitWords :: Int -> String -> [String] splitWords n = go [] 0 . words diff --git a/src/Env/Parse.hs b/src/Env/Parse.hs index a331144..fd84faa 100644 --- a/src/Env/Parse.hs +++ b/src/Env/Parse.hs @@ -44,23 +44,27 @@ import Env.Val -- | Try to parse a pure environment -parsePure :: Error.AsUnset e => Parser e b -> [(String, String)] -> Either [e] b +parsePure :: Error.AsUnset e => Parser e b -> [(String, String)] -> Either [(String, e)] b parsePure (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 env)) -lookupVar :: Error.AsUnset e => VarF e a -> Map String String -> Either [e] String -lookupVar v = note [Error.unset (varfName v)] . Map.lookup (varfName v) +lookupVar :: Error.AsUnset e => VarF e a -> Map String String -> Either [(String, e)] String +lookupVar VarF {varfName} = + note [(varfName, Error.unset)] . Map.lookup varfName -readVar :: VarF e a -> String -> Either [e] a -readVar v = mapLeft (pure . ($ varfName v)) . varfReader v +readVar :: VarF e a -> String -> Either [(String, e)] a +readVar VarF {varfName, varfReader} = + mapLeft (pure . (\err -> (varfName, err))) . varfReader note :: a -> Maybe b -> Either a b -note a = maybe (Left a) Right +note a = + maybe (Left a) Right mapLeft :: (a -> b) -> Either a t -> Either b t -mapLeft f = either (Left . f) Right +mapLeft f = + either (Left . f) Right -- | An environment parser @@ -90,7 +94,7 @@ data VarF e a = VarF } deriving (Functor) -- | An environment variable's value parser. Use @(<=<)@ and @(>=>)@ to combine these -type Reader e a = String -> Either (String -> e) a +type Reader e a = String -> Either e a -- | Parse a particular variable from the environment -- From 81fdbe93439845479301e279453e51cf40805158 Mon Sep 17 00:00:00 2001 From: Matvey Aksenov Date: Sun, 28 Feb 2016 09:27:32 +0000 Subject: [PATCH 04/15] Custom help. --- shell.nix | 4 +-- src/Env.hs | 28 +++++++++++++++++--- src/Env/Error.hs | 33 +++++++++++++++++------- src/Env/Help.hs | 67 ++++++++++++++++++++++++++++++------------------ 4 files changed, 93 insertions(+), 39 deletions(-) diff --git a/shell.nix b/shell.nix index 3709213..b883623 100644 --- a/shell.nix +++ b/shell.nix @@ -8,9 +8,9 @@ in pkgs.stdenv.mkDerivation rec { name = pkg.pname; - buildInputs = [ ghc cabal-install ] ++ pkg.env.buildInputs; + buildInputs = [ ghc cabal-install pkgs.moreutils ] ++ pkg.env.buildInputs; shellHook = '' ${pkg.env.shellHook} - cabal configure --package-db=$NIX_GHC_LIBDIR/package.conf.d --enable-tests + chronic cabal configure --package-db=$NIX_GHC_LIBDIR/package.conf.d --enable-tests ''; } diff --git a/src/Env.hs b/src/Env.hs index a2b495e..74e96ca 100644 --- a/src/Env.hs +++ b/src/Env.hs @@ -75,6 +75,13 @@ module Env , (<=<), (>=>) , (<>), mempty, mconcat , asum + -- * Custom Errors + -- $custom-errors + , parseWith + , parseWithOr + , ErrorHandler + , helpInfoWith + , handleError -- * Testing -- $testing , parsePure @@ -92,7 +99,7 @@ import System.Environment (getEnvironment) import System.Exit (exitFailure) import qualified System.IO as IO -import Env.Help (helpInfo, helpDoc) +import Env.Help (ErrorHandler, helpDoc, helpInfoWith, handleError) import Env.Parse import Env.Error (Error, AsUnset) @@ -103,6 +110,9 @@ import Env.Error (Error, AsUnset) -- Utilities to test—without dabbling in IO—that your parsers do -- what you want them to do +-- $custom-errors +-- A slightly generalized parsing functions for when the consumer of the library +-- wants to use custom errors -- | Parse the environment or die -- @@ -112,13 +122,25 @@ import Env.Error (Error, AsUnset) -- >>> parse ('header' \"env-parse 0.2.0\") ('var' 'str' \"USER\" ('def' \"nobody\")) -- @ parse :: Mod Info a -> Parser Error a -> IO a -parse m = fmap (either (\_ -> error "absurd") id) . parseOr die m +parse = + parseWith handleError -- | 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 Error b -> IO (Either a b) -parseOr f (Mod g) p = traverseLeft (f . helpInfo (g defaultInfo) p) . parsePure p =<< getEnvironment +parseOr = + parseWithOr handleError + +-- | Parse the environment handling custom errors or die +parseWith :: AsUnset e => ErrorHandler e -> Mod Info a -> Parser e a -> IO a +parseWith handler m = + fmap (either (\_ -> error "absurd") id) . parseWithOr handler die m + +-- | Try to parse the environment handling custom errors +parseWithOr :: AsUnset e => ErrorHandler e -> (String -> IO a) -> Mod Info b -> Parser e b -> IO (Either a b) +parseWithOr handler f (Mod g) p = + traverseLeft (f . helpInfoWith handler (g defaultInfo) p) . parsePure p =<< getEnvironment die :: String -> IO a die m = do IO.hPutStrLn IO.stderr m; exitFailure diff --git a/src/Env/Error.hs b/src/Env/Error.hs index 8f27062..5e7a824 100644 --- a/src/Env/Error.hs +++ b/src/Env/Error.hs @@ -12,20 +12,35 @@ data Error | InvalidError String deriving (Show, Eq) -instance AsUnset Error where - unset = UnsetError - -instance AsEmpty Error where - empty = EmptyError - -instance AsInvalid Error where - invalid = InvalidError - class AsUnset e where unset :: e + tryUnset :: e -> Maybe () + +instance AsUnset Error where + unset = UnsetError + tryUnset err = + case err of + UnsetError -> Just () + _ -> Nothing class AsEmpty e where empty :: e + tryEmpty :: e -> Maybe () + +instance AsEmpty Error where + empty = EmptyError + tryEmpty err = + case err of + EmptyError -> Just () + _ -> Nothing class AsInvalid e where invalid :: String -> e + tryInvalid :: e -> Maybe String + +instance AsInvalid Error where + invalid = InvalidError + tryInvalid err = + case err of + InvalidError msg -> Just msg + _ -> Nothing diff --git a/src/Env/Help.hs b/src/Env/Help.hs index f13c319..71b4d4b 100644 --- a/src/Env/Help.hs +++ b/src/Env/Help.hs @@ -1,29 +1,37 @@ {-# LANGUAGE NamedFieldPuns #-} module Env.Help ( helpInfo + , helpInfoWith , helpDoc + , ErrorHandler + , handleError ) where +import Data.Foldable (asum) import qualified Data.List as List import qualified Data.Map as Map -import Data.Maybe (catMaybes) +import Data.Maybe (catMaybes, mapMaybe) import Data.Ord (comparing) import Env.Free import Env.Parse import Env.Error (Error(..)) +import qualified Env.Error as Error -helpInfo :: Info a -> Parser e b -> [(String, Error)] -> String -helpInfo Info { infoHeader, infoDesc, infoFooter } p errors = +helpInfo :: Info a -> Parser Error b -> [(String, Error)] -> String +helpInfo = helpInfoWith handleError + +helpInfoWith :: ErrorHandler e -> Info a -> Parser e b -> [(String, e)] -> String +helpInfoWith handler Info { infoHeader, infoDesc, infoFooter } p errors = List.intercalate "\n\n" $ catMaybes [ infoHeader , fmap (List.intercalate "\n" . splitWords 50) infoDesc , Just (helpDoc p) , fmap (List.intercalate "\n" . splitWords 50) infoFooter - ] ++ helpErrors errors + ] ++ helpErrors handler errors --- | A pretty-printed list of recognized environment variables suitable for usage messages. +-- | A pretty-printed list of recognized environment variables suitable for usage messages helpDoc :: Parser e a -> String helpDoc p = List.intercalate "\n" ("Available environment variables:\n" : helpParserDoc p) @@ -44,26 +52,6 @@ helpVarfDoc VarF { varfName, varfHelp, varfHelpDef } = where k = length varfName t = maybe h (\s -> h ++ " (default: " ++ s ++")") varfHelpDef -helpErrors :: [(String, Error)] -> [String] -helpErrors [] = [] -helpErrors fs = - [ "Parsing errors:" - , List.intercalate "\n" (map (uncurry helpError) (List.sortBy (comparing varName) fs)) - ] - -helpError :: String -> Error -> String -helpError n err = - case err of - UnsetError -> - " " ++ n ++ " is unset" - EmptyError -> - " " ++ n ++ " is empty" - InvalidError val -> - " " ++ n ++ " has an invalid value " ++ val - -varName :: (String, Error) -> String -varName (n, _) = n - splitWords :: Int -> String -> [String] splitWords n = go [] 0 . words where @@ -80,3 +68,32 @@ splitWords n = go [] 0 . words indent :: Int -> String -> String indent n s = replicate n ' ' ++ s + +-- | Given a variable name and an error value, try to produce a useful error message +type ErrorHandler e = String -> e -> Maybe String + +helpErrors :: ErrorHandler e -> [(String, e)] -> [String] +helpErrors _ [] = [] +helpErrors handler fs = + [ "Parsing errors:" + , List.intercalate "\n" (mapMaybe (uncurry handler) (List.sortBy (comparing varName) fs)) + ] + +handleError :: (Error.AsUnset e, Error.AsEmpty e, Error.AsInvalid e) => ErrorHandler e +handleError name err = + asum [handleUnsetError name err, handleEmptyError name err, handleInvalidError name err] + +handleUnsetError :: Error.AsUnset e => ErrorHandler e +handleUnsetError name = + fmap (\() -> indent 2 (name ++ " is unset")) . Error.tryUnset + +handleEmptyError :: Error.AsEmpty e => ErrorHandler e +handleEmptyError name = + fmap (\() -> indent 2 (name ++ " is empty")) . Error.tryEmpty + +handleInvalidError :: Error.AsInvalid e => ErrorHandler e +handleInvalidError name = + fmap (\val -> indent 2 (name ++ " has the invalid value " ++ val)) . Error.tryInvalid + +varName :: (String, e) -> String +varName (n, _) = n From ded0a01b880beb4cd70062b1838716f63a676354 Mon Sep 17 00:00:00 2001 From: Matvey Aksenov Date: Mon, 29 Feb 2016 21:37:12 +0000 Subject: [PATCH 05/15] Provide an example. --- example/CustomError.hs | 92 ++++++++++++++++++++++++++++++++++++++++++ example/Main.hs | 2 +- 2 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 example/CustomError.hs diff --git a/example/CustomError.hs b/example/CustomError.hs new file mode 100644 index 0000000..0d25b67 --- /dev/null +++ b/example/CustomError.hs @@ -0,0 +1,92 @@ +{-# LANGUAGE NamedFieldPuns #-} +-- | Greetings for $NAMES +-- +-- @ +-- % NAMES=foo runhaskell -isrc example/Main.hs +-- ... +-- NAMES should have between 3 and 4 names, but there's 2 of them +-- % NAMES=foo,bar,baz runhaskell -isrc example/Main.hs +-- Hello, foo! +-- Hello, bar! +-- Hello, baz! +module Main (main) where + +import Control.Monad (forM_) +import Env +import qualified Env.Error as Error +import Text.Printf (printf) + + +newtype Hello = Hello { names :: [String] } + + +main :: IO () +main = do + Hello {names} <- hello + forM_ names $ \name -> + putStrLn ("Hello, " ++ name ++ "!") + +hello :: IO Hello +hello = Env.parseWith handleCustomError (header "envparse example") $ Hello + <$> var (goodEnough <=< sepBy ',' <=< nonempty) "NAMES" (help "Targets for the greeting") + +handleCustomError :: ErrorHandler CustomError +handleCustomError name err = + case err of + LengthError l (lmin, lmax) -> + Just (printf " %s should have between %d and %d names, but there's %d of them" name lmin lmax l) + _ -> + handleError name err + +goodEnough :: [String] -> Either CustomError [String] +goodEnough xs + | l < lmin || l > lmax = + Left (LengthError l range) + | otherwise = + pure xs + where + l = length xs + range@(lmin, lmax) = (3, 4) + +sepBy :: Char -> Reader e [String] +sepBy sep = + pure . splitOn sep + +data CustomError + = LengthError Int (Int, Int) + | EnvError Error + +-- * Boilerplate + +instance AsUnset CustomError where + unset = + EnvError Error.unset + tryUnset err = + case err of + EnvError err' -> Error.tryUnset err' + _ -> Nothing + +instance Error.AsEmpty CustomError where + empty = + EnvError Error.empty + tryEmpty err = + case err of + EnvError err' -> Error.tryEmpty err' + _ -> Nothing + +instance Error.AsInvalid CustomError where + invalid = + EnvError . Error.invalid + tryInvalid err = + case err of + EnvError err' -> Error.tryInvalid err' + _ -> Nothing + +splitOn :: Eq a => a -> [a] -> [[a]] +splitOn sep = go + where + go xs = + case break (== sep) xs of + ([], _) -> [] + (ys, []) -> ys : [] + (ys, _ : zs) -> ys : go zs diff --git a/example/Main.hs b/example/Main.hs index cb22faf..da53dc3 100644 --- a/example/Main.hs +++ b/example/Main.hs @@ -18,7 +18,7 @@ data Hello = Hello { name :: String, quiet :: Bool } main :: IO () main = do - Hello { name, quiet } <- hello + Hello {name, quiet} <- hello unless quiet $ putStrLn ("Hello, " ++ name ++ "!") From 957b676a7c578bb42633784b1b0d68c176c6e04b Mon Sep 17 00:00:00 2001 From: Matvey Aksenov Date: Mon, 29 Feb 2016 21:50:06 +0000 Subject: [PATCH 06/15] Wording. --- src/Env.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Env.hs b/src/Env.hs index 74e96ca..3da270d 100644 --- a/src/Env.hs +++ b/src/Env.hs @@ -111,7 +111,7 @@ import Env.Error (Error, AsUnset) -- what you want them to do -- $custom-errors --- A slightly generalized parsing functions for when the consumer of the library +-- Generalized parsing functions for when the consumer of the library -- wants to use custom errors -- | Parse the environment or die From 3e7a76e26374028ca0d911f6fc0567363d66f877 Mon Sep 17 00:00:00 2001 From: Matvey Aksenov Date: Wed, 2 Mar 2016 08:08:39 +0000 Subject: [PATCH 07/15] A more focused example. --- example/CustomError.hs | 54 +++++++++++++++--------------------------- example/Main.hs | 1 - 2 files changed, 19 insertions(+), 36 deletions(-) diff --git a/example/CustomError.hs b/example/CustomError.hs index 0d25b67..d893118 100644 --- a/example/CustomError.hs +++ b/example/CustomError.hs @@ -2,58 +2,51 @@ -- | Greetings for $NAMES -- -- @ --- % NAMES=foo runhaskell -isrc example/Main.hs +-- % NAME=a5579150 COUNT=0 runhaskell -isrc example/Main.hs -- ... --- NAMES should have between 3 and 4 names, but there's 2 of them --- % NAMES=foo,bar,baz runhaskell -isrc example/Main.hs +-- COUNT must be > 0, but is 0 +-- % NAME=a5579150 COUNT=3 runhaskell -isrc example/Main.hs +-- Hello, foo! +-- Hello, foo! -- Hello, foo! --- Hello, bar! --- Hello, baz! module Main (main) where -import Control.Monad (forM_) +import Control.Monad (replicateM_) import Env import qualified Env.Error as Error import Text.Printf (printf) -newtype Hello = Hello { names :: [String] } - +data Hello = Hello { name :: String, count :: Int } main :: IO () main = do - Hello {names} <- hello - forM_ names $ \name -> + Hello {name, count} <- hello + replicateM_ count $ putStrLn ("Hello, " ++ name ++ "!") hello :: IO Hello hello = Env.parseWith handleCustomError (header "envparse example") $ Hello - <$> var (goodEnough <=< sepBy ',' <=< nonempty) "NAMES" (help "Targets for the greeting") + <$> var nonempty "NAME" (help "Target for the greeting") + <*> var (positive <=< auto) "COUNT" (help "How many times to greet?") handleCustomError :: ErrorHandler CustomError handleCustomError name err = case err of - LengthError l (lmin, lmax) -> - Just (printf " %s should have between %d and %d names, but there's %d of them" name lmin lmax l) + NonPositive n -> + Just (printf " %s must be > 0, but is %d" name n) _ -> handleError name err -goodEnough :: [String] -> Either CustomError [String] -goodEnough xs - | l < lmin || l > lmax = - Left (LengthError l range) +positive :: Int -> Either CustomError Int +positive n + | n <= 0 = + Left (NonPositive n) | otherwise = - pure xs - where - l = length xs - range@(lmin, lmax) = (3, 4) - -sepBy :: Char -> Reader e [String] -sepBy sep = - pure . splitOn sep + pure n data CustomError - = LengthError Int (Int, Int) + = NonPositive Int | EnvError Error -- * Boilerplate @@ -81,12 +74,3 @@ instance Error.AsInvalid CustomError where case err of EnvError err' -> Error.tryInvalid err' _ -> Nothing - -splitOn :: Eq a => a -> [a] -> [[a]] -splitOn sep = go - where - go xs = - case break (== sep) xs of - ([], _) -> [] - (ys, []) -> ys : [] - (ys, _ : zs) -> ys : go zs diff --git a/example/Main.hs b/example/Main.hs index da53dc3..040d9f3 100644 --- a/example/Main.hs +++ b/example/Main.hs @@ -15,7 +15,6 @@ import Env data Hello = Hello { name :: String, quiet :: Bool } - main :: IO () main = do Hello {name, quiet} <- hello From 0fc61132db7ad67149989575f7292058376f9f59 Mon Sep 17 00:00:00 2001 From: Matvey Aksenov Date: Fri, 11 Mar 2016 20:53:52 +0000 Subject: [PATCH 08/15] This is somewhat closer to what I want. --- example/CustomError.hs | 10 +++--- src/Env.hs | 48 +++++++++------------------ src/Env/Help.hs | 75 +++++++++++++++++++++++++++++++++--------- src/Env/Parse.hs | 31 ----------------- 4 files changed, 81 insertions(+), 83 deletions(-) diff --git a/example/CustomError.hs b/example/CustomError.hs index d893118..b43a5ab 100644 --- a/example/CustomError.hs +++ b/example/CustomError.hs @@ -11,9 +11,11 @@ -- Hello, foo! module Main (main) where +import Control.Category (Category(..)) import Control.Monad (replicateM_) import Env import qualified Env.Error as Error +import Prelude hiding ((.), id) import Text.Printf (printf) @@ -26,17 +28,17 @@ main = do putStrLn ("Hello, " ++ name ++ "!") hello :: IO Hello -hello = Env.parseWith handleCustomError (header "envparse example") $ Hello +hello = Env.parse (header "envparse example" . handleError customErrorHandler) $ Hello <$> var nonempty "NAME" (help "Target for the greeting") <*> var (positive <=< auto) "COUNT" (help "How many times to greet?") -handleCustomError :: ErrorHandler CustomError -handleCustomError name err = +customErrorHandler :: ErrorHandler CustomError +customErrorHandler name err = case err of NonPositive n -> Just (printf " %s must be > 0, but is %d" name n) _ -> - handleError name err + defaultErrorHandler name err positive :: Int -> Either CustomError Int positive n diff --git a/src/Env.hs b/src/Env.hs index 3da270d..50a7355 100644 --- a/src/Env.hs +++ b/src/Env.hs @@ -47,10 +47,13 @@ module Env , parseOr , Parser , Mod - , Info - , header - , desc - , footer + , Help.Info + , Help.header + , Help.desc + , Help.footer + , Help.handleError + , Help.ErrorHandler + , Help.defaultErrorHandler , prefixed , var , Var @@ -65,7 +68,7 @@ module Env , Flag , HasHelp , help - , helpDoc + , Help.helpDoc , Error , AsUnset -- * Re-exports @@ -75,13 +78,6 @@ module Env , (<=<), (>=>) , (<>), mempty, mconcat , asum - -- * Custom Errors - -- $custom-errors - , parseWith - , parseWithOr - , ErrorHandler - , helpInfoWith - , handleError -- * Testing -- $testing , parsePure @@ -99,7 +95,7 @@ import System.Environment (getEnvironment) import System.Exit (exitFailure) import qualified System.IO as IO -import Env.Help (ErrorHandler, helpDoc, helpInfoWith, handleError) +import qualified Env.Help as Help import Env.Parse import Env.Error (Error, AsUnset) @@ -110,10 +106,6 @@ import Env.Error (Error, AsUnset) -- Utilities to test—without dabbling in IO—that your parsers do -- what you want them to do --- $custom-errors --- Generalized parsing functions for when the consumer of the library --- wants to use custom errors - -- | Parse the environment or die -- -- Prints the help text and exits with @EXIT_FAILURE@ on encountering a parse error. @@ -121,26 +113,16 @@ import Env.Error (Error, AsUnset) -- @ -- >>> parse ('header' \"env-parse 0.2.0\") ('var' 'str' \"USER\" ('def' \"nobody\")) -- @ -parse :: Mod Info a -> Parser Error a -> IO a -parse = - parseWith handleError +parse :: AsUnset e => Help.Mod Help.Info Error e -> Parser e a -> IO a +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 Error b -> IO (Either a b) -parseOr = - parseWithOr handleError - --- | Parse the environment handling custom errors or die -parseWith :: AsUnset e => ErrorHandler e -> Mod Info a -> Parser e a -> IO a -parseWith handler m = - fmap (either (\_ -> error "absurd") id) . parseWithOr handler die m - --- | Try to parse the environment handling custom errors -parseWithOr :: AsUnset e => ErrorHandler e -> (String -> IO a) -> Mod Info b -> Parser e b -> IO (Either a b) -parseWithOr handler f (Mod g) p = - traverseLeft (f . helpInfoWith handler (g defaultInfo) p) . parsePure p =<< getEnvironment +parseOr :: AsUnset e => (String -> IO a) -> Help.Mod Help.Info Error e -> Parser e b -> IO (Either a b) +parseOr f (Help.Mod g) p = + traverseLeft (f . Help.helpInfo (g Help.defaultInfo) p) . parsePure p =<< getEnvironment die :: String -> IO a die m = do IO.hPutStrLn IO.stderr m; exitFailure diff --git a/src/Env/Help.hs b/src/Env/Help.hs index 71b4d4b..d862ea4 100644 --- a/src/Env/Help.hs +++ b/src/Env/Help.hs @@ -1,35 +1,40 @@ {-# LANGUAGE NamedFieldPuns #-} module Env.Help ( helpInfo - , helpInfoWith , helpDoc + , Mod(..) + , Info(..) , ErrorHandler + , defaultInfo + , defaultErrorHandler + , header + , desc + , footer , handleError ) where +import Control.Category (Category(..)) import Data.Foldable (asum) import qualified Data.List as List import qualified Data.Map as Map import Data.Maybe (catMaybes, mapMaybe) import Data.Ord (comparing) +import Prelude hiding ((.), id) -import Env.Free -import Env.Parse -import Env.Error (Error(..)) +import Env.Error (Error) import qualified Env.Error as Error +import Env.Free +import Env.Parse hiding (Mod) -helpInfo :: Info a -> Parser Error b -> [(String, Error)] -> String -helpInfo = helpInfoWith handleError - -helpInfoWith :: ErrorHandler e -> Info a -> Parser e b -> [(String, e)] -> String -helpInfoWith handler Info { infoHeader, infoDesc, infoFooter } p errors = +helpInfo :: Info e -> Parser e b -> [(String, e)] -> String +helpInfo Info {infoHeader, infoDesc, infoFooter, infoHandleError} p errors = List.intercalate "\n\n" $ catMaybes [ infoHeader , fmap (List.intercalate "\n" . splitWords 50) infoDesc , Just (helpDoc p) , fmap (List.intercalate "\n" . splitWords 50) infoFooter - ] ++ helpErrors handler errors + ] ++ helpErrors infoHandleError errors -- | A pretty-printed list of recognized environment variables suitable for usage messages helpDoc :: Parser e a -> String @@ -69,9 +74,6 @@ splitWords n = go [] 0 . words indent :: Int -> String -> String indent n s = replicate n ' ' ++ s --- | Given a variable name and an error value, try to produce a useful error message -type ErrorHandler e = String -> e -> Maybe String - helpErrors :: ErrorHandler e -> [(String, e)] -> [String] helpErrors _ [] = [] helpErrors handler fs = @@ -79,8 +81,51 @@ helpErrors handler fs = , List.intercalate "\n" (mapMaybe (uncurry handler) (List.sortBy (comparing varName) fs)) ] -handleError :: (Error.AsUnset e, Error.AsEmpty e, Error.AsInvalid e) => ErrorHandler e -handleError name err = +-- | This represents a modification of the properties of a particular 'Parser'. +-- Combine them using the 'Monoid' instance. +newtype Mod t a b = Mod (t a -> t b) + +instance Category (Mod t) where + id = Mod id + Mod f . Mod g = Mod (f . g) + +-- | Parser's metadata +data Info e = Info + { infoHeader :: Maybe String + , infoDesc :: Maybe String + , infoFooter :: Maybe String + , infoHandleError :: ErrorHandler e + } + +-- | Given a variable name and an error value, try to produce a useful error message +type ErrorHandler e = String -> e -> Maybe String + +defaultInfo :: Info Error +defaultInfo = Info + { infoHeader = Nothing + , infoDesc = Nothing + , infoFooter = Nothing + , infoHandleError = defaultErrorHandler + } + +-- | A help text header (it usually includes the application's name and version) +header :: String -> Mod Info e e +header h = Mod (\i -> i { infoHeader = Just h }) + +-- | A short description +desc :: String -> Mod Info e e +desc h = Mod (\i -> i { infoDesc = Just h }) + +-- | A help text footer (it usually includes examples) +footer :: String -> Mod Info e e +footer h = Mod (\i -> i { infoFooter = Just h }) + +-- | An error handler +handleError :: ErrorHandler e -> Mod Info x e +handleError handler = Mod (\i -> i { infoHandleError = handler }) + +defaultErrorHandler :: (Error.AsUnset e, Error.AsEmpty e, Error.AsInvalid e) => ErrorHandler e +defaultErrorHandler name err = asum [handleUnsetError name err, handleEmptyError name err, handleInvalidError name err] handleUnsetError :: Error.AsUnset e => ErrorHandler e diff --git a/src/Env/Parse.hs b/src/Env/Parse.hs index fd84faa..9ecc9ba 100644 --- a/src/Env/Parse.hs +++ b/src/Env/Parse.hs @@ -8,11 +8,6 @@ module Env.Parse , VarF(..) , parsePure , Mod(..) - , Info(..) - , defaultInfo - , header - , desc - , footer , prefixed , var , Var(..) @@ -160,32 +155,6 @@ instance Monoid (Mod t a) where mappend (Mod f) (Mod g) = Mod (g . f) --- | Parser's metadata -data Info a = Info - { infoHeader :: Maybe String - , infoDesc :: Maybe String - , infoFooter :: Maybe String - } - -defaultInfo :: Info a -defaultInfo = Info - { infoHeader = Nothing - , infoDesc = Nothing - , infoFooter = Nothing - } - --- | A help text header (it usually includes an application name and version) -header :: String -> Mod Info a -header h = Mod (\i -> i { infoHeader = Just h }) - --- | A short program description -desc :: String -> Mod Info a -desc h = Mod (\i -> i { infoDesc = Just h }) - --- | A help text footer (it usually includes examples) -footer :: String -> Mod Info a -footer h = Mod (\i -> i { infoFooter = Just h }) - -- | Environment variable metadata data Var a = Var From a3d920319d692afb89a4f8b8585385d775e7adf1 Mon Sep 17 00:00:00 2001 From: Matvey Aksenov Date: Fri, 11 Mar 2016 21:04:27 +0000 Subject: [PATCH 09/15] Drop the Category nonsense. --- example/CustomError.hs | 2 -- src/Env.hs | 7 ++++--- src/Env/Help.hs | 29 +++++++++++------------------ 3 files changed, 15 insertions(+), 23 deletions(-) diff --git a/example/CustomError.hs b/example/CustomError.hs index b43a5ab..eac48eb 100644 --- a/example/CustomError.hs +++ b/example/CustomError.hs @@ -11,11 +11,9 @@ -- Hello, foo! module Main (main) where -import Control.Category (Category(..)) import Control.Monad (replicateM_) import Env import qualified Env.Error as Error -import Prelude hiding ((.), id) import Text.Printf (printf) diff --git a/src/Env.hs b/src/Env.hs index 50a7355..3c073db 100644 --- a/src/Env.hs +++ b/src/Env.hs @@ -48,6 +48,7 @@ module Env , Parser , Mod , Help.Info + , Help.InfoMod , Help.header , Help.desc , Help.footer @@ -113,15 +114,15 @@ import Env.Error (Error, AsUnset) -- @ -- >>> parse ('header' \"env-parse 0.2.0\") ('var' 'str' \"USER\" ('def' \"nobody\")) -- @ -parse :: AsUnset e => Help.Mod Help.Info Error e -> Parser e a -> IO a +parse :: AsUnset e => Help.InfoMod Error e -> Parser e a -> IO a 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 :: AsUnset e => (String -> IO a) -> Help.Mod Help.Info Error e -> Parser e b -> IO (Either a b) -parseOr f (Help.Mod g) p = +parseOr :: AsUnset e => (String -> IO a) -> Help.InfoMod Error e -> Parser e b -> IO (Either a b) +parseOr f g p = traverseLeft (f . Help.helpInfo (g Help.defaultInfo) p) . parsePure p =<< getEnvironment die :: String -> IO a diff --git a/src/Env/Help.hs b/src/Env/Help.hs index d862ea4..f8034f8 100644 --- a/src/Env/Help.hs +++ b/src/Env/Help.hs @@ -2,7 +2,7 @@ module Env.Help ( helpInfo , helpDoc - , Mod(..) + , InfoMod , Info(..) , ErrorHandler , defaultInfo @@ -13,13 +13,11 @@ module Env.Help , handleError ) where -import Control.Category (Category(..)) import Data.Foldable (asum) import qualified Data.List as List import qualified Data.Map as Map import Data.Maybe (catMaybes, mapMaybe) import Data.Ord (comparing) -import Prelude hiding ((.), id) import Env.Error (Error) import qualified Env.Error as Error @@ -81,13 +79,8 @@ helpErrors handler fs = , List.intercalate "\n" (mapMaybe (uncurry handler) (List.sortBy (comparing varName) fs)) ] --- | This represents a modification of the properties of a particular 'Parser'. --- Combine them using the 'Monoid' instance. -newtype Mod t a b = Mod (t a -> t b) - -instance Category (Mod t) where - id = Mod id - Mod f . Mod g = Mod (f . g) +-- | This represents a modification of the properties of a particular 'Info'. +type InfoMod a b = Info a -> Info b -- | Parser's metadata data Info e = Info @@ -109,20 +102,20 @@ defaultInfo = Info } -- | A help text header (it usually includes the application's name and version) -header :: String -> Mod Info e e -header h = Mod (\i -> i { infoHeader = Just h }) +header :: String -> InfoMod e e +header h i = i {infoHeader=Just h} -- | A short description -desc :: String -> Mod Info e e -desc h = Mod (\i -> i { infoDesc = Just h }) +desc :: String -> InfoMod e e +desc h i = i {infoDesc=Just h} -- | A help text footer (it usually includes examples) -footer :: String -> Mod Info e e -footer h = Mod (\i -> i { infoFooter = Just h }) +footer :: String -> InfoMod e e +footer h i = i {infoFooter=Just h} -- | An error handler -handleError :: ErrorHandler e -> Mod Info x e -handleError handler = Mod (\i -> i { infoHandleError = handler }) +handleError :: ErrorHandler e -> InfoMod x e +handleError handler i = i {infoHandleError=handler} defaultErrorHandler :: (Error.AsUnset e, Error.AsEmpty e, Error.AsInvalid e) => ErrorHandler e defaultErrorHandler name err = From 18909a3fffe18fc05d9428be6bcc11d85a7198c7 Mon Sep 17 00:00:00 2001 From: Matvey Aksenov Date: Fri, 11 Mar 2016 21:08:02 +0000 Subject: [PATCH 10/15] Drop the InfoMod nonsense. --- src/Env.hs | 5 ++--- src/Env/Help.hs | 14 +++++--------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/Env.hs b/src/Env.hs index 3c073db..697254a 100644 --- a/src/Env.hs +++ b/src/Env.hs @@ -48,7 +48,6 @@ module Env , Parser , Mod , Help.Info - , Help.InfoMod , Help.header , Help.desc , Help.footer @@ -114,14 +113,14 @@ import Env.Error (Error, AsUnset) -- @ -- >>> parse ('header' \"env-parse 0.2.0\") ('var' 'str' \"USER\" ('def' \"nobody\")) -- @ -parse :: AsUnset e => Help.InfoMod Error e -> Parser e a -> IO a +parse :: AsUnset e => (Help.Info Error -> Help.Info e) -> Parser e a -> IO a 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 :: AsUnset e => (String -> IO a) -> Help.InfoMod Error e -> Parser e b -> IO (Either a b) +parseOr :: AsUnset e => (String -> IO a) -> (Help.Info Error -> Help.Info e) -> Parser e b -> IO (Either a b) parseOr f g p = traverseLeft (f . Help.helpInfo (g Help.defaultInfo) p) . parsePure p =<< getEnvironment diff --git a/src/Env/Help.hs b/src/Env/Help.hs index f8034f8..aee6cb1 100644 --- a/src/Env/Help.hs +++ b/src/Env/Help.hs @@ -2,8 +2,7 @@ module Env.Help ( helpInfo , helpDoc - , InfoMod - , Info(..) + , Info , ErrorHandler , defaultInfo , defaultErrorHandler @@ -79,9 +78,6 @@ helpErrors handler fs = , List.intercalate "\n" (mapMaybe (uncurry handler) (List.sortBy (comparing varName) fs)) ] --- | This represents a modification of the properties of a particular 'Info'. -type InfoMod a b = Info a -> Info b - -- | Parser's metadata data Info e = Info { infoHeader :: Maybe String @@ -102,19 +98,19 @@ defaultInfo = Info } -- | A help text header (it usually includes the application's name and version) -header :: String -> InfoMod e e +header :: String -> Info e -> Info e header h i = i {infoHeader=Just h} -- | A short description -desc :: String -> InfoMod e e +desc :: String -> Info e -> Info e desc h i = i {infoDesc=Just h} -- | A help text footer (it usually includes examples) -footer :: String -> InfoMod e e +footer :: String -> Info e -> Info e footer h i = i {infoFooter=Just h} -- | An error handler -handleError :: ErrorHandler e -> InfoMod x e +handleError :: ErrorHandler e -> Info x -> Info e handleError handler i = i {infoHandleError=handler} defaultErrorHandler :: (Error.AsUnset e, Error.AsEmpty e, Error.AsInvalid e) => ErrorHandler e From b23c6d0d25694a5743ed9902aac27137776d31ce Mon Sep 17 00:00:00 2001 From: Matvey Aksenov Date: Sat, 12 Mar 2016 09:33:58 +0000 Subject: [PATCH 11/15] Move constraints to readers. --- src/Env.hs | 4 ++-- src/Env/Error.hs | 18 +++++++++--------- src/Env/Help.hs | 10 +++++----- src/Env/Parse.hs | 37 +++++++++++++++++-------------------- test/EnvSpec.hs | 6 +++--- 5 files changed, 36 insertions(+), 39 deletions(-) diff --git a/src/Env.hs b/src/Env.hs index 697254a..8df6f29 100644 --- a/src/Env.hs +++ b/src/Env.hs @@ -113,14 +113,14 @@ import Env.Error (Error, AsUnset) -- @ -- >>> parse ('header' \"env-parse 0.2.0\") ('var' 'str' \"USER\" ('def' \"nobody\")) -- @ -parse :: AsUnset e => (Help.Info Error -> Help.Info e) -> Parser e a -> IO a +parse :: (Help.Info Error -> Help.Info e) -> Parser e a -> IO a 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 :: AsUnset e => (String -> IO a) -> (Help.Info Error -> Help.Info e) -> Parser e b -> IO (Either a b) +parseOr :: (String -> IO a) -> (Help.Info Error -> Help.Info e) -> Parser e b -> IO (Either a b) parseOr f g p = traverseLeft (f . Help.helpInfo (g Help.defaultInfo) p) . parsePure p =<< getEnvironment diff --git a/src/Env/Error.hs b/src/Env/Error.hs index 5e7a824..5cd2f18 100644 --- a/src/Env/Error.hs +++ b/src/Env/Error.hs @@ -2,14 +2,14 @@ module Env.Error ( Error(..) , AsUnset(..) , AsEmpty(..) - , AsInvalid(..) + , AsUnread(..) ) where data Error = UnsetError | EmptyError - | InvalidError String + | UnreadError String deriving (Show, Eq) class AsUnset e where @@ -34,13 +34,13 @@ instance AsEmpty Error where EmptyError -> Just () _ -> Nothing -class AsInvalid e where - invalid :: String -> e - tryInvalid :: e -> Maybe String +class AsUnread e where + unread :: String -> e + tryUnread :: e -> Maybe String -instance AsInvalid Error where - invalid = InvalidError - tryInvalid err = +instance AsUnread Error where + unread = UnreadError + tryUnread err = case err of - InvalidError msg -> Just msg + UnreadError msg -> Just msg _ -> Nothing diff --git a/src/Env/Help.hs b/src/Env/Help.hs index aee6cb1..16222e0 100644 --- a/src/Env/Help.hs +++ b/src/Env/Help.hs @@ -113,9 +113,9 @@ footer h i = i {infoFooter=Just h} handleError :: ErrorHandler e -> Info x -> Info e handleError handler i = i {infoHandleError=handler} -defaultErrorHandler :: (Error.AsUnset e, Error.AsEmpty e, Error.AsInvalid e) => ErrorHandler e +defaultErrorHandler :: (Error.AsUnset e, Error.AsEmpty e, Error.AsUnread e) => ErrorHandler e defaultErrorHandler name err = - asum [handleUnsetError name err, handleEmptyError name err, handleInvalidError name err] + asum [handleUnsetError name err, handleEmptyError name err, handleUnreadError name err] handleUnsetError :: Error.AsUnset e => ErrorHandler e handleUnsetError name = @@ -125,9 +125,9 @@ handleEmptyError :: Error.AsEmpty e => ErrorHandler e handleEmptyError name = fmap (\() -> indent 2 (name ++ " is empty")) . Error.tryEmpty -handleInvalidError :: Error.AsInvalid e => ErrorHandler e -handleInvalidError name = - fmap (\val -> indent 2 (name ++ " has the invalid value " ++ val)) . Error.tryInvalid +handleUnreadError :: Error.AsUnread e => ErrorHandler e +handleUnreadError name = + fmap (\val -> indent 2 (name ++ " has value " ++ val ++ " that cannot be parsed")) . Error.tryUnread varName :: (String, e) -> String varName (n, _) = n diff --git a/src/Env/Parse.hs b/src/Env/Parse.hs index 9ecc9ba..52ae411 100644 --- a/src/Env/Parse.hs +++ b/src/Env/Parse.hs @@ -26,6 +26,7 @@ module Env.Parse ) where import Control.Applicative +import Control.Monad ((<=<)) import Data.Map (Map) import qualified Data.Map as Map #if __GLASGOW_HASKELL__ < 710 @@ -39,23 +40,15 @@ import Env.Val -- | Try to parse a pure environment -parsePure :: Error.AsUnset e => Parser e b -> [(String, String)] -> Either [(String, e)] b +parsePure :: Parser e b -> [(String, String)] -> Either [(String, e)] b parsePure (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 env)) + go v = maybe id (\d x -> x <|> pure d) (varfDef v) (fromEither (readVar v env)) -lookupVar :: Error.AsUnset e => VarF e a -> Map String String -> Either [(String, e)] String -lookupVar VarF {varfName} = - note [(varfName, Error.unset)] . Map.lookup varfName - -readVar :: VarF e a -> String -> Either [(String, e)] a +readVar :: VarF e a -> Map String String -> Either [(String, e)] a readVar VarF {varfName, varfReader} = - mapLeft (pure . (\err -> (varfName, err))) . varfReader - -note :: a -> Maybe b -> Either a b -note a = - maybe (Left a) Right + mapLeft (pure . (\err -> (varfName, err))) . varfReader varfName mapLeft :: (a -> b) -> Either a t -> Either b t mapLeft f = @@ -82,7 +75,7 @@ prefixed pre = data VarF e a = VarF { varfName :: String - , varfReader :: Reader e a + , varfReader :: String -> Map String String -> Either e a , varfHelp :: Maybe String , varfDef :: Maybe a , varfHelpDef :: Maybe String @@ -91,15 +84,19 @@ data VarF e a = VarF -- | An environment variable's value parser. Use @(<=<)@ and @(>=>)@ to combine these type Reader e a = String -> Either e a +lookupVar :: Error.AsUnset e => String -> Map String String -> Either e String +lookupVar name = + maybe (Left Error.unset) Right . Map.lookup name + -- | Parse a particular variable from the environment -- -- @ -- >>> var 'str' \"EDITOR\" ('def' \"vim\" <> 'helpDef' show) -- @ -var :: Reader e a -> String -> Mod Var a -> Parser e a +var :: Error.AsUnset e => Reader e a -> String -> Mod Var a -> Parser e a var r n (Mod f) = Parser . liftAlt $ VarF { varfName = n - , varfReader = r + , varfReader = \name -> r <=< lookupVar name , varfHelp = varHelp , varfDef = varDef , varfHelpDef = varHelpDef <*> varDef @@ -112,13 +109,13 @@ var r n (Mod f) = Parser . liftAlt $ VarF -- -- /Note:/ this parser never fails. flag - :: forall e a. Error.AsEmpty e + :: forall e a. (Error.AsUnset e, Error.AsEmpty e) => 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 = Right . either (const f) (const t) . (nonempty :: Reader e String) + , varfReader = \name -> Right . either (const f) (const t) . (nonempty :: Reader e String) <=< lookupVar name , varfHelp = flagHelp , varfDef = Just f , varfHelpDef = Nothing @@ -129,7 +126,7 @@ flag f t n (Mod g) = Parser . liftAlt $ VarF -- | A simple boolean 'flag' -- -- /Note:/ the same caveats apply. -switch :: Error.AsEmpty e => String -> Mod Flag Bool -> Parser e Bool +switch :: (Error.AsUnset e, Error.AsEmpty e) => String -> Mod Flag Bool -> Parser e Bool switch = flag False True -- | The trivial reader @@ -141,8 +138,8 @@ nonempty :: (Error.AsEmpty e, IsString s) => Reader e s nonempty = fmap fromString . go where go [] = Left Error.empty; go xs = Right xs -- | The reader that uses the 'Read' instance of the type -auto :: (Error.AsInvalid e, Read a) => Reader e a -auto = \s -> case reads s of [(v, "")] -> Right v; _ -> Left (Error.invalid (show s)) +auto :: (Error.AsUnread e, Read a) => Reader e a +auto = \s -> case reads s of [(v, "")] -> Right v; _ -> Left (Error.unread (show s)) {-# ANN auto "HLint: ignore Redundant lambda" #-} diff --git a/test/EnvSpec.hs b/test/EnvSpec.hs index f39ccd7..dc7fc43 100644 --- a/test/EnvSpec.hs +++ b/test/EnvSpec.hs @@ -67,7 +67,7 @@ spec = context "modifiers" $ do it "the latter modifier overwrites the former" $ - p (var (\_ -> Left (Error.invalid "nope")) "never" (def 4 <> def 7)) `shouldBe` Just 7 + p (var (\_ -> Left (Error.unread "nope")) "never" (def 4 <> def 7)) `shouldBe` Just 7 it "‘prefixed’ modifier changes the names of the variables" $ p (prefixed "spec_" (var str "foo" mempty)) `shouldBe` Just "totally-not-bar" @@ -78,9 +78,9 @@ spec = Just "zygohistomorphic" -greaterThan5 :: Error.AsInvalid e => Reader e Int +greaterThan5 :: Error.AsUnread e => Reader e Int greaterThan5 s = - note (Error.invalid "fail") (do v <- readMaybe s; guard (v > 5); return v) + note (Error.unread "fail") (do v <- readMaybe s; guard (v > 5); return v) p :: Parser Error a -> Maybe a p x = hush (parsePure x fancyEnv) From 504977cec1b08a5bd37d52b385f8a032547d8227 Mon Sep 17 00:00:00 2001 From: Matvey Aksenov Date: Sat, 12 Mar 2016 10:04:52 +0000 Subject: [PATCH 12/15] Some documentation. --- src/Env/Error.hs | 18 ++++++++++++++++++ src/Env/Help.hs | 7 ++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/Env/Error.hs b/src/Env/Error.hs index 5cd2f18..1a18c0c 100644 --- a/src/Env/Error.hs +++ b/src/Env/Error.hs @@ -1,3 +1,8 @@ +-- | This module contains an extensible error infrastructure. +-- +-- Each kind of errors gets a separate type class which encodes +-- a 'Prism' (roughly a getter and a constructor). The 'Reader's, then, +-- have the constraints for precisely the set of errors they can return. module Env.Error ( Error(..) , AsUnset(..) @@ -6,12 +11,20 @@ module Env.Error ) where +-- | The type of errors returned by @envparse@'s 'Reader's. These fall into 3 +-- categories: +-- +-- * Variables that are unset in the environment. +-- * Variables whose value is empty. +-- * Variables whose value cannot be parsed using the 'Read' instance. data Error = UnsetError | EmptyError | UnreadError String deriving (Show, Eq) +-- | The class of types that contain and can be constructed from +-- the error returned from parsing unset variables. class AsUnset e where unset :: e tryUnset :: e -> Maybe () @@ -23,6 +36,8 @@ instance AsUnset Error where UnsetError -> Just () _ -> Nothing +-- | The class of types that contain and can be constructed from +-- the error returned from parsing variables whose value is empty. class AsEmpty e where empty :: e tryEmpty :: e -> Maybe () @@ -34,6 +49,9 @@ instance AsEmpty Error where EmptyError -> Just () _ -> Nothing +-- | The class of types that contain and can be constructed from +-- the error returned from parsing variable whose value cannot +-- be parsed using the 'Read' instance. class AsUnread e where unread :: String -> e tryUnread :: e -> Maybe String diff --git a/src/Env/Help.hs b/src/Env/Help.hs index 16222e0..b6884bc 100644 --- a/src/Env/Help.hs +++ b/src/Env/Help.hs @@ -97,15 +97,15 @@ defaultInfo = Info , infoHandleError = defaultErrorHandler } --- | A help text header (it usually includes the application's name and version) +-- | Set the help text header (it usually includes the application's name and version) header :: String -> Info e -> Info e header h i = i {infoHeader=Just h} --- | A short description +-- | Set the short description desc :: String -> Info e -> Info e desc h i = i {infoDesc=Just h} --- | A help text footer (it usually includes examples) +-- | Set the help text footer (it usually includes examples) footer :: String -> Info e -> Info e footer h i = i {infoFooter=Just h} @@ -113,6 +113,7 @@ footer h i = i {infoFooter=Just h} handleError :: ErrorHandler e -> Info x -> Info e handleError handler i = i {infoHandleError=handler} +-- | The default error handler defaultErrorHandler :: (Error.AsUnset e, Error.AsEmpty e, Error.AsUnread e) => ErrorHandler e defaultErrorHandler name err = asum [handleUnsetError name err, handleEmptyError name err, handleUnreadError name err] From ffb4e09868c7151559e220a8729d7f003bd02335 Mon Sep 17 00:00:00 2001 From: Matvey Aksenov Date: Sat, 12 Mar 2016 10:06:30 +0000 Subject: [PATCH 13/15] Fix the example. --- example/CustomError.hs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/example/CustomError.hs b/example/CustomError.hs index eac48eb..a8e5a61 100644 --- a/example/CustomError.hs +++ b/example/CustomError.hs @@ -67,10 +67,10 @@ instance Error.AsEmpty CustomError where EnvError err' -> Error.tryEmpty err' _ -> Nothing -instance Error.AsInvalid CustomError where - invalid = - EnvError . Error.invalid - tryInvalid err = +instance Error.AsUnread CustomError where + unread = + EnvError . Error.unread + tryUnread err = case err of - EnvError err' -> Error.tryInvalid err' + EnvError err' -> Error.tryUnread err' _ -> Nothing From 9dacabc99b649f804a5a6abb44928e0c55722691 Mon Sep 17 00:00:00 2001 From: Matvey Aksenov Date: Sat, 12 Mar 2016 10:19:58 +0000 Subject: [PATCH 14/15] Cosmetics. --- envparse.cabal | 2 +- src/Env.hs | 13 ++++++------- src/Env/Mon.hs | 6 ------ test/EnvSpec.hs | 14 ++++++++------ 4 files changed, 15 insertions(+), 20 deletions(-) delete mode 100644 src/Env/Mon.hs diff --git a/envparse.cabal b/envparse.cabal index 408a47c..615dcf1 100644 --- a/envparse.cabal +++ b/envparse.cabal @@ -77,8 +77,8 @@ library src exposed-modules: Env - Env.Error other-modules: + Env.Error Env.Free Env.Help Env.Parse diff --git a/src/Env.hs b/src/Env.hs index 8df6f29..472a23e 100644 --- a/src/Env.hs +++ b/src/Env.hs @@ -69,15 +69,13 @@ module Env , HasHelp , help , Help.helpDoc - , Error - , AsUnset + , Error(..) + , AsUnset(..) + , Error.AsEmpty(..) + , Error.AsUnread(..) -- * Re-exports -- $re-exports - , pure, (<$>), (<*>), (*>), (<*), optional - , empty, (<|>) - , (<=<), (>=>) - , (<>), mempty, mconcat - , asum + , optional, (<=<), (>=>), (<>), asum -- * Testing -- $testing , parsePure @@ -98,6 +96,7 @@ import qualified System.IO as IO import qualified Env.Help as Help import Env.Parse import Env.Error (Error, AsUnset) +import qualified Env.Error as Error -- $re-exports -- External functions that may be useful to the consumer of the library diff --git a/src/Env/Mon.hs b/src/Env/Mon.hs deleted file mode 100644 index 05c395b..0000000 --- a/src/Env/Mon.hs +++ /dev/null @@ -1,6 +0,0 @@ --- | This module provides 'Mon', the 'Alternative' functor --- induced by an instance of the 'Monoid' typeclass -module Env.Mon where - -import Control.Applicative (Applicative(..), Alternative(..)) -import Data.Monoid (Monoid(..)) diff --git a/test/EnvSpec.hs b/test/EnvSpec.hs index dc7fc43..426ca1f 100644 --- a/test/EnvSpec.hs +++ b/test/EnvSpec.hs @@ -9,7 +9,6 @@ import Test.Hspec import Text.Read (readMaybe) import Env -import qualified Env.Error as Error default (Integer, Double, String) @@ -17,8 +16,11 @@ default (Integer, Double, String) spec :: Spec spec = describe "parsing" $ do - it "parsing the environment with the noop parser always fails" $ - p empty `shouldBe` Nothing + it "parsing the environment with the noop parser always succeeds" $ + p (pure ()) `shouldBe` Just () + + it "parsing the environment with the failing parser always fails" $ + p Control.Applicative.empty `shouldBe` Nothing it "looking for the non-existing env var fails" $ p (var str "xyzzy" mempty) `shouldBe` Nothing @@ -67,7 +69,7 @@ spec = context "modifiers" $ do it "the latter modifier overwrites the former" $ - p (var (\_ -> Left (Error.unread "nope")) "never" (def 4 <> def 7)) `shouldBe` Just 7 + p (var (\_ -> Left (unread "nope")) "never" (def 4 <> def 7)) `shouldBe` Just 7 it "‘prefixed’ modifier changes the names of the variables" $ p (prefixed "spec_" (var str "foo" mempty)) `shouldBe` Just "totally-not-bar" @@ -78,9 +80,9 @@ spec = Just "zygohistomorphic" -greaterThan5 :: Error.AsUnread e => Reader e Int +greaterThan5 :: AsUnread e => Reader e Int greaterThan5 s = - note (Error.unread "fail") (do v <- readMaybe s; guard (v > 5); return v) + note (unread "fail") (do v <- readMaybe s; guard (v > 5); return v) p :: Parser Error a -> Maybe a p x = hush (parsePure x fancyEnv) From b8f33c67e3db050b153266ec93a5cf1f78f108f1 Mon Sep 17 00:00:00 2001 From: Matvey Aksenov Date: Sat, 12 Mar 2016 10:21:43 +0000 Subject: [PATCH 15/15] More cosmetics. --- src/Env.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Env.hs b/src/Env.hs index 472a23e..cb2d92e 100644 --- a/src/Env.hs +++ b/src/Env.hs @@ -70,7 +70,7 @@ module Env , help , Help.helpDoc , Error(..) - , AsUnset(..) + , Error.AsUnset(..) , Error.AsEmpty(..) , Error.AsUnread(..) -- * Re-exports @@ -95,7 +95,7 @@ import qualified System.IO as IO import qualified Env.Help as Help import Env.Parse -import Env.Error (Error, AsUnset) +import Env.Error (Error) import qualified Env.Error as Error -- $re-exports