    {-# Language ScopedTypeVariables, OverloadedStrings #-}

    {-|
    Module      : Config.Schema.Spec
    Description : Operations for describing a configuration file format.
    Copyright   : (c) Eric Mertens, 2017
    License     : ISC
    Maintainer  : emertens@gmail.com

    This module provides a set of types and operations for defining configuration
    file schemas.

    These specifications are can be consumed by "Config.Schema.Load"
    and "Config.Schema.Docs".

    This is the schema system used by the @glirc@ IRC client
    <https://hackage.haskell.org/package/glirc>. For a significant example,
    visit the @Client.Configuration@ and @Client.Configuration.Colors@ modules.

    -}
    module Config.Schema.Spec
      (
      -- * Specifying values
      -- $values

        ValueSpec
      , customSpec
      , namedSpec
      , exactSpec
      , HasSpec(..)

      -- ** Key-value mapping specifications
      -- $sections
      , sectionsSpec
      , assocSpec

      -- ** Number specifications
      -- $number
      , numberSpec
      , integerSpec
      , rationalSpec
      , naturalSpec
      , fractionalSpec
      , numSpec

      -- ** Text specifications
      -- $text
      , textSpec
      , stringSpec

      -- ** Atom specifications
      -- $atom
      , atomSpec
      , anyAtomSpec
      , yesOrNoSpec
      , trueOrFalseSpec

      -- ** List specifications
      -- $list
      , listSpec
      , oneOrList
      , nonemptySpec
      , oneOrNonemptySpec

      -- * Specifying sections
      -- $sectionsspec
      , SectionsSpec
      , reqSection
      , optSection
      , reqSection'
      , optSection'

      -- * Re-exports
      , Alt(..)

      ) where

    import           Data.Bits                        (FiniteBits, isSigned, toIntegralSized, finiteBitSize)
    import           Data.Functor.Alt                 (Alt(..))
    import           Data.Int
    import           Data.List.NonEmpty               (NonEmpty)
    import qualified Data.List.NonEmpty as NonEmpty
    import           Data.Text                        (Text)
    import qualified Data.Text as Text
    import           Data.Word
    import           Data.Ratio
    import           GHC.Natural                      (Natural)

    import           Config.Schema.Types
    import           Config (Value(..), Atom(..))
    import           Config.Number (Number, numberToInteger, numberToRational)

    ------------------------------------------------------------------------
    -- 'ValueSpec' builders
    ------------------------------------------------------------------------

    -- $values
    --
    -- 'ValueSpec' allows you to define specifications that will match
    -- parsed config-value configuration files. 'ValueSpec' allows
    -- us to define the shape of configuration values that will match
    -- the specification as well as a way to process those matches.
    --
    -- Below we have an example configuration record that can be matched
    -- from a configuration file.
    --
    -- More documentation for defining key-value pairs is available below.
    --
    -- This configuration file expects either a given username or allows
    -- the user to ask for a random username. The ('<!>') operator allows
    -- us to combine two alternatives as seen below. The config-value
    -- language distinguishes between atoms like @random@ and strings like
    -- @"random"@ allowing unambiguous special cases to be added in addition
    -- to free-form text.
    --
    -- @
    -- {-\# Language RecordWildCards, OverloadedStrings, ApplicativeDo \#-}
    -- module Example where
    --
    -- import "Config.Schema"
    -- import "Data.Functor.Alt" (('<!>'))
    -- import "Data.Maybe"       ('Data.Maybe.fromMaybe')
    -- import "Data.Text"        ('Text')
    --
    -- data Config = Config
    --   { userName :: UserName
    --   , retries  :: 'Int'
    --   }
    --
    -- data UserName = Random | Given 'Text'
    --
    -- userNameSpec :: ValueSpec UserName
    -- userNameSpec = Random '<$'  'atomSpec' \"random\"
    --            '<!>' Given  '<$>' 'anySpec' -- matches string literals
    --
    -- nameExample :: 'ValueSpec' Config
    -- nameExample = 'sectionsSpec' \"config\" '$'
    --
    --   do userName <- 'reqSection'' \"username\" userNameSpec \"Configured user name\"
    --
    --      retries  <- 'Data.Maybe.fromMaybe' 3
    --              '<$>' 'optSection' \"retries\" \"Number of attempts (default: 3)\"
    --
    --      'pure' Config{..}
    -- @
    --
    -- Examples:
    --
    -- > username: random
    -- > retries: 5
    -- > -- Generates: Config { userName = Random, retries = 5 }
    --
    -- We can omit the retries:
    --
    -- > username: random
    -- > -- Generates: Config { userName = Random, retries = 3 }
    --
    -- We can specify a specific username as a string literal instead
    -- of using the atom @random@:
    --
    -- > username: "me"
    -- > -- Generates: Config { userName = Given "me", retries = 3 }
    --
    -- Sections can be reordered:
    --
    -- > retries: 5
    -- > username: random
    -- > -- Generates: Config { userName = Random, retries = 5 }

    -- | Class of value specifications without parameters.
    class    HasSpec a        where anySpec :: ValueSpec a
    instance HasSpec Text     where anySpec :: ValueSpec Text
    anySpec = ValueSpec Text
    textSpec
    instance HasSpec Integer  where anySpec :: ValueSpec Integer
    anySpec = ValueSpec Integer
    integerSpec
    instance HasSpec Int      where anySpec :: ValueSpec Int
    anySpec = ValueSpec Int
    forall a. (Integral a, FiniteBits a) => ValueSpec a
    sizedBitsSpec
    instance HasSpec Int8     where anySpec :: ValueSpec Int8
    anySpec = ValueSpec Int8
    forall a. (Integral a, FiniteBits a) => ValueSpec a
    sizedBitsSpec
    instance HasSpec Int16    where anySpec :: ValueSpec Int16
    anySpec = ValueSpec Int16
    forall a. (Integral a, FiniteBits a) => ValueSpec a
    sizedBitsSpec
    instance HasSpec Int32    where anySpec :: ValueSpec Int32
    anySpec = ValueSpec Int32
    forall a. (Integral a, FiniteBits a) => ValueSpec a
    sizedBitsSpec
    instance HasSpec Int64    where anySpec :: ValueSpec Int64
    anySpec = ValueSpec Int64
    forall a. (Integral a, FiniteBits a) => ValueSpec a
    sizedBitsSpec
    instance HasSpec Word     where anySpec :: ValueSpec Word
    anySpec = ValueSpec Word
    forall a. (Integral a, FiniteBits a) => ValueSpec a
    sizedBitsSpec
    instance HasSpec Word8    where anySpec :: ValueSpec Word8
    anySpec = ValueSpec Word8
    forall a. (Integral a, FiniteBits a) => ValueSpec a
    sizedBitsSpec
    instance HasSpec Word16   where anySpec :: ValueSpec Word16
    anySpec = ValueSpec Word16
    forall a. (Integral a, FiniteBits a) => ValueSpec a
    sizedBitsSpec
    instance HasSpec Word32   where anySpec :: ValueSpec Word32
    anySpec = ValueSpec Word32
    forall a. (Integral a, FiniteBits a) => ValueSpec a
    sizedBitsSpec
    instance HasSpec Word64   where anySpec :: ValueSpec Word64
    anySpec = ValueSpec Word64
    forall a. (Integral a, FiniteBits a) => ValueSpec a
    sizedBitsSpec

    -- | @since 1.2.0.0
    instance HasSpec Natural  where anySpec :: ValueSpec Natural
    anySpec = ValueSpec Natural
    naturalSpec

    -- | @since 1.2.0.0
    instance HasSpec Double   where anySpec :: ValueSpec Double
    anySpec = ValueSpec Double
    forall a. Fractional a => ValueSpec a
    fractionalSpec

    -- | @since 1.2.0.0
    instance HasSpec Float    where anySpec :: ValueSpec Float
    anySpec = ValueSpec Float
    forall a. Fractional a => ValueSpec a
    fractionalSpec

    -- | For 'Ratio' and 'Rational'
    --
    -- @since 1.2.0.0
    instance Integral a => HasSpec (Ratio a) where
      anySpec :: ValueSpec (Ratio a)
    anySpec = ValueSpec (Ratio a)
    forall a. Fractional a => ValueSpec a
    fractionalSpec

    -- | Zero or more elements in a list
    instance HasSpec a => HasSpec [a] where
      anySpec :: ValueSpec [a]
    anySpec = ValueSpec a -> ValueSpec [a]
    forall a. ValueSpec a -> ValueSpec [a]
    listSpec ValueSpec a
    forall a. HasSpec a => ValueSpec a
    anySpec

    -- | One or more elements in a list
    --
    -- @since 1.2.0.0
    instance HasSpec a => HasSpec (NonEmpty a) where
      anySpec :: ValueSpec (NonEmpty a)
    anySpec = ValueSpec a -> ValueSpec (NonEmpty a)
    forall a. ValueSpec a -> ValueSpec (NonEmpty a)
    nonemptySpec ValueSpec a
    forall a. HasSpec a => ValueSpec a
    anySpec

    -- | Left-biased, untagged union of specs
    instance (HasSpec a, HasSpec b) => HasSpec (Either a b) where
      anySpec :: ValueSpec (Either a b)
    anySpec = a -> Either a b
    forall a b. a -> Either a b
    Left (a -> Either a b) -> ValueSpec a -> ValueSpec (Either a b)
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> ValueSpec a
    forall a. HasSpec a => ValueSpec a
    anySpec ValueSpec (Either a b)
    -> ValueSpec (Either a b) -> ValueSpec (Either a b)
    forall a. ValueSpec a -> ValueSpec a -> ValueSpec a
    forall (f :: * -> *) a. Alt f => f a -> f a -> f a
    <!> b -> Either a b
    forall a b. b -> Either a b
    Right (b -> Either a b) -> ValueSpec b -> ValueSpec (Either a b)
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> ValueSpec b
    forall a. HasSpec a => ValueSpec a
    anySpec

    -- | Named value specification. This is useful for factoring complicated
    -- value specifications out in the documentation to avoid repetition of
    -- complex specifications.
    namedSpec ::
      Text        {- ^ name                     -} ->
      ValueSpec a {- ^ underlying specification -} ->
      ValueSpec a
    namedSpec :: forall a. Text -> ValueSpec a -> ValueSpec a
    namedSpec Text
    n ValueSpec a
    s = PrimValueSpec a -> ValueSpec a
    forall a. PrimValueSpec a -> ValueSpec a
    primValueSpec (Text -> ValueSpec a -> PrimValueSpec a
    forall a. Text -> ValueSpec a -> PrimValueSpec a
    NamedSpec Text
    n ValueSpec a
    s)

    -- | The custom specification allows an arbitrary function to be used
    -- to validate the value extracted by a specification. If 'Nothing'
    -- is returned the value is considered to have failed validation.
    customSpec :: Text -> ValueSpec a -> (a -> Either Text b) -> ValueSpec b
    customSpec :: forall a b.
    Text -> ValueSpec a -> (a -> Either Text b) -> ValueSpec b
    customSpec Text
    lbl ValueSpec a
    w a -> Either Text b
    f = PrimValueSpec b -> ValueSpec b
    forall a. PrimValueSpec a -> ValueSpec a
    primValueSpec (Text -> ValueSpec (Either Text b) -> PrimValueSpec b
    forall a. Text -> ValueSpec (Either Text a) -> PrimValueSpec a
    CustomSpec Text
    lbl (a -> Either Text b
    f (a -> Either Text b) -> ValueSpec a -> ValueSpec (Either Text b)
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> ValueSpec a
    w))

    -- | Match an exact value. This can be used to match a specific text
    -- literal number literal, atom, list of exact things, etc.
    exactSpec :: Value () -> ValueSpec ()
    exactSpec :: Value () -> ValueSpec ()
    exactSpec = PrimValueSpec () -> ValueSpec ()
    forall a. PrimValueSpec a -> ValueSpec a
    primValueSpec (PrimValueSpec () -> ValueSpec ())
    -> (Value () -> PrimValueSpec ()) -> Value () -> ValueSpec ()
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Value () -> PrimValueSpec ()
    ExactSpec

    ------------------------------------------------------------------------

    -- $sections
    -- Specifications that match key-value map literals.

    -- | Named subsection value specification. The unique identifier will be used
    -- for generating a documentation section for this specification and should
    -- be unique within the scope of the specification being built.
    sectionsSpec ::
      Text           {- ^ unique documentation identifier -} ->
      SectionsSpec a {- ^ underlying specification        -} ->
      ValueSpec a
    sectionsSpec :: forall a. Text -> SectionsSpec a -> ValueSpec a
    sectionsSpec Text
    i SectionsSpec a
    s = PrimValueSpec a -> ValueSpec a
    forall a. PrimValueSpec a -> ValueSpec a
    primValueSpec (Text -> SectionsSpec a -> PrimValueSpec a
    forall a. Text -> SectionsSpec a -> PrimValueSpec a
    SectionsSpec Text
    i SectionsSpec a
    s)

    -- | Specification for a section list where the keys are user-defined.
    -- Values are matched against the underlying specification and returned
    -- as a list of section-name\/value pairs.
    --
    -- @since 0.3.0.0
    assocSpec ::
      ValueSpec a {- ^ underlying specification -} ->
      ValueSpec [(Text,a)]
    assocSpec :: forall a. ValueSpec a -> ValueSpec [(Text, a)]
    assocSpec = PrimValueSpec [(Text, a)] -> ValueSpec [(Text, a)]
    forall a. PrimValueSpec a -> ValueSpec a
    primValueSpec (PrimValueSpec [(Text, a)] -> ValueSpec [(Text, a)])
    -> (ValueSpec a -> PrimValueSpec [(Text, a)])
    -> ValueSpec a
    -> ValueSpec [(Text, a)]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . ValueSpec a -> PrimValueSpec [(Text, a)]
    forall a1. ValueSpec a1 -> PrimValueSpec [(Text, a1)]
    AssocSpec

    ------------------------------------------------------------------------

    -- $number
    -- Specifications built from 'numberSpec' matching number literals.

    -- | Primitive specification for matching any number.
    --
    -- @since 1.2.0.0
    numberSpec :: ValueSpec Number
    numberSpec :: ValueSpec Number
    numberSpec = PrimValueSpec Number -> ValueSpec Number
    forall a. PrimValueSpec a -> ValueSpec a
    primValueSpec PrimValueSpec Number
    NumberSpec

    {-# INLINE sizedBitsSpec #-}
    sizedBitsSpec :: forall a. (Integral a, FiniteBits a) => ValueSpec a
    sizedBitsSpec :: forall a. (Integral a, FiniteBits a) => ValueSpec a
    sizedBitsSpec = Text
    -> ValueSpec Integer -> (Integer -> Either Text a) -> ValueSpec a
    forall a b.
    Text -> ValueSpec a -> (a -> Either Text b) -> ValueSpec b
    customSpec Text
    label ValueSpec Integer
    integerSpec Integer -> Either Text a
    forall {a} {b} {a}.
    (Integral a, Integral b, Bits a, Bits b, IsString a) =>
    a -> Either a b
    check
      where
        signText :: [Char]
    signText = if a -> Bool
    forall a. Bits a => a -> Bool
    isSigned (a
    0::a) then [Char]
    "signed" else [Char]
    "unsigned"

        label :: Text
    label = [Char] -> Text
    Text.pack (Int -> [Char]
    forall a. Show a => a -> [Char]
    show (a -> Int
    forall b. FiniteBits b => b -> Int
    finiteBitSize (a
    0::a)) [Char] -> [Char] -> [Char]
    forall a. [a] -> [a] -> [a]
    ++ [Char]
    "-bit " [Char] -> [Char] -> [Char]
    forall a. [a] -> [a] -> [a]
    ++ [Char]
    signText)

        check :: a -> Either a b
    check a
    i = case a -> Maybe b
    forall a b.
    (Integral a, Integral b, Bits a, Bits b) =>
    a -> Maybe b
    toIntegralSized a
    i of
                    Maybe b
    Nothing -> a -> Either a b
    forall a b. a -> Either a b
    Left a
    "out of bounds"
                    Just b
    j  -> b -> Either a b
    forall a b. b -> Either a b
    Right b
    j

    -- | Specification for matching any non-negative, integral number
    --
    -- @since 1.2.0.0
    naturalSpec :: ValueSpec Natural
    naturalSpec :: ValueSpec Natural
    naturalSpec = Text
    -> ValueSpec Integer
    -> (Integer -> Either Text Natural)
    -> ValueSpec Natural
    forall a b.
    Text -> ValueSpec a -> (a -> Either Text b) -> ValueSpec b
    customSpec Text
    "non-negative" ValueSpec Integer
    integerSpec Integer -> Either Text Natural
    forall {a} {b}. (IsString a, Num b) => Integer -> Either a b
    check
      where
        check :: Integer -> Either a b
    check Integer
    i
          | Integer
    i Integer -> Integer -> Bool
    forall a. Ord a => a -> a -> Bool
    < Integer
    0     = a -> Either a b
    forall a b. a -> Either a b
    Left a
    "negative number"
          | Bool
    otherwise = b -> Either a b
    forall a b. b -> Either a b
    Right (Integer -> b
    forall a. Num a => Integer -> a
    fromInteger Integer
    i)

    -- | Specification for matching any integral number.
    numSpec :: Num a => ValueSpec a
    numSpec :: forall a. Num a => ValueSpec a
    numSpec = Integer -> a
    forall a. Num a => Integer -> a
    fromInteger (Integer -> a) -> ValueSpec Integer -> ValueSpec a
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> ValueSpec Integer
    integerSpec

    -- | Specification for matching any fractional number.
    --
    -- @since 0.2.0.0
    fractionalSpec :: Fractional a => ValueSpec a
    fractionalSpec :: forall a. Fractional a => ValueSpec a
    fractionalSpec = Rational -> a
    forall a. Fractional a => Rational -> a
    fromRational (Rational -> a) -> ValueSpec Rational -> ValueSpec a
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> ValueSpec Rational
    rationalSpec

    -- | Specification for matching any integral number.
    --
    -- @since 1.2.0.0
    integerSpec :: ValueSpec Integer
    integerSpec :: ValueSpec Integer
    integerSpec = Text
    -> ValueSpec Number
    -> (Number -> Either Text Integer)
    -> ValueSpec Integer
    forall a b.
    Text -> ValueSpec a -> (a -> Either Text b) -> ValueSpec b
    customSpec Text
    "integral" ValueSpec Number
    numberSpec Number -> Either Text Integer
    forall {a}. IsString a => Number -> Either a Integer
    check
      where
        check :: Number -> Either a Integer
    check Number
    n =
          case Number -> Maybe Integer
    numberToInteger Number
    n of
            Maybe Integer
    Nothing -> a -> Either a Integer
    forall a b. a -> Either a b
    Left a
    "fractional number"
            Just Integer
    i  -> Integer -> Either a Integer
    forall a b. b -> Either a b
    Right Integer
    i

    -- | Specification for matching any number as a 'Rational'.
    --
    -- @since 1.2.0.0
    rationalSpec :: ValueSpec Rational
    rationalSpec :: ValueSpec Rational
    rationalSpec = Number -> Rational
    numberToRational (Number -> Rational) -> ValueSpec Number -> ValueSpec Rational
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> ValueSpec Number
    numberSpec

    ------------------------------------------------------------------------

    -- $atom
    -- Specifications built to match atoms.

    -- | Primitive specification for matching a particular atom.
    atomSpec ::
      Text {- ^ atom -} ->
      ValueSpec ()
    atomSpec :: Text -> ValueSpec ()
    atomSpec Text
    t = () () -> ValueSpec () -> ValueSpec ()
    forall a b. a -> ValueSpec b -> ValueSpec a
    forall (f :: * -> *) a b. Functor f => a -> f b -> f a
    <$ PrimValueSpec () -> ValueSpec ()
    forall a. PrimValueSpec a -> ValueSpec a
    primValueSpec (Value () -> PrimValueSpec ()
    ExactSpec (() -> Atom -> Value ()
    forall a. a -> Atom -> Value a
    Atom () (Text -> Atom
    MkAtom Text
    t)))

    -- | Primitive specification for matching any atom. Matched atom is returned.
    anyAtomSpec :: ValueSpec Text
    anyAtomSpec :: ValueSpec Text
    anyAtomSpec = PrimValueSpec Text -> ValueSpec Text
    forall a. PrimValueSpec a -> ValueSpec a
    primValueSpec PrimValueSpec Text
    AtomSpec

    -- | Specification for using atoms @yes@ and @no@ to represent booleans 'True'
    -- and 'False' respectively
    yesOrNoSpec :: ValueSpec Bool
    yesOrNoSpec :: ValueSpec Bool
    yesOrNoSpec = Bool
    True Bool -> ValueSpec () -> ValueSpec Bool
    forall a b. a -> ValueSpec b -> ValueSpec a
    forall (f :: * -> *) a b. Functor f => a -> f b -> f a
    <$ Text -> ValueSpec ()
    atomSpec Text
    "yes" ValueSpec Bool -> ValueSpec Bool -> ValueSpec Bool
    forall a. ValueSpec a -> ValueSpec a -> ValueSpec a
    forall (f :: * -> *) a. Alt f => f a -> f a -> f a
    <!> Bool
    False Bool -> ValueSpec () -> ValueSpec Bool
    forall a b. a -> ValueSpec b -> ValueSpec a
    forall (f :: * -> *) a b. Functor f => a -> f b -> f a
    <$ Text -> ValueSpec ()
    atomSpec Text
    "no"

    -- | Specification for using atoms @true@ and @false@ to represent booleans 'True'
    -- and 'False' respectively.
    --
    -- @since 1.2.0.0
    trueOrFalseSpec :: ValueSpec Bool
    trueOrFalseSpec :: ValueSpec Bool
    trueOrFalseSpec = Bool
    True Bool -> ValueSpec () -> ValueSpec Bool
    forall a b. a -> ValueSpec b -> ValueSpec a
    forall (f :: * -> *) a b. Functor f => a -> f b -> f a
    <$ Text -> ValueSpec ()
    atomSpec Text
    "true" ValueSpec Bool -> ValueSpec Bool -> ValueSpec Bool
    forall a. ValueSpec a -> ValueSpec a -> ValueSpec a
    forall (f :: * -> *) a. Alt f => f a -> f a -> f a
    <!> Bool
    False Bool -> ValueSpec () -> ValueSpec Bool
    forall a b. a -> ValueSpec b -> ValueSpec a
    forall (f :: * -> *) a b. Functor f => a -> f b -> f a
    <$ Text -> ValueSpec ()
    atomSpec Text
    "false"

    ------------------------------------------------------------------------

    -- $text
    -- Specifications built from 'textSpec' for matching string literals.

    -- | Specification for matching any text literal
    --
    -- @since 1.2.0.0
    textSpec :: ValueSpec Text
    textSpec :: ValueSpec Text
    textSpec = PrimValueSpec Text -> ValueSpec Text
    forall a. PrimValueSpec a -> ValueSpec a
    primValueSpec PrimValueSpec Text
    TextSpec

    -- | Specification for matching any text as a 'String'
    stringSpec :: ValueSpec String
    stringSpec :: ValueSpec [Char]
    stringSpec = Text -> [Char]
    Text.unpack (Text -> [Char]) -> ValueSpec Text -> ValueSpec [Char]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> ValueSpec Text
    textSpec

    ------------------------------------------------------------------------

    -- $list
    -- Specifications for matching list literals built with 'listSpec.

    -- | Primitive specification for matching a list of values each satisfying a
    -- given element specification.
    listSpec ::
      ValueSpec a {- ^ element specification -} ->
      ValueSpec [a]
    listSpec :: forall a. ValueSpec a -> ValueSpec [a]
    listSpec = PrimValueSpec [a] -> ValueSpec [a]
    forall a. PrimValueSpec a -> ValueSpec a
    primValueSpec (PrimValueSpec [a] -> ValueSpec [a])
    -> (ValueSpec a -> PrimValueSpec [a])
    -> ValueSpec a
    -> ValueSpec [a]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . ValueSpec a -> PrimValueSpec [a]
    forall a1. ValueSpec a1 -> PrimValueSpec [a1]
    ListSpec

    -- | Matches a non-empty list.
    --
    -- @since 0.2.0.0
    nonemptySpec ::
      ValueSpec a {- ^ element specification -} ->
      ValueSpec (NonEmpty a)
    nonemptySpec :: forall a. ValueSpec a -> ValueSpec (NonEmpty a)
    nonemptySpec ValueSpec a
    s = Text
    -> ValueSpec [a]
    -> ([a] -> Either Text (NonEmpty a))
    -> ValueSpec (NonEmpty a)
    forall a b.
    Text -> ValueSpec a -> (a -> Either Text b) -> ValueSpec b
    customSpec Text
    "nonempty" (ValueSpec a -> ValueSpec [a]
    forall a. ValueSpec a -> ValueSpec [a]
    listSpec ValueSpec a
    s) [a] -> Either Text (NonEmpty a)
    forall {a} {a}. IsString a => [a] -> Either a (NonEmpty a)
    check
      where
        check :: [a] -> Either a (NonEmpty a)
    check [a]
    xs = case [a] -> Maybe (NonEmpty a)
    forall a. [a] -> Maybe (NonEmpty a)
    NonEmpty.nonEmpty [a]
    xs of
                     Maybe (NonEmpty a)
    Nothing -> a -> Either a (NonEmpty a)
    forall a b. a -> Either a b
    Left a
    "empty list"
                     Just NonEmpty a
    xxs -> NonEmpty a -> Either a (NonEmpty a)
    forall a b. b -> Either a b
    Right NonEmpty a
    xxs

    -- | Matches a single element or a non-empty list.
    --
    -- @since 0.2.0.0
    oneOrNonemptySpec ::
      ValueSpec a {- ^ element specification -} ->
      ValueSpec (NonEmpty a)
    oneOrNonemptySpec :: forall a. ValueSpec a -> ValueSpec (NonEmpty a)
    oneOrNonemptySpec ValueSpec a
    s = a -> NonEmpty a
    forall a. a -> NonEmpty a
    forall (f :: * -> *) a. Applicative f => a -> f a
    pure (a -> NonEmpty a) -> ValueSpec a -> ValueSpec (NonEmpty a)
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> ValueSpec a
    s ValueSpec (NonEmpty a)
    -> ValueSpec (NonEmpty a) -> ValueSpec (NonEmpty a)
    forall a. ValueSpec a -> ValueSpec a -> ValueSpec a
    forall (f :: * -> *) a. Alt f => f a -> f a -> f a
    <!> ValueSpec a -> ValueSpec (NonEmpty a)
    forall a. ValueSpec a -> ValueSpec (NonEmpty a)
    nonemptySpec ValueSpec a
    s

    -- | Specification that matches either a single element or multiple
    -- elements in a list. This can be convenient for allowing the user
    -- to avoid having to specify singleton lists in the configuration file.
    oneOrList ::
      ValueSpec a {- ^ element specification -} ->
      ValueSpec [a]
    oneOrList :: forall a. ValueSpec a -> ValueSpec [a]
    oneOrList ValueSpec a
    s = a -> [a]
    forall a. a -> [a]
    forall (f :: * -> *) a. Applicative f => a -> f a
    pure (a -> [a]) -> ValueSpec a -> ValueSpec [a]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> ValueSpec a
    s ValueSpec [a] -> ValueSpec [a] -> ValueSpec [a]
    forall a. ValueSpec a -> ValueSpec a -> ValueSpec a
    forall (f :: * -> *) a. Alt f => f a -> f a -> f a
    <!> ValueSpec a -> ValueSpec [a]
    forall a. ValueSpec a -> ValueSpec [a]
    listSpec ValueSpec a
    s

    ------------------------------------------------------------------------
    -- 'SectionsSpec' builders
    ------------------------------------------------------------------------

    -- $sectionsspec
    -- Sections specifications allow you to define an unordered collection
    -- of required and optional sections using a convenient 'Applicative'
    -- do-notation syntax.
    --
    -- Let's consider an example of a way to specify a name given a base
    -- and optional suffix.
    --
    -- @
    -- {-\# Language OverloadedStrings, ApplicativeDo \#-}
    -- module Example where
    --
    -- import "Config.Schema"
    -- import "Data.Text" ('Text')
    --
    -- nameExample :: 'ValueSpec' 'Text'
    -- nameExample =
    --   'sectionsSpec' \"name\" '$'
    --   do x <- 'reqSection' \"base\" \"Base name\"
    --      y <- 'optSection' \"suffix\" \"Optional name suffix\"
    --      'pure' ('maybe' x (x '<>') y)
    -- @
    --
    -- Example configuration components and their extracted values.
    --
    -- > base:     "VAR"
    -- > optional: "1"
    -- > -- Generates: VAR1
    --
    -- Order doesn't matter
    --
    -- > optional: "1"
    -- > base:     "VAR"
    -- > -- Generates: VAR1
    --
    -- Optional fields can be omitted
    --
    -- > base:     "VAR"
    -- > -- Generates: VAR
    --
    -- Unexpected sections will generate errors to help detect typos
    --
    -- > base:     "VAR"
    -- > extra:    0
    -- > -- Failure due to unexpected extra section
    --
    -- All required sections must appear for successful match
    --
    -- > optional: "1"
    -- > -- Failure due to missing required section

    -- | Specification for a required section with an implicit value specification.
    reqSection ::
      HasSpec a =>
      Text {- ^ section name -} ->
      Text {- ^ description  -} ->
      SectionsSpec a
    reqSection :: forall a. HasSpec a => Text -> Text -> SectionsSpec a
    reqSection Text
    n = Text -> ValueSpec a -> Text -> SectionsSpec a
    forall a. Text -> ValueSpec a -> Text -> SectionsSpec a
    reqSection' Text
    n ValueSpec a
    forall a. HasSpec a => ValueSpec a
    anySpec

    -- | Specification for a required section with an explicit value specification.
    reqSection' ::
      Text        {- ^ section name        -} ->
      ValueSpec a {- ^ value specification -} ->
      Text        {- ^ description         -} ->
      SectionsSpec a
    reqSection' :: forall a. Text -> ValueSpec a -> Text -> SectionsSpec a
    reqSection' Text
    n ValueSpec a
    w Text
    i = PrimSectionSpec a -> SectionsSpec a
    forall a. PrimSectionSpec a -> SectionsSpec a
    primSectionsSpec (Text -> Text -> ValueSpec a -> PrimSectionSpec a
    forall a. Text -> Text -> ValueSpec a -> PrimSectionSpec a
    ReqSection Text
    n Text
    i ValueSpec a
    w)

    -- | Specification for an optional section with an implicit value specification.
    optSection ::
      HasSpec a =>
      Text {- ^ section name -} ->
      Text {- ^ description  -} ->
      SectionsSpec (Maybe a)
    optSection :: forall a. HasSpec a => Text -> Text -> SectionsSpec (Maybe a)
    optSection Text
    n = Text -> ValueSpec a -> Text -> SectionsSpec (Maybe a)
    forall a. Text -> ValueSpec a -> Text -> SectionsSpec (Maybe a)
    optSection' Text
    n ValueSpec a
    forall a. HasSpec a => ValueSpec a
    anySpec

    -- | Specification for an optional section with an explicit value specification.
    optSection' ::
      Text        {- ^ section name        -} ->
      ValueSpec a {- ^ value specification -} ->
      Text        {- ^ description         -} ->
      SectionsSpec (Maybe a)
    optSection' :: forall a. Text -> ValueSpec a -> Text -> SectionsSpec (Maybe a)
    optSection' Text
    n ValueSpec a
    w Text
    i = PrimSectionSpec (Maybe a) -> SectionsSpec (Maybe a)
    forall a. PrimSectionSpec a -> SectionsSpec a
    primSectionsSpec (Text -> Text -> ValueSpec a -> PrimSectionSpec (Maybe a)
    forall a1.
    Text -> Text -> ValueSpec a1 -> PrimSectionSpec (Maybe a1)
    OptSection Text
    n Text
    i ValueSpec a
    w)