config-schema
=============

[![Hackage](https://img.shields.io/hackage/v/config-schema.svg)](https://hackage.haskell.org/package/config-schema) [![Build Status](https://secure.travis-ci.org/glguy/config-schema.png?branch=master)](http://travis-ci.org/glguy/config-schema)

Live Demo
--------

The config-value and config-schema packages are available in a [live demo](https://glguy.net/config-demo/).

About
--------

This package allows the user to define configuration schemas suitable for
matching against configuration files written in the
[config-value](https://hackage.haskell.org/package/config-value) format.
These schemas allow the user to extract an arbitrary Haskell value from
an interpretation of a configuration file. It also allows the user to
programatically generate documentation for the configuration files
accepted by the loader.

```haskell
{-# Language OverloadedStrings, ApplicativeDo #-}
module Example where

import qualified Data.Text as Text
import           Data.Text (Text)
import           Data.Monoid ((<>))
import           Data.Functor.Alt ((<!>))
import           Data.List.NonEmpty (NonEmpty)

import           Config
import           Config.Schema

exampleFile :: Text
exampleFile =
  " name: \"Johny Appleseed\" \n\
  \ age : 99                  \n\
  \ happy: yes                \n\
  \ kids:                     \n\
  \   * name: \"Bob\"         \n\
  \   * name: \"Tom\"         \n"

exampleValue :: Value Position
Right exampleValue = parse exampleFile

exampleSpec :: ValueSpec Text
exampleSpec = sectionsSpec "" $
  do name  <- reqSection  "name" "Full name"
     age   <- reqSection  "age"  "Age of user"
     happy <- optSection' "happy" yesOrNo
              "Current happiness status"
     kids  <- reqSection' "kids"  (oneOrList kidSpec)
              "All children's names"

     return $
       let happyText = case happy of Just True  -> " and is happy"
                                     Just False -> " and is not happy"
                                     Nothing    -> " and is private"

       in name <> " is " <> Text.pack (show (age::Integer)) <>
             " years old and has kids " <>
             Text.intercalate ", " kids <>
             happyText

kidSpec :: ValueSpec Text
kidSpec = sectionsSpec "kid" (reqSection "name" "Kid's name")


-- | Matches the 'yes' and 'no' atoms
yesOrNo :: ValueSpec Bool
yesOrNo = True  <$ atomSpec "yes" <!>
          False <$ atomSpec "no"


printDoc :: IO ()
printDoc = print (generateDocs exampleSpec)
-- *Example> printDoc
-- Top-level configuration file fields:
--     name: REQUIRED text
--        Full name
--     age: REQUIRED integer
--        Age of user
--     happy: `yes` or `no`
--        Current happiness status
--     kids: REQUIRED kid or list of kid
--        All children
--
-- kid
--     name: REQUIRED text
--        Kid's name

example :: Either (NonEmpty (LoadError Position)) Text
example = loadValue exampleSpec exampleValue
-- *Example> exampleVal
-- Right "Johny Appleseed is 99 years old and has kids Bob, Tom and is happy"
```
