{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE RankNTypes #-}
module Text.ConfigParser.Types where

import Text.Parsec (string, spaces, char, (<?>))
import Text.Parsec.Text (Parser)

type Key = String

-- Key-value pair to parse from a config file.
data ConfigOption c
    = forall a. ConfigOption
        { key      :: Key         -- ^ Key name.
        , required :: Bool        -- ^ Whether it is an error to omit this key.
        , parser   :: Parser a    -- ^ Parser for the given value type.
        , action   :: a -> c -> c -- ^ How the value should change the state @c@.
        }

optionalCO, requiredCO :: Key -> Parser a -> (a -> c -> c) -> ConfigOption c
optionalCO k = ConfigOption k False
requiredCO k = ConfigOption k True

-- | Parameters for a parser that takes a config file and produces a @c@. Use
-- the 'ConfigParser' constructor if you want to specify your own 'lineParser'
-- or 'commentStart'. Otherwise, use the 'configParser' smart constructor.
data ConfigParser c = ConfigParser
    { keyValue :: forall a. Key -> Parser a -> Parser a
      -- ^ Specifies how a key and a value parser should be represented in the
      -- config file, e.g., @key = value@, or @key: value@.
    , lineCommentInit :: [String]
      -- Strings to start a line comment, such as @#@, @--@, or @//@. All
      -- characters following this string up to the following newline or EOF
      -- will be removed. You can use the string without starting a comment by
      -- escaping it with a backslash, e.g. @\#@ or @\--@.
    , defaults :: c
      -- Initial @c@ to fold each 'ConfigOption's action over.
    , options :: [ConfigOption c]
      -- List of key-value pairs to parse from the config file. Any key in the
      -- config file that doesn't appear here will result in parse error.
    }

-- | Smart constructor for a 'ConfigParser' that uses a default syntax like
-- @key = value@ and line comments starting with @#@.
configParser :: c -> [ConfigOption c] -> ConfigParser c
configParser = ConfigParser defaultKeyValue defaultLineCommentInit

-- | Default syntax like @key = value@.
defaultKeyValue :: Key -> Parser a -> Parser a
defaultKeyValue k p = keyParser *> separator *> p
    where
    keyParser = string k <?> "key \"" ++ k ++ "\""
    separator = spaces *> char '=' <* spaces

-- | Default line comment like @# comment text@.
defaultLineCommentInit :: [String]
defaultLineCommentInit = ["#"]