Skip to content

Commit

Permalink
[#43] Introduce LHS readme with usage example (#161)
Browse files Browse the repository at this point in the history
  • Loading branch information
chshersh authored and vrom911 committed Jan 9, 2019
1 parent 8401b0c commit e8340d1
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 7 deletions.
1 change: 1 addition & 0 deletions README.lhs
142 changes: 139 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,144 @@
# tomland

[![Hackage](https://img.shields.io/hackage/v/tomland.svg)](https://hackage.haskell.org/package/tomland)
[![Build status](https://secure.travis-ci.org/kowainik/tomland.svg)](https://travis-ci.org/kowainik/tomland)
[![Windows build status](https://ci.appveyor.com/api/projects/status/github/kowainik/tomland?branch=master&svg=true)](https://ci.appveyor.com/project/kowainik/tomland)
[![Hackage](https://img.shields.io/hackage/v/tomland.svg)](https://hackage.haskell.org/package/tomland)
[![Stackage LTS](http://stackage.org/package/tomland/badge/lts)](http://stackage.org/lts/package/tomland)
[![Stackage Nightly](http://stackage.org/package/tomland/badge/nightly)](http://stackage.org/nightly/package/tomland)
[![MPL-2.0 license](https://img.shields.io/badge/license-MPL--2.0-blue.svg)](https://github.com/kowainik/tomland/blob/master/LICENSE)

TOML parser
> “A library is like an island in the middle of a vast sea of ignorance,
> particularly if the library is very tall and the surrounding area has been
> flooded.”
>
> ― Lemony Snicket, Horseradish
Bidirectional TOML serialization. The following blog post has more details about
library design:

* TODO: insert link to blog post

This README contains a basic usage example of the `tomland` library. All code
below can be compiled and run with the following command:

```
cabal new-run readme
```

## Preamble: imports and language extensions

Since this is a literate haskell file, we need to specify all our language
extensions and imports up front.

```haskell
{-# OPTIONS -Wno-unused-top-binds #-}

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}

import Control.Applicative ((<|>))
import Control.Category ((>>>))
import Data.Text (Text)
import Toml (TomlBiMap, TomlCodec, (.=))

import qualified Data.Text.IO as TIO
import qualified Toml
```

`tomland` is mostly designed for qualified imports and intended to be imported
as follows:

```haskell ignore
import Toml (TomlCodec, (.=)) -- add 'TomlBiMap' and 'Key' here optionally
import qualified Toml
```

## Data type: parsing and printing

We're going to parse TOML configuration from [`examples/readme.toml`](examples/readme.toml) file.

This static configuration is captured by the following Haskell data type:

```haskell
data Settings = Settings
{ settingsPort :: !Port
, settingsDescription :: !Text
, settingsCodes :: [Int]
, settingsMail :: !Mail
, settingsUsers :: ![User]
}

data Mail = Mail
{ mailHost :: !Host
, mailSendIfInactive :: !Bool
}

data User
= Admin Integer -- id of admin
| Client Text -- name of the client
deriving (Show)

newtype Port = Port Int
newtype Host = Host Text
```

Using `tomland` library, you can write bidirectional converters for these types
using the following guidelines and helper functions:

1. If your fields are some simple basic types like `Int` or `Text` you can just
use standard codecs like `Toml.int` and `Toml.text`.
2. If you want to parse `newtype`s, use `Toml.diwrap` to wrap parsers for
underlying `newtype` representation.
3. For parsing nested data types, use `Toml.table`. But this requires to specify
this data type as TOML table in `.toml` file.
4. If you have lists of custom data types, use `Toml.list`. Such lists are
represented as array of tables in TOML. If you have lists of primitive types
like `Int`, `Bool`, `Double`, `Text` or time types, that you can use
`Toml.arrayOf` and parse arrays of values.
5. `tomland` separates conversion between Haskell types and TOML values from
matching values by keys. Converters between types and values have type
`TomlBiMap` and are named with capital letter started with underscore. Main
type for TOML codecs is called `TomlCodec`. To lift `TomlBiMap` to
`TomlCodec` you need to use `Toml.match` function.

```haskell
settingsCodec :: TomlCodec Settings
settingsCodec = Settings
<$> Toml.diwrap (Toml.int "server.port") .= settingsPort
<*> Toml.text "server.description" .= settingsDescription
<*> Toml.arrayOf Toml._Int "server.codes" .= settingsCodes
<*> Toml.table mailCodec "mail" .= settingsMail
<*> Toml.list userCodec "user" .= settingsUsers

mailCodec :: TomlCodec Mail
mailCodec = Mail
<$> Toml.diwrap (Toml.text "host") .= mailHost
<*> Toml.bool "send-if-inactive" .= mailSendIfInactive

_Admin :: TomlBiMap User Integer
_Admin = Toml.prism Admin $ \case
Admin i -> Right i
other -> Toml.wrongConstructor "Admin" other

_Client :: TomlBiMap User Text
_Client = Toml.prism Client $ \case
Client n -> Right n
other -> Toml.wrongConstructor "Client" other

userCodec :: TomlCodec User
userCodec =
Toml.match (_Admin >>> Toml._Integer) "id"
<|> Toml.match (_Client >>> Toml._Text) "name"
```

And now we're ready to parse our TOML and print the result back to see whether
everything is okay.

```haskell
main :: IO ()
main = do
tomlExample <- TIO.readFile "examples/readme.toml"
let res = Toml.decode settingsCodec tomlExample
case res of
Left err -> print err
Right settings -> TIO.putStrLn $ Toml.encode settingsCodec settings
```
2 changes: 1 addition & 1 deletion examples/Playground.hs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ main = do
TIO.putStrLn $ pretty myToml

TIO.putStrLn "=== Printing parsed TOML ==="
content <- TIO.readFile "test.toml"
content <- TIO.readFile "examples/test.toml"
case Toml.parse content of
Left (ParseException e) -> TIO.putStrLn e
Right toml -> TIO.putStrLn $ pretty toml
Expand Down
16 changes: 16 additions & 0 deletions examples/readme.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
server.port = 8080
server.codes = [ 5, 10, 42 ]
server.description = """
This is production server.
Don't touch it!
"""

[mail]
host = "smtp.gmail.com"
send-if-inactive = false

[[user]]
id = 42

[[user]]
name = "Foo Bar"
File renamed without changes.
9 changes: 6 additions & 3 deletions src/Toml/Bi/Map.hs
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,22 @@
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE TypeFamilies #-}

{- | Implementation of partial bidirectional mapping as a data type.
{- | Implementation of tagged partial bidirectional isomorphism.
-}

module Toml.Bi.Map
( -- * BiMap idea
BiMap (..)
, TomlBiMapError (..)
, TomlBiMap
, invert
, iso
, prism

-- * 'BiMap' errors for TOML
, TomlBiMapError (..)
, wrongConstructor
, prettyBiMapError

-- * Helpers for BiMap and AnyValue
, mkAnyValueBiMap
, _TextBy
Expand Down Expand Up @@ -56,7 +60,6 @@ module Toml.Bi.Map

-- * Useful utility functions
, toMArray
, prettyBiMapError
) where

import Control.Arrow ((>>>))
Expand Down
10 changes: 10 additions & 0 deletions tomland.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,16 @@ library
ScopedTypeVariables
TypeApplications

executable readme
main-is: README.lhs
build-depends: base
, text
, tomland

build-tool-depends: markdown-unlit:markdown-unlit
ghc-options: -Wall -pgmL markdown-unlit
default-language: Haskell2010

executable play-tomland
main-is: Playground.hs
build-depends: base
Expand Down

0 comments on commit e8340d1

Please sign in to comment.