packages feed

tomland 1.1.0.1 → 1.2.0.0

raw patch · 33 files changed

+985/−712 lines, 33 filesdep −aesondep −gaugedep −htomldep ~basedep ~containersdep ~hashablePVP ok

version bump matches the API change (PVP)

Dependencies removed: aeson, gauge, htoml, htoml-megaparsec, parsec, toml-parser

Dependency ranges changed: base, containers, hashable, hedgehog, text, time

API changes (from Hackage documentation)

- Toml.Parser.TOML: keyP :: Parser Key
- Toml.Parser.TOML: tomlP :: Parser TOML
- Toml.Type.Value: instance (t Data.Type.Equality.~ 'Toml.Type.Value.TInteger) => GHC.Num.Num (Toml.Type.Value.Value t)
- Toml.Type.Value: instance (t Data.Type.Equality.~ 'Toml.Type.Value.TText) => Data.String.IsString (Toml.Type.Value.Value t)
+ Toml.Bi.Combinators: byteStringArray :: Key -> TomlCodec ByteString
+ Toml.Bi.Combinators: hashSet :: forall a. (Hashable a, Eq a) => TomlCodec a -> Key -> TomlCodec (HashSet a)
+ Toml.Bi.Combinators: lazyByteStringArray :: Key -> TomlCodec ByteString
+ Toml.Bi.Combinators: set :: forall a. Ord a => TomlCodec a -> Key -> TomlCodec (Set a)
+ Toml.Bi.Combinators: word8 :: Key -> TomlCodec Word8
+ Toml.Bi.Map: _ByteStringArray :: TomlBiMap ByteString AnyValue
+ Toml.Bi.Map: _LByteStringArray :: TomlBiMap ByteString AnyValue
+ Toml.Bi.Map: _Word8 :: TomlBiMap Word8 AnyValue
+ Toml.Bi.Monad: dimatch :: (Functor r, Alternative w) => (c -> Maybe d) -> (a -> b) -> Codec r w d a -> Codec r w c b
+ Toml.Generic: instance (Data.Hashable.Class.Hashable a, GHC.Classes.Eq a, Toml.Generic.HasItemCodec a) => Toml.Generic.HasCodec (Data.HashSet.Base.HashSet a)
+ Toml.Generic: instance (GHC.Classes.Ord a, Toml.Generic.HasItemCodec a) => Toml.Generic.HasCodec (Data.Set.Internal.Set a)
+ Toml.Generic: instance Toml.Generic.HasCodec GHC.Word.Word8
+ Toml.Generic: instance Toml.Generic.HasItemCodec GHC.Word.Word8
+ Toml.Parser: [unParseException] :: ParseException -> Text
+ Toml.Parser.Item: InlineTable :: !Key -> !Table -> TomlItem
+ Toml.Parser.Item: InlineTableArray :: !Key -> !NonEmpty Table -> TomlItem
+ Toml.Parser.Item: KeyVal :: !Key -> !AnyValue -> TomlItem
+ Toml.Parser.Item: Table :: [(Key, AnyValue)] -> Table
+ Toml.Parser.Item: TableArrayName :: !Key -> TomlItem
+ Toml.Parser.Item: TableName :: !Key -> TomlItem
+ Toml.Parser.Item: [unTable] :: Table -> [(Key, AnyValue)]
+ Toml.Parser.Item: data TomlItem
+ Toml.Parser.Item: instance GHC.Classes.Eq Toml.Parser.Item.Table
+ Toml.Parser.Item: instance GHC.Classes.Eq Toml.Parser.Item.TomlItem
+ Toml.Parser.Item: instance GHC.Show.Show Toml.Parser.Item.Table
+ Toml.Parser.Item: instance GHC.Show.Show Toml.Parser.Item.TomlItem
+ Toml.Parser.Item: newtype Table
+ Toml.Parser.Item: setTableName :: Key -> TomlItem -> TomlItem
+ Toml.Parser.Item: tomlP :: Parser [TomlItem]
+ Toml.Parser.Key: keyP :: Parser Key
+ Toml.Parser.Key: tableArrayNameP :: Parser Key
+ Toml.Parser.Key: tableNameP :: Parser Key
+ Toml.Parser.Validate: DuplicateKey :: !Key -> ValidationError
+ Toml.Parser.Validate: DuplicateTable :: !Key -> ValidationError
+ Toml.Parser.Validate: SameNameKeyTable :: !Key -> ValidationError
+ Toml.Parser.Validate: SameNameTableArray :: !Key -> ValidationError
+ Toml.Parser.Validate: data ValidationError
+ Toml.Parser.Validate: groupItems :: [TomlItem] -> Forest TomlItem
+ Toml.Parser.Validate: groupWithParent :: Maybe Key -> [TomlItem] -> (Forest TomlItem, [TomlItem])
+ Toml.Parser.Validate: instance GHC.Classes.Eq Toml.Parser.Validate.ValidationError
+ Toml.Parser.Validate: instance GHC.Show.Show Toml.Parser.Validate.ValidationError
+ Toml.Parser.Validate: validateItemForest :: Forest TomlItem -> Either ValidationError TOML
+ Toml.Parser.Validate: validateItems :: [TomlItem] -> Either ValidationError TOML
+ Toml.Type.Value: instance (t GHC.Types.~ 'Toml.Type.Value.TInteger) => GHC.Num.Num (Toml.Type.Value.Value t)
+ Toml.Type.Value: instance (t GHC.Types.~ 'Toml.Type.Value.TText) => Data.String.IsString (Toml.Type.Value.Value t)
- Toml.Bi.Code: BiMapError :: TomlBiMapError -> DecodeException
+ Toml.Bi.Code: BiMapError :: !TomlBiMapError -> DecodeException
- Toml.Bi.Code: KeyNotFound :: Key -> DecodeException
+ Toml.Bi.Code: KeyNotFound :: !Key -> DecodeException
- Toml.Bi.Code: LoadTomlException :: FilePath -> Text -> LoadTomlException
+ Toml.Bi.Code: LoadTomlException :: !FilePath -> !Text -> LoadTomlException
- Toml.Bi.Code: ParseError :: ParseException -> DecodeException
+ Toml.Bi.Code: ParseError :: !ParseException -> DecodeException
- Toml.Bi.Code: TableNotFound :: Key -> DecodeException
+ Toml.Bi.Code: TableNotFound :: !Key -> DecodeException
- Toml.Bi.Code: TypeMismatch :: Key -> Text -> TValue -> DecodeException
+ Toml.Bi.Code: TypeMismatch :: !Key -> !Text -> !TValue -> DecodeException
- Toml.Bi.Map: ArbitraryError :: Text -> TomlBiMapError
+ Toml.Bi.Map: ArbitraryError :: !Text -> TomlBiMapError
- Toml.Bi.Map: WrongConstructor :: Text -> Text -> TomlBiMapError
+ Toml.Bi.Map: WrongConstructor :: !Text -> !Text -> TomlBiMapError
- Toml.Bi.Map: WrongValue :: MatchError -> TomlBiMapError
+ Toml.Bi.Map: WrongValue :: !MatchError -> TomlBiMapError
- Toml.Bi.Map: toMArray :: [AnyValue] -> Either MatchError (Value 'TArray)
+ Toml.Bi.Map: toMArray :: [AnyValue] -> Either MatchError (Value 'TArray)
- Toml.Parser.Core: parse :: () => Parsec e s a -> String -> s -> Either (ParseErrorBundle s e) a
+ Toml.Parser.Core: parse :: Parsec e s a -> String -> s -> Either (ParseErrorBundle s e) a
- Toml.Type.AnyValue: MatchError :: TValue -> AnyValue -> MatchError
+ Toml.Type.AnyValue: MatchError :: !TValue -> !AnyValue -> MatchError
- Toml.Type.AnyValue: [valueActual] :: MatchError -> AnyValue
+ Toml.Type.AnyValue: [valueActual] :: MatchError -> !AnyValue
- Toml.Type.AnyValue: [valueExpected] :: MatchError -> TValue
+ Toml.Type.AnyValue: [valueExpected] :: MatchError -> !TValue
- Toml.Type.AnyValue: toMArray :: [AnyValue] -> Either MatchError (Value 'TArray)
+ Toml.Type.AnyValue: toMArray :: [AnyValue] -> Either MatchError (Value 'TArray)
- Toml.Type.TOML: TOML :: HashMap Key AnyValue -> PrefixMap TOML -> HashMap Key (NonEmpty TOML) -> TOML
+ Toml.Type.TOML: TOML :: !HashMap Key AnyValue -> !PrefixMap TOML -> !HashMap Key (NonEmpty TOML) -> TOML
- Toml.Type.TOML: [tomlPairs] :: TOML -> HashMap Key AnyValue
+ Toml.Type.TOML: [tomlPairs] :: TOML -> !HashMap Key AnyValue
- Toml.Type.TOML: [tomlTableArrays] :: TOML -> HashMap Key (NonEmpty TOML)
+ Toml.Type.TOML: [tomlTableArrays] :: TOML -> !HashMap Key (NonEmpty TOML)
- Toml.Type.TOML: [tomlTables] :: TOML -> PrefixMap TOML
+ Toml.Type.TOML: [tomlTables] :: TOML -> !PrefixMap TOML
- Toml.Type.Value: TypeMismatchError :: TValue -> TValue -> TypeMismatchError
+ Toml.Type.Value: TypeMismatchError :: !TValue -> !TValue -> TypeMismatchError
- Toml.Type.Value: [Array] :: [Value t] -> Value 'TArray
+ Toml.Type.Value: [Array] :: [Value t] -> Value 'TArray
- Toml.Type.Value: [Bool] :: Bool -> Value 'TBool
+ Toml.Type.Value: [Bool] :: Bool -> Value 'TBool
- Toml.Type.Value: [Day] :: Day -> Value 'TDay
+ Toml.Type.Value: [Day] :: Day -> Value 'TDay
- Toml.Type.Value: [Double] :: Double -> Value 'TDouble
+ Toml.Type.Value: [Double] :: Double -> Value 'TDouble
- Toml.Type.Value: [Hours] :: TimeOfDay -> Value 'THours
+ Toml.Type.Value: [Hours] :: TimeOfDay -> Value 'THours
- Toml.Type.Value: [Integer] :: Integer -> Value 'TInteger
+ Toml.Type.Value: [Integer] :: Integer -> Value 'TInteger
- Toml.Type.Value: [Local] :: LocalTime -> Value 'TLocal
+ Toml.Type.Value: [Local] :: LocalTime -> Value 'TLocal
- Toml.Type.Value: [Text] :: Text -> Value 'TText
+ Toml.Type.Value: [Text] :: Text -> Value 'TText
- Toml.Type.Value: [Zoned] :: ZonedTime -> Value 'TZoned
+ Toml.Type.Value: [Zoned] :: ZonedTime -> Value 'TZoned
- Toml.Type.Value: [typeActual] :: TypeMismatchError -> TValue
+ Toml.Type.Value: [typeActual] :: TypeMismatchError -> !TValue
- Toml.Type.Value: [typeExpected] :: TypeMismatchError -> TValue
+ Toml.Type.Value: [typeExpected] :: TypeMismatchError -> !TValue

Files

CHANGELOG.md view
@@ -3,6 +3,39 @@ tomland uses [PVP Versioning][1]. The changelog is available [on GitHub][2]. +## 1.2.0.0 — Oct 12, 2019++* [#216](https://github.com/kowainik/tomland/issues/216):+  Refactor TOML parser significantly. Check for some validation errors.+  (by [@chshersh](https://github.com/chshersh))+* [#213](https://github.com/kowainik/tomland/issues/213):+  Support GHC-8.8.1.+  (by [@vrom911](https://github.com/vrom911))+* [#226](https://github.com/kowainik/tomland/issues/226):+  Add `dimatch` combinator for better support of sum types.+  (by [@Nimor111](https://github.com/Nimor111))+* [#219](https://github.com/kowainik/tomland/issues/219):+  Add INLINE pragmas to code.+  (by [@willbasky](https://github.com/willbasky))+* [#204](https://github.com/kowainik/tomland/issues/204):+  Implement bidirectional codecs to work with `ByteString` as array of bytes.+  (by [@crtschin](https://github.com/crtschin))+* [#201](https://github.com/kowainik/tomland/issues/201):+  Implement `set` and `hashSet` combinators for array of tables.+  (by [@SanchayanMaity](https://github.com/SanchayanMaity))+* [#215](https://github.com/kowainik/tomland/issues/215):+  Move benchmarks to separate repository+  [toml-benchmarks](https://github.com/kowainik/toml-benchmarks).+  (by [@kutyel](https://github.com/kutyel))+* [#209](https://github.com/kowainik/tomland/issues/209):+  Bump up `parser-combinators` to `1.2.0`.+  (by [@vrom911](https://github.com/vrom911))+* [#198](https://github.com/kowainik/tomland/issues/198):+  Improve test generators.+  (by [@gabrielelana](https://github.com/gabrielelana)+  , [@chshersh](https://github.com/chshersh)+  )+ ## 1.1.0.1 — Jul 10, 2019  * [#206](https://github.com/kowainik/tomland/issues/206):
README.lhs view
@@ -1,7 +1,9 @@ # tomland  ![palm](https://user-images.githubusercontent.com/4276606/51088259-7a777000-176e-11e9-9d76-6be4023c0ac3.png)-[![Build status](https://img.shields.io/travis/kowainik/tomland.svg?logo=travis)](https://travis-ci.org/kowainik/tomland)+[![GitHub CI](https://github.com/kowainik/tomland/workflows/CI/badge.svg)](https://github.com/kowainik/tomland/actions)+[![Travis CI](https://img.shields.io/travis/kowainik/tomland.svg?logo=travis)](https://travis-ci.org/kowainik/tomland)+[![AppVeyor CI](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?logo=haskell)](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)@@ -74,9 +76,9 @@     }  data User-    = Admin  Integer  -- id of admin-    | Client Text     -- name of the client-    deriving (Show)+    = Admin  !Integer  -- id of admin+    | Client !Text     -- name of the client+    deriving stock (Show)  newtype Port = Port Int newtype Host = Host Text@@ -95,7 +97,11 @@    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+5. If you have sets of custom data types, use `Toml.set` or `Toml.HashSet`. Such+   sets are represented as array of tables in TOML.+6. For parsing sum types, use `Toml.dimatch`. This requires writing matching functions+   for the constructors of the sum type.+7. `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@@ -146,7 +152,7 @@  ## Benchmarks and comparison with other libraries -`tomland` is compared with other libraries. Since it uses 2-step approach with+`tomland` is [compared](https://github.com/kowainik/toml-benchmarks) with other libraries. Since it uses 2-step approach with converting text to intermediate AST and only then decoding Haskell type from this AST, benchmarks are also implemented in a way to reflect this difference. 
README.md view
@@ -1,7 +1,9 @@ # tomland  ![palm](https://user-images.githubusercontent.com/4276606/51088259-7a777000-176e-11e9-9d76-6be4023c0ac3.png)-[![Build status](https://img.shields.io/travis/kowainik/tomland.svg?logo=travis)](https://travis-ci.org/kowainik/tomland)+[![GitHub CI](https://github.com/kowainik/tomland/workflows/CI/badge.svg)](https://github.com/kowainik/tomland/actions)+[![Travis CI](https://img.shields.io/travis/kowainik/tomland.svg?logo=travis)](https://travis-ci.org/kowainik/tomland)+[![AppVeyor CI](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?logo=haskell)](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)@@ -74,9 +76,9 @@     }  data User-    = Admin  Integer  -- id of admin-    | Client Text     -- name of the client-    deriving (Show)+    = Admin  !Integer  -- id of admin+    | Client !Text     -- name of the client+    deriving stock (Show)  newtype Port = Port Int newtype Host = Host Text@@ -95,7 +97,11 @@    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+5. If you have sets of custom data types, use `Toml.set` or `Toml.HashSet`. Such+   sets are represented as array of tables in TOML.+6. For parsing sum types, use `Toml.dimatch`. This requires writing matching functions+   for the constructors of the sum type.+7. `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@@ -146,7 +152,7 @@  ## Benchmarks and comparison with other libraries -`tomland` is compared with other libraries. Since it uses 2-step approach with+`tomland` is [compared](https://github.com/kowainik/toml-benchmarks) with other libraries. Since it uses 2-step approach with converting text to intermediate AST and only then decoding Haskell type from this AST, benchmarks are also implemented in a way to reflect this difference. 
− benchmark/Benchmark/Htoml.hs
@@ -1,45 +0,0 @@-{-# OPTIONS_GHC -fno-warn-orphans #-}--{-# LANGUAGE DeriveAnyClass     #-}-{-# LANGUAGE DeriveGeneric      #-}-{-# LANGUAGE PackageImports     #-}-{-# LANGUAGE StandaloneDeriving #-}--module Benchmark.Htoml-       ( decode-       , parse-       , convert-       ) where--import Control.DeepSeq (NFData, rnf, rwhnf)-import Data.Aeson.Types (parseEither, parseJSON)-import Data.Semigroup ((<>))-import Data.Text (Text)-import GHC.Generics (Generic)-import Text.Parsec.Error (ParseError)--import "htoml" Text.Toml (parseTomlDoc)-import "htoml" Text.Toml.Types (Node (..), Table, toJSON)--import Benchmark.Type (HaskellType)----- | Decode toml file to Haskell type.-decode :: Text -> Either String HaskellType-decode txt = case parseTomlDoc "" txt of-    Left err   -> error $ "'htoml' parsing failed: " <> show err-    Right toml -> convert toml---- | Wrapper on htoml's 'parseTomlDoc'-parse :: Text -> Either ParseError Table-parse = parseTomlDoc ""---- | Convert from already parsed toml to Haskell type.-convert :: Table -> Either String HaskellType-convert = parseEither parseJSON . toJSON--deriving instance NFData Node-deriving instance Generic Node--instance NFData ParseError where-    rnf = rwhnf
− benchmark/Benchmark/HtomlMegaparsec.hs
@@ -1,43 +0,0 @@-{-# OPTIONS_GHC -fno-warn-orphans #-}--{-# LANGUAGE PackageImports #-}--module Benchmark.HtomlMegaparsec-       ( decode-       , parse-       , convert-       ) where--import Data.Aeson.Types (ToJSON, parseEither, parseJSON, toJSON)-import Data.Semigroup ((<>))-import Data.Text (Text)--import "htoml-megaparsec" Text.Toml (Node (..), Table, TomlError, parseTomlDoc)--import Benchmark.Type (HaskellType)---- | Decode toml file to Haskell type.-decode :: Text -> Either String HaskellType-decode txt = case parseTomlDoc "log" txt of-    Left err   -> error $ "'htoml-megaparsec' parsing failed: " <> show err-    Right toml -> convert toml---- | Wrapper on htoml-megaparsec's parseTomlDoc-parse :: Text -> Either TomlError Table-parse = parseTomlDoc "log"---- | Convert from already parsed toml to Haskell type.-convert :: Table -> Either String HaskellType-convert = parseEither parseJSON . toJSON---- | 'ToJSON' instances for the 'Node' type that produce Aeson (JSON)--- in line with the TOML specification.-instance ToJSON Node where-    toJSON (VTable v)    = toJSON v-    toJSON (VTArray v)   = toJSON v-    toJSON (VString v)   = toJSON v-    toJSON (VInteger v)  = toJSON v-    toJSON (VFloat v)    = toJSON v-    toJSON (VBoolean v)  = toJSON v-    toJSON (VDatetime v) = toJSON v-    toJSON (VArray v)    = toJSON v
− benchmark/Benchmark/TomlParser.hs
@@ -1,91 +0,0 @@-{-# OPTIONS_GHC -fno-warn-orphans #-}--{-# LANGUAGE DeriveAnyClass      #-}-{-# LANGUAGE DeriveGeneric       #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE StandaloneDeriving  #-}--module Benchmark.TomlParser-       ( decode-       , parse-       , convert-       ) where--import Control.DeepSeq (NFData, rnf, rwhnf)-import Data.Semigroup ((<>))-import Data.Text (Text)-import GHC.Generics (Generic)--import Benchmark.Type (FruitInside (..), HaskellType (..), SizeInside (..))--import qualified TOML----- | Decode toml file to Haskell type.-decode :: Text -> Either String HaskellType-decode txt = case parse txt of-    Left err   -> error $ "'toml-parser' parsing failed: " <> show err-    Right toml -> maybe (Left "Some error during conversion") Right $ convert toml---- | Wrapper over 'TOML.parseTOML'-parse :: Text -> Either TOML.TOMLError [(Text, TOML.Value)]-parse = TOML.parseTOML---- | Convert from already parsed toml to Haskell type.-convert :: [(Text, TOML.Value)] -> Maybe HaskellType-convert toml = do-    TOML.String htTitle <- lookup "title" toml-    TOML.Double htAtom <- lookup "atom" toml-    TOML.Bool htCash <- lookup "cash" toml--    TOML.List tomlWords <- lookup "words" toml-    htWords <- mapM matchText tomlWords--    TOML.List tomlBools <- lookup "bool" toml-    htBool <- mapM matchBool tomlBools--    TOML.ZonedTimeV htToday <- lookup "today" toml--    TOML.List tomlInts <- lookup "ints" toml-    htInteger <- mapM matchInteger tomlInts--    TOML.Table fruit <- lookup "fruit" toml-    htFruit <- do-        TOML.String fiName <- lookup "name" fruit-        TOML.String fiDescription <- lookup "description" fruit-        pure FruitInside{..}--    TOML.Table size <- lookup "size" toml-    htSize <- do-        TOML.List dimensions <- lookup "dimensions" size-        arrays :: [[TOML.Value]] <- mapM matchArray dimensions-        unSize <- mapM (mapM matchDouble) arrays-        pure SizeInside{..}--    pure HaskellType{..}--matchText :: TOML.Value -> Maybe Text-matchText (TOML.String t) = Just t-matchText _               = Nothing--matchBool :: TOML.Value -> Maybe Bool-matchBool (TOML.Bool b) = Just b-matchBool _             = Nothing--matchInteger :: TOML.Value -> Maybe Integer-matchInteger (TOML.Integer i) = Just i-matchInteger _                = Nothing--matchArray :: TOML.Value -> Maybe [TOML.Value]-matchArray (TOML.List a) = Just a-matchArray _             = Nothing--matchDouble :: TOML.Value -> Maybe Double-matchDouble (TOML.Double d) = Just d-matchDouble _               = Nothing--deriving instance Generic TOML.Value-deriving instance NFData  TOML.Value--instance NFData TOML.TOMLError where-  rnf = rwhnf
− benchmark/Benchmark/Tomland.hs
@@ -1,39 +0,0 @@-module Benchmark.Tomland-       ( decode-       , parse-       , convert-       ) where--import Data.Text (Text)--import Benchmark.Type (FruitInside (..), HaskellType (..), SizeInside (..))-import Toml (DecodeException, TOML, TomlCodec, parse, (.=))--import qualified Toml--decode :: Text -> Either DecodeException HaskellType-decode = Toml.decode codec--convert :: TOML -> Either DecodeException HaskellType-convert = Toml.runTomlCodec codec---- | Codec to use in tomland decode and convert functions.-codec :: TomlCodec HaskellType-codec = HaskellType-    <$> Toml.text "title" .= htTitle-    <*> Toml.double "atom" .= htAtom-    <*> Toml.bool "cash" .= htCash-    <*> Toml.arrayOf Toml._Text "words" .= htWords-    <*> Toml.arrayOf Toml._Bool "bool" .= htBool-    <*> Toml.zonedTime "today" .= htToday-    <*> Toml.arrayOf Toml._Integer "ints" .= htInteger-    <*> Toml.table insideF "fruit" .= htFruit-    <*> Toml.table insideS "size" .= htSize--insideF :: TomlCodec FruitInside-insideF = FruitInside-    <$> Toml.text "name" .= fiName-    <*> Toml.text "description" .= fiDescription--insideS :: TomlCodec SizeInside-insideS = Toml.diwrap $ Toml.arrayOf (Toml._Array Toml._Double) "dimensions"
− benchmark/Benchmark/Type.hs
@@ -1,57 +0,0 @@-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric  #-}--module Benchmark.Type-       ( HaskellType (..)-       , FruitInside (..)-       , SizeInside (..)-       ) where--import Control.DeepSeq (NFData)-import Data.Aeson.Types (FromJSON, parseJSON, withObject, (.:))-import Data.Text (Text)-import Data.Time (ZonedTime)-import GHC.Generics (Generic)----- | Haskell type to convert to.-data HaskellType = HaskellType-    { htTitle   :: Text-    , htAtom    :: Double-    , htCash    :: Bool-    , htWords   :: [Text]-    , htBool    :: [Bool]-    , htToday   :: ZonedTime-    , htInteger :: [Integer]-    , htFruit   :: FruitInside-    , htSize    :: SizeInside-    } deriving (Show, NFData, Generic)--instance FromJSON HaskellType where-    parseJSON = withObject "HaskellType" $ \o -> HaskellType-        <$> o .: "title"-        <*> o .: "atom"-        <*> o .: "cash"-        <*> o .: "words"-        <*> o .: "bool"-        <*> o .: "today"-        <*> o .: "ints"-        <*> o .: "fruit"-        <*> o .: "size"--data FruitInside = FruitInside-    { fiName        :: Text-    , fiDescription :: Text-    } deriving (Show, NFData, Generic)--instance FromJSON FruitInside where-    parseJSON = withObject "FruitInside" $ \o -> FruitInside-        <$> o .: "name"-        <*> o .: "description"--newtype SizeInside = SizeInside-    { unSize :: [[Double]]-    } deriving (Show, NFData, Generic)--instance FromJSON SizeInside where-    parseJSON = withObject "SizeInside" $ \o -> SizeInside <$> o .: "dimensions"
− benchmark/Main.hs
@@ -1,41 +0,0 @@-module Main-       ( main-       ) where--import Gauge.Main (bench, bgroup, defaultMain, nf)--import qualified Benchmark.Htoml as Htoml-import qualified Benchmark.HtomlMegaparsec as HtomlM-import qualified Benchmark.Tomland as Tomland-import qualified Benchmark.TomlParser as TomlParser-import qualified Data.Text.IO as TIO----- | Benchmark.-main :: IO ()-main = do-    txt <- TIO.readFile "./benchmark/benchmark.toml"-    Right tomlandVal <- pure $ Tomland.parse txt-    Right htomlVal <- pure $ Htoml.parse txt-    Right htomlMegaVal <- pure $ HtomlM.parse txt-    Right tomlParserVal <- pure $ TomlParser.parse txt-    defaultMain-        [ bgroup "Parse"-            [ bench "tomland"          $ nf Tomland.parse txt-            , bench "htoml"            $ nf Htoml.parse txt-            , bench "htoml-megaparsec" $ nf HtomlM.parse txt-            , bench "toml-parser"      $ nf TomlParser.parse txt-            ]-        , bgroup "Convert"-            [ bench "tomland"          $ nf Tomland.convert tomlandVal-            , bench "htoml"            $ nf Htoml.convert htomlVal-            , bench "htoml-megaparsec" $ nf HtomlM.convert htomlMegaVal-            , bench "toml-parser"      $ nf TomlParser.convert tomlParserVal-            ]-        , bgroup "Decode"-            [ bench "tomland"          $ nf Tomland.decode txt-            , bench "htoml"            $ nf Htoml.decode txt-            , bench "htoml-megaparsec" $ nf HtomlM.decode txt-            , bench "toml-parser"      $ nf TomlParser.decode txt-            ]-        ]
+ examples/Main.hs view
@@ -0,0 +1,153 @@+{-# OPTIONS -Wno-unused-top-binds #-}+{-# LANGUAGE DeriveAnyClass #-}++module Main (main) where++import Control.Applicative ((<|>))+import Control.Arrow ((>>>))+import Data.Hashable (Hashable)+import Data.HashSet (HashSet)+import Data.Set (Set)+import Data.Text (Text)+import Data.Time (fromGregorian)+import GHC.Generics (Generic)++import Toml (ParseException (..), TomlCodec, pretty, (.=), (<!>))+import Toml.Edsl (mkToml, table, (=:))+import Toml.Type (TOML (..), Value (..))++import qualified Data.Text.IO as TIO+import qualified Toml++newtype TestInside = TestInside { unInside :: Text }++insideCodec :: TomlCodec TestInside+insideCodec = Toml.dimap unInside TestInside $ Toml.text "inside"++data User = User+    { userName :: !Text+    , userAge  :: !Int+    } deriving stock (Eq, Ord, Generic)+      deriving anyclass (Hashable)++userCodec :: TomlCodec User+userCodec = User+    <$> Toml.text "name" .= userName+    <*> Toml.int  "age"  .= userAge++newtype N = N+    { unN :: Text+    }++data ColorScheme+    = Light+    | Dark+    | HighContrast+    deriving stock (Show, Enum, Bounded)++data UserStatus+    = Registered Text Text+    | Anonymous Text++matchRegistered :: UserStatus -> Maybe (Text, Text)+matchRegistered (Registered username password) = Just (username, password)+matchRegistered _                              = Nothing++matchAnonymous :: UserStatus -> Maybe Text+matchAnonymous (Anonymous username) = Just username+matchAnonymous _                    = Nothing++userPassCodec :: TomlCodec (Text, Text)+userPassCodec = (,)+    <$> Toml.text "username" .= fst+    <*> Toml.text "password" .= snd++userStatusCodec :: TomlCodec UserStatus+userStatusCodec =+    Toml.dimatch matchRegistered (uncurry Registered) (Toml.table userPassCodec "testStatus")+    <|> Toml.dimatch matchAnonymous Anonymous (Toml.text "testStatus")++data Test = Test+    { testB      :: !Bool+    , testI      :: !Int+    , testF      :: !Double+    , testS      :: !Text+    , testA      :: ![Text]+    , testM      :: !(Maybe Bool)+    , testX      :: !TestInside+    , testY      :: !(Maybe TestInside)+    , testN      :: !N+    , testC      :: !ColorScheme+    , testE1     :: !(Either Integer String)+    , testE2     :: !(Either String Double)+    , testStatus :: !UserStatus+    , users      :: ![User]+    , susers     :: !(Set User)+    , husers     :: !(HashSet User)+    }+++testT :: TomlCodec Test+testT = Test+    <$> Toml.bool "testB" .= testB+    <*> Toml.int "testI" .= testI+    <*> Toml.double "testF" .= testF+    <*> Toml.text "testS" .= testS+    <*> Toml.arrayOf Toml._Text "testA" .= testA+    <*> Toml.dioptional (Toml.bool "testM") .= testM+    <*> Toml.table insideCodec "testX" .= testX+    <*> Toml.dioptional ((Toml.table insideCodec) "testY") .= testY+    <*> Toml.diwrap (Toml.text "testN") .= testN+    <*> Toml.enumBounded "testC" .= testC+    <*> eitherT1 .= testE1+    <*> eitherT2 .= testE2+    <*> userStatusCodec .= testStatus+    <*> Toml.list userCodec "user" .= users+    <*> Toml.set userCodec "suser" .= susers+    <*> Toml.hashSet userCodec "huser" .= husers+  where+    -- different keys for sum type+    eitherT1 :: TomlCodec (Either Integer String)+    eitherT1 = Toml.match (Toml._Left >>> Toml._Integer)  "either.Left"+           <|> Toml.match (Toml._Right >>> Toml._String) "either.Right"++    -- same key for sum type;+    -- doesn't work if you have something like `Either String String`,+    -- you should distinguish these cases by different keys like in `eitherT1` example+    eitherT2 :: TomlCodec (Either String Double)+    eitherT2 = ( Toml.match (Toml._Left >>> Toml._String)+             <!> Toml.match (Toml._Right >>> Toml._Double)+               ) "either"++main :: IO ()+main = do+    TIO.putStrLn "=== Printing manually specified TOML ==="+    TIO.putStrLn $ pretty myToml++    TIO.putStrLn "=== Trying to print invalid TOML ==="+    content <- TIO.readFile "examples/invalid.toml"+    TIO.putStrLn $ case Toml.parse content of+        Left (ParseException e) -> e+        Right toml              -> pretty toml++    TIO.putStrLn "=== Testing bidirectional conversion ==="+    biFile <- TIO.readFile "examples/biTest.toml"+    TIO.putStrLn $ case Toml.decode testT biFile of+        Left msg   -> Toml.prettyException msg+        Right test -> Toml.encode testT test++myToml :: TOML+myToml = mkToml $ do+    "a" =: Bool True+    "list" =: Array ["one", "two"]+    "time" =: Array [Day (fromGregorian 2018 3 29)]+    table "table.name.1" $ do+        "aInner" =: 1+        "listInner" =: Array [Bool True, Bool False]+        table "1" $ do+            "aInner11" =: 11+            "listInner11" =: Array [0, 1]+        table "2" $+            "Inner12" =: "12"+    table "table.name.2" $+        "Inner2" =: 42
− examples/Playground.hs
@@ -1,116 +0,0 @@-{-# OPTIONS -Wno-unused-top-binds #-}--module Main (main) where--import Control.Applicative ((<|>))-import Control.Arrow ((>>>))-import Data.Text (Text)-import Data.Time (fromGregorian)--import Toml (ParseException (..), TomlCodec, pretty, (.=), (<!>))-import Toml.Edsl (mkToml, table, (=:))-import Toml.Type (TOML (..), Value (..))--import qualified Data.Text.IO as TIO-import qualified Toml--newtype TestInside = TestInside { unInside :: Text }--insideCodec :: TomlCodec TestInside-insideCodec = Toml.dimap unInside TestInside $ Toml.text "inside"--data User = User-    { userName :: Text-    , userAge  :: Int-    }--userCodec :: TomlCodec User-userCodec = User-    <$> Toml.text "name" .= userName-    <*> Toml.int  "age"  .= userAge--newtype N = N Text--data ColorScheme = Light-                 | Dark-                 | HighContrast-                 deriving (Show, Enum, Bounded)--data Test = Test-    { testB  :: Bool-    , testI  :: Int-    , testF  :: Double-    , testS  :: Text-    , testA  :: [Text]-    , testM  :: Maybe Bool-    , testX  :: TestInside-    , testY  :: Maybe TestInside-    , testN  :: N-    , testC  :: ColorScheme-    , testE1 :: Either Integer String-    , testE2 :: Either String Double-    , users  :: [User]-    }---testT :: TomlCodec Test-testT = Test-    <$> Toml.bool "testB" .= testB-    <*> Toml.int "testI" .= testI-    <*> Toml.double "testF" .= testF-    <*> Toml.text "testS" .= testS-    <*> Toml.arrayOf Toml._Text "testA" .= testA-    <*> Toml.dioptional (Toml.bool "testM") .= testM-    <*> Toml.table insideCodec "testX" .= testX-    <*> Toml.dioptional ((Toml.table insideCodec) "testY") .= testY-    <*> Toml.diwrap (Toml.text "testN") .= testN-    <*> Toml.enumBounded "testC" .= testC-    <*> eitherT1 .= testE1-    <*> eitherT2 .= testE2-    <*> Toml.list userCodec "user" .= users-  where-    -- different keys for sum type-    eitherT1 :: TomlCodec (Either Integer String)-    eitherT1 = Toml.match (Toml._Left >>> Toml._Integer)  "either.Left"-           <|> Toml.match (Toml._Right >>> Toml._String) "either.Right"--    -- same key for sum type;-    -- doesn't work if you have something like `Either String String`,-    -- you should distinguish these cases by different keys like in `eitherT1` example-    eitherT2 :: TomlCodec (Either String Double)-    eitherT2 = ( Toml.match (Toml._Left >>> Toml._String)-             <!> Toml.match (Toml._Right >>> Toml._Double)-               ) "either"--main :: IO ()-main = do-    TIO.putStrLn "=== Printing manually specified TOML ==="-    TIO.putStrLn $ pretty myToml--    TIO.putStrLn "=== Trying to print invalid TOML ==="-    content <- TIO.readFile "examples/invalid.toml"-    TIO.putStrLn $ case Toml.parse content of-        Left (ParseException e) -> e-        Right toml              -> pretty toml--    TIO.putStrLn "=== Testing bidirectional conversion ==="-    biFile <- TIO.readFile "examples/biTest.toml"-    TIO.putStrLn $ case Toml.decode testT biFile of-        Left msg   -> Toml.prettyException msg-        Right test -> Toml.encode testT test--myToml :: TOML-myToml = mkToml $ do-    "a" =: Bool True-    "list" =: Array ["one", "two"]-    "time" =: Array [Day (fromGregorian 2018 3 29)]-    table "table.name.1" $ do-        "aInner" =: 1-        "listInner" =: Array [Bool True, Bool False]-        table "1" $ do-            "aInner11" =: 11-            "listInner11" =: Array [0, 1]-        table "2" $-            "Inner12" =: "12"-    table "table.name.2" $-        "Inner2" =: 42
src/Toml/Bi/Code.hs view
@@ -45,15 +45,17 @@ import qualified Data.Text as Text import qualified Data.Text.IO as TIO + -- | Type of exception for converting from TOML to user custom data type. data DecodeException     = TrivialError-    | BiMapError TomlBiMapError-    | KeyNotFound Key  -- ^ No such key-    | TableNotFound Key  -- ^ No such table-    | TypeMismatch Key Text TValue  -- ^ Expected type vs actual type-    | ParseError ParseException  -- ^ Exception during parsing-    deriving (Eq, Generic, NFData)+    | BiMapError !TomlBiMapError+    | KeyNotFound !Key  -- ^ No such key+    | TableNotFound !Key  -- ^ No such table+    | TypeMismatch !Key !Text !TValue  -- ^ Expected type vs actual type+    | ParseError !ParseException  -- ^ Exception during parsing+    deriving stock (Eq, Generic)+    deriving anyclass (NFData)  instance Show DecodeException where     show = Text.unpack . prettyException@@ -120,7 +122,7 @@ execTomlCodec codec obj = execState (runMaybeT $ codecWrite codec obj) mempty  -- | File loading error data type.-data LoadTomlException = LoadTomlException FilePath Text+data LoadTomlException = LoadTomlException !FilePath !Text  instance Show LoadTomlException where     show (LoadTomlException filePath msg) = "Couldnt parse file " ++ filePath ++ ": " ++ show msg
src/Toml/Bi/Combinators.hs view
@@ -13,6 +13,7 @@        , natural        , int        , word+       , word8          -- ** Floating point numbers        , double        , float@@ -21,6 +22,8 @@        , lazyText        , byteString        , lazyByteString+       , byteStringArray+       , lazyByteStringArray        , string          -- ** Time types        , zonedTime@@ -44,6 +47,8 @@        , table        , nonEmpty        , list+       , set+       , hashSet           -- * General construction of codecs        , match@@ -66,24 +71,25 @@ import Data.Set (Set) import Data.Text (Text) import Data.Time (Day, LocalTime, TimeOfDay, ZonedTime)-import Data.Word (Word)+import Data.Word (Word8) import Numeric.Natural (Natural)  import Toml.Bi.Code (DecodeException (..), Env, St, TomlCodec, execTomlCodec)-import Toml.Bi.Map (BiMap (..), TomlBiMap, _Array, _Bool, _ByteString, _Day, _Double, _EnumBounded,-                    _Float, _HashSet, _Int, _IntSet, _Integer, _LByteString, _LText, _LocalTime,-                    _Natural, _NonEmpty, _Read, _Set, _String, _Text, _TextBy, _TimeOfDay, _Word,-                    _ZonedTime)-import Toml.Bi.Monad (Codec (..))+import Toml.Bi.Map (BiMap (..), TomlBiMap, _Array, _Bool, _ByteString, _ByteStringArray, _Day,+                    _Double, _EnumBounded, _Float, _HashSet, _Int, _IntSet, _Integer, _LByteString,+                    _LByteStringArray, _LText, _LocalTime, _Natural, _NonEmpty, _Read, _Set,+                    _String, _Text, _TextBy, _TimeOfDay, _Word, _Word8, _ZonedTime)+import Toml.Bi.Monad (Codec (..), dimap) import Toml.PrefixTree (Key) import Toml.Type (AnyValue (..), TOML (..), insertKeyAnyVal, insertTable, insertTableArrays)  import qualified Data.ByteString.Lazy as BL import qualified Data.HashMap.Strict as HashMap+import qualified Data.HashSet as HS+import qualified Data.Set as S import qualified Data.Text.Lazy as L import qualified Toml.PrefixTree as Prefix - {- | General function to create bidirectional converters for key-value pairs. In order to use this function you need to create 'TomlBiMap' for your type and 'AnyValue':@@ -119,50 +125,70 @@ -- | Codec for boolean values. bool :: Key -> TomlCodec Bool bool = match _Bool+{-# INLINE bool #-}  -- | Codec for integer values. integer :: Key -> TomlCodec Integer integer = match _Integer+{-# INLINE integer #-}  -- | Codec for integer values. int :: Key -> TomlCodec Int int = match _Int+{-# INLINE int #-}  -- | Codec for natural values. natural :: Key -> TomlCodec Natural natural = match _Natural+{-# INLINE natural #-}  -- | Codec for word values. word :: Key -> TomlCodec Word word = match _Word+{-# INLINE word #-} +{- | Codec for word8 values.++@since 1.2.0.0+-}+word8 :: Key -> TomlCodec Word8+word8 = match _Word8+{-# INLINE word8 #-}+ -- | Codec for floating point values with double precision. double :: Key -> TomlCodec Double double = match _Double+{-# INLINE double #-}  -- | Codec for floating point values. float :: Key -> TomlCodec Float float = match _Float+{-# INLINE float #-}  -- | Codec for text values. text :: Key -> TomlCodec Text text = match _Text+{-# INLINE text #-}  -- | Codec for lazy text values. lazyText :: Key -> TomlCodec L.Text lazyText = match _LText+{-# INLINE lazyText #-}  -- | Codec for text values with custom error messages for parsing. textBy :: (a -> Text) -> (Text -> Either Text a) -> Key -> TomlCodec a textBy to from = match (_TextBy to from)+{-# INLINE textBy #-}  -- | Codec for string values. string :: Key -> TomlCodec String string = match _String+{-# INLINE string #-}  -- | Codec for values with a 'Read' and 'Show' instance. read :: (Show a, Read a) => Key -> TomlCodec a read = match _Read+{-# INLINE read #-}  {- | Codec for general nullary sum data types with a 'Bounded', 'Enum', and 'Show' instance. This codec provides much better error messages than 'read' for@@ -172,45 +198,71 @@ -} enumBounded :: (Bounded a, Enum a, Show a) => Key -> TomlCodec a enumBounded = match _EnumBounded+{-# INLINE enumBounded #-}  -- | Codec for text values as 'ByteString'. byteString :: Key -> TomlCodec ByteString byteString = match _ByteString+{-# INLINE byteString #-}  -- | Codec for text values as 'BL.ByteString'. lazyByteString :: Key -> TomlCodec BL.ByteString lazyByteString = match _LByteString+{-# INLINE lazyByteString #-} +{- | Codec for positive integer array values as 'ByteString'.++@since 1.2.0.0+-}+byteStringArray :: Key -> TomlCodec ByteString+byteStringArray = match _ByteStringArray+{-# INLINE byteStringArray #-}++{- | Codec for positive integer array values as lazy 'ByteString'.++@since 1.2.0.0+-}+lazyByteStringArray :: Key -> TomlCodec BL.ByteString+lazyByteStringArray = match _LByteStringArray+{-# INLINE lazyByteStringArray #-}+ -- | Codec for zoned time values. zonedTime :: Key -> TomlCodec ZonedTime zonedTime = match _ZonedTime+{-# INLINE zonedTime #-}  -- | Codec for local time values. localTime :: Key -> TomlCodec LocalTime localTime = match _LocalTime+{-# INLINE localTime #-}  -- | Codec for day values. day :: Key -> TomlCodec Day day = match _Day+{-# INLINE day #-}  -- | Codec for time of day values. timeOfDay :: Key -> TomlCodec TimeOfDay timeOfDay = match _TimeOfDay+{-# INLINE timeOfDay #-}  -- | Codec for list of values. Takes converter for single value and -- returns a list of values. arrayOf :: TomlBiMap a AnyValue -> Key -> TomlCodec [a] arrayOf = match . _Array+{-# INLINE arrayOf #-}  -- | Codec for sets. Takes converter for single value and -- returns a set of values. arraySetOf :: Ord a => TomlBiMap a AnyValue -> Key -> TomlCodec (Set a) arraySetOf = match . _Set+{-# INLINE arraySetOf #-}  -- | Codec for sets of ints. Takes converter for single value and -- returns a set of ints. arrayIntSet :: Key -> TomlCodec IntSet arrayIntSet = match _IntSet+{-# INLINE arrayIntSet #-}  -- | Codec for hash sets. Takes converter for single hashable value and -- returns a set of hashable values.@@ -220,11 +272,13 @@     -> Key     -> TomlCodec (HashSet a) arrayHashSetOf = match . _HashSet+{-# INLINE arrayHashSetOf #-}  -- | Codec for non- empty lists of values. Takes converter for single value and -- returns a non-empty list of values. arrayNonEmptyOf :: TomlBiMap a AnyValue -> Key -> TomlCodec (NonEmpty a) arrayNonEmptyOf = match . _NonEmpty+{-# INLINE arrayNonEmptyOf #-}  {- | Prepends given key to all errors that contain key. This function is used to give better error messages. So when error happens we know all pieces of table@@ -298,3 +352,20 @@   where     nonEmptyCodec :: TomlCodec (NonEmpty a)     nonEmptyCodec = nonEmpty codec key++{- | 'Codec' for set of values. Represented in TOML as array of tables.++@since 1.2.0.0+-}+set :: forall a . Ord a => TomlCodec a -> Key -> TomlCodec (Set a)+set codec key = dimap S.toList S.fromList (list codec key)+{-# INLINE set #-}++{- | 'Codec' for HashSet of values. Represented in TOML as array of tables.++@since 1.2.0.0+-}++hashSet :: forall a . (Hashable a, Eq a) => TomlCodec a -> Key -> TomlCodec (HashSet a)+hashSet codec key = dimap HS.toList HS.fromList (list codec key)+{-# INLINE hashSet #-}
src/Toml/Bi/Map.hs view
@@ -21,18 +21,6 @@        , wrongConstructor        , prettyBiMapError -         -- * Helpers for BiMap and AnyValue-       , mkAnyValueBiMap-       , _TextBy-       , _LTextText-       , _NaturalInteger-       , _StringText-       , _ReadString-       , _BoundedInteger-       , _EnumBoundedText-       , _ByteStringText-       , _LByteStringText-          -- * Some predefined bi mappings        , _Array        , _Bool@@ -48,15 +36,29 @@        , _Read        , _Natural        , _Word+       , _Word8        , _Int        , _Float        , _ByteString        , _LByteString+       , _ByteStringArray+       , _LByteStringArray+       , _NonEmpty        , _Set        , _IntSet        , _HashSet-       , _NonEmpty +         -- * Helpers for BiMap and AnyValue+       , mkAnyValueBiMap+       , _TextBy+       , _LTextText+       , _NaturalInteger+       , _StringText+       , _ReadString+       , _BoundedInteger+       , _EnumBoundedText+       , _ByteStringText+       , _LByteStringText        , _Left        , _Right        , _EnumBounded@@ -76,7 +78,7 @@ import Data.Semigroup (Semigroup (..)) import Data.Text (Text) import Data.Time (Day, LocalTime, TimeOfDay, ZonedTime)-import Data.Word (Word)+import Data.Word (Word8) import GHC.Generics (Generic) import Numeric.Natural (Natural) import Text.Read (readEither)@@ -86,6 +88,7 @@                   matchZoned, mkMatchError, toMArray)  import qualified Control.Category as Cat+import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as BL import qualified Data.HashSet as HS import qualified Data.IntSet as IS@@ -125,16 +128,19 @@ instance Cat.Category (BiMap e) where     id :: BiMap e a a     id = BiMap Right Right+    {-# INLINE id #-}      (.) :: BiMap e b c -> BiMap e a b -> BiMap e a c     bc . ab = BiMap         { forward  =  forward ab >=>  forward bc         , backward = backward bc >=> backward ab         }+    {-# INLINE (.) #-}  -- | Inverts bidirectional mapping. invert :: BiMap e a b -> BiMap e b a invert (BiMap f g) = BiMap g f+{-# INLINE invert #-}  {- | Creates 'BiMap' from isomorphism. Can be used in the following way: @@ -154,6 +160,7 @@ -} iso :: (a -> b) -> (b -> a) -> BiMap e a b iso f g = BiMap (Right . f) (Right . g)+{-# INLINE iso #-}  {- | Creates 'BiMap' from prism-like pair of functions. This combinator can be used to create 'BiMap' for custom data types like this:@@ -175,8 +182,12 @@     other    -> Toml.'wrongConstructor' \"Client\" other @ -}-prism :: (field -> object) -> (object -> Either error field) -> BiMap error object field+prism+    :: (field -> object)+    -> (object -> Either error field)+    -> BiMap error object field prism review preview = BiMap preview (Right . review)+{-# INLINE prism #-}  ---------------------------------------------------------------------------- -- BiMap error types@@ -190,13 +201,14 @@     = WrongConstructor -- ^ Error for cases with wrong constructors. For                        -- example, you're trying to convert 'Left' but                        -- bidirectional converter expects 'Right'.-        Text           -- ^ Expected constructor name-        Text           -- ^ Actual value+        !Text          -- ^ Expected constructor name+        !Text          -- ^ Actual value     | WrongValue       -- ^ Error for cases with wrong values-        MatchError     -- ^ Information about failed matching+        !MatchError    -- ^ Information about failed matching     | ArbitraryError   -- ^ Arbitrary textual error-        Text           -- ^ Error message-    deriving (Eq, Show, Generic, NFData)+        !Text          -- ^ Error message+    deriving stock (Eq, Show, Generic)+    deriving anyclass (NFData)  -- | Converts 'TomlBiMapError' into pretty human-readable text. prettyBiMapError :: TomlBiMapError -> Text@@ -283,77 +295,91 @@ -} _Bool :: TomlBiMap Bool AnyValue _Bool = mkAnyValueBiMap matchBool Bool+{-# INLINE _Bool #-}  {- | 'Prelude.Integer' bimap for 'AnyValue'. Usually used as 'Toml.Bi.Combinators.integer' combinator. -} _Integer :: TomlBiMap Integer AnyValue _Integer = mkAnyValueBiMap matchInteger Integer+{-# INLINE _Integer #-}  {- | 'Prelude.Double' bimap for 'AnyValue'. Usually used as 'Toml.Bi.Combinators.double' combinator. -} _Double :: TomlBiMap Double AnyValue _Double = mkAnyValueBiMap matchDouble Double+{-# INLINE _Double #-}  {- | 'Data.Text.Text' bimap for 'AnyValue'. Usually used as 'Toml.Bi.Combinators.text' combinator. -} _Text :: TomlBiMap Text AnyValue _Text = mkAnyValueBiMap matchText Text+{-# INLINE _Text #-}  -- | Helper bimap for 'Data.Text.Lazy.Text' and 'Data.Text.Text'. _LTextText :: BiMap e TL.Text Text _LTextText = iso TL.toStrict TL.fromStrict+{-# INLINE _LTextText #-}  {- | 'Data.Text.Lazy.Text' bimap for 'AnyValue'. Usually used as 'Toml.Bi.Combinators.lazyText' combinator. -} _LText :: TomlBiMap TL.Text AnyValue _LText = _LTextText >>> _Text+{-# INLINE _LText #-}  {- | 'Data.Time.ZonedTime' bimap for 'AnyValue'. Usually used as 'Toml.Bi.Combinators.zonedTime' combinator. -} _ZonedTime :: TomlBiMap ZonedTime AnyValue _ZonedTime = mkAnyValueBiMap matchZoned Zoned+{-# INLINE _ZonedTime #-}  {- | 'Data.Time.LocalTime' bimap for 'AnyValue'. Usually used as 'Toml.Bi.Combinators.localTime' combinator. -} _LocalTime :: TomlBiMap LocalTime AnyValue _LocalTime = mkAnyValueBiMap matchLocal Local+{-# INLINE _LocalTime #-}  {- | 'Data.Time.Day' bimap for 'AnyValue'. Usually used as 'Toml.Bi.Combinators.day' combinator. -} _Day :: TomlBiMap Day AnyValue _Day = mkAnyValueBiMap matchDay Day+{-# INLINE _Day #-}  {- | 'Data.Time.TimeOfDay' bimap for 'AnyValue'. Usually used as 'Toml.Bi.Combinators.timeOfDay' combinator. -} _TimeOfDay :: TomlBiMap TimeOfDay AnyValue _TimeOfDay = mkAnyValueBiMap matchHours Hours+{-# INLINE _TimeOfDay #-}  -- | Helper bimap for 'String' and 'Data.Text.Text'. _StringText :: BiMap e String Text _StringText = iso T.pack T.unpack+{-# INLINE _StringText #-}  {- | 'String' bimap for 'AnyValue'. Usually used as 'Toml.Bi.Combinators.string' combinator. -} _String :: TomlBiMap String AnyValue _String = _StringText >>> _Text+{-# INLINE _String #-}  -- | Helper bimap for 'String' and types with 'Read' and 'Show' instances. _ReadString :: (Show a, Read a) => TomlBiMap a String _ReadString = BiMap (Right . show) (first (ArbitraryError . T.pack) . readEither)+{-# INLINE _ReadString #-}  -- | Bimap for 'AnyValue' and values with a 'Read' and 'Show' instance. -- Usually used as 'Toml.Bi.Combinators.read' combinator. _Read :: (Show a, Read a) => TomlBiMap a AnyValue _Read = _ReadString >>> _String+{-# INLINE _Read #-}  -- | Helper bimap for 'Natural' and 'Prelude.Integer'. _NaturalInteger :: TomlBiMap Natural Integer@@ -369,6 +395,7 @@ -} _Natural :: TomlBiMap Natural AnyValue _Natural = _NaturalInteger >>> _Integer+{-# INLINE _Natural #-}  -- | Helper bimap for 'Prelude.Integer' and integral, bounded values. _BoundedInteger :: (Integral a, Bounded a, Show a) => TomlBiMap a Integer@@ -384,7 +411,7 @@          in Left $ ArbitraryError msg       | otherwise = Right (fromIntegral n) -{- | Helper bimap for 'EnumBounded' and 'Data.Text.Text'.+{- | Helper bimap for '_EnumBounded' and 'Data.Text.Text'.  @since 1.1.1.0 -}@@ -413,24 +440,37 @@ -} _EnumBounded :: (Show a, Enum a, Bounded a) => TomlBiMap a AnyValue _EnumBounded = _EnumBoundedText >>> _Text+{-# INLINE _EnumBounded #-}  {- | 'Word' bimap for 'AnyValue'. Usually used as 'Toml.Bi.Combinators.word' combinator. -} _Word :: TomlBiMap Word AnyValue _Word = _BoundedInteger >>> _Integer+{-# INLINE _Word #-} +{- | 'Word8' bimap for 'AnyValue'. Usually used as+'Toml.Bi.Combinators.word8' combinator.++@since 1.2.0.0+-}+_Word8 :: TomlBiMap Word8 AnyValue+_Word8 = _BoundedInteger >>> _Integer+{-# INLINE _Word8 #-}+ {- | 'Int' bimap for 'AnyValue'. Usually used as 'Toml.Bi.Combinators.int' combinator. -} _Int :: TomlBiMap Int AnyValue _Int = _BoundedInteger >>> _Integer+{-# INLINE _Int #-}  {- | 'Float' bimap for 'AnyValue'. Usually used as 'Toml.Bi.Combinators.float' combinator. -} _Float :: TomlBiMap Float AnyValue _Float = iso realToFrac realToFrac >>> _Double+{-# INLINE _Float #-}  -- | Helper bimap for 'Data.Text.Text' and strict 'ByteString' _ByteStringText :: TomlBiMap ByteString Text@@ -438,11 +478,13 @@   where     eitherText :: ByteString -> Either TomlBiMapError Text     eitherText = either (\err -> Left $ ArbitraryError $ tShow err) Right . T.decodeUtf8'+{-# INLINE _ByteStringText #-}  -- | UTF8 encoded 'ByteString' bimap for 'AnyValue'. -- Usually used as 'Toml.Bi.Combinators.byteString' combinator. _ByteString :: TomlBiMap ByteString AnyValue _ByteString = _ByteStringText >>> _Text+{-# INLINE _ByteString #-}  -- | Helper bimap for 'Data.Text.Text' and lazy 'BL.ByteString'. _LByteStringText :: TomlBiMap BL.ByteString Text@@ -450,12 +492,32 @@   where     eitherText :: BL.ByteString -> Either TomlBiMapError Text     eitherText = bimap (ArbitraryError . tShow) TL.toStrict . TL.decodeUtf8'+{-# INLINE _LByteStringText #-}  -- | UTF8 encoded lazy 'BL.ByteString' bimap for 'AnyValue'. -- Usually used as 'Toml.Bi.Combinators.lazyByteString' combinator. _LByteString :: TomlBiMap BL.ByteString AnyValue _LByteString = _LByteStringText >>> _Text+{-# INLINE _LByteString #-} +{- | 'ByteString' bimap for 'AnyValue' encoded as a list of non-negative integers.+Usually used as 'Toml.Bi.Combinators.byteStringArray' combinator.++@since 1.2.0.0+-}+_ByteStringArray :: TomlBiMap ByteString AnyValue+_ByteStringArray = iso BS.unpack BS.pack >>> _Array _Word8+{-# INLINE _ByteStringArray #-}++{- | Lazy 'ByteString' bimap for 'AnyValue' encoded as a list of non-negative integers.+Usually used as 'Toml.Bi.Combinators.lazyByteStringArray' combinator.++@since 1.2.0.0+-}+_LByteStringArray :: TomlBiMap BL.ByteString AnyValue+_LByteStringArray = iso BL.unpack BL.pack >>>  _Array _Word8+{-# INLINE _LByteStringArray #-}+ -- | Takes a bimap of a value and returns a bimap between a list of values and 'AnyValue' -- as an array. Usually used as 'Toml.Bi.Combinators.arrayOf' combinator. _Array :: forall a . TomlBiMap a AnyValue -> TomlBiMap [a] AnyValue@@ -477,28 +539,34 @@ -- as an array. Usually used as 'Toml.Bi.Combinators.nonEmpty' combinator. _NonEmpty :: TomlBiMap a AnyValue -> TomlBiMap (NE.NonEmpty a) AnyValue _NonEmpty bi = _NonEmptyArray >>> _Array bi+{-# INLINE _NonEmpty #-}  _NonEmptyArray :: TomlBiMap (NE.NonEmpty a) [a] _NonEmptyArray = BiMap     { forward  = Right . NE.toList     , backward = maybe (Left $ ArbitraryError "Empty array list, but expected NonEmpty") Right . NE.nonEmpty     }+{-# INLINE _NonEmptyArray #-}  -- | Takes a bimap of a value and returns a bimap between a set of values and 'AnyValue' -- as an array. Usually used as 'Toml.Bi.Combinators.arraySetOf' combinator. _Set :: (Ord a) => TomlBiMap a AnyValue -> TomlBiMap (S.Set a) AnyValue _Set bi = iso S.toList S.fromList >>> _Array bi+{-# INLINE _Set #-}  -- | Takes a bimap of a value and returns a bimap between a hash set of values and 'AnyValue' -- as an array. Usually used as 'Toml.Bi.Combinators.arrayHashSetOf' combinator. _HashSet :: (Eq a, Hashable a) => TomlBiMap a AnyValue -> TomlBiMap (HS.HashSet a) AnyValue _HashSet bi = iso HS.toList HS.fromList >>> _Array bi+{-# INLINE _HashSet #-}  {- | 'IS.IntSet' bimap for 'AnyValue'. Usually used as 'Toml.Bi.Combinators.arrayIntSet' combinator. -} _IntSet :: TomlBiMap IS.IntSet AnyValue _IntSet = iso IS.toList IS.fromList >>> _Array _Int+{-# INLINE _IntSet #-}  tShow :: Show a => a -> Text tShow = T.pack . show+{-# INLINE tShow #-}
src/Toml/Bi/Monad.hs view
@@ -6,6 +6,7 @@        , dimap        , dioptional        , diwrap+       , dimatch        , (<!>)        , (.=)        ) where@@ -54,6 +55,7 @@         { codecRead  = f <$> codecRead codec         , codecWrite = fmap f . codecWrite codec         }+    {-# INLINE fmap #-}  instance (Applicative r, Applicative w) => Applicative (Codec r w c) where     pure :: a -> Codec r w c a@@ -61,12 +63,14 @@         { codecRead  = pure a         , codecWrite = \_ -> pure a         }+    {-# INLINE pure #-}      (<*>) :: Codec r w c (a -> b) -> Codec r w c a -> Codec r w c b     codecf <*> codeca = Codec         { codecRead  = codecRead codecf <*> codecRead codeca         , codecWrite = \c -> codecWrite codecf c <*> codecWrite codeca c         }+    {-# INLINE (<*>) #-}  instance (Monad r, Monad w) => Monad (Codec r w c) where     (>>=) :: Codec r w c a -> (a -> Codec r w c b) -> Codec r w c b@@ -74,6 +78,7 @@         { codecRead  = codecRead codec >>= \a -> codecRead (f a)         , codecWrite = \c -> codecWrite codec c >>= \a -> codecWrite (f a) c         }+    {-# INLINE (>>=) #-}  instance (Alternative r, Alternative w) => Alternative (Codec r w c) where     empty :: Codec r w c a@@ -81,21 +86,26 @@         { codecRead  = empty         , codecWrite = \_ -> empty         }+    {-# INLINE empty #-}      (<|>) :: Codec r w c a -> Codec r w c a -> Codec r w c a     codec1 <|> codec2 = Codec         { codecRead  = codecRead codec1 <|> codecRead codec2         , codecWrite = \c -> codecWrite codec1 c <|> codecWrite codec2 c         }+    {-# INLINE (<|>) #-}  instance (MonadPlus r, MonadPlus w) => MonadPlus (Codec r w c) where     mzero = empty+    {-# INLINE mzero #-}     mplus = (<|>)+    {-# INLINE mplus #-}  -- | Alternative instance for function arrow but without 'empty'. infixl 3 <!> (<!>) :: Alternative f => (a -> f x) -> (a -> f x) -> (a -> f x) f <!> g = \a -> f a <|> g a+{-# INLINE (<!>) #-}  {- | This is an instance of @Profunctor@ for 'Codec'. But since there's no @Profunctor@ type class in @base@ or package with no dependencies (and we don't@@ -151,6 +161,7 @@     { codecRead  = g <$> codecRead codec     , codecWrite = fmap g . codecWrite codec . f     }+{-# INLINE dimap #-}  {- | Bidirectional converter for @Maybe a@ values. For example, given the data type:@@ -179,6 +190,7 @@     { codecRead  = optional codecRead     , codecWrite = traverse codecWrite     }+{-# INLINE dioptional #-}  {- | Combinator used for @newtype@ wrappers. For example, given the data types: @@ -206,7 +218,55 @@     => BiCodec r w a     -> BiCodec r w b diwrap = dimap coerce coerce+{-# INLINE diwrap #-} +{- | Bidirectional converter for @sum types@. For example, given the data+type:++@+__data__ Example+    = Foo Int+    | Bar Bool Int+@++the TOML codec will look like++@+matchFoo :: Example -> Maybe Int+matchFoo (Foo num) = Just num+matchFoo _         = Nothing++matchBar :: Example -> Maybe (Bool, Int)+matchBar (Bar b num) = Just (b, num)+matchBar _           = Nothing++barCodec :: TomlCodec (Bool, Int)+barCodec = (,)+    \<$\> Toml.bool "a" '.=' fst+    \<*\> Toml.int "b" '.=' snd++exampleCodec :: TomlCodec Example+exampleCodec =+    dimatch matchFoo Foo (Toml.int "foo")+    \<|\> dimatch matchBar (uncurry Bar) (Toml.table barCodec "bar")+@++@since 1.2.0.0+-}+dimatch+    :: (Functor r, Alternative w)+    => (c -> Maybe d) -- ^ Mapper for consumer+    -> (a -> b)       -- ^ Mapper for producer+    -> Codec r w d a  -- ^ Source 'Codec' object+    -> Codec r w c b  -- ^ Target 'Codec' object+dimatch match ctor codec = Codec+    { codecRead = ctor <$> codecRead codec+    , codecWrite = \c -> case match c of+        Nothing -> empty+        Just d  -> ctor <$> codecWrite codec d+    }+{-# INLINE dimatch #-}+ {- | Operator to connect two operations:  1. How to get field from object?@@ -229,3 +289,4 @@ infixl 5 .= (.=) :: Codec r w field a -> (object -> field) -> Codec r w object a codec .= getter = codec { codecWrite = codecWrite codec . getter }+{-# INLINE (.=) #-}
src/Toml/Edsl.hs view
@@ -58,19 +58,24 @@ -- | Creates 'TOML' from the 'TDSL'. mkToml :: TDSL -> TOML mkToml env = execState env mempty+{-# INLINE mkToml #-}  -- | Creates an empty 'TDSL'. empty :: TDSL empty = put mempty+{-# INLINE empty #-}  -- | Adds key-value pair to the 'TDSL'. (=:) :: Key -> Value a -> TDSL (=:) k v = modify $ insertKeyVal k v+{-# INLINE (=:) #-}  -- | Adds table to the 'TDSL'. table :: Key -> TDSL -> TDSL table k = modify . insertTable k . mkToml+{-# INLINE table #-}  -- | Adds array of tables to the 'TDSL'. tableArray :: Key -> NonEmpty TDSL -> TDSL tableArray k = modify . insertTableArrays k . fmap mkToml+{-# INLINE tableArray #-}
src/Toml/Generic.hs view
@@ -116,16 +116,19 @@        ) where  import Data.Char (isLower, toLower)+import Data.Hashable (Hashable)+import Data.HashSet (HashSet) import Data.IntSet (IntSet) import Data.Kind (Type) import Data.List (stripPrefix) import Data.List.NonEmpty (NonEmpty) import Data.Proxy (Proxy (..))+import Data.Set (Set) import Data.String (IsString (..)) import Data.Text (Text) import Data.Time (Day, LocalTime, TimeOfDay, ZonedTime) import Data.Typeable (Typeable, typeRep)-import Data.Word (Word)+import Data.Word (Word8) import GHC.Generics ((:*:) (..), (:+:), C1, D1, Generic (..), K1 (..), M1 (..), Rec0, S1,                      Selector (..)) import GHC.TypeLits (ErrorMessage (..), TypeError)@@ -230,6 +233,8 @@ -- Generic typeclasses ---------------------------------------------------------------------------- +{- | Helper class to derive TOML codecs generically.+-} class GenericCodec (f :: k -> Type) where     genericTomlCodec :: GenericOptions -> TomlCodec (f p) @@ -288,6 +293,8 @@ instance HasItemCodec Bool      where hasItemCodec = Left Toml._Bool instance HasItemCodec Int       where hasItemCodec = Left Toml._Int instance HasItemCodec Word      where hasItemCodec = Left Toml._Word+-- | @since 1.2.0.0+instance HasItemCodec Word8     where hasItemCodec = Left Toml._Word8 instance HasItemCodec Integer   where hasItemCodec = Left Toml._Integer instance HasItemCodec Natural   where hasItemCodec = Left Toml._Natural instance HasItemCodec Double    where hasItemCodec = Left Toml._Double@@ -322,6 +329,8 @@ instance HasCodec Bool      where hasCodec = Toml.bool instance HasCodec Int       where hasCodec = Toml.int instance HasCodec Word      where hasCodec = Toml.word+-- | @since 1.2.0.0+instance HasCodec Word8     where hasCodec = Toml.word8 instance HasCodec Integer   where hasCodec = Toml.integer instance HasCodec Natural   where hasCodec = Toml.natural instance HasCodec Double    where hasCodec = Toml.double@@ -346,6 +355,18 @@     hasCodec = case hasItemCodec @a of         Left prim   -> Toml.arrayNonEmptyOf prim         Right codec -> Toml.nonEmpty codec++-- | @since 1.2.0.0+instance (Ord a, HasItemCodec a) => HasCodec (Set a) where+    hasCodec = case hasItemCodec @a of+        Left prim   -> Toml.arraySetOf prim+        Right codec -> Toml.set codec++-- | @since 1.2.0.0+instance (Hashable a, Eq a, HasItemCodec a) => HasCodec (HashSet a) where+    hasCodec = case hasItemCodec @a of+        Left prim   -> Toml.arrayHashSetOf prim+        Right codec -> Toml.hashSet codec  {- TODO: uncomment when higher-kinded roles will be implemented
src/Toml/Parser.hs view
@@ -8,18 +8,22 @@        ) where  import Control.DeepSeq (NFData)-import Data.Text (Text, pack)+import Data.Text (Text) import GHC.Generics (Generic) -import Toml.Parser.TOML (tomlP)+import Toml.Parser.Item (tomlP)+import Toml.Parser.Validate (validateItems) import Toml.Type (TOML) +import qualified Data.Text as T import qualified Toml.Parser.Core as P (errorBundlePretty, parse)   -- | Pretty parse exception for parsing toml.-newtype ParseException = ParseException Text-    deriving (Show, Eq, Generic, NFData)+newtype ParseException = ParseException+    { unParseException :: Text+    } deriving stock (Show, Generic)+      deriving newtype (Eq, NFData)  {- | Parses 'Text' as 'TOML' AST object. If you want to convert 'Text' to your custom haskell data type, use 'Toml.Bi.Code.decode' or 'Toml.Bi.Code.decodeFile'@@ -27,5 +31,7 @@ -} parse :: Text -> Either ParseException TOML parse t = case P.parse tomlP "" t of-    Left err   -> Left $ ParseException $ pack $ P.errorBundlePretty err-    Right toml -> Right toml+    Left err    -> Left $ ParseException $ T.pack $ P.errorBundlePretty err+    Right items -> case validateItems items of+        Left err   -> Left $ ParseException $ T.pack $ show err+        Right toml -> Right toml
+ src/Toml/Parser/Item.hs view
@@ -0,0 +1,104 @@+{-| This module contains the definition of the 'TomlItem' data type which+represents either key-value pair or table name. This data type serves the+purpose to be the intermediate representation of parsing a TOML file which will+be assembled to TOML AST later.++@since 1.2.0.0+-}++module Toml.Parser.Item+       ( TomlItem (..)+       , Table (..)+       , setTableName++       , tomlP+       ) where++import Control.Applicative (liftA2, many)+import Control.Applicative.Combinators.NonEmpty (sepEndBy1)+import Control.Monad.Combinators (between, sepEndBy)+import Data.Foldable (asum)+import Data.List.NonEmpty (NonEmpty)++import Toml.Parser.Core (Parser, eof, sc, text, try, (<?>))+import Toml.Parser.Key (keyP, tableArrayNameP, tableNameP)+import Toml.Parser.Value (anyValueP)+import Toml.PrefixTree (Key)+import Toml.Type (AnyValue)+++{- | One item of a TOML file. It could be either:++* A name of a table+* A name of a table array+* Key-value pair+* Inline table+* Inline array of tables++Knowing a list of 'TomlItem's, it's possible to construct 'Toml.Type.TOML.TOML'+from this information.+-}+data TomlItem+    = TableName !Key+    | TableArrayName !Key+    | KeyVal !Key !AnyValue+    | InlineTable !Key !Table+    | InlineTableArray !Key !(NonEmpty Table)+    deriving stock (Show, Eq)++{- | Changes name of table to a new one. Works only for 'TableName' and+'TableArrayName' constructors.+-}+setTableName :: Key -> TomlItem -> TomlItem+setTableName new = \case+    TableName _ -> TableName new+    TableArrayName _ -> TableArrayName new+    item -> item++{- | Table that contains only @key = val@ pairs.+-}+newtype Table = Table+    { unTable :: [(Key, AnyValue)]+    } deriving stock (Show)+      deriving newtype (Eq)++----------------------------------------------------------------------------+-- Parser+----------------------------------------------------------------------------++-- | Parser for inline tables.+inlineTableP :: Parser Table+inlineTableP =+    fmap Table+    $ between (text "{") (text "}")+    $ (liftA2 (,) (keyP <* text "=") anyValueP) `sepEndBy` text ","++-- | Parser for inline arrays of tables.+inlineTableArrayP :: Parser (NonEmpty Table)+inlineTableArrayP = between (text "[") (text "]")+    $ inlineTableP `sepEndBy1` text ","++-- | Parser for a single item in the TOML file.+tomlItemP :: Parser TomlItem+tomlItemP = asum+    [ TableName <$> try tableNameP <?> "table name"+    , TableArrayName <$> tableArrayNameP <?> "array of tables name"+    , keyValP+    ]+  where+    -- parser for "key = val" pairs; can be one of three forms:+    -- 1. key = { ... }+    -- 2. key = [ {...}, {...}, ... ]+    -- 3. key = ...+    keyValP :: Parser TomlItem+    keyValP = do+        key <- keyP <* text "="+        asum+            [ InlineTable key <$> inlineTableP <?> "inline table"+            , InlineTableArray key <$> try inlineTableArrayP <?> "inline array of tables"+            , KeyVal key <$> anyValueP <?> "key-value pair"+            ]++-- | Parser for the full content of the .toml file.+tomlP :: Parser [TomlItem]+tomlP = sc *> many tomlItemP <* eof
+ src/Toml/Parser/Key.hs view
@@ -0,0 +1,51 @@+{- | Parsers for keys and table names.++@since 1.2.0.0+-}++module Toml.Parser.Key+       ( keyP+       , tableNameP+       , tableArrayNameP+       ) where++import Control.Applicative (Alternative (..))+import Control.Applicative.Combinators.NonEmpty (sepBy1)+import Control.Monad.Combinators (between)+import Data.Semigroup ((<>))+import Data.Text (Text)++import Toml.Parser.Core (Parser, alphaNumChar, char, lexeme, text)+import Toml.Parser.String (basicStringP, literalStringP)+import Toml.PrefixTree (Key (..), Piece (..))++import qualified Data.Text as Text+++-- | Parser for bare key piece, like @foo@.+bareKeyPieceP :: Parser Text+bareKeyPieceP = lexeme $ Text.pack <$> bareStrP+  where+    bareStrP :: Parser String+    bareStrP = some $ alphaNumChar <|> char '_' <|> char '-'++-- | Parser for 'Piece'.+keyComponentP :: Parser Piece+keyComponentP = Piece <$>+    (bareKeyPieceP <|> (quote "\"" <$> basicStringP) <|> (quote "'" <$> literalStringP))+  where+    -- adds " or ' to both sides+    quote :: Text -> Text -> Text+    quote q t = q <> t <> q++-- | Parser for 'Key': dot-separated list of 'Piece'.+keyP :: Parser Key+keyP = Key <$> keyComponentP `sepBy1` char '.'++-- | Parser for table name: 'Key' inside @[]@.+tableNameP :: Parser Key+tableNameP = between (text "[") (text "]") keyP++-- | Parser for array of tables name: 'Key' inside @[[]]@.+tableArrayNameP :: Parser Key+tableArrayNameP = between (text "[[") (text "]]") keyP
− src/Toml/Parser/TOML.hs
@@ -1,134 +0,0 @@-{- | Parser for 'TOML' data type: keys, tables, array of tables. Uses value-parsers from "Toml.Parser.Value".--}--module Toml.Parser.TOML-       ( keyP-       , tomlP-       ) where--import Control.Applicative (Alternative (..))-import Control.Monad.Combinators (between, sepEndBy)-import Data.List.NonEmpty (NonEmpty (..))-import Data.Semigroup ((<>))-import Data.Text (Text)--import Toml.Parser.Core (Parser, alphaNumChar, char, eof, lexeme, sc, text, try)-import Toml.Parser.String (basicStringP, literalStringP)-import Toml.Parser.Value (anyValueP)-import Toml.PrefixTree (Key (..), KeysDiff (..), Piece (..), keysDiff, single)-import Toml.Type (AnyValue, TOML (..))--import qualified Control.Applicative.Combinators.NonEmpty as NC-import qualified Data.HashMap.Lazy as HashMap-import qualified Data.Text as Text----- | Parser for bare key piece, like @foo@.-bareKeyPieceP :: Parser Text-bareKeyPieceP = lexeme $ Text.pack <$> bareStrP-  where-    bareStrP :: Parser String-    bareStrP = some $ alphaNumChar <|> char '_' <|> char '-'---- | Parser for 'Piece'.-keyComponentP :: Parser Piece-keyComponentP = Piece <$>-    (bareKeyPieceP <|> (quote "\"" <$> basicStringP) <|> (quote "'" <$> literalStringP))-  where-    -- adds " or ' to both sides-    quote :: Text -> Text -> Text-    quote q t = q <> t <> q---- | Parser for 'Key': dot-separated list of 'Piece'.-keyP :: Parser Key-keyP = Key <$> keyComponentP `NC.sepBy1` char '.'---- | Parser for table name: 'Key' inside @[]@.-tableNameP :: Parser Key-tableNameP = between (text "[") (text "]") keyP---- | Parser for array of tables name: 'Key' inside @[[]]@.-tableArrayNameP :: Parser Key-tableArrayNameP = between (text "[[") (text "]]") keyP---- Helper functions for building TOML-tomlKV :: Key -> AnyValue -> TOML-tomlKV k v = mempty { tomlPairs = HashMap.singleton k v }--tomlT :: Key -> TOML -> TOML-tomlT k t = mempty { tomlTables = single k t }--tomlA :: Key -> NonEmpty TOML -> TOML-tomlA k a = mempty { tomlTableArrays = HashMap.singleton k a }---- | Parser for lines starting with 'key =', either values, inline tables or--- inline arrays of tables.-hasKeyP :: Maybe Key -> Parser TOML-hasKeyP key = do-    k <- keyP <* text "="-    table k <|> try (tableArray k) <|> (tomlKV k <$> anyValueP)-  where-    table :: Key -> Parser TOML-    table k = do-        (kDiff, _) <- childKeyP key (pure k)-        tomlT kDiff <$> inlineTableP--    tableArray :: Key -> Parser TOML-    tableArray k = do-        (kDiff, _) <- childKeyP key (pure k)-        tomlA kDiff <$> inlineTableArrayP---- | Parser for inline tables.-inlineTableP :: Parser TOML-inlineTableP = between (text "{") (text "}") $-    mconcat <$>-    (tomlKV <$> (keyP <* text "=") <*> anyValueP ) `sepEndBy` text ","---- | Parser for inline arrays of tables.-inlineTableArrayP :: Parser (NonEmpty TOML)-inlineTableArrayP = between (text "[") (text "]") $-    inlineTableP `NC.sepEndBy1` text ","---- | Parser for an array of tables under a certain key.-tableArrayP :: Key -> Parser (NonEmpty TOML)-tableArrayP key =-    localTomlP (Just key) `NC.sepBy1` sameKeyP key tableArrayNameP---- | Parser for a '.toml' file-tomlP :: Parser TOML-tomlP = sc *> localTomlP Nothing <* eof---- | Parser for a toml under a certain key-localTomlP :: Maybe Key -> Parser TOML-localTomlP key = mconcat <$> many (subArray <|> subTable <|> (try $ hasKeyP key))-  where-    subTable :: Parser TOML-    subTable = do-        (kDiff, k) <- try $ childKeyP key tableNameP-        tomlT kDiff <$> localTomlP (Just k)--    subArray :: Parser TOML-    subArray = do-        (kDiff, k) <- try $ childKeyP key tableArrayNameP-        tomlA kDiff <$> tableArrayP k---- | @childKeyP (Just key) p@ checks if the result of @p@ if a child key of--- @key@ and returns the difference of the keys and the child key.--- @childKeyP Nothing p@ is only called from @tomlP@ (no parent key).-childKeyP :: Maybe Key -> Parser Key -> Parser (Key, Key)-childKeyP Nothing parser = (\k -> (k, k)) <$> parser-childKeyP (Just key) parser = do-    k <- parser-    case keysDiff key k of-        FstIsPref d -> pure (d, k)-        _           -> fail $ show k ++ " is not a child key of " ++ show key---- | @sameKeyP key p@ returns the result of @p@ if the key returned by @p@ is--- the same as @key@, and fails otherwise.-sameKeyP :: Key -> Parser Key -> Parser Key-sameKeyP key parser = try $ do-    k <- parser-    case keysDiff key k of-        Equal -> pure k-        _     -> fail $ show k ++ " is not the same as " ++ show key
+ src/Toml/Parser/Validate.hs view
@@ -0,0 +1,181 @@+{- | This module contains functions that aggregate the result of+'Toml.Parser.Item.tomlP' parser into 'TOML'. This approach allows to keep parser+fast and simple and delegate the process of creating tree structure to a+separate function.++@since 1.2.0.0+-}++module Toml.Parser.Validate+       ( -- * Decoding+         validateItems+       , ValidationError (..)++         -- * Internal helpers+       , groupItems+       , groupWithParent+       , validateItemForest+       ) where++import Data.Bifunctor (first)+import Data.List.NonEmpty (NonEmpty (..))+import Data.Semigroup ((<>))+import Data.Tree (Forest, Tree (..))++import Toml.Parser.Item (Table (..), TomlItem (..), setTableName)+import Toml.PrefixTree (Key, KeysDiff (FstIsPref), keysDiff)+import Toml.Type (TOML (..), insertKeyAnyVal, insertTable, insertTableArrays)++import qualified Data.HashMap.Strict as HashMap+import qualified Toml.PrefixTree as PrefixMap+++{- | Validate list of 'TomlItem's and convert to 'TOML' if not validation+errors are found.+-}+validateItems :: [TomlItem] -> Either ValidationError TOML+validateItems = validateItemForest . groupItems++----------------------------------------------------------------------------+-- Grouping+----------------------------------------------------------------------------++{- | This function takes flat list of 'TomlItem's and groups it into list of+'Tree's by putting all corresponding items inside tables and table arrays.  It+doesn't perform any validation, just groups items according to prefixes of their+keys. So, for example, if you have the following keys as flat list:++@+aaa              # ordinary key+aaa.bbb          # ordinary key+[foo]            # table nam+foo.bar+foo.baz+[xxx]            # table name+[xxx.yyy]        # table name+zzz+@++the following tree structure will be created:++@+aaa+aaa.bbb+[foo]+├──── foo.bar+└──── foo.baz+[xxx]+└──── [yyy]+      └──── zzz+@+-}+groupItems :: [TomlItem] -> Forest TomlItem+groupItems = fst . groupWithParent Nothing++{- | This function groups list of TOML items into 'Forest' and returns list of+items that are not children of specified parent.++__Invariant:__ When this function is called with 'Nothing', second element in+the result tuple should be empty list.+-}+groupWithParent+    :: Maybe Key   -- ^ Parent name+    -> [TomlItem]  -- ^ List of items+    -> (Forest TomlItem, [TomlItem])  -- ^ Forest of times and remaining items+groupWithParent _ [] = ([], [])+groupWithParent parent (item:items) = case item of+    KeyVal{}            -> Node item [] <:> groupWithParent parent items+    InlineTable{}       -> Node item [] <:> groupWithParent parent items+    InlineTableArray{}  -> Node item [] <:> groupWithParent parent items+    TableName name      -> groupTable item name+    TableArrayName name -> groupTable item name+  where+    -- prepend to the first list, just to remove some code noise+    (<:>) :: a -> ([a], b) -> ([a], b)+    a <:> tup = first (a :) tup++    -- takes table item and its name, collects all children into table subforest+    -- and returns all elements after the table+    groupTable :: TomlItem -> Key -> (Forest TomlItem, [TomlItem])+    groupTable tableItem tableName = case parent of+        Nothing -> tableWithChildren tableName+        Just parentKey -> case keysDiff parentKey tableName of+            FstIsPref diff -> tableWithChildren diff+            _              -> ([], item:items)+      where+        tableWithChildren :: Key -> (Forest TomlItem, [TomlItem])+        tableWithChildren newName =+            let (children, rest) = groupWithParent (Just tableName) items+                newItem = setTableName newName tableItem+            in Node newItem children <:> groupWithParent parent rest++----------------------------------------------------------------------------+-- Decoding+----------------------------------------------------------------------------++{- | Error that happens during validating TOML which is already syntactically+correct. For the list of all possible validation errors and their explanation,+see the following issue on GitHub:++* https://github.com/kowainik/tomland/issues/5+-}++data ValidationError+    = DuplicateKey !Key+    | DuplicateTable !Key+    | SameNameKeyTable !Key+    | SameNameTableArray !Key+    deriving stock (Show, Eq)++{- | Construct 'TOML' from the 'Forest' of 'TomlItem' and performing validation+of TOML at the same time.+-}+validateItemForest :: Forest TomlItem -> Either ValidationError TOML+validateItemForest = go mempty+  where+    go :: TOML -> Forest TomlItem -> Either ValidationError TOML+    go toml [] = Right toml+    go toml@TOML{..} (node:nodes) = case rootLabel node of+        -- ignore subforest here+        KeyVal key val -> do+            HashMap.lookup key tomlPairs `errorOnJust` DuplicateKey key+            PrefixMap.lookup key tomlTables `errorOnJust` SameNameKeyTable key+            go (insertKeyAnyVal key val toml) nodes++        -- ignore subforest here+        InlineTable key table -> do+            HashMap.lookup key tomlPairs `errorOnJust` SameNameKeyTable key+            PrefixMap.lookup key tomlTables `errorOnJust` DuplicateTable key+            tableToml <- createTomlFromTable table+            go (insertTable key tableToml toml) nodes++        -- ignore subforest here+        InlineTableArray key tables -> do+            PrefixMap.lookup key tomlTables `errorOnJust` SameNameTableArray key+            arrayToml <- mapM createTomlFromTable tables+            go (insertTableArrays key arrayToml toml) nodes++        TableName key -> do+            HashMap.lookup key tomlPairs `errorOnJust` SameNameKeyTable key+            PrefixMap.lookup key tomlTables `errorOnJust` DuplicateTable key+            subTable <- go mempty (subForest node)+            go (insertTable key subTable toml) nodes++        TableArrayName key -> do+            PrefixMap.lookup key tomlTables `errorOnJust` SameNameTableArray key+            subTable <- go mempty (subForest node)+            let newArray = case HashMap.lookup key tomlTableArrays of+                    Nothing  -> HashMap.insert key (subTable :| []) tomlTableArrays+                    Just arr ->+                        HashMap.insert key (arr <> (subTable :| [])) tomlTableArrays+            go (toml { tomlTableArrays = newArray }) nodes++    createTomlFromTable :: Table -> Either ValidationError TOML+    createTomlFromTable (Table table) =+        go mempty $ map (\(k, v) -> Node (KeyVal k v) []) table++++errorOnJust :: Maybe a -> e -> Either e ()+errorOnJust (Just _) e = Left e+errorOnJust Nothing  _ = Right ()
src/Toml/PrefixTree.hs view
@@ -1,7 +1,5 @@-{-# LANGUAGE DeriveAnyClass             #-}-{-# LANGUAGE DerivingStrategies         #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE PatternSynonyms            #-}+{-# LANGUAGE DeriveAnyClass  #-}+{-# LANGUAGE PatternSynonyms #-}  -- | Implementation of prefix tree for TOML AST. @@ -53,9 +51,10 @@   -- | Represents the key piece of some layer.-newtype Piece = Piece { unPiece :: Text }-    deriving stock (Generic)-    deriving newtype (Show, Eq, Ord, Hashable, IsString, NFData)+newtype Piece = Piece+    { unPiece :: Text+    } deriving stock (Generic)+      deriving newtype (Show, Eq, Ord, Hashable, IsString, NFData)  {- | Key of value in @key = val@ pair. Represents as non-empty list of key components — 'Piece's. Key like@@ -70,9 +69,10 @@ Key (Piece "site" :| [Piece "\\"google.com\\""]) @ -}-newtype Key = Key { unKey :: NonEmpty Piece }-    deriving stock (Generic)-    deriving newtype (Show, Eq, Ord, Hashable, NFData, Semigroup)+newtype Key = Key+    { unKey :: NonEmpty Piece+    } deriving stock (Generic)+      deriving newtype (Show, Eq, Ord, Hashable, NFData, Semigroup)  {- | Split a dot-separated string into 'Key'. Empty string turns into a 'Key' with single element — empty 'Piece'.@@ -113,7 +113,8 @@         !Prefix        -- ^ Greatest common key prefix.         !(Maybe a)     -- ^ Possible value at that point.         !(PrefixMap a) -- ^ Values at suffixes of the prefix.-    deriving (Show, Eq, NFData, Generic)+    deriving stock (Show, Eq, Generic)+    deriving anyclass (NFData)  instance Semigroup (PrefixTree a) where     a <> b = foldl' (\tree (k, v) -> insertT k v tree) a (toListT b)@@ -130,7 +131,7 @@         !Key     -- ^ Common prefix.         !Key     -- ^ Rest of the first key.         !Key     -- ^ Rest of the second key.-    deriving (Show, Eq)+    deriving stock (Show, Eq)  -- | Compares two keys keysDiff :: Key -> Key -> KeysDiff@@ -150,10 +151,12 @@ -- | Prepends 'Piece' to the beginning of the 'Key'. (<|) :: Piece -> Key -> Key (<|) p k = Key (p NonEmpty.<| unKey k)+{-# INLINE (<|) #-}  -- | Creates a 'PrefixTree' of one key-value element. singleT :: Key -> a -> PrefixTree a singleT = Leaf+{-# INLINE singleT #-}  -- | Creates a 'PrefixMap' of one key-value element. single :: Key -> a -> PrefixMap a
src/Toml/Printer.hs view
@@ -13,7 +13,7 @@ import Data.Bifunctor (first) import Data.Function (on) import Data.HashMap.Strict (HashMap)-import Data.List (sortBy, splitAt)+import Data.List (sortBy) import Data.List.NonEmpty (NonEmpty) import Data.Monoid ((<>)) import Data.Text (Text)@@ -25,6 +25,7 @@ import qualified Data.HashMap.Strict as HashMap import qualified Data.List.NonEmpty as NonEmpty import qualified Data.Text as Text+  {- | Configures the pretty printer. -} data PrintOptions = PrintOptions
src/Toml/Type/AnyValue.hs view
@@ -52,9 +52,10 @@  -- | Value type mismatch error. data MatchError = MatchError-    { valueExpected :: TValue-    , valueActual   :: AnyValue-    } deriving (Eq, Show, Generic, NFData)+    { valueExpected :: !TValue+    , valueActual   :: !AnyValue+    } deriving stock (Eq, Show, Generic)+      deriving anyclass (NFData)  -- | Helper function to create 'MatchError'. mkMatchError :: TValue -> Value t -> Either MatchError a@@ -68,46 +69,55 @@ matchBool :: Value t -> Either MatchError Bool matchBool (Bool b) = Right b matchBool value    = mkMatchError TBool value+{-# INLINE matchBool #-}  -- | Extract 'Prelude.Integer' from 'Value'. matchInteger :: Value t -> Either MatchError Integer matchInteger (Integer n) = Right n matchInteger value       = mkMatchError TInteger value+{-# INLINE matchInteger #-}  -- | Extract 'Prelude.Double' from 'Value'. matchDouble :: Value t -> Either MatchError Double matchDouble (Double f) = Right f matchDouble value      = mkMatchError TDouble value+{-# INLINE matchDouble #-}  -- | Extract 'Data.Text.Text' from 'Value'. matchText :: Value t -> Either MatchError Text matchText (Text s) = Right s matchText value    = mkMatchError TText value+{-# INLINE matchText #-}  -- | Extract 'Data.Time.ZonedTime' from 'Value'. matchZoned :: Value t -> Either MatchError ZonedTime matchZoned (Zoned d) = Right d matchZoned value     = mkMatchError TZoned value+{-# INLINE matchZoned #-}  -- | Extract 'Data.Time.LocalTime' from 'Value'. matchLocal :: Value t -> Either MatchError LocalTime matchLocal (Local d) = Right d matchLocal value     = mkMatchError TLocal value+{-# INLINE matchLocal #-}  -- | Extract 'Data.Time.Day' from 'Value'. matchDay :: Value t -> Either MatchError Day matchDay (Day d) = Right d matchDay value   = mkMatchError TDay value+{-# INLINE matchDay #-}  -- | Extract 'Data.Time.TimeOfDay' from 'Value'. matchHours :: Value t -> Either MatchError TimeOfDay matchHours (Hours d) = Right d matchHours value     = mkMatchError THours value+{-# INLINE matchHours #-}  -- | Extract list of elements of type @a@ from array. matchArray :: (AnyValue -> Either MatchError a) -> Value t -> Either MatchError [a] matchArray matchValue (Array a) = mapM (applyAsToAny matchValue) a matchArray _          value     = mkMatchError TArray value+{-# INLINE matchArray #-}  -- | Make function that works with 'AnyValue' also work with specific 'Value'. applyAsToAny :: (AnyValue -> r) -> (Value t -> r)
src/Toml/Type/TOML.hs view
@@ -86,37 +86,49 @@ @ -} data TOML = TOML-    { tomlPairs       :: HashMap Key AnyValue-    , tomlTables      :: PrefixMap TOML-    , tomlTableArrays :: HashMap Key (NonEmpty TOML)-    } deriving (Show, Eq, NFData, Generic)+    { tomlPairs       :: !(HashMap Key AnyValue)+    , tomlTables      :: !(PrefixMap TOML)+    , tomlTableArrays :: !(HashMap Key (NonEmpty TOML))+    } deriving stock (Show, Eq, Generic)+      deriving anyclass (NFData)  instance Semigroup TOML where-    (TOML pairsA tablesA arraysA) <> (TOML pairsB tablesB arraysB) = TOML+    (<>) :: TOML -> TOML -> TOML+    TOML pairsA tablesA arraysA <> TOML pairsB tablesB arraysB = TOML         (pairsA <> pairsB)         (HashMap.unionWith (<>) tablesA tablesB)         (arraysA <> arraysB)+    {-# INLINE (<>) #-}  instance Monoid TOML where-    mappend = (<>)+    mempty :: TOML     mempty = TOML mempty mempty mempty+    {-# INLINE mempty #-} +    mappend :: TOML -> TOML -> TOML+    mappend = (<>)+    {-# INLINE mappend #-}+ -- | Inserts given key-value into the 'TOML'. insertKeyVal :: Key -> Value a -> TOML -> TOML insertKeyVal k v = insertKeyAnyVal k (AnyValue v)+{-# INLINE insertKeyVal #-}  -- | Inserts given key-value into the 'TOML'. insertKeyAnyVal :: Key -> AnyValue -> TOML -> TOML-insertKeyAnyVal k av toml =toml { tomlPairs = HashMap.insert k av (tomlPairs toml) }+insertKeyAnyVal k av toml = toml { tomlPairs = HashMap.insert k av (tomlPairs toml) }+{-# INLINE insertKeyAnyVal #-}  -- | Inserts given table into the 'TOML'. insertTable :: Key -> TOML -> TOML -> TOML insertTable k inToml toml = toml     { tomlTables = Prefix.insert k inToml (tomlTables toml)     }+{-# INLINE insertTable #-}  -- | Inserts given array of tables into the 'TOML'. insertTableArrays :: Key -> NonEmpty TOML -> TOML -> TOML insertTableArrays k arr toml = toml     { tomlTableArrays = HashMap.insert k arr (tomlTableArrays toml)     }+{-# INLINE insertTableArrays #-}
src/Toml/Type/UValue.hs view
@@ -14,6 +14,7 @@ import Toml.Type.AnyValue (AnyValue (..)) import Toml.Type.Value (TypeMismatchError, Value (..), sameValue) + -- | Untyped value of @TOML@. You shouldn't use this type in your code. Use -- 'Value' instead. data UValue@@ -26,7 +27,7 @@     | UDay !Day     | UHours !TimeOfDay     | UArray ![UValue]-    deriving (Show)+    deriving stock (Show)  instance Eq UValue where     (UBool b1)    == (UBool b2)    = b1 == b2
src/Toml/Type/Value.hs view
@@ -30,9 +30,20 @@ import Data.Type.Equality ((:~:) (..)) import GHC.Generics (Generic) + -- | Needed for GADT parameterization-data TValue = TBool | TInteger | TDouble | TText | TZoned | TLocal | TDay | THours | TArray-    deriving (Eq, Show, Read, NFData, Generic)+data TValue+    = TBool+    | TInteger+    | TDouble+    | TText+    | TZoned+    | TLocal+    | TDay+    | THours+    | TArray+    deriving stock (Eq, Show, Read, Generic)+    deriving anyclass (NFData)  -- | Convert 'TValue' constructors to 'String' without @T@ prefix. showType :: TValue -> String@@ -167,7 +178,7 @@     -}     Array  :: [Value t] -> Value 'TArray -deriving instance Show (Value t)+deriving stock instance Show (Value t)  instance NFData (Value t) where     rnf (Bool n)    = rnf n@@ -190,6 +201,7 @@  instance (t ~ 'TText) => IsString (Value t) where     fromString = Text . fromString @Text+    {-# INLINE fromString #-}  instance Eq (Value t) where     (Bool b1)    == (Bool b2)    = b1 == b2@@ -231,9 +243,9 @@  -- | Data type that holds expected vs. actual type. data TypeMismatchError = TypeMismatchError-  { typeExpected :: TValue-  , typeActual   :: TValue-  } deriving (Eq)+  { typeExpected :: !TValue+  , typeActual   :: !TValue+  } deriving stock (Eq)  instance Show TypeMismatchError where     show TypeMismatchError{..} = "Expected type '" ++ showType typeExpected
test/Test/Toml/BiCode/Property.hs view
@@ -25,6 +25,7 @@ import qualified Hedgehog.Range as Range import qualified Toml + test_encodeDecodeProp :: PropertyTest test_encodeDecodeProp = prop "decode . encode == id" $ do     bigType <- forAll genBigType@@ -55,11 +56,12 @@     , btNewtype       :: !BigTypeNewtype     , btSum           :: !BigTypeSum     , btRecord        :: !BigTypeRecord-    } deriving (Show, Eq)+    } deriving stock (Show, Eq)  -- | Wrapper over 'Double' and 'Float' to be equal on @NaN@ values.-newtype Batman a = Batman a-    deriving (Show)+newtype Batman a = Batman+    { unBatman :: a+    } deriving stock (Show)  instance RealFloat a => Eq (Batman a) where     Batman a == Batman b =@@ -67,19 +69,22 @@             then isNaN b             else a == b -newtype BigTypeNewtype = BigTypeNewtype ZonedTime-    deriving (Show)+newtype BigTypeNewtype = BigTypeNewtype+    { unBigTypeNewtype :: ZonedTime+    } deriving stock (Show)  instance Eq BigTypeNewtype where     (BigTypeNewtype a) == (BigTypeNewtype b) = zonedTimeToUTC a == zonedTimeToUTC b -data BigTypeSum = BigTypeSumA Integer | BigTypeSumB Text-    deriving (Show, Eq)+data BigTypeSum+    = BigTypeSumA !Integer+    | BigTypeSumB !Text+    deriving stock (Show, Eq)  data BigTypeRecord = BigTypeRecord-    { btrBoolSet     :: Set Bool-    , btrNewtypeList :: [BigTypeSum]-    } deriving (Show, Eq)+    { btrBoolSet     :: !(Set Bool)+    , btrNewtypeList :: ![BigTypeSum]+    } deriving stock (Show, Eq)  bigTypeCodec :: TomlCodec BigType bigTypeCodec = BigType
test/Test/Toml/BiMap/Property.hs view
@@ -39,6 +39,7 @@     , prop "Natural" (testBiMap B._Natural G.genNatural)     , prop "Int" (testBiMap B._Int G.genInt)     , prop "Word" (testBiMap B._Word G.genWord)+    , prop "Word8" (testBiMap B._Word8 G.genWord8)     , prop "Double" testDouble     , prop "Float" (testBiMap B._Float G.genFloat)     , prop "Text" (testBiMap B._Text G.genText)@@ -47,6 +48,8 @@     , prop "Read (Integer)" (testBiMap B._Read G.genInteger)     , prop "ByteString" (testBiMap B._ByteString G.genByteString)     , prop "Lazy ByteString" (testBiMap B._LByteString G.genLByteString)+    , prop "ByteStringArray" (testBiMap B._ByteStringArray G.genByteString)+    , prop "Lazy ByteStringArray" (testBiMap B._LByteStringArray G.genLByteString)     , prop "ZonedTime" (testBiMap B._ZonedTime G.genZoned)     , prop "LocalTime" (testBiMap B._LocalTime G.genLocal)     , prop "TimeOfDay" (testBiMap B._TimeOfDay G.genHours)
test/Test/Toml/Gen.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE DataKinds           #-}+{-# LANGUAGE MultiWayIf          #-} {-# LANGUAGE PatternSynonyms     #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications    #-}@@ -17,6 +18,7 @@        , genInteger        , genDouble        , genWord+       , genWord8        , genNatural        , genFloat @@ -61,6 +63,7 @@ import Data.Text (Text) import Data.Time (Day, LocalTime (..), TimeOfDay (..), ZonedTime (..), fromGregorian,                   minutesToTimeZone)+import Data.Word (Word8) import GHC.Exts (fromList) import GHC.Stack (HasCallStack) import Hedgehog (Gen, GenBase, MonadGen, PropertyT, Range, property)@@ -102,7 +105,7 @@ genVal = Gen.int (Range.constant 0 256)  -- | Generates random value of 'AnyValue' type.-genAnyValue :: MonadGen m => m AnyValue+genAnyValue :: (MonadGen m, GenBase m ~ Identity) => m AnyValue genAnyValue = Gen.choice $     (AnyValue <$> genArray) : noneArrayList @@ -126,7 +129,7 @@     quotedWith c = wrapChar c <$> Gen.text (Range.constant 1 10) (Gen.filter (/= c) notControl)      quoted :: m Text-    quoted = Gen.choice [quotedWith '"', quotedWith '\'']+    quoted = genNotEscape $ Gen.choice [quotedWith '"', quotedWith '\'']  genKey :: (MonadGen m, GenBase m ~ Identity) => m Key genKey = Key <$> Gen.nonEmpty (Range.constant 1 10) genPiece@@ -234,6 +237,9 @@ genWord :: MonadGen m => m Word genWord = Gen.word Range.constantBounded +genWord8 :: MonadGen m => m Word8+genWord8 = Gen.word8 Range.constantBounded+ genNatural :: MonadGen m => m Natural genNatural = fromIntegral <$> genWord @@ -286,7 +292,7 @@  -- | Generates text from different symbols. genText :: MonadGen m => m Text-genText =  fmap Text.concat $ Gen.list (Range.constant 0 256) $ Gen.choice+genText = genNotEscape $ fmap Text.concat $ Gen.list (Range.constant 0 256) $ Gen.choice     [ Text.singleton <$> Gen.alphaNum     , genEscapeSequence     , genPunctuation@@ -332,7 +338,16 @@ Nested array of AnyValue: Array [Array [Text "ACyz38VcLz0hxwdFkHTU6PYK8h8CeaiEpI2xAaiZTKBQ3zC1W717cZY35lk8EAK6pPw3WvwIdNktxIV2LrvFSpU8ee6zkXvpvePitW9aspAeeOCF9Q9ry20y7skFZ2qShi7CSx8888zWIqyc8iBkoLNvq4fONLtuUqSw2SlNee4hDIwrnx5O4RuHW1dQfJcnC34h9S0DlIGYP08qq6QHxO4E0HE74cNmiViGm3xpDC8Ro5D8Y6p0FLSN1ELq9Lwm",Text "HhNv0LKICdlKxN"],Array [Integer 986479839551009895,Integer 8636972066308796678,Integer (-3464941350081979804),Integer (-6560688879547055621),Integer (-4749037439349044738)],Array []] -}-genArray :: MonadGen m => m (Value 'TArray)+genArray :: (MonadGen m, GenBase m ~ Identity) => m (Value 'TArray) genArray = Gen.recursive Gen.choice     [Gen.choice $ map genArrayFrom noneArrayList]     [Array <$> Gen.list (Range.constant 0 5) genArray]++-- filters++-- | Discards strings that end with \+genNotEscape :: MonadGen m => m Text -> m Text+genNotEscape gen = gen >>= \t ->+    if | Text.null t -> pure t+       | Text.last t == '\\' -> Gen.discard+       | otherwise -> pure t
test/Test/Toml/Parsing/Unit.hs view
@@ -12,8 +12,10 @@ import Text.Megaparsec (Parsec, ShowErrorComponent, Stream, parse)  import Toml.Edsl (mkToml, table, tableArray, (=:))+import Toml.Parser.Item (tomlP)+import Toml.Parser.Key (keyP) import Toml.Parser.String (textP)-import Toml.Parser.TOML (keyP, tomlP)+import Toml.Parser.Validate (validateItems) import Toml.Parser.Value (arrayP, boolP, dateTimeP, doubleP, integerP) import Toml.PrefixTree (Key (..), Piece (..), fromList) import Toml.Type (AnyValue (..), TOML (..), UValue (..), Value (..))@@ -397,12 +399,21 @@             parseToml "table = {}" (tomlFromTable [(makeKey ["table"], mempty)])         it "can parse a table followed by an inline table" $             parseToml "[table1] \n  key1 = \"some string\" \n table2 = {key2 = 123}"-                (tomlFromTable [(makeKey ["table1"], tomlFromKeyVal [str])-                               ,(makeKey ["table2"], tomlFromKeyVal [int])])+                (tomlFromTable+                    [ ( "table1"+                      , tomlFromKeyVal [str]+                        <> tomlFromTable [ ("table2", tomlFromKeyVal [int]) ]+                      )+                    ]+                )         it "can parse an empty table followed by an inline table" $             parseToml "[table1] \n table2 = {key2 = 123}"-                (tomlFromTable [(makeKey ["table1"], mempty)-                               ,(makeKey ["table2"], tomlFromKeyVal [int])])+                (tomlFromTable+                    [ ( "table1"+                      , tomlFromTable [ ("table2", tomlFromKeyVal [int]) ]+                      )+                    ]+                )         it "allows the name of the table to be any valid TOML key" $ do             parseToml "dog.\"tater.man\"={}"                 (tomlFromTable [(makeKey ["dog", dquote "tater.man"], mempty)])@@ -481,8 +492,9 @@ -- Utilities ---------------------------------------------------------------------------- -parseX :: (ShowErrorComponent e, Stream s, Show a, Eq a)-       => Parsec e s a -> s -> a -> Expectation+parseX+    :: (ShowErrorComponent e, Stream s, Show a, Eq a)+    => Parsec e s a -> s -> a -> Expectation parseX p given expected = parse p "" given `shouldParse` expected  failOn :: Show a => Parsec e s a -> s -> Expectation@@ -490,23 +502,35 @@  parseArray :: Text -> [UValue] -> Expectation parseArray = parseX arrayP+ parseBool :: Text -> Bool -> Expectation parseBool = parseX boolP+ parseDateTime :: Text -> UValue -> Expectation parseDateTime = parseX dateTimeP+ parseDouble :: Text -> Double -> Expectation parseDouble = parseX doubleP+ parseInteger :: Text -> Integer -> Expectation parseInteger = parseX integerP+ parseKey :: Text -> Key -> Expectation parseKey = parseX keyP+ parseText :: Text -> Text -> Expectation parseText = parseX textP  parseToml :: Text -> TOML -> Expectation-parseToml = parseX tomlP+parseToml test toml = parseX (validateItems <$> tomlP) test (Right toml) -arrayFailOn, boolFailOn, dateTimeFailOn, doubleFailOn, integerFailOn, textFailOn, tomlFailOn :: Text -> Expectation+arrayFailOn+  , boolFailOn+  , dateTimeFailOn+  , doubleFailOn+  , integerFailOn+  , textFailOn+  , tomlFailOn :: Text -> Expectation arrayFailOn     = failOn arrayP boolFailOn      = failOn boolP dateTimeFailOn  = failOn dateTimeP
tomland.cabal view
@@ -1,6 +1,6 @@ cabal-version:       2.4 name:                tomland-version:             1.1.0.1+version:             1.2.0.0 synopsis:            Bidirectional TOML serialization description:     Implementation of bidirectional TOML serialization. Simple codecs look like this:@@ -31,19 +31,20 @@ category:            TOML, Text, Configuration build-type:          Simple extra-doc-files:     README.md-                   , CHANGELOG.md+                     CHANGELOG.md extra-source-files:  test/golden/*.golden-                   , test/examples/*.toml+                     test/examples/*.toml tested-with:         GHC == 8.2.2-                   , GHC == 8.4.4-                   , GHC == 8.6.5+                     GHC == 8.4.4+                     GHC == 8.6.5+                     GHC == 8.8.1  source-repository head   type:                git   location:            https://github.com/kowainik/tomland.git  common common-options-  build-depends:       base >= 4.10 && < 4.13+  build-depends:       base >= 4.10 && < 4.14    ghc-options:         -Wall                        -Wincomplete-uni-patterns@@ -54,9 +55,14 @@                        -Wpartial-fields                        -fhide-source-paths                        -freverse-errors+  if impl(ghc >= 8.8.1)+    ghc-options:       -Wmissing-deriving-strategies+                       -Werror=missing-deriving-strategies    default-language:    Haskell2010   default-extensions:  DeriveGeneric+                       DerivingStrategies+                       GeneralizedNewtypeDeriving                        InstanceSigs                        LambdaCase                        OverloadedStrings@@ -78,9 +84,11 @@                          Toml.Generic                          Toml.Parser                            Toml.Parser.Core+                           Toml.Parser.Item+                           Toml.Parser.Key                            Toml.Parser.String+                           Toml.Parser.Validate                            Toml.Parser.Value-                           Toml.Parser.TOML                          Toml.PrefixTree                          Toml.Printer                          Toml.Type@@ -92,10 +100,10 @@   build-depends:       bytestring ^>= 0.10                      , containers >= 0.5.7 && < 0.7                      , deepseq ^>= 1.4-                     , hashable ^>= 1.2+                     , hashable >= 1.2 && < 1.4                      , megaparsec ^>= 7.0.5                      , mtl ^>= 2.2-                     , parser-combinators ^>= 1.1.0+                     , parser-combinators >= 1.1.0 && < 1.3                      , text ^>= 1.2                      , time >= 1.8 && < 1.10                      , transformers ^>= 0.5@@ -103,6 +111,9 @@  executable readme   import:              common-options+  -- doesn't work on windows for unknown reasons+  if os(windows)+    buildable: False   main-is:             README.lhs   build-depends:       text                      , tomland@@ -112,8 +123,10 @@  executable play-tomland   import:              common-options-  main-is:             Playground.hs+  main-is:             Main.hs   build-depends:       tomland+                     , containers+                     , hashable                      , text                      , time                      , unordered-containers@@ -143,7 +156,7 @@   build-depends:       bytestring ^>= 0.10                      , containers >= 0.5.7 && < 0.7                      , hashable-                     , hedgehog ^>= 1.0+                     , hedgehog ^>= 1.0.1                      , hspec-megaparsec ^>= 2.0.0                      , megaparsec                      , tasty ^>= 1.2@@ -157,31 +170,3 @@                      , unordered-containers    ghc-options:         -threaded -rtsopts -with-rtsopts=-N--benchmark tomland-benchmark-  import:             common-options-  type:               exitcode-stdio-1.0-  main-is:            Main.hs-  hs-source-dirs:     benchmark--  other-modules:      Benchmark.Type-                      Benchmark.Htoml-                      Benchmark.HtomlMegaparsec-                      Benchmark.Tomland-                      Benchmark.TomlParser--  ghc-options:        -threaded-                      -rtsopts-                      -with-rtsopts=-N-                      -O2--  build-depends:      aeson-                    , deepseq ^>= 1.4-                    , gauge ^>= 0.2.4-                    , htoml ^>= 1.0.0.3-                    , htoml-megaparsec ^>= 2.1.0.3-                    , parsec-                    , text-                    , time-                    , toml-parser ^>= 0.1.0.0-                    , tomland