config-schema 0.1.0.0 → 0.2.0.0
raw patch · 5 files changed
+109/−41 lines, 5 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Config.Schema.Load: instance GHC.Base.Alternative Config.Schema.Load.Load
- Config.Schema.Load: instance GHC.Base.MonadPlus Config.Schema.Load.Load
+ Config.Schema.Load: instance Data.Functor.Alt.Alt Config.Schema.Load.Load
+ Config.Schema.Spec: fractionalSpec :: Fractional a => ValueSpecs a
+ Config.Schema.Spec: liftSectionSpec :: SectionSpec a -> SectionSpecs a
+ Config.Schema.Spec: liftValueSpec :: ValueSpec a -> ValueSpecs a
+ Config.Schema.Spec: nonemptySpec :: ValueSpecs a -> ValueSpecs (NonEmpty a)
+ Config.Schema.Spec: oneOrNonemptySpec :: ValueSpecs a -> ValueSpecs (NonEmpty a)
- Config.Schema.Load: loadValue :: ValueSpecs a -> Value -> Either [LoadError] a
+ Config.Schema.Load: loadValue :: ValueSpecs a -> Value -> Either (NonEmpty LoadError) a
Files
- ChangeLog.md +8/−1
- config-schema.cabal +1/−1
- src/Config/Schema.hs +4/−0
- src/Config/Schema/Load.hs +27/−17
- src/Config/Schema/Spec.hs +69/−22
ChangeLog.md view
@@ -1,5 +1,12 @@ # Revision history for config-schema -## 0.1.0.0 -- YYYY-mm-dd+## 0.2.0.0 -- 2017-05-07++* Expose `liftValueSpec` and `liftSectionSpec`+* Add `fractionalSpec`+* Add `nonemptySpec` and `oneOrNonemptySpec`+* `loadValue` returns a `NonEmpty LoadError`++## 0.1.0.0 -- 2017-05-06 * First version. Released on an unsuspecting world.
config-schema.cabal view
@@ -1,5 +1,5 @@ name: config-schema-version: 0.1.0.0+version: 0.2.0.0 synopsis: Schema definitions for the config-value package description: This package makes it possible to defined schemas for use when loading configuration files using the config-value format.
src/Config/Schema.hs view
@@ -9,6 +9,10 @@ These schemas can be used to generate a validating configuration file loader, and to produce documentation about the supported format. +For documentation on the+<https://hackage.haskell.org/package/config-value config-value> file format, see the+<https://hackage.haskell.org/package/config-value/docs/Config.html Config> module.+ "Config.Schema.Spec" provides definitions used to make new schemas. "Config.Schema.Load" uses schemas to match schemas against configuration values.
src/Config/Schema/Load.hs view
@@ -18,15 +18,16 @@ , Problem(..) ) where -import Control.Applicative (Alternative, optional)-import Control.Monad (MonadPlus, unless, zipWithM)+import Control.Monad (unless, zipWithM) import Control.Monad.Trans.Class (lift) import Control.Monad.Trans.State (StateT(..), runStateT)-import Control.Monad.Trans.Except-import Control.Monad.Trans.Reader-import Data.Foldable (asum)+import Control.Monad.Trans.Except (Except, runExcept, throwE)+import Control.Monad.Trans.Reader (ReaderT, runReaderT, ask, local)+import Data.Functor.Alt (Alt((<!>))) import Data.Monoid ((<>)) import Data.Ratio (numerator, denominator)+import Data.Semigroup.Foldable (asum1)+import Data.List.NonEmpty (NonEmpty) import Data.Text (Text) import qualified Data.Text as Text @@ -38,9 +39,9 @@ -- the interpretation of that value or the list of errors -- encountered. loadValue ::- ValueSpecs a {- ^ specification -} ->- Value {- ^ value -} ->- Either [LoadError] a {- ^ error or decoded value -}+ ValueSpecs a {- ^ specification -} ->+ Value {- ^ value -} ->+ Either (NonEmpty LoadError) a {- ^ errors or decoded value -} loadValue spec val = runLoad (getValue spec val) @@ -49,7 +50,7 @@ do v <- StateT (lookupSection k) lift (scope k (getValue w v)) getSection (OptSection k _ w) =- do mb <- optional (StateT (lookupSection k))+ do mb <- optional1 (StateT (lookupSection k)) lift (traverse (scope k . getValue w) mb) @@ -61,7 +62,7 @@ getValue :: ValueSpecs a -> Value -> Load a-getValue s v = asum (runValueSpecs (getValue1 v) s)+getValue s v = asum1 (runValueSpecs (getValue1 v) s) -- | Match a primitive value specification against a single value.@@ -80,7 +81,7 @@ getValue1 _ TextSpec = loadFail (SpecMismatch "text") getValue1 _ IntegerSpec = loadFail (SpecMismatch "integer")-getValue1 _ RationalSpec = loadFail (SpecMismatch "rational")+getValue1 _ RationalSpec = loadFail (SpecMismatch "number") getValue1 _ ListSpec{} = loadFail (SpecMismatch "list") getValue1 _ AnyAtomSpec = loadFail (SpecMismatch "atom") getValue1 _ (AtomSpec a) = loadFail (SpecMismatch ("`" <> a <> "`"))@@ -109,8 +110,8 @@ -- | Extract a section from a list of sections by name. lookupSection ::- Text {- ^ section name -} ->- [Section] {- ^ available sections -} ->+ Text {- ^ section name -} ->+ [Section] {- ^ available sections -} -> Load (Value, [Section]) {- ^ found value and remaining sections -} lookupSection key [] = loadFail (MissingSection key) lookupSection key (s@(Section k v):xs)@@ -140,9 +141,11 @@ -- | Type used to match values against specifiations. This type tracks -- the current nested fields (updated with scope) and can throw -- errors using loadFail.-newtype Load a = MkLoad { unLoad :: ReaderT [Text] (Except [LoadError]) a }- deriving (Functor, Applicative, Monad, Alternative, MonadPlus)+newtype Load a = MkLoad { unLoad :: ReaderT [Text] (Except (NonEmpty LoadError)) a }+ deriving (Functor, Applicative, Monad) +instance Alt Load where MkLoad x <!> MkLoad y = MkLoad (x <!> y)+ -- | Type for errors that can be encountered while decoding a value according -- to a specification. The error includes a key path indicating where in -- the configuration file the error occurred.@@ -152,7 +155,7 @@ -- | Run the Load computation until it produces a result or terminates -- with a list of errors.-runLoad :: Load a -> Either [LoadError] a+runLoad :: Load a -> Either (NonEmpty LoadError) a runLoad = runExcept . flip runReaderT [] . unLoad @@ -171,4 +174,11 @@ loadFail :: Problem -> Load a loadFail cause = MkLoad $ do path <- ask- lift (throwE [LoadError (reverse path) cause])+ lift (throwE (pure (LoadError (reverse path) cause)))++------------------------------------------------------------------------++-- | One or none. This definition is different from the normal @optional@ definition+-- because it uses 'Alt'. This allows it to work on types that are not @Alternative@.+optional1 :: (Applicative f, Alt f) => f a -> f (Maybe a)+optional1 fa = Just <$> fa <!> pure Nothing
src/Config/Schema/Spec.hs view
@@ -1,5 +1,5 @@ {-# Language FlexibleInstances, RankNTypes, GADTs, KindSignatures #-}-{-# Language GeneralizedNewtypeDeriving #-}+{-# Language GeneralizedNewtypeDeriving, OverloadedStrings #-} {-| Module : Config.Schema.Spec@@ -40,9 +40,12 @@ -- * Derived specifications , oneOrList- , numSpec , yesOrNoSpec , stringSpec+ , numSpec+ , fractionalSpec+ , nonemptySpec+ , oneOrNonemptySpec -- * Executing specifications , runSections@@ -52,7 +55,9 @@ -- * Primitive specifications , SectionSpec(..)+ , liftSectionSpec , ValueSpec(..)+ , liftValueSpec ) where @@ -62,6 +67,7 @@ import Data.Functor.Compose (Compose(..), getCompose) import Data.Functor.Alt (Alt(..)) import Data.List.NonEmpty (NonEmpty)+import qualified Data.List.NonEmpty as NonEmpty import Data.Text (Text) import qualified Data.Text as Text @@ -91,14 +97,32 @@ -- | Lift a single specification into a list of specifications.-sectionSpec :: SectionSpec a -> SectionSpecs a-sectionSpec = MkSectionSpecs . liftAp+--+-- @since 0.1.1.0+liftSectionSpec :: SectionSpec a -> SectionSpecs a+liftSectionSpec = MkSectionSpecs . liftAp +-- | Given an function that handles a single, primitive section specification;+-- 'runSections' will generate one that processes a whole 'SectionsSpec'.+--+-- The results from each section will be sequence together using the 'Applicative'+-- instance in of the result type, and the results can be indexed by the type+-- parameter of the specification.+--+-- For an example use of 'runSections', see "Config.Schema.Load". runSections :: Applicative f => (forall x. SectionSpec x -> f x) -> SectionSpecs a -> f a runSections f (MkSectionSpecs s) = runAp f s +-- | Given an function that handles a single, primitive section specification;+-- 'runSections_' will generate one that processes a whole 'SectionsSpec'.+--+-- The results from each section will be sequence together using the 'Monoid'+-- instance in of the result type, and the results will not be indexed by the+-- type parameter of the specifications.+--+-- For an example use of 'runSections_', see "Config.Schema.Docs". runSections_ :: Monoid m => (forall x. SectionSpec x -> m) -> SectionSpecs a -> m runSections_ f (MkSectionSpecs s) = runAp_ f s @@ -114,7 +138,7 @@ Text {- ^ section name -} -> Text {- ^ description -} -> SectionSpecs a-reqSection n i = sectionSpec (ReqSection n i valuesSpec)+reqSection n i = liftSectionSpec (ReqSection n i valuesSpec) -- | Specification for a required section with an explicit value specification.@@ -123,7 +147,7 @@ Text {- ^ description -} -> ValueSpecs a {- ^ value specification -} -> SectionSpecs a-reqSection' n i w = sectionSpec (ReqSection n i w)+reqSection' n i w = liftSectionSpec (ReqSection n i w) -- | Specification for an optional section with an implicit value specification.@@ -132,7 +156,7 @@ Text {- ^ section name -} -> Text {- ^ description -} -> SectionSpecs (Maybe a)-optSection n i = sectionSpec (OptSection n i valuesSpec)+optSection n i = liftSectionSpec (OptSection n i valuesSpec) -- | Specification for an optional section with an explicit value specification.@@ -141,7 +165,7 @@ Text {- ^ description -} -> ValueSpecs a {- ^ value specification -} -> SectionSpecs (Maybe a)-optSection' n i w = sectionSpec (OptSection n i w)+optSection' n i w = liftSectionSpec (OptSection n i w) ------------------------------------------------------------------------@@ -161,7 +185,7 @@ RationalSpec :: ValueSpec Rational -- | Matches any atom- AnyAtomSpec :: ValueSpec Text+ AnyAtomSpec :: ValueSpec Text -- | Specific atom to be matched AtomSpec :: Text -> ValueSpec ()@@ -182,6 +206,8 @@ -- | Non-empty disjunction of value specifications. This type is the primary -- way to specify expected values. Use the 'Spec' class to generate 'ValueSpecs' -- for simple types.+--+-- Multiple specifications can be combined using this type's 'Alt' instance. newtype ValueSpecs a = MkValueSpecs { unValueSpecs :: Compose NonEmpty (Coyoneda ValueSpec) a } deriving Functor @@ -195,7 +221,7 @@ -- Unlike 'runValueSpecs_', this allows the result of the interpretation to be indexed -- by the type of the primitive value specifications. runValueSpecs :: Functor f => (forall x. ValueSpec x -> f x) -> ValueSpecs a -> NonEmpty (f a)-runValueSpecs f = fmap (lowerCoyoneda . hoistCoyoneda f) . getCompose . unValueSpecs+runValueSpecs f = fmap (lowerCoyoneda . hoistCoyoneda f) . getCompose . unValueSpecs -- | Given an interpretation of a primitive value specification, extract a list of@@ -205,8 +231,10 @@ -- | Lift a primitive value specification to 'ValueSpecs'.-valueSpec :: ValueSpec a -> ValueSpecs a-valueSpec = MkValueSpecs . Compose . pure . liftCoyoneda+--+-- @since 0.1.1.0+liftValueSpec :: ValueSpec a -> ValueSpecs a+liftValueSpec = MkValueSpecs . Compose . pure . liftCoyoneda ------------------------------------------------------------------------@@ -216,22 +244,22 @@ -- | Class of value specifications that don't require arguments. class Spec a where valuesSpec :: ValueSpecs a-instance Spec Text where valuesSpec = valueSpec TextSpec-instance Spec Integer where valuesSpec = valueSpec IntegerSpec-instance Spec Rational where valuesSpec = valueSpec RationalSpec+instance Spec Text where valuesSpec = liftValueSpec TextSpec+instance Spec Integer where valuesSpec = liftValueSpec IntegerSpec+instance Spec Rational where valuesSpec = liftValueSpec RationalSpec instance Spec Int where valuesSpec = fromInteger <$> valuesSpec-instance Spec a => Spec [a] where valuesSpec = valueSpec (ListSpec valuesSpec)+instance Spec a => Spec [a] where valuesSpec = liftValueSpec (ListSpec valuesSpec) instance (Spec a, Spec b) => Spec (Either a b) where valuesSpec = Left <$> valuesSpec <!> Right <$> valuesSpec -- | Specification for matching a particular atom. atomSpec :: Text -> ValueSpecs ()-atomSpec = valueSpec . AtomSpec+atomSpec = liftValueSpec . AtomSpec -- | Specification for matching any atom. Matched atom is returned. anyAtomSpec :: ValueSpecs Text-anyAtomSpec = valueSpec AnyAtomSpec+anyAtomSpec = liftValueSpec AnyAtomSpec -- | Specification for matching any text as a 'String' stringSpec :: ValueSpecs String@@ -241,10 +269,16 @@ numSpec :: Num a => ValueSpecs a numSpec = fromInteger <$> valuesSpec +-- | Specification for matching any fractional number.+--+-- @since 0.1.1.0+fractionalSpec :: Fractional a => ValueSpecs a+fractionalSpec = fromRational <$> valuesSpec+ -- | Specification for matching a list of values each satisfying a -- given element specification. listSpec :: ValueSpecs a -> ValueSpecs [a]-listSpec = valueSpec . ListSpec+listSpec = liftValueSpec . ListSpec -- | Named subsection value specification. The unique identifier will be used@@ -254,7 +288,7 @@ Text {- ^ unique documentation identifier -} -> SectionSpecs a {- ^ underlying specification -} -> ValueSpecs a-sectionsSpec i s = valueSpec (SectionSpecs i s)+sectionsSpec i s = liftValueSpec (SectionSpecs i s) -- | Named value specification. This is useful for factoring complicated@@ -264,7 +298,7 @@ Text {- ^ name -} -> ValueSpecs a {- ^ underlying specification -} -> ValueSpecs a-namedSpec n s = valueSpec (NamedSpec n s)+namedSpec n s = liftValueSpec (NamedSpec n s) -- | Specification that matches either a single element or multiple@@ -278,7 +312,7 @@ -- to validate the value extracted by a specification. If 'Nothing' -- is returned the value is considered to have failed validation. customSpec :: Text -> ValueSpecs a -> (a -> Maybe b) -> ValueSpecs b-customSpec lbl w f = valueSpec (CustomSpec lbl (f <$> w))+customSpec lbl w f = liftValueSpec (CustomSpec lbl (f <$> w)) -- | Specification for using @yes@ and @no@ to represent booleans 'True'@@ -286,6 +320,19 @@ yesOrNoSpec :: ValueSpecs Bool yesOrNoSpec = True <$ atomSpec (Text.pack "yes") <!> False <$ atomSpec (Text.pack "no")+++-- | Matches a non-empty list.+--+-- @since 0.1.1.0+nonemptySpec :: ValueSpecs a -> ValueSpecs (NonEmpty a)+nonemptySpec s = customSpec "nonempty" (listSpec s) NonEmpty.nonEmpty++-- | Matches a single element or a non-empty list.+--+-- @since 0.1.1.0+oneOrNonemptySpec :: ValueSpecs a -> ValueSpecs (NonEmpty a)+oneOrNonemptySpec s = pure <$> s <!> nonemptySpec s ------------------------------------------------------------------------