{-# 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
  , sectionsSpec
  , assocSpec
  , atomSpec
  , anyAtomSpec
  , listSpec
  , customSpec
  , namedSpec
  , numberSpec
  , integerSpec
  , naturalSpec
  , rationalSpec
  , textSpec
  , HasSpec(..)

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

  -- * Derived specifications
  , oneOrList
  , yesOrNoSpec
  , trueOrFalseSpec
  , stringSpec
  , numSpec
  , fractionalSpec
  , nonemptySpec
  , oneOrNonemptySpec

  ) 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.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 (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

{-# INLINE sizedBitsSpec #-}
sizedBitsSpec :: forall a. (Integral a, FiniteBits a) => ValueSpec a
sizedBitsSpec :: 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 (0::a) then "signed" else "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 (0::a)) [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ "-bit " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
signText)

    check :: a -> Either a b
check i :: a
i = case a -> Maybe b
forall a b.
(Integral a, Integral b, Bits a, Bits b) =>
a -> Maybe b
toIntegralSized a
i of
                Nothing -> a -> Either a b
forall a b. a -> Either a b
Left "out of bounds"
                Just j :: 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 "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 i :: Integer
i
      | Integer
i Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
< 0     = a -> Either a b
forall a b. a -> Either a b
Left "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 a particular atom.
atomSpec :: Text -> ValueSpec ()
atomSpec :: Text -> ValueSpec ()
atomSpec = PrimValueSpec () -> ValueSpec ()
forall a. PrimValueSpec a -> ValueSpec a
primValueSpec (PrimValueSpec () -> ValueSpec ())
-> (Text -> PrimValueSpec ()) -> Text -> ValueSpec ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> PrimValueSpec ()
AtomSpec

-- | 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
AnyAtomSpec

-- | 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

-- | Specification for matching any integral number.
numSpec :: Num a => ValueSpec a
numSpec :: 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 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 fractional number.
--
-- @since 0.2.0.0
fractionalSpec :: Fractional a => ValueSpec a
fractionalSpec :: 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 fractional 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

-- | 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 "integral" ValueSpec Number
numberSpec Number -> Either Text Integer
forall a. IsString a => Number -> Either a Integer
check
  where
    check :: Number -> Either a Integer
check n :: Number
n =
      case Number -> Maybe Integer
numberToInteger Number
n of
        Nothing -> a -> Either a Integer
forall a b. a -> Either a b
Left "fractional number"
        Just i :: 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

-- | Specification for matching a list of values each satisfying a
-- given element specification.
listSpec :: ValueSpec a -> ValueSpec [a]
listSpec :: 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 a. ValueSpec a -> PrimValueSpec [a]
ListSpec


-- | 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 :: Text -> SectionsSpec a -> ValueSpec a
sectionsSpec i :: Text
i s :: 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 :: 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 a. ValueSpec a -> PrimValueSpec [(Text, a)]
AssocSpec


-- | 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 :: Text -> ValueSpec a -> ValueSpec a
namedSpec n :: Text
n s :: 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)


-- | 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 -> ValueSpec [a]
oneOrList :: ValueSpec a -> ValueSpec [a]
oneOrList s :: ValueSpec a
s = 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 (f :: * -> *) a. Alt f => f a -> f a -> f a
<!> ValueSpec a -> ValueSpec [a]
forall a. ValueSpec a -> ValueSpec [a]
listSpec 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 :: Text -> ValueSpec a -> (a -> Either Text b) -> ValueSpec b
customSpec lbl :: Text
lbl w :: ValueSpec a
w f :: 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))


-- | 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 (f :: * -> *) a b. Functor f => a -> f b -> f a
<$ Text -> ValueSpec ()
atomSpec "yes" ValueSpec Bool -> ValueSpec Bool -> ValueSpec Bool
forall (f :: * -> *) a. Alt f => f a -> f a -> f a
<!> Bool
False Bool -> ValueSpec () -> ValueSpec Bool
forall (f :: * -> *) a b. Functor f => a -> f b -> f a
<$ Text -> ValueSpec ()
atomSpec "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 (f :: * -> *) a b. Functor f => a -> f b -> f a
<$ Text -> ValueSpec ()
atomSpec "true" ValueSpec Bool -> ValueSpec Bool -> ValueSpec Bool
forall (f :: * -> *) a. Alt f => f a -> f a -> f a
<!> Bool
False Bool -> ValueSpec () -> ValueSpec Bool
forall (f :: * -> *) a b. Functor f => a -> f b -> f a
<$ Text -> ValueSpec ()
atomSpec "false"

-- | Matches a non-empty list.
--
-- @since 0.2.0.0
nonemptySpec :: ValueSpec a -> ValueSpec (NonEmpty a)
nonemptySpec :: ValueSpec a -> ValueSpec (NonEmpty a)
nonemptySpec s :: 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 "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 xs :: [a]
xs = case [a] -> Maybe (NonEmpty a)
forall a. [a] -> Maybe (NonEmpty a)
NonEmpty.nonEmpty [a]
xs of
                 Nothing -> a -> Either a (NonEmpty a)
forall a b. a -> Either a b
Left "empty list"
                 Just xxs :: 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 -> ValueSpec (NonEmpty a)
oneOrNonemptySpec :: ValueSpec a -> ValueSpec (NonEmpty a)
oneOrNonemptySpec s :: ValueSpec a
s = 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 (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


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

-- $sections
-- 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 :: Text -> Text -> SectionsSpec a
reqSection n :: 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' :: Text -> ValueSpec a -> Text -> SectionsSpec a
reqSection' n :: Text
n w :: ValueSpec a
w i :: 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 :: Text -> Text -> SectionsSpec (Maybe a)
optSection n :: 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' :: Text -> ValueSpec a -> Text -> SectionsSpec (Maybe a)
optSection' n :: Text
n w :: ValueSpec a
w i :: Text
i = PrimSectionSpec (Maybe a) -> SectionsSpec (Maybe a)
forall a. PrimSectionSpec a -> SectionsSpec a
primSectionsSpec (Text -> Text -> ValueSpec a -> PrimSectionSpec (Maybe a)
forall a. Text -> Text -> ValueSpec a -> PrimSectionSpec (Maybe a)
OptSection Text
n Text
i ValueSpec a
w)