packages feed

tomland 1.2.1.0 → 1.3.0.0

raw patch · 107 files changed

+7672/−4063 lines, 107 filesdep +hspecdep +hspec-goldendep +hspec-hedgehogdep −tastydep −tasty-hedgehogdep −tasty-hspecdep ~basedep ~bytestringdep ~timenew-uploaderPVP ok

version bump matches the API change (PVP)

Dependencies added: hspec, hspec-golden, hspec-hedgehog, validation-selective

Dependencies removed: tasty, tasty-hedgehog, tasty-hspec, tasty-silver

Dependency ranges changed: base, bytestring, time

API changes (from Hackage documentation)

- Toml.Bi.Code: BiMapError :: !TomlBiMapError -> DecodeException
- Toml.Bi.Code: KeyNotFound :: !Key -> DecodeException
- Toml.Bi.Code: LoadTomlException :: !FilePath -> !Text -> LoadTomlException
- Toml.Bi.Code: ParseError :: !ParseException -> DecodeException
- Toml.Bi.Code: TableNotFound :: !Key -> DecodeException
- Toml.Bi.Code: TrivialError :: DecodeException
- Toml.Bi.Code: TypeMismatch :: !Key -> !Text -> !TValue -> DecodeException
- Toml.Bi.Code: data DecodeException
- Toml.Bi.Code: data LoadTomlException
- Toml.Bi.Code: decode :: TomlCodec a -> Text -> Either DecodeException a
- Toml.Bi.Code: decodeFile :: MonadIO m => TomlCodec a -> FilePath -> m a
- Toml.Bi.Code: encode :: TomlCodec a -> a -> Text
- Toml.Bi.Code: execTomlCodec :: TomlCodec a -> a -> TOML
- Toml.Bi.Code: instance Control.DeepSeq.NFData Toml.Bi.Code.DecodeException
- Toml.Bi.Code: instance GHC.Base.Monoid Toml.Bi.Code.DecodeException
- Toml.Bi.Code: instance GHC.Base.Semigroup Toml.Bi.Code.DecodeException
- Toml.Bi.Code: instance GHC.Classes.Eq Toml.Bi.Code.DecodeException
- Toml.Bi.Code: instance GHC.Exception.Type.Exception Toml.Bi.Code.LoadTomlException
- Toml.Bi.Code: instance GHC.Generics.Generic Toml.Bi.Code.DecodeException
- Toml.Bi.Code: instance GHC.Show.Show Toml.Bi.Code.DecodeException
- Toml.Bi.Code: instance GHC.Show.Show Toml.Bi.Code.LoadTomlException
- Toml.Bi.Code: prettyException :: DecodeException -> Text
- Toml.Bi.Code: runTomlCodec :: TomlCodec a -> TOML -> Either DecodeException a
- Toml.Bi.Code: type Env = ExceptT DecodeException (Reader TOML)
- Toml.Bi.Code: type St = MaybeT (State TOML)
- Toml.Bi.Code: type TomlCodec a = BiCodec Env St a
- Toml.Bi.Combinators: all :: Key -> TomlCodec All
- Toml.Bi.Combinators: any :: Key -> TomlCodec Any
- Toml.Bi.Combinators: arrayHashSetOf :: (Hashable a, Eq a) => TomlBiMap a AnyValue -> Key -> TomlCodec (HashSet a)
- Toml.Bi.Combinators: arrayIntSet :: Key -> TomlCodec IntSet
- Toml.Bi.Combinators: arrayNonEmptyOf :: TomlBiMap a AnyValue -> Key -> TomlCodec (NonEmpty a)
- Toml.Bi.Combinators: arrayOf :: TomlBiMap a AnyValue -> Key -> TomlCodec [a]
- Toml.Bi.Combinators: arraySetOf :: Ord a => TomlBiMap a AnyValue -> Key -> TomlCodec (Set a)
- Toml.Bi.Combinators: bool :: Key -> TomlCodec Bool
- Toml.Bi.Combinators: byteString :: Key -> TomlCodec ByteString
- Toml.Bi.Combinators: byteStringArray :: Key -> TomlCodec ByteString
- Toml.Bi.Combinators: day :: Key -> TomlCodec Day
- Toml.Bi.Combinators: double :: Key -> TomlCodec Double
- Toml.Bi.Combinators: enumBounded :: (Bounded a, Enum a, Show a) => Key -> TomlCodec a
- Toml.Bi.Combinators: first :: (Key -> TomlCodec a) -> Key -> TomlCodec (First a)
- Toml.Bi.Combinators: float :: Key -> TomlCodec Float
- Toml.Bi.Combinators: hashSet :: forall a. (Hashable a, Eq a) => TomlCodec a -> Key -> TomlCodec (HashSet a)
- Toml.Bi.Combinators: int :: Key -> TomlCodec Int
- Toml.Bi.Combinators: integer :: Key -> TomlCodec Integer
- Toml.Bi.Combinators: last :: (Key -> TomlCodec a) -> Key -> TomlCodec (Last a)
- Toml.Bi.Combinators: lazyByteString :: Key -> TomlCodec ByteString
- Toml.Bi.Combinators: lazyByteStringArray :: Key -> TomlCodec ByteString
- Toml.Bi.Combinators: lazyText :: Key -> TomlCodec Text
- Toml.Bi.Combinators: list :: forall a. TomlCodec a -> Key -> TomlCodec [a]
- Toml.Bi.Combinators: localTime :: Key -> TomlCodec LocalTime
- Toml.Bi.Combinators: map :: forall k v. Ord k => TomlCodec k -> TomlCodec v -> Key -> TomlCodec (Map k v)
- Toml.Bi.Combinators: match :: forall a. TomlBiMap a AnyValue -> Key -> TomlCodec a
- Toml.Bi.Combinators: natural :: Key -> TomlCodec Natural
- Toml.Bi.Combinators: nonEmpty :: forall a. TomlCodec a -> Key -> TomlCodec (NonEmpty a)
- Toml.Bi.Combinators: product :: (Key -> TomlCodec a) -> Key -> TomlCodec (Product a)
- Toml.Bi.Combinators: read :: (Show a, Read a) => Key -> TomlCodec a
- Toml.Bi.Combinators: set :: forall a. Ord a => TomlCodec a -> Key -> TomlCodec (Set a)
- Toml.Bi.Combinators: string :: Key -> TomlCodec String
- Toml.Bi.Combinators: sum :: (Key -> TomlCodec a) -> Key -> TomlCodec (Sum a)
- Toml.Bi.Combinators: table :: forall a. TomlCodec a -> Key -> TomlCodec a
- Toml.Bi.Combinators: text :: Key -> TomlCodec Text
- Toml.Bi.Combinators: textBy :: (a -> Text) -> (Text -> Either Text a) -> Key -> TomlCodec a
- Toml.Bi.Combinators: timeOfDay :: Key -> TomlCodec TimeOfDay
- Toml.Bi.Combinators: word :: Key -> TomlCodec Word
- Toml.Bi.Combinators: word8 :: Key -> TomlCodec Word8
- Toml.Bi.Combinators: zonedTime :: Key -> TomlCodec ZonedTime
- Toml.Bi.Map: ArbitraryError :: !Text -> TomlBiMapError
- Toml.Bi.Map: BiMap :: (a -> Either e b) -> (b -> Either e a) -> BiMap e a b
- Toml.Bi.Map: WrongConstructor :: !Text -> !Text -> TomlBiMapError
- Toml.Bi.Map: WrongValue :: !MatchError -> TomlBiMapError
- Toml.Bi.Map: [backward] :: BiMap e a b -> b -> Either e a
- Toml.Bi.Map: [forward] :: BiMap e a b -> a -> Either e b
- Toml.Bi.Map: _Array :: forall a. TomlBiMap a AnyValue -> TomlBiMap [a] AnyValue
- Toml.Bi.Map: _Bool :: TomlBiMap Bool AnyValue
- Toml.Bi.Map: _BoundedInteger :: (Integral a, Bounded a, Show a) => TomlBiMap a Integer
- Toml.Bi.Map: _ByteString :: TomlBiMap ByteString AnyValue
- Toml.Bi.Map: _ByteStringArray :: TomlBiMap ByteString AnyValue
- Toml.Bi.Map: _ByteStringText :: TomlBiMap ByteString Text
- Toml.Bi.Map: _Day :: TomlBiMap Day AnyValue
- Toml.Bi.Map: _Double :: TomlBiMap Double AnyValue
- Toml.Bi.Map: _EnumBounded :: (Show a, Enum a, Bounded a) => TomlBiMap a AnyValue
- Toml.Bi.Map: _EnumBoundedText :: forall a. (Show a, Enum a, Bounded a) => TomlBiMap a Text
- Toml.Bi.Map: _Float :: TomlBiMap Float AnyValue
- Toml.Bi.Map: _HashSet :: (Eq a, Hashable a) => TomlBiMap a AnyValue -> TomlBiMap (HashSet a) AnyValue
- Toml.Bi.Map: _Int :: TomlBiMap Int AnyValue
- Toml.Bi.Map: _IntSet :: TomlBiMap IntSet AnyValue
- Toml.Bi.Map: _Integer :: TomlBiMap Integer AnyValue
- Toml.Bi.Map: _Just :: Show r => TomlBiMap (Maybe r) r
- Toml.Bi.Map: _LByteString :: TomlBiMap ByteString AnyValue
- Toml.Bi.Map: _LByteStringArray :: TomlBiMap ByteString AnyValue
- Toml.Bi.Map: _LByteStringText :: TomlBiMap ByteString Text
- Toml.Bi.Map: _LText :: TomlBiMap Text AnyValue
- Toml.Bi.Map: _LTextText :: BiMap e Text Text
- Toml.Bi.Map: _Left :: (Show l, Show r) => TomlBiMap (Either l r) l
- Toml.Bi.Map: _LocalTime :: TomlBiMap LocalTime AnyValue
- Toml.Bi.Map: _Natural :: TomlBiMap Natural AnyValue
- Toml.Bi.Map: _NaturalInteger :: TomlBiMap Natural Integer
- Toml.Bi.Map: _NonEmpty :: TomlBiMap a AnyValue -> TomlBiMap (NonEmpty a) AnyValue
- Toml.Bi.Map: _Read :: (Show a, Read a) => TomlBiMap a AnyValue
- Toml.Bi.Map: _ReadString :: (Show a, Read a) => TomlBiMap a String
- Toml.Bi.Map: _Right :: (Show l, Show r) => TomlBiMap (Either l r) r
- Toml.Bi.Map: _Set :: Ord a => TomlBiMap a AnyValue -> TomlBiMap (Set a) AnyValue
- Toml.Bi.Map: _String :: TomlBiMap String AnyValue
- Toml.Bi.Map: _StringText :: BiMap e String Text
- Toml.Bi.Map: _Text :: TomlBiMap Text AnyValue
- Toml.Bi.Map: _TextBy :: forall a. (a -> Text) -> (Text -> Either Text a) -> TomlBiMap a AnyValue
- Toml.Bi.Map: _TimeOfDay :: TomlBiMap TimeOfDay AnyValue
- Toml.Bi.Map: _Word :: TomlBiMap Word AnyValue
- Toml.Bi.Map: _Word8 :: TomlBiMap Word8 AnyValue
- Toml.Bi.Map: _ZonedTime :: TomlBiMap ZonedTime AnyValue
- Toml.Bi.Map: data BiMap e a b
- Toml.Bi.Map: data TomlBiMapError
- Toml.Bi.Map: instance Control.Category.Category (Toml.Bi.Map.BiMap e)
- Toml.Bi.Map: instance Control.DeepSeq.NFData Toml.Bi.Map.TomlBiMapError
- Toml.Bi.Map: instance GHC.Classes.Eq Toml.Bi.Map.TomlBiMapError
- Toml.Bi.Map: instance GHC.Generics.Generic Toml.Bi.Map.TomlBiMapError
- Toml.Bi.Map: instance GHC.Show.Show Toml.Bi.Map.TomlBiMapError
- Toml.Bi.Map: invert :: BiMap e a b -> BiMap e b a
- Toml.Bi.Map: iso :: (a -> b) -> (b -> a) -> BiMap e a b
- Toml.Bi.Map: mkAnyValueBiMap :: forall a (tag :: TValue). (forall (t :: TValue). Value t -> Either MatchError a) -> (a -> Value tag) -> TomlBiMap a AnyValue
- Toml.Bi.Map: prettyBiMapError :: TomlBiMapError -> Text
- Toml.Bi.Map: prism :: (field -> object) -> (object -> Either error field) -> BiMap error object field
- Toml.Bi.Map: toMArray :: [AnyValue] -> Either MatchError (Value 'TArray)
- Toml.Bi.Map: type TomlBiMap = BiMap TomlBiMapError
- Toml.Bi.Map: wrongConstructor :: Show a => Text -> a -> Either TomlBiMapError b
- Toml.Bi.Monad: (.=) :: Codec r w field a -> (object -> field) -> Codec r w object a
- Toml.Bi.Monad: (<!>) :: Alternative f => (a -> f x) -> (a -> f x) -> a -> f x
- Toml.Bi.Monad: Codec :: r a -> (c -> w a) -> Codec r w c a
- Toml.Bi.Monad: [codecRead] :: Codec r w c a -> r a
- Toml.Bi.Monad: [codecWrite] :: Codec r w c a -> c -> w a
- Toml.Bi.Monad: data Codec r w c a
- Toml.Bi.Monad: dimap :: (Functor r, Functor w) => (c -> d) -> (a -> b) -> Codec r w d a -> Codec r w c b
- Toml.Bi.Monad: dimatch :: (Functor r, Alternative w) => (c -> Maybe d) -> (a -> b) -> Codec r w d a -> Codec r w c b
- Toml.Bi.Monad: dioptional :: (Alternative r, Applicative w) => Codec r w c a -> Codec r w (Maybe c) (Maybe a)
- Toml.Bi.Monad: diwrap :: forall b a r w. (Coercible a b, Functor r, Functor w) => BiCodec r w a -> BiCodec r w b
- Toml.Bi.Monad: infixl 3 <!>
- Toml.Bi.Monad: infixl 5 .=
- Toml.Bi.Monad: instance (GHC.Base.Alternative r, GHC.Base.Alternative w) => GHC.Base.Alternative (Toml.Bi.Monad.Codec r w c)
- Toml.Bi.Monad: instance (GHC.Base.Applicative r, GHC.Base.Applicative w) => GHC.Base.Applicative (Toml.Bi.Monad.Codec r w c)
- Toml.Bi.Monad: instance (GHC.Base.Functor r, GHC.Base.Functor w) => GHC.Base.Functor (Toml.Bi.Monad.Codec r w c)
- Toml.Bi.Monad: instance (GHC.Base.Monad r, GHC.Base.Monad w) => GHC.Base.Monad (Toml.Bi.Monad.Codec r w c)
- Toml.Bi.Monad: instance (GHC.Base.MonadPlus r, GHC.Base.MonadPlus w) => GHC.Base.MonadPlus (Toml.Bi.Monad.Codec r w c)
- Toml.Bi.Monad: type BiCodec r w a = Codec r w a a
- Toml.Edsl: (=:) :: Key -> Value a -> TDSL
- Toml.Edsl: empty :: TDSL
- Toml.Edsl: mkToml :: TDSL -> TOML
- Toml.Edsl: table :: Key -> TDSL -> TDSL
- Toml.Edsl: tableArray :: Key -> NonEmpty TDSL -> TDSL
- Toml.Edsl: type TDSL = State TOML ()
- Toml.Generic: GenericOptions :: (String -> String) -> GenericOptions
- Toml.Generic: TomlOptions :: (Typeable a => Proxy a -> String -> String) -> TomlOptions a
- Toml.Generic: [genericOptionsFieldModifier] :: GenericOptions -> String -> String
- Toml.Generic: [tomlOptionsFieldModifier] :: TomlOptions a -> Typeable a => Proxy a -> String -> String
- Toml.Generic: class GenericCodec (f :: k -> Type)
- Toml.Generic: class HasCodec a
- Toml.Generic: class HasItemCodec a
- Toml.Generic: data TomlOptions a
- Toml.Generic: genericCodec :: (Generic a, GenericCodec (Rep a)) => TomlCodec a
- Toml.Generic: genericCodecWithOptions :: forall a. (Generic a, GenericCodec (Rep a), Typeable a) => TomlOptions a -> TomlCodec a
- Toml.Generic: genericTomlCodec :: GenericCodec f => GenericOptions -> TomlCodec (f p)
- Toml.Generic: hasCodec :: HasCodec a => Key -> TomlCodec a
- Toml.Generic: hasItemCodec :: HasItemCodec a => Either (TomlBiMap a AnyValue) (TomlCodec a)
- 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 (GHC.Generics.Selector s, Toml.Generic.HasCodec a) => Toml.Generic.GenericCodec (GHC.Generics.S1 s (GHC.Generics.Rec0 a))
- Toml.Generic: instance (Toml.Generic.HasItemCodec a, Data.Typeable.Internal.Typeable a) => Toml.Generic.HasItemCodec [a]
- Toml.Generic: instance Toml.Generic.HasCodec Data.IntSet.Internal.IntSet
- Toml.Generic: instance Toml.Generic.HasCodec Data.Text.Internal.Lazy.Text
- Toml.Generic: instance Toml.Generic.HasCodec Data.Text.Internal.Text
- Toml.Generic: instance Toml.Generic.HasCodec Data.Time.Calendar.Days.Day
- Toml.Generic: instance Toml.Generic.HasCodec Data.Time.LocalTime.Internal.LocalTime.LocalTime
- Toml.Generic: instance Toml.Generic.HasCodec Data.Time.LocalTime.Internal.TimeOfDay.TimeOfDay
- Toml.Generic: instance Toml.Generic.HasCodec Data.Time.LocalTime.Internal.ZonedTime.ZonedTime
- Toml.Generic: instance Toml.Generic.HasCodec GHC.Integer.Type.Integer
- Toml.Generic: instance Toml.Generic.HasCodec GHC.Natural.Natural
- Toml.Generic: instance Toml.Generic.HasCodec GHC.Types.Bool
- Toml.Generic: instance Toml.Generic.HasCodec GHC.Types.Double
- Toml.Generic: instance Toml.Generic.HasCodec GHC.Types.Float
- Toml.Generic: instance Toml.Generic.HasCodec GHC.Types.Int
- Toml.Generic: instance Toml.Generic.HasCodec GHC.Types.Word
- Toml.Generic: instance Toml.Generic.HasCodec GHC.Word.Word8
- Toml.Generic: instance Toml.Generic.HasCodec a => Toml.Generic.HasCodec (GHC.Maybe.Maybe a)
- Toml.Generic: instance Toml.Generic.HasItemCodec Data.IntSet.Internal.IntSet
- Toml.Generic: instance Toml.Generic.HasItemCodec Data.Text.Internal.Lazy.Text
- Toml.Generic: instance Toml.Generic.HasItemCodec Data.Text.Internal.Text
- Toml.Generic: instance Toml.Generic.HasItemCodec Data.Time.Calendar.Days.Day
- Toml.Generic: instance Toml.Generic.HasItemCodec Data.Time.LocalTime.Internal.LocalTime.LocalTime
- Toml.Generic: instance Toml.Generic.HasItemCodec Data.Time.LocalTime.Internal.TimeOfDay.TimeOfDay
- Toml.Generic: instance Toml.Generic.HasItemCodec Data.Time.LocalTime.Internal.ZonedTime.ZonedTime
- Toml.Generic: instance Toml.Generic.HasItemCodec GHC.Integer.Type.Integer
- Toml.Generic: instance Toml.Generic.HasItemCodec GHC.Natural.Natural
- Toml.Generic: instance Toml.Generic.HasItemCodec GHC.Types.Bool
- Toml.Generic: instance Toml.Generic.HasItemCodec GHC.Types.Double
- Toml.Generic: instance Toml.Generic.HasItemCodec GHC.Types.Float
- Toml.Generic: instance Toml.Generic.HasItemCodec GHC.Types.Int
- Toml.Generic: instance Toml.Generic.HasItemCodec GHC.Types.Word
- Toml.Generic: instance Toml.Generic.HasItemCodec GHC.Word.Word8
- Toml.Generic: instance Toml.Generic.HasItemCodec a => Toml.Generic.HasCodec (GHC.Base.NonEmpty a)
- Toml.Generic: instance Toml.Generic.HasItemCodec a => Toml.Generic.HasCodec [a]
- Toml.Generic: instance forall k (f :: k -> *) (c :: GHC.Generics.Meta). Toml.Generic.GenericCodec f => Toml.Generic.GenericCodec (GHC.Generics.C1 c f)
- Toml.Generic: instance forall k (f :: k -> *) (d :: GHC.Generics.Meta). Toml.Generic.GenericCodec f => Toml.Generic.GenericCodec (GHC.Generics.D1 d f)
- Toml.Generic: instance forall k (f :: k -> *) (g :: k -> *). (Toml.Generic.GenericCodec f, Toml.Generic.GenericCodec g) => Toml.Generic.GenericCodec (f GHC.Generics.:*: g)
- Toml.Generic: instance forall k (f :: k -> *) (g :: k -> *). (TypeError ...) => Toml.Generic.GenericCodec (f GHC.Generics.:+: g)
- Toml.Generic: newtype GenericOptions
- Toml.Generic: stripTypeNameCodec :: forall a. (Generic a, GenericCodec (Rep a), Typeable a) => TomlCodec a
- Toml.Generic: stripTypeNameOptions :: Typeable a => TomlOptions a
- Toml.Generic: stripTypeNamePrefix :: forall a. Typeable a => Proxy a -> String -> String
- Toml.Parser: ParseException :: Text -> ParseException
- Toml.Parser: [unParseException] :: ParseException -> Text
- Toml.Parser: instance Control.DeepSeq.NFData Toml.Parser.ParseException
- Toml.Parser: instance GHC.Classes.Eq Toml.Parser.ParseException
- Toml.Parser: instance GHC.Generics.Generic Toml.Parser.ParseException
- Toml.Parser: instance GHC.Show.Show Toml.Parser.ParseException
- Toml.Parser: newtype ParseException
- Toml.PrefixTree: (<|) :: Piece -> Key -> Key
- Toml.PrefixTree: Branch :: !Prefix -> !Maybe a -> !PrefixMap a -> PrefixTree a
- Toml.PrefixTree: Diff :: !Key -> !Key -> !Key -> KeysDiff
- Toml.PrefixTree: Equal :: KeysDiff
- Toml.PrefixTree: FstIsPref :: !Key -> KeysDiff
- Toml.PrefixTree: Key :: NonEmpty Piece -> Key
- Toml.PrefixTree: Leaf :: !Key -> !a -> PrefixTree a
- Toml.PrefixTree: NoPrefix :: KeysDiff
- Toml.PrefixTree: Piece :: Text -> Piece
- Toml.PrefixTree: SndIsPref :: !Key -> KeysDiff
- Toml.PrefixTree: [unKey] :: Key -> NonEmpty Piece
- Toml.PrefixTree: [unPiece] :: Piece -> Text
- Toml.PrefixTree: data KeysDiff
- Toml.PrefixTree: data PrefixTree a
- Toml.PrefixTree: fromList :: [(Key, a)] -> PrefixMap a
- Toml.PrefixTree: insert :: Key -> a -> PrefixMap a -> PrefixMap a
- Toml.PrefixTree: insertT :: Key -> a -> PrefixTree a -> PrefixTree a
- Toml.PrefixTree: instance Control.DeepSeq.NFData Toml.PrefixTree.Key
- Toml.PrefixTree: instance Control.DeepSeq.NFData Toml.PrefixTree.Piece
- Toml.PrefixTree: instance Control.DeepSeq.NFData a => Control.DeepSeq.NFData (Toml.PrefixTree.PrefixTree a)
- Toml.PrefixTree: instance Data.Hashable.Class.Hashable Toml.PrefixTree.Key
- Toml.PrefixTree: instance Data.Hashable.Class.Hashable Toml.PrefixTree.Piece
- Toml.PrefixTree: instance Data.String.IsString Toml.PrefixTree.Key
- Toml.PrefixTree: instance Data.String.IsString Toml.PrefixTree.Piece
- Toml.PrefixTree: instance GHC.Base.Semigroup (Toml.PrefixTree.PrefixTree a)
- Toml.PrefixTree: instance GHC.Base.Semigroup Toml.PrefixTree.Key
- Toml.PrefixTree: instance GHC.Classes.Eq Toml.PrefixTree.Key
- Toml.PrefixTree: instance GHC.Classes.Eq Toml.PrefixTree.KeysDiff
- Toml.PrefixTree: instance GHC.Classes.Eq Toml.PrefixTree.Piece
- Toml.PrefixTree: instance GHC.Classes.Eq a => GHC.Classes.Eq (Toml.PrefixTree.PrefixTree a)
- Toml.PrefixTree: instance GHC.Classes.Ord Toml.PrefixTree.Key
- Toml.PrefixTree: instance GHC.Classes.Ord Toml.PrefixTree.Piece
- Toml.PrefixTree: instance GHC.Generics.Generic (Toml.PrefixTree.PrefixTree a)
- Toml.PrefixTree: instance GHC.Generics.Generic Toml.PrefixTree.Key
- Toml.PrefixTree: instance GHC.Generics.Generic Toml.PrefixTree.Piece
- Toml.PrefixTree: instance GHC.Show.Show Toml.PrefixTree.Key
- Toml.PrefixTree: instance GHC.Show.Show Toml.PrefixTree.KeysDiff
- Toml.PrefixTree: instance GHC.Show.Show Toml.PrefixTree.Piece
- Toml.PrefixTree: instance GHC.Show.Show a => GHC.Show.Show (Toml.PrefixTree.PrefixTree a)
- Toml.PrefixTree: keysDiff :: Key -> Key -> KeysDiff
- Toml.PrefixTree: lookup :: Key -> PrefixMap a -> Maybe a
- Toml.PrefixTree: lookupT :: Key -> PrefixTree a -> Maybe a
- Toml.PrefixTree: newtype Key
- Toml.PrefixTree: newtype Piece
- Toml.PrefixTree: pattern (:||) :: Piece -> [Piece] -> Key
- Toml.PrefixTree: single :: Key -> a -> PrefixMap a
- Toml.PrefixTree: singleT :: Key -> a -> PrefixTree a
- Toml.PrefixTree: toList :: PrefixMap a -> [(Key, a)]
- Toml.PrefixTree: toListT :: PrefixTree a -> [(Key, a)]
- Toml.PrefixTree: type Prefix = Key
- Toml.PrefixTree: type PrefixMap a = HashMap Piece (PrefixTree a)
- Toml.Printer: PrintOptions :: !Maybe (Key -> Key -> Ordering) -> !Int -> PrintOptions
- Toml.Printer: [printOptionsIndent] :: PrintOptions -> !Int
- Toml.Printer: [printOptionsSorting] :: PrintOptions -> !Maybe (Key -> Key -> Ordering)
- Toml.Printer: data PrintOptions
- Toml.Printer: defaultOptions :: PrintOptions
- Toml.Printer: pretty :: TOML -> Text
- Toml.Printer: prettyKey :: Key -> Text
- Toml.Printer: prettyOptions :: PrintOptions -> TOML -> Text
+ Toml.Codec.BiMap: ArbitraryError :: !Text -> TomlBiMapError
+ Toml.Codec.BiMap: BiMap :: (a -> Either e b) -> (b -> Either e a) -> BiMap e a b
+ Toml.Codec.BiMap: WrongConstructor :: !Text -> !Text -> TomlBiMapError
+ Toml.Codec.BiMap: WrongValue :: !MatchError -> TomlBiMapError
+ Toml.Codec.BiMap: [backward] :: BiMap e a b -> b -> Either e a
+ Toml.Codec.BiMap: [forward] :: BiMap e a b -> a -> Either e b
+ Toml.Codec.BiMap: data BiMap e a b
+ Toml.Codec.BiMap: data TomlBiMapError
+ Toml.Codec.BiMap: instance Control.Category.Category (Toml.Codec.BiMap.BiMap e)
+ Toml.Codec.BiMap: instance Control.DeepSeq.NFData Toml.Codec.BiMap.TomlBiMapError
+ Toml.Codec.BiMap: instance GHC.Classes.Eq Toml.Codec.BiMap.TomlBiMapError
+ Toml.Codec.BiMap: instance GHC.Generics.Generic Toml.Codec.BiMap.TomlBiMapError
+ Toml.Codec.BiMap: instance GHC.Show.Show Toml.Codec.BiMap.TomlBiMapError
+ Toml.Codec.BiMap: invert :: BiMap e a b -> BiMap e b a
+ Toml.Codec.BiMap: iso :: (a -> b) -> (b -> a) -> BiMap e a b
+ Toml.Codec.BiMap: mkAnyValueBiMap :: forall a (tag :: TValue). (forall (t :: TValue). Value t -> Either MatchError a) -> (a -> Value tag) -> TomlBiMap a AnyValue
+ Toml.Codec.BiMap: prettyBiMapError :: TomlBiMapError -> Text
+ Toml.Codec.BiMap: prism :: (field -> object) -> (object -> Either error field) -> BiMap error object field
+ Toml.Codec.BiMap: tShow :: Show a => a -> Text
+ Toml.Codec.BiMap: type TomlBiMap = BiMap TomlBiMapError
+ Toml.Codec.BiMap: wrongConstructor :: Show a => Text -> a -> Either TomlBiMapError b
+ Toml.Codec.BiMap.Conversion: _Array :: forall a. TomlBiMap a AnyValue -> TomlBiMap [a] AnyValue
+ Toml.Codec.BiMap.Conversion: _Bool :: TomlBiMap Bool AnyValue
+ Toml.Codec.BiMap.Conversion: _BoundedInteger :: (Integral a, Bounded a, Show a) => TomlBiMap a Integer
+ Toml.Codec.BiMap.Conversion: _ByteString :: TomlBiMap ByteString AnyValue
+ Toml.Codec.BiMap.Conversion: _ByteStringArray :: TomlBiMap ByteString AnyValue
+ Toml.Codec.BiMap.Conversion: _ByteStringText :: TomlBiMap ByteString Text
+ Toml.Codec.BiMap.Conversion: _Coerce :: Coercible a b => TomlBiMap a AnyValue -> TomlBiMap b AnyValue
+ Toml.Codec.BiMap.Conversion: _Day :: TomlBiMap Day AnyValue
+ Toml.Codec.BiMap.Conversion: _Double :: TomlBiMap Double AnyValue
+ Toml.Codec.BiMap.Conversion: _EnumBounded :: (Show a, Enum a, Bounded a) => TomlBiMap a AnyValue
+ Toml.Codec.BiMap.Conversion: _EnumBoundedText :: forall a. (Show a, Enum a, Bounded a) => TomlBiMap a Text
+ Toml.Codec.BiMap.Conversion: _Float :: TomlBiMap Float AnyValue
+ Toml.Codec.BiMap.Conversion: _HashSet :: (Eq a, Hashable a) => TomlBiMap a AnyValue -> TomlBiMap (HashSet a) AnyValue
+ Toml.Codec.BiMap.Conversion: _Int :: TomlBiMap Int AnyValue
+ Toml.Codec.BiMap.Conversion: _IntSet :: TomlBiMap IntSet AnyValue
+ Toml.Codec.BiMap.Conversion: _Integer :: TomlBiMap Integer AnyValue
+ Toml.Codec.BiMap.Conversion: _Just :: Show r => TomlBiMap (Maybe r) r
+ Toml.Codec.BiMap.Conversion: _KeyInt :: TomlBiMap Key Int
+ Toml.Codec.BiMap.Conversion: _KeyString :: TomlBiMap Key String
+ Toml.Codec.BiMap.Conversion: _KeyText :: TomlBiMap Key Text
+ Toml.Codec.BiMap.Conversion: _LByteString :: TomlBiMap ByteString AnyValue
+ Toml.Codec.BiMap.Conversion: _LByteStringArray :: TomlBiMap ByteString AnyValue
+ Toml.Codec.BiMap.Conversion: _LByteStringText :: TomlBiMap ByteString Text
+ Toml.Codec.BiMap.Conversion: _LText :: TomlBiMap Text AnyValue
+ Toml.Codec.BiMap.Conversion: _LTextText :: BiMap e Text Text
+ Toml.Codec.BiMap.Conversion: _Left :: (Show l, Show r) => TomlBiMap (Either l r) l
+ Toml.Codec.BiMap.Conversion: _LocalTime :: TomlBiMap LocalTime AnyValue
+ Toml.Codec.BiMap.Conversion: _Natural :: TomlBiMap Natural AnyValue
+ Toml.Codec.BiMap.Conversion: _NaturalInteger :: TomlBiMap Natural Integer
+ Toml.Codec.BiMap.Conversion: _NonEmpty :: TomlBiMap a AnyValue -> TomlBiMap (NonEmpty a) AnyValue
+ Toml.Codec.BiMap.Conversion: _NonEmptyList :: TomlBiMap (NonEmpty a) [a]
+ Toml.Codec.BiMap.Conversion: _Read :: (Show a, Read a) => TomlBiMap a AnyValue
+ Toml.Codec.BiMap.Conversion: _ReadString :: (Show a, Read a) => TomlBiMap a String
+ Toml.Codec.BiMap.Conversion: _Right :: (Show l, Show r) => TomlBiMap (Either l r) r
+ Toml.Codec.BiMap.Conversion: _Set :: Ord a => TomlBiMap a AnyValue -> TomlBiMap (Set a) AnyValue
+ Toml.Codec.BiMap.Conversion: _String :: TomlBiMap String AnyValue
+ Toml.Codec.BiMap.Conversion: _StringText :: BiMap e String Text
+ Toml.Codec.BiMap.Conversion: _Text :: TomlBiMap Text AnyValue
+ Toml.Codec.BiMap.Conversion: _TextBy :: forall a. (a -> Text) -> (Text -> Either Text a) -> TomlBiMap a AnyValue
+ Toml.Codec.BiMap.Conversion: _TimeOfDay :: TomlBiMap TimeOfDay AnyValue
+ Toml.Codec.BiMap.Conversion: _Validate :: forall a. (a -> Either Text a) -> TomlBiMap a AnyValue -> TomlBiMap a AnyValue
+ Toml.Codec.BiMap.Conversion: _Word :: TomlBiMap Word AnyValue
+ Toml.Codec.BiMap.Conversion: _Word8 :: TomlBiMap Word8 AnyValue
+ Toml.Codec.BiMap.Conversion: _ZonedTime :: TomlBiMap ZonedTime AnyValue
+ Toml.Codec.Code: decode :: TomlCodec a -> Text -> Either [TomlDecodeError] a
+ Toml.Codec.Code: decodeFile :: forall a m. MonadIO m => TomlCodec a -> FilePath -> m a
+ Toml.Codec.Code: decodeFileEither :: forall a m. MonadIO m => TomlCodec a -> FilePath -> m (Either [TomlDecodeError] a)
+ Toml.Codec.Code: decodeValidation :: TomlCodec a -> Text -> Validation [TomlDecodeError] a
+ Toml.Codec.Code: encode :: TomlCodec a -> a -> Text
+ Toml.Codec.Code: encodeToFile :: forall a m. MonadIO m => TomlCodec a -> FilePath -> a -> m Text
+ Toml.Codec.Code: execTomlCodec :: TomlCodec a -> a -> TOML
+ Toml.Codec.Code: runTomlCodec :: TomlCodec a -> TOML -> Validation [TomlDecodeError] a
+ Toml.Codec.Combinator.Common: match :: forall a. TomlBiMap a AnyValue -> Key -> TomlCodec a
+ Toml.Codec.Combinator.Common: whenLeftBiMapError :: Key -> Either TomlBiMapError a -> (a -> Validation [TomlDecodeError] b) -> Validation [TomlDecodeError] b
+ Toml.Codec.Combinator.Custom: enumBounded :: (Bounded a, Enum a, Show a) => Key -> TomlCodec a
+ Toml.Codec.Combinator.Custom: read :: (Show a, Read a) => Key -> TomlCodec a
+ Toml.Codec.Combinator.Custom: textBy :: (a -> Text) -> (Text -> Either Text a) -> Key -> TomlCodec a
+ Toml.Codec.Combinator.Custom: validate :: (a -> Either Text a) -> TomlBiMap a AnyValue -> Key -> TomlCodec a
+ Toml.Codec.Combinator.Custom: validateIf :: forall a. (a -> Bool) -> TomlBiMap a AnyValue -> Key -> TomlCodec a
+ Toml.Codec.Combinator.List: arrayNonEmptyOf :: TomlBiMap a AnyValue -> Key -> TomlCodec (NonEmpty a)
+ Toml.Codec.Combinator.List: arrayOf :: TomlBiMap a AnyValue -> Key -> TomlCodec [a]
+ Toml.Codec.Combinator.List: list :: forall a. TomlCodec a -> Key -> TomlCodec [a]
+ Toml.Codec.Combinator.List: nonEmpty :: forall a. TomlCodec a -> Key -> TomlCodec (NonEmpty a)
+ Toml.Codec.Combinator.Map: hashMap :: forall k v. (Eq k, Hashable k) => TomlCodec k -> TomlCodec v -> Key -> TomlCodec (HashMap k v)
+ Toml.Codec.Combinator.Map: intMap :: forall v. TomlCodec Int -> TomlCodec v -> Key -> TomlCodec (IntMap v)
+ Toml.Codec.Combinator.Map: map :: forall k v. Ord k => TomlCodec k -> TomlCodec v -> Key -> TomlCodec (Map k v)
+ Toml.Codec.Combinator.Map: tableHashMap :: forall k v. (Eq k, Hashable k) => TomlBiMap Key k -> (Key -> TomlCodec v) -> Key -> TomlCodec (HashMap k v)
+ Toml.Codec.Combinator.Map: tableIntMap :: forall v. TomlBiMap Key Int -> (Key -> TomlCodec v) -> Key -> TomlCodec (IntMap v)
+ Toml.Codec.Combinator.Map: tableMap :: forall k v. Ord k => TomlBiMap Key k -> (Key -> TomlCodec v) -> Key -> TomlCodec (Map k v)
+ Toml.Codec.Combinator.Monoid: all :: Key -> TomlCodec All
+ Toml.Codec.Combinator.Monoid: any :: Key -> TomlCodec Any
+ Toml.Codec.Combinator.Monoid: first :: (Key -> TomlCodec a) -> Key -> TomlCodec (First a)
+ Toml.Codec.Combinator.Monoid: last :: (Key -> TomlCodec a) -> Key -> TomlCodec (Last a)
+ Toml.Codec.Combinator.Monoid: product :: Num a => (Key -> TomlCodec a) -> Key -> TomlCodec (Product a)
+ Toml.Codec.Combinator.Monoid: sum :: Num a => (Key -> TomlCodec a) -> Key -> TomlCodec (Sum a)
+ Toml.Codec.Combinator.Primitive: bool :: Key -> TomlCodec Bool
+ Toml.Codec.Combinator.Primitive: byteString :: Key -> TomlCodec ByteString
+ Toml.Codec.Combinator.Primitive: byteStringArray :: Key -> TomlCodec ByteString
+ Toml.Codec.Combinator.Primitive: double :: Key -> TomlCodec Double
+ Toml.Codec.Combinator.Primitive: float :: Key -> TomlCodec Float
+ Toml.Codec.Combinator.Primitive: int :: Key -> TomlCodec Int
+ Toml.Codec.Combinator.Primitive: integer :: Key -> TomlCodec Integer
+ Toml.Codec.Combinator.Primitive: lazyByteString :: Key -> TomlCodec ByteString
+ Toml.Codec.Combinator.Primitive: lazyByteStringArray :: Key -> TomlCodec ByteString
+ Toml.Codec.Combinator.Primitive: lazyText :: Key -> TomlCodec Text
+ Toml.Codec.Combinator.Primitive: natural :: Key -> TomlCodec Natural
+ Toml.Codec.Combinator.Primitive: string :: Key -> TomlCodec String
+ Toml.Codec.Combinator.Primitive: text :: Key -> TomlCodec Text
+ Toml.Codec.Combinator.Primitive: word :: Key -> TomlCodec Word
+ Toml.Codec.Combinator.Primitive: word8 :: Key -> TomlCodec Word8
+ Toml.Codec.Combinator.Set: arrayHashSetOf :: (Hashable a, Eq a) => TomlBiMap a AnyValue -> Key -> TomlCodec (HashSet a)
+ Toml.Codec.Combinator.Set: arrayIntSet :: Key -> TomlCodec IntSet
+ Toml.Codec.Combinator.Set: arraySetOf :: Ord a => TomlBiMap a AnyValue -> Key -> TomlCodec (Set a)
+ Toml.Codec.Combinator.Set: hashSet :: forall a. (Hashable a, Eq a) => TomlCodec a -> Key -> TomlCodec (HashSet a)
+ Toml.Codec.Combinator.Set: intSet :: TomlCodec Int -> Key -> TomlCodec IntSet
+ Toml.Codec.Combinator.Set: set :: forall a. Ord a => TomlCodec a -> Key -> TomlCodec (Set a)
+ Toml.Codec.Combinator.Table: handleTableErrors :: TomlCodec a -> Key -> TOML -> Validation [TomlDecodeError] a
+ Toml.Codec.Combinator.Table: mapTableErrors :: Key -> [TomlDecodeError] -> [TomlDecodeError]
+ Toml.Codec.Combinator.Table: table :: forall a. TomlCodec a -> Key -> TomlCodec a
+ Toml.Codec.Combinator.Time: day :: Key -> TomlCodec Day
+ Toml.Codec.Combinator.Time: localTime :: Key -> TomlCodec LocalTime
+ Toml.Codec.Combinator.Time: timeOfDay :: Key -> TomlCodec TimeOfDay
+ Toml.Codec.Combinator.Time: zonedTime :: Key -> TomlCodec ZonedTime
+ Toml.Codec.Combinator.Tuple: pair :: TomlCodec a -> TomlCodec b -> TomlCodec (a, b)
+ Toml.Codec.Combinator.Tuple: triple :: TomlCodec a -> TomlCodec b -> TomlCodec c -> TomlCodec (a, b, c)
+ Toml.Codec.Di: (.=) :: Codec field a -> (object -> field) -> Codec object a
+ Toml.Codec.Di: dimap :: (b -> a) -> (a -> b) -> TomlCodec a -> TomlCodec b
+ Toml.Codec.Di: dimatch :: (b -> Maybe a) -> (a -> b) -> TomlCodec a -> TomlCodec b
+ Toml.Codec.Di: dioptional :: TomlCodec a -> TomlCodec (Maybe a)
+ Toml.Codec.Di: diwrap :: forall b a. Coercible a b => TomlCodec a -> TomlCodec b
+ Toml.Codec.Di: infixl 5 .=
+ Toml.Codec.Error: BiMapError :: !Key -> !TomlBiMapError -> TomlDecodeError
+ Toml.Codec.Error: KeyNotFound :: !Key -> TomlDecodeError
+ Toml.Codec.Error: LoadTomlException :: !FilePath -> !Text -> LoadTomlException
+ Toml.Codec.Error: ParseError :: !TomlParseError -> TomlDecodeError
+ Toml.Codec.Error: TableArrayNotFound :: !Key -> TomlDecodeError
+ Toml.Codec.Error: TableNotFound :: !Key -> TomlDecodeError
+ Toml.Codec.Error: data LoadTomlException
+ Toml.Codec.Error: data TomlDecodeError
+ Toml.Codec.Error: instance Control.DeepSeq.NFData Toml.Codec.Error.TomlDecodeError
+ Toml.Codec.Error: instance GHC.Classes.Eq Toml.Codec.Error.TomlDecodeError
+ Toml.Codec.Error: instance GHC.Exception.Type.Exception Toml.Codec.Error.LoadTomlException
+ Toml.Codec.Error: instance GHC.Generics.Generic Toml.Codec.Error.TomlDecodeError
+ Toml.Codec.Error: instance GHC.Show.Show Toml.Codec.Error.LoadTomlException
+ Toml.Codec.Error: instance GHC.Show.Show Toml.Codec.Error.TomlDecodeError
+ Toml.Codec.Error: prettyTomlDecodeError :: TomlDecodeError -> Text
+ Toml.Codec.Error: prettyTomlDecodeErrors :: [TomlDecodeError] -> Text
+ Toml.Codec.Generic: ByteStringAsBytes :: ByteString -> ByteStringAsBytes
+ Toml.Codec.Generic: ByteStringAsText :: ByteString -> ByteStringAsText
+ Toml.Codec.Generic: GenericOptions :: (String -> String) -> GenericOptions
+ Toml.Codec.Generic: LByteStringAsBytes :: ByteString -> LByteStringAsBytes
+ Toml.Codec.Generic: LByteStringAsText :: ByteString -> LByteStringAsText
+ Toml.Codec.Generic: TomlOptions :: (Typeable a => Proxy a -> String -> String) -> TomlOptions a
+ Toml.Codec.Generic: TomlTable :: a -> TomlTable a
+ Toml.Codec.Generic: [genericOptionsFieldModifier] :: GenericOptions -> String -> String
+ Toml.Codec.Generic: [tomlOptionsFieldModifier] :: TomlOptions a -> Typeable a => Proxy a -> String -> String
+ Toml.Codec.Generic: [unByteStringAsBytes] :: ByteStringAsBytes -> ByteString
+ Toml.Codec.Generic: [unByteStringAsText] :: ByteStringAsText -> ByteString
+ Toml.Codec.Generic: [unLByteStringAsBytes] :: LByteStringAsBytes -> ByteString
+ Toml.Codec.Generic: [unLByteStringAsText] :: LByteStringAsText -> ByteString
+ Toml.Codec.Generic: [unTomlTable] :: TomlTable a -> a
+ Toml.Codec.Generic: class GenericCodec (f :: k -> Type)
+ Toml.Codec.Generic: class HasCodec a
+ Toml.Codec.Generic: class HasItemCodec a
+ Toml.Codec.Generic: data TomlOptions a
+ Toml.Codec.Generic: genericCodec :: (Generic a, GenericCodec (Rep a)) => TomlCodec a
+ Toml.Codec.Generic: genericCodecWithOptions :: forall a. (Generic a, GenericCodec (Rep a), Typeable a) => TomlOptions a -> TomlCodec a
+ Toml.Codec.Generic: genericTomlCodec :: GenericCodec f => GenericOptions -> TomlCodec (f p)
+ Toml.Codec.Generic: hasCodec :: HasCodec a => Key -> TomlCodec a
+ Toml.Codec.Generic: hasItemCodec :: HasItemCodec a => Either (TomlBiMap a AnyValue) (TomlCodec a)
+ Toml.Codec.Generic: instance (Data.Hashable.Class.Hashable a, GHC.Classes.Eq a, Toml.Codec.Generic.HasItemCodec a) => Toml.Codec.Generic.HasCodec (Data.HashSet.Base.HashSet a)
+ Toml.Codec.Generic: instance (Data.Hashable.Class.Hashable k, GHC.Classes.Eq k, Toml.Codec.Generic.HasCodec k, Toml.Codec.Generic.HasCodec v) => Toml.Codec.Generic.HasCodec (Data.HashMap.Base.HashMap k v)
+ Toml.Codec.Generic: instance (GHC.Classes.Ord a, Toml.Codec.Generic.HasItemCodec a) => Toml.Codec.Generic.HasCodec (Data.Set.Internal.Set a)
+ Toml.Codec.Generic: instance (GHC.Classes.Ord k, Toml.Codec.Generic.HasCodec k, Toml.Codec.Generic.HasCodec v) => Toml.Codec.Generic.HasCodec (Data.Map.Internal.Map k v)
+ Toml.Codec.Generic: instance (GHC.Generics.Generic a, Toml.Codec.Generic.GenericCodec (GHC.Generics.Rep a)) => Toml.Codec.Generic.HasCodec (Toml.Codec.Generic.TomlTable a)
+ Toml.Codec.Generic: instance (GHC.Generics.Generic a, Toml.Codec.Generic.GenericCodec (GHC.Generics.Rep a)) => Toml.Codec.Generic.HasItemCodec (Toml.Codec.Generic.TomlTable a)
+ Toml.Codec.Generic: instance (GHC.Generics.Selector s, Toml.Codec.Generic.HasCodec a) => Toml.Codec.Generic.GenericCodec (GHC.Generics.S1 s (GHC.Generics.Rec0 a))
+ Toml.Codec.Generic: instance (GHC.Num.Num a, Toml.Codec.Generic.HasCodec a) => Toml.Codec.Generic.HasCodec (Data.Semigroup.Internal.Product a)
+ Toml.Codec.Generic: instance (GHC.Num.Num a, Toml.Codec.Generic.HasCodec a) => Toml.Codec.Generic.HasCodec (Data.Semigroup.Internal.Sum a)
+ Toml.Codec.Generic: instance (Toml.Codec.Generic.HasItemCodec a, Data.Typeable.Internal.Typeable a) => Toml.Codec.Generic.HasItemCodec [a]
+ Toml.Codec.Generic: instance GHC.Classes.Eq Toml.Codec.Generic.ByteStringAsBytes
+ Toml.Codec.Generic: instance GHC.Classes.Eq Toml.Codec.Generic.ByteStringAsText
+ Toml.Codec.Generic: instance GHC.Classes.Eq Toml.Codec.Generic.LByteStringAsBytes
+ Toml.Codec.Generic: instance GHC.Classes.Eq Toml.Codec.Generic.LByteStringAsText
+ Toml.Codec.Generic: instance GHC.Show.Show Toml.Codec.Generic.ByteStringAsBytes
+ Toml.Codec.Generic: instance GHC.Show.Show Toml.Codec.Generic.ByteStringAsText
+ Toml.Codec.Generic: instance GHC.Show.Show Toml.Codec.Generic.LByteStringAsBytes
+ Toml.Codec.Generic: instance GHC.Show.Show Toml.Codec.Generic.LByteStringAsText
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasCodec Data.IntSet.Internal.IntSet
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasCodec Data.Semigroup.Internal.All
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasCodec Data.Semigroup.Internal.Any
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasCodec Data.Text.Internal.Lazy.Text
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasCodec Data.Text.Internal.Text
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasCodec Data.Time.Calendar.Days.Day
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasCodec Data.Time.LocalTime.Internal.LocalTime.LocalTime
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasCodec Data.Time.LocalTime.Internal.TimeOfDay.TimeOfDay
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasCodec Data.Time.LocalTime.Internal.ZonedTime.ZonedTime
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasCodec GHC.Integer.Type.Integer
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasCodec GHC.Natural.Natural
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasCodec GHC.Types.Bool
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasCodec GHC.Types.Double
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasCodec GHC.Types.Float
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasCodec GHC.Types.Int
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasCodec GHC.Types.Word
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasCodec GHC.Word.Word8
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasCodec Toml.Codec.Generic.ByteStringAsBytes
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasCodec Toml.Codec.Generic.ByteStringAsText
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasCodec Toml.Codec.Generic.LByteStringAsBytes
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasCodec Toml.Codec.Generic.LByteStringAsText
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasCodec a => Toml.Codec.Generic.HasCodec (Data.Monoid.First a)
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasCodec a => Toml.Codec.Generic.HasCodec (Data.Monoid.Last a)
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasCodec a => Toml.Codec.Generic.HasCodec (GHC.Maybe.Maybe a)
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasCodec v => Toml.Codec.Generic.HasCodec (Data.IntMap.Internal.IntMap v)
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasItemCodec Data.IntSet.Internal.IntSet
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasItemCodec Data.Text.Internal.Lazy.Text
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasItemCodec Data.Text.Internal.Text
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasItemCodec Data.Time.Calendar.Days.Day
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasItemCodec Data.Time.LocalTime.Internal.LocalTime.LocalTime
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasItemCodec Data.Time.LocalTime.Internal.TimeOfDay.TimeOfDay
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasItemCodec Data.Time.LocalTime.Internal.ZonedTime.ZonedTime
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasItemCodec GHC.Integer.Type.Integer
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasItemCodec GHC.Natural.Natural
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasItemCodec GHC.Types.Bool
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasItemCodec GHC.Types.Double
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasItemCodec GHC.Types.Float
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasItemCodec GHC.Types.Int
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasItemCodec GHC.Types.Word
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasItemCodec GHC.Word.Word8
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasItemCodec Toml.Codec.Generic.ByteStringAsBytes
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasItemCodec Toml.Codec.Generic.ByteStringAsText
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasItemCodec Toml.Codec.Generic.LByteStringAsBytes
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasItemCodec Toml.Codec.Generic.LByteStringAsText
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasItemCodec a => Toml.Codec.Generic.HasCodec (GHC.Base.NonEmpty a)
+ Toml.Codec.Generic: instance Toml.Codec.Generic.HasItemCodec a => Toml.Codec.Generic.HasCodec [a]
+ Toml.Codec.Generic: instance forall k (f :: k -> *) (c :: GHC.Generics.Meta). Toml.Codec.Generic.GenericCodec f => Toml.Codec.Generic.GenericCodec (GHC.Generics.C1 c f)
+ Toml.Codec.Generic: instance forall k (f :: k -> *) (d :: GHC.Generics.Meta). Toml.Codec.Generic.GenericCodec f => Toml.Codec.Generic.GenericCodec (GHC.Generics.D1 d f)
+ Toml.Codec.Generic: instance forall k (f :: k -> *) (g :: k -> *). (Toml.Codec.Generic.GenericCodec f, Toml.Codec.Generic.GenericCodec g) => Toml.Codec.Generic.GenericCodec (f GHC.Generics.:*: g)
+ Toml.Codec.Generic: instance forall k (f :: k -> *) (g :: k -> *). (TypeError ...) => Toml.Codec.Generic.GenericCodec (f GHC.Generics.:+: g)
+ Toml.Codec.Generic: newtype ByteStringAsBytes
+ Toml.Codec.Generic: newtype ByteStringAsText
+ Toml.Codec.Generic: newtype GenericOptions
+ Toml.Codec.Generic: newtype LByteStringAsBytes
+ Toml.Codec.Generic: newtype LByteStringAsText
+ Toml.Codec.Generic: newtype TomlTable a
+ Toml.Codec.Generic: stripTypeNameCodec :: forall a. (Generic a, GenericCodec (Rep a), Typeable a) => TomlCodec a
+ Toml.Codec.Generic: stripTypeNameOptions :: Typeable a => TomlOptions a
+ Toml.Codec.Generic: stripTypeNamePrefix :: forall a. Typeable a => Proxy a -> String -> String
+ Toml.Codec.Types: (<!>) :: Alternative f => (a -> f x) -> (a -> f x) -> a -> f x
+ Toml.Codec.Types: Codec :: TomlEnv o -> (i -> TomlState o) -> Codec i o
+ Toml.Codec.Types: TomlState :: (TOML -> (Maybe a, TOML)) -> TomlState a
+ Toml.Codec.Types: [codecRead] :: Codec i o -> TomlEnv o
+ Toml.Codec.Types: [codecWrite] :: Codec i o -> i -> TomlState o
+ Toml.Codec.Types: [unTomlState] :: TomlState a -> TOML -> (Maybe a, TOML)
+ Toml.Codec.Types: data Codec i o
+ Toml.Codec.Types: eitherToTomlState :: Either e a -> TomlState a
+ Toml.Codec.Types: infixl 3 <!>
+ Toml.Codec.Types: instance (s GHC.Types.~ Toml.Type.TOML.TOML) => Control.Monad.State.Class.MonadState s Toml.Codec.Types.TomlState
+ Toml.Codec.Types: instance GHC.Base.Alternative (Toml.Codec.Types.Codec i)
+ Toml.Codec.Types: instance GHC.Base.Alternative Toml.Codec.Types.TomlState
+ Toml.Codec.Types: instance GHC.Base.Applicative (Toml.Codec.Types.Codec i)
+ Toml.Codec.Types: instance GHC.Base.Applicative Toml.Codec.Types.TomlState
+ Toml.Codec.Types: instance GHC.Base.Functor (Toml.Codec.Types.Codec i)
+ Toml.Codec.Types: instance GHC.Base.Functor Toml.Codec.Types.TomlState
+ Toml.Codec.Types: instance GHC.Base.Monad Toml.Codec.Types.TomlState
+ Toml.Codec.Types: newtype TomlState a
+ Toml.Codec.Types: type TomlCodec a = Codec a a
+ Toml.Codec.Types: type TomlEnv a = TOML -> Validation [TomlDecodeError] a
+ Toml.Parser: TomlParseError :: Text -> TomlParseError
+ Toml.Parser: [unTomlParseError] :: TomlParseError -> Text
+ Toml.Parser: instance Control.DeepSeq.NFData Toml.Parser.TomlParseError
+ Toml.Parser: instance GHC.Classes.Eq Toml.Parser.TomlParseError
+ Toml.Parser: instance GHC.Generics.Generic Toml.Parser.TomlParseError
+ Toml.Parser: instance GHC.Show.Show Toml.Parser.TomlParseError
+ Toml.Parser: newtype TomlParseError
+ Toml.Parser: parseKey :: Text -> Either TomlParseError Key
+ Toml.Parser.Core: infix 0 <?>
+ Toml.Parser.Item: keyValP :: Parser TomlItem
+ Toml.Type.Edsl: (=:) :: Key -> Value a -> TDSL
+ Toml.Type.Edsl: empty :: TDSL
+ Toml.Type.Edsl: mkToml :: TDSL -> TOML
+ Toml.Type.Edsl: table :: Key -> TDSL -> TDSL
+ Toml.Type.Edsl: tableArray :: Key -> NonEmpty TDSL -> TDSL
+ Toml.Type.Edsl: type TDSL = State TOML ()
+ Toml.Type.Key: (<|) :: Piece -> Key -> Key
+ Toml.Type.Key: Diff :: !Key -> !Key -> !Key -> KeysDiff
+ Toml.Type.Key: Equal :: KeysDiff
+ Toml.Type.Key: FstIsPref :: !Key -> KeysDiff
+ Toml.Type.Key: Key :: NonEmpty Piece -> Key
+ Toml.Type.Key: NoPrefix :: KeysDiff
+ Toml.Type.Key: Piece :: Text -> Piece
+ Toml.Type.Key: SndIsPref :: !Key -> KeysDiff
+ Toml.Type.Key: [unKey] :: Key -> NonEmpty Piece
+ Toml.Type.Key: [unPiece] :: Piece -> Text
+ Toml.Type.Key: data KeysDiff
+ Toml.Type.Key: instance Control.DeepSeq.NFData Toml.Type.Key.Key
+ Toml.Type.Key: instance Control.DeepSeq.NFData Toml.Type.Key.Piece
+ Toml.Type.Key: instance Data.Hashable.Class.Hashable Toml.Type.Key.Key
+ Toml.Type.Key: instance Data.Hashable.Class.Hashable Toml.Type.Key.Piece
+ Toml.Type.Key: instance Data.String.IsString Toml.Type.Key.Key
+ Toml.Type.Key: instance Data.String.IsString Toml.Type.Key.Piece
+ Toml.Type.Key: instance GHC.Base.Semigroup Toml.Type.Key.Key
+ Toml.Type.Key: instance GHC.Classes.Eq Toml.Type.Key.Key
+ Toml.Type.Key: instance GHC.Classes.Eq Toml.Type.Key.KeysDiff
+ Toml.Type.Key: instance GHC.Classes.Eq Toml.Type.Key.Piece
+ Toml.Type.Key: instance GHC.Classes.Ord Toml.Type.Key.Key
+ Toml.Type.Key: instance GHC.Classes.Ord Toml.Type.Key.Piece
+ Toml.Type.Key: instance GHC.Generics.Generic Toml.Type.Key.Key
+ Toml.Type.Key: instance GHC.Generics.Generic Toml.Type.Key.Piece
+ Toml.Type.Key: instance GHC.Show.Show Toml.Type.Key.Key
+ Toml.Type.Key: instance GHC.Show.Show Toml.Type.Key.KeysDiff
+ Toml.Type.Key: instance GHC.Show.Show Toml.Type.Key.Piece
+ Toml.Type.Key: keysDiff :: Key -> Key -> KeysDiff
+ Toml.Type.Key: newtype Key
+ Toml.Type.Key: newtype Piece
+ Toml.Type.Key: pattern (:||) :: Piece -> [Piece] -> Key
+ Toml.Type.Key: type Prefix = Key
+ Toml.Type.PrefixTree: Branch :: !Prefix -> !Maybe a -> !PrefixMap a -> PrefixTree a
+ Toml.Type.PrefixTree: Leaf :: !Key -> !a -> PrefixTree a
+ Toml.Type.PrefixTree: data PrefixTree a
+ Toml.Type.PrefixTree: fromList :: [(Key, a)] -> PrefixMap a
+ Toml.Type.PrefixTree: insert :: Key -> a -> PrefixMap a -> PrefixMap a
+ Toml.Type.PrefixTree: insertT :: Key -> a -> PrefixTree a -> PrefixTree a
+ Toml.Type.PrefixTree: instance Control.DeepSeq.NFData a => Control.DeepSeq.NFData (Toml.Type.PrefixTree.PrefixTree a)
+ Toml.Type.PrefixTree: instance GHC.Base.Semigroup (Toml.Type.PrefixTree.PrefixTree a)
+ Toml.Type.PrefixTree: instance GHC.Classes.Eq a => GHC.Classes.Eq (Toml.Type.PrefixTree.PrefixTree a)
+ Toml.Type.PrefixTree: instance GHC.Generics.Generic (Toml.Type.PrefixTree.PrefixTree a)
+ Toml.Type.PrefixTree: instance GHC.Show.Show a => GHC.Show.Show (Toml.Type.PrefixTree.PrefixTree a)
+ Toml.Type.PrefixTree: lookup :: Key -> PrefixMap a -> Maybe a
+ Toml.Type.PrefixTree: lookupT :: Key -> PrefixTree a -> Maybe a
+ Toml.Type.PrefixTree: single :: Key -> a -> PrefixMap a
+ Toml.Type.PrefixTree: singleT :: Key -> a -> PrefixTree a
+ Toml.Type.PrefixTree: toList :: PrefixMap a -> [(Key, a)]
+ Toml.Type.PrefixTree: toListT :: PrefixTree a -> [(Key, a)]
+ Toml.Type.PrefixTree: type PrefixMap a = HashMap Piece (PrefixTree a)
+ Toml.Type.Printer: PrintOptions :: !Maybe (Key -> Key -> Ordering) -> !Int -> PrintOptions
+ Toml.Type.Printer: [printOptionsIndent] :: PrintOptions -> !Int
+ Toml.Type.Printer: [printOptionsSorting] :: PrintOptions -> !Maybe (Key -> Key -> Ordering)
+ Toml.Type.Printer: data PrintOptions
+ Toml.Type.Printer: defaultOptions :: PrintOptions
+ Toml.Type.Printer: pretty :: TOML -> Text
+ Toml.Type.Printer: prettyKey :: Key -> Text
+ Toml.Type.Printer: prettyOptions :: PrintOptions -> TOML -> Text
- Toml.Parser: parse :: Text -> Either ParseException TOML
+ Toml.Parser: parse :: Text -> Either TomlParseError TOML
- Toml.Parser.Core: binary :: (MonadParsec e s m, Token s ~ Char, Integral a) => m a
+ Toml.Parser.Core: binary :: (MonadParsec e s m, Token s ~ Char, Num a) => m a
- Toml.Parser.Core: hexadecimal :: (MonadParsec e s m, Token s ~ Char, Integral a) => m a
+ Toml.Parser.Core: hexadecimal :: (MonadParsec e s m, Token s ~ Char, Num a) => m a
- Toml.Parser.Core: octal :: (MonadParsec e s m, Token s ~ Char, Integral a) => m a
+ Toml.Parser.Core: octal :: (MonadParsec e s m, Token s ~ Char, Num a) => m a

Files

CHANGELOG.md view
@@ -3,6 +3,106 @@ tomland uses [PVP Versioning][1]. The changelog is available [on GitHub][2]. +## 1.3.0.0 — May 19, 2020++* [#253](https://github.com/kowainik/tomland/issues/253):+  Support GHC-8.10.1. Move to GHC-8.8.3 from 8.8.1.+* Drop support of GHC-8.2.2.+* [#271](https://github.com/kowainik/tomland/issues/271):+  Use `Validation` from `validation-selective` in `TomlEnv`.+  This allows to accumulate and display all errors that occurs during the+  decoding phase.+  All  previous decode functions return list of all `TomlDecodeError`s.++  __Note:__ Due to the specific of `Validation` data type, there is no `Monad`+  instanse of `Codec` anymore. However, this doesn't limit any previously+  released features.+* Add `decodeValidation`, `decodeFileValidation` functions to return+  `Validation` instead of `Either`.+* [#263](https://github.com/kowainik/tomland/issues/263):+  Simplify `Codec` abstraction. Instead of having `Codec r w c a`+  now it is `Codec TomlEnv TomlState c a`.++  Remove `BiCodec` as it is simple `TomlCodec` with this change.+* [#256](https://github.com/kowainik/tomland/issues/256),+  [#278](https://github.com/kowainik/tomland/issues/278):+  Rename modules to simplify module structure.++  __Migration guide:__ If you use `Toml` module (as advised by the library) then+  no changes required in your code.+   If you import some particular modules from `tomland` here is the renaming+   scheme you can use to apply to your imports:++   | __Old__ | __New__ |+   |-----|-----|+   | `Toml.Bi` | `Toml.Codec` |+   | `Toml.Bi.Combinators` | `Toml.Codec.Combinator` |+   | `Toml.Bi.Monad` | `Toml.Codec.Types` |+   | `Toml.Bi.Code` | `Toml.Codec.Code` or `Toml.Codec.Types` or `Toml.Codec.Error` |+   | `Toml.Bi.Map` | `Toml.Codec.BiMap` or `Toml.Codec.BiMap.Conversion` |+   | `Toml.Generic` | `Toml.Codec.Generic` |+   | `Toml.Edsl` | `Toml.Type.Edsl` |+   | `Toml.Printer` | `Toml.Type.Printer` |+   | `Toml.PrefixTree` | `Toml.Type.PrefixTree` or `Toml.Type.Key` |+++* [#283](https://github.com/kowainik/tomland/issues/283):+  Documentation improvements:+    - Add Codec Tables to each kind of codecs with examples+    - Add high-level description to each reexported module+    - Add @since annotations+    - Improve README+    - Add more examples into functions+* [#237](https://github.com/kowainik/tomland/issues/237):+  Add `BiMap` `_Validate` and codecs `validate` and `validateIf` for custom+  validation.+* [#289](https://github.com/kowainik/tomland/issues/289):+  Add `_Coerce` `TomlBiMap`.+* [#270](https://github.com/kowainik/tomland/issues/270):+  Add `pair` and `triple` codecs for tuples.+* [#261](https://github.com/kowainik/tomland/issues/261):+  Implement `tableMap` codec to use TOML keys as `Map` keys.+* [#243](https://github.com/kowainik/tomland/issues/243):+  Implement `hashMap`, `tableHashMap`, `intMap`, `tableIntMap` codec+  combinators.+* Add `intSet` codec.+* Add `_KeyInt` `BiMap`for key-as-int approach.+* [#242](https://github.com/kowainik/tomland/issues/242):+  Add `HasCodec` instances for `Map`, `HashMap` and `IntMap` for+  `Generic` deriving.+* [#272](https://github.com/kowainik/tomland/issues/272):+  Add `TomlTable` newtype to be used in generic `DerivingVia`.+* [#251](https://github.com/kowainik/tomland/issues/251):+  Implement `ByteStringAsText`, `ByteStringAsBytes`, `LByteStringAsText`,+  `LByteStringAsBytes` newtypes. Add corresponding `HasCodec` instances for+  these data types.+* [#311](https://github.com/kowainik/tomland/issues/311):+  Reimplement custom `TomlState` instead of using `MaybeT` and `State`.+* Rename `ParseException` to `TomlParseError`.+* Rename `DecodeException` to `TomlDecodeError`.+* Add `TableArrayNotFound` constructor to `TomlDecodeError`.+* Remove `TrivialError` and `TypeMismatch` constructors of the `TomlDecodeError`+  type.+* [#313](https://github.com/kowainik/tomland/issues/313):+  Store `Key` in the `BiMapError` constructor of `TomlDecodeError`.+* Add `decodeFileEither` and `encodeToFile` functions.+* Fix `sum` and `product` behaviour on missing fields. Now it returns the+  wrapper of `mempty` instead of failure.+* [#302](https://github.com/kowainik/tomland/issues/302):+  `nonEmpty` codec throws `TableArrayNotFound` instead of `TableNotFound`.+* [#318](https://github.com/kowainik/tomland/issues/318):+  Export a function for parsing TOML keys `parseKey`.+* [#310](https://github.com/kowainik/tomland/issues/310):+  Add tests on all kinds of `TomlDecodeError` with `decode` function.+* [#218](https://github.com/kowainik/tomland/issues/218):+  Add tests for TOML validation.+* [#252](https://github.com/kowainik/tomland/issues/252):+  Move to `hspec-*` family of libraries from `tasty-*`.+* [#297](https://github.com/kowainik/tomland/issues/297):+  Tests parallelism and speed-up.+* [#246](https://github.com/kowainik/tomland/issues/246):+  Bump up `megaparsec` version to `8.0.0`.+ ## 1.2.1.0 — Nov 6, 2019  * [#203](https://github.com/kowainik/tomland/issues/203):
README.lhs view
@@ -1,8 +1,8 @@ # tomland  ![palm](https://user-images.githubusercontent.com/4276606/51088259-7a777000-176e-11e9-9d76-6be4023c0ac3.png)+ [![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)@@ -12,19 +12,28 @@ > “A library is like an island in the middle of a vast sea of ignorance, > particularly if the library is very tall and the surrounding area has been > flooded.”->+ > ― Lemony Snicket, Horseradish -Bidirectional TOML serialization. The following blog post has more details about-library design:+`tomland` is a Haskell library for _Bidirectional TOML+Serialization_. It provides the composable interface for implementing+[TOML](https://github.com/toml-lang/toml) codecs. If you want to use+TOML as a configuration for your tool or application, you can use+`tomland` to easily convert in both ways between textual TOML+representation and Haskell types. -* [`tomland`: Bidirectional TOML serialization](https://kowainik.github.io/posts/2019-01-14-tomland)+✍️ `tomland` supports [TOML spec version 0.5.0](https://github.com/toml-lang/toml/wiki#v050-compliant). +The following blog post has more details about the library design and+internal implementation details:++* [`tomland`: Bidirectional TOML Serialization](https://kowainik.github.io/posts/2019-01-14-tomland)+ This README contains a basic usage example of the `tomland` library. All code below can be compiled and run with the following command:  ```-cabal new-run readme+cabal run readme ```  ## Preamble: imports and language extensions@@ -39,15 +48,15 @@ {-# LANGUAGE OverloadedStrings #-}  import Control.Applicative ((<|>))-import Control.Category ((>>>)) import Data.Text (Text)-import Toml (TomlBiMap, TomlCodec, (.=))+import Data.Time (Day)+import Toml (TomlCodec, (.=))  import qualified Data.Text.IO as TIO import qualified Toml ``` -`tomland` is mostly designed for qualified imports and intended to be imported+`tomland` is designed for qualified imports and intended to be imported as follows:  ```haskell ignore@@ -57,10 +66,40 @@  ## Data type: parsing and printing -We're going to parse TOML configuration from [`examples/readme.toml`](examples/readme.toml) file.+We're going to parse TOML configuration from+[`examples/readme.toml`](examples/readme.toml) file. The configuration+contains the following description of our data: -This static configuration is captured by the following Haskell data type:+```toml+server.port        = 8080+server.codes       = [ 5, 10, 42 ]+server.description = """+This is production server.+Don't touch it!+""" +[mail]+    host = "smtp.gmail.com"+    send-if-inactive = false++[[user]]+   guestId = 42++[[user]]+   guestId = 114++[[user]]+    login = "Foo Bar"+    createdAt = 2020-05-19+```++The above static configuration describes `Settings` for some+server. It has several top-level fields, a table with the name `mail`+and an array of tables with the name `user` that stores list of+different types of users.++We can model such TOML using the following Haskell data types:+ ```haskell data Settings = Settings     { settingsPort        :: !Port@@ -76,25 +115,29 @@     }  data User-    = Admin  !Integer  -- id of admin-    | Client !Text     -- name of the client-    deriving stock (Show)+    = Guest !Integer  -- id of guest+    | Registered !RegisteredUser  -- login and createdAt of registered user +data RegisteredUser = RegisteredUser+    { registeredUserLogin     :: !Text+    , registeredUserCreatedAt :: !Day+    }+ newtype Port = Port Int newtype Host = Host Text ``` -Using `tomland` library, you can write bidirectional converters for these types-using the following guidelines and helper functions:+Using the `tomland` library, you can write bidirectional converters for these types+with the following guidelines and helper functions: -1. If your fields are some simple basic types like `Int` or `Text` you can just+1. If your fields are some simple primitive types like `Int` or `Text` you can just    use standard codecs like `Toml.int` and `Toml.text`. 2. If you want to parse `newtype`s, use `Toml.diwrap` to wrap parsers for    underlying `newtype` representation.-3. For parsing nested data types, use `Toml.table`. But this requires to specify-   this data type as TOML table in `.toml` file.+3. For parsing nested data types, use `Toml.table`. But it requires to specify+   this data type as TOML table in the `.toml` file. 4. If you have lists of custom data types, use `Toml.list`. Such lists are-   represented as array of tables in TOML. If you have lists of primitive types+   represented as _array of tables_ in TOML. If you have lists of the primitive types    like `Int`, `Bool`, `Double`, `Text` or time types, that you can use    `Toml.arrayOf` and parse arrays of values. 5. If you have sets of custom data types, use `Toml.set` or `Toml.HashSet`. Such@@ -121,41 +164,49 @@     <$> Toml.diwrap (Toml.text "host") .= mailHost     <*> Toml.bool "send-if-inactive"   .= mailSendIfInactive -_Admin :: TomlBiMap User Integer-_Admin = Toml.prism Admin $ \case-    Admin i -> Right i-    other   -> Toml.wrongConstructor "Admin" other+matchGuest :: User -> Maybe Integer+matchGuest = \case+   Guest i -> Just i+   _ -> Nothing -_Client :: TomlBiMap User Text-_Client = Toml.prism Client $ \case-    Client n -> Right n-    other    -> Toml.wrongConstructor "Client" other+matchRegistered :: User -> Maybe RegisteredUser+matchRegistered = \case+   Registered u -> Just u+   _ -> Nothing  userCodec :: TomlCodec User userCodec =-        Toml.match (_Admin >>> Toml._Integer) "id"-    <|> Toml.match (_Client >>> Toml._Text) "name"+        Toml.dimatch matchGuest      Guest      (Toml.integer "guestId")+    <|> Toml.dimatch matchRegistered Registered registeredUserCodec++registeredUserCodec :: TomlCodec RegisteredUser+registeredUserCodec = RegisteredUser+    <$> Toml.text "login"     .= registeredUserLogin+    <*> Toml.day  "createdAt" .= registeredUserCreatedAt ``` -And now we're ready to parse our TOML and print the result back to see whether+And now we are ready to parse our TOML and print the result back to see whether everything is okay.  ```haskell main :: IO () main = do-    tomlExample <- TIO.readFile "examples/readme.toml"-    let res = Toml.decode settingsCodec tomlExample-    case res of-        Left err -> print err+    tomlRes <- Toml.decodeFileEither settingsCodec "examples/readme.toml"+    case tomlRes of+        Left errs      -> TIO.putStrLn $ Toml.prettyTomlDecodeErrors errs         Right settings -> TIO.putStrLn $ Toml.encode settingsCodec settings ```  ## Benchmarks and comparison with other libraries -`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.+You can find benchmarks of the `tomland` library in the following repository: +* [kowainik/toml-benchmarks](https://github.com/kowainik/toml-benchmarks)++Since `tomland` 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.+ | Library            | parse :: Text -> AST | transform :: AST -> Haskell | |--------------------|----------------------|-----------------------------| | `tomland`          | `305.5 μs`           | `1.280 μs`                  |@@ -163,21 +214,22 @@ | `htoml-megaparsec` | `295.0 μs`           | `33.62 μs`                  | | `toml-parser`      | `164.6 μs`           | `1.101 μs`                  | -You may see that `tomland` is not the fastest one (though still very fast). But-performance hasn’t been optimized so far and:+In addition to the above numbers, `tomland` has several features that+make it unique: -1. `toml-parser` doesn’t support the array of tables and because of that it’s-   hardly possible to specify the list of custom data types in TOML with this-   library.-2. `tomland` supports latest TOML spec while `htoml` and `htoml-megaparsec`-   don’t have support for all types, values and formats.-3. `tomland` is the only library that has pretty-printing.-4. `toml-parser` doesn’t have ways to convert TOML AST to custom Haskell types-   and `htoml*` libraries use typeclasses-based approach via `aeson` library.-5. `tomland` is bidirectional :slightly_smiling_face:+1. `tomland` is the only Haskell library that has pretty-printing.+2. `tomland` is compatible with the latest TOML spec while other libraries are not.+3. `tomland` is bidirectional, which means that your encoding and+   decoding are consistent with each other by construction.+4. `tomland` provides abilities for `Generic` and `DerivingVia`+   deriving out-of-the-box.+5. Despite being the fastest, `toml-parser` doesn’t support the array+   of tables and because of that it’s hardly possible to specify the list+   of custom data types in TOML with this library. In addition,+   `toml-parser` doesn’t have ways to convert TOML AST to custom+   Haskell types and `htoml*` libraries use typeclasses-based approach+   via `aeson` library.  ## Acknowledgement -Icons made by [Freepik](http://www.freepik.com) from-[www.flaticon.com](https://www.flaticon.com/) is licensed by-[CC 3.0 BY](http://creativecommons.org/licenses/by/3.0/).+Icons made by [Freepik](http://www.freepik.com) from [www.flaticon.com](https://www.flaticon.com/) is licensed by [CC 3.0 BY](http://creativecommons.org/licenses/by/3.0/).
README.md view
@@ -1,8 +1,8 @@ # tomland  ![palm](https://user-images.githubusercontent.com/4276606/51088259-7a777000-176e-11e9-9d76-6be4023c0ac3.png)+ [![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)@@ -12,19 +12,28 @@ > “A library is like an island in the middle of a vast sea of ignorance, > particularly if the library is very tall and the surrounding area has been > flooded.”->+ > ― Lemony Snicket, Horseradish -Bidirectional TOML serialization. The following blog post has more details about-library design:+`tomland` is a Haskell library for _Bidirectional TOML+Serialization_. It provides the composable interface for implementing+[TOML](https://github.com/toml-lang/toml) codecs. If you want to use+TOML as a configuration for your tool or application, you can use+`tomland` to easily convert in both ways between textual TOML+representation and Haskell types. -* [`tomland`: Bidirectional TOML serialization](https://kowainik.github.io/posts/2019-01-14-tomland)+✍️ `tomland` supports [TOML spec version 0.5.0](https://github.com/toml-lang/toml/wiki#v050-compliant). +The following blog post has more details about the library design and+internal implementation details:++* [`tomland`: Bidirectional TOML Serialization](https://kowainik.github.io/posts/2019-01-14-tomland)+ This README contains a basic usage example of the `tomland` library. All code below can be compiled and run with the following command:  ```-cabal new-run readme+cabal run readme ```  ## Preamble: imports and language extensions@@ -39,15 +48,15 @@ {-# LANGUAGE OverloadedStrings #-}  import Control.Applicative ((<|>))-import Control.Category ((>>>)) import Data.Text (Text)-import Toml (TomlBiMap, TomlCodec, (.=))+import Data.Time (Day)+import Toml (TomlCodec, (.=))  import qualified Data.Text.IO as TIO import qualified Toml ``` -`tomland` is mostly designed for qualified imports and intended to be imported+`tomland` is designed for qualified imports and intended to be imported as follows:  ```haskell ignore@@ -57,10 +66,40 @@  ## Data type: parsing and printing -We're going to parse TOML configuration from [`examples/readme.toml`](examples/readme.toml) file.+We're going to parse TOML configuration from+[`examples/readme.toml`](examples/readme.toml) file. The configuration+contains the following description of our data: -This static configuration is captured by the following Haskell data type:+```toml+server.port        = 8080+server.codes       = [ 5, 10, 42 ]+server.description = """+This is production server.+Don't touch it!+""" +[mail]+    host = "smtp.gmail.com"+    send-if-inactive = false++[[user]]+   guestId = 42++[[user]]+   guestId = 114++[[user]]+    login = "Foo Bar"+    createdAt = 2020-05-19+```++The above static configuration describes `Settings` for some+server. It has several top-level fields, a table with the name `mail`+and an array of tables with the name `user` that stores list of+different types of users.++We can model such TOML using the following Haskell data types:+ ```haskell data Settings = Settings     { settingsPort        :: !Port@@ -76,25 +115,29 @@     }  data User-    = Admin  !Integer  -- id of admin-    | Client !Text     -- name of the client-    deriving stock (Show)+    = Guest !Integer  -- id of guest+    | Registered !RegisteredUser  -- login and createdAt of registered user +data RegisteredUser = RegisteredUser+    { registeredUserLogin     :: !Text+    , registeredUserCreatedAt :: !Day+    }+ newtype Port = Port Int newtype Host = Host Text ``` -Using `tomland` library, you can write bidirectional converters for these types-using the following guidelines and helper functions:+Using the `tomland` library, you can write bidirectional converters for these types+with the following guidelines and helper functions: -1. If your fields are some simple basic types like `Int` or `Text` you can just+1. If your fields are some simple primitive types like `Int` or `Text` you can just    use standard codecs like `Toml.int` and `Toml.text`. 2. If you want to parse `newtype`s, use `Toml.diwrap` to wrap parsers for    underlying `newtype` representation.-3. For parsing nested data types, use `Toml.table`. But this requires to specify-   this data type as TOML table in `.toml` file.+3. For parsing nested data types, use `Toml.table`. But it requires to specify+   this data type as TOML table in the `.toml` file. 4. If you have lists of custom data types, use `Toml.list`. Such lists are-   represented as array of tables in TOML. If you have lists of primitive types+   represented as _array of tables_ in TOML. If you have lists of the primitive types    like `Int`, `Bool`, `Double`, `Text` or time types, that you can use    `Toml.arrayOf` and parse arrays of values. 5. If you have sets of custom data types, use `Toml.set` or `Toml.HashSet`. Such@@ -121,41 +164,49 @@     <$> Toml.diwrap (Toml.text "host") .= mailHost     <*> Toml.bool "send-if-inactive"   .= mailSendIfInactive -_Admin :: TomlBiMap User Integer-_Admin = Toml.prism Admin $ \case-    Admin i -> Right i-    other   -> Toml.wrongConstructor "Admin" other+matchGuest :: User -> Maybe Integer+matchGuest = \case+   Guest i -> Just i+   _ -> Nothing -_Client :: TomlBiMap User Text-_Client = Toml.prism Client $ \case-    Client n -> Right n-    other    -> Toml.wrongConstructor "Client" other+matchRegistered :: User -> Maybe RegisteredUser+matchRegistered = \case+   Registered u -> Just u+   _ -> Nothing  userCodec :: TomlCodec User userCodec =-        Toml.match (_Admin >>> Toml._Integer) "id"-    <|> Toml.match (_Client >>> Toml._Text) "name"+        Toml.dimatch matchGuest      Guest      (Toml.integer "guestId")+    <|> Toml.dimatch matchRegistered Registered registeredUserCodec++registeredUserCodec :: TomlCodec RegisteredUser+registeredUserCodec = RegisteredUser+    <$> Toml.text "login"     .= registeredUserLogin+    <*> Toml.day  "createdAt" .= registeredUserCreatedAt ``` -And now we're ready to parse our TOML and print the result back to see whether+And now we are ready to parse our TOML and print the result back to see whether everything is okay.  ```haskell main :: IO () main = do-    tomlExample <- TIO.readFile "examples/readme.toml"-    let res = Toml.decode settingsCodec tomlExample-    case res of-        Left err -> print err+    tomlRes <- Toml.decodeFileEither settingsCodec "examples/readme.toml"+    case tomlRes of+        Left errs      -> TIO.putStrLn $ Toml.prettyTomlDecodeErrors errs         Right settings -> TIO.putStrLn $ Toml.encode settingsCodec settings ```  ## Benchmarks and comparison with other libraries -`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.+You can find benchmarks of the `tomland` library in the following repository: +* [kowainik/toml-benchmarks](https://github.com/kowainik/toml-benchmarks)++Since `tomland` 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.+ | Library            | parse :: Text -> AST | transform :: AST -> Haskell | |--------------------|----------------------|-----------------------------| | `tomland`          | `305.5 μs`           | `1.280 μs`                  |@@ -163,21 +214,22 @@ | `htoml-megaparsec` | `295.0 μs`           | `33.62 μs`                  | | `toml-parser`      | `164.6 μs`           | `1.101 μs`                  | -You may see that `tomland` is not the fastest one (though still very fast). But-performance hasn’t been optimized so far and:+In addition to the above numbers, `tomland` has several features that+make it unique: -1. `toml-parser` doesn’t support the array of tables and because of that it’s-   hardly possible to specify the list of custom data types in TOML with this-   library.-2. `tomland` supports latest TOML spec while `htoml` and `htoml-megaparsec`-   don’t have support for all types, values and formats.-3. `tomland` is the only library that has pretty-printing.-4. `toml-parser` doesn’t have ways to convert TOML AST to custom Haskell types-   and `htoml*` libraries use typeclasses-based approach via `aeson` library.-5. `tomland` is bidirectional :slightly_smiling_face:+1. `tomland` is the only Haskell library that has pretty-printing.+2. `tomland` is compatible with the latest TOML spec while other libraries are not.+3. `tomland` is bidirectional, which means that your encoding and+   decoding are consistent with each other by construction.+4. `tomland` provides abilities for `Generic` and `DerivingVia`+   deriving out-of-the-box.+5. Despite being the fastest, `toml-parser` doesn’t support the array+   of tables and because of that it’s hardly possible to specify the list+   of custom data types in TOML with this library. In addition,+   `toml-parser` doesn’t have ways to convert TOML AST to custom+   Haskell types and `htoml*` libraries use typeclasses-based approach+   via `aeson` library.  ## Acknowledgement -Icons made by [Freepik](http://www.freepik.com) from-[www.flaticon.com](https://www.flaticon.com/) is licensed by-[CC 3.0 BY](http://creativecommons.org/licenses/by/3.0/).+Icons made by [Freepik](http://www.freepik.com) from [www.flaticon.com](https://www.flaticon.com/) is licensed by [CC 3.0 BY](http://creativecommons.org/licenses/by/3.0/).
examples/Main.hs view
@@ -1,25 +1,34 @@ {-# OPTIONS -Wno-unused-top-binds #-}+ {-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DerivingVia    #-}  module Main (main) where  import Control.Applicative ((<|>)) import Control.Arrow ((>>>))+import Data.ByteString (ByteString) import Data.Hashable (Hashable) import Data.HashSet (HashSet)+import Data.IntSet (IntSet)+import Data.List.NonEmpty (NonEmpty) import Data.Map.Strict (Map) 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 (TomlCodec, TomlParseError (..), pretty, (.=), (<!>))+import Toml.Codec.Generic (ByteStringAsBytes (..), HasCodec (..), TomlTable (..),+                           stripTypeNameCodec) import Toml.Type (TOML (..), Value (..))+import Toml.Type.Edsl (mkToml, table, (=:))  import qualified Data.Text.IO as TIO+ import qualified Toml + newtype TestInside = TestInside { unInside :: Text }  insideCodec :: TomlCodec TestInside@@ -44,7 +53,7 @@     = Light     | Dark     | HighContrast-    deriving stock (Show, Enum, Bounded)+    deriving stock (Show, Read, Enum, Bounded)  data UserStatus     = Registered Text Text@@ -59,33 +68,71 @@ matchAnonymous _                    = Nothing  userPassCodec :: TomlCodec (Text, Text)-userPassCodec = (,)-    <$> Toml.text "username" .= fst-    <*> Toml.text "password" .= snd+userPassCodec = Toml.pair+    (Toml.text "username")+    (Toml.text "password")  userStatusCodec :: TomlCodec UserStatus userStatusCodec =     Toml.dimatch matchRegistered (uncurry Registered) (Toml.table userPassCodec "testStatus")     <|> Toml.dimatch matchAnonymous Anonymous (Toml.text "testStatus") +data Colour+   = Hex Text+   | RGB Rgb++matchHex :: Colour -> Maybe Text+matchHex = \case+    Hex t -> Just t+    _ -> Nothing++matchRgb :: Colour -> Maybe Rgb+matchRgb = \case+    RGB rgb -> Just rgb+    _ -> Nothing++colourCodec :: Toml.Key -> TomlCodec Colour+colourCodec key =+        Toml.dimatch matchHex Hex (Toml.text key)+    <|> Toml.dimatch matchRgb RGB (Toml.table rgbCodec key)++data Rgb = Rgb+    { rgbRed   :: Int+    , rgbGreen :: Int+    , rgbBlue  :: Int+    }++rgbCodec :: TomlCodec Rgb+rgbCodec = Rgb+    <$> Toml.int "red"   .= rgbRed+    <*> Toml.int "green" .= rgbGreen+    <*> Toml.int "blue"  .= rgbBlue+ data Test = Test     { testB      :: !Bool     , testI      :: !Int     , testF      :: !Double     , testS      :: !Text     , testA      :: ![Text]+    , testNE     :: !(NonEmpty Text)+    , testNET    :: !(NonEmpty Int)     , testM      :: !(Maybe Bool)     , testX      :: !TestInside     , testY      :: !(Maybe TestInside)+    , testEven   :: !Int     , testN      :: !N     , testC      :: !ColorScheme+    , testPair   :: !(Int, Text)+    , testTriple :: !(Int, Text, Bool)     , testE1     :: !(Either Integer String)     , testE2     :: !(Either String Double)     , testStatus :: !UserStatus     , users      :: ![User]     , susers     :: !(Set User)     , husers     :: !(HashSet User)+    , intset     :: !IntSet     , payloads   :: !(Map Text Int)+    , colours    :: !(Map Text Colour)     }  @@ -96,19 +143,32 @@     <*> Toml.double "testF" .= testF     <*> Toml.text "testS" .= testS     <*> Toml.arrayOf Toml._Text "testA" .= testA+    <*> Toml.arrayNonEmptyOf Toml._Text "testNE" .= testNE+    <*> Toml.nonEmpty (Toml.int "a") "testNET" .= testNET     <*> Toml.dioptional (Toml.bool "testM") .= testM     <*> Toml.table insideCodec "testX" .= testX-    <*> Toml.dioptional ((Toml.table insideCodec) "testY") .= testY+    <*> Toml.dioptional (Toml.table insideCodec "testY") .= testY+    <*> Toml.validateIf even Toml._Int "testEven" .= testEven     <*> Toml.diwrap (Toml.text "testN") .= testN     <*> Toml.enumBounded "testC" .= testC+    <*> Toml.table pairC "testPair" .= testPair+    <*> Toml.table tripleC "testTriple" .= testTriple     <*> eitherT1 .= testE1     <*> eitherT2 .= testE2     <*> userStatusCodec .= testStatus     <*> Toml.list userCodec "user" .= users     <*> Toml.set userCodec "suser" .= susers     <*> Toml.hashSet userCodec "huser" .= husers+    <*> Toml.arrayIntSet "intset" .= intset     <*> Toml.map (Toml.text "name") (Toml.int "payload") "payloads" .= payloads+    <*> Toml.tableMap Toml._KeyText colourCodec "colours" .= colours   where+    pairC :: TomlCodec (Int, Text)+    pairC = Toml.pair (Toml.int "pNum") (Toml.text "pName")++    tripleC :: TomlCodec (Int, Text, Bool)+    tripleC = Toml.triple (Toml.int "tNum") (Toml.text "tName") (Toml.bool "isFav")+     -- different keys for sum type     eitherT1 :: TomlCodec (Either Integer String)     eitherT1 = Toml.match (Toml._Left >>> Toml._Integer)  "either.Left"@@ -122,6 +182,24 @@              <!> Toml.match (Toml._Right >>> Toml._Double)                ) "either" +data GenericPerson = GenericPerson+    { genericPersonName    :: !Text+    , genericPersonAddress :: !Address+    } deriving stock (Generic)++data Address = Address+    { addressStreet :: !Text+    , addressHouse  :: !Int+    } deriving stock (Generic)+      deriving HasCodec via (TomlTable Address)++testGeneric :: TomlCodec GenericPerson+testGeneric = stripTypeNameCodec++newtype MyByteString = MyByteString+    { unMyByteString :: ByteString+    } deriving HasCodec via ByteStringAsBytes+ main :: IO () main = do     TIO.putStrLn "=== Printing manually specified TOML ==="@@ -130,14 +208,20 @@     TIO.putStrLn "=== Trying to print invalid TOML ==="     content <- TIO.readFile "examples/invalid.toml"     TIO.putStrLn $ case Toml.parse content of-        Left (ParseException e) -> e+        Left (TomlParseError 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+        Left msgs  -> Toml.prettyTomlDecodeErrors msgs         Right test -> Toml.encode testT test++    TIO.putStrLn "=== Testing Deriving Via ==="+    genericFile <- TIO.readFile "examples/generic.toml"+    TIO.putStrLn $ case Toml.decode testGeneric genericFile of+        Left msg   -> Toml.prettyTomlDecodeErrors msg+        Right test -> Toml.encode testGeneric test  myToml :: TOML myToml = mkToml $ do
src/Toml.hs view
@@ -1,17 +1,18 @@ {- |-Copyright: (c) 2018-2019 Kowainik+Copyright: (c) 2018-2020 Kowainik SPDX-License-Identifier: MPL-2.0 Maintainer: Kowainik <xrom.xkov@gmail.com> -This module reexports all functionality of @tomland@ package. It's+This module reexports all functionality of the @tomland@ package. It's recommended to import this module qualified, like this:  @-__import__ Toml (TomlCodec, (.=))-__import__ __qualified__ Toml+__import__ "Toml" ('TomlCodec', '(.=)')+__import__ __qualified__ "Toml" @ -Simple @'TomlCodec'@ could be written in the following way:+Simple @'TomlCodec'@ for a Haskell value, that can be decoded from+TOML or encoded as TOML, could be written in the following way:  @ __data__ User = User@@ -25,28 +26,51 @@     \<*\> Toml.'int'  "age"  '.=' userAge @ +A value of such type will look in TOML like this:++@+name = "Alice"+age  = 27+@+ For more detailed examples see README.md in the repository:  * [tomland/README.md](https://github.com/kowainik/tomland#tomland)  For the details of the library implementation see blog post: - * [tomland: Bidirectional TOML serialization](https://kowainik.github.io/posts/2019-01-14-tomland) -}  module Toml-    ( module Toml.Bi-    , module Toml.Generic-    , module Toml.Parser-    , module Toml.PrefixTree-    , module Toml.Printer+    ( -- $codec+      module Toml.Codec+      -- $type     , module Toml.Type+      -- $parser+    , module Toml.Parser     ) where -import Toml.Bi-import Toml.Generic+import Toml.Codec import Toml.Parser-import Toml.PrefixTree-import Toml.Printer import Toml.Type++{- $codec+Main types and functions to implement TOML codecs. This module+provides high-level API of @tomland@ library.+-}++{- $type+Low-level implementation details of types that power @tomland@. The+"Toml.Type" module contains types to represent TOML AST and basic+functions to work with it. This module also contains pretty-printing+API for 'TOML' AST and eDSL for type-safe definition of the TOML+values.+-}++{- $parser+Parser for types, defined in "Toml.Type". This modules contains+low-level functions to parse 'TOML' from text. If you want to convert+between Haskell types and TOML representation, use functions from+"Toml.Codec".+-}
− src/Toml/Bi.hs
@@ -1,27 +0,0 @@-{- |-Copyright: (c) 2018-2019 Kowainik-SPDX-License-Identifier: MPL-2.0-Maintainer: Kowainik <xrom.xkov@gmail.com>--Reexports functions under @Toml.Bi.*@.--* __"Toml.Bi.Map"__: contains implementation of tagged bidirectional-  isomorphisms.-* __"Toml.Bi.Monad"__: core abstraction for bidirectional conversion based on-  profunctor monads.-* __"Toml.Bi.Combinators"__: TOML specific combinators based on 'Codec' type-  from "Toml.Bi.Monad".-* __"Toml.Bi.Code"__: conversion functions like 'decode' and 'encode'.--}--module Toml.Bi-       ( module Toml.Bi.Code-       , module Toml.Bi.Combinators-       , module Toml.Bi.Map-       , module Toml.Bi.Monad-       ) where--import Toml.Bi.Code-import Toml.Bi.Combinators-import Toml.Bi.Map-import Toml.Bi.Monad
− src/Toml/Bi/Code.hs
@@ -1,144 +0,0 @@-{-# LANGUAGE DeriveAnyClass #-}--{- |-Copyright: (c) 2018-2019 Kowainik-SPDX-License-Identifier: MPL-2.0-Maintainer: Kowainik <xrom.xkov@gmail.com>--Coding functions like 'decode' and 'encode'. Also contains specialization of 'Codec' for TOML.--}--module Toml.Bi.Code-       ( -- * Types-         TomlCodec-       , Env-       , St--         -- * Exceptions-       , DecodeException (..)-       , LoadTomlException (..)-       , prettyException--         -- * Encode/Decode-       , decode-       , decodeFile-       , runTomlCodec-       , encode-       , execTomlCodec-       ) where--import Control.DeepSeq (NFData)-import Control.Exception (Exception, throwIO)-import Control.Monad.Except (ExceptT, runExceptT)-import Control.Monad.IO.Class (MonadIO (..))-import Control.Monad.Reader (Reader, runReader)-import Control.Monad.State (State, execState)-import Control.Monad.Trans.Maybe (MaybeT (..))-import Data.Bifunctor (first)-import Data.Foldable (toList)-import Data.Semigroup (Semigroup (..))-import Data.Text (Text)-import GHC.Generics (Generic)--import Toml.Bi.Map (TomlBiMapError, prettyBiMapError)-import Toml.Bi.Monad (BiCodec, Codec (..))-import Toml.Parser (ParseException (..), parse)-import Toml.PrefixTree (Key (..), unPiece)-import Toml.Printer (pretty)-import Toml.Type (TOML (..), TValue, showType)--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 stock (Eq, Generic)-    deriving anyclass (NFData)--instance Show DecodeException where-    show = Text.unpack . prettyException--instance Semigroup DecodeException where-    TrivialError <> e = e-    e <> _ = e--instance Monoid DecodeException where-    mempty = TrivialError-    mappend = (<>)---- | Converts 'DecodeException' into pretty human-readable text.-prettyException :: DecodeException -> Text-prettyException de = "tomland decode error:  " <> case de of-    TrivialError -> "'empty' parser from 'Alternative' is used"-    BiMapError biError -> prettyBiMapError biError-    KeyNotFound name -> "Key " <> joinKey name <> " is not found"-    TableNotFound name -> "Table [" <> joinKey name <> "] is not found"-    TypeMismatch name expected actual -> "Type for key " <> joinKey name <> " doesn't match."-        <> "\n  Expected: " <> expected-        <> "\n  Actual:   " <> Text.pack (showType actual)-    ParseError (ParseException msg) -> "Parse error during conversion from TOML to custom user type: \n  " <> msg-  where-    joinKey :: Key -> Text-    joinKey = Text.intercalate "." . map unPiece . toList . unKey---- | Immutable environment for TOML conversion.--- This is @r@ type variable in 'Codec' data type.-type Env = ExceptT DecodeException (Reader TOML)--{- | Mutable context for TOML conversion.-This is @w@ type variable in 'Codec' data type.--@-MaybeT (State TOML) a-    = State TOML (Maybe a)-    = TOML -> (Maybe a, TOML)-@--}-type St = MaybeT (State TOML)--{- | Specialied 'BiCodec' type alias for bidirectional TOML serialization. Keeps-'TOML' object as both environment and state.--}-type TomlCodec a = BiCodec Env St a---- | Convert textual representation of toml into user data type.-decode :: TomlCodec a -> Text -> Either DecodeException a-decode codec text = do-    toml <- first ParseError (parse text)-    runTomlCodec codec toml---- | Convert toml into user data type.-runTomlCodec :: TomlCodec a -> TOML -> Either DecodeException a-runTomlCodec codec = runReader (runExceptT $ codecRead codec)---- | Convert object to textual representation.-encode :: TomlCodec a -> a -> Text-encode codec obj = pretty $ execTomlCodec codec obj---- | Runs 'codecWrite' of 'TomlCodec' and returns intermediate TOML AST.-execTomlCodec :: TomlCodec a -> a -> TOML-execTomlCodec codec obj = execState (runMaybeT $ codecWrite codec obj) mempty---- | File loading error data type.-data LoadTomlException = LoadTomlException !FilePath !Text--instance Show LoadTomlException where-    show (LoadTomlException filePath msg) = "Couldnt parse file " ++ filePath ++ ": " ++ show msg--instance Exception LoadTomlException---- | Decode a value from a file. In case of parse errors, throws 'LoadTomlException'.-decodeFile :: (MonadIO m) => TomlCodec a -> FilePath -> m a-decodeFile codec filePath = liftIO $-    (decode codec <$> TIO.readFile filePath) >>= errorWhenLeft-  where-    errorWhenLeft :: Either DecodeException a -> IO a-    errorWhenLeft (Left e)   = throwIO $ LoadTomlException filePath $ prettyException e-    errorWhenLeft (Right pc) = pure pc
− src/Toml/Bi/Combinators.hs
@@ -1,515 +0,0 @@-{-# LANGUAGE AllowAmbiguousTypes #-}-{-# LANGUAGE FlexibleContexts    #-}-{-# LANGUAGE GADTs               #-}--{- |-Copyright: (c) 2018-2019 Kowainik-SPDX-License-Identifier: MPL-2.0-Maintainer: Kowainik <xrom.xkov@gmail.com>--Contains TOML-specific combinators for converting between TOML and user data types.--}--module Toml.Bi.Combinators-       ( -- * Basic codecs for primitive values-         -- ** Boolean-         bool-         -- ** Integral numbers-       , integer-       , natural-       , int-       , word-       , word8-         -- ** Floating point numbers-       , double-       , float-         -- ** Text types-       , text-       , lazyText-       , byteString-       , lazyByteString-       , byteStringArray-       , lazyByteStringArray-       , string-         -- ** Time types-       , zonedTime-       , localTime-       , day-       , timeOfDay--         -- * Codecs for containers of primitives-       , arrayOf-       , arraySetOf-       , arrayIntSet-       , arrayHashSetOf-       , arrayNonEmptyOf--         -- * Codecs for 'Monoid's-         -- ** Bool wrappers-       , all-       , any-         -- ** 'Num' wrappers-       , sum-       , product-         -- ** 'Maybe' wrappers-       , first-       , last--         -- * Additional codecs for custom types-       , textBy-       , read-       , enumBounded--         -- * Combinators for tables-       , table-       , nonEmpty-       , list-       , set-       , hashSet--         -- * Combinators for Maps-       , map--         -- * General construction of codecs-       , match-       ) where--import Prelude hiding (all, any, last, map, product, read, sum)--import Control.Monad (forM)-import Control.Monad.Except (catchError, throwError)-import Control.Monad.Reader (asks, local)-import Control.Monad.State (execState, gets, modify)-import Control.Monad.Trans.Maybe (MaybeT (..), runMaybeT)-import Data.ByteString (ByteString)-import Data.Hashable (Hashable)-import Data.HashSet (HashSet)-import Data.IntSet (IntSet)-import Data.List.NonEmpty (NonEmpty (..), toList)-import Data.Map.Strict (Map)-import Data.Maybe (fromMaybe)-import Data.Monoid (All (..), Any (..), First (..), Last (..), Product (..), Sum (..))-import Data.Semigroup ((<>))-import Data.Set (Set)-import Data.Text (Text)-import Data.Time (Day, LocalTime, TimeOfDay, ZonedTime)-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, _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, dioptional)-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.List.NonEmpty as NE-import qualified Data.Map.Strict as Map-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':--@-_MyType :: 'TomlBiMap' MyType 'AnyValue'-@--And then you can create codec for your type using 'match' function:--@-myType :: 'Key' -> 'TomlCodec' MyType-myType = 'match' _MyType-@--}-match :: forall a . TomlBiMap a AnyValue -> Key -> TomlCodec a-match BiMap{..} key = Codec input output-  where-    input :: Env a-    input = do-        mVal <- asks $ HashMap.lookup key . tomlPairs-        case mVal of-            Nothing -> throwError $ KeyNotFound key-            Just anyVal -> case backward anyVal of-                Right v  -> pure v-                Left err -> throwError $ BiMapError err--    output :: a -> St a-    output a = do-        anyVal <- MaybeT $ pure $ either (const Nothing) Just $ forward a-        a <$ modify (insertKeyAnyVal key anyVal)---- | 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-nullary sum types.--@since 1.1.1.0--}-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.-arrayHashSetOf-    :: (Hashable a, Eq a)-    => TomlBiMap a AnyValue-    -> 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 #-}--------------------------------------------------------------------------------- Monoid codecs-------------------------------------------------------------------------------{- | Codec for 'All' wrapper for boolean values.-Returns @'All' 'True'@ on missing fields.--@since 1.2.1.0--}-all :: Key -> TomlCodec All-all = dimap (Just . getAll) (All . fromMaybe True) . dioptional . bool-{-# INLINE all #-}--{- | Codec for 'Any' wrapper for boolean values.-Returns @'Any' 'False'@ on missing fields.--@since 1.2.1.0--}-any :: Key -> TomlCodec Any-any = dimap (Just . getAny) (Any . fromMaybe False) . dioptional . bool-{-# INLINE any #-}--{- | Codec for 'Sum' wrapper for given converter's values.--@since 1.2.1.0--}-sum :: (Key -> TomlCodec a) -> Key -> TomlCodec (Sum a)-sum codec = dimap getSum Sum . codec-{-# INLINE sum #-}--{- | Codec for 'Product' wrapper for given converter's values.--@since 1.2.1.0--}-product :: (Key -> TomlCodec a) -> Key -> TomlCodec (Product a)-product codec = dimap getProduct Product . codec-{-# INLINE product #-}--{- | Codec for 'First' wrapper for given converter's values.--@since 1.2.1.0--}-first :: (Key -> TomlCodec a) -> Key -> TomlCodec (First a)-first codec = dimap getFirst First . dioptional . codec-{-# INLINE first #-}--{- | Codec for 'Last' wrapper for given converter's values.--@since 1.2.1.0--}-last :: (Key -> TomlCodec a) -> Key -> TomlCodec (Last a)-last codec = dimap getLast Last . dioptional . codec-{-# INLINE last #-}--------------------------------------------------------------------------------- Tables and arrays of tables-------------------------------------------------------------------------------{- | 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-key, not only the last one.--}-handleErrorInTable :: Key -> DecodeException -> Env a-handleErrorInTable key = \case-    KeyNotFound name        -> throwError $ KeyNotFound (key <> name)-    TableNotFound name      -> throwError $ TableNotFound (key <> name)-    TypeMismatch name t1 t2 -> throwError $ TypeMismatch (key <> name) t1 t2-    e                       -> throwError e---- | Run 'codecRead' function with given 'TOML' inside 'Control.Monad.Reader.ReaderT' context.-codecReadTOML :: TOML -> TomlCodec a -> Env a-codecReadTOML toml codec = local (const toml) (codecRead codec)---- | Codec for tables. Use it when when you have nested objects.-table :: forall a . TomlCodec a -> Key -> TomlCodec a-table codec key = Codec input output-  where-    input :: Env a-    input = do-        mTable <- asks $ Prefix.lookup key . tomlTables-        case mTable of-            Nothing   -> throwError $ TableNotFound key-            Just toml -> codecReadTOML toml codec `catchError` handleErrorInTable key--    output :: a -> St a-    output a = do-        mTable <- gets $ Prefix.lookup key . tomlTables-        let toml = fromMaybe mempty mTable-        let newToml = execState (runMaybeT $ codecWrite codec a) toml-        a <$ modify (insertTable key newToml)--{- | 'Codec' for 'NonEmpty' list of values. Represented in TOML as array of-tables.--}-nonEmpty :: forall a . TomlCodec a -> Key -> TomlCodec (NonEmpty a)-nonEmpty codec key = Codec input output-  where-    input :: Env (NonEmpty a)-    input = do-        mTables <- asks $ HashMap.lookup key . tomlTableArrays-        case mTables of-            Nothing    -> throwError $ TableNotFound key-            Just tomls -> forM tomls $ \toml ->-                codecReadTOML toml codec `catchError` handleErrorInTable key--    -- adds all TOML objects to the existing list if there are some-    output :: NonEmpty a -> St (NonEmpty a)-    output as = do-        let tomls = fmap (execTomlCodec codec) as-        mTables <- gets $ HashMap.lookup key . tomlTableArrays--        let newTomls = case mTables of-                Nothing       -> tomls-                Just oldTomls -> oldTomls <> tomls--        as <$ modify (insertTableArrays key newTomls)---- | 'Codec' for list of values. Represented in TOML as array of tables.-list :: forall a . TomlCodec a -> Key -> TomlCodec [a]-list codec key = Codec-    { codecRead = (toList <$> codecRead nonEmptyCodec) `catchError` \case-        TableNotFound errKey | errKey == key -> pure []-        err -> throwError err-    , codecWrite = \case-        [] -> pure []-        l@(x:xs) -> l <$ codecWrite nonEmptyCodec (x :| xs)-    }-  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 #-}--------------------------------------------------------------------------------- Map-like combinators-------------------------------------------------------------------------------{- | Bidirectional codec for 'Map'. It takes birectional converter for keys and-values and produces bidirectional codec for 'Map'. Currently it works only with array-of tables, so you need to specify 'Map's in TOML files like this:--@-myMap =-    [ { name = "foo", payload = 42 }-    , { name = "bar", payload = 69 }-    ]-@--'TomlCodec' for such TOML field can look like this:--@-Toml.'map' (Toml.'text' "name") (Toml.'int' "payload") "myMap"-@--If there's no key with the name @"myMap"@ then empty 'Map' is returned.--@since 1.2.1.0--}-map :: forall k v .-       Ord k-    => TomlCodec k  -- ^ Codec for 'Map' keys-    -> TomlCodec v  -- ^ Codec for 'Map' values-    -> Key          -- ^ TOML key where 'Map' is stored-    -> TomlCodec (Map k v)  -- ^ Codec for the 'Map'-map keyCodec valCodec key = Codec input output-  where-    input :: Env (Map k v)-    input = do-        mTables <- asks $ HashMap.lookup key . tomlTableArrays-        case mTables of-            Nothing -> pure Map.empty-            Just tomls -> fmap Map.fromList $ forM (NE.toList tomls) $ \toml -> do-                k <- codecReadTOML toml keyCodec-                v <- codecReadTOML toml valCodec-                pure (k, v)--    output :: Map k v -> St (Map k v)-    output dict = do-        let tomls = fmap-                (\(k, v) -> execTomlCodec keyCodec k <> execTomlCodec valCodec v)-                (Map.toList dict)--        mTables <- gets $ HashMap.lookup key . tomlTableArrays--        let updateAction :: TOML -> TOML-            updateAction = case mTables of-                Nothing -> case tomls of-                    []   -> id-                    t:ts -> insertTableArrays key (t :| ts)-                Just (t :| ts) ->-                    insertTableArrays key $ t :| (ts ++ tomls)--        dict <$ modify updateAction
− src/Toml/Bi/Map.hs
@@ -1,577 +0,0 @@-{-# LANGUAGE AllowAmbiguousTypes #-}-{-# LANGUAGE DataKinds           #-}-{-# LANGUAGE DeriveAnyClass      #-}-{-# LANGUAGE KindSignatures      #-}-{-# LANGUAGE Rank2Types          #-}-{-# LANGUAGE TypeFamilies        #-}--{- |-Copyright: (c) 2018-2019 Kowainik-SPDX-License-Identifier: MPL-2.0-Maintainer: Kowainik <xrom.xkov@gmail.com>--Implementation of tagged partial bidirectional isomorphism.--}--module Toml.Bi.Map-       ( -- * BiMap idea-         BiMap (..)-       , TomlBiMap-       , invert-       , iso-       , prism--         -- * 'BiMap' errors for TOML-       , TomlBiMapError (..)-       , wrongConstructor-       , prettyBiMapError--         -- * Some predefined bi mappings-       , _Array-       , _Bool-       , _Double-       , _Integer-       , _Text-       , _LText-       , _ZonedTime-       , _LocalTime-       , _Day-       , _TimeOfDay-       , _String-       , _Read-       , _Natural-       , _Word-       , _Word8-       , _Int-       , _Float-       , _ByteString-       , _LByteString-       , _ByteStringArray-       , _LByteStringArray-       , _NonEmpty-       , _Set-       , _IntSet-       , _HashSet--         -- * Helpers for BiMap and AnyValue-       , mkAnyValueBiMap-       , _TextBy-       , _LTextText-       , _NaturalInteger-       , _StringText-       , _ReadString-       , _BoundedInteger-       , _EnumBoundedText-       , _ByteStringText-       , _LByteStringText-       , _Left-       , _Right-       , _EnumBounded-       , _Just--         -- * Useful utility functions-       , toMArray-       ) where--import Control.Arrow ((>>>))-import Control.DeepSeq (NFData)-import Control.Monad ((>=>))-import Data.Bifunctor (bimap, first)-import Data.ByteString (ByteString)-import Data.Hashable (Hashable)-import Data.Map (Map)-import Data.Semigroup (Semigroup (..))-import Data.Text (Text)-import Data.Time (Day, LocalTime, TimeOfDay, ZonedTime)-import Data.Word (Word8)-import GHC.Generics (Generic)-import Numeric.Natural (Natural)-import Text.Read (readEither)--import Toml.Type (AnyValue (..), MatchError (..), TValue (..), Value (..), applyAsToAny, matchBool,-                  matchDay, matchDouble, matchHours, matchInteger, matchLocal, matchText,-                  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-import qualified Data.List.NonEmpty as NE-import qualified Data.Map as M-import qualified Data.Set as S-import qualified Data.Text as T-import qualified Data.Text.Encoding as T-import qualified Data.Text.Lazy as TL-import qualified Data.Text.Lazy.Encoding as TL---------------------------------------------------------------------------------- BiMap concepts and ideas-------------------------------------------------------------------------------{- | Partial bidirectional isomorphism. @BiMap a b@ contains two function:--1. @a -> Either e b@-2. @b -> Either e a@--If you think of types as sets then this data type can be illustrated by the-following picture:--![bimap-type](https://user-images.githubusercontent.com/4276606/50770531-b6a36000-1298-11e9-9528-caae87951d2a.png)--'BiMap' also implements 'Cat.Category' typeclass. And this instance can be described-clearly by this illustration:--![bimap-cat](https://user-images.githubusercontent.com/4276606/50771234-13a01580-129b-11e9-93da-6c5dd0f7f160.png)--}-data BiMap e a b = BiMap-    { forward  :: a -> Either e b-    , backward :: b -> Either e a-    }--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:--@-__newtype__ Even = Even Integer-__newtype__ Odd  = Odd  Integer--succEven :: Even -> Odd-succEven (Even n) = Odd (n + 1)--predOdd :: Odd -> Even-predOdd (Odd n) = Even (n - 1)--_EvenOdd :: 'BiMap' e Even Odd-_EvenOdd = 'iso' succEven predOdd-@--}-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:--@-__data__ User-    = Admin  Integer  -- id of admin-    | Client Text     -- name of the client-    __deriving__ (Show)--_Admin :: 'TomlBiMap' User Integer-_Admin = Toml.'prism' Admin $ \\__case__-    Admin i -> Right i-    other   -> Toml.'wrongConstructor' \"Admin\" other--_Client :: 'TomlBiMap' User Text-_Client = Toml.'prism' Client $ \\__case__-    Client n -> Right n-    other    -> Toml.'wrongConstructor' \"Client\" other-@--}-prism-    :: (field -> object)-    -> (object -> Either error field)-    -> BiMap error object field-prism review preview = BiMap preview (Right . review)-{-# INLINE prism #-}--------------------------------------------------------------------------------- BiMap error types--------------------------------------------------------------------------------- | 'BiMap' specialized to TOML error.-type TomlBiMap = BiMap TomlBiMapError---- | Type of errors for TOML 'BiMap'.-data TomlBiMapError-    = 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-    | WrongValue       -- ^ Error for cases with wrong values-        !MatchError    -- ^ Information about failed matching-    | ArbitraryError   -- ^ Arbitrary textual error-        !Text          -- ^ Error message-    deriving stock (Eq, Show, Generic)-    deriving anyclass (NFData)---- | Converts 'TomlBiMapError' into pretty human-readable text.-prettyBiMapError :: TomlBiMapError -> Text-prettyBiMapError = \case-    WrongConstructor expected actual -> T.unlines-        [ "Invalid constructor"-        , "  * Expected: " <> expected-        , "  * Actual:   " <> actual-        ]-    WrongValue (MatchError expected actual) -> T.unlines-        [ "Invalid constructor"-        , "  * Expected: " <> tShow expected-        , "  * Actual:   " <> tShow actual-        ]-    ArbitraryError text  -> text---- | Helper to construct WrongConstuctor error.-wrongConstructor-    :: Show a-    => Text  -- ^ Name of the expected constructor-    -> a     -- ^ Actual value-    -> Either TomlBiMapError b-wrongConstructor constructor x = Left $ WrongConstructor constructor (tShow x)--------------------------------------------------------------------------------- General purpose bimaps--------------------------------------------------------------------------------- | Bimap for 'Either' and its left type-_Left :: (Show l, Show r) => TomlBiMap (Either l r) l-_Left = prism Left $ \case-    Left l -> Right l-    x -> wrongConstructor "Left" x---- | Bimap for 'Either' and its right type-_Right :: (Show l, Show r) => TomlBiMap (Either l r) r-_Right = prism Right $ \case-    Right r -> Right r-    x -> wrongConstructor "Right" x---- | Bimap for 'Maybe'-_Just :: Show r => TomlBiMap (Maybe r) r-_Just = prism Just $ \case-    Just r -> Right r-    x -> wrongConstructor "Just" x---------------------------------------------------------------------------------  BiMaps for value--------------------------------------------------------------------------------- | Creates prism for 'AnyValue'.-mkAnyValueBiMap-    :: forall a (tag :: TValue) . (forall (t :: TValue) . Value t -> Either MatchError a)-    -> (a -> Value tag)-    -> TomlBiMap a AnyValue-mkAnyValueBiMap matchValue toValue = BiMap-    { forward = Right . toAnyValue-    , backward = fromAnyValue-    }-  where-    toAnyValue :: a -> AnyValue-    toAnyValue = AnyValue . toValue--    fromAnyValue :: AnyValue -> Either TomlBiMapError a-    fromAnyValue (AnyValue value) = first WrongValue $ matchValue value---- | Creates bimap for 'Data.Text.Text' to 'AnyValue' with custom functions-_TextBy-    :: forall a .-       (a -> Text)              -- ^ @show@ function for @a@-    -> (Text -> Either Text a)  -- ^ Parser of @a@ from 'Data.Text.Text'-    -> TomlBiMap a AnyValue-_TextBy toText parseText = BiMap toAnyValue fromAnyValue-  where-    toAnyValue :: a -> Either TomlBiMapError AnyValue-    toAnyValue = Right . AnyValue . Text . toText--    fromAnyValue :: AnyValue -> Either TomlBiMapError a-    fromAnyValue (AnyValue v) =-        first WrongValue (matchText v) >>= first ArbitraryError . parseText--{- | 'Prelude.Bool' bimap for 'AnyValue'. Usually used as-'Toml.Bi.Combinators.bool' combinator.--}-_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-_NaturalInteger = BiMap (Right . toInteger) eitherInteger-  where-    eitherInteger :: Integer -> Either TomlBiMapError Natural-    eitherInteger n-      | n < 0     = Left $ ArbitraryError $ "Value is below zero, but expected Natural: " <> tShow n-      | otherwise = Right (fromIntegral n)--{- | 'String' bimap for 'AnyValue'. Usually used as-'Toml.Bi.Combinators.natural' combinator.--}-_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-_BoundedInteger = BiMap (Right . toInteger) eitherBounded-  where-    eitherBounded :: forall a. (Integral a, Bounded a, Show a) => Integer -> Either TomlBiMapError a-    eitherBounded n-      | n < toInteger (minBound @a) =-         let msg = "Value " <> tShow n <> " is less than minBound: " <> tShow (minBound @a)-         in Left $ ArbitraryError msg-      | n > toInteger (maxBound @a) =-         let msg = "Value " <> tShow n <> " is greater than maxBound: " <> tShow (maxBound @a)-         in Left $ ArbitraryError msg-      | otherwise = Right (fromIntegral n)--{- | Helper bimap for '_EnumBounded' and 'Data.Text.Text'.--@since 1.1.1.0--}-_EnumBoundedText :: forall a. (Show a, Enum a, Bounded a) => TomlBiMap a Text-_EnumBoundedText = BiMap-    { forward  = Right . tShow-    , backward = toEnumBounded-    }-  where-    toEnumBounded :: Text -> Either TomlBiMapError a-    toEnumBounded value = case M.lookup value enumOptions of-        Just a  -> Right a-        Nothing ->-            let msg = "Value is '" <> value <> "' but expected one of: " <> T.intercalate ", " options-            in Left (ArbitraryError msg)-      where-        enumOptions :: Map Text a-        enumOptions = M.fromList $ zip options enums-        options  = fmap tShow enums-        enums = [minBound @a .. maxBound @a]--{- | Bimap for nullary sum data types with 'Show', 'Enum' and 'Bounded'-instances.  Usually used as 'Toml.Bi.Combinators.enumBounded' combinator.--@since 1.1.1.0--}-_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-_ByteStringText = prism T.encodeUtf8 eitherText-  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-_LByteStringText = prism (TL.encodeUtf8 . TL.fromStrict) eitherText-  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-_Array elementBimap = BiMap toAnyValue fromAnyValue-  where-    toAnyValue :: [a] -> Either TomlBiMapError AnyValue-    toAnyValue = mapM (forward elementBimap) >=> bimap WrongValue AnyValue . toMArray--    fromAnyValue :: AnyValue -> Either TomlBiMapError [a]-    fromAnyValue (AnyValue v) = matchElements (backward elementBimap) v--    -- can't reuse matchArray here :(-    matchElements :: (AnyValue -> Either TomlBiMapError a) -> Value t -> Either TomlBiMapError [a]-    matchElements match (Array a) = mapM (applyAsToAny match) a-    matchElements _ val           = first WrongValue $ mkMatchError TArray val----- | Takes a bimap of a value and returns a bimap between a non-empty list of values and 'AnyValue'--- 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
@@ -1,298 +0,0 @@-{- |-Copyright: (c) 2018-2019 Kowainik-SPDX-License-Identifier: MPL-2.0-Maintainer: Kowainik <xrom.xkov@gmail.com>--Contains general underlying monad for bidirectional conversion.--}--module Toml.Bi.Monad-       ( Codec (..)-       , BiCodec-       , dimap-       , dioptional-       , diwrap-       , dimatch-       , (<!>)-       , (.=)-       ) where--import Control.Applicative (Alternative (..), optional)-import Control.Monad (MonadPlus (..))-import Data.Coerce (Coercible, coerce)---{- | Monad for bidirectional conversion. Contains pair of functions:--1. How to read value of type @a@ from immutable environment context @r@?-2. How to store value of type @a@ in stateful context @w@?--In practice instead of @r@ we will use some @Reader Toml@ and instead of @w@ we will-use @State Toml@. This approach with the bunch of utility functions allows to-have single description for from/to @TOML@ conversion.--In practice this type will always be used in the following way:--@-type 'BiCodec' r w a = 'Codec' r w a a-@--Type parameter @c@ if fictional. Here some trick is used. This trick is-implemented in the [codec](http://hackage.haskell.org/package/codec) package and-described in more details in related blog post:-<https://blog.poisson.chat/posts/2016-10-12-bidirectional-serialization.html>.--}-data Codec r w c a = Codec-    { -- | Extract value of type @a@ from monadic context @r@.-      codecRead  :: r a--      -- | Store value of type @c@ inside monadic context @w@ and returning-      -- value of type @a@. Type of this function actually should be @a -> w ()@ but with-      -- such type it's impossible to have 'Monad' and other instances.-    , codecWrite :: c -> w a-    }---- | Specialized version of 'Codec' data type. This type alias is used in practice.-type BiCodec r w a = Codec r w a a--instance (Functor r, Functor w) => Functor (Codec r w c) where-    fmap :: (a -> b) -> Codec r w c a -> Codec r w c b-    fmap f codec = Codec-        { 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-    pure a = Codec-        { 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-    codec >>= f = Codec-        { 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-    empty = Codec-        { 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-want to bring extra dependencies) this instance is implemented as a single-top-level function.--Useful when you want to parse @newtype@s. For example, if you had data type like-this:--@-__data__ Example = Example-    { foo :: Bool-    , bar :: Text-    }-@--Bidirectional TOML converter for this type will look like this:--@-exampleCodec :: TomlCodec Example-exampleCodec = Example-    \<$\> Toml.bool "foo" '.=' foo-    \<*\> Toml.text "bar" '.=' bar-@--Now if you change your type in the following way:--@-__newtype__ Email = Email { unEmail :: Text }--__data__ Example = Example-    { foo :: Bool-    , bar :: Email-    }-@--you need to patch your TOML codec like this:--@-exampleCodec :: TomlCodec Example-exampleCodec = Example-    \<$\> Toml.bool "foo" '.=' foo-    \<*\> 'dimap' unEmail Email (Toml.text "bar") '.=' bar-@--}-dimap-    :: (Functor r, Functor w)-    => (c -> 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-dimap f g codec = Codec-    { codecRead  = g <$> codecRead codec-    , codecWrite = fmap g . codecWrite codec . f-    }-{-# INLINE dimap #-}--{- | Bidirectional converter for @Maybe a@ values. For example, given the data-type:--@-__data__ Example = Example-    { foo :: Bool-    , bar :: Maybe Int-    }-@--the TOML codec will look like--@-exampleCodec :: TomlCodec Example-exampleCodec = Example-    \<$\> Toml.bool "foo" '.=' foo-    \<*\> 'dioptional' (Toml.int "bar") '.=' bar-@--}-dioptional-    :: (Alternative r, Applicative w)-    => Codec r w c a-    -> Codec r w (Maybe c) (Maybe a)-dioptional Codec{..} = Codec-    { codecRead  = optional codecRead-    , codecWrite = traverse codecWrite-    }-{-# INLINE dioptional #-}--{- | Combinator used for @newtype@ wrappers. For example, given the data types:--@-__newtype__ N = N Int--__data__ Example = Example-    { foo :: Bool-    , bar :: N-    }-@--the TOML codec can look like--@-exampleCodec :: TomlCodec Example-exampleCodec = Example-    \<$\> Toml.bool "foo" '.=' foo-    \<*\> 'diwrap' (Toml.int "bar") '.=' bar-@--}-diwrap-    :: forall b a r w .-       (Coercible a b, Functor r, Functor w)-    => 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?-2. How to write this field to toml?--In code this should be used like this:--@-__data__ Foo = Foo-    { fooBar :: Int-    , fooBaz :: String-    }--fooCodec :: TomlCodec Foo-fooCodec = Foo-    \<$\> Toml.int "bar" '.=' fooBar-    \<*\> Toml.str "baz" '.=' fooBaz-@--}-infixl 5 .=-(.=) :: Codec r w field a -> (object -> field) -> Codec r w object a-codec .= getter = codec { codecWrite = codecWrite codec . getter }-{-# INLINE (.=) #-}
+ src/Toml/Codec.hs view
@@ -0,0 +1,122 @@+{- |+Copyright: (c) 2018-2020 Kowainik+SPDX-License-Identifier: MPL-2.0+Maintainer: Kowainik <xrom.xkov@gmail.com>++This module provides utilities for implementing and using+bidirectional TOML codecs. The concept of bidirectional conversion in+@tomland@ has two parts: __'TomlBiMap'__ and __'TomlCodec'__.++== General TOML description++In the following TOML++@+name = "foo"+@++we call @name@ as __key__ and @"foo"@ as __value__.++== 'TomlBiMap'++'TomlBiMap' provides a bidirectional conversion between+__TOML values__ and Haskell primitive values. TOML specification+defines some primitive values you can use in key-value pairs+(e.g. /integer/, /string/, /local time/). 'TomlBiMap' provides a way+to convert between TOML primitives and Haskell values. 'TomlBiMap'+doesn't know anything about TOML keys.++== 'TomlCodec'++'TomlCodec' describes how to convert in both ways between a single or+multiple key-value pairs and Haskell types. @tomland@ provides basic+primitives for decoding and encoding single key-value pairs, but also+a way to compose multiple 'TomlCodec's into a single one. So, if you+have a custom data type, that has several fields or several+constructors, you need to define 'TomlCodec' for your data type.++== Encoding and decoding++If you have a type like @User@ then @userCodec :: 'TomlCodec' User@ is+an object that describes how to 'encode' a value of type @User@ to+TOML and 'decode' TOML to a value of type @User@.++* To TOML: @'encode' userCodec someUser@+* From TOML: @'decode' userCodec someToml@++== Naming conventions++@tomland@ uses the following naming conventions (and encourages+library users to follow them as well):++* __\_SomeName__: for 'TomlBiMap' (e.g. '_Int', '_Text', '_Array')+* __someName__: for basic 'TomlCodec's (e.g. 'int', 'text', 'arrayOf')+* __someNameCodec__: for user defined codecs for custom types (e.g. @userCodec@, @configCodec@, @serverCodec@)++@since 1.3.0.0+-}++module Toml.Codec+    ( -- $types+      module Toml.Codec.Types+      -- $error+    , module Toml.Codec.Error+      -- $code+    , module Toml.Codec.Code+      -- $di+    , module Toml.Codec.Di+      -- $combinator+    , module Toml.Codec.Combinator+      -- $generic+    , module Toml.Codec.Generic+      -- $bimap+    , module Toml.Codec.BiMap+      -- $bimapConversion+    , module Toml.Codec.BiMap.Conversion+    ) where++import Toml.Codec.BiMap+import Toml.Codec.BiMap.Conversion+import Toml.Codec.Code+import Toml.Codec.Combinator+import Toml.Codec.Di+import Toml.Codec.Error+import Toml.Codec.Generic+import Toml.Codec.Types++{- $types+Core codec types, including @Toml@ specialised ones: 'TomlCodec', 'TomlState'+and 'TomlEnv'.+-}++{- $error+Core error types, including 'TomlDecodeError' and 'LoadTomlException'.+-}++{- $code+Contains TOML-specific combinators for converting between TOML and user data+types.+-}++{- $di+Forward and backward mapping functions and combinators (similar to profunctors).+-}++{- $combinator+Contains TOML-specific combinators and codecs for converting between TOML and+user data types.+-}++{- $generic+Automatic TOML codecs using 'GHC.Generics.Generic'.+-}++{- $bimap+'BiMap' type that represents /Tagged Partial Bidirectional Conversion/+between TOML primitives and Haskell types.+-}++{- $bimapConversion+Specific implementations of 'BiMap' between Haskell types and TOML+values.+-}
+ src/Toml/Codec/BiMap.hs view
@@ -0,0 +1,237 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE DataKinds           #-}+{-# LANGUAGE DeriveAnyClass      #-}+{-# LANGUAGE GADTs               #-}+{-# LANGUAGE KindSignatures      #-}+{-# LANGUAGE Rank2Types          #-}++{- |+Copyright: (c) 2018-2020 Kowainik+SPDX-License-Identifier: MPL-2.0+Maintainer: Kowainik <xrom.xkov@gmail.com>++Implementation of /Tagged Partial Bidirectional Isomorphism/. This+module contains the 'BiMap' type that represents conversion between+two types with the possibility of failure.++See "Toml.Codec.BiMap.Conversion" for examples of 'BiMap' with+specific types. The 'BiMap' concept is general and is not specific to+TOML, but in this package most usages of 'BiMap' are between TOML+values and Haskell values.+-}++module Toml.Codec.BiMap+    ( -- * 'BiMap' concept+      BiMap (..)+    , invert+    , iso+    , prism++      -- * TOML 'BiMap'+      -- ** Type+    , TomlBiMap+      -- ** Error+    , TomlBiMapError (..)+    , wrongConstructor+    , prettyBiMapError+      -- ** Smart constructors+    , mkAnyValueBiMap+      -- ** Internals+    , tShow+    ) where++import Control.DeepSeq (NFData)+import Control.Monad ((>=>))+import Data.Bifunctor (first)+import Data.Text (Text)+import GHC.Generics (Generic)++import Toml.Type.AnyValue (AnyValue (..), MatchError (..))+import Toml.Type.Value (TValue (..), Value (..))++import qualified Control.Category as Cat+import qualified Data.Text as T+++{- | Partial bidirectional isomorphism. @BiMap a b@ contains two function:++1. @a -> Either e b@+2. @b -> Either e a@++If you think of types as sets then this data type can be illustrated by the+following picture:++![bimap-type](https://user-images.githubusercontent.com/4276606/50770531-b6a36000-1298-11e9-9528-caae87951d2a.png)++'BiMap' also implements 'Cat.Category' typeclass. And this instance can be described+clearly by this illustration:++![bimap-cat](https://user-images.githubusercontent.com/4276606/50771234-13a01580-129b-11e9-93da-6c5dd0f7f160.png)++@since 0.4.0+-}+data BiMap e a b = BiMap+    { forward  :: a -> Either e b+    , backward :: b -> Either e a+    }++-- | @since 0.4.0+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.++@since 0.4.0+-}+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:++@+__newtype__ Even = Even Integer+__newtype__ Odd  = Odd  Integer++succEven :: Even -> Odd+succEven (Even n) = Odd (n + 1)++predOdd :: Odd -> Even+predOdd (Odd n) = Even (n - 1)++_EvenOdd :: 'BiMap' e Even Odd+_EvenOdd = 'iso' succEven predOdd+@++@since 0.4.0+-}+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 sum types like this:++@+__data__ User+    = Admin  Integer  -- id of admin+    | Client Text     -- name of the client+    __deriving__ (Show)++_Admin :: 'TomlBiMap' User Integer+_Admin = Toml.'prism' Admin $ \\__case__+    Admin i -> Right i+    other   -> Toml.'wrongConstructor' \"Admin\" other++_Client :: 'TomlBiMap' User Text+_Client = Toml.'prism' Client $ \\__case__+    Client n -> Right n+    other    -> Toml.'wrongConstructor' \"Client\" other+@++@since 0.4.0+-}+prism+    :: (field -> object)+    -- ^ Constructor+    -> (object -> Either error field)+    -- ^ Match object to either error or field+    -> BiMap error object field+prism review preview = BiMap preview (Right . review)+{-# INLINE prism #-}++----------------------------------------------------------------------------+-- TOML BiMap+----------------------------------------------------------------------------++{- | 'BiMap' specialized to TOML error.++@since 1.0.0+-}+type TomlBiMap = BiMap TomlBiMapError++{- | Type of errors for TOML 'BiMap'.++@since 1.0.0+-}+data TomlBiMapError+    = 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+    | WrongValue       -- ^ Error for cases with wrong values+        !MatchError    -- ^ Information about failed matching+    | ArbitraryError   -- ^ Arbitrary textual error+        !Text          -- ^ Error message+    deriving stock (Eq, Show, Generic)+    deriving anyclass (NFData)++{- | Converts 'TomlBiMapError' into pretty human-readable text.++@since 1.0.0+-}+prettyBiMapError :: TomlBiMapError -> Text+prettyBiMapError = \case+    WrongConstructor expected actual -> T.unlines+        [ "Invalid constructor"+        , "  * Expected: " <> expected+        , "  * Actual:   " <> actual+        ]+    WrongValue (MatchError expected actual) -> T.unlines+        [ "Invalid constructor"+        , "  * Expected: " <> tShow expected+        , "  * Actual:   " <> tShow actual+        ]+    ArbitraryError text  -> text++{- | Helper to construct WrongConstuctor error.++@since 1.0.0+-}+wrongConstructor+    :: Show a+    => Text  -- ^ Name of the expected constructor+    -> a     -- ^ Actual value+    -> Either TomlBiMapError b+wrongConstructor constructor x = Left $ WrongConstructor constructor (tShow x)++tShow :: Show a => a -> Text+tShow = T.pack . show+{-# INLINE tShow #-}++----------------------------------------------------------------------------+--  BiMaps for value+----------------------------------------------------------------------------++{- | Smart constructor for 'BiMap' from a Haskell value (some+primitive like 'Int' or 'Text') to 'AnyValue'.++@since 0.4.0+-}+mkAnyValueBiMap+    :: forall a (tag :: TValue)+    .  (forall (t :: TValue) . Value t -> Either MatchError a)+    -- ^ Haskell type exctractor from 'Value'+    -> (a -> Value tag)+    -- ^ Convert Haskell type back to 'Value'+    -> TomlBiMap a AnyValue+mkAnyValueBiMap matchValue toValue = BiMap+    { forward  = Right . toAnyValue+    , backward = fromAnyValue+    }+  where+    toAnyValue :: a -> AnyValue+    toAnyValue = AnyValue . toValue++    fromAnyValue :: AnyValue -> Either TomlBiMapError a+    fromAnyValue (AnyValue value) = first WrongValue $ matchValue value
+ src/Toml/Codec/BiMap/Conversion.hs view
@@ -0,0 +1,619 @@+{-# LANGUAGE GADTs #-}++{- |+Copyright: (c) 2018-2020 Kowainik+SPDX-License-Identifier: MPL-2.0+Maintainer: Kowainik <xrom.xkov@gmail.com>++Implementations of 'BiMap' for specific Haskell types and TOML+values. Usually, you use codecs from the "Toml.Codec.Combinator"+module. You may need to use these 'BiMap's instead of codecs in the+following situations:++1. When using 'Toml.Codec.Combinator.List.arrayOf' combinator (or similar).+2. When using 'Toml.Codec.Combinator.Map.tableMap' combinator (for keys).+3. When implementing custom 'BiMap' for your types.++@since 1.3.0.0+-}++module Toml.Codec.BiMap.Conversion+    ( -- * Primitive+      -- ** Boolean+      _Bool+      -- ** Integral+    , _Int+    , _Word+    , _Word8+    , _Integer+    , _Natural+      -- ** Floating+    , _Double+    , _Float+      -- ** Text+    , _Text+    , _LText+    , _ByteString+    , _LByteString+    , _String++      -- * Time+    , _ZonedTime+    , _LocalTime+    , _Day+    , _TimeOfDay++      -- * Arrays+    , _Array+    , _NonEmpty+    , _Set+    , _HashSet+    , _IntSet+    , _ByteStringArray+    , _LByteStringArray++      -- * Coerce+    , _Coerce++      -- * Custom+    , _EnumBounded+    , _Read+    , _TextBy+    , _Validate++      -- * 'Key's+    , _KeyText+    , _KeyString+    , _KeyInt++      -- * General purpose+    , _Just+    , _Left+    , _Right++      -- * Internal helpers+    , _LTextText+    , _NaturalInteger+    , _NonEmptyList+    , _StringText+    , _ReadString+    , _BoundedInteger+    , _EnumBoundedText+    , _ByteStringText+    , _LByteStringText+    ) where++import Control.Category ((>>>))+import Control.Monad ((>=>))+import Data.Bifunctor (bimap, first)+import Data.ByteString (ByteString)+import Data.Coerce (Coercible, coerce)+import Data.Hashable (Hashable)+import Data.Map (Map)+import Data.Text (Text)+import Data.Time (Day, LocalTime, TimeOfDay, ZonedTime)+import Data.Word (Word8)+import Numeric.Natural (Natural)+import Text.Read (readEither)++import Toml.Codec.BiMap (BiMap (..), TomlBiMap, TomlBiMapError (..), iso, mkAnyValueBiMap, prism,+                         tShow, wrongConstructor)+import Toml.Parser (TomlParseError (..), parseKey)+import Toml.Type.AnyValue (AnyValue (..), applyAsToAny, matchBool, matchDay, matchDouble,+                           matchHours, matchInteger, matchLocal, matchText, matchZoned,+                           mkMatchError, toMArray)+import Toml.Type.Key (Key (..))+import Toml.Type.Printer (prettyKey)+import Toml.Type.Value (TValue (..), Value (..))++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+import qualified Data.List.NonEmpty as NE+import qualified Data.Map as M+import qualified Data.Set as S+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import qualified Data.Text.Lazy as TL+import qualified Data.Text.Lazy.Encoding as TL++----------------------------------------------------------------------------+-- Primitive+----------------------------------------------------------------------------++{- | 'Prelude.Bool' 'BiMap' for 'AnyValue'. Usually used as the+'Toml.Codec.Combinator.Primitive.bool' combinator.++@since 0.4.0+-}+_Bool :: TomlBiMap Bool AnyValue+_Bool = mkAnyValueBiMap matchBool Bool+{-# INLINE _Bool #-}++{- | 'Prelude.Integer' 'BiMap' for 'AnyValue'. Usually used as the+'Toml.Codec.Combinator.Primitive.integer' combinator.++@since 0.4.0+-}+_Integer :: TomlBiMap Integer AnyValue+_Integer = mkAnyValueBiMap matchInteger Integer+{-# INLINE _Integer #-}++{- | 'Prelude.Double' 'BiMap' for 'AnyValue'. Usually used as the+'Toml.Codec.Combinator.Primitive.double' combinator.++@since 0.4.0+-}+_Double :: TomlBiMap Double AnyValue+_Double = mkAnyValueBiMap matchDouble Double+{-# INLINE _Double #-}++{- | 'Data.Text.Text' 'BiMap' for 'AnyValue'. Usually used as the+'Toml.Codec.Combinator.Primitive.text' combinator.++@since 0.4.0+-}+_Text :: TomlBiMap Text AnyValue+_Text = mkAnyValueBiMap matchText Text+{-# INLINE _Text #-}++{- | Helper bimap for 'Data.Text.Lazy.Text' and 'Data.Text.Text'.++@since 1.0.0+-}+_LTextText :: BiMap e TL.Text Text+_LTextText = iso TL.toStrict TL.fromStrict+{-# INLINE _LTextText #-}++{- | 'Data.Text.Lazy.Text' 'BiMap' for 'AnyValue'. Usually used as the+'Toml.Codec.Combinator.Primitive.lazyText' combinator.++@since 1.0.0+-}+_LText :: TomlBiMap TL.Text AnyValue+_LText = _LTextText >>> _Text+{-# INLINE _LText #-}++{- | 'Data.Time.ZonedTime' bimap for 'AnyValue'. Usually used as the+'Toml.Codec.Combinator.Time.zonedTime' combinator.++@since 0.5.0+-}+_ZonedTime :: TomlBiMap ZonedTime AnyValue+_ZonedTime = mkAnyValueBiMap matchZoned Zoned+{-# INLINE _ZonedTime #-}++{- | 'Data.Time.LocalTime' bimap for 'AnyValue'. Usually used as the+'Toml.Codec.Combinator.Time.localTime' combinator.++@since 0.5.0+-}+_LocalTime :: TomlBiMap LocalTime AnyValue+_LocalTime = mkAnyValueBiMap matchLocal Local+{-# INLINE _LocalTime #-}++{- | 'Data.Time.Day' 'BiMap' for 'AnyValue'. Usually used as the+'Toml.Codec.Combinator.Time.day' combinator.++@since 0.5.0+-}+_Day :: TomlBiMap Day AnyValue+_Day = mkAnyValueBiMap matchDay Day+{-# INLINE _Day #-}++{- | 'Data.Time.TimeOfDay' 'BiMap' for 'AnyValue'. Usually used as the+'Toml.Codec.Combinator.Time.timeOfDay' combinator.++@since 0.5.0+-}+_TimeOfDay :: TomlBiMap TimeOfDay AnyValue+_TimeOfDay = mkAnyValueBiMap matchHours Hours+{-# INLINE _TimeOfDay #-}++{- | Helper 'BiMap' for 'String' and 'Data.Text.Text'.++@since 0.4.0+-}+_StringText :: BiMap e String Text+_StringText = iso T.pack T.unpack+{-# INLINE _StringText #-}++{- | 'String' 'BiMap' for 'AnyValue'. Usually used as the+'Toml.Codec.Combinator.Primitive.string' combinator.++@since 0.4.0+-}+_String :: TomlBiMap String AnyValue+_String = _StringText >>> _Text+{-# INLINE _String #-}++{- | Helper 'BiMap' for 'Natural' and 'Prelude.Integer'.++@since 0.5.0+-}+_NaturalInteger :: TomlBiMap Natural Integer+_NaturalInteger = BiMap (Right . toInteger) eitherInteger+  where+    eitherInteger :: Integer -> Either TomlBiMapError Natural+    eitherInteger n+      | n < 0     = Left $ ArbitraryError $ "Value is below zero, but expected Natural: " <> tShow n+      | otherwise = Right (fromIntegral n)++{- | 'Natural' 'BiMap' for 'AnyValue'. Usually used as the+'Toml.Codec.Combinator.Primitive.natural' combinator.++@since 0.5.0+-}+_Natural :: TomlBiMap Natural AnyValue+_Natural = _NaturalInteger >>> _Integer+{-# INLINE _Natural #-}++{- | Helper 'BiMap' for 'Prelude.Integer' and integral, bounded values.++@since 0.5.0+-}+_BoundedInteger :: (Integral a, Bounded a, Show a) => TomlBiMap a Integer+_BoundedInteger = BiMap (Right . toInteger) eitherBounded+  where+    eitherBounded :: forall a. (Integral a, Bounded a, Show a) => Integer -> Either TomlBiMapError a+    eitherBounded n+      | n < toInteger (minBound @a) =+         let msg = "Value " <> tShow n <> " is less than minBound: " <> tShow (minBound @a)+         in Left $ ArbitraryError msg+      | n > toInteger (maxBound @a) =+         let msg = "Value " <> tShow n <> " is greater than maxBound: " <> tShow (maxBound @a)+         in Left $ ArbitraryError msg+      | otherwise = Right (fromIntegral n)+++{- | 'Word' 'BiMap' for 'AnyValue'. Usually used as the+'Toml.Codec.Combinator.Primitive.word' combinator.++@since 0.5.0+-}+_Word :: TomlBiMap Word AnyValue+_Word = _BoundedInteger >>> _Integer+{-# INLINE _Word #-}++{- | 'Word8' 'BiMap' for 'AnyValue'. Usually used as the+'Toml.Codec.Combinator.Primitive.word8' combinator.++@since 1.2.0.0+-}+_Word8 :: TomlBiMap Word8 AnyValue+_Word8 = _BoundedInteger >>> _Integer+{-# INLINE _Word8 #-}++{- | 'Int' 'BiMap' for 'AnyValue'. Usually used as the+'Toml.Codec.Combinator.Primitive.int' combinator.++@since 0.5.0+-}+_Int :: TomlBiMap Int AnyValue+_Int = _BoundedInteger >>> _Integer+{-# INLINE _Int #-}++{- | 'Float' 'BiMap' for 'AnyValue'. Usually used as the+'Toml.Codec.Combinator.Primitive.float' combinator.++@since 0.5.0+-}+_Float :: TomlBiMap Float AnyValue+_Float = iso realToFrac realToFrac >>> _Double+{-# INLINE _Float #-}++{- | Helper 'BiMap' for 'Data.Text.Text' and strict 'ByteString'++@since 0.5.0+-}+_ByteStringText :: TomlBiMap ByteString Text+_ByteStringText = prism T.encodeUtf8 eitherText+  where+    eitherText :: ByteString -> Either TomlBiMapError Text+    eitherText = either (\err -> Left $ ArbitraryError $ tShow err) Right . T.decodeUtf8'+{-# INLINE _ByteStringText #-}++{- | UTF-8 encoded 'ByteString' 'BiMap' for 'AnyValue'.+Usually used as the 'Toml.Codec.Combinator.Primitive.byteString' combinator.++@since 0.5.0+-}+_ByteString :: TomlBiMap ByteString AnyValue+_ByteString = _ByteStringText >>> _Text+{-# INLINE _ByteString #-}++{- | Helper 'BiMap' for 'Data.Text.Text' and lazy 'BL.ByteString'.++@since 0.5.0+-}+_LByteStringText :: TomlBiMap BL.ByteString Text+_LByteStringText = prism (TL.encodeUtf8 . TL.fromStrict) eitherText+  where+    eitherText :: BL.ByteString -> Either TomlBiMapError Text+    eitherText = bimap (ArbitraryError . tShow) TL.toStrict . TL.decodeUtf8'+{-# INLINE _LByteStringText #-}++{- | UTF-8 encoded lazy 'BL.ByteString' 'BiMap' for 'AnyValue'.+Usually used as the 'Toml.Codec.Combinator.Primitive.lazyByteString' combinator.++@since 0.5.0+-}+_LByteString :: TomlBiMap BL.ByteString AnyValue+_LByteString = _LByteStringText >>> _Text+{-# INLINE _LByteString #-}++----------------------------------------------------------------------------+-- Array+----------------------------------------------------------------------------++{- | 'ByteString' 'BiMap' for 'AnyValue' encoded as a list of bytes+(non-negative integers between 0 and 255). Usually used as the+'Toml.Codec.Combinator.Primitive.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+bytes (non-negative integers between 0 and 255). Usually used as+'Toml.Codec.Combinator.Primitive.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' for a list of values and 'AnyValue'+as an array. Usually used as the 'Toml.Codec.Combinator.List.arrayOf' combinator.++@since 0.4.0+-}+_Array :: forall a . TomlBiMap a AnyValue -> TomlBiMap [a] AnyValue+_Array elementBimap = BiMap toAnyValue fromAnyValue+  where+    toAnyValue :: [a] -> Either TomlBiMapError AnyValue+    toAnyValue = mapM (forward elementBimap) >=> bimap WrongValue AnyValue . toMArray++    fromAnyValue :: AnyValue -> Either TomlBiMapError [a]+    fromAnyValue (AnyValue v) = matchElements (backward elementBimap) v++    -- can't reuse matchArray here :(+    matchElements :: (AnyValue -> Either TomlBiMapError a) -> Value t -> Either TomlBiMapError [a]+    matchElements match (Array a) = mapM (applyAsToAny match) a+    matchElements _ val           = first WrongValue $ mkMatchError TArray val++{- | Takes a 'BiMap' of a value and returns a 'BiMap' for a 'NonEmpty'+list of values and 'AnyValue' as an array. Usually used as the+'Toml.Codec.Combinator.List.arrayNonEmptyOf' combinator.++@since 0.5.0+-}+_NonEmpty :: TomlBiMap a AnyValue -> TomlBiMap (NE.NonEmpty a) AnyValue+_NonEmpty bi = _NonEmptyList >>> _Array bi+{-# INLINE _NonEmpty #-}++{- | Helper 'BiMap' for lists and 'NE.NonEmpty'.++@since 1.3.0.0+-}+_NonEmptyList :: TomlBiMap (NE.NonEmpty a) [a]+_NonEmptyList = BiMap+    { forward  = Right . NE.toList+    , backward = maybe (Left $ ArbitraryError "Empty array list, but expected NonEmpty") Right . NE.nonEmpty+    }+{-# INLINE _NonEmptyList #-}++{- | Takes a 'BiMap' of a value and returns a 'BiMap' for a 'Set' of+values and 'AnyValue' as an array. Usually used as the+'Toml.Codec.Combinator.Set.arraySetOf' combinator.++@since 0.5.0+-}+_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' for a 'HashSet' of+values and 'AnyValue' as an array. Usually used as the+'Toml.Codec.Combinator.Set.arrayHashSetOf' combinator.++@since 0.5.0+-}+_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 the+'Toml.Codec.Combinator.Set.arrayIntSet' combinator.++@since 0.5.0+-}+_IntSet :: TomlBiMap IS.IntSet AnyValue+_IntSet = iso IS.toList IS.fromList >>> _Array _Int+{-# INLINE _IntSet #-}++----------------------------------------------------------------------------+-- Coerce+----------------------------------------------------------------------------++{- | 'BiMap' for 'Coercible' values. It takes a 'TomlBiMap'+for @a@ type and returns a 'TomlBiMap' @b@ if these types are coercible.++It is supposed to be used to ease the work with @newtypes@.++E.g.++@+__newtype__ Foo = Foo+    { unFoo :: 'Int'+    }++fooBiMap :: 'TomlBiMap' Foo 'AnyValue'+fooBiMap = '_Coerce' '_Int'+@++@since 1.3.0.0+-}+_Coerce :: (Coercible a b) => TomlBiMap a AnyValue -> TomlBiMap b AnyValue+_Coerce = coerce+{-# INLINE _Coerce #-}++----------------------------------------------------------------------------+-- Custom+----------------------------------------------------------------------------++{- | Helper 'BiMap' for 'String' and types with 'Read' and 'Show' instances.++@since 0.5.0+-}+_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' instances.+Usually used as the 'Toml.Codec.Combinator.Custom.read' combinator.++@since 0.5.0+-}+_Read :: (Show a, Read a) => TomlBiMap a AnyValue+_Read = _ReadString >>> _String+{-# INLINE _Read #-}++{- | Creates 'BiMap' for 'Data.Text.Text' to 'AnyValue' with custom functions.+Usually used as the 'Toml.Codec.Combinator.Custom.textBy' combinator.++@since 0.5.0+-}+_TextBy+    :: forall a .+       (a -> Text)              -- ^ @show@ function for @a@+    -> (Text -> Either Text a)  -- ^ Parser of @a@ from 'Data.Text.Text'+    -> TomlBiMap a AnyValue+_TextBy toText parseText = BiMap toAnyValue fromAnyValue+  where+    toAnyValue :: a -> Either TomlBiMapError AnyValue+    toAnyValue = Right . AnyValue . Text . toText++    fromAnyValue :: AnyValue -> Either TomlBiMapError a+    fromAnyValue (AnyValue v) =+        first WrongValue (matchText v) >>= first ArbitraryError . parseText++{- | By the given 'BiMap' validates it with the given predicate that returns+'Either' the value, if the validation is successful, or the 'Text' of the error+that should be returned in case of validation failure.++Usually used as the 'Toml.Codec.Combinator.Custom.validate' or+'Toml.Codec.Combinator.Custom.validateIf' combinator.++@since 1.3.0.0+-}+_Validate :: forall a . (a -> Either Text a) -> TomlBiMap a AnyValue -> TomlBiMap a AnyValue+_Validate p BiMap{..} = BiMap forward backwardWithValidation+  where+    backwardWithValidation :: AnyValue -> Either TomlBiMapError a+    backwardWithValidation anyVal = backward anyVal >>= first ArbitraryError . p++{- | Helper 'BiMap' for '_EnumBounded' and 'Data.Text.Text'.++@since 1.1.0.0+-}+_EnumBoundedText :: forall a. (Show a, Enum a, Bounded a) => TomlBiMap a Text+_EnumBoundedText = BiMap+    { forward  = Right . tShow+    , backward = toEnumBounded+    }+  where+    toEnumBounded :: Text -> Either TomlBiMapError a+    toEnumBounded value = case M.lookup value enumOptions of+        Just a  -> Right a+        Nothing ->+            let msg = "Value is '" <> value <> "' but expected one of: " <> T.intercalate ", " options+            in Left (ArbitraryError msg)+      where+        enumOptions :: Map Text a+        enumOptions = M.fromList $ zip options enums+        options  = fmap tShow enums+        enums = [minBound @a .. maxBound @a]++{- | 'BiMap' for nullary sum data types (enumerations) with 'Show',+'Enum' and 'Bounded' instances. Usually used as the+'Toml.Codec.Combinator.Custom.enumBounded' combinator.++@since 1.1.0.0+-}+_EnumBounded :: (Show a, Enum a, Bounded a) => TomlBiMap a AnyValue+_EnumBounded = _EnumBoundedText >>> _Text+{-# INLINE _EnumBounded #-}++----------------------------------------------------------------------------+-- Keys+----------------------------------------------------------------------------++{- | Bidirectional converter between 'Key' and+'Data.Text.Text'. Usually used as an argument for+'Toml.Codec.Combinator.Map.tableMap'.++@since 1.3.0.0+-}+_KeyText :: TomlBiMap Key Text+_KeyText = BiMap+    { forward = Right . prettyKey+    , backward = first (ArbitraryError . unTomlParseError) . parseKey+    }++{- | Bidirectional converter between 'Key' and 'String'. Usually used+as an argument for 'Toml.Codec.Combinator.Map.tableMap'.++@since 1.3.0.0+-}+_KeyString :: TomlBiMap Key String+_KeyString = BiMap+    { forward = Right . T.unpack . prettyKey+    , backward = first (ArbitraryError . unTomlParseError) . parseKey . T.pack+    }++{- | Bidirectional converter between 'Key' and 'Int'. Usually used+as an argument for 'Toml.Codec.Combinator.Map.tableIntMap'.++@since 1.3.0.0+-}+_KeyInt :: TomlBiMap Key Int+_KeyInt = BiMap+    { forward = first (ArbitraryError . T.pack) . readEither . T.unpack . prettyKey+    , backward = first (ArbitraryError . unTomlParseError) . parseKey . tShow+    }++----------------------------------------------------------------------------+-- General purpose bimaps+----------------------------------------------------------------------------++{- | 'BiMap' for 'Either' and its 'Left' part.++@since 0.4.0+-}+_Left :: (Show l, Show r) => TomlBiMap (Either l r) l+_Left = prism Left $ \case+    Left l -> Right l+    x -> wrongConstructor "Left" x++{- | 'BiMap' for 'Either' and its 'Right' part.++@since 0.4.0+-}+_Right :: (Show l, Show r) => TomlBiMap (Either l r) r+_Right = prism Right $ \case+    Right r -> Right r+    x -> wrongConstructor "Right" x++{- | 'BiMap' for 'Maybe' and its 'Just' part.++@since 0.5.0+-}+_Just :: Show r => TomlBiMap (Maybe r) r+_Just = prism Just $ \case+    Just r -> Right r+    x -> wrongConstructor "Just" x
+ src/Toml/Codec/Code.hs view
@@ -0,0 +1,122 @@+{- |+Copyright: (c) 2018-2020 Kowainik+SPDX-License-Identifier: MPL-2.0+Maintainer: Kowainik <xrom.xkov@gmail.com>++Conversion from textual representation of @TOML@ values to the Haskell data+types by the given 'TomlCodec'.++This module includes coding functions like 'decode' and 'encode'.+-}++module Toml.Codec.Code+       ( -- * Decode+         decode+       , decodeValidation+       , decodeFileEither+       , decodeFile+         -- * Encode+       , encode+       , encodeToFile+         -- * Run+       , runTomlCodec+       , execTomlCodec+       ) where++import Control.Exception (throwIO)+import Control.Monad.IO.Class (MonadIO (..))+import Data.Text (Text)+import Validation (Validation (..), validationToEither)++import Toml.Codec.Error (LoadTomlException (..), TomlDecodeError (..), prettyTomlDecodeErrors)+import Toml.Codec.Types (Codec (..), TomlCodec, TomlState (..))+import Toml.Parser (parse)+import Toml.Type (TOML (..))+import Toml.Type.Printer (pretty)++import qualified Data.Text.IO as TIO+++{- | Convert textual representation of @TOML@ into user data type by the+provided codec.++@since 1.3.0.0+-}+decodeValidation :: TomlCodec a -> Text -> Validation [TomlDecodeError] a+decodeValidation codec text = case parse text of+    Left err   -> Failure [ParseError err]+    Right toml -> runTomlCodec codec toml++{- | Convert textual representation of @TOML@ into user data type by the+provided codec.++@since 0.0.0+-}+decode :: TomlCodec a -> Text -> Either [TomlDecodeError] a+decode codec = validationToEither . decodeValidation codec++{- | Similar to 'decodeValidation', but takes a path to a file with textual @TOML@+values from which it decodes them with the provided codec.++@since 1.3.0.0+-}+decodeFileValidation+    :: forall a m . (MonadIO m)+    => TomlCodec a+    -> FilePath+    -> m (Validation [TomlDecodeError] a)+decodeFileValidation codec = fmap (decodeValidation codec) . liftIO . TIO.readFile++{- | Similar to 'decode', but takes a path to a file with textual @TOML@+values from which it decodes them with the provided codec.++@since 1.3.0.0+-}+decodeFileEither+    :: forall a m . (MonadIO m)+    => TomlCodec a+    -> FilePath+    -> m (Either [TomlDecodeError] a)+decodeFileEither codec = fmap validationToEither . decodeFileValidation codec++{- | Similar to 'decodeFileEither', throws 'LoadTomlException' in case of parse+errors ('TomlDecodeError').++@since 0.3.1+-}+decodeFile :: forall a m . (MonadIO m) => TomlCodec a -> FilePath -> m a+decodeFile codec filePath = decodeFileEither codec filePath >>= errorWhenLeft+  where+    errorWhenLeft :: Either [TomlDecodeError] a -> m a+    errorWhenLeft (Left errs) =+        liftIO+        $ throwIO+        $ LoadTomlException filePath+        $ prettyTomlDecodeErrors errs+    errorWhenLeft (Right pc) = pure pc++{- | Convert data type to the textual representation of @TOML@ values.++@since 0.0.0+-}+encode :: TomlCodec a -> a -> Text+encode codec obj = pretty $ execTomlCodec codec obj++{- | Convert data type to the textual representation of @TOML@ values.+and write it info the given file.++@since 1.3.0.0+-}+encodeToFile :: forall a m . (MonadIO m) => TomlCodec a -> FilePath -> a -> m Text+encodeToFile codec filePath obj = content <$ liftIO (TIO.writeFile filePath content)+  where+    content :: Text+    content = encode codec obj++-- | Convert toml into user data type.+runTomlCodec :: TomlCodec a -> TOML -> Validation [TomlDecodeError] a+runTomlCodec = codecRead++-- | Runs 'codecWrite' of 'TomlCodec' and returns intermediate TOML AST.+execTomlCodec :: TomlCodec a -> a -> TOML+execTomlCodec codec obj = snd $ unTomlState (codecWrite codec obj) mempty
+ src/Toml/Codec/Combinator.hs view
@@ -0,0 +1,152 @@+{- |+Copyright: (c) 2018-2020 Kowainik+SPDX-License-Identifier: MPL-2.0+Maintainer: Kowainik <xrom.xkov@gmail.com>++Contains TOML-specific combinators for converting between TOML and user data+types.+-}++module Toml.Codec.Combinator+    ( -- * Basic codecs for primitive values+      -- $primitive+      module Toml.Codec.Combinator.Primitive+      -- ** Time types+      -- $time+    , module Toml.Codec.Combinator.Time++      -- * Combinators for tables+      -- $table+    , module Toml.Codec.Combinator.Table++      -- * Codecs for containers of primitives+      -- ** Lists+      -- $list+    , module Toml.Codec.Combinator.List+      -- ** Sets+      -- $set+    , module Toml.Codec.Combinator.Set+      -- ** Maps+      -- $map+    , module Toml.Codec.Combinator.Map+      -- ** Tuples+      -- $tuple+    , module Toml.Codec.Combinator.Tuple++      -- * Codecs for 'Monoid's+      -- $monoid+    , module Toml.Codec.Combinator.Monoid++      -- * Additional codecs for custom types+      -- $custom+    , module Toml.Codec.Combinator.Custom++      -- * General construction of codecs+      -- $common+    , module Toml.Codec.Combinator.Common+    ) where++import Prelude hiding (all, any, last, map, product, read, sum)++import Toml.Codec.Combinator.Common+import Toml.Codec.Combinator.Custom+import Toml.Codec.Combinator.List+import Toml.Codec.Combinator.Map+import Toml.Codec.Combinator.Monoid+import Toml.Codec.Combinator.Primitive+import Toml.Codec.Combinator.Set+import Toml.Codec.Combinator.Table+import Toml.Codec.Combinator.Time+import Toml.Codec.Combinator.Tuple++{- $primitive+TOML-specific combinators for converting between TOML and Haskell primitive+types, e.g. 'int' \<-\> 'Int', 'byteString' \<-\> 'ByteString'.++See the "Toml.Codec.Combinators.Primitive" module documentation for the overview+table and more examples.+-}++{- $time+TOML-specific combinators for converting between TOML and Haskell date and time+data types. TOML specification describes date and time primitives you+can use in your configuration. @tomland@ provides mapping of those+primitives to types from the @time@ library.++See the "Toml.Codec.Combinators.Time" module documentation for the overview+table and more examples.+-}++{- $table+Combinators for the @TOML@ tables.++See the "Toml.Codec.Combinators.Table" module documentation for more examples.+-}++{- $list+TOML-specific combinators for converting between TOML and Haskell list-like data+types.++See the "Toml.Codec.Combinators.List" module documentation for the overview+table and more examples.+-}++{- $set+TOML-specific combinators for converting between TOML and Haskell set-like data+types.++See the "Toml.Codec.Combinators.Set" module documentation for the overview+table and more examples.+-}++{- $map+TOML-specific combinators for converting between TOML and Haskell map-like data+types.++See the "Toml.Codec.Combinators.Map" module documentation for the overview+table and more examples.+-}++{- $tuple+TOML-specific combinators for converting between TOML and Haskell tuples.+It's recommended to create your custom data types and implement codecs+for them, but if you need to have tuples (e.g. for decoding different+constructors of sum types), you can find codecs from this module+helpful.++See the "Toml.Codec.Combinators.Tuple" module documentation for the overview+table and more examples.+-}++{- $monoid+TOML-specific combinators for converting between TOML and Haskell 'Monoid'+wrapper data types. These codecs are especially handy when you are implementing+the [Partial Options Monoid](https://medium.com/@jonathangfischoff/the-partial-options-monoid-pattern-31914a71fc67)+pattern.++See the "Toml.Codec.Combinators.Monoid" module documentation for the overview+table and more examples.+-}++{- $custom+This module provides additional combinators that could help in the situation+when some additional manipulations for the standard combinators is required.++/For example,/ 'validate' allows to perform some custom validation on the codec+before encoding. And 'enumBounded' is an automatical codec that uses 'Enum' and+'Bounded' instances of the data type only and provides descriptive error+messages at the same time.++See the "Toml.Codec.Combinators.Custom" module documentation for the overview+table and more examples.+-}++{- $common+This module implements common utilities for writing custom codecs+without diving into internal implementation details. Most of the time+you don't need to implement your own codecs and can reuse existing+ones. But if you need something that library doesn't provide, you can+find functions in this module useful.++See the "Toml.Codec.Combinators.Common" module documentation for more examples.+-}
+ src/Toml/Codec/Combinator/Common.hs view
@@ -0,0 +1,77 @@+{-# LANGUAGE FlexibleContexts #-}++{- |+Copyright: (c) 2018-2020 Kowainik+SPDX-License-Identifier: MPL-2.0+Maintainer: Kowainik <xrom.xkov@gmail.com>++This module implements common utilities for writing custom codecs+without diving into internal implementation details. Most of the time+you don't need to implement your own codecs and can reuse existing+ones. But if you need something that library doesn't provide, you can+find functions in this module useful.++@since 1.3.0.0+-}++module Toml.Codec.Combinator.Common+    ( match+    , whenLeftBiMapError+    ) where++import Control.Monad.State (modify)+import Validation (Validation (..))++import Toml.Codec.BiMap (BiMap (..), TomlBiMap, TomlBiMapError)+import Toml.Codec.Error (TomlDecodeError (..))+import Toml.Codec.Types (Codec (..), TomlCodec, TomlEnv, TomlState, eitherToTomlState)+import Toml.Type.AnyValue (AnyValue (..))+import Toml.Type.Key (Key)+import Toml.Type.TOML (TOML (..), insertKeyAnyVal)++import qualified Data.HashMap.Strict as HashMap+++{- | 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':++@+_MyType :: 'TomlBiMap' MyType 'AnyValue'+@++And then you can create codec for your type using 'match' function:++@+myType :: 'Key' -> 'TomlCodec' MyType+myType = 'match' _MyType+@++@since 0.4.0+-}+match :: forall a . TomlBiMap a AnyValue -> Key -> TomlCodec a+match BiMap{..} key = Codec input output+  where+    input :: TomlEnv a+    input = \toml -> case HashMap.lookup key (tomlPairs toml) of+        Nothing     -> Failure [KeyNotFound key]+        Just anyVal -> whenLeftBiMapError key (backward anyVal) pure++    output :: a -> TomlState a+    output a = do+        anyVal <- eitherToTomlState $ forward a+        a <$ modify (insertKeyAnyVal key anyVal)+++{- | Throw error on 'Left', or perform a given action with 'Right'.++@since 1.3.0.0+-}+whenLeftBiMapError+    :: Key+    -> Either TomlBiMapError a+    -> (a -> Validation [TomlDecodeError] b)+    -> Validation [TomlDecodeError] b+whenLeftBiMapError key val action = case val of+    Right a  -> action a+    Left err -> Failure [BiMapError key err]
+ src/Toml/Codec/Combinator/Custom.hs view
@@ -0,0 +1,237 @@+{- |+Copyright: (c) 2018-2020 Kowainik+SPDX-License-Identifier: MPL-2.0+Maintainer: Kowainik <xrom.xkov@gmail.com>++Contains TOML-specific custom combinators for converting between TOML and+special user data types.++See examples below of the situations you may need the following combinators.++@since 1.3.0.0+-}++module Toml.Codec.Combinator.Custom+    ( -- * 'Text' combinators+      textBy+    , read+    , enumBounded++      -- * Validation+    , validate+    , validateIf+    ) where++import Prelude hiding (read)++import Data.Text (Text)++import Toml.Codec.BiMap (TomlBiMap)+import Toml.Codec.BiMap.Conversion (_EnumBounded, _Read, _TextBy, _Validate)+import Toml.Codec.Combinator.Common (match)+import Toml.Codec.Types (TomlCodec)+import Toml.Type.AnyValue (AnyValue)+import Toml.Type.Key (Key)+import Toml.Type.Printer (prettyKey)+++{- | Codec for text values with custom error messages for parsing.++__Example:__++We have the following type that represents the image format:++@+__data__ Format+    = Jpeg+    | Png+    | Gif+    __deriving__ ('Show', 'Read', 'Enum')+@++But we want to be able to decode and encode this data type through the custom+text representation, that can be formilised in the following functions:++@+showFormat :: Format -> 'Text'+showFormat = \case+    Jpeg -> ".jpeg"+    Png  -> ".png"+    Gif  -> ".gif"++parseFormat :: 'Text' -> 'Either' 'Text' Format+parseFormat = __\case__+    ".jpeg" -> 'Right' Jpeg+    ".png"  -> 'Right' Png+    ".gif"  -> 'Right' Gif+    other   -> 'Left' $ "Unsupported format: " <> other+@++To write the codec for @Format@ data type using the above rules we can use+'textBy' combinator:++@+formatCodec :: 'Key' -> 'TomlCodec' Format+formatCodec = 'textBy' showFormat parseFormat+@++And now with the @formatCodec "foo"@ we can have the following line in our+@TOML@ perfectly encoded:++@+foo = ".gif"+@++But the @foo = "jif"@ will lead to the following error:++@+tomland decode error:  Unsupported format: jif+@++@since 1.0.0+-}+textBy :: (a -> Text) -> (Text -> Either Text a) -> Key -> TomlCodec a+textBy to from = match (_TextBy to from)+{-# INLINE textBy #-}+++{- | Codec for values with a 'Read' and 'Show' instance.++__Example:__++We have the following type that represents the image format:++@+__data__ Format+    = Jpeg+    | Png+    | Gif+    deriving (Show, Read, Enum)+@++And we want to be able to decode and encode this data type through the 'Show'+and 'Read' instances.++To write the codec for @Format@ data type using the existing instances we can+use 'read' combinator. And now with the @Toml.'read' "foo"@ we can have the+following line in our @TOML@ perfectly encoded:++@+foo = "Gif"+@++But the @foo = ".gif"@ will lead to the following error:++@+tomland decode error:  Prelude.read: no parse+@++@since 0.5.0+-}+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 is similar to 'read' but provides much better error+messages than 'read' for nullary sum types.++E.g. for the same @Format@ example from 'read' function, but with the+@Toml.'enumBounded' "foo"@ codec the error for @foo = \"Jif\"@ in the @TOML@ file+will look like this:++@+tomland decode error:  Value is 'Jif' but expected one of: Jpeg, Png, Gif+@++@since 1.1.0.0+-}+enumBounded :: (Bounded a, Enum a, Show a) => Key -> TomlCodec a+enumBounded = match _EnumBounded+{-# INLINE enumBounded #-}++{- | Codec that checks the 'BiMap' on the given predicate.+The predicate function returns the value, if the validation is successful, or+the 'Text' of the error that should be returned in case of validation failure.++__Example:__++Let's imagine that we want to have the list in @TOML@ that could only+have even 'Int's inside. In this case, you can write the following codec:++@+allEven :: ['Int'] -> 'Either' 'Text' ['Int']+allEven xs =+    if all even xs+    then 'Right' xs+    else 'Left' "This is wrong, I asked you for even only :("++allEvenCodec :: 'TomlCodec' ['Int']+allEvenCodec = Toml.'validate' allEven (Toml._Array Toml._Int) \"myEvenList\"+@++Then in your @TOML@ file you can have:++@+myEvenList = [2, 4, 6]+@++But the following one will lead to the error:++@+myEvenList = [1, 2, 3]+@++@+tomland decode error:  This is wrong, I asked you for even only :(+@++@since 1.3.0.0+-}+validate :: (a -> Either Text a) -> TomlBiMap a AnyValue -> Key -> TomlCodec a+validate p biMap = match (_Validate p biMap)+{-# INLINE validate #-}++{- | Similar to 'validate' but takes the predicate that returnes 'Bool'.+The error in case of the validation failure looks like this:++@+Value does not pass the validation for key: KEY_NAME+@++__Example:__++Let's imagine that we want to have the 'Text' field in @TOML@ that could only+have 3 chars in it. In this case, you can write the following codec:++@+my3charTextCodec :: TomlCodec Text+my3CharTextCodec = Toml.'validateIf' ((==) 3 . Text.length) Toml._Text "myKeyName"+@++The in your @TOML@ file you can have:++@+myKeyName = "abc"+@++But the following one will lead to the error:++@+myKeyName = "I have more than enough"+@++@+tomland decode error:  Value does not pass the validation for key: myKeyName+@++@since 1.3.0.0+-}+validateIf :: forall a . (a -> Bool) -> TomlBiMap a AnyValue -> Key -> TomlCodec a+validateIf p biMap k = validate validateEither biMap k+  where+    validateEither :: a -> Either Text a+    validateEither a =+        if p a+        then Right a+        else Left $ "Value does not pass the validation for key: " <> prettyKey k
+ src/Toml/Codec/Combinator/List.hs view
@@ -0,0 +1,222 @@+{- |+Copyright: (c) 2018-2020 Kowainik+SPDX-License-Identifier: MPL-2.0+Maintainer: Kowainik <xrom.xkov@gmail.com>++TOML-specific combinators for converting between TOML and Haskell list-like data+types.++There are two way to represent list-like structures with the @tomland@ library.++* Ordinary array lists of primitives:++    @+    foo = [100, 200, 300]+    @++* Lists via tables:++    @+    foo =+        [ {x = 100}+        , {x = 200}+        , {x = 300}+        ]++    __OR__++    [[foo]]+        x = 100+    [[foo]]+        x = 200+    [[foo]]+        x = 300+    @++You can find both types of the codecs in this module for different list-like+structures. See the following table for the better understanding:+++-------------------------+----------------------------------+-------------------------------------++|      Haskell Type       |              @TOML@              |             'TomlCodec'             |++=========================+==================================+=====================================++| __@['Int']@__           | @a = [1, 2, 3]@                  | @'arrayOf' 'Toml._Int' "a"@         |++-------------------------+----------------------------------+-------------------------------------++| __@'NonEmpty' 'Int'@__  | @a = [11, 42]@                   | @'arrayNonEmptyOf' 'Toml._Int' "a"@ |++-------------------------+----------------------------------+-------------------------------------++| __@['Text']@__          | @x = [{a = "foo"}, {a = "bar"}]@ | @'list' ('Toml.text' "a") "x"@      |++-------------------------+----------------------------------+-------------------------------------++| __@'NonEmpty' 'Text'@__ | @x = [{a = "foo"}, {a = "bar"}]@ | @'nonEmpty' ('Toml.text' "a") "x"@  |++-------------------------+----------------------------------+-------------------------------------+++@since 1.3.0.0+-}++module Toml.Codec.Combinator.List+    ( -- * Array lists+      arrayOf+    , arrayNonEmptyOf++      -- * Table lists+    , list+    , nonEmpty+    ) where++import Control.Monad.State (gets, modify)+import Data.List.NonEmpty (NonEmpty (..), toList)+import Validation (Validation (..))++import Toml.Codec.BiMap (TomlBiMap)+import Toml.Codec.BiMap.Conversion (_Array, _NonEmpty)+import Toml.Codec.Code (execTomlCodec)+import Toml.Codec.Combinator.Common (match)+import Toml.Codec.Combinator.Table (handleTableErrors)+import Toml.Codec.Error (TomlDecodeError (..))+import Toml.Codec.Types (Codec (..), TomlCodec, TomlEnv, TomlState)+import Toml.Type.AnyValue (AnyValue (..))+import Toml.Type.Key (Key)+import Toml.Type.TOML (TOML (..), insertTableArrays)++import qualified Data.HashMap.Strict as HashMap+++{- | Codec for list of values. Takes converter for single value and+returns a list of values.++__Example:__++Haskell @['Int']@ can look like this in your @TOML@ file:++@+foo = [1, 2, 3]+@++If the key is not present in @TOML@ the following decode error will be spotted:++@+tomland decode error:  Key foo is not found+@++@since 0.1.0+-}+arrayOf :: TomlBiMap a AnyValue -> Key -> TomlCodec [a]+arrayOf = match . _Array+{-# INLINE arrayOf #-}++{- | Codec for non- empty lists of values. Takes converter for single value and+returns a non-empty list of values.++__Example:__++Haskell @'NonEmpty' 'Int'@ can look like this in your @TOML@ file:++@+foo = [1, 2, 3]+@++If you try to decode an empty @TOML@ list you will see the error:++@+tomland decode error:  Empty array list, but expected NonEmpty+@++If the key is not present in @TOML@ the following decode error will be spotted:++@+tomland decode error:  Key foo is not found+@++@since 0.5.0+-}+arrayNonEmptyOf :: TomlBiMap a AnyValue -> Key -> TomlCodec (NonEmpty a)+arrayNonEmptyOf = match . _NonEmpty+{-# INLINE arrayNonEmptyOf #-}++{- | 'Codec' for list of values. Represented in TOML as array of tables.++__Example:__++Haskell @['Int']@ can look like this in your @TOML@ file:++@+foo =+  [ {a = 1}+  , {a = 2}+  , {a = 3}+  ]+@++Decodes to an empty list @[]@ when the key is not present.++@since 1.0.0+-}+list :: forall a . TomlCodec a -> Key -> TomlCodec [a]+list codec key = Codec+    { codecRead = \toml -> case codecRead nonEmptyCodec toml of+        Success ne -> Success $ toList ne+        Failure [TableArrayNotFound errKey]+            | errKey == key -> pure []+        Failure errs -> Failure errs+    , codecWrite = \case+        [] -> pure []+        l@(x:xs) -> l <$ codecWrite nonEmptyCodec (x :| xs)+    }+  where+    nonEmptyCodec :: TomlCodec (NonEmpty a)+    nonEmptyCodec = nonEmpty codec key+++{- | 'Codec' for 'NonEmpty' list of values. Represented in TOML as array of+tables.++__Example:__++Haskell @'NonEmpty' 'Int'@ can look like this in your @TOML@ file:++@+foo =+  [ {a = 1}+  , {a = 2}+  , {a = 3}+  ]+@++If you try to decode an empty @TOML@ list you will see the error:++@+tomland decode error:  Table array [[foo]] is not found+@++or++@+tomland decode error:  Key foo.a is not found+@++If the key is not present in @TOML@ the following decode error will be spotted:++@+tomland decode error:  Table array [[foo]] is not found+@++@since 1.0.0+-}+nonEmpty :: forall a . TomlCodec a -> Key -> TomlCodec (NonEmpty a)+nonEmpty codec key = Codec input output+  where+    input :: TomlEnv (NonEmpty a)+    input = \t -> case HashMap.lookup key $ tomlTableArrays t of+        Nothing    -> Failure [TableArrayNotFound key]+        Just tomls -> traverse (handleTableErrors codec key) tomls+++    -- adds all TOML objects to the existing list if there are some+    output :: NonEmpty a -> TomlState (NonEmpty a)+    output as = do+        let tomls = fmap (execTomlCodec codec) as+        mTables <- gets $ HashMap.lookup key . tomlTableArrays++        let newTomls = case mTables of+                Nothing       -> tomls+                Just oldTomls -> oldTomls <> tomls++        as <$ modify (insertTableArrays key newTomls)
+ src/Toml/Codec/Combinator/Map.hs view
@@ -0,0 +1,399 @@+{-# LANGUAGE ApplicativeDo   #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE TupleSections   #-}++{- |+Copyright: (c) 2018-2020 Kowainik+SPDX-License-Identifier: MPL-2.0+Maintainer: Kowainik <xrom.xkov@gmail.com>++TOML-specific combinators for converting between TOML and Haskell Map-like data+types.++There are two way to represent map-like structures with the @tomland@ library.++* Map structure with the key and value represented as key-value pairs:++    @+    foo =+        [ {myKey = "name", myVal = 42}+        , {myKey = "otherName", myVal = 100}+        ]+    @++* Map structure as a table with the @TOML@ key as the map key:++    @+    [foo]+        name = 42+        otherName = 100+    @++You can find both types of the codecs in this module for different map-like+structures. See the following table for the heads up:+++------------------------------+--------------------------------+----------------------------------------------------++|         Haskell Type         |             @TOML@             |                    'TomlCodec'                     |++==============================+================================+====================================================++| __@'Map' 'Int' 'Text'@__     | @x = [{k = 42, v = "foo"}]@    | @'map' ('Toml.int' "k") ('Toml.text' "v") "x"@     |++------------------------------+--------------------------------+----------------------------------------------------++| __@'Map' 'Text' 'Int'@__     | @x = {a = 42, b = 11}@         | @'tableMap' 'Toml._KeyText' 'Toml.int' "x"@        |++------------------------------+--------------------------------+----------------------------------------------------++| __@'HashMap' 'Int' 'Text'@__ | @x = [{k = 42, v = "foo"}]@    | @'hashMap' ('Toml.int' "k") ('Toml.text' "v") "x"@ |++------------------------------+--------------------------------+----------------------------------------------------++| __@'HashMap' 'Text' 'Int'@__ | @x = {a = 42, b = 11}@         | @'tableHashMap' 'Toml._KeyText' 'Toml.int' "x"@    |++------------------------------+--------------------------------+----------------------------------------------------++| __@'IntMap' 'Text'@__        | @x = [{k = 42, v = "foo"}]@    | @'intMap' ('Toml.int' "k") ('Toml.text' "v") "x"@  |++------------------------------+--------------------------------+----------------------------------------------------++| __@'IntMap' 'Text'@__        | @x = {1 = "one", 2 = "two"}@   | @'tableIntMap' 'Toml._KeyInt' 'Toml.text' "x"@     |++------------------------------+--------------------------------+----------------------------------------------------+++__Note:__ in case of the missing key on the @TOML@ side an empty map structure+is returned.++@since 1.3.0.0+-}++module Toml.Codec.Combinator.Map+    ( -- * 'Map' codecs+      map+    , tableMap+      -- * 'HashMap' codecs+    , hashMap+    , tableHashMap+      -- * 'IntMap' codecs+    , intMap+    , tableIntMap+    ) where++import Prelude hiding (map)++import Control.Applicative (empty)+import Control.Monad (forM_)+import Control.Monad.State (gets, modify)+import Data.Hashable (Hashable)+import Data.HashMap.Strict (HashMap)+import Data.IntMap.Strict (IntMap)+import Data.List.NonEmpty (NonEmpty (..))+import Data.Map.Strict (Map)+import Data.Maybe (fromMaybe)+import Data.Traversable (for)+import Validation (Validation (..))++import Toml.Codec.BiMap (BiMap (..), TomlBiMap)+import Toml.Codec.Code (execTomlCodec)+import Toml.Codec.Combinator.Common (whenLeftBiMapError)+import Toml.Codec.Types (Codec (..), TomlCodec, TomlEnv, TomlState (..))+import Toml.Type.Key (pattern (:||), Key)+import Toml.Type.TOML (TOML (..), insertTable, insertTableArrays)++import qualified Data.HashMap.Strict as HashMap+import qualified Data.IntMap.Strict as IntMap+import qualified Data.List.NonEmpty as NE+import qualified Data.Map.Strict as Map++import qualified Toml.Type.PrefixTree as Prefix+++{- | Bidirectional codec for 'Map'. It takes birectional converter for keys and+values and produces bidirectional codec for 'Map'. Currently it works only with array+of tables, so you need to specify 'Map's in TOML files like this:++@+myMap =+    [ { name = "foo", payload = 42 }+    , { name = "bar", payload = 69 }+    ]+@++'TomlCodec' for such TOML field can look like this:++@+Toml.'map' (Toml.'text' "name") (Toml.'int' "payload") "myMap"+@++If there's no key with the name @"myMap"@ then empty 'Map' is returned.++@since 1.2.1.0+-}+map :: forall k v .+       Ord k+    => TomlCodec k  -- ^ Codec for 'Map' keys+    -> TomlCodec v  -- ^ Codec for 'Map' values+    -> Key          -- ^ TOML key where 'Map' is stored+    -> TomlCodec (Map k v)  -- ^ Codec for the 'Map'+map = internalMap Map.empty Map.toList Map.fromList++{- | This 'TomlCodec' helps you to convert TOML key-value pairs+directly to 'Map' using TOML keys as 'Map' keys. It can be convenient+if your 'Map' keys are types like 'Text' or 'Int' and you want to work with raw+TOML keys directly.++For example, if you have TOML like this:++@+[colours]+yellow = "#FFFF00"+red    = { red = 255, green = 0, blue = 0 }+pink   = "#FFC0CB"+@++You want to convert such TOML configuration into the following Haskell+types:+++@+__data__ Rgb = Rgb+    { rgbRed   :: Int+    , rgbGreen :: Int+    , rgbBlue  :: Int+    }++__data__ Colour+    = Hex Text+    | RGB Rgb++colourCodec :: 'TomlCodec' Colour+colourCodec = ...++__data__ ColourConfig = ColourConfig+    { configColours :: 'Map' 'Text' Colour+    }+@++And you want in the result to have a 'Map' like this:++@+'Map.fromList'+    [ "yellow" -> Hex "#FFFF00"+    , "pink"   -> Hex "#FFC0CB"+    , "red"    -> Rgb 255 0 0+    ]+@++You can use 'tableMap' to define 'TomlCodec' in the following way:++@+colourConfigCodec :: 'TomlCodec' ColourConfig+colourConfigCodec = ColourConfig+    \<$\> Toml.'tableMap' Toml._KeyText colourCodec "colours" .= configColours+@++__Hint:__ You can use 'Toml.Codec.BiMap._KeyText' or+'Toml.Codec.BiMap._KeyString' to convert betwen TOML keys and 'Map'+keys (or you can write your custom 'TomlBiMap').++__NOTE__: Unlike the 'map' codec, this codec is less flexible (i.e. it doesn't+allow to have arbitrary structures as 'Key's, it works only for+text-like keys), but can be helpful if you want to save a few+keystrokes during TOML configuration. A similar TOML configuration,+but suitable for the 'map' codec will look like this:++@+colours =+    [ { key = "yellow", hex = "#FFFF00" }+    , { key = "pink",   hex = "#FFC0CB" }+    , { key = "red",    rgb = { red = 255, green = 0, blue = 0 } }+    ]+@++@since 1.3.0.0+-}+tableMap+    :: forall k v+    .  Ord k+    => TomlBiMap Key k+    -- ^ Bidirectional converter between TOML 'Key's and 'Map' keys+    -> (Key -> TomlCodec v)+    -- ^ Codec for 'Map' values for the corresponding 'Key'+    -> Key+    -- ^ Table name for 'Map'+    -> TomlCodec (Map k v)+tableMap = internalTableMap Map.empty Map.toList Map.fromList++{- | Bidirectional codec for 'HashMap'. It takes birectional converter for keys and+values and produces bidirectional codec for 'HashMap'. It works with array of+tables, so you need to specify 'HashMap's in TOML files like this:++@+myHashMap =+    [ { name = "foo", payload = 42 }+    , { name = "bar", payload = 69 }+    ]+@++'TomlCodec' for such TOML field can look like this:++@+Toml.'hashMap' (Toml.'text' "name") (Toml.'int' "payload") "myHashMap"+@++If there's no key with the name @"myHashMap"@ then empty 'HashMap' is returned.++@since 1.3.0.0+-}+hashMap+    :: forall k v+    .  (Eq k, Hashable k)+    => TomlCodec k  -- ^ Codec for 'HashMap' keys+    -> TomlCodec v  -- ^ Codec for 'HashMap' values+    -> Key          -- ^ TOML key where 'HashMap' is stored+    -> TomlCodec (HashMap k v)  -- ^ Codec for the 'HashMap'+hashMap = internalMap HashMap.empty HashMap.toList HashMap.fromList++{- | This 'TomlCodec' helps to convert TOML key-value pairs+directly to 'HashMap' using TOML keys as 'HashMap' keys.+It can be convenient if your 'HashMap' keys are types like 'Text' or 'Int' and+you want to work with raw TOML keys directly.++For example, if you can write your 'HashMap' in @TOML@ like this:++@+[myHashMap]+key1 = "value1"+key2 = "value2"+@++@since 1.3.0.0+-}+tableHashMap+    :: forall k v+    .  (Eq k, Hashable k)+    => TomlBiMap Key k+    -- ^ Bidirectional converter between TOML 'Key's and 'HashMap' keys+    -> (Key -> TomlCodec v)+    -- ^ Codec for 'HashMap' values for the corresponding 'Key'+    -> Key+    -- ^ Table name for 'HashMap'+    -> TomlCodec (HashMap k v)+tableHashMap = internalTableMap HashMap.empty HashMap.toList HashMap.fromList++{- | Bidirectional codec for 'IntMap'. It takes birectional converter for keys and+values and produces bidirectional codec for 'IntMap'. It works with array of+tables, so you need to specify 'IntMap's in TOML files like this:++@+myIntMap =+    [ { name = "foo", payload = 42 }+    , { name = "bar", payload = 69 }+    ]+@++'TomlCodec' for such TOML field can look like this:++@+Toml.'intMap' (Toml.'text' "name") (Toml.'int' "payload") "myIntMap"+@++If there's no key with the name @"myIntMap"@ then empty 'IntMap' is returned.++@since 1.3.0.0+-}+intMap+    :: forall v+    .  TomlCodec Int  -- ^ Codec for 'IntMap' keys+    -> TomlCodec v  -- ^ Codec for 'IntMap' values+    -> Key          -- ^ TOML key where 'IntMap' is stored+    -> TomlCodec (IntMap v)  -- ^ Codec for the 'IntMap'+intMap = internalMap IntMap.empty IntMap.toList IntMap.fromList++{- | This 'TomlCodec' helps to convert TOML key-value pairs+directly to 'IntMap' using TOML 'Int' keys as 'IntMap' keys.++For example, if you can write your 'IntMap' in @TOML@ like this:++@+[myIntMap]+1 = "value1"+2 = "value2"+@++@since 1.3.0.0+-}+tableIntMap+    :: forall v+    .  TomlBiMap Key Int+    -- ^ Bidirectional converter between TOML 'Key's and 'IntMap' keys+    -> (Key -> TomlCodec v)+    -- ^ Codec for 'IntMap' values for the corresponding 'Key'+    -> Key+    -- ^ Table name for 'IntMap'+    -> TomlCodec (IntMap v)+tableIntMap = internalTableMap IntMap.empty IntMap.toList IntMap.fromList+++----------------------------------------------------------------------------+-- Internal+----------------------------------------------------------------------------++internalMap :: forall map k v+    .  map  -- ^ empty map+    -> (map -> [(k, v)])  -- ^ toList function+    -> ([(k, v)] -> map)  -- ^ fromList function+    -> TomlCodec k  -- ^ Codec for Map keys+    -> TomlCodec v  -- ^ Codec for Map values+    -> Key          -- ^ TOML key where Map is stored+    -> TomlCodec map  -- ^ Codec for the Map+internalMap emptyMap toListMap fromListMap keyCodec valCodec key = Codec input output+  where+    input :: TomlEnv map+    input = \t -> case HashMap.lookup key $ tomlTableArrays t of+        Nothing -> Success emptyMap+        Just tomls -> fmap fromListMap $ for (NE.toList tomls) $ \toml -> do+            k <- codecRead keyCodec toml+            v <- codecRead valCodec toml+            pure (k, v)++    output :: map -> TomlState map+    output dict = do+        let tomls = fmap+                (\(k, v) -> execTomlCodec keyCodec k <> execTomlCodec valCodec v)+                (toListMap dict)++        mTables <- gets $ HashMap.lookup key . tomlTableArrays++        let updateAction :: TOML -> TOML+            updateAction = case mTables of+                Nothing -> case tomls of+                    []   -> id+                    t:ts -> insertTableArrays key (t :| ts)+                Just (t :| ts) ->+                    insertTableArrays key $ t :| (ts ++ tomls)++        dict <$ modify updateAction++internalTableMap+    :: forall map k v+    .  map  -- ^ empty map+    -> (map -> [(k, v)])  -- ^ toList function+    -> ([(k, v)] -> map)  -- ^ fromList function+    -> TomlBiMap Key k+    -- ^ Bidirectional converter between TOML 'Key's and Map keys+    -> (Key -> TomlCodec v)+    -- ^ Codec for Map values for the corresponding 'Key'+    -> Key+    -- ^ Table name for Map+    -> TomlCodec map+internalTableMap emptyMap toListMap fromListMap keyBiMap valCodec tableName = Codec input output+  where+    input :: TomlEnv map+    input = \t -> case Prefix.lookup tableName $ tomlTables t of+        Nothing -> Success emptyMap+        Just toml ->+            let valKeys = HashMap.keys $ tomlPairs toml+                tableKeys = fmap (:|| []) $ HashMap.keys $ tomlTables toml+            in fmap fromListMap $ for (valKeys <> tableKeys) $ \key ->+                whenLeftBiMapError key (forward keyBiMap key) $ \k ->+                    (k,) <$> codecRead (valCodec key) toml++    output :: map -> TomlState map+    output m = do+        mTable <- gets $ Prefix.lookup tableName . tomlTables+        let toml = fromMaybe mempty mTable+        let (_, newToml) = unTomlState updateMapTable toml+        m <$ modify (insertTable tableName newToml)+      where+        updateMapTable :: TomlState ()+        updateMapTable = forM_ (toListMap m) $ \(k, v) -> case backward keyBiMap k of+            Left _    -> empty+            Right key -> codecWrite (valCodec key) v
+ src/Toml/Codec/Combinator/Monoid.hs view
@@ -0,0 +1,114 @@+{- |+Copyright: (c) 2018-2020 Kowainik+SPDX-License-Identifier: MPL-2.0+Maintainer: Kowainik <xrom.xkov@gmail.com>++TOML-specific combinators for converting between TOML and Haskell 'Monoid'+wrapper data types. These codecs are especially handy when you are implementing+the [Partial Options Monoid](https://medium.com/@jonathangfischoff/the-partial-options-monoid-pattern-31914a71fc67)+pattern.+++-----------------------+------------+----------------------------+---------------------++|     Haskell Type      |   @TOML@   |        'TomlCodec'         | Default on          |+|                       |            |                            | missing field       |++=======================+============+============================+=====================++| __'All'__             | @a = true@ | @'all' "a"@                | @'All' 'True'@      |++-----------------------+------------+----------------------------+---------------------++| __'Any'__             | @a = true@ | @'any' "a"@                | @'Any' 'False'@     |++-----------------------+------------+----------------------------+---------------------++| __@'Sum' 'Int'@__     | @a = 11@   | @'sum' 'Toml.int' "a"@     | @'Sum' 0@           |++-----------------------+------------+----------------------------+---------------------++| __@'Product' 'Int'@__ | @a = 11@   | @'product' 'Toml.int' "a"@ | @'Product' 1@       |++-----------------------+------------+----------------------------+---------------------++| __@'First' 'Int'@__   | @a = 42@   | @'first' 'Toml.int' "a"@   | @'First' 'Nothing'@ |++-----------------------+------------+----------------------------+---------------------++| __@'Last' 'Bool'@__   | @a = true@ | @'last' 'Toml.bool' "a"@   | @'Last' 'Nothing'@  |++-----------------------+------------+----------------------------+---------------------+++@since 1.3.0.0+-}++module Toml.Codec.Combinator.Monoid+    ( -- * Codecs for 'Monoid's+      -- ** Bool wrappers+      all+    , any+      -- ** 'Num' wrappers+    , sum+    , product+      -- ** 'Maybe' wrappers+    , first+    , last+    ) where++import Prelude hiding (all, any, last, product, sum)++import Data.Monoid (All (..), Any (..), First (..), Last (..), Product (..), Sum (..))++import Toml.Codec.Combinator.Primitive (bool)+import Toml.Codec.Di (dimap, dioptional, diwrap)+import Toml.Codec.Types (TomlCodec)+import Toml.Type.Key (Key)+++{- | Codec for 'All' wrapper for boolean values.+Returns @'All' 'True'@ on missing fields.++Decodes to @'All' 'True'@ when the key is not present.++@since 1.2.1.0+-}+all :: Key -> TomlCodec All+all = dimap (Just . getAll) (maybe mempty All) . dioptional . bool+{-# INLINE all #-}++{- | Codec for 'Any' wrapper for boolean values.+Returns @'Any' 'False'@ on missing fields.++Decodes to @'Any' 'False'@ when the key is not present.++@since 1.2.1.0+-}+any :: Key -> TomlCodec Any+any = dimap (Just . getAny) (maybe mempty Any) . dioptional . bool+{-# INLINE any #-}++{- | Codec for 'Sum' wrapper for given converter's values.++Decodes to @'Sum' 0@ when the key is not present.++@since 1.2.1.0+-}+sum :: (Num a) => (Key -> TomlCodec a) -> Key -> TomlCodec (Sum a)+sum codec = dimap (Just . getSum) (maybe mempty Sum) . dioptional . codec+{-# INLINE sum #-}++{- | Codec for 'Product' wrapper for given converter's values.++Decodes to @'Product' 1@ when the key is not present.++@since 1.2.1.0+-}+product :: (Num a) => (Key -> TomlCodec a) -> Key -> TomlCodec (Product a)+product codec = dimap (Just . getProduct) (maybe mempty Product) . dioptional . codec+{-# INLINE product #-}++{- | Codec for 'First' wrapper for given converter's values.++Decodes to @'First' 'Nothing'@ when the key is not present.++@since 1.2.1.0+-}+first :: (Key -> TomlCodec a) -> Key -> TomlCodec (First a)+first codec = diwrap . dioptional . codec+{-# INLINE first #-}++{- | Codec for 'Last' wrapper for given converter's values.++Decodes to @'Last' 'Nothing'@ when the key is not present.++@since 1.2.1.0+-}+last :: (Key -> TomlCodec a) -> Key -> TomlCodec (Last a)+last codec = diwrap . dioptional . codec+{-# INLINE last #-}
+ src/Toml/Codec/Combinator/Primitive.hs view
@@ -0,0 +1,205 @@+{- |+Copyright: (c) 2018-2020 Kowainik+SPDX-License-Identifier: MPL-2.0+Maintainer: Kowainik <xrom.xkov@gmail.com>++TOML-specific combinators for converting between TOML and Haskell primitive+types, e.g. 'Int', 'ByteString'.++For the overall picture you can see how different types are represented by+codecs in the following table:+++---------------------------+-----------------+---------------------------++|       Haskell Type        |     @TOML@      |      'TomlCodec'          |++===========================+=================+===========================++| 'Bool'                    | @a = true@      | 'bool' "a"                |++---------------------------+-----------------+---------------------------++| 'Integer'                 | @a = 100@       | 'integer' "a"             |++---------------------------+-----------------+---------------------------++| 'Int'                     | @a = -42@       | 'int' "a"                 |++---------------------------+-----------------+---------------------------++| 'Natural'                 | @a = 11@        | 'natural' "a"             |++---------------------------+-----------------+---------------------------++| 'Word'                    | @a = 1@         | 'word' "a"                |++---------------------------+-----------------+---------------------------++| 'Word8'                   | @a = 1@         | 'word8' "a"               |++---------------------------+-----------------+---------------------------++| 'Double'                  | @a = 36.6@      | 'double' "a"              |++---------------------------+-----------------+---------------------------++| 'Float'                   | @a = -100.09@   | 'float' "a"               |++---------------------------+-----------------+---------------------------++| 'String'                  | @a = \"Hello\"@ | 'string' "a"              |++---------------------------+-----------------+---------------------------++| 'Text'                    | @a = \"Hello\"@ | 'text' "a"                |++---------------------------+-----------------+---------------------------++| Lazy'L.Text'              | @a = \"Hey\"@   | 'lazyText' "a"            |++---------------------------+-----------------+---------------------------++| 'ByteString'              | @a = \"Hello\"@ | 'byteString' "a"          |++---------------------------+-----------------+---------------------------++| Lazy'BL.ByteString'       | @a = \"Hey\"@   | 'lazyByteString' "a"      |++---------------------------+-----------------+---------------------------++| 'ByteString' as Array     | @a = [10, 15]@  | 'byteStringArray' "a"     |++---------------------------+-----------------+---------------------------++| Lazy'ByteString' as Array | @a = [15, 10]@  | 'lazyByteStringArray' "a" |++---------------------------+-----------------+---------------------------+++@since 1.3.0.0+-}++module Toml.Codec.Combinator.Primitive+    ( -- * Boolean+      bool+      -- * Integral numbers+    , integer+    , int+    , natural+    , word+    , word8+      -- * Floating point numbers+    , double+    , float+      -- * Text types+    , string+    , text+    , lazyText+    , byteString+    , lazyByteString+    , byteStringArray+    , lazyByteStringArray+    ) where++import Data.ByteString (ByteString)+import Data.Text (Text)+import Data.Word (Word8)+import Numeric.Natural (Natural)++import Toml.Codec.BiMap.Conversion (_Bool, _ByteString, _ByteStringArray, _Double, _Float, _Int,+                                    _Integer, _LByteString, _LByteStringArray, _LText, _Natural,+                                    _String, _Text, _Word, _Word8)+import Toml.Codec.Combinator.Common (match)+import Toml.Codec.Types (TomlCodec)+import Toml.Type.Key (Key)++import qualified Data.ByteString.Lazy as BL+import qualified Data.Text.Lazy as L+++{- | Codec for boolean values.++@since 0.0.0+-}+bool :: Key -> TomlCodec Bool+bool = match _Bool+{-# INLINE bool #-}++{- | Codec for integer values.++@since 0.1.0+-}+integer :: Key -> TomlCodec Integer+integer = match _Integer+{-# INLINE integer #-}++{- | Codec for integer values.++@since 0.0.0+-}+int :: Key -> TomlCodec Int+int = match _Int+{-# INLINE int #-}++{- | Codec for natural values.++@since 0.5.0+-}+natural :: Key -> TomlCodec Natural+natural = match _Natural+{-# INLINE natural #-}++{- | Codec for word values.++@since 0.5.0+-}+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.++@since 0.0.0+-}+double :: Key -> TomlCodec Double+double = match _Double+{-# INLINE double #-}++{- | Codec for floating point values.++@since 0.5.0+-}+float :: Key -> TomlCodec Float+float = match _Float+{-# INLINE float #-}++{- | Codec for string values.++@since 0.4.0+-}+string :: Key -> TomlCodec String+string = match _String+{-# INLINE string #-}++{- | Codec for text values.++@since 0.3.0+-}+text :: Key -> TomlCodec Text+text = match _Text+{-# INLINE text #-}++{- | Codec for lazy text values.++@since 1.0.0+-}+lazyText :: Key -> TomlCodec L.Text+lazyText = match _LText+{-# INLINE lazyText #-}++{- | Codec for text values as 'ByteString'.++@since 0.5.0+-}+byteString :: Key -> TomlCodec ByteString+byteString = match _ByteString+{-# INLINE byteString #-}++{- | Codec for text values as 'BL.ByteString'.++@since 0.5.0+-}+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 #-}
+ src/Toml/Codec/Combinator/Set.hs view
@@ -0,0 +1,224 @@+{- |+Copyright: (c) 2018-2020 Kowainik+SPDX-License-Identifier: MPL-2.0+Maintainer: Kowainik <xrom.xkov@gmail.com>++TOML-specific combinators for converting between TOML and Haskell Set-like data+types.++There are two way to represent list-like structures with the @tomland@ library.++* Ordinary array sets of primitives:++    @+    foo = [100, 200, 300]+    @++* Sets via tables:++    @+    foo =+        [ {x = 100}+        , {x = 200}+        , {x = 300}+        ]++    __OR__++    [[foo]]+        x = 100+    [[foo]]+        x = 200+    [[foo]]+        x = 300+    @++You can find both types of the codecs in this module for different set-like+structures. See the following table for the better understanding:+++------------------------+----------------------------------+-------------------------------------++|      Haskell Type      |              @TOML@              |             'TomlCodec'             |++========================+==================================+=====================================++| __@'Set' 'Text'@__     | @a = ["foo", "bar", "baz"]@      | @'arraySetOf' 'Toml._Text' "a"@     |++------------------------+----------------------------------+-------------------------------------++| __'IntSet'__           | @a = [11, 42]@                   | @'arrayIntSet' "a"@                 |++------------------------+----------------------------------+-------------------------------------++| __@'HashSet' 'Text'@__ | @a = ["foo", "bar"]@             | @'arrayHashSetOf' 'Toml._Text' "a"@ |++------------------------+----------------------------------+-------------------------------------++| __@'Set' 'Text'@__     | @x = [{a = "foo"}, {a = "bar"}]@ | @'set' ('Toml.text' "a") "x"@       |++------------------------+----------------------------------+-------------------------------------++| __@'HashSet' 'Text'@__ | @x = [{a = "foo"}, {a = "bar"}]@ | @'hashSet' ('Toml.text' "a") "x"@   |++------------------------+----------------------------------+-------------------------------------+++@since 1.3.0.0+-}++module Toml.Codec.Combinator.Set+    ( -- * Array sets+      arraySetOf+    , arrayIntSet+    , arrayHashSetOf+      -- * Table Sets+    , set+    , intSet+    , hashSet+    ) where++import Data.Hashable (Hashable)+import Data.HashSet (HashSet)+import Data.IntSet (IntSet)+import Data.Set (Set)++import Toml.Codec.BiMap (TomlBiMap)+import Toml.Codec.BiMap.Conversion (_HashSet, _IntSet, _Set)+import Toml.Codec.Combinator.Common (match)+import Toml.Codec.Combinator.List (list)+import Toml.Codec.Di (dimap)+import Toml.Codec.Types (TomlCodec)+import Toml.Type.AnyValue (AnyValue (..))+import Toml.Type.Key (Key)++import qualified Data.HashSet as HS+import qualified Data.IntSet as IS+import qualified Data.Set as S+++{- | Codec for sets. Takes converter for single value and+returns a set of values.++__Example:__++Haskell @'Set' 'Int'@ can look like this in your @TOML@ file:++@+foo = [1, 2, 3]+@++In case of the missing field, the following error will be seen:++@+tomland decode error:  Key foo is not found+@++@since 0.5.0+-}+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.++__Example:__++Haskell @'IntSet'@ can look like this in your @TOML@ file:++@+foo = [1, 2, 3]+@++In case of the missing field, the following error will be seen:++@+tomland decode error:  Key foo is not found+@++@since 0.5.0+-}+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.++__Example:__++Haskell @'HashSet' 'Int'@ can look like this in your @TOML@ file:++@+foo = [1, 2, 3]+@++In case of the missing field, the following error will be seen:++@+tomland decode error:  Key foo is not found+@++@since 0.5.0+-}+arrayHashSetOf+    :: (Hashable a, Eq a)+    => TomlBiMap a AnyValue+    -> Key+    -> TomlCodec (HashSet a)+arrayHashSetOf = match . _HashSet+{-# INLINE arrayHashSetOf #-}++----------------------------------------------------------------------------+-- Tables and arrays of tables+----------------------------------------------------------------------------++{- | 'Codec' for set of values. Represented in TOML as array of tables.++__Example:__++Haskell @'Set' 'Int'@ can look like this in your @TOML@ file:++@+foo =+  [ {a = 1}+  , {a = 2}+  ]+@++Decodes to an empty 'Set' in case of the missing field in @TOML@.++@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 'IntSet'. Represented in TOML as an array of tables.++__Example:__++Haskell 'IntSet' can look like this in your @TOML@ file:++@+foo =+  [ {a = 1}+  , {a = 2}+  ]+@++Decodes to an empty 'IntSet' in case of the missing field in @TOML@.++@since 1.3.0.0+-}+intSet :: TomlCodec Int -> Key -> TomlCodec IntSet+intSet codec key = dimap IS.toList IS.fromList (list codec key)+{-# INLINE intSet #-}++{- | 'Codec' for 'HashSet' of values. Represented in TOML as an array of tables.++__Example:__++Haskell @'HashSet' 'Int'@ can look like this in your @TOML@ file:++@+foo =+  [ {a = 1}+  , {a = 2}+  ]+@++Decodes to an empty 'HashSet' in case of the missing field in @TOML@.++@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/Codec/Combinator/Table.hs view
@@ -0,0 +1,92 @@+{- |+Copyright: (c) 2018-2020 Kowainik+SPDX-License-Identifier: MPL-2.0+Maintainer: Kowainik <xrom.xkov@gmail.com>++Contains TOML-specific combinators for converting between TOML and user data+types.++Tables can be represented in @TOML@ in one of the following ways:++@+foo =+    { x = ...+    , y = ...+    , ...+    }+@++__Or__++@+[foo]+    x = ...+    y = ...+    ...+@++@since 1.3.0.0+-}++module Toml.Codec.Combinator.Table+    ( -- * Tables+      table+      -- * Error Helpers+    , handleTableErrors+    , mapTableErrors+    ) where++import Control.Monad.State (gets, modify)+import Data.Maybe (fromMaybe)+import Validation (Validation (..))++import Toml.Codec.Error (TomlDecodeError (..))+import Toml.Codec.Types (Codec (..), TomlCodec, TomlEnv, TomlState (..))+import Toml.Type.Key (Key)+import Toml.Type.TOML (TOML (..), insertTable)++import qualified Toml.Type.PrefixTree as Prefix++++{- | Maps errors in tables with 'mapTableErrors'++@since 1.3.0.0+-}+handleTableErrors :: TomlCodec a -> Key -> TOML -> Validation [TomlDecodeError] a+handleTableErrors codec key toml = case codecRead codec toml of+    Success res  -> Success res+    Failure errs -> Failure $ mapTableErrors key errs++{- | 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+key, not only the last one.++@since 0.2.0+-}+mapTableErrors :: Key -> [TomlDecodeError] -> [TomlDecodeError]+mapTableErrors key = map (\case+    KeyNotFound name        -> KeyNotFound (key <> name)+    TableNotFound name      -> TableNotFound (key <> name)+    TableArrayNotFound name -> TableArrayNotFound (key <> name)+    e                       -> e+    )++{- | Codec for tables. Use it when when you have nested objects.++@since 0.2.0+-}+table :: forall a . TomlCodec a -> Key -> TomlCodec a+table codec key = Codec input output+  where+    input :: TomlEnv a+    input = \t -> case Prefix.lookup key $ tomlTables t of+        Nothing   -> Failure [TableNotFound key]+        Just toml -> handleTableErrors codec key toml++    output :: a -> TomlState a+    output a = do+        mTable <- gets $ Prefix.lookup key . tomlTables+        let toml = fromMaybe mempty mTable+        let (_, newToml) = unTomlState (codecWrite codec a) toml+        a <$ modify (insertTable key newToml)
+ src/Toml/Codec/Combinator/Time.hs view
@@ -0,0 +1,71 @@+{- |+Copyright: (c) 2018-2020 Kowainik+SPDX-License-Identifier: MPL-2.0+Maintainer: Kowainik <xrom.xkov@gmail.com>++TOML-specific combinators for converting between TOML and Haskell date and time+data types. TOML specification describes date and time primitives you+can use in your configuration. @tomland@ provides mapping of those+primitives to types from the @time@ library.+++-----------------+----------------------------+-------------------++|  Haskell Type   |           @TOML@           |    'TomlCodec'    |++=================+============================+===================++| __'ZonedTime'__ | @a = 2020-05-16T04:32:00Z@ | @'zonedTime' "a"@ |++-----------------+----------------------------+-------------------++| __'LocalTime'__ | @a = 2020-05-16T04:32:00@  | @'localTime' "a"@ |++-----------------+----------------------------+-------------------++| __'Day'__       | @a = 2020-05-16@           | @'day' "a"@       |++-----------------+----------------------------+-------------------++| __'TimeOfDay'__ | @a = 04:32:00@             | @'timeOfDay' "a"@ |++-----------------+----------------------------+-------------------+++@since 1.3.0.0+-}++module Toml.Codec.Combinator.Time+    ( zonedTime+    , localTime+    , day+    , timeOfDay+    ) where++import Data.Time (Day, LocalTime, TimeOfDay, ZonedTime)++import Toml.Codec.BiMap.Conversion (_Day, _LocalTime, _TimeOfDay, _ZonedTime)+import Toml.Codec.Combinator.Common (match)+import Toml.Codec.Types (TomlCodec)+import Toml.Type.Key (Key)+++{- | Codec for zoned time values.++@since 0.5.0+-}+zonedTime :: Key -> TomlCodec ZonedTime+zonedTime = match _ZonedTime+{-# INLINE zonedTime #-}++{- | Codec for local time values.++@since 0.5.0+-}+localTime :: Key -> TomlCodec LocalTime+localTime = match _LocalTime+{-# INLINE localTime #-}++{- | Codec for day values.++@since 0.5.0+-}+day :: Key -> TomlCodec Day+day = match _Day+{-# INLINE day #-}++{- | Codec for time of day values.++@since 0.5.0+-}+timeOfDay :: Key -> TomlCodec TimeOfDay+timeOfDay = match _TimeOfDay+{-# INLINE timeOfDay #-}
+ src/Toml/Codec/Combinator/Tuple.hs view
@@ -0,0 +1,100 @@+{- |+Copyright: (c) 2018-2020 Kowainik+SPDX-License-Identifier: MPL-2.0+Maintainer: Kowainik <xrom.xkov@gmail.com>++TOML-specific combinators for converting between TOML and Haskell tuples.+It's recommended to create your custom data types and implement codecs+for them, but if you need to have tuples (e.g. for decoding different+constructors of sum types), you can find codecs from this module+helpful.+++-------------------------------+---------------+------------------------++|         Haskell Type          |    @TOML@     |   'TomlCodec'          |++===============================+===============+========================++| __@('Int', 'Text')@__         | @[foo]@       | @'pair'@               |++-------------------------------+---------------+------------------------++|                               | @    a = 42@  | @    ('Toml.int' "a")@ |++-------------------------------+---------------+------------------------++|                               |@    b = "bar"@| @    ('Toml.text' "b")@|++-------------------------------+---------------+------------------------++| __@('Int', 'Text', 'Bool')@__ | @[foo]@       | @'triple'@             |++-------------------------------+---------------+------------------------++|                               | @    a = 42@  | @    ('Toml.int' "a")@ |++-------------------------------+---------------+------------------------++|                               |@    b = "bar"@| @    ('Toml.text' "b")@|++-------------------------------+---------------+------------------------++|                               |@    c = false@| @    ('Toml.bool' "c")@|++-------------------------------+---------------+------------------------+++@since 1.3.0.0+-}++module Toml.Codec.Combinator.Tuple+    ( pair+    , triple+    ) where++import Toml.Codec.Di ((.=))+import Toml.Codec.Types (TomlCodec)+++{- | Codec for pair of values. Takes codecs for the first and for the second+values of the pair.++If I have the following @TOML@ entry++@+myPair = { first = 11, second = "eleven"}+@++and want to convert it into the Haskell tuple of two elements, I can use the+following codec:++@+myPairCodec :: 'TomlCodec' ('Int', 'Text')+myPairCodec = flip Toml.'table' \"myPair\" $ Toml.'pair'+    (Toml.'int' \"first\")+    (Toml.'text' \"second\")+@++@since 1.3.0.0+-}+pair :: TomlCodec a -> TomlCodec b -> TomlCodec (a, b)+pair aCodec bCodec = (,)+    <$> aCodec .= fst+    <*> bCodec .= snd+{-# INLINE pair #-}++{- | Codec for triple of values. Takes codecs for the first, second and third+values of the triple.++If I have the following @TOML@ entry++@+myTriple =+    { first = 11+    , second = "eleven"+    , isMyFavourite = true+    }+@++and want to convert it into the Haskell tuple of three elements, I can use the+following codec:++@+myTripleCodec :: 'TomlCodec' ('Int', 'Text', 'Bool')+myTripleCodec = flip Toml.'table' \"myTriple\" $ Toml.'triple'+    (Toml.'int' \"first\")+    (Toml.'text' \"second\")+    (Toml.'bool' \"isMyFavourite\")+@++@since 1.3.0.0+-}+triple :: TomlCodec a -> TomlCodec b -> TomlCodec c -> TomlCodec (a, b, c)+triple aCodec bCodec cCodec = (,,)+    <$> aCodec .= (\(a, _, _) -> a)+    <*> bCodec .= (\(_, b, _) -> b)+    <*> cCodec .= (\(_, _, c) -> c)+{-# INLINE triple #-}
+ src/Toml/Codec/Di.hs view
@@ -0,0 +1,210 @@+{- |+Copyright: (c) 2018-2020 Kowainik+SPDX-License-Identifier: MPL-2.0+Maintainer: Kowainik <xrom.xkov@gmail.com>++Forward and backward mapping functions and combinators (similar to profunctors).++@since 1.3.0.0+-}++module Toml.Codec.Di+    ( dimap+    , dioptional+    , diwrap+    , dimatch+    , (.=)+    ) where++import Control.Applicative (Alternative (..))+import Data.Coerce (Coercible, coerce)++import Toml.Codec.Types (Codec (..), TomlCodec, (<!>))+++{- | 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+want to bring extra dependencies) this instance is implemented as a single+top-level function.++Useful when you want to parse @newtype@s. For example, if you had data type like+this:++@+__data__ Example = Example+    { foo :: Bool+    , bar :: Text+    }+@++Bidirectional TOML converter for this type will look like this:++@+exampleCodec :: TomlCodec Example+exampleCodec = Example+    \<$\> Toml.bool "foo" '.=' foo+    \<*\> Toml.text "bar" '.=' bar+@++Now if you change your type in the following way:++@+__newtype__ Email = Email { unEmail :: Text }++__data__ Example = Example+    { foo :: Bool+    , bar :: Email+    }+@++you need to patch your TOML codec like this:++@+exampleCodec :: TomlCodec Example+exampleCodec = Example+    \<$\> Toml.bool "foo" '.=' foo+    \<*\> 'dimap' unEmail Email (Toml.text "bar") '.=' bar+@++@since 0.2.0+-}+dimap+    :: (b -> a)    -- ^ Mapper for consumer+    -> (a -> b)  -- ^ Mapper for producer+    -> TomlCodec a  -- ^ Source 'Codec' object+    -> TomlCodec b  -- ^ Target 'Codec' object+dimap f g codec = Codec+    { codecRead  = fmap g . codecRead codec+    , codecWrite = fmap g . codecWrite codec . f+    }+{-# INLINE dimap #-}++{- | Bidirectional converter for @Maybe a@ values. For example, given the data+type:++@+__data__ Example = Example+    { foo :: Bool+    , bar :: Maybe Int+    }+@++the TOML codec will look like++@+exampleCodec :: TomlCodec Example+exampleCodec = Example+    \<$\> Toml.bool "foo" '.=' foo+    \<*\> 'dioptional' (Toml.int "bar") '.=' bar+@++@since 0.5.0+-}+dioptional+    :: TomlCodec a+    -> TomlCodec (Maybe a)+dioptional Codec{..} = Codec+    { codecRead  = fmap Just . codecRead <!> \_ -> pure Nothing+    , codecWrite = traverse codecWrite+    }+{-# INLINE dioptional #-}++{- | Combinator used for @newtype@ wrappers. For example, given the data types:++@+__newtype__ N = N Int++__data__ Example = Example+    { foo :: Bool+    , bar :: N+    }+@++the TOML codec can look like++@+exampleCodec :: TomlCodec Example+exampleCodec = Example+    \<$\> Toml.bool "foo" '.=' foo+    \<*\> 'diwrap' (Toml.int "bar") '.=' bar+@++@since 1.0.0+-}+diwrap+    :: forall b a .+       (Coercible a b)+    => TomlCodec a+    -> TomlCodec b+diwrap = 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.pair+    (Toml.bool "a")+    (Toml.int "b")++exampleCodec :: TomlCodec Example+exampleCodec =+    dimatch matchFoo Foo (Toml.int "foo")+    \<|\> dimatch matchBar (uncurry Bar) (Toml.table barCodec "bar")+@++@since 1.2.0.0+-}+dimatch+    :: (b -> Maybe a)  -- ^ Mapper for consumer+    -> (a -> b)     -- ^ Mapper for producer+    -> TomlCodec a  -- ^ Source 'Codec' object+    -> TomlCodec b  -- ^ Target 'Codec' object+dimatch match ctor codec = Codec+    { codecRead = fmap 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?+2. How to write this field to toml?++In code this should be used like this:++@+__data__ Foo = Foo+    { fooBar :: Int+    , fooBaz :: String+    }++fooCodec :: TomlCodec Foo+fooCodec = Foo+    \<$\> Toml.int "bar" '.=' fooBar+    \<*\> Toml.str "baz" '.=' fooBaz+@+-}+infixl 5 .=+(.=) :: Codec field a -> (object -> field) -> Codec object a+codec .= getter = codec { codecWrite = codecWrite codec . getter }+{-# INLINE (.=) #-}
+ src/Toml/Codec/Error.hs view
@@ -0,0 +1,85 @@+{-# LANGUAGE DeriveAnyClass #-}++{- |+Copyright: (c) 2018-2020 Kowainik+SPDX-License-Identifier: MPL-2.0+Maintainer: Kowainik <xrom.xkov@gmail.com>++Core error types, including 'TomlDecodeError' and 'LoadTomlException'.++@since 1.3.0.0+-}++module Toml.Codec.Error+    ( TomlDecodeError (..)+    , prettyTomlDecodeErrors+    , prettyTomlDecodeError++    , LoadTomlException (..)+    ) where++import Control.DeepSeq (NFData)+import Control.Exception (Exception)+import Data.Text (Text)+import GHC.Generics (Generic)++import Toml.Codec.BiMap (TomlBiMapError, prettyBiMapError)+import Toml.Parser (TomlParseError (..))+import Toml.Type.Key (Key (..))+import Toml.Type.Printer (prettyKey)++import qualified Data.Text as Text+++{- | Type of exception for converting from TOML to user custom data type.++@since 1.3.0.0+-}+data TomlDecodeError+    = BiMapError !Key !TomlBiMapError+    | KeyNotFound !Key  -- ^ No such key+    | TableNotFound !Key  -- ^ No such table+    | TableArrayNotFound !Key+      {- ^ No such table array++      @since 1.3.0.0+      -}+    | ParseError !TomlParseError  -- ^ Exception during parsing+    deriving stock (Show, Eq, Generic)+    deriving anyclass (NFData)++{- | Converts 'TomlDecodeError's into pretty human-readable text.++@since 1.3.0.0+-}+prettyTomlDecodeErrors :: [TomlDecodeError] -> Text+prettyTomlDecodeErrors errs = Text.unlines $+    ("tomland errors number: " <> Text.pack (show $ length errs))+    : map prettyTomlDecodeError errs++{- | Converts 'TomlDecodeError' into pretty human-readable text.++@since 1.3.0.0+-}+prettyTomlDecodeError :: TomlDecodeError -> Text+prettyTomlDecodeError de = "tomland decode error:  " <> case de of+    BiMapError name biError -> "BiMap error in key '" <> prettyKey name <> "' : "+        <> prettyBiMapError biError+    KeyNotFound name -> "Key " <> prettyKey name <> " is not found"+    TableNotFound name -> "Table [" <> prettyKey name <> "] is not found"+    TableArrayNotFound name -> "Table array [[" <> prettyKey name <> "]] is not found"+    ParseError (TomlParseError msg) ->+        "Parse error during conversion from TOML to custom user type: \n  " <> msg++{- | File loading error data type.++@since 0.3.1+-}+data LoadTomlException = LoadTomlException !FilePath !Text++-- | @since 0.3.1+instance Show LoadTomlException where+    show (LoadTomlException filePath msg) = "Couldnt parse file " ++ filePath ++ ": " ++ show msg++-- | @since 0.3.1+instance Exception LoadTomlException
+ src/Toml/Codec/Generic.hs view
@@ -0,0 +1,776 @@+{-# LANGUAGE AllowAmbiguousTypes  #-}+{-# LANGUAGE DataKinds            #-}+{-# LANGUAGE FlexibleContexts     #-}+{-# LANGUAGE FlexibleInstances    #-}+{-# LANGUAGE GADTs                #-}+{-# LANGUAGE PolyKinds            #-}+{-# LANGUAGE Rank2Types           #-}+{-# LANGUAGE TypeOperators        #-}+{-# LANGUAGE TypeSynonymInstances #-}+{-# LANGUAGE UndecidableInstances #-}++{- |+Copyright: (c) 2018-2020 Kowainik+SPDX-License-Identifier: MPL-2.0+Maintainer: Kowainik <xrom.xkov@gmail.com>++This module contains implementation of the 'Generic' TOML codec. If your+data types are big and nested, and you want to have codecs for them without writing a lot of+boilerplate code, you can find this module helpful. Below you can find the detailed+explanation on how the 'Generic' codecs work.++Consider the following Haskell data types:++@+__data__ User = User+    { age     :: Int+    , address :: Address+    , socials :: [Social]+    } __deriving__ ('Generic')++__data__ Address = Address+    { street :: Text+    , house  :: Int+    } __deriving__ ('Generic')++__data__ Social = Social+    { name :: Text+    , link :: Text+    } __deriving__ ('Generic')+@++Value of the @User@ type represents the following TOML:++@+age = 27++[address]+  street = "Miami Beach"+  house  = 42++[[socials]]+  name = \"Twitter\"+  link = "https://twitter.com/foo"++[[socials]]+  name = \"GitHub\"+  link = "https://github.com/bar"+@++Normally you would write 'TomlCodec' for this data type like this:++@+userCodec :: 'TomlCodec' User+userCodec = User+    \<$\> Toml.int "age" .= age+    \<*\> Toml.table addressCodec "address" .= address+    \<*\> Toml.list  socialCodec  "socials" .= socials++addressCodec :: 'TomlCodec' Address+addressCodec = Address+    \<$\> Toml.text "street" .= street+    \<*\> Toml.int  "house"  .= house++socialCodec :: 'TomlCodec' Social+socialCodec = Social+    \<$\> Toml.text "name" .= name+    \<*\> Toml.text "link" .= link+@++However, if you derive 'Generic' instance for your data types (as we do in the+example), you can write your codecs in a simpler way.++@+userCodec :: 'TomlCodec' User+userCodec = 'genericCodec'++__instance__ 'HasCodec' Address __where__+    hasCodec = Toml.table 'genericCodec'++__instance__ 'HasItemCodec' Social __where__+    hasItemCodec = Right 'genericCodec'+@++Several notes about the interface:++1. Your top-level data types are always implemented as 'genericCodec' (or other+generic codecs).+2. If you have a custom data type as a field of another type, you need to implement+the instance of the 'HasCodec' typeclass.+3. If the data type appears as an element of a list, you need to implement the instance+of the 'HasItemCodec' typeclass.++@since 1.1.0.0+-}++module Toml.Codec.Generic+       ( genericCodec+       , genericCodecWithOptions+       , stripTypeNameCodec++         -- * Options+       , TomlOptions (..)+       , GenericOptions (..)+       , stripTypeNameOptions+       , stripTypeNamePrefix++         -- * Core generic typeclass+       , HasCodec (..)+       , HasItemCodec (..)+       , GenericCodec (..)++         -- * 'ByteString' newtypes+         -- $bytestring+       , ByteStringAsText (..)+       , ByteStringAsBytes (..)+       , LByteStringAsText (..)+       , LByteStringAsBytes (..)++         -- * Deriving Via+       , TomlTable (..)+       ) where++import Data.ByteString (ByteString)+import Data.Char (isLower, toLower)+import Data.Coerce (coerce)+import Data.Hashable (Hashable)+import Data.HashMap.Strict (HashMap)+import Data.HashSet (HashSet)+import Data.IntMap.Strict (IntMap)+import Data.IntSet (IntSet)+import Data.Kind (Type)+import Data.List (stripPrefix)+import Data.List.NonEmpty (NonEmpty)+import Data.Map.Strict (Map)+import Data.Monoid (All (..), Any (..), First (..), Last (..), Product (..), Sum (..))+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 (Word8)+import GHC.Generics ((:*:) (..), (:+:), C1, D1, Generic (..), K1 (..), M1 (..), Rec0, S1,+                     Selector (..))+import GHC.TypeLits (ErrorMessage (..), TypeError)+import Numeric.Natural (Natural)++import Toml.Codec.BiMap (TomlBiMap)+import Toml.Codec.Di (diwrap, (.=))+import Toml.Codec.Types (TomlCodec)+import Toml.Type.AnyValue (AnyValue)+import Toml.Type.Key (Key)++import qualified Data.ByteString.Lazy as LBS+import qualified Data.Text.Lazy as L++import qualified Toml.Codec.BiMap.Conversion as Toml+import qualified Toml.Codec.Combinator as Toml+import qualified Toml.Codec.Di as Toml+++{- | Generic codec for arbitrary data types. Uses field names as keys.++@since 1.1.0.0+-}+genericCodec :: (Generic a, GenericCodec (Rep a)) => TomlCodec a+genericCodec = Toml.dimap from to $ genericTomlCodec (GenericOptions id)+{-# INLINE genericCodec #-}++{- | Generic codec with options for arbitrary data types.++@since 1.1.0.0+-}+genericCodecWithOptions+    :: forall a+     . (Generic a, GenericCodec (Rep a), Typeable a)+    => TomlOptions a+    -> TomlCodec a+genericCodecWithOptions = Toml.dimap from to . genericTomlCodec . toGenericOptions @a+{-# INLINE genericCodecWithOptions #-}++{- | Generic codec that uses 'stripTypeNameOptions'.++@since 1.1.0.0+-}+stripTypeNameCodec+    :: forall a+     . (Generic a, GenericCodec (Rep a), Typeable a)+    => TomlCodec a+stripTypeNameCodec = genericCodecWithOptions $ stripTypeNameOptions @a+{-# INLINE stripTypeNameCodec #-}++----------------------------------------------------------------------------+-- Generic typeclasses+----------------------------------------------------------------------------++{- | Options to configure various parameters of generic encoding. Specifically:++*  __'tomlOptionsFieldModifier'__: how to translate field names to TOML keys?++@since 1.1.0.0+-}+data TomlOptions a = TomlOptions+    { tomlOptionsFieldModifier :: Typeable a => Proxy a -> String -> String+    }++{- | Same as 'TomlOptions' but with all data type information erased. This data+type is used internally. Define your options using 'TomlOptions' data type.++@since 1.1.0.0+-}+newtype GenericOptions = GenericOptions+    { genericOptionsFieldModifier :: String -> String+    }++toGenericOptions :: forall a . Typeable a => TomlOptions a -> GenericOptions+toGenericOptions TomlOptions{..} = GenericOptions+    { genericOptionsFieldModifier = tomlOptionsFieldModifier (Proxy @a)+    }++{- | Options that use 'stripTypeNamePrefix' as 'tomlOptionsFieldModifier'.++@since 1.1.0.0+-}+stripTypeNameOptions :: Typeable a => TomlOptions a+stripTypeNameOptions = TomlOptions+    { tomlOptionsFieldModifier = stripTypeNamePrefix+    }++{- | Strips name of the type name from field name prefix.++>>> data UserData = UserData { userDataId :: Int, userDataShortInfo :: Text }+>>> stripTypeNamePrefix (Proxy @UserData) "userDataId"+"id"+>>> stripTypeNamePrefix (Proxy @UserData) "userDataShortInfo"+"shortInfo"+>>> stripTypeNamePrefix (Proxy @UserData) "udStats"+"stats"+>>> stripTypeNamePrefix (Proxy @UserData) "fooBar"+"bar"+>>> stripTypeNamePrefix (Proxy @UserData) "name"+"name"++@since 1.1.0.0+-}+stripTypeNamePrefix :: forall a . Typeable a => Proxy a -> String -> String+stripTypeNamePrefix _ fieldName =+    case stripPrefix (headToLower $ typeName @a) fieldName of+        Just rest -> leaveIfEmpty rest+        Nothing   -> leaveIfEmpty (dropWhile isLower fieldName)+  where+    headToLower :: String -> String+    headToLower = \case+        []   -> error "Cannot use 'headToLower' on empty Text"+        x:xs -> toLower x : xs++    -- if all lower case then leave field as it is+    leaveIfEmpty :: String -> String+    leaveIfEmpty rest = if null rest then fieldName else headToLower rest++typeName :: forall a . Typeable a => String+typeName = show $ typeRep (Proxy @a)++----------------------------------------------------------------------------+-- Generic typeclasses+----------------------------------------------------------------------------++{- | Helper class to derive TOML codecs generically.++@since 1.1.0.0+-}+class GenericCodec (f :: k -> Type) where+    genericTomlCodec :: GenericOptions -> TomlCodec (f p)++-- | @since 1.1.0.0+instance GenericCodec f => GenericCodec (D1 d f) where+    genericTomlCodec = Toml.dimap unM1 M1 . genericTomlCodec+    {-# INLINE genericTomlCodec #-}++type GenericSumTomlNotSupported =+    'Text "Generic TOML deriving for arbitrary sum types is not supported currently."++-- | @since 1.1.0.0+instance (TypeError GenericSumTomlNotSupported) => GenericCodec (f :+: g) where+    genericTomlCodec = error "Not supported"++-- | @since 1.1.0.0+instance GenericCodec f => GenericCodec (C1 c f) where+    genericTomlCodec = Toml.dimap unM1 M1 . genericTomlCodec+    {-# INLINE genericTomlCodec #-}++-- | @since 1.1.0.0+instance (GenericCodec f, GenericCodec g) => GenericCodec (f :*: g) where+    genericTomlCodec options = (:*:)+        <$> genericTomlCodec options .= fstG+        <*> genericTomlCodec options .= sndG+      where+        fstG :: (f :*: g) p -> f p+        fstG (f :*: _) = f++        sndG :: (f :*: g) p -> g p+        sndG (_ :*: g) = g+    {-# INLINE genericTomlCodec #-}++-- | @since 1.1.0.0+instance (Selector s, HasCodec a) => GenericCodec (S1 s (Rec0 a)) where+    genericTomlCodec GenericOptions{..} = genericWrap $ hasCodec @a fieldName+      where+        genericWrap :: TomlCodec a -> TomlCodec (S1 s (Rec0 a) p)+        genericWrap = Toml.dimap (unK1 . unM1) (M1 . K1)++        fieldName :: Key+        fieldName =+            fromString+            $ genericOptionsFieldModifier+            $ selName (error "S1" :: S1 s Proxy ())+    {-# INLINE genericTomlCodec #-}++----------------------------------------------------------------------------+-- Helper typeclasses+----------------------------------------------------------------------------++{- | This typeclass tells how the data type should be coded as an item of a+list. Lists in TOML can have two types: __primitive__ and __table of arrays__.++* If 'hasItemCodec' returns 'Left': __primitive__ arrays codec is used.+* If 'hasItemCodec' returns 'Right:' __table of arrays__ codec is used.++@since 1.1.0.0+-}+class HasItemCodec a where+    hasItemCodec :: Either (TomlBiMap a AnyValue) (TomlCodec a)++-- | @since 1.1.0.0+instance HasItemCodec Bool where+    hasItemCodec = Left Toml._Bool+    {-# INLINE hasItemCodec #-}++-- | @since 1.1.0.0+instance HasItemCodec Int where+    hasItemCodec = Left Toml._Int+    {-# INLINE hasItemCodec #-}++-- | @since 1.1.0.0+instance HasItemCodec Word where+    hasItemCodec = Left Toml._Word+    {-# INLINE hasItemCodec #-}++-- | @since 1.2.0.0+instance HasItemCodec Word8 where+    hasItemCodec = Left Toml._Word8+    {-# INLINE hasItemCodec #-}++-- | @since 1.1.0.0+instance HasItemCodec Integer where+    hasItemCodec = Left Toml._Integer+    {-# INLINE hasItemCodec #-}++-- | @since 1.1.0.0+instance HasItemCodec Natural where+    hasItemCodec = Left Toml._Natural+    {-# INLINE hasItemCodec #-}++-- | @since 1.1.0.0+instance HasItemCodec Double where+    hasItemCodec = Left Toml._Double+    {-# INLINE hasItemCodec #-}++-- | @since 1.1.0.0+instance HasItemCodec Float where+    hasItemCodec = Left Toml._Float+    {-# INLINE hasItemCodec #-}++-- | @since 1.1.0.0+instance HasItemCodec Text where+    hasItemCodec = Left Toml._Text+    {-# INLINE hasItemCodec #-}++-- | @since 1.1.0.0+instance HasItemCodec L.Text where+    hasItemCodec = Left Toml._LText+    {-# INLINE hasItemCodec #-}++-- | @since 1.3.0.0+instance HasItemCodec ByteStringAsText where+    hasItemCodec = Left $ coerce Toml._ByteString+    {-# INLINE hasItemCodec #-}++-- | @since 1.3.0.0+instance HasItemCodec ByteStringAsBytes where+    hasItemCodec = Left $ coerce Toml._ByteStringArray+    {-# INLINE hasItemCodec #-}++-- | @since 1.3.0.0+instance HasItemCodec LByteStringAsText where+    hasItemCodec = Left $ coerce Toml._LByteString+    {-# INLINE hasItemCodec #-}++-- | @since 1.3.0.0+instance HasItemCodec LByteStringAsBytes where+    hasItemCodec = Left $ coerce Toml._LByteStringArray+    {-# INLINE hasItemCodec #-}++-- | @since 1.1.0.0+instance HasItemCodec ZonedTime where+    hasItemCodec = Left Toml._ZonedTime+    {-# INLINE hasItemCodec #-}++-- | @since 1.1.0.0+instance HasItemCodec LocalTime where+    hasItemCodec = Left Toml._LocalTime+    {-# INLINE hasItemCodec #-}++-- | @since 1.1.0.0+instance HasItemCodec Day where+    hasItemCodec = Left Toml._Day+    {-# INLINE hasItemCodec #-}++-- | @since 1.1.0.0+instance HasItemCodec TimeOfDay where+    hasItemCodec = Left Toml._TimeOfDay+    {-# INLINE hasItemCodec #-}++-- | @since 1.1.0.0+instance HasItemCodec IntSet where+    hasItemCodec = Left Toml._IntSet+    {-# INLINE hasItemCodec #-}++{- | If data type @a@ is not primitive then this instance returns codec for list+under key equal to @a@ type name.++@since 1.1.0.0+-}+instance (HasItemCodec a, Typeable a) => HasItemCodec [a] where+    hasItemCodec = case hasItemCodec @a of+        Left prim   -> Left $ Toml._Array prim+        Right codec -> Right $ Toml.list codec (fromString $ typeName @a)+    {-# INLINE hasItemCodec #-}++{- | Helper typeclass for generic deriving. This instance tells how the data+type should be coded if it's a field of another data type.++__NOTE:__ If you implement TOML codecs for your data types manually, prefer more+explicit @Toml.int@ or @Toml.text@ instead of implicit @Toml.hasCodec@.+Implement instances of this typeclass only when using 'genericCodec' and when+your custom data types are not covered here.++@since 1.1.0.0+-}+class HasCodec a where+    hasCodec :: Key -> TomlCodec a++-- | @since 1.1.0.0+instance HasCodec Bool where+    hasCodec = Toml.bool+    {-# INLINE hasCodec #-}++-- | @since 1.1.0.0+instance HasCodec Int where+    hasCodec = Toml.int+    {-# INLINE hasCodec #-}++-- | @since 1.1.0.0+instance HasCodec Word where+    hasCodec = Toml.word+    {-# INLINE hasCodec #-}++-- | @since 1.2.0.0+instance HasCodec Word8 where+    hasCodec = Toml.word8+    {-# INLINE hasCodec #-}++-- | @since 1.1.0.0+instance HasCodec Integer where+    hasCodec = Toml.integer+    {-# INLINE hasCodec #-}++-- | @since 1.1.0.0+instance HasCodec Natural where+    hasCodec = Toml.natural+    {-# INLINE hasCodec #-}++-- | @since 1.1.0.0+instance HasCodec Double where+    hasCodec = Toml.double+    {-# INLINE hasCodec #-}++-- | @since 1.1.0.0+instance HasCodec Float where+    hasCodec = Toml.float+    {-# INLINE hasCodec #-}++-- | @since 1.1.0.0+instance HasCodec Text where+    hasCodec = Toml.text+    {-# INLINE hasCodec #-}++-- | @since 1.1.0.0+instance HasCodec L.Text where+    hasCodec = Toml.lazyText+    {-# INLINE hasCodec #-}++-- | @since 1.3.0.0+instance HasCodec ByteStringAsText where+    hasCodec = diwrap . Toml.byteString+    {-# INLINE hasCodec #-}++-- | @since 1.3.0.0+instance HasCodec ByteStringAsBytes where+    hasCodec = diwrap . Toml.byteStringArray+    {-# INLINE hasCodec #-}++-- | @since 1.3.0.0+instance HasCodec LByteStringAsText where+    hasCodec = diwrap . Toml.lazyByteString+    {-# INLINE hasCodec #-}++-- | @since 1.3.0.0+instance HasCodec LByteStringAsBytes where+    hasCodec = diwrap . Toml.lazyByteStringArray+    {-# INLINE hasCodec #-}++-- | @since 1.1.0.0+instance HasCodec ZonedTime where+    hasCodec = Toml.zonedTime+    {-# INLINE hasCodec #-}++-- | @since 1.1.0.0+instance HasCodec LocalTime where+    hasCodec = Toml.localTime+    {-# INLINE hasCodec #-}++-- | @since 1.1.0.0+instance HasCodec Day where+    hasCodec = Toml.day+    {-# INLINE hasCodec #-}++-- | @since 1.1.0.0+instance HasCodec TimeOfDay where+    hasCodec = Toml.timeOfDay+    {-# INLINE hasCodec #-}++-- | @since 1.1.0.0+instance HasCodec IntSet where+    hasCodec = Toml.arrayIntSet+    {-# INLINE hasCodec #-}++-- | @since 1.1.0.0+instance HasCodec a => HasCodec (Maybe a) where+    hasCodec = Toml.dioptional . hasCodec @a+    {-# INLINE hasCodec #-}++-- | @since 1.1.0.0+instance HasItemCodec a => HasCodec [a] where+    hasCodec = case hasItemCodec @a of+        Left prim   -> Toml.arrayOf prim+        Right codec -> Toml.list codec+    {-# INLINE hasCodec #-}++-- | @since 1.1.0.0+instance HasItemCodec a => HasCodec (NonEmpty a) where+    hasCodec = case hasItemCodec @a of+        Left prim   -> Toml.arrayNonEmptyOf prim+        Right codec -> Toml.nonEmpty codec+    {-# INLINE hasCodec #-}++-- | @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+    {-# INLINE hasCodec #-}++-- | @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+    {-# INLINE hasCodec #-}++{- | Encodes 'Map' as array of tables with the @key@ and @val@ TOML+key names for 'Map' keys and values. E.g. if you have a type+@'Map' 'Int' 'Text'@, the 'HasCodec' instance for 'Generic' deriving+will work with the following TOML representation:++@+fieldName =+    [ { key = 10, val = "book" }+    , { key = 42, val = "food" }+    ]+@++@since 1.3.0.0+-}+instance (Ord k, HasCodec k, HasCodec v) => HasCodec (Map k v) where+    hasCodec = Toml.map (hasCodec @k "key") (hasCodec @v "val")+    {-# INLINE hasCodec #-}++{- | Encodes 'HashMap' as array of tables with the @key@ and @val@ TOML+key names for 'HashMap' keys and values. E.g. if you have a type+@'HashMap' 'Text' 'Int'@, the 'HasCodec' instance for 'Generic'+deriving will work with the following TOML representation:++@+fieldName =+    [ { key = "foo", val = 15 }+    , { key = "bar", val = 7  }+    ]+@++@since 1.3.0.0+-}+instance (Hashable k, Eq k, HasCodec k, HasCodec v) => HasCodec (HashMap k v) where+    hasCodec = Toml.hashMap (hasCodec @k "key") (hasCodec @v "val")+    {-# INLINE hasCodec #-}++{- | Encodes 'IntMap' as array of tables with the @key@ and @val@ TOML+key names for 'IntMap' keys and values. E.g. if you have a type+@'IntMap' 'Text'@, the 'HasCodec' instance for 'Generic' deriving will+work with the following TOML representation:++@+fieldName =+    [ { key = 10, val = "foo" }+    , { key = 42, val = "bar" }+    ]+@++@since 1.3.0.0+-}+instance (HasCodec v) => HasCodec (IntMap v) where+    hasCodec = Toml.intMap (hasCodec @Int "key") (hasCodec @v "val")+    {-# INLINE hasCodec #-}++-- | @since 1.3.0.0+instance HasCodec All where+    hasCodec = Toml.all+    {-# INLINE hasCodec #-}++-- | @since 1.3.0.0+instance HasCodec Any where+    hasCodec = Toml.any+    {-# INLINE hasCodec #-}++-- | @since 1.3.0.0+instance (Num a, HasCodec a) => HasCodec (Sum a) where+    hasCodec = Toml.sum (hasCodec @a)+    {-# INLINE hasCodec #-}++-- | @since 1.3.0.0+instance (Num a, HasCodec a) => HasCodec (Product a) where+    hasCodec = Toml.product (hasCodec @a)+    {-# INLINE hasCodec #-}++-- | @since 1.3.0.0+instance HasCodec a => HasCodec (First a) where+    hasCodec = Toml.first (hasCodec @a)+    {-# INLINE hasCodec #-}++-- | @since 1.3.0.0+instance HasCodec a => HasCodec (Last a) where+    hasCodec = Toml.last (hasCodec @a)+    {-# INLINE hasCodec #-}++{- | @newtype@ for generic deriving of 'HasCodec' typeclass for custom data+types that should we wrapped into separate table. Use it only for data types+that are fields of another data types.++@+__data__ Person = Person+    { personName    :: !'Text'+    , personAddress :: !Address+    } __deriving__ ('Generic')++data Address = Address+    { addressStreet :: !'Text'+    , addressHouse  :: !'Int'+    } __deriving__ ('Generic')+      __deriving__ 'HasCodec' __via__ 'TomlTable' Address++personCodec :: 'TomlCodec' Person+personCodec = 'stripTypeNameCodec'+@++@personCodec@ corresponds to the TOML of the following structure:++@+name = "foo"+[address]+    addressStreet = \"Bar\"+    addressHouse = 42+@++@since 1.3.0.0+-}+newtype TomlTable a = TomlTable+    { unTomlTable :: a+    }++-- | @since 1.3.0.0+instance (Generic a, GenericCodec (Rep a)) => HasCodec (TomlTable a) where+    hasCodec :: Key -> TomlCodec (TomlTable a)+    hasCodec = Toml.diwrap . Toml.table (genericCodec @a)+    {-# INLINE hasCodec #-}++-- | @since 1.3.0.0+instance (Generic a, GenericCodec (Rep a)) => HasItemCodec (TomlTable a) where+    hasItemCodec = Right $ Toml.diwrap $ genericCodec @a+    {-# INLINE hasItemCodec #-}++{- $bytestring+There are two ways to encode 'ByteString' in TOML:++1. Via text.+2. Via an array of integers (aka array of bytes).++To handle all these cases, @tomland@ provides helpful newtypes, specifically:++* 'ByteStringAsText'+* 'ByteStringAsBytes'+* 'LByteStringAsText'+* 'LByteStringAsBytes'++As a bonus, on GHC >= 8.6 you can use these newtypes with the @DerivingVia@+extensions for your own 'ByteString' types.++@+__newtype__ MyByteString = MyByteString+    { unMyByteString :: 'ByteString'+    } __deriving__ 'HasCodec' __via__ 'ByteStringAsBytes'+@+-}++{- | Newtype wrapper over 'ByteString' to be used for text values.++@since 1.3.0.0+-}+newtype ByteStringAsText = ByteStringAsText+    { unByteStringAsText :: ByteString+    } deriving newtype (Show, Eq)++{- | Newtype wrapper over 'ByteString' to be used for array of integers+representation.++@since 1.3.0.0+-}+newtype ByteStringAsBytes = ByteStringAsBytes+    { unByteStringAsBytes :: ByteString+    } deriving newtype (Show, Eq)++{- | Newtype wrapper over lazy 'LBS.ByteString' to be used for text values.++@since 1.3.0.0+-}+newtype LByteStringAsText = LByteStringAsText+    { unLByteStringAsText :: LBS.ByteString+    } deriving newtype (Show, Eq)++{- | Newtype wrapper over lazy 'LBS.ByteString' to be used for array of integers+representation.++@since 1.3.0.0+-}+newtype LByteStringAsBytes = LByteStringAsBytes+    { unLByteStringAsBytes :: LBS.ByteString+    } deriving newtype (Show, Eq)
+ src/Toml/Codec/Types.hs view
@@ -0,0 +1,220 @@+{-# LANGUAGE FlexibleInstances     #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TupleSections         #-}+{-# LANGUAGE TypeFamilies          #-}+{-# LANGUAGE UndecidableInstances  #-}++{- |+Copyright: (c) 2018-2020 Kowainik+SPDX-License-Identifier: MPL-2.0+Maintainer: Kowainik <xrom.xkov@gmail.com>++Contains general underlying monad for bidirectional conversion.++@since 1.3.0.0+-}++module Toml.Codec.Types+       ( -- * Toml Codec+         TomlCodec+         -- * Toml Environment+       , TomlEnv+         -- * Toml State+       , TomlState (..)+       , eitherToTomlState++         -- * Codec+       , Codec (..)++         -- * Function alternative+       , (<!>)+       ) where++import Control.Applicative (Alternative (..), liftA2)+import Control.Monad.State (MonadState (..))+import Data.Bifunctor (first)+import Validation (Validation (..))++import Toml.Codec.Error (TomlDecodeError)+import Toml.Type (TOML (..))+++{- | Immutable environment for TOML conversion.++@since 1.3.0.0+-}+type TomlEnv a = TOML -> Validation [TomlDecodeError] a+++{- | Specialied 'Codec' type alias for bidirectional TOML serialization. Keeps+'TOML' object as both environment and state.++@since 0.5.0+-}+type TomlCodec a = Codec a a++{- | Monad for bidirectional conversion. Contains pair of functions:++1. How to read value of type @o@ (out) from immutable environment context+('TomlEnv')?+2. How to store a value of type @i@ (in) in stateful context ('TomlState') and+return a value of type @o@?++This approach with the bunch of utility functions allows to+have single description for from/to @TOML@ conversion.++In practice this type will always be used in the following way:++@+type 'TomlCodec' a = 'Codec' a a+@++Type parameter @i@ if fictional. Here some trick is used. This trick is+implemented in the [codec](http://hackage.haskell.org/package/codec) package and+described in more details in related blog post:+<https://blog.poisson.chat/posts/2016-10-12-bidirectional-serialization.html>.++@since 0.0.0+-}+data Codec i o = Codec+    { -- | Extract value of type @o@ from monadic context 'TomlEnv'.+      codecRead  :: TomlEnv o++      {- | Store value of type @i@ inside monadic context 'TomlState' and+      returning value of type @o@. Type of this function actually should be+      @o -> TomlState ()@ but with such type it's impossible to have 'Monad'+      and other instances.+      -}+    , codecWrite :: i -> TomlState o+    }++-- | @since 0.0.0+instance Functor (Codec i) where+    fmap :: (oA -> oB) -> Codec i oA -> Codec i oB+    fmap f codec = Codec+        { codecRead  = fmap f . codecRead codec+        , codecWrite = fmap f . codecWrite codec+        }+    {-# INLINE fmap #-}++-- | @since 0.0.0+instance Applicative (Codec i) where+    pure :: o -> Codec i o+    pure a = Codec+        { codecRead  = \_ -> Success a+        , codecWrite = \_ -> pure a+        }+    {-# INLINE pure #-}++    (<*>) :: Codec i (oA -> oB) -> Codec i oA -> Codec i oB+    codecf <*> codeca = Codec+        { codecRead  = liftA2 (<*>) (codecRead codecf) (codecRead codeca)+        , codecWrite = \c -> codecWrite codecf c <*> codecWrite codeca c+        }+    {-# INLINE (<*>) #-}++instance Alternative (Codec i) where+    empty :: Codec i o+    empty = Codec+        { codecRead  = \_ -> empty+        , codecWrite = \_ -> empty+        }+    {-# INLINE empty #-}++    (<|>) :: Codec i o -> Codec i o -> Codec i o+    codec1 <|> codec2 = Codec+        { codecRead  = codecRead codec1 <!> codecRead codec2+        , codecWrite = \c -> codecWrite codec1 c <|> codecWrite codec2 c+        }+    {-# INLINE (<|>) #-}++-- | 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 (<!>) #-}++{- | Mutable context for TOML conversion.+We are introducing our own implemetation of state with 'MonadState' instance due+to some limitation in the design connected to the usage of State.++This newtype is equivalent to the following transformer:++@+MaybeT (State TOML)+@+++@since 1.3.0.0+-}+newtype TomlState a = TomlState+    { unTomlState :: TOML -> (Maybe a, TOML)+    }++-- | @since 1.3.0.0+instance Functor TomlState where+    fmap :: (a -> b) -> TomlState a -> TomlState b+    fmap f TomlState{..} = TomlState (first (fmap f) . unTomlState)+    {-# INLINE fmap #-}++    (<$) :: a -> TomlState b -> TomlState a+    a <$ TomlState{..} = TomlState (first (fmap (const a)) . unTomlState)+    {-# INLINE (<$) #-}++-- | @since 1.3.0.0+instance Applicative TomlState where+    pure :: a -> TomlState a+    pure a = TomlState (Just a,)+    {-# INLINE pure #-}++    (<*>) :: TomlState (a -> b) -> TomlState a -> TomlState b+    tsF <*> tsA = TomlState $ \t ->+        let (mF, tF) = unTomlState tsF t+            (mA, tA) = unTomlState tsA tF+        in (mF <*> mA , tA)+    {-# INLINE (<*>) #-}++-- | @since 1.3.0.0+instance Alternative TomlState where+    empty :: TomlState a+    empty = TomlState (Nothing,)+    {-# INLINE empty #-}++    (<|>) :: TomlState a -> TomlState a -> TomlState a+    ts1 <|> ts2 = TomlState $ \t -> let (m1, t1) = unTomlState ts1 t in case m1 of+        Nothing -> unTomlState ts2 t+        Just _  -> (m1, t1)+    {-# INLINE (<|>) #-}++-- | @since 1.3.0.0+instance Monad TomlState where+    return :: a -> TomlState a+    return = pure+    {-# INLINE return #-}++    (>>=) :: TomlState a -> (a -> TomlState b) -> TomlState b+    tsA >>= f = TomlState $ \t -> let (mA, newT) = unTomlState tsA t in case mA of+        Nothing -> (Nothing, newT)+        Just a  -> unTomlState (f a) newT+    {-# INLINE (>>=) #-}++-- | @since 1.3.0.0+instance (s ~ TOML) => MonadState s TomlState where+    state :: (TOML -> (a, TOML)) -> TomlState a+    state f = TomlState (first Just . f)+    {-# INLINE state #-}++    get :: TomlState TOML+    get = TomlState (\t -> (Just t, t))+    {-# INLINE get #-}++    put :: TOML -> TomlState ()+    put t = TomlState (\_ -> (Just (), t))+    {-# INLINE put #-}++{- | Transform 'Either' into 'TomlState'.++@since 1.3.0.0+-}+eitherToTomlState :: Either e a -> TomlState a+eitherToTomlState e = TomlState (either (const Nothing) Just e,)
− src/Toml/Edsl.hs
@@ -1,86 +0,0 @@-{- |-Copyright: (c) 2018-2019 Kowainik-SPDX-License-Identifier: MPL-2.0-Maintainer: Kowainik <xrom.xkov@gmail.com>--This module introduces EDSL for manually specifying 'TOML' data types.--Consider the following raw TOML:--@-key1 = 1-key2 = true--[meme-quotes]-  quote1 = [ \"Oh\", \"Hi\", \"Mark\" ]--[[arrayName]]-  elem1 = "yes"--[[arrayName]]-  [arrayName.elem2]-    deep = 7--[[arrayName]]-@--using functions from this module you can specify the above TOML in safer way:--@-exampleToml :: 'TOML'-exampleToml = 'mkToml' $ __do__-    \"key1\" '=:' 1-    \"key2\" '=:' Bool True-    'table' \"meme-quotes\" $-        \"quote1\" '=:' Array [\"Oh\", \"Hi\", \"Mark\"]-    'tableArray' \"arrayName\" $-        \"elem1\" '=:' \"yes\" :|-        [ 'table' \"elem2\" $ \"deep\" '=:' Integer 7-        , 'empty'-        ]-@--}--module Toml.Edsl-       ( TDSL-       , mkToml-       , empty-       , (=:)-       , table-       , tableArray-       ) where--import Control.Monad.State (State, execState, modify, put)-import Data.List.NonEmpty (NonEmpty)--import Toml.PrefixTree (Key)-import Toml.Type (TOML (..), Value, insertKeyVal, insertTable, insertTableArrays)----- | Monad for creating TOML.-type TDSL = State TOML ()---- | 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
@@ -1,419 +0,0 @@-{-# LANGUAGE AllowAmbiguousTypes  #-}-{-# LANGUAGE DataKinds            #-}-{-# LANGUAGE FlexibleContexts     #-}-{-# LANGUAGE FlexibleInstances    #-}-{-# LANGUAGE GADTs                #-}-{-# LANGUAGE PolyKinds            #-}-{-# LANGUAGE Rank2Types           #-}-{-# LANGUAGE TypeOperators        #-}-{-# LANGUAGE TypeSynonymInstances #-}-{-# LANGUAGE UndecidableInstances #-}--{- |-Copyright: (c) 2018-2019 Kowainik-SPDX-License-Identifier: MPL-2.0-Maintainer: Kowainik <xrom.xkov@gmail.com>--This module contains implementation of the 'Generic' TOML codec. If your-data types are big and nested, and you want to have codecs for them without writing a lot of-boilerplate code, you can find this module helpful. Below you can find the detailed-explanation on how the 'Generic' codecs work.--Consider the following Haskell data types:--@-__data__ User = User-    { age     :: Int-    , address :: Address-    , socials :: [Social]-    } __deriving__ ('Generic')--__data__ Address = Address-    { street :: Text-    , house  :: Int-    } __deriving__ ('Generic')--__data__ Social = Social-    { name :: Text-    , link :: Text-    } __deriving__ ('Generic')-@--Value of the @User@ type represents the following TOML:--@-age = 27--[address]-  street = "Miami Beach"-  house  = 42--[[socials]]-  name = \"Twitter\"-  link = "https://twitter.com/foo"--[[socials]]-  name = \"GitHub\"-  link = "https://github.com/bar"-@--Normally you would write 'TomlCodec' for this data type like this:--@-userCodec :: 'TomlCodec' User-userCodec = User-    \<$\> Toml.int "age" .= age-    \<*\> Toml.table addressCodec "address" .= address-    \<*\> Toml.list  socialCodec  "socials" .= socials--addressCodec :: 'TomlCodec' Address-addressCodec = Address-    \<$\> Toml.text "street" .= street-    \<*\> Toml.int  "house"  .= house--socialCodec :: 'TomlCodec' Social-socialCodec = Social-    \<$\> Toml.text "name" .= name-    \<*\> Toml.text "link" .= link-@--However, if you derive 'Generic' instance for your data types (as we do in the-example), you can write your codecs in a simpler way.--@-userCodec :: 'TomlCodec' User-userCodec = 'genericCodec'--__instance__ 'HasCodec' Address __where__-    hasCodec = Toml.table 'genericCodec'--__instance__ 'HasItemCodec' Social __where__-    hasItemCodec = Right 'genericCodec'-@--Several notes about the interface:--1. Your top-level data types are always implemented as 'genericCodec' (or other-generic codecs).-2. If you have a custom data type as a field of another type, you need to implement-the instance of the 'HasCodec' typeclass.-3. If the data type appears as an element of a list, you need to implement the instance-of the 'HasItemCodec' typeclass.--@since 1.1.1.0--}--module Toml.Generic-       ( genericCodec-       , genericCodecWithOptions-       , stripTypeNameCodec--         -- * Options-       , TomlOptions (..)-       , GenericOptions (..)-       , stripTypeNameOptions-       , stripTypeNamePrefix--         -- * Core generic typeclass-       , HasCodec (..)-       , HasItemCodec (..)-       , GenericCodec (..)-       ) 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 (Word8)-import GHC.Generics ((:*:) (..), (:+:), C1, D1, Generic (..), K1 (..), M1 (..), Rec0, S1,-                     Selector (..))-import GHC.TypeLits (ErrorMessage (..), TypeError)-import Numeric.Natural (Natural)--import Toml.Bi (TomlBiMap, TomlCodec, (.=))-import Toml.PrefixTree (Key)-import Toml.Type (AnyValue)--import qualified Data.Text.Lazy as L-import qualified Toml.Bi as Toml---{- | Generic codec for arbitrary data types. Uses field names as keys.--}-genericCodec :: (Generic a, GenericCodec (Rep a)) => TomlCodec a-genericCodec = Toml.dimap from to $ genericTomlCodec (GenericOptions id)-{-# INLINE genericCodec #-}--{- | Generic codec with options for arbitrary data types.--}-genericCodecWithOptions-    :: forall a-     . (Generic a, GenericCodec (Rep a), Typeable a)-    => TomlOptions a-    -> TomlCodec a-genericCodecWithOptions = Toml.dimap from to . genericTomlCodec . toGenericOptions @a-{-# INLINE genericCodecWithOptions #-}--{- | Generic codec that uses 'stripTypeNameOptions'.--}-stripTypeNameCodec-    :: forall a-     . (Generic a, GenericCodec (Rep a), Typeable a)-    => TomlCodec a-stripTypeNameCodec = genericCodecWithOptions $ stripTypeNameOptions @a-{-# INLINE stripTypeNameCodec #-}--------------------------------------------------------------------------------- Generic typeclasses-------------------------------------------------------------------------------{- | Options to configure various parameters of generic encoding. Specifically:--*  __'tomlOptionsFieldModifier'__: how to translate field names to TOML keys?--}-data TomlOptions a = TomlOptions-    { tomlOptionsFieldModifier :: Typeable a => Proxy a -> String -> String-    }--{- | Same as 'TomlOptions' but with all data type information erased. This data-type is used internally. Define your options using 'TomlOptions' data type.--}-newtype GenericOptions = GenericOptions-    { genericOptionsFieldModifier :: String -> String-    }--toGenericOptions :: forall a . Typeable a => TomlOptions a -> GenericOptions-toGenericOptions TomlOptions{..} = GenericOptions-    { genericOptionsFieldModifier = tomlOptionsFieldModifier (Proxy @a)-    }---- | Options that use 'stripTypeNamePrefix' as 'tomlOptionsFieldModifier'.-stripTypeNameOptions :: Typeable a => TomlOptions a-stripTypeNameOptions = TomlOptions-    { tomlOptionsFieldModifier = stripTypeNamePrefix-    }--{- | Strips name of the type name from field name prefix.-->>> data UserData = UserData { userDataId :: Int, userDataShortInfo :: Text }->>> stripTypeNamePrefix (Proxy @UserData) "userDataId"-"id"->>> stripTypeNamePrefix (Proxy @UserData) "userDataShortInfo"-"shortInfo"->>> stripTypeNamePrefix (Proxy @UserData) "udStats"-"stats"->>> stripTypeNamePrefix (Proxy @UserData) "fooBar"-"bar"->>> stripTypeNamePrefix (Proxy @UserData) "name"-"name"--}-stripTypeNamePrefix :: forall a . Typeable a => Proxy a -> String -> String-stripTypeNamePrefix _ fieldName =-    case stripPrefix (headToLower $ typeName @a) fieldName of-        Just rest -> leaveIfEmpty rest-        Nothing   -> leaveIfEmpty (dropWhile isLower fieldName)-  where-    headToLower :: String -> String-    headToLower = \case-        []   -> error "Cannot use 'headToLower' on empty Text"-        x:xs -> toLower x : xs--    -- if all lower case then leave field as it is-    leaveIfEmpty :: String -> String-    leaveIfEmpty rest = if null rest then fieldName else headToLower rest--typeName :: forall a . Typeable a => String-typeName = show $ typeRep (Proxy @a)--------------------------------------------------------------------------------- Generic typeclasses-------------------------------------------------------------------------------{- | Helper class to derive TOML codecs generically.--}-class GenericCodec (f :: k -> Type) where-    genericTomlCodec :: GenericOptions -> TomlCodec (f p)--instance GenericCodec f => GenericCodec (D1 d f) where-    genericTomlCodec = Toml.dimap unM1 M1 . genericTomlCodec-    {-# INLINE genericTomlCodec #-}--type GenericSumTomlNotSupported =-    'Text "Generic TOML deriving for arbitrary sum types is not supported currently."--instance (TypeError GenericSumTomlNotSupported) => GenericCodec (f :+: g) where-    genericTomlCodec = error "Not supported"--instance GenericCodec f => GenericCodec (C1 c f) where-    genericTomlCodec = Toml.dimap unM1 M1 . genericTomlCodec-    {-# INLINE genericTomlCodec #-}--instance (GenericCodec f, GenericCodec g) => GenericCodec (f :*: g) where-    genericTomlCodec options = (:*:)-        <$> genericTomlCodec options .= fstG-        <*> genericTomlCodec options .= sndG-      where-        fstG :: (f :*: g) p -> f p-        fstG (f :*: _) = f--        sndG :: (f :*: g) p -> g p-        sndG (_ :*: g) = g-    {-# INLINE genericTomlCodec #-}--instance (Selector s, HasCodec a) => GenericCodec (S1 s (Rec0 a)) where-    genericTomlCodec GenericOptions{..} = genericWrap $ hasCodec @a fieldName-      where-        genericWrap :: TomlCodec a -> TomlCodec (S1 s (Rec0 a) p)-        genericWrap = Toml.dimap (unK1 . unM1) (M1 . K1)--        fieldName :: Key-        fieldName =-            fromString-            $ genericOptionsFieldModifier-            $ selName (error "S1" :: S1 s Proxy ())-    {-# INLINE genericTomlCodec #-}--------------------------------------------------------------------------------- Helper typeclasses-------------------------------------------------------------------------------{- | This typeclass tells how the data type should be coded as an item of a-list. Lists in TOML can have two types: __primitive__ and __table of arrays__.--* If 'hasItemCodec' returns 'Left': __primitive__ arrays codec is used.-* If 'hasItemCodec' returns 'Right:' __table of arrays__ codec is used.--}-class HasItemCodec a where-    hasItemCodec :: Either (TomlBiMap a AnyValue) (TomlCodec a)--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-instance HasItemCodec Float     where hasItemCodec = Left Toml._Float-instance HasItemCodec Text      where hasItemCodec = Left Toml._Text-instance HasItemCodec L.Text    where hasItemCodec = Left Toml._LText-instance HasItemCodec ZonedTime where hasItemCodec = Left Toml._ZonedTime-instance HasItemCodec LocalTime where hasItemCodec = Left Toml._LocalTime-instance HasItemCodec Day       where hasItemCodec = Left Toml._Day-instance HasItemCodec TimeOfDay where hasItemCodec = Left Toml._TimeOfDay-instance HasItemCodec IntSet    where hasItemCodec = Left Toml._IntSet--{- | If data type @a@ is not primitive then this instance returns codec for list-under key equal to @a@ type name.--}-instance (HasItemCodec a, Typeable a) => HasItemCodec [a] where-    hasItemCodec = case hasItemCodec @a of-        Left prim   -> Left $ Toml._Array prim-        Right codec -> Right $ Toml.list codec (fromString $ typeName @a)--{- | Helper typeclass for generic deriving. This instance tells how the data-type should be coded if it's a field of another data type.--__NOTE:__ If you implement TOML codecs for your data types manually, prefer more-explicit @Toml.int@ or @Toml.text@ instead of implicit @Toml.hasCodec@.-Implement instances of this typeclass only when using 'genericCodec' and when-your custom data types are not covered here.--}-class HasCodec a where-    hasCodec :: Key -> TomlCodec a--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-instance HasCodec Float     where hasCodec = Toml.float-instance HasCodec Text      where hasCodec = Toml.text-instance HasCodec L.Text    where hasCodec = Toml.lazyText-instance HasCodec ZonedTime where hasCodec = Toml.zonedTime-instance HasCodec LocalTime where hasCodec = Toml.localTime-instance HasCodec Day       where hasCodec = Toml.day-instance HasCodec TimeOfDay where hasCodec = Toml.timeOfDay-instance HasCodec IntSet    where hasCodec = Toml.arrayIntSet--instance HasCodec a => HasCodec (Maybe a) where-    hasCodec = Toml.dioptional . hasCodec @a--instance HasItemCodec a => HasCodec [a] where-    hasCodec = case hasItemCodec @a of-        Left prim   -> Toml.arrayOf prim-        Right codec -> Toml.list codec--instance HasItemCodec a => HasCodec (NonEmpty a) where-    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-* https://github.com/ghc-proposals/ghc-proposals/pull/233--{- | @newtype@ for generic deriving of 'HasCodec' typeclass for custom data-types that should we wrapped into separate table. Use it only for data types-that are fields of another data types.--@-data Person = Person-    { personName    :: Text-    , personAddress :: Address-    } deriving (Generic)--data Address = Address-    { addressStreet :: Text-    , addressHouse  :: Int-    } deriving (Generic)-      deriving HasCodec via TomlTable Address--personCodec :: TomlCodec Person-personCodec = genericCodec-@--@personCodec@ corresponds to the TOML of the following structure:--@-name = "foo"-[address]-    street = \"Bar\"-    house = 42-@--}-newtype TomlTable a = TomlTable-    { unTomlTable :: a-    }--instance (Generic a, GenericCodec (Rep a)) => HasCodec (TomlTable a) where-    hasCodec :: Key -> TomlCodec (TomlTable a)-    hasCodec = Toml.diwrap . Toml.table (genericCodec @a)--instance (Generic a, GenericCodec (Rep a)) => HasItemCodec (TomlTable a) where-    hasItemCodec = Right $ Toml.diwrap $ genericCodec @a--}
src/Toml/Parser.hs view
@@ -1,16 +1,17 @@-{-# LANGUAGE DeriveAnyClass #-}- {- |-Copyright: (c) 2018-2019 Kowainik+Copyright: (c) 2018-2020 Kowainik SPDX-License-Identifier: MPL-2.0 Maintainer: Kowainik <xrom.xkov@gmail.com>  Parser for text to TOML AST.++@since 0.0.0 -}  module Toml.Parser-       ( ParseException (..)+       ( TomlParseError (..)        , parse+       , parseKey        ) where  import Control.DeepSeq (NFData)@@ -18,26 +19,42 @@ import GHC.Generics (Generic)  import Toml.Parser.Item (tomlP)+import Toml.Parser.Key (keyP) import Toml.Parser.Validate (validateItems)-import Toml.Type (TOML)+import Toml.Type.Key (Key)+import Toml.Type.TOML (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-    { unParseException :: Text+{- | Pretty parse exception for parsing toml.++@since 1.3.0.0+-}+newtype TomlParseError = TomlParseError+    { unTomlParseError :: 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'-functions.+{- | Parses 'Data.Text.Text' as 'TOML' AST object. If you want to+convert 'Data.Text.Text' to your custom haskell data type, use+'Toml.Codec.Code.decode' or 'Toml.Codec.Code.decodeFile' functions.++@since 0.0.0 -}-parse :: Text -> Either ParseException TOML+parse :: Text -> Either TomlParseError TOML parse t = case P.parse tomlP "" t of-    Left err    -> Left $ ParseException $ T.pack $ P.errorBundlePretty err+    Left err    -> Left $ TomlParseError $ T.pack $ P.errorBundlePretty err     Right items -> case validateItems items of-        Left err   -> Left $ ParseException $ T.pack $ show err+        Left err   -> Left $ TomlParseError $ T.pack $ show err         Right toml -> Right toml++{- | Parse TOML 'Key' from 'Text'.++@since 1.3.0.0+-}+parseKey :: Text -> Either TomlParseError Key+parseKey t = case P.parse keyP "" t of+    Left err  -> Left $ TomlParseError $ T.pack $ P.errorBundlePretty err+    Right key -> Right key
src/Toml/Parser/Core.hs view
@@ -1,5 +1,5 @@ {- |-Copyright: (c) 2018-2019 Kowainik+Copyright: (c) 2018-2020 Kowainik SPDX-License-Identifier: MPL-2.0 Maintainer: Kowainik <xrom.xkov@gmail.com> 
src/Toml/Parser/Item.hs view
@@ -1,5 +1,5 @@ {-|-Copyright: (c) 2018-2019 Kowainik+Copyright: (c) 2018-2020 Kowainik SPDX-License-Identifier: MPL-2.0 Maintainer: Kowainik <xrom.xkov@gmail.com> @@ -17,6 +17,7 @@        , setTableName         , tomlP+       , keyValP        ) where  import Control.Applicative (liftA2, many)@@ -28,8 +29,8 @@ 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)+import Toml.Type.AnyValue (AnyValue)+import Toml.Type.Key (Key)   {- | One item of a TOML file. It could be either:@@ -76,7 +77,7 @@ inlineTableP =     fmap Table     $ between (text "{") (text "}")-    $ (liftA2 (,) (keyP <* text "=") anyValueP) `sepEndBy` text ","+    $ liftA2 (,) (keyP <* text "=") anyValueP `sepEndBy` text ","  -- | Parser for inline arrays of tables. inlineTableArrayP :: Parser (NonEmpty Table)@@ -90,19 +91,21 @@     , 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 @"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]
src/Toml/Parser/Key.hs view
@@ -1,5 +1,5 @@ {- |-Copyright: (c) 2018-2019 Kowainik+Copyright: (c) 2018-2020 Kowainik SPDX-License-Identifier: MPL-2.0 Maintainer: Kowainik <xrom.xkov@gmail.com> @@ -17,12 +17,11 @@ 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 Toml.Type.Key (Key (..), Piece (..))  import qualified Data.Text as Text 
src/Toml/Parser/String.hs view
@@ -1,5 +1,5 @@ {- |-Copyright: (c) 2018-2019 Kowainik+Copyright: (c) 2018-2020 Kowainik SPDX-License-Identifier: MPL-2.0 Maintainer: Kowainik <xrom.xkov@gmail.com> @@ -16,7 +16,6 @@ import Control.Applicative (Alternative (..)) import Control.Applicative.Combinators (count, manyTill, optional) import Data.Char (chr, isControl)-import Data.Semigroup ((<>)) import Data.Text (Text)  import Toml.Parser.Core (Parser, anySingle, char, eol, hexDigitChar, lexeme, satisfy, space, string,
src/Toml/Parser/Validate.hs view
@@ -1,5 +1,5 @@ {- |-Copyright: (c) 2018-2019 Kowainik+Copyright: (c) 2018-2020 Kowainik SPDX-License-Identifier: MPL-2.0 Maintainer: Kowainik <xrom.xkov@gmail.com> @@ -24,15 +24,14 @@  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 Toml.Type.Key (Key, KeysDiff (FstIsPref), keysDiff)+import Toml.Type.TOML (TOML (..), insertKeyAnyVal, insertTable, insertTableArrays)  import qualified Data.HashMap.Strict as HashMap-import qualified Toml.PrefixTree as PrefixMap+import qualified Toml.Type.PrefixTree as PrefixMap   {- | Validate list of 'TomlItem's and convert to 'TOML' if not validation@@ -150,6 +149,7 @@         -- ignore subforest here         InlineTable key table -> do             HashMap.lookup key tomlPairs `errorOnJust` SameNameKeyTable key+            HashMap.lookup key tomlTableArrays `errorOnJust` SameNameTableArray key             PrefixMap.lookup key tomlTables `errorOnJust` DuplicateTable key             tableToml <- createTomlFromTable table             go (insertTable key tableToml toml) nodes@@ -162,6 +162,7 @@          TableName key -> do             HashMap.lookup key tomlPairs `errorOnJust` SameNameKeyTable key+            HashMap.lookup key tomlTableArrays `errorOnJust` SameNameTableArray key             PrefixMap.lookup key tomlTables `errorOnJust` DuplicateTable key             subTable <- go mempty (subForest node)             go (insertTable key subTable toml) nodes
src/Toml/Parser/Value.hs view
@@ -1,5 +1,5 @@ {- |-Copyright: (c) 2018-2019 Kowainik+Copyright: (c) 2018-2020 Kowainik SPDX-License-Identifier: MPL-2.0 Maintainer: Kowainik <xrom.xkov@gmail.com> @@ -20,7 +20,6 @@ import Control.Applicative.Combinators (between, count, option, optional, sepBy1, sepEndBy,                                         skipMany) import Data.Fixed (Pico)-import Data.Semigroup ((<>)) import Data.Time (Day, LocalTime (..), TimeOfDay, ZonedTime (..), fromGregorianValid,                   makeTimeOfDayValid, minutesToTimeZone) import Text.Read (readMaybe)
− src/Toml/PrefixTree.hs
@@ -1,231 +0,0 @@-{-# LANGUAGE DeriveAnyClass  #-}-{-# LANGUAGE PatternSynonyms #-}--{- |-Copyright: (c) 2018-2019 Kowainik-SPDX-License-Identifier: MPL-2.0-Maintainer: Kowainik <xrom.xkov@gmail.com>--Implementation of prefix tree for TOML AST.--}--module Toml.PrefixTree-       ( -- * Core types-         Piece (..)-       , Key (..)-       , Prefix-       , pattern (:||)--         -- * Key difference-       , KeysDiff (..)-       , keysDiff--         -- * Non-empty prefix tree-       , PrefixTree (..)-       , (<|)-       , singleT-       , insertT-       , lookupT-       , toListT--         -- * Prefix map that stores roots of 'PrefixTree'-       , PrefixMap-       , single-       , insert-       , lookup-       , fromList-       , toList-       ) where--import Prelude hiding (lookup)--import Control.DeepSeq (NFData)-import Data.Bifunctor (first)-import Data.Coerce (coerce)-import Data.Foldable (foldl')-import Data.Hashable (Hashable)-import Data.HashMap.Strict (HashMap)-import Data.List.NonEmpty (NonEmpty (..))-import Data.Semigroup (Semigroup (..))-import Data.String (IsString (..))-import Data.Text (Text)-import GHC.Generics (Generic)--import qualified Data.HashMap.Strict as HashMap-import qualified Data.List.NonEmpty as NonEmpty-import qualified Data.Text as Text----- | Represents the key piece of some layer.-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--@-site."google.com"-@--is represented like--@-Key (Piece "site" :| [Piece "\\"google.com\\""])-@--}-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'.--This instance is not safe for now. Use carefully. If you try to use as a key-string like this @site.\"google.com\"@ you will have list of three components-instead of desired two.--}-instance IsString Key where-    fromString :: String -> Key-    fromString = \case-        "" -> Key ("" :| [])-        s  -> case Text.splitOn "." (fromString s) of-            []   -> error "Text.splitOn returned empty string"  -- can't happen-            x:xs -> coerce @(NonEmpty Text) @Key (x :| xs)--{- | Bidirectional pattern synonym for constructing and deconstructing 'Key's.--}-pattern (:||) :: Piece -> [Piece] -> Key-pattern x :|| xs <- Key (x :| xs)-  where-    x :|| xs = Key (x :| xs)--{-# COMPLETE (:||) #-}---- | Type synonym for 'Key'.-type Prefix = Key---- | Map of layer names and corresponding 'PrefixTree's.-type PrefixMap a = HashMap Piece (PrefixTree a)---- | Data structure to represent table tree for @toml@.-data PrefixTree a-    = Leaf             -- ^ End of a key.-        !Key           -- ^ End piece of the key.-        !a             -- ^ Value at the end.-    | Branch           -- ^ Values along pieces of a key.-        !Prefix        -- ^ Greatest common key prefix.-        !(Maybe a)     -- ^ Possible value at that point.-        !(PrefixMap a) -- ^ Values at suffixes of the prefix.-    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)---- | Data structure to compare keys.-data KeysDiff-    = Equal      -- ^ Keys are equal-    | NoPrefix   -- ^ Keys don't have any common part.-    | FstIsPref  -- ^ The first key is the prefix of the second one.-        !Key     -- ^ Rest of the second key.-    | SndIsPref  -- ^ The second key is the prefix of the first one.-        !Key     -- ^ Rest of the first key.-    | Diff       -- ^ Key have a common prefix.-        !Key     -- ^ Common prefix.-        !Key     -- ^ Rest of the first key.-        !Key     -- ^ Rest of the second key.-    deriving stock (Show, Eq)---- | Compares two keys-keysDiff :: Key -> Key -> KeysDiff-keysDiff (x :|| xs) (y :|| ys)-    | x == y    = listSame xs ys []-    | otherwise = NoPrefix-  where-    listSame :: [Piece] -> [Piece] -> [Piece] -> KeysDiff-    listSame [] []     _ = Equal-    listSame [] (s:ss) _ = FstIsPref $ s :|| ss-    listSame (f:fs) [] _ = SndIsPref $ f :|| fs-    listSame (f:fs) (s:ss) pr =-        if f == s-        then listSame fs ss (pr ++ [f])-        else Diff (x :|| pr) (f :|| fs) (s :|| ss)---- | 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-single k@(p :|| _) = HashMap.singleton p . singleT k---- | Inserts key-value element into the given 'PrefixTree'.-insertT :: Key -> a -> PrefixTree a -> PrefixTree a-insertT newK newV (Leaf k v) =-    case keysDiff k newK of-        Equal -> Leaf k newV-        NoPrefix -> error "Algorithm error: can't be equal prefixes in insertT:Leaf case"-        FstIsPref rK -> Branch k (Just v) $ single rK newV-        SndIsPref lK -> Branch newK (Just newV) $ single lK v-        Diff p k1@(f :|| _) k2@(s :|| _) ->-          Branch p Nothing $ HashMap.fromList [(f, Leaf k1 v), (s, Leaf k2 newV)]-insertT newK newV (Branch pref mv prefMap) =-    case keysDiff pref newK of-        Equal    -> Branch pref (Just newV) prefMap-        NoPrefix -> error "Algorithm error: can't be equal prefixes in insertT:Branch case"-        FstIsPref rK -> Branch pref mv $ insert rK newV prefMap-        SndIsPref lK@(piece :|| _) ->-            Branch newK (Just newV) $ HashMap.singleton piece (Branch lK mv prefMap)-        Diff p k1@(f :|| _) k2@(s :|| _) ->-            Branch p Nothing $ HashMap.fromList [ (f, Branch k1 mv prefMap)-                                                , (s, Leaf k2 newV)-                                                ]---- | Inserts key-value element into the given 'PrefixMap'.-insert :: Key -> a -> PrefixMap a -> PrefixMap a-insert k@(p :|| _) v prefMap = case HashMap.lookup p prefMap of-    Just tree -> HashMap.insert p (insertT k v tree) prefMap-    Nothing   -> HashMap.insert p (singleT k v) prefMap---- | Looks up the value at a key in the 'PrefixTree'.-lookupT :: Key -> PrefixTree a -> Maybe a-lookupT lk (Leaf k v) = if lk == k then Just v else Nothing-lookupT lk (Branch pref mv prefMap) =-    case keysDiff pref lk of-        Equal       -> mv-        NoPrefix    -> Nothing-        Diff _ _ _  -> Nothing-        SndIsPref _ -> Nothing-        FstIsPref k -> lookup k prefMap---- | Looks up the value at a key in the 'PrefixMap'.-lookup :: Key -> PrefixMap a -> Maybe a-lookup k@(p :|| _) prefMap = HashMap.lookup p prefMap >>= lookupT k---- | Constructs 'PrefixMap' structure from the given list of 'Key' and value pairs.-fromList :: [(Key, a)] -> PrefixMap a-fromList = foldl' insertPair mempty-  where-    insertPair :: PrefixMap a -> (Key, a) -> PrefixMap a-    insertPair prefMap (k, v) = insert k v prefMap---- | Converts 'PrefixTree' to the list of pairs.-toListT :: PrefixTree a -> [(Key, a)]-toListT (Leaf k v) = [(k, v)]-toListT (Branch pref ma prefMap) = case ma of-    Just a  -> (:) (pref, a)-    Nothing -> id-    $ map (\(k, v) -> (pref <> k, v)) $ toList prefMap---- | Converts 'PrefixMap' to the list of pairs.-toList :: PrefixMap a -> [(Key, a)]-toList = concatMap (\(p, tr) -> first (p <|) <$> toListT tr) . HashMap.toList
− src/Toml/Printer.hs
@@ -1,208 +0,0 @@-{-# LANGUAGE TypeFamilies #-}--{- |-Copyright: (c) 2018-2019 Kowainik-SPDX-License-Identifier: MPL-2.0-Maintainer: Kowainik <xrom.xkov@gmail.com>--Contains functions for pretty printing @toml@ types.--}--module Toml.Printer-       ( PrintOptions(..)-       , defaultOptions-       , pretty-       , prettyOptions-       , prettyKey-       ) where--import Data.Bifunctor (first)-import Data.Function (on)-import Data.HashMap.Strict (HashMap)-import Data.List (sortBy)-import Data.List.NonEmpty (NonEmpty)-import Data.Monoid ((<>))-import Data.Text (Text)-import Data.Time (ZonedTime, defaultTimeLocale, formatTime)--import Toml.PrefixTree (Key (..), Piece (..), PrefixMap, PrefixTree (..))-import Toml.Type (AnyValue (..), TOML (..), Value (..))--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-    { {- | How table keys should be sorted, if at all.--      @since 1.1.1.0-      -}-      printOptionsSorting :: !(Maybe (Key -> Key -> Ordering))--      {- | Number of spaces by which to indent.-      -}-    , printOptionsIndent  :: !Int-    }--{- | Default printing options.--1. Sorts all keys and tables by name.-2. Indents with 2 spaces.--}-defaultOptions :: PrintOptions-defaultOptions = PrintOptions (Just compare) 2--{- | Converts 'TOML' type into 'Data.Text.Text' (using 'defaultOptions').--For example, this--@-TOML-    { tomlPairs  = HashMap.fromList-          [("title", AnyValue $ Text "TOML example")]-    , tomlTables = PrefixTree.fromList-          [( "example" <| "owner"-           , mempty-                 { tomlPairs  = HashMap.fromList-                       [("name", AnyValue $ Text "Kowainik")]-                 }-           )]-    , tomlTableArrays = mempty-    }-@--will be translated to this--@-title = "TOML Example"--[example.owner]-  name = \"Kowainik\"-@--}-pretty :: TOML -> Text-pretty = prettyOptions defaultOptions---- | Converts 'TOML' type into 'Data.Text.Text' using provided 'PrintOptions'-prettyOptions :: PrintOptions -> TOML -> Text-prettyOptions options = Text.unlines . prettyTomlInd options 0 ""---- | Converts 'TOML' into a list of 'Data.Text.Text' elements with the given indent.-prettyTomlInd :: PrintOptions -- ^ Printing options-              -> Int          -- ^ Current indentation-              -> Text         -- ^ Accumulator for table names-              -> TOML         -- ^ Given 'TOML'-              -> [Text]       -- ^ Pretty result-prettyTomlInd options i prefix TOML{..} = concat-    [ prettyKeyValue    options i tomlPairs-    , prettyTables      options i prefix tomlTables-    , prettyTableArrays options i prefix tomlTableArrays-    ]---- | Converts a key to text-prettyKey :: Key -> Text-prettyKey = Text.intercalate "." . map unPiece . NonEmpty.toList . unKey---- | Returns pretty formatted  key-value pairs of the 'TOML'.-prettyKeyValue :: PrintOptions -> Int -> HashMap Key AnyValue -> [Text]-prettyKeyValue options i = mapOrdered (\kv -> [kvText kv]) options . HashMap.toList-  where-    kvText :: (Key, AnyValue) -> Text-    kvText (k, AnyValue v) =-      tabWith options i <> prettyKey k <>  " = " <> valText v--    valText :: Value t -> Text-    valText (Bool b)    = Text.toLower $ showText b-    valText (Integer n) = showText n-    valText (Double d)  = showDouble d-    valText (Text s)    = showText s-    valText (Zoned z)   = showZonedTime z-    valText (Local l)   = showText l-    valText (Day d)     = showText d-    valText (Hours h)   = showText h-    valText (Array a)   = "[" <> Text.intercalate ", " (map valText a) <> "]"--    showText :: Show a => a -> Text-    showText = Text.pack . show--    showDouble :: Double -> Text-    showDouble d | isInfinite d && d < 0 = "-inf"-                 | isInfinite d = "inf"-                 | isNaN d = "nan"-                 | otherwise = showText d--    showZonedTime :: ZonedTime -> Text-    showZonedTime t = Text.pack $ showZonedDateTime t <> showZonedZone t-      where-        showZonedDateTime = formatTime defaultTimeLocale "%FT%T%Q"-        showZonedZone-            = (\(x,y) -> x ++ ":" ++ y)-            . (\z -> splitAt (length z - 2) z)-            . formatTime defaultTimeLocale "%z"---- | Returns pretty formatted tables section of the 'TOML'.-prettyTables :: PrintOptions -> Int -> Text -> PrefixMap TOML -> [Text]-prettyTables options i pref asPieces = mapOrdered (prettyTable . snd) options asKeys-  where-    asKeys :: [(Key, PrefixTree TOML)]-    asKeys = map (first pieceToKey) $ HashMap.toList asPieces--    pieceToKey :: Piece -> Key-    pieceToKey = Key . pure--    prettyTable :: PrefixTree TOML -> [Text]-    prettyTable (Leaf k toml) =-        let name = addPrefix k pref-        -- Each "" results in an empty line, inserted above table names-        in "": tabWith options i <> prettyTableName name :-        -- We don't want empty lines between a table name and a subtable name-             dropWhile (== "") (prettyTomlInd options (i + 1) name toml)--    prettyTable (Branch k mToml prefMap) =-        let name  = addPrefix k pref-            nextI = i + 1-            toml  = case mToml of-                        Nothing -> []-                        Just t  -> prettyTomlInd options nextI name t-        -- Each "" results in an empty line, inserted above table names-        in "": tabWith options i <> prettyTableName name :-        -- We don't want empty lines between a table name and a subtable name-             dropWhile (== "") (toml ++ prettyTables options nextI name prefMap)--    prettyTableName :: Text -> Text-    prettyTableName n = "[" <> n <> "]"--prettyTableArrays :: PrintOptions -> Int -> Text -> HashMap Key (NonEmpty TOML) -> [Text]-prettyTableArrays options i pref = mapOrdered arrText options . HashMap.toList-  where-    arrText :: (Key, NonEmpty TOML) -> [Text]-    arrText (k, ne) =-      let name = addPrefix k pref-          render toml =-            -- Each "" results in an empty line, inserted above array names-            "": tabWith options i <> "[[" <> name <> "]]" :-            -- We don't want empty lines between an array name and a subtable name-              dropWhile (== "") (prettyTomlInd options (i + 1) name toml)-      in concatMap render $ NonEmpty.toList ne---------------------------------------------------------- Helper functions---------------------------------------------------------- Returns an indentation prefix-tabWith :: PrintOptions -> Int -> Text-tabWith PrintOptions{..} n = Text.replicate (n * printOptionsIndent) " "---- Returns a proper sorting function-mapOrdered :: ((Key, v) -> [t]) -> PrintOptions -> [(Key, v)] -> [t]-mapOrdered f options = case printOptionsSorting options of-    Just sorter -> concatMap f . sortBy (sorter `on` fst)-    Nothing     -> concatMap f---- Adds next part of the table name to the accumulator.-addPrefix :: Key -> Text -> Text-addPrefix key = \case-    "" -> prettyKey key-    prefix -> prefix <> "." <> prettyKey key
src/Toml/Type.hs view
@@ -1,5 +1,5 @@ {- |-Copyright: (c) 2018-2019 Kowainik+Copyright: (c) 2018-2020 Kowainik SPDX-License-Identifier: MPL-2.0 Maintainer: Kowainik <xrom.xkov@gmail.com> @@ -7,13 +7,67 @@ -}  module Toml.Type-       ( module Toml.Type.AnyValue-       , module Toml.Type.TOML-       , module Toml.Type.UValue-       , module Toml.Type.Value-       ) where+    ( -- $toml+      module Toml.Type.TOML +--      -- $edsl+--    , module Toml.Type.Edsl+--    Edsl.table conflicts with Codec.table++      -- $printer+    , module Toml.Type.Printer+      -- $prefix+    , module Toml.Type.PrefixTree+      -- $key+    , module Toml.Type.Key+      -- $uvalue+    , module Toml.Type.UValue+      -- $value+    , module Toml.Type.Value+      -- $anyvalue+    , module Toml.Type.AnyValue+    ) where+ import Toml.Type.AnyValue+-- import Toml.Type.Edsl+import Toml.Type.Key+import Toml.Type.PrefixTree+import Toml.Type.Printer import Toml.Type.TOML import Toml.Type.UValue import Toml.Type.Value++{- $toml+Main TOML AST data type. Text is converted to 'TOML' first. All codecs+work with the 'TOML' type instead of raw text.+-}++-- {- $eDSL+-- eDSL for type-safe construction of the 'TOML' values.+-- -}++{- $printer+Pretty-printer for 'TOML'.+-}++{- $prefix+'PrefixMap' and 'PrefixTree' types that help representing 'TOML' AST.+-}++{- $key+Key in key-value pairs and table names. Also 'Key' in 'PrefixMap'.+-}++{- $uvalue+Untyped value obtained directly from parsing.+-}++{- $value+Typed 'Value'. Result of type-checking 'UValue'.+-}++{- $anyvalue+Existential wrapper around 'Value' to be able to store 'Value's of+different types inside lists or other containers like+'Data.Map.Strict.Map' or 'Data.HashMap.Strict.HashMap'.+-}
src/Toml/Type/AnyValue.hs view
@@ -6,11 +6,13 @@ {-# LANGUAGE KindSignatures            #-}  {- |-Copyright: (c) 2018-2019 Kowainik+Copyright: (c) 2018-2020 Kowainik SPDX-License-Identifier: MPL-2.0 Maintainer: Kowainik <xrom.xkov@gmail.com>  Existential wrapper over 'Value' type and matching functions.++@since 0.0.0 -}  module Toml.Type.AnyValue@@ -42,7 +44,10 @@ import Toml.Type.Value (TValue (..), TypeMismatchError (..), Value (..), sameValue)  --- | Existential wrapper for 'Value'.+{- | Existential wrapper for 'Value'.++@since 0.0.0+-} data AnyValue = forall (t :: TValue) . AnyValue (Value t)  instance Show AnyValue where
+ src/Toml/Type/Edsl.hs view
@@ -0,0 +1,107 @@+{- |+Copyright: (c) 2018-2020 Kowainik+SPDX-License-Identifier: MPL-2.0+Maintainer: Kowainik <xrom.xkov@gmail.com>++This module introduces EDSL for manually specifying 'TOML' data types.++Consider the following raw TOML:++@+key1 = 1+key2 = true++[meme-quotes]+  quote1 = [ \"Oh\", \"Hi\", \"Mark\" ]++[[arrayName]]+  elem1 = "yes"++[[arrayName]]+  [arrayName.elem2]+    deep = 7++[[arrayName]]+@++using functions from this module you can specify the above TOML in safer way:++@+exampleToml :: 'TOML'+exampleToml = 'mkToml' $ __do__+    \"key1\" '=:' 1+    \"key2\" '=:' Bool True+    'table' \"meme-quotes\" $+        \"quote1\" '=:' Array [\"Oh\", \"Hi\", \"Mark\"]+    'tableArray' \"arrayName\" $+        \"elem1\" '=:' \"yes\" :|+        [ 'table' \"elem2\" $ \"deep\" '=:' Integer 7+        , 'empty'+        ]+@++@since 0.3+-}++module Toml.Type.Edsl+       ( TDSL+       , mkToml+       , empty+       , (=:)+       , table+       , tableArray+       ) where++import Control.Monad.State (State, execState, modify, put)+import Data.List.NonEmpty (NonEmpty)++import Toml.Type.Key (Key)+import Toml.Type.TOML (TOML (..), insertKeyVal, insertTable, insertTableArrays)+import Toml.Type.Value (Value)+++{- | Monad for creating TOML.++@since 0.3+-}+type TDSL = State TOML ()++{- | Creates 'TOML' from the 'TDSL'.++@since 0.3+-}+mkToml :: TDSL -> TOML+mkToml env = execState env mempty+{-# INLINE mkToml #-}++{- | Creates an empty 'TDSL'.++@since 0.3+-}+empty :: TDSL+empty = put mempty+{-# INLINE empty #-}++{- | Adds key-value pair to the 'TDSL'.++@since 0.3+-}+(=:) :: Key -> Value a -> TDSL+(=:) k v = modify $ insertKeyVal k v+{-# INLINE (=:) #-}++{- | Adds table to the 'TDSL'.++@since 0.3+-}+table :: Key -> TDSL -> TDSL+table k = modify . insertTable k . mkToml+{-# INLINE table #-}++{- | Adds array of tables to the 'TDSL'.++@since 1.0.0+-}+tableArray :: Key -> NonEmpty TDSL -> TDSL+tableArray k = modify . insertTableArrays k . fmap mkToml+{-# INLINE tableArray #-}
+ src/Toml/Type/Key.hs view
@@ -0,0 +1,138 @@+{-# LANGUAGE PatternSynonyms #-}++{- |+Copyright: (c) 2018-2020 Kowainik+SPDX-License-Identifier: MPL-2.0+Maintainer: Kowainik <xrom.xkov@gmail.com>++Implementation of key type. The type is used for key-value pairs and+table names.++@since 1.3.0.0+-}++module Toml.Type.Key+    ( -- * Core types+      Key (..)+    , Prefix+    , Piece (..)+    , pattern (:||)+    , (<|)++      -- * Key difference+    , KeysDiff (..)+    , keysDiff+    ) where++import Control.DeepSeq (NFData)+import Data.Coerce (coerce)+import Data.Hashable (Hashable)+import Data.List.NonEmpty (NonEmpty (..))+import Data.String (IsString (..))+import Data.Text (Text)+import GHC.Generics (Generic)++import qualified Data.List.NonEmpty as NonEmpty+import qualified Data.Text as Text+++{- | Represents the key piece of some layer.++@since 0.0.0+-}+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++@+site."google.com"+@++is represented like++@+Key (Piece "site" :| [Piece "\\"google.com\\""])+@++@since 0.0.0+-}+newtype Key = Key+    { unKey :: NonEmpty Piece+    } deriving stock (Generic)+      deriving newtype (Show, Eq, Ord, Hashable, NFData, Semigroup)++{- | Type synonym for 'Key'.++@since 0.0.0+-}+type Prefix = Key++{- | Split a dot-separated string into 'Key'. Empty string turns into a 'Key'+with single element — empty 'Piece'.++This instance is not safe for now. Use carefully. If you try to use as a key+string like this @site.\"google.com\"@ you will have list of three components+instead of desired two.++@since 0.1.0+-}+instance IsString Key where+    fromString :: String -> Key+    fromString = \case+        "" -> Key ("" :| [])+        s  -> case Text.splitOn "." (fromString s) of+            []   -> error "Text.splitOn returned empty string"  -- can't happen+            x:xs -> coerce @(NonEmpty Text) @Key (x :| xs)++{- | Bidirectional pattern synonym for constructing and deconstructing 'Key's.+-}+pattern (:||) :: Piece -> [Piece] -> Key+pattern x :|| xs <- Key (x :| xs)+  where+    x :|| xs = Key (x :| xs)++{-# COMPLETE (:||) #-}++-- | Prepends 'Piece' to the beginning of the 'Key'.+(<|) :: Piece -> Key -> Key+(<|) p k = Key (p NonEmpty.<| unKey k)+{-# INLINE (<|) #-}++{- | Data represent difference between two keys.++@since 0.0.0+-}+data KeysDiff+    = Equal      -- ^ Keys are equal+    | NoPrefix   -- ^ Keys don't have any common part.+    | FstIsPref  -- ^ The first key is the prefix of the second one.+        !Key     -- ^ Rest of the second key.+    | SndIsPref  -- ^ The second key is the prefix of the first one.+        !Key     -- ^ Rest of the first key.+    | Diff       -- ^ Key have a common prefix.+        !Key     -- ^ Common prefix.+        !Key     -- ^ Rest of the first key.+        !Key     -- ^ Rest of the second key.+    deriving stock (Show, Eq)++{- | Find key difference between two keys.++@since 0.0.0+-}+keysDiff :: Key -> Key -> KeysDiff+keysDiff (x :|| xs) (y :|| ys)+    | x == y    = listSame xs ys []+    | otherwise = NoPrefix+  where+    listSame :: [Piece] -> [Piece] -> [Piece] -> KeysDiff+    listSame [] []     _ = Equal+    listSame [] (s:ss) _ = FstIsPref $ s :|| ss+    listSame (f:fs) [] _ = SndIsPref $ f :|| fs+    listSame (f:fs) (s:ss) pr =+        if f == s+        then listSame fs ss (pr ++ [f])+        else Diff (x :|| pr) (f :|| fs) (s :|| ss)
+ src/Toml/Type/PrefixTree.hs view
@@ -0,0 +1,167 @@+{-# LANGUAGE DeriveAnyClass  #-}+{-# LANGUAGE PatternSynonyms #-}++{- |+Copyright: (c) 2018-2020 Kowainik+SPDX-License-Identifier: MPL-2.0+Maintainer: Kowainik <xrom.xkov@gmail.com>++Implementation of prefix tree for TOML AST.++@since 0.0.0+-}++module Toml.Type.PrefixTree+    (+      -- * Non-empty prefix tree+      PrefixTree (..)+    , singleT+    , insertT+    , lookupT+    , toListT++      -- * Prefix map that stores roots of 'PrefixTree'+    , PrefixMap+    , single+    , insert+    , lookup+    , fromList+    , toList+    ) where++import Prelude hiding (lookup)++import Control.DeepSeq (NFData)+import Data.Bifunctor (first)+import Data.Foldable (foldl')+import Data.HashMap.Strict (HashMap)+import GHC.Generics (Generic)++import Toml.Type.Key (pattern (:||), Key, KeysDiff (..), Piece, Prefix, keysDiff, (<|))++import qualified Data.HashMap.Strict as HashMap+++{- | Map of layer names and corresponding 'PrefixTree's.++@since 0.0.0+-}+type PrefixMap a = HashMap Piece (PrefixTree a)++{- | Data structure to represent table tree for @toml@.++@since 0.0.0+-}+data PrefixTree a+    = Leaf             -- ^ End of a key.+        !Key           -- ^ End piece of the key.+        !a             -- ^ Value at the end.+    | Branch           -- ^ Values along pieces of a key.+        !Prefix        -- ^ Greatest common key prefix.+        !(Maybe a)     -- ^ Possible value at that point.+        !(PrefixMap a) -- ^ Values at suffixes of the prefix.+    deriving stock (Show, Eq, Generic)+    deriving anyclass (NFData)++-- | @since 0.3+instance Semigroup (PrefixTree a) where+    a <> b = foldl' (\tree (k, v) -> insertT k v tree) a (toListT b)+++{- | Creates a 'PrefixTree' of one key-value element.++@since 0.0.0+-}+singleT :: Key -> a -> PrefixTree a+singleT = Leaf+{-# INLINE singleT #-}++{- | Creates a 'PrefixMap' of one key-value element.++@since 0.0.0+-}+single :: Key -> a -> PrefixMap a+single k@(p :|| _) = HashMap.singleton p . singleT k++{- | Inserts key-value element into the given 'PrefixTree'.++@since 0.0.0+-}+insertT :: Key -> a -> PrefixTree a -> PrefixTree a+insertT newK newV (Leaf k v) =+    case keysDiff k newK of+        Equal -> Leaf k newV+        NoPrefix -> error "Algorithm error: can't be equal prefixes in insertT:Leaf case"+        FstIsPref rK -> Branch k (Just v) $ single rK newV+        SndIsPref lK -> Branch newK (Just newV) $ single lK v+        Diff p k1@(f :|| _) k2@(s :|| _) ->+          Branch p Nothing $ HashMap.fromList [(f, Leaf k1 v), (s, Leaf k2 newV)]+insertT newK newV (Branch pref mv prefMap) =+    case keysDiff pref newK of+        Equal    -> Branch pref (Just newV) prefMap+        NoPrefix -> error "Algorithm error: can't be equal prefixes in insertT:Branch case"+        FstIsPref rK -> Branch pref mv $ insert rK newV prefMap+        SndIsPref lK@(piece :|| _) ->+            Branch newK (Just newV) $ HashMap.singleton piece (Branch lK mv prefMap)+        Diff p k1@(f :|| _) k2@(s :|| _) ->+            Branch p Nothing $ HashMap.fromList [ (f, Branch k1 mv prefMap)+                                                , (s, Leaf k2 newV)+                                                ]++{- | Inserts key-value element into the given 'PrefixMap'.++@since 0.0.0+-}+insert :: Key -> a -> PrefixMap a -> PrefixMap a+insert k@(p :|| _) v prefMap = case HashMap.lookup p prefMap of+    Just tree -> HashMap.insert p (insertT k v tree) prefMap+    Nothing   -> HashMap.insert p (singleT k v) prefMap++{- | Looks up the value at a key in the 'PrefixTree'.++@since 0.0.0+-}+lookupT :: Key -> PrefixTree a -> Maybe a+lookupT lk (Leaf k v) = if lk == k then Just v else Nothing+lookupT lk (Branch pref mv prefMap) =+    case keysDiff pref lk of+        Equal       -> mv+        NoPrefix    -> Nothing+        Diff _ _ _  -> Nothing+        SndIsPref _ -> Nothing+        FstIsPref k -> lookup k prefMap++{- | Looks up the value at a key in the 'PrefixMap'.++@since 0.0.0+-}+lookup :: Key -> PrefixMap a -> Maybe a+lookup k@(p :|| _) prefMap = HashMap.lookup p prefMap >>= lookupT k++{- | Constructs 'PrefixMap' structure from the given list of 'Key' and value pairs.++@since 0.0.0+-}+fromList :: [(Key, a)] -> PrefixMap a+fromList = foldl' insertPair mempty+  where+    insertPair :: PrefixMap a -> (Key, a) -> PrefixMap a+    insertPair prefMap (k, v) = insert k v prefMap++{- | Converts 'PrefixTree' to the list of pairs.++@since 0.0.0+-}+toListT :: PrefixTree a -> [(Key, a)]+toListT (Leaf k v) = [(k, v)]+toListT (Branch pref ma prefMap) = case ma of+    Just a  -> (:) (pref, a)+    Nothing -> id+    $ map (\(k, v) -> (pref <> k, v)) $ toList prefMap++{- | Converts 'PrefixMap' to the list of pairs.++@since 0.0.0+-}+toList :: PrefixMap a -> [(Key, a)]+toList = concatMap (\(p, tr) -> first (p <|) <$> toListT tr) . HashMap.toList
+ src/Toml/Type/Printer.hs view
@@ -0,0 +1,229 @@+{-# LANGUAGE TypeFamilies #-}++{- |+Copyright: (c) 2018-2020 Kowainik+SPDX-License-Identifier: MPL-2.0+Maintainer: Kowainik <xrom.xkov@gmail.com>++Contains functions for pretty printing @toml@ types.++@since 0.0.0+-}++module Toml.Type.Printer+       ( PrintOptions(..)+       , defaultOptions+       , pretty+       , prettyOptions+       , prettyKey+       ) where++import Data.Bifunctor (first)+import Data.Coerce (coerce)+import Data.Function (on)+import Data.HashMap.Strict (HashMap)+import Data.List (sortBy)+import Data.List.NonEmpty (NonEmpty)+import Data.Text (Text)+import Data.Time (ZonedTime, defaultTimeLocale, formatTime)++import Toml.Type.AnyValue (AnyValue (..))+import Toml.Type.Key (Key (..), Piece (..))+import Toml.Type.PrefixTree (PrefixMap, PrefixTree (..))+import Toml.Type.TOML (TOML (..))+import Toml.Type.Value (Value (..))++import qualified Data.HashMap.Strict as HashMap+import qualified Data.List.NonEmpty as NonEmpty+import qualified Data.Text as Text+++{- | Configures the pretty printer.++@since 0.5.0+-}+data PrintOptions = PrintOptions+    { {- | How table keys should be sorted, if at all.++      @since 1.1.0.0+      -}+      printOptionsSorting :: !(Maybe (Key -> Key -> Ordering))++      {- | Number of spaces by which to indent.++      @since 1.1.0.0+      -}+    , printOptionsIndent  :: !Int+    }++{- | Default printing options.++1. Sorts all keys and tables by name.+2. Indents with 2 spaces.++@since 0.5.0+-}+defaultOptions :: PrintOptions+defaultOptions = PrintOptions (Just compare) 2++{- | Converts 'TOML' type into 'Data.Text.Text' (using 'defaultOptions').++For example, this++@+TOML+    { tomlPairs  = HashMap.fromList+          [("title", AnyValue $ Text "TOML example")]+    , tomlTables = PrefixTree.fromList+          [( "example" <| "owner"+           , mempty+                 { tomlPairs  = HashMap.fromList+                       [("name", AnyValue $ Text "Kowainik")]+                 }+           )]+    , tomlTableArrays = mempty+    }+@++will be translated to this++@+title = "TOML Example"++[example.owner]+  name = \"Kowainik\"+@++@since 0.0.0+-}+pretty :: TOML -> Text+pretty = prettyOptions defaultOptions++{- | Converts 'TOML' type into 'Data.Text.Text' using provided 'PrintOptions'++@since 0.5.0+-}+prettyOptions :: PrintOptions -> TOML -> Text+prettyOptions options = Text.unlines . prettyTomlInd options 0 ""++-- | Converts 'TOML' into a list of 'Data.Text.Text' elements with the given indent.+prettyTomlInd :: PrintOptions -- ^ Printing options+              -> Int          -- ^ Current indentation+              -> Text         -- ^ Accumulator for table names+              -> TOML         -- ^ Given 'TOML'+              -> [Text]       -- ^ Pretty result+prettyTomlInd options i prefix TOML{..} = concat+    [ prettyKeyValue    options i tomlPairs+    , prettyTables      options i prefix tomlTables+    , prettyTableArrays options i prefix tomlTableArrays+    ]++{- | Converts a key to text++@since 0.0.0+-}+prettyKey :: Key -> Text+prettyKey = Text.intercalate "." . NonEmpty.toList . coerce+{-# INLINE prettyKey #-}++-- | Returns pretty formatted  key-value pairs of the 'TOML'.+prettyKeyValue :: PrintOptions -> Int -> HashMap Key AnyValue -> [Text]+prettyKeyValue options i = mapOrdered (\kv -> [kvText kv]) options . HashMap.toList+  where+    kvText :: (Key, AnyValue) -> Text+    kvText (k, AnyValue v) =+      tabWith options i <> prettyKey k <>  " = " <> valText v++    valText :: Value t -> Text+    valText (Bool b)    = Text.toLower $ showText b+    valText (Integer n) = showText n+    valText (Double d)  = showDouble d+    valText (Text s)    = showText s+    valText (Zoned z)   = showZonedTime z+    valText (Local l)   = showText l+    valText (Day d)     = showText d+    valText (Hours h)   = showText h+    valText (Array a)   = "[" <> Text.intercalate ", " (map valText a) <> "]"++    showText :: Show a => a -> Text+    showText = Text.pack . show++    showDouble :: Double -> Text+    showDouble d | isInfinite d && d < 0 = "-inf"+                 | isInfinite d = "inf"+                 | isNaN d = "nan"+                 | otherwise = showText d++    showZonedTime :: ZonedTime -> Text+    showZonedTime t = Text.pack $ showZonedDateTime t <> showZonedZone t+      where+        showZonedDateTime = formatTime defaultTimeLocale "%FT%T%Q"+        showZonedZone+            = (\(x,y) -> x ++ ":" ++ y)+            . (\z -> splitAt (length z - 2) z)+            . formatTime defaultTimeLocale "%z"++-- | Returns pretty formatted tables section of the 'TOML'.+prettyTables :: PrintOptions -> Int -> Text -> PrefixMap TOML -> [Text]+prettyTables options i pref asPieces = mapOrdered (prettyTable . snd) options asKeys+  where+    asKeys :: [(Key, PrefixTree TOML)]+    asKeys = map (first pieceToKey) $ HashMap.toList asPieces++    pieceToKey :: Piece -> Key+    pieceToKey = Key . pure++    prettyTable :: PrefixTree TOML -> [Text]+    prettyTable (Leaf k toml) =+        let name = addPrefix k pref+        -- Each "" results in an empty line, inserted above table names+        in "": tabWith options i <> prettyTableName name :+        -- We don't want empty lines between a table name and a subtable name+             dropWhile (== "") (prettyTomlInd options (i + 1) name toml)++    prettyTable (Branch k mToml prefMap) =+        let name  = addPrefix k pref+            nextI = i + 1+            toml  = case mToml of+                        Nothing -> []+                        Just t  -> prettyTomlInd options nextI name t+        -- Each "" results in an empty line, inserted above table names+        in "": tabWith options i <> prettyTableName name :+        -- We don't want empty lines between a table name and a subtable name+             dropWhile (== "") (toml ++ prettyTables options nextI name prefMap)++    prettyTableName :: Text -> Text+    prettyTableName n = "[" <> n <> "]"++prettyTableArrays :: PrintOptions -> Int -> Text -> HashMap Key (NonEmpty TOML) -> [Text]+prettyTableArrays options i pref = mapOrdered arrText options . HashMap.toList+  where+    arrText :: (Key, NonEmpty TOML) -> [Text]+    arrText (k, ne) =+      let name = addPrefix k pref+          render toml =+            -- Each "" results in an empty line, inserted above array names+            "": tabWith options i <> "[[" <> name <> "]]" :+            -- We don't want empty lines between an array name and a subtable name+              dropWhile (== "") (prettyTomlInd options (i + 1) name toml)+      in concatMap render $ NonEmpty.toList ne++-----------------------------------------------------+-- Helper functions+-----------------------------------------------------++-- Returns an indentation prefix+tabWith :: PrintOptions -> Int -> Text+tabWith PrintOptions{..} n = Text.replicate (n * printOptionsIndent) " "++-- Returns a proper sorting function+mapOrdered :: ((Key, v) -> [t]) -> PrintOptions -> [(Key, v)] -> [t]+mapOrdered f options = case printOptionsSorting options of+    Just sorter -> concatMap f . sortBy (sorter `on` fst)+    Nothing     -> concatMap f++-- Adds next part of the table name to the accumulator.+addPrefix :: Key -> Text -> Text+addPrefix key = \case+    "" -> prettyKey key+    prefix -> prefix <> "." <> prettyKey key
src/Toml/Type/TOML.hs view
@@ -1,7 +1,7 @@ {-# LANGUAGE DeriveAnyClass #-}  {- |-Copyright: (c) 2018-2019 Kowainik+Copyright: (c) 2018-2020 Kowainik SPDX-License-Identifier: MPL-2.0 Maintainer: Kowainik <xrom.xkov@gmail.com> @@ -19,15 +19,15 @@ import Control.DeepSeq (NFData) import Data.HashMap.Strict (HashMap) import Data.List.NonEmpty (NonEmpty)-import Data.Semigroup (Semigroup (..)) import GHC.Generics (Generic) -import Toml.PrefixTree (Key (..), PrefixMap) import Toml.Type.AnyValue (AnyValue (..))+import Toml.Type.Key (Key (..))+import Toml.Type.PrefixTree (PrefixMap) import Toml.Type.Value (Value)  import qualified Data.HashMap.Strict as HashMap-import qualified Toml.PrefixTree as Prefix+import qualified Toml.Type.PrefixTree as Prefix   {- | Represents TOML configuration value.@@ -90,6 +90,8 @@         ]     } @++@since 0.0.0 -} data TOML = TOML     { tomlPairs       :: !(HashMap Key AnyValue)@@ -98,6 +100,7 @@     } deriving stock (Show, Eq, Generic)       deriving anyclass (NFData) +-- | @since 0.3 instance Semigroup TOML where     (<>) :: TOML -> TOML -> TOML     TOML pairsA tablesA arraysA <> TOML pairsB tablesB arraysB = TOML@@ -106,6 +109,7 @@         (arraysA <> arraysB)     {-# INLINE (<>) #-} +-- | @since 0.3 instance Monoid TOML where     mempty :: TOML     mempty = TOML mempty mempty mempty
src/Toml/Type/UValue.hs view
@@ -1,11 +1,13 @@ {-# LANGUAGE GADTs #-}  {- |-Copyright: (c) 2018-2019 Kowainik+Copyright: (c) 2018-2020 Kowainik SPDX-License-Identifier: MPL-2.0 Maintainer: Kowainik <xrom.xkov@gmail.com>  Intermediate untype value representation used for parsing.++@since 0.0.0 -}  module Toml.Type.UValue@@ -21,8 +23,11 @@ import Toml.Type.Value (TypeMismatchError, Value (..), sameValue)  --- | Untyped value of @TOML@. You shouldn't use this type in your code. Use--- 'Value' instead.+{- | Untyped value of @TOML@. You shouldn't use this type in your+code. Use 'Value' instead.++@since 0.0.0+-} data UValue     = UBool !Bool     | UInteger !Integer@@ -35,6 +40,7 @@     | UArray ![UValue]     deriving stock (Show) +-- | @since 0.0.0 instance Eq UValue where     (UBool b1)    == (UBool b2)    = b1 == b2     (UInteger i1) == (UInteger i2) = i1 == i2@@ -49,7 +55,10 @@     (UArray a1)   == (UArray a2)   = a1 == a2     _             == _             = False --- | Ensures that 'UValue's represents type-safe version of @toml@.+{- | Ensures that 'UValue's represents type-safe version of @toml@.++@since 0.0.0+-} typeCheck :: UValue -> Either TypeMismatchError AnyValue typeCheck (UBool b)    = rightAny $ Bool b typeCheck (UInteger n) = rightAny $ Integer n
src/Toml/Type/Value.hs view
@@ -7,11 +7,13 @@ {-# LANGUAGE TypeOperators      #-}  {- |-Copyright: (c) 2018-2019 Kowainik+Copyright: (c) 2018-2020 Kowainik SPDX-License-Identifier: MPL-2.0 Maintainer: Kowainik <xrom.xkov@gmail.com>  GADT value for TOML.++@since 0.0.0 -}  module Toml.Type.Value@@ -37,7 +39,10 @@ import GHC.Generics (Generic)  --- | Needed for GADT parameterization+{- | Needed for GADT parameterization++@since 0.0.0+-} data TValue     = TBool     | TInteger@@ -51,11 +56,17 @@     deriving stock (Eq, Show, Read, Generic)     deriving anyclass (NFData) --- | Convert 'TValue' constructors to 'String' without @T@ prefix.+{- | Convert 'TValue' constructors to 'String' without @T@ prefix.++@since 0.0.0+-} showType :: TValue -> String showType = drop 1 . show --- | Value in @key = value@ pair.+{- | Value in @key = value@ pair.++@since 0.0.0+-} data Value (t :: TValue) where     {- | Boolean value: @@ -184,6 +195,7 @@     -}     Array  :: [Value t] -> Value 'TArray +-- | @since 0.0.0 deriving stock instance Show (Value t)  instance NFData (Value t) where@@ -222,7 +234,10 @@     (Hours a)    == (Hours b)    = a == b     (Array a1)   == (Array a2)   = eqValueList a1 a2 --- | Compare list of 'Value' of possibly different types.+{- | Compare list of 'Value' of possibly different types.++@since 0.0.0+-} eqValueList :: [Value a] -> [Value b] -> Bool eqValueList [] [] = True eqValueList (x:xs) (y:ys) = case sameValue x y of@@ -230,8 +245,12 @@     Left _     -> False eqValueList _ _ = False --- | Reifies type of 'Value' into 'TValue'. Unfortunately, there's no way to--- guarantee that 'valueType' will return @t@ for object with type @Value \'t@.+{- | Reifies type of 'Value' into 'TValue'. Unfortunately, there's no+way to guarantee that 'valueType' will return @t@ for object with type+@Value \'t@.++@since 0.0.0+-} valueType :: Value t -> TValue valueType (Bool _)    = TBool valueType (Integer _) = TInteger@@ -247,12 +266,16 @@ -- Typechecking values ---------------------------------------------------------------------------- --- | Data type that holds expected vs. actual type.+{- | Data type that holds expected vs. actual type.++@since 0.1.0+-} data TypeMismatchError = TypeMismatchError   { typeExpected :: !TValue   , typeActual   :: !TValue   } deriving stock (Eq) +-- | @since 0.1.0 instance Show TypeMismatchError where     show TypeMismatchError{..} = "Expected type '" ++ showType typeExpected                               ++ "' but actual type: '" ++ showType typeActual ++ "'"@@ -260,6 +283,8 @@ {- | Checks whether two values are the same. This function is used for type checking where first argument is expected type and second argument is actual type.++@since 0.0.0 -} sameValue :: Value a -> Value b -> Either TypeMismatchError (a :~: b) sameValue Bool{}    Bool{}    = Right Refl
test/Spec.hs view
@@ -1,1 +1,20 @@-{-# OPTIONS_GHC -F -pgmF tasty-discover #-}+module Main (main) where++import Test.Hspec (hspec, parallel)+import Test.Hspec.Hedgehog (modifyMaxDiscardRatio)++import Test.Toml.Codec (codecSpec)+import Test.Toml.Parser (parserSpec)+import Test.Toml.Type (typeSpec)+++{- Default QuickCheck discard Ratio is 10 while @hedgehog@s is 100.+So we need to modify it manually in here.++See issue: <https://github.com/parsonsmatt/hspec-hedgehog/issues/9>+-}+main :: IO ()+main = hspec $ modifyMaxDiscardRatio (+ 90) $ parallel $ do+    typeSpec+    parserSpec+    codecSpec
− test/Test/Toml/BiCode/Property.hs
@@ -1,206 +0,0 @@-module Test.Toml.BiCode.Property where--import Control.Applicative (liftA2, (<|>))-import Control.Category ((>>>))-import Data.ByteString (ByteString)-import Data.HashSet (HashSet)-import Data.IntSet (IntSet)-import Data.List.NonEmpty (NonEmpty)-import Data.Map.Strict (Map)-import Data.Monoid (All (..), Any (..), First (..), Last (..), Product (..), Sum (..))-import Data.Set (Set)-import Data.Text (Text)-import Data.Time (Day, LocalTime, TimeOfDay, ZonedTime, zonedTimeToUTC)-import GHC.Exts (fromList)-import Hedgehog (Gen, forAll, tripping)-import Numeric.Natural (Natural)--import Toml (TomlBiMap, TomlCodec, (.=))-import Toml.Bi.Code (decode, encode)--import Test.Toml.Gen (PropertyTest, genBool, genByteString, genDay, genDouble, genFloat, genHashSet,-                      genHours, genInt, genIntSet, genInteger, genLByteString, genLocal, genNatural,-                      genNonEmpty, genString, genText, genWord, genZoned, prop)--import qualified Data.ByteString.Lazy as L-import qualified Hedgehog.Gen as Gen-import qualified Hedgehog.Range as Range-import qualified Toml---test_encodeDecodeProp :: PropertyTest-test_encodeDecodeProp = prop "decode . encode == id" $ do-    bigType <- forAll genBigType-    tripping bigType (encode bigTypeCodec) (decode bigTypeCodec)--data BigType = BigType-    { btBool          :: !Bool-    , btInteger       :: !Integer-    , btNatural       :: !Natural-    , btInt           :: !Int-    , btWord          :: !Word-    , btDouble        :: !(Batman Double)-    , btFloat         :: !(Batman Float)-    , btText          :: !Text-    , btString        :: !String-    , btBS            :: !ByteString-    , btLazyBS        :: !L.ByteString-    , btLocalTime     :: !LocalTime-    , btDay           :: !Day-    , btTimeOfDay     :: !TimeOfDay-    , btArray         :: ![Int]-    , btArraySet      :: !(Set Word)-    , btArrayIntSet   :: !IntSet-    , btArrayHashSet  :: !(HashSet Natural)-    , btArrayNonEmpty :: !(NonEmpty Text)-    , btNonEmpty      :: !(NonEmpty ByteString)-    , btList          :: ![Bool]-    , btNewtype       :: !BigTypeNewtype-    , btSumType       :: !BigTypeSum-    , btRecord        :: !BigTypeRecord-    , btMap           :: !(Map Int Bool)-    , btAll           :: !All-    , btAny           :: !Any-    , btSum           :: !(Sum Int)-    , btProduct       :: !(Product Int)-    , btFirst         :: !(First Int)-    , btLast          :: !(Last Int)-    } deriving stock (Show, Eq)---- | Wrapper over 'Double' and 'Float' to be equal on @NaN@ values.-newtype Batman a = Batman-    { unBatman :: a-    } deriving stock (Show)--instance RealFloat a => Eq (Batman a) where-    Batman a == Batman b =-        if isNaN a-            then isNaN b-            else a == b--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 stock (Show, Eq)--data BigTypeRecord = BigTypeRecord-    { btrBoolSet     :: !(Set Bool)-    , btrNewtypeList :: ![BigTypeSum]-    } deriving stock (Show, Eq)--bigTypeCodec :: TomlCodec BigType-bigTypeCodec = BigType-    <$> Toml.bool                            "bool"                .= btBool-    <*> Toml.integer                         "integer"             .= btInteger-    <*> Toml.natural                         "natural"             .= btNatural-    <*> Toml.int                             "int"                 .= btInt-    <*> Toml.word                            "word"                .= btWord-    <*> Toml.diwrap (Toml.double "double")                         .= btDouble-    <*> Toml.diwrap (Toml.float "float")                           .= btFloat-    <*> Toml.text                            "text"                .= btText-    <*> Toml.string                          "string"              .= btString-    <*> Toml.byteString                      "bs"                  .= btBS-    <*> Toml.lazyByteString                  "lbs"                 .= btLazyBS-    <*> Toml.localTime                       "localTime"           .= btLocalTime-    <*> Toml.day                             "day"                 .= btDay-    <*> Toml.timeOfDay                       "timeOfDay"           .= btTimeOfDay-    <*> Toml.arrayOf Toml._Int               "arrayOfInt"          .= btArray-    <*> Toml.arraySetOf Toml._Word           "arraySetOfWord"      .= btArraySet-    <*> Toml.arrayIntSet                     "arrayIntSet"         .= btArrayIntSet-    <*> Toml.arrayHashSetOf Toml._Natural    "arrayHashSetDouble"  .= btArrayHashSet-    <*> Toml.arrayNonEmptyOf Toml._Text      "arrayNonEmptyOfText" .= btArrayNonEmpty-    <*> Toml.nonEmpty (Toml.byteString "bs") "nonEmptyBS"          .= btNonEmpty-    <*> Toml.list (Toml.bool "bool")         "listBool"            .= btList-    <*> Toml.diwrap (Toml.zonedTime "nt.zonedTime")                .= btNewtype-    <*> bigTypeSumCodec                                            .= btSumType-    <*> Toml.table bigTypeRecordCodec        "table-record"        .= btRecord-    <*> Toml.map (Toml.int "key") (Toml.bool "val") "map"          .= btMap-    <*> Toml.all                             "all"                 .= btAll-    <*> Toml.any                             "any"                 .= btAny-    <*> Toml.sum Toml.int                    "sum"                 .= btSum-    <*> Toml.product Toml.int                "product"             .= btProduct-    <*> Toml.first Toml.int                  "first"               .= btFirst-    <*> Toml.last Toml.int                   "last"                .= btLast--_BigTypeSumA :: TomlBiMap BigTypeSum Integer-_BigTypeSumA = Toml.prism BigTypeSumA $ \case-    BigTypeSumA i -> Right i-    other   -> Toml.wrongConstructor "BigTypeSumA" other--_BigTypeSumB :: TomlBiMap BigTypeSum Text-_BigTypeSumB = Toml.prism BigTypeSumB $ \case-    BigTypeSumB n -> Right n-    other    -> Toml.wrongConstructor "BigTypeSumB" other--bigTypeSumCodec :: TomlCodec BigTypeSum-bigTypeSumCodec =-        Toml.match (_BigTypeSumA >>> Toml._Integer) "sum.integer"-    <|> Toml.match (_BigTypeSumB >>> Toml._Text)    "sum.text"--bigTypeRecordCodec :: TomlCodec BigTypeRecord-bigTypeRecordCodec = BigTypeRecord-    <$> Toml.arraySetOf Toml._Bool "rboolSet" .= btrBoolSet-    <*> Toml.list bigTypeSumCodec "rnewtype" .= btrNewtypeList--------------------------------------------------------------------------------- Generator-------------------------------------------------------------------------------genBigType :: Gen BigType-genBigType = do-    btBool          <- genBool-    btInteger       <- genInteger-    btNatural       <- genNatural-    btInt           <- genInt-    btWord          <- genWord-    btDouble        <- Batman <$> genDouble-    btFloat         <- Batman <$> genFloat-    btText          <- genText-    btString        <- genString-    btBS            <- genByteString-    btLazyBS        <- genLByteString-    btLocalTime     <- genLocal-    btDay           <- genDay-    btTimeOfDay     <- genHours-    btArray         <- Gen.list (Range.constant 0 5) genInt-    btArraySet      <- Gen.set (Range.constant 0 5) genWord-    btArrayIntSet   <- genIntSet-    btArrayHashSet  <- genHashSet genNatural-    btArrayNonEmpty <- genNonEmpty genText-    btNonEmpty      <- genNonEmpty genByteString-    btList          <- Gen.list (Range.constant 0 5) genBool-    btNewtype       <- genNewType-    btSumType       <- genSum-    btRecord        <- genRec-    btMap           <- Gen.map (Range.constant 0 10) (liftA2 (,) genInt genBool)-    btAll           <- All <$> genBool-    btAny           <- Any <$> genBool-    btSum           <- Sum <$> genInt-    btProduct       <- Product <$> genInt-    btFirst         <- First <$> Gen.maybe genInt-    btLast          <- Last <$> Gen.maybe genInt-    pure BigType {..}---- Custom generators--genNewType :: Gen BigTypeNewtype-genNewType = BigTypeNewtype <$> genZoned--genSum :: Gen BigTypeSum-genSum = Gen.choice-    [ BigTypeSumA <$> genInteger-    , BigTypeSumB <$> genText-    ]--genRec :: Gen BigTypeRecord-genRec = do-    btrBoolSet <- fromList <$> Gen.list (Range.constant 0 5) genBool-    btrNewtypeList <- Gen.list (Range.constant 0 5) genSum-    pure BigTypeRecord{..}
− test/Test/Toml/BiMap/Property.hs
@@ -1,67 +0,0 @@-{-# OPTIONS_GHC -fno-warn-orphans #-}--module Test.Toml.BiMap.Property where--import Hedgehog (Gen, PropertyT, assert, forAll, tripping, (===))--import Data.Time (ZonedTime (..))-import Test.Tasty (testGroup)-import Test.Toml.Gen (PropertyTest, prop)-import Toml.Bi.Map (BiMap (..), TomlBiMap)--import qualified Hedgehog.Gen as Gen-import qualified Test.Toml.Gen as G-import qualified Toml.Bi.Map as B---testBiMap-    :: (Monad m, Show a, Show b, Eq a)-    => TomlBiMap a b-    -> Gen a-    -> PropertyT m ()-testBiMap bimap gen = do-    x <- forAll gen-    tripping x (forward bimap) (backward bimap =<<)---- Double needs a special test because NaN /= NaN-testDouble :: PropertyT IO ()-testDouble = do-    x <- forAll G.genDouble-    if isNaN x-      then assert $-          fmap isNaN (forward B._Double x >>= backward B._Double) == Right True-      else (forward B._Double x >>= backward B._Double) === Right x--test_BiMaps :: PropertyTest-test_BiMaps = pure $ testGroup "BiMap roundtrip tests" $ concat-    [ prop "Bool" (testBiMap B._Bool G.genBool)-    , prop "Integer" (testBiMap B._Integer G.genInteger)-    , 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)-    , prop "LazyText" (testBiMap B._LText G.genLText)-    , prop "String" (testBiMap B._String G.genString)-    , 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)-    , prop "Day" (testBiMap B._Day G.genDay)-    , prop "IntSet" (testBiMap B._IntSet G.genIntSet)-    , prop "Array (Day)" (testBiMap (B._Array B._Day) (G.genList G.genDay))-    , prop "Set (Day)" (testBiMap (B._Set B._Day) (Gen.set G.range100 G.genDay))-    , prop "NonEmpty (Day)" (testBiMap (B._NonEmpty B._Day) (G.genNonEmpty G.genDay))-    , prop "HashSet (Integer)" (testBiMap (B._HashSet B._Integer) (G.genHashSet G.genInteger))-    ]---- Orphan instances--instance Eq ZonedTime where-  (ZonedTime a b) == (ZonedTime c d) = a == c && b == d
+ test/Test/Toml/Codec.hs view
@@ -0,0 +1,22 @@+module Test.Toml.Codec+    ( codecSpec+    ) where++import Test.Hspec (Spec, describe, parallel)++import Test.Toml.Codec.BiMap (biMapSpec)+import Test.Toml.Codec.Code (codeSpec)+import Test.Toml.Codec.Combinator (combinatorSpec)+import Test.Toml.Codec.Di (diSpec)+import Test.Toml.Codec.Generic (genericSpec)+import Test.Toml.Codec.SmallType (smallTypeSpec)+++codecSpec :: Spec+codecSpec = parallel $ describe "Codec: unit and property tests for bidirectional codecs" $ do+    biMapSpec+    codeSpec+    combinatorSpec+    diSpec+    genericSpec+    smallTypeSpec
+ test/Test/Toml/Codec/BiMap.hs view
@@ -0,0 +1,67 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}++module Test.Toml.Codec.BiMap+    ( biMapSpec+    ) where++import Prelude hiding (id, (.))++import Control.Category (id, (.))+import Data.Word (Word8)+import Hedgehog (Gen, PropertyT, forAll, (===))+import Test.Hspec (Spec, describe, it, parallel)+import Test.Hspec.Hedgehog (hedgehog)++import Test.Toml.Codec.BiMap.Conversion (conversionSpec)+import Test.Toml.Gen (genInteger, genText, genWord8)+import Toml.Codec.BiMap (BiMap (..), TomlBiMap)+import Toml.Codec.BiMap.Conversion (_BoundedInteger, _ReadString, _StringText)+++biMapSpec :: Spec+biMapSpec = parallel $ describe "Tagged Partial Bidirectional Isomorphism: tests" $ do+    conversionSpec+    categoryLaws++categoryLaws :: Spec+categoryLaws = describe "BiMap Category instance laws" $ do+    it "Right identity: f . id ≡ f" $ biMapEquality+        (_Word8Integer . id)+        _Word8Integer+        genWord8+        genInteger++    it "Left identity:  id . f ≡ f" $ biMapEquality+        _Word8Integer+        (_Word8Integer . id)+        genWord8+        genInteger++    it "Associativity:  f . (g . h) ≡ (f . g) . h" $ biMapEquality+        (_StringText . (_ReadString . _Word8Integer))+        ((_StringText . _ReadString) . _Word8Integer)+        genWord8+        genText++{- | Property that takes two 'BiMap's and checks them on equality. We+consider two 'BiMap's @m1@ and @m2@ equal if:++* @forward  m1 ≡ forward  m2@+* @backward m1 ≡ backward m2@+-}+biMapEquality+    :: (Eq a, Eq b, Show a, Show b)+    => TomlBiMap a b+    -> TomlBiMap a b+    -> Gen a+    -> Gen b+    -> PropertyT IO ()+biMapEquality m1 m2 genA genB = hedgehog $ do+    a <- forAll genA+    b <- forAll genB++    forward  m1 a === forward  m2 a+    backward m1 b === backward m2 b++_Word8Integer :: TomlBiMap Word8 Integer+_Word8Integer = _BoundedInteger
+ test/Test/Toml/Codec/BiMap/Conversion.hs view
@@ -0,0 +1,71 @@+{-# LANGUAGE PatternSynonyms #-}++module Test.Toml.Codec.BiMap.Conversion+    ( conversionSpec+    ) where++import Hedgehog (Gen, PropertyT, forAll, tripping)+import Test.Hspec (Spec, describe, it, parallel)+import Test.Hspec.Hedgehog (hedgehog)++import Test.Toml.Codec.Combinator.Common (Batman (..), _BatmanDouble)+import Toml.Codec.BiMap (BiMap (..), TomlBiMap, tShow)+import Toml.Type.Key (pattern (:||), Piece (..))++import qualified Hedgehog.Gen as Gen++import qualified Test.Toml.Gen as G+import qualified Toml.Codec.BiMap.Conversion as B+++conversionSpec :: Spec+conversionSpec = parallel $ describe "BiMap Rountrip Property tests" $ do+    describe "Primitive" $ do+        it "Bool"            $ testBiMap B._Bool G.genBool+        it "Int"             $ testBiMap B._Int G.genInt+        it "Word"            $ testBiMap B._Word G.genWord+        it "Word8"           $ testBiMap B._Word8 G.genWord8+        it "Integer"         $ testBiMap B._Integer G.genInteger+        it "Natural"         $ testBiMap B._Natural G.genNatural+        it "Double"          $ testBiMap _BatmanDouble (Batman <$> G.genDouble)+        it "Float"           $ testBiMap B._Float G.genFloat+        it "Text"            $ testBiMap B._Text G.genText+        it "LazyText"        $ testBiMap B._LText G.genLText+        it "ByteString"      $ testBiMap B._ByteString G.genByteString+        it "Lazy ByteString" $ testBiMap B._LByteString G.genLByteString+        it "String"          $ testBiMap B._String G.genString++    describe "Time" $ do+        it "ZonedTime" $ testBiMap B._ZonedTime G.genZonedTime+        it "LocalTime" $ testBiMap B._LocalTime G.genLocalTime+        it "Day"       $ testBiMap B._Day G.genDay+        it "TimeOfDay" $ testBiMap B._TimeOfDay G.genTimeOfDay++    describe "Arrays" $ do+        it "Array (Int)"      $ testBiMap (B._Array B._Int) (G.genList G.genInt)+        it "Array (Day)"      $ testBiMap (B._Array B._Day) (G.genList G.genDay)+        it "NonEmpty (Int)"   $ testBiMap (B._NonEmpty B._Int) (G.genNonEmpty G.genInt)+        it "Set (Int)"        $ testBiMap (B._Set B._Int) (G.genSet G.genInt)+        it "HashSet (Int)"    $ testBiMap (B._HashSet B._Int) (G.genHashSet G.genInt)+        it "IntSet"           $ testBiMap B._IntSet G.genIntSet+        it "ByteStringArray"  $ testBiMap B._ByteStringArray G.genByteString+        it "LByteStringArray" $ testBiMap B._LByteStringArray G.genLByteString++    describe "Custom" $ do+        it "EnumBounded (Ordering)" $ testBiMap B._EnumBounded $ Gen.enumBounded @_ @Ordering+        it "Read (Integer)"         $ testBiMap B._Read G.genInteger+        it "TextBy (Text)"          $ testBiMap (B._TextBy id Right) G.genText++    describe "Key" $ do+        it "KeyText"   $ testBiMap B._KeyText G.genKey+        it "KeyString" $ testBiMap B._KeyString G.genKey+        it "KeyInt"    $ testBiMap B._KeyInt ((:|| []) . Piece . tShow <$> G.genInt)++testBiMap+    :: (Show a, Show b, Eq a)+    => TomlBiMap a b+    -> Gen a+    -> PropertyT IO ()+testBiMap bimap gen = hedgehog $ do+    x <- forAll gen+    tripping x (forward bimap) (backward bimap =<<)
+ test/Test/Toml/Codec/Code.hs view
@@ -0,0 +1,110 @@+module Test.Toml.Codec.Code+    ( codeSpec+    ) where++import Test.Hspec (Spec, describe, it, shouldBe)++import Toml.Codec.BiMap (TomlBiMapError (..))+import Toml.Codec.Code (decode)+import Toml.Codec.Error (TomlDecodeError (..))+import Toml.Parser (TomlParseError (..))+import Toml.Type.AnyValue (AnyValue (..), MatchError (..))+import Toml.Type.Key (Key)+import Toml.Type.Value (TValue (..), Value (..))++import qualified Data.Map.Strict as Map+import qualified Data.Text as Text+import qualified Toml.Codec as Toml+++codeSpec :: Spec+codeSpec = describe "Codec.Code decode tests on different TomlDecodeErrors" $ do+    it "fails parse as unclosed bracket" $+        decode (Toml.int "a") "a = 'foo" `shouldBe`+            Left [ ParseError $ TomlParseError $ Text.unlines+                     [ "1:9:"+                     , "  |"+                     , "1 | a = 'foo"+                     , "  |         ^"+                     , "unexpected end of input"+                     , "expecting '''"+                     ]+                 ]+    it "fails decode text as Toml.int" $+        decode (Toml.int "a") "a = 'foo'" `shouldBe`+            Left [ matchErr "a" TInteger $ AnyValue $ Text "foo"]+    -- table+    it "fails to decode table on missing field" $+        decode (Toml.table (Toml.int "x") "foo") "" `shouldBe`+            Left [ TableNotFound "foo"]+    it "fails to decode table on missing field" $+        decode (Toml.table (Toml.int "x") "foo") "[foo]" `shouldBe`+            Left [ KeyNotFound "foo.x"]++    -- lists++    it "fails to decode arrayOf when field is missing" $+        decode (Toml.arrayOf Toml._Int "foo") "" `shouldBe`+            Left [KeyNotFound "foo"]+    it "fails to decode arrayNonEmptyOf when field is missing" $+        decode (Toml.arrayNonEmptyOf Toml._Int "foo") "" `shouldBe`+            Left [KeyNotFound "foo"]+    it "fails to decode arrayNonEmptyOf when list is empty" $+        decode (Toml.arrayNonEmptyOf Toml._Int "foo") "foo = []" `shouldBe`+            Left [BiMapError "foo" $ ArbitraryError "Empty array list, but expected NonEmpty"]++    it "decodes to an empty list when field is missing" $+        decode (Toml.list (Toml.int "i") "foo") "" `shouldBe`+            Right []+    it "decodes to an empty list when empty list" $+        decode (Toml.list (Toml.int "i") "foo") "foo = []" `shouldBe`+            Right []+    it "fails to decode list when internal field is missing" $+        decode (Toml.nonEmpty (Toml.int "i") "foo") "foo = [{a = 42}]" `shouldBe`+            Left [KeyNotFound "foo.i"]+    it "fails to decode nonEmpty when field is missing" $+        decode (Toml.nonEmpty (Toml.int "i") "foo") "" `shouldBe`+            Left [TableArrayNotFound "foo"]+    it "fails to decode nonEmpty  when empty list" $+        decode (Toml.nonEmpty (Toml.int "i") "foo") "foo = []" `shouldBe`+            Left [TableArrayNotFound "foo"]++    -- map+    it "map: decodes to an empty map when field is missing" $+        decode (Toml.map (Toml.int "key") (Toml.text "val") "foo") "" `shouldBe`+            Right Map.empty+    it "map: fails to decode when key and value mismatch type" $+        decode+            (Toml.map (Toml.int "key") (Toml.text "val") "foo")+            "foo = [{key = 'a', val = 42}]"+            `shouldBe` Left+            [ matchErr "key" TInteger $ AnyValue $ Text "a"+            , matchErr "val" TText $ AnyValue $ Integer 42+            ]+    it "map: fails to decode when value is missing" $+        decode+            (Toml.map (Toml.int "key") (Toml.text "val") "foo")+            "foo = [{key = 42}]"+            `shouldBe` Left [ KeyNotFound "val"]++    it "tableMap: decodes to an empty map when field is missing" $+        decode (Toml.tableMap Toml._KeyText Toml.int "foo") "" `shouldBe`+            Right Map.empty+    it "tableMap: throws error on invalid key type" $+        decode (Toml.tableMap Toml._KeyInt Toml.text "foo")+            "foo = {a = 'a'}"+            `shouldBe` Left [BiMapError "a" $ ArbitraryError "Prelude.read: no parse"]++    -- custom+    it "fails to decode via read when non-existing value" $+        decode (Toml.read @Ordering "foo") "foo = 'EQUAL'" `shouldBe`+            Left [BiMapError "foo" $ ArbitraryError "Prelude.read: no parse"]+    it "fails to decode via enumBounded when non-existing value" $+        decode (Toml.enumBounded @Ordering "foo") "foo = 'EQUAL'" `shouldBe`+            Left [BiMapError "foo" $ ArbitraryError "Value is 'EQUAL' but expected one of: LT, EQ, GT"]+    it "fails to validateIf" $+        decode (Toml.validateIf even Toml._Int "foo") "foo = 5" `shouldBe`+            Left [BiMapError "foo" $ ArbitraryError "Value does not pass the validation for key: foo"]+  where+    matchErr :: Key -> TValue -> AnyValue -> TomlDecodeError+    matchErr key valueExpected valueActual = BiMapError key $ WrongValue $ MatchError {..}
+ test/Test/Toml/Codec/Combinator.hs view
@@ -0,0 +1,28 @@+module Test.Toml.Codec.Combinator+    ( combinatorSpec+    ) where++import Test.Hspec (Spec, describe, parallel)++import Test.Toml.Codec.Combinator.Custom (customSpec)+import Test.Toml.Codec.Combinator.List (listSpec)+import Test.Toml.Codec.Combinator.Map (mapSpec)+import Test.Toml.Codec.Combinator.Monoid (monoidSpec)+import Test.Toml.Codec.Combinator.Primitive (primitiveSpec)+import Test.Toml.Codec.Combinator.Set (setSpec)+import Test.Toml.Codec.Combinator.Table (tableSpec)+import Test.Toml.Codec.Combinator.Time (timeSpec)+import Test.Toml.Codec.Combinator.Tuple (tupleSpec)+++combinatorSpec :: Spec+combinatorSpec = parallel $ describe "Combinator spec" $ do+    customSpec+    listSpec+    mapSpec+    monoidSpec+    primitiveSpec+    setSpec+    tableSpec+    timeSpec+    tupleSpec
+ test/Test/Toml/Codec/Combinator/Common.hs view
@@ -0,0 +1,61 @@+module Test.Toml.Codec.Combinator.Common+    ( codecRoundtrip++      -- * Double helpers+    , Batman (..)+    , _BatmanDouble+    , batmanDoubleCodec+    , batmanFloatCodec+    ) where++import Hedgehog (Gen, forAll, tripping)+import Test.Hspec (Arg, Expectation, SpecWith, it)+import Test.Hspec.Hedgehog (hedgehog)++import Toml.Codec.BiMap (TomlBiMap)+import Toml.Codec.Code (decode, encode)+import Toml.Codec.Types (TomlCodec)+import Toml.Type.AnyValue (AnyValue)+import Toml.Type.Key (Key)+++import qualified Toml.Codec as Toml+++codecRoundtrip+    :: forall a+    .  (Eq a, Show a)+    => String+    -> (Key -> TomlCodec a)+    -> Gen a+    -> SpecWith (Arg Expectation)+codecRoundtrip typeName mkCodec genA = it label $ hedgehog $ do+    a <- forAll genA+    let codec = mkCodec "a"+    tripping a (encode codec) (decode codec)+  where+    label :: String+    label = typeName ++ ": decode . encode ≡ id"++-- | Wrapper over 'Double' and 'Float' to be equal on @NaN@ values.+newtype Batman a = Batman+    { unBatman :: a+    } deriving stock (Show)++instance Toml.HasCodec a => Toml.HasCodec (Batman a) where+    hasCodec = Toml.diwrap . Toml.hasCodec @a++instance RealFloat a => Eq (Batman a) where+    Batman a == Batman b =+        if isNaN a+            then isNaN b+            else a == b++_BatmanDouble :: TomlBiMap (Batman Double) AnyValue+_BatmanDouble = Toml._Coerce Toml._Double++batmanDoubleCodec :: Key -> TomlCodec (Batman Double)+batmanDoubleCodec = Toml.match _BatmanDouble++batmanFloatCodec :: Key -> TomlCodec (Batman Float)+batmanFloatCodec = Toml.diwrap . Toml.float
+ test/Test/Toml/Codec/Combinator/Custom.hs view
@@ -0,0 +1,27 @@+module Test.Toml.Codec.Combinator.Custom+    ( customSpec+    ) where++import Data.Bifunctor (first)+import Test.Hspec (Spec, describe)+import Text.Read (readEither)++import Test.Toml.Codec.Combinator.Common (codecRoundtrip)++import qualified Data.Text as Text+import qualified Hedgehog.Gen as Gen+import qualified Test.Toml.Gen as Gen+import qualified Toml.Codec.BiMap.Conversion as Toml+import qualified Toml.Codec.Combinator.Custom as Toml+++customSpec :: Spec+customSpec = describe "Combinator.Custom: Roundtrip tests" $ do+    codecRoundtrip "Read (Int)      " (Toml.read @Int) Gen.genInt+    codecRoundtrip "Enum (Ordering) " (Toml.enumBounded @Ordering) Gen.enumBounded+    codecRoundtrip "Validate (Even) "+        (Toml.validateIf even Toml._Int)+        ((* 2) <$> Gen.genInt)+    codecRoundtrip "TextBy          "+        (Toml.textBy (Text.pack . show) (first Text.pack . readEither . Text.unpack))+        Gen.genInt
+ test/Test/Toml/Codec/Combinator/List.hs view
@@ -0,0 +1,28 @@+module Test.Toml.Codec.Combinator.List+    ( listSpec+    ) where++import Test.Hspec (Spec, describe, parallel)++import Test.Toml.Codec.Combinator.Common (codecRoundtrip)++import qualified Test.Toml.Gen as Gen+import qualified Toml.Codec.BiMap.Conversion as Toml+import qualified Toml.Codec.Combinator.List as Toml+import qualified Toml.Codec.Combinator.Primitive as Toml+++listSpec :: Spec+listSpec = parallel $ describe "Combinator.List: Roundtrip tests" $ do+    codecRoundtrip "[Int] (Array)       "+        (Toml.arrayOf Toml._Int)+        (Gen.genList Gen.genInt)+    codecRoundtrip "NonEmpty Int (Array)"+        (Toml.arrayNonEmptyOf Toml._Int)+        (Gen.genNonEmpty Gen.genInt)+    codecRoundtrip "[Int] (Table)       "+        (Toml.list $ Toml.int "a")+        (Gen.genList Gen.genInt)+    codecRoundtrip "NonEmpty Int (Table)"+        (Toml.nonEmpty $ Toml.int "a")+        (Gen.genNonEmpty Gen.genInt)
+ test/Test/Toml/Codec/Combinator/Map.hs view
@@ -0,0 +1,23 @@+module Test.Toml.Codec.Combinator.Map+    ( mapSpec+    ) where++import Test.Hspec (Spec, describe, parallel)++import Test.Toml.Codec.Combinator.Common (codecRoundtrip)+import Toml.Type.Printer (prettyKey)++import qualified Test.Toml.Gen as Gen+import qualified Toml.Codec.BiMap.Conversion as Toml+import qualified Toml.Codec.Combinator.Map as Toml+import qualified Toml.Codec.Combinator.Primitive as Toml+++mapSpec :: Spec+mapSpec = parallel $ describe "Combinator.Map: Roundtrip tests" $ do+    codecRoundtrip "Map Int Text (map)     "+        (Toml.map (Toml.int "key") (Toml.text "val"))+        (Gen.genMap Gen.genInt Gen.genText)+    codecRoundtrip "Map Text Int (tableMap)"+        (Toml.tableMap Toml._KeyText Toml.int)+        (Gen.genMap (prettyKey <$> Gen.genKey) Gen.genInt)
+ test/Test/Toml/Codec/Combinator/Monoid.hs view
@@ -0,0 +1,24 @@+module Test.Toml.Codec.Combinator.Monoid+    ( monoidSpec+    ) where++import Data.Monoid (All (..), Any (..), First (..), Last (..), Product (..), Sum (..))+import Test.Hspec (Spec, describe)++import Test.Toml.Codec.Combinator.Common (codecRoundtrip)++import qualified Hedgehog.Gen as Gen++import qualified Test.Toml.Gen as Gen+import qualified Toml.Codec.Combinator.Monoid as Toml+import qualified Toml.Codec.Combinator.Primitive as Toml+++monoidSpec :: Spec+monoidSpec = describe "Combinator.Monoid: Roundtrip tests" $ do+    codecRoundtrip "All        " Toml.all                (All     <$> Gen.genBool)+    codecRoundtrip "Any        " Toml.any                (Any     <$> Gen.genBool)+    codecRoundtrip "Sum     Int" (Toml.sum Toml.int)     (Sum     <$> Gen.genInt)+    codecRoundtrip "Product Int" (Toml.product Toml.int) (Product <$> Gen.genInt)+    codecRoundtrip "First   Int" (Toml.first Toml.int)   (First   <$> Gen.maybe Gen.genInt)+    codecRoundtrip "Last    Int" (Toml.last Toml.int)    (Last    <$> Gen.maybe Gen.genInt)
+ test/Test/Toml/Codec/Combinator/Primitive.hs view
@@ -0,0 +1,30 @@+module Test.Toml.Codec.Combinator.Primitive+    ( primitiveSpec+    ) where++import Test.Hspec (Spec, describe, parallel)++import Test.Toml.Codec.Combinator.Common (Batman (..), batmanDoubleCodec, batmanFloatCodec,+                                          codecRoundtrip)++import qualified Test.Toml.Gen as Gen+import qualified Toml.Codec.Combinator.Primitive as Toml+++primitiveSpec :: Spec+primitiveSpec = parallel $ describe "Combinator.Primitive: Roundtrip tests" $ do+    codecRoundtrip "Bool             " Toml.bool                Gen.genBool+    codecRoundtrip "Integer          " Toml.integer             Gen.genInteger+    codecRoundtrip "Int              " Toml.int                 Gen.genInt+    codecRoundtrip "Natural          " Toml.natural             Gen.genNatural+    codecRoundtrip "Word             " Toml.word                Gen.genWord+    codecRoundtrip "Word8            " Toml.word8               Gen.genWord8+    codecRoundtrip "Double           " batmanDoubleCodec        (Batman <$> Gen.genDouble)+    codecRoundtrip "Float            " batmanFloatCodec         (Batman <$> Gen.genFloat)+    codecRoundtrip "String           " Toml.string              Gen.genString+    codecRoundtrip "Text             " Toml.text                Gen.genText+    codecRoundtrip "LText            " Toml.lazyText            Gen.genLText+    codecRoundtrip "ByteString       " Toml.byteString          Gen.genByteString+    codecRoundtrip "LByteString      " Toml.lazyByteString      Gen.genLByteString+    codecRoundtrip "ByteString Array " Toml.byteStringArray     Gen.genByteString+    codecRoundtrip "LByteString Array" Toml.lazyByteStringArray Gen.genLByteString
+ test/Test/Toml/Codec/Combinator/Set.hs view
@@ -0,0 +1,30 @@+module Test.Toml.Codec.Combinator.Set+    ( setSpec+    ) where++import Test.Hspec (Spec, describe, parallel)++import Test.Toml.Codec.Combinator.Common (codecRoundtrip)++import qualified Data.HashSet as HashSet+import qualified Data.Set as Set+import qualified Test.Toml.Gen as Gen+import qualified Toml.Codec.BiMap.Conversion as Toml+import qualified Toml.Codec.Combinator.Primitive as Toml+import qualified Toml.Codec.Combinator.Set as Toml+++setSpec :: Spec+setSpec = parallel $ describe "Combinator.Set: Roundtrip tests" $ do+    codecRoundtrip "IntSet (Array)      " Toml.arrayIntSet Gen.genIntSet+    codecRoundtrip "IntSet (Table)      " (Toml.intSet $ Toml.int "a") Gen.genIntSet+    codecRoundtrip "Set Int (Array)     " (Toml.arraySetOf Toml._Int) (Gen.genSet Gen.genInt)+    codecRoundtrip "Set Int (Table)     "+        (Toml.set $ Toml.int "a")+        (Set.fromList <$> Gen.genSmallList Gen.genInt)+    codecRoundtrip "HashSet Text (Array)"+        (Toml.arrayHashSetOf Toml._Text)+        (HashSet.fromList <$> Gen.genSmallList Gen.genText)+    codecRoundtrip "HashSet Text (Table)"+        (Toml.hashSet $ Toml.text "a")+        (HashSet.fromList <$> Gen.genSmallList Gen.genText)
+ test/Test/Toml/Codec/Combinator/Table.hs view
@@ -0,0 +1,16 @@+module Test.Toml.Codec.Combinator.Table+    ( tableSpec+    ) where++import Test.Hspec (Spec, describe)++import Test.Toml.Codec.Combinator.Common (codecRoundtrip)++import qualified Test.Toml.Gen as Gen+import qualified Toml.Codec.Combinator.Primitive as Toml+import qualified Toml.Codec.Combinator.Table as Toml+++tableSpec :: Spec+tableSpec = describe "Combinator.Table: Roundtrip tests" $+    codecRoundtrip "Table (Int)" (Toml.table $ Toml.int "a") Gen.genInt
+ test/Test/Toml/Codec/Combinator/Time.hs view
@@ -0,0 +1,18 @@+module Test.Toml.Codec.Combinator.Time+    ( timeSpec+    ) where++import Test.Hspec (Spec, describe)++import Test.Toml.Codec.Combinator.Common (codecRoundtrip)++import qualified Test.Toml.Gen as Gen+import qualified Toml.Codec.Combinator.Time as Toml+++timeSpec :: Spec+timeSpec = describe "Combinator.Time: Roundtrip tests" $ do+    codecRoundtrip "ZonedTime " Toml.zonedTime Gen.genZonedTime+    codecRoundtrip "LocalTime " Toml.localTime Gen.genLocalTime+    codecRoundtrip "Day       " Toml.day       Gen.genDay+    codecRoundtrip "TimeOfDay " Toml.timeOfDay Gen.genTimeOfDay
+ test/Test/Toml/Codec/Combinator/Tuple.hs view
@@ -0,0 +1,24 @@+module Test.Toml.Codec.Combinator.Tuple+    ( tupleSpec+    ) where++import Control.Applicative (liftA2, liftA3)+import Test.Hspec (Spec, describe, parallel)++import Test.Toml.Codec.Combinator.Common (codecRoundtrip)++import qualified Test.Toml.Gen as Gen+import qualified Toml.Codec.Combinator.Primitive as Toml+import qualified Toml.Codec.Combinator.Tuple as Toml+++tupleSpec :: Spec+tupleSpec = parallel $ describe "Combinator.Tuple: Roundtrip tests" $ do+    codecRoundtrip+        "(Int, Text)      "+        (const $ Toml.pair (Toml.int "a") (Toml.text "b"))+        (liftA2 (,) Gen.genInt Gen.genText)+    codecRoundtrip+        "(Int, Text, Bool)"+        (const $ Toml.triple (Toml.int "a") (Toml.text "b") (Toml.bool "c"))+        (liftA3 (,,) Gen.genInt Gen.genText Gen.genBool)
+ test/Test/Toml/Codec/Di.hs view
@@ -0,0 +1,57 @@+module Test.Toml.Codec.Di+    ( diSpec+    ) where++import Control.Applicative ((<|>))+import Data.Text (Text)+import Hedgehog (Gen)+import Test.Hspec (Spec, describe)++import Test.Toml.Codec.Combinator.Common (codecRoundtrip)+import Toml.Codec.Di (dimatch)+import Toml.Codec.Types (TomlCodec)+import Toml.Type.Key (Key)++import qualified Hedgehog.Gen as Gen+import qualified Test.Toml.Gen as Gen+import qualified Toml.Codec as Toml+++diSpec :: Spec+diSpec = describe "Codec.Di functions tests" $+    describe "dimatch" $+        codecRoundtrip "SumType" sumTypeExampleCodec genSumTypeExample++data SumType+    = One Bool+    | Two Int Text+    | Three [Int]+    deriving stock (Eq, Show)++matchOne :: SumType -> Maybe Bool+matchOne = \case+    One b -> Just b+    _ -> Nothing++matchTwo :: SumType -> Maybe (Int, Text)+matchTwo = \case+    Two i t -> Just (i, t)+    _ -> Nothing++matchThree :: SumType -> Maybe [Int]+matchThree = \case+    Three l -> Just l+    _ -> Nothing++sumTypeExampleCodec :: Key -> TomlCodec SumType+sumTypeExampleCodec _ =+        dimatch matchOne One (Toml.bool "one")+    <|> dimatch matchTwo (uncurry Two) (Toml.pair (Toml.int "two.a") (Toml.text "two.b"))+    <|> dimatch matchThree Three (Toml.arrayOf Toml._Int "three")++genSumTypeExample :: Gen SumType+genSumTypeExample = Gen.choice+    [ One <$> Gen.genBool+    , Two <$> Gen.genInt <*> Gen.genText+    , Three <$> Gen.genSmallList Gen.genInt+    ]
+ test/Test/Toml/Codec/Generic.hs view
@@ -0,0 +1,33 @@+module Test.Toml.Codec.Generic+    ( genericSpec+    ) where++import Hedgehog (forAll, tripping, (===))+import Test.Hspec (Arg, Expectation, Spec, SpecWith, describe, it, parallel)+import Test.Hspec.Hedgehog (hedgehog)++import Test.Toml.Codec.SmallType (SmallType, genSmallType, smallTypeCodec)+import Toml.Codec.Code (decode, encode)+import Toml.Codec.Generic (genericCodec)+import Toml.Codec.Types (TomlCodec)+++genericSpec :: Spec+genericSpec = parallel $ describe "Generic codecs tests" $ do+    genericRoundtripSpec+    genericDecodeSpec++genericRoundtripSpec :: SpecWith (Arg Expectation)+genericRoundtripSpec = it "genericDecode . genericEncode ≡ id" $ hedgehog $ do+    smallType <- forAll genSmallType+    tripping smallType (encode smallTypeGenericCodec) (decode smallTypeGenericCodec)++genericDecodeSpec :: SpecWith (Arg Expectation)+genericDecodeSpec = it "genericDecode . genericEncode ≡ decode . encode" $ hedgehog $ do+    smallType <- forAll genSmallType+    let genericDecode = decode smallTypeGenericCodec . encode smallTypeGenericCodec+    let manualDecode  = decode smallTypeCodec . encode smallTypeCodec+    genericDecode smallType === manualDecode smallType++smallTypeGenericCodec :: TomlCodec SmallType+smallTypeGenericCodec = genericCodec
+ test/Test/Toml/Codec/SmallType.hs view
@@ -0,0 +1,58 @@+module Test.Toml.Codec.SmallType+    ( smallTypeSpec++      -- * Internals+    , SmallType+    , smallTypeCodec+    , genSmallType+    ) where++import Data.Monoid (Any (..))+import Data.Text (Text)+import Data.Time (Day)+import GHC.Generics (Generic)+import Hedgehog (Gen)+import Test.Hspec (Spec, describe)++import Test.Toml.Codec.Combinator.Common (codecRoundtrip)+import Toml.Codec (ByteStringAsBytes (..), TomlCodec, (.=))++import qualified Hedgehog.Gen as Gen+import qualified Test.Toml.Gen as Gen+import qualified Toml.Codec as Toml+++smallTypeSpec :: Spec+smallTypeSpec = describe "SmallType: tests for custom data type" $+    codecRoundtrip "SmallType" (Toml.table smallTypeCodec) genSmallType++data SmallType = SmallType+    { smallTypeInt       :: !Int+    , smallTypeText      :: !Text+    , smallTypeAny       :: !Any+    , smallTypeDay       :: !Day+    , smallTypeMaybeWord :: !(Maybe Word)+    , smallTypeListInt   :: ![Int]+    , smallTypeBS        :: !ByteStringAsBytes+    } deriving stock (Eq, Show, Generic)++smallTypeCodec :: TomlCodec SmallType+smallTypeCodec = SmallType+    <$> Toml.int "int" .= smallTypeInt+    <*> Toml.text "text" .= smallTypeText+    <*> Toml.any "any" .= smallTypeAny+    <*> Toml.day "day" .= smallTypeDay+    <*> Toml.dioptional (Toml.word "maybe.word") .= smallTypeMaybeWord+    <*> Toml.arrayOf Toml._Int "list.int" .= smallTypeListInt+    <*> Toml.diwrap (Toml.byteStringArray "bs") .= smallTypeBS++genSmallType :: Gen SmallType+genSmallType = do+    smallTypeInt       <- Gen.genInt+    smallTypeText      <- Gen.genText+    smallTypeAny       <- Any <$> Gen.genBool+    smallTypeDay       <- Gen.genDay+    smallTypeMaybeWord <- Gen.maybe Gen.genWord+    smallTypeListInt   <- Gen.genSmallList Gen.genInt+    smallTypeBS        <- ByteStringAsBytes <$> Gen.genByteString+    pure SmallType{..}
test/Test/Toml/Gen.hs view
@@ -1,3 +1,5 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+ {-# LANGUAGE DataKinds           #-} {-# LANGUAGE MultiWayIf          #-} {-# LANGUAGE PatternSynonyms     #-}@@ -8,12 +10,9 @@ -- | This module contains all generators for @tomland@ testing.  module Test.Toml.Gen-       ( -- * Property-         PropertyTest-       , prop--         -- * Generators+       ( -- * Generators          -- ** Primitive+         genBool        , genInt        , genInteger        , genDouble@@ -23,11 +22,13 @@        , genFloat         , genList+       , genSmallList        , genNonEmpty+       , genSet        , genHashSet        , genIntSet -       , genBool+       , genMap         , genText        , genString@@ -37,9 +38,9 @@           -- ** Dates        , genDay-       , genHours-       , genLocal-       , genZoned+       , genTimeOfDay+       , genLocalTime+       , genZonedTime           -- ** @TOML@ specific        , genVal@@ -55,44 +56,39 @@ import Control.Monad (forM, replicateM) import Data.ByteString (ByteString) import Data.Fixed (Fixed (..))-import Data.Functor.Identity (Identity) import Data.Hashable (Hashable)+import Data.HashMap.Strict (HashMap) import Data.HashSet (HashSet) import Data.IntSet (IntSet) import Data.List.NonEmpty (NonEmpty)+import Data.Map.Strict (Map)+import Data.Set (Set) 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)+import Hedgehog (Gen, Range) import Numeric.Natural (Natural)-import Test.Tasty (TestName, TestTree)-import Test.Tasty.Hedgehog (testProperty) -import Toml.Bi.Map (toMArray)-import Toml.PrefixTree (pattern (:||), Key (..), Piece (..), PrefixMap, PrefixTree (..))-import Toml.Type (AnyValue (..), TOML (..), TValue (..), Value (..))+import Toml.Type.AnyValue (AnyValue (..), toMArray)+import Toml.Type.Key (pattern (:||), Key (..), Piece (..))+import Toml.Type.PrefixTree (PrefixMap, PrefixTree (..))+import Toml.Type.TOML (TOML (..))+import Toml.Type.Value (TValue (..), Value (..))  import qualified Data.ByteString.Lazy as LB import qualified Data.Char as Char import qualified Data.List.NonEmpty as NE+import qualified Data.Map.Strict as Map import qualified Data.Text as Text import qualified Data.Text.Lazy as L import qualified Hedgehog.Gen as Gen import qualified Hedgehog.Range as Range-import qualified Toml.PrefixTree as Toml (fromList) -------------------------------------------------------------------------------- Property test creator-----------------------------------------------------------------------------+import qualified Toml.Type.PrefixTree as Toml (fromList) -type PropertyTest = [TestTree] -prop :: HasCallStack => TestName -> PropertyT IO () -> [TestTree]-prop testName = pure . testProperty testName . property- ---------------------------------------------------------------------------- -- Common generators ----------------------------------------------------------------------------@@ -101,51 +97,51 @@  type V = Int -genVal :: MonadGen m => m V+genVal :: Gen V genVal = Gen.int (Range.constant 0 256)  -- | Generates random value of 'AnyValue' type.-genAnyValue :: (MonadGen m, GenBase m ~ Identity) => m AnyValue+genAnyValue :: Gen AnyValue genAnyValue = Gen.choice $     (AnyValue <$> genArray) : noneArrayList  -- | Generate either a bare piece, or a quoted piece-genPiece :: forall m . (MonadGen m, GenBase m ~ Identity) => m Piece+genPiece :: Gen Piece genPiece = Piece <$> Gen.choice [bare, quoted]   where-    alphadashes :: m Char+    bare :: Gen Text+    bare = liftA2 Text.cons Gen.alpha $ Gen.text (Range.constant 1 10) alphadashes++    alphadashes :: Gen Char     alphadashes = Gen.choice [Gen.alphaNum, Gen.element "_-"] -    notControl :: m Char-    notControl = Gen.filter (not . Char.isControl) Gen.unicode+    quoted :: Gen Text+    quoted = genNotEscape $ Gen.choice [quotedWith '"', quotedWith '\''] -    bare :: m Text-    bare = Gen.text (Range.constant 1 10) alphadashes+    quotedWith :: Char -> Gen Text+    quotedWith c = wrapChar c <$> Gen.text (Range.constant 1 10) notControl+      where+        notControl :: Gen Char+        notControl = Gen.filter (\x -> x /= c && not (Char.isControl x)) Gen.unicode      wrapChar :: Char -> Text -> Text     wrapChar c = Text.cons c . (`Text.append` Text.singleton c) -    quotedWith :: Char -> m Text-    quotedWith c = wrapChar c <$> Gen.text (Range.constant 1 10) (Gen.filter (/= c) notControl)--    quoted :: m Text-    quoted = genNotEscape $ Gen.choice [quotedWith '"', quotedWith '\'']--genKey :: (MonadGen m, GenBase m ~ Identity) => m Key+genKey :: Gen Key genKey = Key <$> Gen.nonEmpty (Range.constant 1 10) genPiece -genKeyAnyValue :: (MonadGen m, GenBase m ~ Identity) => m (Key, AnyValue)+genKeyAnyValue :: Gen (Key, AnyValue) genKeyAnyValue = liftA2 (,) genKey genAnyValue -genKeyAnyValueList :: (MonadGen m, GenBase m ~ Identity) => m [(Key, AnyValue)]+genKeyAnyValueList :: Gen [(Key, AnyValue)] genKeyAnyValueList = Gen.list (Range.linear 0 10) genKeyAnyValue  -- Generates key-value pair for PrefixMap-genEntry :: (MonadGen m, GenBase m ~ Identity) => m (Piece, Key)+genEntry :: Gen (Piece, Key) genEntry = genKey >>= \case     key@(piece :|| _) -> pure (piece, key) -genPrefixMap :: (MonadGen m, GenBase m ~ Identity) => m (PrefixMap V)+genPrefixMap :: Gen (PrefixMap V) genPrefixMap = do     entries <- Gen.list (Range.linear 0 10) genEntry     kvps    <- forM entries $ \(piece, key) -> do@@ -154,7 +150,7 @@      pure $ fromList kvps -genPrefixTree :: forall m . (MonadGen m, GenBase m ~ Identity) => Key -> m (PrefixTree V)+genPrefixTree :: Key -> Gen (PrefixTree V) genPrefixTree key = Gen.recursive     -- list picker generator combinator     Gen.choice@@ -163,7 +159,7 @@     -- recursive generators     [ genPrefixMap >>= genBranch ]   where-    genBranch :: PrefixMap V -> m (PrefixTree V)+    genBranch :: PrefixMap V -> Gen (PrefixTree V)     genBranch prefMap = do         prefVal <- Gen.maybe genVal         pure $ Branch key prefVal prefMap@@ -171,46 +167,50 @@ makeToml :: [(Key, AnyValue)] -> TOML makeToml kv = TOML (fromList kv) mempty mempty -genToml :: (MonadGen m, GenBase m ~ Identity) => m TOML+genToml :: Gen TOML genToml = Gen.recursive-            Gen.choice-            [ makeToml <$> genKeyAnyValueList ]-            [ TOML <$> keyValues <*> tables <*> arrays ]+    Gen.choice+    [ makeToml <$> genKeyAnyValueList ]+    [ TOML <$> keyValues <*> tables <*> arrays ]   where+    keyValues :: Gen (HashMap Key AnyValue)     keyValues = fromList <$> genKeyAnyValueList++    tables :: Gen (PrefixMap TOML)     tables = fmap Toml.fromList-             $ Gen.list (Range.linear 0 5)-             $ (,) <$> genKey <*> genToml-    arrays = fmap fromList $-             Gen.list (Range.linear 0 10) $ do-               key <- genKey-               arr <- Gen.list (Range.linear 1 10) genToml-               return (key, NE.fromList arr)+        $ Gen.list (Range.linear 0 5)+        $ (,) <$> genKey <*> genToml +    arrays :: Gen (HashMap Key (NonEmpty TOML))+    arrays = fmap fromList $ Gen.list (Range.linear 0 10) $ do+        key <- genKey+        arr <- Gen.list (Range.linear 1 10) genToml+        pure (key, NE.fromList arr)+ -- Date generators -genDay :: MonadGen m => m Day+genDay :: Gen Day genDay = do     y <- toInteger <$> Gen.int (Range.constant 1968 2019)     m <- Gen.int (Range.constant 1 12)     d <- Gen.int (Range.constant 1 28)     pure $ fromGregorian y m d -genHours :: MonadGen m => m TimeOfDay-genHours = do+genTimeOfDay :: Gen TimeOfDay+genTimeOfDay = do     secs <- MkFixed <$> Gen.integral (Range.constant 0 61)     mins <- Gen.int (Range.constant 0 59)     hours <- Gen.int (Range.constant 0 23)     pure $ TimeOfDay hours mins secs -genLocal :: MonadGen m => m LocalTime-genLocal = do+genLocalTime :: Gen LocalTime+genLocalTime = do     day <- genDay-    LocalTime day <$> genHours+    LocalTime day <$> genTimeOfDay -genZoned :: MonadGen m => m ZonedTime-genZoned = do-    local <- genLocal+genZonedTime :: Gen ZonedTime+genZonedTime = do+    local <- genLocalTime     zMin <- Gen.int (Range.constant (-720) 720)     let zTime = minutesToTimeZone zMin     pure $ ZonedTime local zTime@@ -220,13 +220,16 @@ range100 :: Range Int range100 = Range.constant 0 100 -genInt :: MonadGen m => m Int+genBool :: Gen Bool+genBool = Gen.bool++genInt :: Gen Int genInt = Gen.int Range.constantBounded -genInteger :: MonadGen m => m Integer+genInteger :: Gen Integer genInteger = toInteger <$> genInt -genDouble :: MonadGen m => m Double+genDouble :: Gen Double genDouble = Gen.frequency     [ (10, Gen.double $ Range.constant @Double (-1000000.0) 1000000.0)     , (1, Gen.constant $ 1/0)@@ -234,18 +237,21 @@     , (1, Gen.constant $ 0/0)     ] -genWord :: MonadGen m => m Word+genWord :: Gen Word genWord = Gen.word Range.constantBounded -genWord8 :: MonadGen m => m Word8+genWord8 :: Gen Word8 genWord8 = Gen.word8 Range.constantBounded -genNatural :: MonadGen m => m Natural+genNatural :: Gen Natural genNatural = fromIntegral <$> genWord -genFloat :: MonadGen m => m Float+genFloat :: Gen Float genFloat = Gen.float (Range.constant (-10000.0) 10000.0) +genSet :: Ord a => Gen a -> Gen (Set a)+genSet genA = fromList <$> genList genA+ genHashSet :: (Eq a, Hashable a) => Gen a -> Gen (HashSet a) genHashSet genA = fromList <$> genList genA @@ -255,19 +261,22 @@ genList :: Gen a -> Gen [a] genList = Gen.list range100 +genSmallList :: Gen a -> Gen [a]+genSmallList = Gen.list $ Range.constant 0 10+ genIntSet :: Gen IntSet genIntSet = fromList <$> genList genInt -genBool :: MonadGen m => m Bool-genBool = Gen.bool+genMap :: Ord k => Gen k -> Gen v -> Gen (Map k v)+genMap genK genV = Map.fromList <$> genSmallList (liftA2 (,) genK genV)  -- | Generatates control sympol.-genEscapeSequence :: MonadGen m => m Text+genEscapeSequence :: Gen Text genEscapeSequence = Gen.element     [ "\n", "\b", "\f", "\r", "\t", "\\", "\"" ]  -- | Generatates punctuation.-genPunctuation :: MonadGen m => m Text+genPunctuation :: Gen Text genPunctuation = Gen.element     [ ",", ".", ":", ";", "'", "?", "!", "`"     , "-", "_", "*", "$", "#", "@", "(", ")"@@ -275,23 +284,23 @@     ]  -- | Generatates n length list of hex chars.-genDiffHex :: MonadGen m => Int -> m String+genDiffHex :: Int -> Gen String genDiffHex n = replicateM n Gen.hexit  -- | Generates unicode color string (u1234)-genUniHex4Color :: MonadGen m => m Text+genUniHex4Color :: Gen Text genUniHex4Color = do     hex <- genDiffHex 4     pure . Text.pack $ "\\u" ++ hex  -- | Generates unicode color string (u12345678)-genUniHex8Color :: MonadGen m => m Text+genUniHex8Color :: Gen Text genUniHex8Color = do     hex <- genDiffHex 8     pure . Text.pack $ "\\U" ++ hex  -- | Generates text from different symbols.-genText :: MonadGen m => m Text+genText :: Gen Text genText = genNotEscape $ fmap Text.concat $ Gen.list (Range.constant 0 256) $ Gen.choice     [ Text.singleton <$> Gen.alphaNum     , genEscapeSequence@@ -313,19 +322,19 @@ genLText = L.fromStrict <$> genText  -- | List of AnyValue generators.-noneArrayList :: MonadGen m => [m AnyValue]+noneArrayList :: [Gen AnyValue] noneArrayList =     [ AnyValue . Bool    <$> genBool     , AnyValue . Integer <$> genInteger     , AnyValue . Double  <$> genDouble     , AnyValue . Text    <$> genText-    , AnyValue . Zoned   <$> genZoned-    , AnyValue . Local   <$> genLocal+    , AnyValue . Zoned   <$> genZonedTime+    , AnyValue . Local   <$> genLocalTime     , AnyValue . Day     <$> genDay-    , AnyValue . Hours   <$> genHours+    , AnyValue . Hours   <$> genTimeOfDay     ] -genArrayFrom :: MonadGen m => m AnyValue -> m (Value 'TArray)+genArrayFrom :: Gen AnyValue -> Gen (Value 'TArray) genArrayFrom noneArray = do     eVal <- toMArray <$> Gen.list (Range.constant 0 5) noneArray     case eVal of@@ -333,12 +342,30 @@         Right val -> pure val  {- | Generate arrays and nested arrays. For example:+ Common array:-Array [Double (-563397.0197456297),Double (-308866.62837749254),Double (-29555.32072604308),Double 772371.8575471763,Double (-880016.1210667372),Double 182763.78796234122,Double (-462893.41157520143),Double 814856.6483699235,Double (-454629.17640282493)]++@+Array+    [ Double (-5.7)+    , Double (-6.4)+    , Double 1.3+    ]+@+ Nested array of AnyValue:-Array [Array [Text "ACyz38VcLz0hxwdFkHTU6PYK8h8CeaiEpI2xAaiZTKBQ3zC1W717cZY35lk8EAK6pPw3WvwIdNktxIV2LrvFSpU8ee6zkXvpvePitW9aspAeeOCF9Q9ry20y7skFZ2qShi7CSx8888zWIqyc8iBkoLNvq4fONLtuUqSw2SlNee4hDIwrnx5O4RuHW1dQfJcnC34h9S0DlIGYP08qq6QHxO4E0HE74cNmiViGm3xpDC8Ro5D8Y6p0FLSN1ELq9Lwm",Text "HhNv0LKICdlKxN"],Array [Integer 986479839551009895,Integer 8636972066308796678,Integer (-3464941350081979804),Integer (-6560688879547055621),Integer (-4749037439349044738)],Array []]++@+Array+    [ Array+        [ Text "AH",Text "HA"]+        , Array [Integer 9,Integer (-3)]+        , Array []+        ]+    ]+@ -}-genArray :: (MonadGen m, GenBase m ~ Identity) => m (Value 'TArray)+genArray :: Gen (Value 'TArray) genArray = Gen.recursive Gen.choice     [Gen.choice $ map genArrayFrom noneArrayList]     [Array <$> Gen.list (Range.constant 0 5) genArray]@@ -346,8 +373,13 @@ -- filters  -- | Discards strings that end with \-genNotEscape :: MonadGen m => m Text -> m Text+genNotEscape :: Gen Text -> Gen Text genNotEscape gen = gen >>= \t ->     if | Text.null t -> pure t        | Text.last t == '\\' -> Gen.discard        | otherwise -> pure t++-- Orphan instances++instance Eq ZonedTime where+  (ZonedTime a b) == (ZonedTime c d) = a == c && b == d
+ test/Test/Toml/Parser.hs view
@@ -0,0 +1,34 @@+module Test.Toml.Parser+    ( parserSpec+    ) where++import Test.Hspec (Spec, describe)++import Test.Toml.Parser.Array (arraySpecs)+import Test.Toml.Parser.Bool (boolSpecs)+import Test.Toml.Parser.Date (dateSpecs)+import Test.Toml.Parser.Double (doubleSpecs)+import Test.Toml.Parser.Examples (examplesSpec)+import Test.Toml.Parser.Integer (integerSpecs)+import Test.Toml.Parser.Key (keySpecs)+import Test.Toml.Parser.Property (propertySpec)+import Test.Toml.Parser.Text (textSpecs)+import Test.Toml.Parser.Toml (tomlSpecs)+import Test.Toml.Parser.Validate (validateSpec)+++parserSpec :: Spec+parserSpec = describe "Parser for TOML" $ do+    examplesSpec+    propertySpec+    validateSpec++    -- unit tests for different parser parts+    arraySpecs+    boolSpecs+    dateSpecs+    doubleSpecs+    integerSpecs+    keySpecs+    textSpecs+    tomlSpecs
+ test/Test/Toml/Parser/Array.hs view
@@ -0,0 +1,74 @@+module Test.Toml.Parser.Array+    ( arraySpecs+    ) where++import Data.Time (TimeOfDay (..))+import Test.Hspec (Spec, describe, it)++import Test.Toml.Parser.Common (arrayFailOn, day1, day2, int1, int2, int3, int4, parseArray)+import Toml.Type (UValue (..))+++arraySpecs :: Spec+arraySpecs = describe "arrayP" $ do+    it "can parse arrays" $ do+        parseArray+            "[]"+            []+        parseArray+            "[1]"+            [int1]+        parseArray+            "[1, 2, 3]"+            [int1, int2, int3]+        parseArray+            "[1.2, 2.3, 3.4]"+            [UDouble 1.2, UDouble 2.3, UDouble 3.4]+        parseArray+            "['x', 'y']"+            [UText "x", UText "y"]+        parseArray+            "[[1], [2]]"+            [UArray [UInteger 1], UArray [UInteger 2]]+        parseArray+            "[1920-12-10, 1979-05-27]"+            [UDay  day2, UDay day1]+        parseArray+            "[16:33:05, 10:15:30]"+            [UHours (TimeOfDay 16 33 5), UHours (TimeOfDay 10 15 30)]+    it "can parse multiline arrays" $+        parseArray+            "[\n1,\n2\n]"+            [int1, int2]+    it "can parse an array of arrays" $+        parseArray+            "[[1], [2.3, 5.1]]"+            [UArray [int1], UArray [UDouble 2.3, UDouble 5.1]]+    it "can parse an array with terminating commas (trailing commas)" $ do+        parseArray+            "[1, 2,]"+            [int1, int2]+        parseArray+            "[1, 2, 3, , ,]"+            [int1, int2, int3]+    it "allows an arbitrary number of comments and newlines before or after a value" $+        parseArray+            "[\n\n#c\n1, #c 2 \n 2, \n\n\n 3, #c \n #c \n 4]"+            [int1, int2, int3, int4]+    it "ignores white spaces" $+        parseArray+            "[   1    ,    2,3,  4      ]"+            [int1, int2, int3, int4]++    it "fails if the elements are not surrounded by square brackets" $ do+        arrayFailOn "1, 2, 3"+        arrayFailOn "[1, 2, 3"+        arrayFailOn "1, 2, 3]"+        arrayFailOn "{'x', 'y', 'z'}"+        arrayFailOn "(\"ab\", \"cd\")"+        arrayFailOn "<true, false>"+    it "fails if the elements are not separated by commas" $ do+        arrayFailOn "[1 2 3]"+        arrayFailOn "[1 . 2 . 3]"+        arrayFailOn "['x' - 'y' - 'z']"+        arrayFailOn "[1920-12-10, 10:15:30]"
+ test/Test/Toml/Parser/Bool.hs view
@@ -0,0 +1,22 @@+module Test.Toml.Parser.Bool+    ( boolSpecs+    ) where++import Test.Hspec (Spec, describe, it)++import Test.Toml.Parser.Common (boolFailOn, parseBool)+++boolSpecs :: Spec+boolSpecs = describe "boolP" $ do+    it "can parse `true` and `false`" $ do+        parseBool "true" True+        parseBool "false" False+        parseBool "true        " True+    it "fails if `true` or `false` are not all lowercase" $ do+        boolFailOn "True"+        boolFailOn "False"+        boolFailOn "TRUE"+        boolFailOn "FALSE"+        boolFailOn "tRuE"+        boolFailOn "fAlSE"
+ test/Test/Toml/Parser/Common.hs view
@@ -0,0 +1,135 @@+module Test.Toml.Parser.Common+       ( parseX+       , failOn+       , parseArray+       , parseBool+       , parseDouble+       , parseInteger+       , parseKey+       , parseText+       , parseToml+       , parseDateTime+       , arrayFailOn+       , boolFailOn+       , integerFailOn+       , dateTimeFailOn+       , doubleFailOn+       , textFailOn+       , tomlFailOn+       , quoteWith+       , squote+       , dquote+       , squote3+       , dquote3+       , makeOffset+       , makeZoned+       , int1+       , int2+       , int3+       , int4+       , offset710+       , offset0+       , day1+       , day2+       , hours1+       ) where++import Data.Text (Text)+import Data.Time (Day, LocalTime (..), TimeOfDay (..), TimeZone, ZonedTime (..), fromGregorian,+                  minutesToTimeZone)+import Test.Hspec (Expectation)+import Test.Hspec.Megaparsec (shouldFailOn, shouldParse)+import Text.Megaparsec (Parsec, ShowErrorComponent, Stream, parse)++import Toml.Parser.Item (tomlP)+import Toml.Parser.Key (keyP)+import Toml.Parser.String (textP)+import Toml.Parser.Validate (validateItems)+import Toml.Parser.Value (arrayP, boolP, dateTimeP, doubleP, integerP)+import Toml.Type.Key (Key (..))+import Toml.Type.TOML (TOML (..))+import Toml.Type.UValue (UValue (..))+++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+failOn p given = parse p "" `shouldFailOn` given++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 test toml = parseX (validateItems <$> tomlP) test (Right toml)++arrayFailOn+  , boolFailOn+  , dateTimeFailOn+  , doubleFailOn+  , integerFailOn+  , textFailOn+  , tomlFailOn :: Text -> Expectation+arrayFailOn     = failOn arrayP+boolFailOn      = failOn boolP+dateTimeFailOn  = failOn dateTimeP+doubleFailOn    = failOn doubleP+integerFailOn   = failOn integerP+textFailOn      = failOn textP+tomlFailOn      = failOn tomlP++-- Surround given text with quotes.+quoteWith :: Text -> Text -> Text+quoteWith q t = q <> t <> q+squote, dquote, squote3, dquote3 :: Text -> Text+squote = quoteWith "'"+dquote = quoteWith "\""+squote3 = quoteWith "'''"+dquote3 = quoteWith "\"\"\""++-- UValue Util++makeZoned :: Day -> TimeOfDay -> TimeZone -> UValue+makeZoned d h offset = UZoned $ ZonedTime (LocalTime d h) offset++makeOffset :: Int -> Int -> TimeZone+makeOffset hours mins = minutesToTimeZone (hours * 60 + mins * signum hours)++-- Test Data++int1, int2, int3, int4 :: UValue+int1 = UInteger 1+int2 = UInteger 2+int3 = UInteger 3+int4 = UInteger 4++offset0, offset710 :: TimeZone+offset0 = makeOffset 0 0+offset710 = makeOffset 7 10++day1, day2 :: Day+day1 = fromGregorian 1979 5 27  -- 1979-05-27+day2 = fromGregorian 1920 12 10 -- 1920-12-10++hours1 :: TimeOfDay+hours1 = TimeOfDay 7 32 0  -- 07:32:00
+ test/Test/Toml/Parser/Date.hs view
@@ -0,0 +1,63 @@+module Test.Toml.Parser.Date+    ( dateSpecs+    ) where++import Data.Time (LocalTime (..), TimeOfDay (..))+import Test.Hspec (Spec, describe, it)++import Test.Toml.Parser.Common (dateTimeFailOn, day1, hours1, makeOffset, makeZoned, offset0,+                                offset710, parseDateTime)+import Toml.Type (UValue (..))+++dateSpecs :: Spec+dateSpecs = describe "dateTimeP" $ do+    it "can parse a date-time with an offset" $ do+        parseDateTime "1979-05-27T07:32:00Z" $+            makeZoned day1 hours1 offset0+        parseDateTime "1979-05-27T00:32:00+07:10" $+            makeZoned day1 (TimeOfDay 0 32 0) offset710+        parseDateTime "1979-05-27T00:32:00.999999-07:25" $+            makeZoned day1 (TimeOfDay 0 32 0.999999) (makeOffset (-7) 25)+    it "can parse a date-time with an offset when the T delimiter is replaced with a space" $+        parseDateTime "1979-05-27 07:32:00Z" $+            makeZoned day1 hours1 offset0+    it "can parse a date-time without an offset" $ do+        parseDateTime "1979-05-27T17:32:00"+            (ULocal $ LocalTime day1 (TimeOfDay 17 32 0))+        parseDateTime "1979-05-27T00:32:00.999999"+            (ULocal $ LocalTime day1 (TimeOfDay 0 32 0.999999))+    it "can parse a local date" $+        parseDateTime "1979-05-27" (UDay day1)+    it "can parse a local time" $ do+        parseDateTime "07:32:00"        (UHours hours1)+        parseDateTime "00:32:00.999999" (UHours $ TimeOfDay 0 32 0.999999)+    it "truncates the additional precision after picoseconds in the fractional seconds" $+        parseDateTime "00:32:00.99999999999199"+            (UHours $ TimeOfDay 0 32 0.999999999991)+    it "fails if the date is not valid" $ do+        dateTimeFailOn "1920-15-12"+        dateTimeFailOn "1920-12-40"+    it "fails if the date does not have the form: 'yyyy-mm-dd'" $ do+        dateTimeFailOn "1920-01-1"+        dateTimeFailOn "1920-1-01"+        dateTimeFailOn "920-01-01"+        dateTimeFailOn "1920/10/01"+    it "fails if the time is not valid" $ do+        dateTimeFailOn "25:10:10"+        dateTimeFailOn "10:70:10"+        dateTimeFailOn "10:10:70"+    it "fails if the time does not have the form: 'hh:mm:ss'" $ do+        dateTimeFailOn "1:12:12"+        dateTimeFailOn "12:1:12"+        dateTimeFailOn "12:12:1"+        dateTimeFailOn "12-12-12"+    it "fails if the offset does not have any of the forms: 'Z', '+hh:mm', '-hh:mm'" $ do+        parseDateTime "1979-05-27T00:32:00X"+            (ULocal $ LocalTime day1 (TimeOfDay 0 32 0))+        parseDateTime "1979-05-27T00:32:00+07:1"+            (ULocal $ LocalTime day1 (TimeOfDay 0 32 0))+        parseDateTime "1979-05-27T00:32:00+7:01"+            (ULocal $ LocalTime day1 (TimeOfDay 0 32 0))+        parseDateTime "1979-05-27T00:32:0007:00"+            (ULocal $ LocalTime day1 (TimeOfDay 0 32 0))
+ test/Test/Toml/Parser/Double.hs view
@@ -0,0 +1,45 @@+module Test.Toml.Parser.Double+    (doubleSpecs+    ) where++import Test.Hspec (Spec, describe, it)+import Test.Hspec.Megaparsec (parseSatisfies)+import Text.Megaparsec (parse)++import Test.Toml.Parser.Common (doubleFailOn, parseDouble)+import Toml.Parser.Value (doubleP)+++doubleSpecs :: Spec+doubleSpecs = describe "doubleP" $ do+    it "can parse a number which consists of an integral part, and a fractional part" $ do+        parseDouble "+1.0"      1.0+        parseDouble "3.1415"    3.1415+        parseDouble "0.0"       0.0+        parseDouble "-0.01"     (-0.01)+        parseDouble "5e+22"     5e+22+        parseDouble "1e6"       1e6+        parseDouble "-2E-2"     (-2E-2)+        parseDouble "6.626e-34" 6.626e-34+    it "can parse a number with underscores" $ do+        parseDouble "5e+2_2"         5e+22+        parseDouble "1.1_1e6"        1.11e6+        parseDouble "-2_2.21_9E-0_2" (-22.219E-2)+    it "can parse sign-prefixed zero" $ do+        parseDouble "+0.0" 0.0+        parseDouble "-0.0" (-0.0)+    it "can parse positive and negative special float values (inf and nan)" $ do+        parseDouble "inf"  (1 / 0)+        parseDouble "+inf" (1 / 0)+        parseDouble "-inf" (-1 / 0)+        doubleSatisfies "nan"  isNaN+        doubleSatisfies "+nan" isNaN+        doubleSatisfies "-nan" isNaN+    it "fails if `inf` or `nan` are not all lowercase" $ do+        doubleFailOn "Inf"+        doubleFailOn "INF"+        doubleFailOn "Nan"+        doubleFailOn "NAN"+        doubleFailOn "NaN"+  where+    doubleSatisfies given f = parse doubleP "" given `parseSatisfies` f
+ test/Test/Toml/Parser/Examples.hs view
@@ -0,0 +1,25 @@+module Test.Toml.Parser.Examples+    ( examplesSpec+    ) where++import Data.Either (isRight)+import System.Directory (listDirectory)+import Test.Hspec (Spec, describe, it, runIO, shouldBe)++import Toml.Parser (parse)++import qualified Data.Text.IO as TIO+++examplesSpec :: Spec+examplesSpec = describe "Can parse official TOML examples" $ do+    files <- runIO $ listDirectory exampleDir+    mapM_ example files++example :: FilePath -> Spec+example file = it ("can parse file " ++ file) $ do+    toml <- TIO.readFile (exampleDir ++ file)+    isRight (parse toml) `shouldBe` True++exampleDir :: FilePath+exampleDir = "test/examples/"
+ test/Test/Toml/Parser/Integer.hs view
@@ -0,0 +1,83 @@+module Test.Toml.Parser.Integer+    ( integerSpecs+    ) where++import Test.Hspec (Spec, context, describe, it)++import Test.Toml.Parser.Common (integerFailOn, parseInteger)+++integerSpecs :: Spec+integerSpecs = describe "integerP" $ do+    context "when the integer is in decimal representation" $ do+        it "can parse positive integer numbers" $ do+            parseInteger "10" 10+            parseInteger "+3" 3+            parseInteger "0"  0+        it "can parse negative integer numbers" $+            parseInteger "-123" (-123)+        it "can parse sign-prefixed zero as an unprefixed zero" $ do+            parseInteger "+0" 0+            parseInteger "-0" 0+        it "can parse both the minimum and maximum numbers in the 64 bit range" $ do+            parseInteger "-9223372036854775808" (-9223372036854775808)+            parseInteger "9223372036854775807"  9223372036854775807+        it "can parse numbers with underscores between digits" $ do+            parseInteger "1_000" 1000+            parseInteger "5_349_221" 5349221+            parseInteger "1_2_3_4_5" 12345+        it "does not parse incorrect underscores" $ do+            integerFailOn "1_2_3_"+            integerFailOn "13_"+            integerFailOn "_123_"+            integerFailOn "_13"+            integerFailOn "_"+        it "does not parse numbers with leading zeros" $ do+            parseInteger "0123" 0+            parseInteger "-023" 0+    context "when the integer is in binary representation" $ do+        it "can parse numbers prefixed with `0b`" $ do+            parseInteger "0b1101" 13+            parseInteger "0b0"    0+        it "does not parse numbers prefixed with `0B`" $+            parseInteger "0B1101" 0+        it "can parse numbers with leading zeros after the prefix" $ do+            parseInteger "0b000"   0+            parseInteger "0b00011" 3+        it "does not parse negative numbers" $+            parseInteger "-0b101" 0+        it "does not parse numbers with non-valid binary digits" $+            parseInteger "0b123" 1+    context "when the integer is in octal representation" $ do+        it "can parse numbers prefixed with `0o`" $ do+            parseInteger "0o567" 0o567+            parseInteger "0o0"   0+        it "does not parse numbers prefixed with `0O`" $+            parseInteger "0O567" 0+        it "can parse numbers with leading zeros after the prefix" $ do+            parseInteger "0o000000" 0+            parseInteger "0o000567" 0o567+        it "does not parse negative numbers" $+            parseInteger "-0o123" 0+        it "does not parse numbers with non-valid octal digits" $+            parseInteger "0o789" 0o7+    context "when the integer is in hexadecimal representation" $ do+        it "can parse numbers prefixed with `0x`" $ do+            parseInteger "0x12af" 0x12af+            parseInteger "0x0"    0+        it "does not parse numbers prefixed with `0X`" $+            parseInteger "0Xfff" 0+        it "can parse numbers with leading zeros after the prefix" $ do+            parseInteger "0x00000" 0+            parseInteger "0x012af" 0x12af+        it "does not parse negative numbers" $+            parseInteger "-0xfff" 0+        it "does not parse numbers with non-valid hexadecimal digits" $+            parseInteger "0xfgh" 0xf+        it "can parse numbers when hex digits are lowercase" $+            parseInteger "0xabcdef" 0xabcdef+        it "can parse numbers when hex digits are uppercase" $+            parseInteger "0xABCDEF" 0xABCDEF+        it "can parse numbers when hex digits are in both lowercase and uppercase" $ do+            parseInteger "0xAbCdEf" 0xAbCdEf+            parseInteger "0xaBcDeF" 0xaBcDeF
+ test/Test/Toml/Parser/Key.hs view
@@ -0,0 +1,48 @@+{-# LANGUAGE PatternSynonyms #-}++module Test.Toml.Parser.Key+    ( keySpecs+    ) where++import Test.Hspec (Spec, context, describe, it, xit)++import Test.Toml.Parser.Common (dquote, parseKey, squote)+import Toml.Type.Key (pattern (:||))+++keySpecs :: Spec+keySpecs = describe "keyP" $ do+    context "when the key is a bare key" $ do+        it "can parse keys which contain ASCII letters, digits, underscores, and dashes" $ do+            parseKey "key"       "key"+            parseKey "bare_key1" "bare_key1"+            parseKey "bare-key2" "bare-key2"+        it "can parse keys which contain only digits" $+            parseKey "1234" "1234"+    context "when the key is a quoted key" $ do+        it "can parse keys that follow the exact same rules as basic strings" $ do+            parseKey (dquote "127.0.0.1") ("\"127.0.0.1\"" :|| [])+            parseKey (dquote "character encoding") "\"character encoding\""+            parseKey (dquote "ʎǝʞ") "\"ʎǝʞ\""+        it "can parse keys that follow the exact same rules as literal strings" $ do+            parseKey (squote "key2") "'key2'"+            parseKey (squote "quoted \"value\"") "'quoted \"value\"'"+    context "when the key is a dotted key" $ do+        it "can parse a sequence of bare or quoted keys joined with a dot" $ do+            parseKey "name"           "name"+            parseKey "physical.color" "physical.color"+            parseKey "physical.shape" "physical.shape"+            parseKey "site.\"google.com\"" ("site" :|| ["\"google.com\""])+        xit "ignores whitespaces around dot-separated parts" $+            parseKey "a . b . c. d" ("a" :|| ["b", "c", "d"])+    context "when the key is symbols only" $ do+        it "parses Haskell comments" $+            parseKey "--" "--"+        it "parses Haskell comments with dots" $+            parseKey "--.--" $ "--" :|| [ "--" ]+        it "parses underscores" $ do+            parseKey "_"   "_"+            parseKey "__"  "__"+            parseKey "___" "___"+        it "parses quotes" $+            parseKey "\".\"" $ "\".\"" :|| []
+ test/Test/Toml/Parser/Property.hs view
@@ -0,0 +1,21 @@+module Test.Toml.Parser.Property+    ( propertySpec+    ) where++import Hedgehog (forAll, tripping)+import Test.Hspec (Arg, Expectation, Spec, SpecWith, describe, it)+import Test.Hspec.Hedgehog (hedgehog)++import Test.Toml.Gen (genToml)+import Toml.Parser (parse)+import Toml.Type.Printer (pretty)+++propertySpec :: Spec+propertySpec = describe "Parsing property tests"+    parsePrintRoundTrip++parsePrintRoundTrip :: SpecWith (Arg Expectation)+parsePrintRoundTrip = it "parse . prettyPrint == id" $ hedgehog $ do+    toml <- forAll genToml+    tripping toml pretty parse
+ test/Test/Toml/Parser/Text.hs view
@@ -0,0 +1,96 @@+module Test.Toml.Parser.Text+    ( textSpecs+    ) where++import Test.Hspec (Spec, context, describe, it)++import Test.Toml.Parser.Common (dquote, dquote3, parseText, squote, squote3, textFailOn)+++textSpecs :: Spec+textSpecs = describe "textP" $ do+    context "when the string is a basic string" $ do+        it "can parse strings surrounded by double quotes" $ do+            parseText (dquote "xyz") "xyz"+            parseText (dquote "")    ""+            textFailOn "\"xyz"+            textFailOn "xyz\""+            textFailOn "xyz"+        it "can parse escaped quotation marks, backslashes, and control characters" $ do+            parseText (dquote "backspace: \\b") "backspace: \b"+            parseText (dquote "tab: \\t")       "tab: \t"+            parseText (dquote "linefeed: \\n")  "linefeed: \n"+            parseText (dquote "form feed: \\f") "form feed: \f"+            parseText (dquote "carriage return: \\r") "carriage return: \r"+            parseText (dquote "quote: \\\"")     "quote: \""+            parseText (dquote "backslash: \\\\") "backslash: \\"+            parseText (dquote "a\\uD7FFxy\\U0010FFFF\\uE000") "a\55295xy\1114111\57344"+        it "fails if the string has an unescaped backslash, or control character" $ do+            textFailOn (dquote "new \n line")+            textFailOn (dquote "back \\ slash")+        it "fails if the string has an escape sequence that is not listed in the TOML specification" $+            textFailOn (dquote "xy\\z \\abc")+        it "fails if the string is not on a single line" $ do+            textFailOn (dquote "\nabc")+            textFailOn (dquote "ab\r\nc")+            textFailOn (dquote "abc\n")+        it "fails if escape codes are not valid Unicode scalar values" $ do+            textFailOn (dquote "\\u1")+            textFailOn (dquote "\\uxyzw")+            textFailOn (dquote "\\U0000")+            textFailOn (dquote "\\uD8FF")+            textFailOn (dquote "\\U001FFFFF")+    context "when the string is a multi-line basic string" $ do++        it "can parse multi-line strings surrounded by three double quotes" $+            parseText (dquote3 "Roses are red\nViolets are blue")+                "Roses are red\nViolets are blue"+        it "can parse single-line strings surrounded by three double quotes" $+            parseText (dquote3 "Roses are red Violets are blue")+                "Roses are red Violets are blue"+        it "can parse all of the escape sequences that are valid for basic strings" $ do+            parseText (dquote3 "backspace: \\b") "backspace: \b"+            parseText (dquote3 "tab: \\t")       "tab: \t"+            parseText (dquote3 "linefeed: \\n")  "linefeed: \n"+            parseText (dquote3 "form feed: \\f") "form feed: \f"+            parseText (dquote3 "carriage return: \\r") "carriage return: \r"+            parseText (dquote3 "quote: \\\"")     "quote: \""+            parseText (dquote3 "backslash: \\\\") "backslash: \\"+            parseText (dquote3 "a\\uD7FFxy\\U0010FFFF\\uE000") "a\55295xy\1114111\57344"+        it "does not ignore whitespaces or newlines" $+            parseText (dquote3 "\nabc  \n   xyz") "abc  \n   xyz"+        it "ignores a newline only if it immediately follows the opening delimiter" $+            parseText (dquote3 "\nThe quick brown") "The quick brown"+        it "ignores whitespaces and newlines after line ending backslash" $+            parseText (dquote3 "The quick brown \\\n\n   fox jumps over") "The quick brown fox jumps over"+        it "fails if the string has an unescaped backslash, or control character" $ do+            textFailOn (dquote3 "backslash \\ .")+            textFailOn (dquote3 "backspace \b ..")+            textFailOn (dquote3 "tab \t ..")+    context "when the string is a literal string" $ do+        it "can parse strings surrounded by single quotes" $ do+            parseText (squote "C:\\Users\\nodejs\\templates")+                      "C:\\Users\\nodejs\\templates"+            parseText (squote "\\\\ServerX\\admin$\\system32\\")+                      "\\\\ServerX\\admin$\\system32\\"+            parseText (squote "Tom \"Dubs\" Preston-Werner")+                      "Tom \"Dubs\" Preston-Werner"+            parseText (squote "<\\i\\c*\\s*>") "<\\i\\c*\\s*>"+            parseText (squote "a \t tab")      "a \t tab"+        it "fails if the string is not on a single line" $ do+            textFailOn (squote "\nabc")+            textFailOn (squote "ab\r\nc")+            textFailOn (squote "abc\n")+    context "when the string is a multi-line literal string" $ do+        it "can parse multi-line strings surrounded by three single quotes" $+            parseText (squote3 "first line \nsecond.\n   3\n")+                "first line \nsecond.\n   3\n"+        it "can parse single-line strings surrounded by three single quotes" $+            parseText (squote3 "I [dw]on't need \\d{2} apples")+                "I [dw]on't need \\d{2} apples"+        it "ignores a newline immediately following the opening delimiter" $+            parseText (squote3 "\na newline \nsecond.\n   3\n")+                "a newline \nsecond.\n   3\n"+        it "fails if the string has an unescaped control character other than tab" $ do+            parseText (squote3 "\t") "\t"+            textFailOn (squote3 "\b")
+ test/Test/Toml/Parser/Toml.hs view
@@ -0,0 +1,163 @@+{-# LANGUAGE PatternSynonyms #-}++module Test.Toml.Parser.Toml+    ( tomlSpecs+    ) where++import Data.List.NonEmpty (NonEmpty ((:|)))+import Data.Text (Text)+import Test.Hspec (Spec, describe, it, xit)++import Test.Toml.Parser.Common (day2, failOn, parseToml, tomlFailOn)+import Toml.Parser.Item (keyValP)+import Toml.Type.Edsl (empty, mkToml, table, tableArray, (=:))+import Toml.Type.Key (pattern (:||))+import Toml.Type.TOML (TOML (..))+import Toml.Type.Value (Value (..))++import qualified Data.List.NonEmpty as NE+import qualified Data.Text as T+++tomlSpecs :: Spec+tomlSpecs = do+    describe "Key/values" $ do+        it "can parse key/value pairs" $ do+            parseToml "x='abcdef'" $ mkToml ("x" =: "abcdef")+            parseToml "x= 1"  $ mkToml ("x" =: 1)+            parseToml "x =5.2" $ mkToml ("x" =: Double 5.2)+            parseToml "x = true" $ mkToml ("x" =: Bool True)+            parseToml "x= [1, 2, 3]" $ mkToml ("x" =: Array [1, 2, 3])+            parseToml "x =1920-12-10" $ mkToml ("x" =: Day day2)+        it "ignores white spaces around key names and values" $ do+            let toml = mkToml ("x" =: 1)+            parseToml "x=1    "   toml+            parseToml "x=    1"   toml+            parseToml "x    =1"   toml+            parseToml "x\t= 1 "   toml+            parseToml "\"x\" = 1" $ mkToml ("\"x\"" =: 1)+        xit "fails if the key, equals sign, and value are not on the same line" $ do+            failOn keyValP "x\n=\n1"+            failOn keyValP "x=\n1"+            failOn keyValP "\"x\"\n=\n1"+        it "works if the value is broken over multiple lines" $+            parseToml "x=[1, \n2\n]" $ mkToml ("x" =: Array [1, 2])+        it "fails if the value is not specified" $+            tomlFailOn "x="++    describe "tables" $ do+        it "can parse a TOML table" $ do+            let t  = mkToml $+                        table "table" $ do+                            "key1" =: "some string"+                            "key2" =: 123++            parseToml "[table] \n key1 = \"some string\"\nkey2 = 123" t+        it "can parse an empty TOML table" $+            parseToml "[table]" $ mkToml (table "table" empty)+        it "can parse a table with subarrays" $ do+            let t = mkToml $+                        table "table" $+                            tableArray "array" ("key1" =: "some string" :| ["key2" =: 123])++            parseToml "[table] \n [[table.array]] \nkey1 = \"some string\"\n \+                                  \[[table.array]] \nkey2 = 123" t+        it "can parse a TOML inline table" $+            parseToml "table={key1 = \"some string\", key2 = 123}" $+                mkToml $+                    table "table" $ do+                        "key1" =: "some string"+                        "key2" =: 123+        it "can parse an empty inline TOML table" $+            parseToml "table = {}" $ mkToml (table "table" empty)+        it "can parse a table followed by an inline table" $+            parseToml "[table1] \n  key1 = \"some string\" \n table2 = {key2 = 123}" $+                mkToml $+                    table "table1" $ do+                        "key1" =: "some string"+                        table "table2" $ "key2" =: 123+        it "can parse an empty table followed by an inline table" $+            parseToml "[table1] \n table2 = {key2 = 123}" $+                mkToml $+                    table "table1" $+                        table "table2" $+                            "key2" =: 123+        it "allows the name of the table to be any valid TOML key" $ do+            parseToml "dog.\"tater.man\"={}" $ mkToml $ table ("dog" :|| ["\"tater.man\""]) empty+            parseToml "j.\"ʞ\".'l'={}" $ mkToml $ table "j.\"ʞ\".'l'" empty++    describe "array of tables" $ do+        it "can parse an empty array" $+            parseToml "[[array]]" $ mkToml $ tableArray "array" (empty :| [])+        it "can parse an array of key/values" $ do+            let array = mkToml $+                        tableArray "array" $+                            "key1" =: "some string" :|+                            ["key2" =: 123]++            parseToml "[[array]]\n key1 = \"some string\"\n \+                       \[[array]]\n key2 = 123" array+        it "can parse an array of tables" $ do+            let table1 = table "table1" ("key1" =: "some string")+                table2 = table "table2" ("key2" =: 123)+                array = mkToml $ tableArray "array" $ table1 :| [table2]++            parseToml "[[array]]\n[array.table1] \n key1 = \"some string\"\n \+                       \[[array]]\n[array.table2] \n key2 = 123" array+        it "can parse an array of array" $ do+            let arr = tableArray "subarray" ("key1" =: "some string" :| ["key2" =: 123])+                array = mkToml $ tableArray "array" (arr :| [])++            parseToml "[[array]] \n [[array.subarray]] \nkey1 = \"some string\"\n \+                       \[[array.subarray]] \nkey2 = 123" array+        it "can parse an array of arrays" $ do+            let+                arr1 = tableArray "table-1" ("key1" =: Text "some string" :| [])+                arr2 = tableArray "table-2" ("key2" =: Integer 123 :| [])+                array = mkToml $ tableArray "array" $ (arr1 >> arr2) :| []++            parseToml "[[array]]\n [[array.table-1]] \nkey1 = \"some string\"\n \+                                     \[[array.table-2]] \nkey2 = 123" array+        it "can parse very large arrays" $ do+            let array = mkToml $ tableArray "array" $ NE.fromList $ replicate 1000 empty+            parseToml (mconcat $ replicate 1000 "[[array]]\n") array+        it "can parse an inline array of tables" $ do+            let array = mkToml $ tableArray "table" $ NE.fromList ["key1" =: "some string", "key2" =: 123]+            parseToml "table = [{key1 = \"some string\"}, {key2 = 123}]" array++    describe "TOML" $ do+        it "can parse TOML files" $+           parseToml tomlStr1 toml1+        it "can parse mix of tables and arrays" $+           parseToml tomlStr2 toml2+      where+        tomlStr1, tomlStr2 :: Text+        tomlStr1 = T.unlines+            [ " # This is a TOML document.\n\n"+            , "title = \"TOML Example\" # Comment \n\n"+            , "[owner]\n"+            , "  name = \"Tom Preston-Werner\" "+            , "  enabled = true # First class dates"+            ]+        tomlStr2 = T.unlines+            [ "[[array1]]\n key1 = \"some string\" \n"+            , ""+            , "[table1]  \n key2 = 123 \n"+            , "[[array2]]\n key3 = 3.14 \n"+            , "  [table2]  \n key4 = true"+            ]++        toml1, toml2 :: TOML+        toml1 = mkToml $ do+            "title" =: "TOML Example"+            table "owner" $ do+                "name" =: "Tom Preston-Werner"+                "enabled" =: Bool True++        toml2 = mkToml $ do+            tableArray "array1" $+                "key1" =: "some string" :| []+            table "table1" $ "key2" =: 123+            tableArray "array2" $+                "key3" =: Double 3.14 :| []+            table "table2" $ "key4" =: Bool True
+ test/Test/Toml/Parser/Validate.hs view
@@ -0,0 +1,79 @@+module Test.Toml.Parser.Validate+    ( validateSpec+    ) where++import Data.List.NonEmpty (NonEmpty (..))+import Hedgehog (evalEither, forAll)+import Test.Hspec (Arg, Expectation, Spec, SpecWith, describe, it, shouldBe)+import Test.Hspec.Hedgehog (hedgehog)+import Text.Megaparsec (parse)++import Test.Toml.Gen (genToml)+import Toml.Parser.Item (Table (..), TomlItem (..), tomlP)+import Toml.Parser.Validate (ValidationError (..), validateItems)+import Toml.Type.AnyValue (AnyValue (..))+import Toml.Type.Key (Key)+import Toml.Type.Printer (pretty)+import Toml.Type.Value (Value (..))+++validateSpec :: Spec+validateSpec = describe "Parser Validation tests" $ do+  -- property success+    validationProperty+    -- failure+    validationFail+        [keyVal "key", keyVal "key"]+        (DuplicateKey "key")+    validationFail+        [TableName "table", TableName "table"]+        (DuplicateTable "table")+    validationFail+        [keyVal "keyAndTable", TableName "keyAndTable"]+        (SameNameKeyTable "keyAndTable")+    validationFail+        [TableName "tableArray", TableArrayName "tableArray"]+        (SameNameTableArray "tableArray")+    validationFail+        [TableArrayName "tableArray", TableName "tableArray"]+        (SameNameTableArray "tableArray")+    validationFail+        [inlineTable "inline", TableName "inline"]+        (DuplicateTable "inline")+    validationFail+        [keyVal "inline", inlineTable "inline"]+        (SameNameKeyTable "inline")+    validationFail+        [inlineTableArray, TableName "inlinearray"]+        (SameNameTableArray "inlinearray")+    validationFail+        [inlineTableArray, inlineTable "inlinearray"]+        (SameNameTableArray "inlinearray")+    validationFail+        [inlineTable "inlinearray", inlineTableArray]+        (SameNameTableArray "inlinearray")++  where+    keyVal :: Key -> TomlItem+    keyVal k = KeyVal k (AnyValue $ Bool True)++    inlineTable :: Key -> TomlItem+    inlineTable k = InlineTable k table++    inlineTableArray :: TomlItem+    inlineTableArray = InlineTableArray "inlinearray" (table :| [])++    table :: Table+    table = Table []++validationProperty :: SpecWith (Arg Expectation)+validationProperty = it "Property: validates any generated TOML" $ hedgehog $ do+    toml <- forAll genToml+    let tomlText = pretty toml+    tomlItems <- evalEither $ parse tomlP "" tomlText+    _ <- evalEither (validateItems tomlItems)+    pure ()++validationFail :: [TomlItem] -> ValidationError -> SpecWith (Arg Expectation)+validationFail tomlItems validationError = it ("fail on " ++ show validationError) $+    validateItems tomlItems `shouldBe` Left validationError
− test/Test/Toml/Parsing/Examples.hs
@@ -1,21 +0,0 @@-module Test.Toml.Parsing.Examples where--import Data.Either (isRight)-import System.Directory (listDirectory)-import Test.Tasty.Hspec (Spec, describe, it, runIO, shouldBe)-import Toml.Parser (parse)--import qualified Data.Text.IO as TIO--spec_Examples :: Spec-spec_Examples = describe "Can parse official TOML examples" $ do-    files <- runIO $ listDirectory exampleDir-    mapM_ example files--example :: FilePath -> Spec-example file = it ("can parse file " ++ file) $ do-    toml <- TIO.readFile (exampleDir ++ file)-    isRight (parse toml) `shouldBe` True--exampleDir :: FilePath-exampleDir = "test/examples/"
− test/Test/Toml/Parsing/Property.hs
@@ -1,13 +0,0 @@-module Test.Toml.Parsing.Property where--import Hedgehog (forAll, tripping)--import Toml.Parser (parse)-import Toml.Printer (pretty)--import Test.Toml.Gen (PropertyTest, genToml, prop)--test_tomlRoundtrip :: PropertyTest-test_tomlRoundtrip =  prop "parse . prettyPrint == id" $ do-    toml <- forAll genToml-    tripping toml pretty parse
− test/Test/Toml/Parsing/Unit.hs
@@ -1,23 +0,0 @@-module Test.Toml.Parsing.Unit where--import Test.Tasty.Hspec (Spec)--import Test.Toml.Parsing.Unit.Array (arraySpecs)-import Test.Toml.Parsing.Unit.Bool (boolSpecs)-import Test.Toml.Parsing.Unit.Date (dateSpecs)-import Test.Toml.Parsing.Unit.Double (doubleSpecs)-import Test.Toml.Parsing.Unit.Integer (integerSpecs)-import Test.Toml.Parsing.Unit.Key (keySpecs)-import Test.Toml.Parsing.Unit.Text (textSpecs)-import Test.Toml.Parsing.Unit.Toml (tomlSpecs)--spec_Parser :: Spec-spec_Parser = do-    arraySpecs-    boolSpecs-    dateSpecs-    doubleSpecs-    integerSpecs-    keySpecs-    textSpecs-    tomlSpecs
− test/Test/Toml/Parsing/Unit/Array.hs
@@ -1,45 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}--module Test.Toml.Parsing.Unit.Array where--import Data.Time (TimeOfDay (..))-import Test.Tasty.Hspec (Spec, describe, it)--import Toml.Type (UValue (..))--import Test.Toml.Parsing.Unit.Common (arrayFailOn, day1, day2, int1, int2, int3, int4, parseArray)--arraySpecs :: Spec-arraySpecs = describe "arrayP" $ do-    it "can parse arrays" $ do-        parseArray "[]"              []-        parseArray "[1]"             [int1]-        parseArray "[1, 2, 3]"       [int1, int2, int3]-        parseArray "[1.2, 2.3, 3.4]" [UDouble 1.2, UDouble 2.3, UDouble 3.4]-        parseArray "['x', 'y']"      [UText "x", UText "y"]-        parseArray "[[1], [2]]" [UArray [UInteger 1], UArray [UInteger 2]]-        parseArray "[1920-12-10, 1979-05-27]" [UDay  day2, UDay day1]-        parseArray "[16:33:05, 10:15:30]" [UHours (TimeOfDay 16 33 5), UHours (TimeOfDay 10 15 30)]-    it "can parse multiline arrays" $-        parseArray "[\n1,\n2\n]" [int1, int2]-    it "can parse an array of arrays" $-        parseArray "[[1], [2.3, 5.1]]" [UArray [int1], UArray [UDouble 2.3, UDouble 5.1]]-    it "can parse an array with terminating commas (trailing commas)" $ do-        parseArray "[1, 2,]"        [int1, int2]-        parseArray "[1, 2, 3, , ,]" [int1, int2, int3]-    it "allows an arbitrary number of comments and newlines before or after a value" $-        parseArray "[\n\n#c\n1, #c 2 \n 2, \n\n\n 3, #c \n #c \n 4]" [int1, int2, int3, int4]-    it "ignores white spaces" $-        parseArray "[   1    ,    2,3,  4      ]" [int1, int2, int3, int4]-    it "fails if the elements are not surrounded by square brackets" $ do-        arrayFailOn "1, 2, 3"-        arrayFailOn "[1, 2, 3"-        arrayFailOn "1, 2, 3]"-        arrayFailOn "{'x', 'y', 'z'}"-        arrayFailOn "(\"ab\", \"cd\")"-        arrayFailOn "<true, false>"-    it "fails if the elements are not separated by commas" $ do-        arrayFailOn "[1 2 3]"-        arrayFailOn "[1 . 2 . 3]"-        arrayFailOn "['x' - 'y' - 'z']"-        arrayFailOn "[1920-12-10, 10:15:30]"
− test/Test/Toml/Parsing/Unit/Bool.hs
@@ -1,21 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}--module Test.Toml.Parsing.Unit.Bool where--import Test.Tasty.Hspec (Spec, describe, it)--import Test.Toml.Parsing.Unit.Common (boolFailOn, parseBool)--boolSpecs :: Spec-boolSpecs = describe "boolP" $ do-    it "can parse `true` and `false`" $ do-        parseBool "true" True-        parseBool "false" False-        parseBool "true        " True-    it "fails if `true` or `false` are not all lowercase" $ do-        boolFailOn "True"-        boolFailOn "False"-        boolFailOn "TRUE"-        boolFailOn "FALSE"-        boolFailOn "tRuE"-        boolFailOn "fAlSE"
− test/Test/Toml/Parsing/Unit/Common.hs
@@ -1,140 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}--module Test.Toml.Parsing.Unit.Common-       ( parseX-       , failOn-       , parseArray-       , parseBool-       , parseDouble-       , parseInteger-       , parseKey-       , parseText-       , parseToml-       , parseDateTime-       , arrayFailOn-       , boolFailOn-       , integerFailOn-       , dateTimeFailOn-       , doubleFailOn-       , textFailOn-       , tomlFailOn-       , quoteWith-       , squote-       , dquote-       , squote3-       , dquote3-       , makeOffset-       , makeZoned-       , int1-       , int2-       , int3-       , int4-       , offset710-       , offset0-       , day1-       , day2-       , hours1-       ) where--import Data.Semigroup ((<>))-import Data.Text (Text)-import Data.Time (Day, LocalTime (..), TimeOfDay (..), TimeZone, ZonedTime (..), fromGregorian,-                  minutesToTimeZone)-import Test.Hspec.Megaparsec (shouldFailOn, shouldParse)-import Test.Tasty.Hspec (Expectation)-import Text.Megaparsec (Parsec, ShowErrorComponent, Stream, parse)--import Toml.Parser.Item (tomlP)-import Toml.Parser.Key (keyP)-import Toml.Parser.String (textP)-import Toml.Parser.Validate (validateItems)-import Toml.Parser.Value (arrayP, boolP, dateTimeP, doubleP, integerP)-import Toml.PrefixTree (Key (..))-import Toml.Type (TOML (..), UValue (..))--------------------------------------------------------------------------------- Utilities-------------------------------------------------------------------------------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-failOn p given = parse p "" `shouldFailOn` given--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 test toml = parseX (validateItems <$> tomlP) test (Right toml)--arrayFailOn-  , boolFailOn-  , dateTimeFailOn-  , doubleFailOn-  , integerFailOn-  , textFailOn-  , tomlFailOn :: Text -> Expectation-arrayFailOn     = failOn arrayP-boolFailOn      = failOn boolP-dateTimeFailOn  = failOn dateTimeP-doubleFailOn    = failOn doubleP-integerFailOn   = failOn integerP-textFailOn      = failOn textP-tomlFailOn      = failOn tomlP---- Surround given text with quotes.-quoteWith :: Text -> Text -> Text-quoteWith q t = q <> t <> q-squote, dquote, squote3, dquote3 :: Text -> Text-squote = quoteWith "'"-dquote = quoteWith "\""-squote3 = quoteWith "'''"-dquote3 = quoteWith "\"\"\""---- UValue Util--makeZoned :: Day -> TimeOfDay -> TimeZone -> UValue-makeZoned d h offset = UZoned $ ZonedTime (LocalTime d h) offset--makeOffset :: Int -> Int -> TimeZone-makeOffset hours mins = minutesToTimeZone (hours * 60 + mins * signum hours)---- Test Data--int1, int2, int3, int4 :: UValue-int1 = UInteger 1-int2 = UInteger 2-int3 = UInteger 3-int4 = UInteger 4--offset0, offset710 :: TimeZone-offset0 = makeOffset 0 0-offset710 = makeOffset 7 10--day1, day2 :: Day-day1 = fromGregorian 1979 5 27  -- 1979-05-27-day2 = fromGregorian 1920 12 10 -- 1920-12-10--hours1 :: TimeOfDay-hours1 = TimeOfDay 7 32 0  -- 07:32:00
− test/Test/Toml/Parsing/Unit/Date.hs
@@ -1,61 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}--module Test.Toml.Parsing.Unit.Date where--import Data.Time (LocalTime (..), TimeOfDay (..))--import Toml.Type (UValue (..))--import Test.Tasty.Hspec (Spec, describe, it)--import Test.Toml.Parsing.Unit.Common (dateTimeFailOn, day1, hours1, makeOffset, makeZoned, offset0,-                                      offset710, parseDateTime)--dateSpecs :: Spec-dateSpecs = describe "dateTimeP" $ do-    it "can parse a date-time with an offset" $ do-        parseDateTime "1979-05-27T07:32:00Z" $ makeZoned day1 hours1 offset0-        parseDateTime "1979-05-27T00:32:00+07:10" $-            makeZoned day1 (TimeOfDay 0 32 0) offset710-        parseDateTime "1979-05-27T00:32:00.999999-07:25" $-            makeZoned day1 (TimeOfDay 0 32 0.999999) (makeOffset (-7) 25)-    it "can parse a date-time with an offset when the T delimiter is replaced with a space" $-        parseDateTime "1979-05-27 07:32:00Z" $ makeZoned day1 hours1 offset0-    it "can parse a date-time without an offset" $ do-        parseDateTime "1979-05-27T17:32:00"-            (ULocal $ LocalTime day1 (TimeOfDay 17 32 0))-        parseDateTime "1979-05-27T00:32:00.999999"-            (ULocal $ LocalTime day1 (TimeOfDay 0 32 0.999999))-    it "can parse a local date"-        $ parseDateTime "1979-05-27" (UDay day1)-    it "can parse a local time" $ do-        parseDateTime "07:32:00"        (UHours hours1)-        parseDateTime "00:32:00.999999" (UHours $ TimeOfDay 0 32 0.999999)-    it "truncates the additional precision after picoseconds in the fractional seconds" $-        parseDateTime "00:32:00.99999999999199" (UHours $ TimeOfDay 0 32 0.999999999991)-    it "fails if the date is not valid" $ do-        dateTimeFailOn "1920-15-12"-        dateTimeFailOn "1920-12-40"-    it "fails if the date does not have the form: 'yyyy-mm-dd'" $ do-        dateTimeFailOn "1920-01-1"-        dateTimeFailOn "1920-1-01"-        dateTimeFailOn "920-01-01"-        dateTimeFailOn "1920/10/01"-    it "fails if the time is not valid" $ do-        dateTimeFailOn "25:10:10"-        dateTimeFailOn "10:70:10"-        dateTimeFailOn "10:10:70"-    it "fails if the time does not have the form: 'hh:mm:ss'" $ do-        dateTimeFailOn "1:12:12"-        dateTimeFailOn "12:1:12"-        dateTimeFailOn "12:12:1"-        dateTimeFailOn "12-12-12"-    it "fails if the offset does not have any of the forms: 'Z', '+hh:mm', '-hh:mm'" $ do-        parseDateTime "1979-05-27T00:32:00X"-            (ULocal $ LocalTime day1 (TimeOfDay 0 32 0))-        parseDateTime "1979-05-27T00:32:00+07:1"-            (ULocal $ LocalTime day1 (TimeOfDay 0 32 0))-        parseDateTime "1979-05-27T00:32:00+7:01"-            (ULocal $ LocalTime day1 (TimeOfDay 0 32 0))-        parseDateTime "1979-05-27T00:32:0007:00"-            (ULocal $ LocalTime day1 (TimeOfDay 0 32 0))
− test/Test/Toml/Parsing/Unit/Double.hs
@@ -1,45 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}--module Test.Toml.Parsing.Unit.Double where--import Test.Hspec.Megaparsec (parseSatisfies)-import Test.Tasty.Hspec (Spec, describe, it)-import Text.Megaparsec (parse)--import Toml.Parser.Value (doubleP)--import Test.Toml.Parsing.Unit.Common (doubleFailOn, parseDouble)--doubleSpecs :: Spec-doubleSpecs = describe "doubleP" $ do-    it "can parse a number which consists of an integral part, and a fractional part" $ do-        parseDouble "+1.0"      1.0-        parseDouble "3.1415"    3.1415-        parseDouble "0.0"       0.0-        parseDouble "-0.01"     (-0.01)-        parseDouble "5e+22"     5e+22-        parseDouble "1e6"       1e6-        parseDouble "-2E-2"     (-2E-2)-        parseDouble "6.626e-34" 6.626e-34-    it "can parse a number with underscores" $ do-        parseDouble "5e+2_2"         5e+22-        parseDouble "1.1_1e6"        1.11e6-        parseDouble "-2_2.21_9E-0_2" (-22.219E-2)-    it "can parse sign-prefixed zero" $ do-        parseDouble "+0.0" 0.0-        parseDouble "-0.0" (-0.0)-    it "can parse positive and negative special float values (inf and nan)" $ do-        parseDouble "inf"  (1 / 0)-        parseDouble "+inf" (1 / 0)-        parseDouble "-inf" (-1 / 0)-        doubleSatisfies "nan"  isNaN-        doubleSatisfies "+nan" isNaN-        doubleSatisfies "-nan" isNaN-    it "fails if `inf` or `nan` are not all lowercase" $ do-        doubleFailOn "Inf"-        doubleFailOn "INF"-        doubleFailOn "Nan"-        doubleFailOn "NAN"-        doubleFailOn "NaN"-  where-    doubleSatisfies given f = parse doubleP "" given `parseSatisfies` f
− test/Test/Toml/Parsing/Unit/Integer.hs
@@ -1,82 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}--module Test.Toml.Parsing.Unit.Integer where--import Test.Tasty.Hspec (Spec, context, describe, it)--import Test.Toml.Parsing.Unit.Common (integerFailOn, parseInteger)--integerSpecs :: Spec-integerSpecs = describe "integerP" $ do-    context "when the integer is in decimal representation" $ do-        it "can parse positive integer numbers" $ do-            parseInteger "10" 10-            parseInteger "+3" 3-            parseInteger "0"  0-        it "can parse negative integer numbers" $-            parseInteger "-123" (-123)-        it "can parse sign-prefixed zero as an unprefixed zero" $ do-            parseInteger "+0" 0-            parseInteger "-0" 0-        it "can parse both the minimum and maximum numbers in the 64 bit range" $ do-            parseInteger "-9223372036854775808" (-9223372036854775808)-            parseInteger "9223372036854775807"  9223372036854775807-        it "can parse numbers with underscores between digits" $ do-            parseInteger "1_000" 1000-            parseInteger "5_349_221" 5349221-            parseInteger "1_2_3_4_5" 12345-        it "does not parse incorrect underscores" $ do-            integerFailOn "1_2_3_"-            integerFailOn "13_"-            integerFailOn "_123_"-            integerFailOn "_13"-            integerFailOn "_"-        it "does not parse numbers with leading zeros" $ do-            parseInteger "0123" 0-            parseInteger "-023" 0-    context "when the integer is in binary representation" $ do-        it "can parse numbers prefixed with `0b`" $ do-            parseInteger "0b1101" 13-            parseInteger "0b0"    0-        it "does not parse numbers prefixed with `0B`" $-            parseInteger "0B1101" 0-        it "can parse numbers with leading zeros after the prefix" $ do-            parseInteger "0b000"   0-            parseInteger "0b00011" 3-        it "does not parse negative numbers" $-            parseInteger "-0b101" 0-        it "does not parse numbers with non-valid binary digits" $-            parseInteger "0b123" 1-    context "when the integer is in octal representation" $ do-        it "can parse numbers prefixed with `0o`" $ do-            parseInteger "0o567" 0o567-            parseInteger "0o0"   0-        it "does not parse numbers prefixed with `0O`" $-            parseInteger "0O567" 0-        it "can parse numbers with leading zeros after the prefix" $ do-            parseInteger "0o000000" 0-            parseInteger "0o000567" 0o567-        it "does not parse negative numbers" $-            parseInteger "-0o123" 0-        it "does not parse numbers with non-valid octal digits" $-            parseInteger "0o789" 0o7-    context "when the integer is in hexadecimal representation" $ do-        it "can parse numbers prefixed with `0x`" $ do-            parseInteger "0x12af" 0x12af-            parseInteger "0x0"    0-        it "does not parse numbers prefixed with `0X`" $-            parseInteger "0Xfff" 0-        it "can parse numbers with leading zeros after the prefix" $ do-            parseInteger "0x00000" 0-            parseInteger "0x012af" 0x12af-        it "does not parse negative numbers" $-            parseInteger "-0xfff" 0-        it "does not parse numbers with non-valid hexadecimal digits" $-            parseInteger "0xfgh" 0xf-        it "can parse numbers when hex digits are lowercase" $-            parseInteger "0xabcdef" 0xabcdef-        it "can parse numbers when hex digits are uppercase" $-            parseInteger "0xABCDEF" 0xABCDEF-        it "can parse numbers when hex digits are in both lowercase and uppercase" $ do-            parseInteger "0xAbCdEf" 0xAbCdEf-            parseInteger "0xaBcDeF" 0xaBcDeF
− test/Test/Toml/Parsing/Unit/Key.hs
@@ -1,35 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE PatternSynonyms  #-}--module Test.Toml.Parsing.Unit.Key where--import Test.Tasty.Hspec (Spec, context, describe, it)-import Toml.PrefixTree (pattern (:||))--import Test.Toml.Parsing.Unit.Common (dquote, parseKey, squote)--keySpecs :: Spec-keySpecs = describe "keyP" $ do-    context "when the key is a bare key" $ do-        it "can parse keys which contain ASCII letters, digits, underscores, and dashes" $ do-            parseKey "key"       "key"-            parseKey "bare_key1" "bare_key1"-            parseKey "bare-key2" "bare-key2"-        it "can parse keys which contain only digits" $-            parseKey "1234" "1234"-    context "when the key is a quoted key" $ do-        it "can parse keys that follow the exact same rules as basic strings" $ do-            parseKey (dquote "127.0.0.1") ("\"127.0.0.1\"" :|| [])-            parseKey (dquote "character encoding") "\"character encoding\""-            parseKey (dquote "ʎǝʞ") "\"ʎǝʞ\""-        it "can parse keys that follow the exact same rules as literal strings" $ do-            parseKey (squote "key2") "'key2'"-            parseKey (squote "quoted \"value\"") "'quoted \"value\"'"-    context "when the key is a dotted key" $ do-        it "can parse a sequence of bare or quoted keys joined with a dot" $ do-            parseKey "name"           "name"-            parseKey "physical.color" "physical.color"-            parseKey "physical.shape" "physical.shape"-            parseKey "site.\"google.com\"" ("site" :|| ["\"google.com\""])-        -- it "ignores whitespaces around dot-separated parts" $ do-        --     parseKey "a . b . c. d" (makeKey ["a", "b", "c", "d"])
− test/Test/Toml/Parsing/Unit/Text.hs
@@ -1,95 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}--module Test.Toml.Parsing.Unit.Text where--import Test.Tasty.Hspec (Spec, context, describe, it)--import Test.Toml.Parsing.Unit.Common (dquote, dquote3, parseText, squote, squote3, textFailOn)--textSpecs :: Spec-textSpecs = describe "textP" $ do-    context "when the string is a basic string" $ do-        it "can parse strings surrounded by double quotes" $ do-            parseText (dquote "xyz") "xyz"-            parseText (dquote "")    ""-            textFailOn "\"xyz"-            textFailOn "xyz\""-            textFailOn "xyz"-        it "can parse escaped quotation marks, backslashes, and control characters" $ do-            parseText (dquote "backspace: \\b") "backspace: \b"-            parseText (dquote "tab: \\t")       "tab: \t"-            parseText (dquote "linefeed: \\n")  "linefeed: \n"-            parseText (dquote "form feed: \\f") "form feed: \f"-            parseText (dquote "carriage return: \\r") "carriage return: \r"-            parseText (dquote "quote: \\\"")     "quote: \""-            parseText (dquote "backslash: \\\\") "backslash: \\"-            parseText (dquote "a\\uD7FFxy\\U0010FFFF\\uE000") "a\55295xy\1114111\57344"-        it "fails if the string has an unescaped backslash, or control character" $ do-            textFailOn (dquote "new \n line")-            textFailOn (dquote "back \\ slash")-        it "fails if the string has an escape sequence that is not listed in the TOML specification" $-            textFailOn (dquote "xy\\z \\abc")-        it "fails if the string is not on a single line" $ do-            textFailOn (dquote "\nabc")-            textFailOn (dquote "ab\r\nc")-            textFailOn (dquote "abc\n")-        it "fails if escape codes are not valid Unicode scalar values" $ do-            textFailOn (dquote "\\u1")-            textFailOn (dquote "\\uxyzw")-            textFailOn (dquote "\\U0000")-            textFailOn (dquote "\\uD8FF")-            textFailOn (dquote "\\U001FFFFF")-    context "when the string is a multi-line basic string" $ do--        it "can parse multi-line strings surrounded by three double quotes" $-            parseText (dquote3 "Roses are red\nViolets are blue")-                "Roses are red\nViolets are blue"-        it "can parse single-line strings surrounded by three double quotes" $-            parseText (dquote3 "Roses are red Violets are blue")-                "Roses are red Violets are blue"-        it "can parse all of the escape sequences that are valid for basic strings" $ do-            parseText (dquote3 "backspace: \\b") "backspace: \b"-            parseText (dquote3 "tab: \\t")       "tab: \t"-            parseText (dquote3 "linefeed: \\n")  "linefeed: \n"-            parseText (dquote3 "form feed: \\f") "form feed: \f"-            parseText (dquote3 "carriage return: \\r") "carriage return: \r"-            parseText (dquote3 "quote: \\\"")     "quote: \""-            parseText (dquote3 "backslash: \\\\") "backslash: \\"-            parseText (dquote3 "a\\uD7FFxy\\U0010FFFF\\uE000") "a\55295xy\1114111\57344"-        it "does not ignore whitespaces or newlines" $-            parseText (dquote3 "\nabc  \n   xyz") "abc  \n   xyz"-        it "ignores a newline only if it immediately follows the opening delimiter" $-            parseText (dquote3 "\nThe quick brown") "The quick brown"-        it "ignores whitespaces and newlines after line ending backslash" $-            parseText (dquote3 "The quick brown \\\n\n   fox jumps over") "The quick brown fox jumps over"-        it "fails if the string has an unescaped backslash, or control character" $ do-            textFailOn (dquote3 "backslash \\ .")-            textFailOn (dquote3 "backspace \b ..")-            textFailOn (dquote3 "tab \t ..")-    context "when the string is a literal string" $ do-        it "can parse strings surrounded by single quotes" $ do-            parseText (squote "C:\\Users\\nodejs\\templates")-                      "C:\\Users\\nodejs\\templates"-            parseText (squote "\\\\ServerX\\admin$\\system32\\")-                      "\\\\ServerX\\admin$\\system32\\"-            parseText (squote "Tom \"Dubs\" Preston-Werner")-                      "Tom \"Dubs\" Preston-Werner"-            parseText (squote "<\\i\\c*\\s*>") "<\\i\\c*\\s*>"-            parseText (squote "a \t tab")      "a \t tab"-        it "fails if the string is not on a single line" $ do-            textFailOn (squote "\nabc")-            textFailOn (squote "ab\r\nc")-            textFailOn (squote "abc\n")-    context "when the string is a multi-line literal string" $ do-        it "can parse multi-line strings surrounded by three single quotes" $-            parseText (squote3 "first line \nsecond.\n   3\n")-                "first line \nsecond.\n   3\n"-        it "can parse single-line strings surrounded by three single quotes" $-            parseText (squote3 "I [dw]on't need \\d{2} apples")-                "I [dw]on't need \\d{2} apples"-        it "ignores a newline immediately following the opening delimiter" $-            parseText (squote3 "\na newline \nsecond.\n   3\n")-                "a newline \nsecond.\n   3\n"-        it "fails if the string has an unescaped control character other than tab" $ do-            parseText (squote3 "\t") "\t"-            textFailOn (squote3 "\b")
− test/Test/Toml/Parsing/Unit/Toml.hs
@@ -1,162 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE PatternSynonyms  #-}--module Test.Toml.Parsing.Unit.Toml where--import Data.List.NonEmpty (NonEmpty ((:|)))-import Data.Semigroup ((<>))-import Data.Text (Text)--import Test.Tasty.Hspec (Spec, describe, it)--import Toml.Edsl (empty, mkToml, table, tableArray, (=:))-import Toml.PrefixTree (pattern (:||))-import Toml.Type (TOML (..), Value (..))--import qualified Data.List.NonEmpty as NE-import qualified Data.Text as T--import Test.Toml.Parsing.Unit.Common (day2, parseToml, tomlFailOn)--tomlSpecs :: Spec-tomlSpecs = do-    describe "Key/values" $ do-        it "can parse key/value pairs" $ do-            parseToml "x='abcdef'" $ mkToml ("x" =: "abcdef")-            parseToml "x= 1"  $ mkToml ("x" =: 1)-            parseToml "x =5.2" $ mkToml ("x" =: Double 5.2)-            parseToml "x = true" $ mkToml ("x" =: Bool True)-            parseToml "x= [1, 2, 3]" $ mkToml ("x" =: Array [1, 2, 3])-            parseToml "x =1920-12-10" $ mkToml ("x" =: Day day2)-        it "ignores white spaces around key names and values" $ do-            let toml = mkToml ("x" =: 1)-            parseToml "x=1    "   toml-            parseToml "x=    1"   toml-            parseToml "x    =1"   toml-            parseToml "x\t= 1 "   toml-            parseToml "\"x\" = 1" $ mkToml ("\"x\"" =: 1)-        --xit "fails if the key, equals sign, and value are not on the same line" $ do-        --  keyValFailOn "x\n=\n1"-        --  keyValFailOn "x=\n1"-        --  keyValFailOn "\"x\"\n=\n1"-        it "works if the value is broken over multiple lines" $-            parseToml "x=[1, \n2\n]" $ mkToml ("x" =: Array [1, 2])-        it "fails if the value is not specified" $-            tomlFailOn "x="--    describe "tables" $ do-        it "can parse a TOML table" $ do-            let t  = mkToml $-                        table "table" $ do-                            "key1" =: "some string"-                            "key2" =: 123--            parseToml "[table] \n key1 = \"some string\"\nkey2 = 123" t-        it "can parse an empty TOML table" $-            parseToml "[table]" $ mkToml (table "table" empty)-        it "can parse a table with subarrays" $ do-            let t = mkToml $-                        table "table" $-                            tableArray "array" ("key1" =: "some string" :| ["key2" =: 123])--            parseToml "[table] \n [[table.array]] \nkey1 = \"some string\"\n \-                                  \[[table.array]] \nkey2 = 123" t-        it "can parse a TOML inline table" $-            parseToml "table={key1 = \"some string\", key2 = 123}" $-                mkToml $-                    table "table" $ do-                        "key1" =: "some string"-                        "key2" =: 123-        it "can parse an empty inline TOML table" $-            parseToml "table = {}" $ mkToml (table "table" empty)-        it "can parse a table followed by an inline table" $-            parseToml "[table1] \n  key1 = \"some string\" \n table2 = {key2 = 123}" $-                mkToml $-                    table "table1" $ do-                        "key1" =: "some string"-                        table "table2" $ "key2" =: 123-        it "can parse an empty table followed by an inline table" $-            parseToml "[table1] \n table2 = {key2 = 123}" $-                mkToml $-                    table "table1" $-                        table "table2" $-                            "key2" =: 123-        it "allows the name of the table to be any valid TOML key" $ do-            parseToml "dog.\"tater.man\"={}" $ mkToml $ table ("dog" :|| ["\"tater.man\""]) empty-            parseToml "j.\"ʞ\".'l'={}" $ mkToml $ table "j.\"ʞ\".'l'" empty--    describe "array of tables" $ do-        it "can parse an empty array" $-            parseToml "[[array]]" $ mkToml $ tableArray "array" (empty :| [])-        it "can parse an array of key/values" $ do-            let array = mkToml $-                        tableArray "array" $-                            "key1" =: "some string" :|-                            ["key2" =: 123]--            parseToml "[[array]]\n key1 = \"some string\"\n \-                       \[[array]]\n key2 = 123" array-        it "can parse an array of tables" $ do-            let table1 = table "table1" ("key1" =: "some string")-                table2 = table "table2" ("key2" =: 123)-                array = mkToml $ tableArray "array" $ table1 :| [table2]--            parseToml "[[array]]\n[array.table1] \n key1 = \"some string\"\n \-                       \[[array]]\n[array.table2] \n key2 = 123" array-        it "can parse an array of array" $ do-            let arr = tableArray "subarray" ("key1" =: "some string" :| ["key2" =: 123])-                array = mkToml $ tableArray "array" (arr :| [])--            parseToml "[[array]] \n [[array.subarray]] \nkey1 = \"some string\"\n \-                       \[[array.subarray]] \nkey2 = 123" array-        it "can parse an array of arrays" $ do-            let-                arr1 = tableArray "table-1" ("key1" =: Text "some string" :| [])-                arr2 = tableArray "table-2" ("key2" =: Integer 123 :| [])-                array = mkToml $ tableArray "array" $ (arr1 >> arr2) :| []--            parseToml "[[array]]\n [[array.table-1]] \nkey1 = \"some string\"\n \-                                     \[[array.table-2]] \nkey2 = 123" array-        it "can parse very large arrays" $ do-            let array = mkToml $ tableArray "array" $ NE.fromList $ replicate 1000 empty-            parseToml (mconcat $ replicate 1000 "[[array]]\n") array-        it "can parse an inline array of tables" $ do-            let array = mkToml $ tableArray "table" $ NE.fromList ["key1" =: "some string", "key2" =: 123]-            parseToml "table = [{key1 = \"some string\"}, {key2 = 123}]" array--    describe "TOML" $ do-        it "can parse TOML files" $-           parseToml tomlStr1 toml1-        it "can parse mix of tables and arrays" $-           parseToml tomlStr2 toml2-      where-        tomlStr1, tomlStr2 :: Text-        tomlStr1 = T.unlines-            [ " # This is a TOML document.\n\n"-            , "title = \"TOML Example\" # Comment \n\n"-            , "[owner]\n"-            , "  name = \"Tom Preston-Werner\" "-            , "  enabled = true # First class dates"-            ]-        tomlStr2 = T.unlines-            [ "[[array1]]\n key1 = \"some string\" \n"-            , ""-            , "[table1]  \n key2 = 123 \n"-            , "[[array2]]\n key3 = 3.14 \n"-            , "  [table2]  \n key4 = true"-            ]--        toml1, toml2 :: TOML-        toml1 = mkToml $ do-            "title" =: "TOML Example"-            table "owner" $ do-                "name" =: "Tom Preston-Werner"-                "enabled" =: Bool True--        toml2 = mkToml $ do-            tableArray "array1" $-                "key1" =: "some string" :| []-            table "table1" $ "key2" =: 123-            tableArray "array2" $-                "key3" =: Double 3.14 :| []-            table "table2" $ "key4" =: Bool True
− test/Test/Toml/PrefixTree/Property.hs
@@ -1,73 +0,0 @@-module Test.Toml.PrefixTree.Property where--import Hedgehog (forAll, tripping, (===))--import Test.Toml.Gen (PropertyTest, genKey, genPrefixMap, genVal, prop)-import Test.Toml.Property (assocLaw, identityLaw)--import Data.String (IsString (..))--import qualified Data.Text as Text-import qualified Toml.PrefixTree as Prefix-import qualified Toml.Printer as Printer--------------------------------------------------------------------------------- Key printing and parsing-------------------------------------------------------------------------------test_KeyPrinting :: PropertyTest-test_KeyPrinting = prop "Key printing: fromString . prettyKey == id" $ do-    key <- forAll genKey-    tripping key Printer.prettyKey (Just . fromString . Text.unpack)--------------------------------------------------------------------------------- InsertLookup-------------------------------------------------------------------------------test_PrefixTreeInsertLookup :: PropertyTest-test_PrefixTreeInsertLookup =  prop "lookup k (insert k v m) == Just v" $ do-    t   <- forAll genPrefixMap-    key <- forAll genKey-    val <- forAll genVal--    Prefix.lookup key (Prefix.insert key val t) === Just val--    -- DEBUG: ensures that trees of depth at least 5 are generated-    -- assert $ depth prefMap < 5--------------------------------------------------------------------------------- InsertInsert-------------------------------------------------------------------------------test_PrefixTreeInsertInsert :: PropertyTest-test_PrefixTreeInsertInsert =  prop "insert x a . insert x b == insert x a" $ do-    t <- forAll genPrefixMap-    x <- forAll genKey-    a <- forAll genVal-    b <- forAll genVal--    Prefix.lookup x (Prefix.insert x a $ Prefix.insert x b t) === Just a--------------------------------------------------------------------------------- DEBUG--------------------------------------------------------------------------------- useful functions to test generators--- uncomment when you need them---- depth :: PrefixMap a -> Int--- depth = HashMap.foldl' (\acc t -> max acc (depthT t)) 0------ depthT :: PrefixTree a -> Int--- depthT (Leaf _ _)           = 1--- depthT (Branch _ _ prefMap) = 1 + depth prefMap--------------------------------------------------------------------------------- Laws-------------------------------------------------------------------------------test_PrefixTreeAssocLaw :: PropertyTest-test_PrefixTreeAssocLaw = assocLaw genPrefixMap--test_PrefixTreeIdentityLaw :: PropertyTest-test_PrefixTreeIdentityLaw = identityLaw genPrefixMap
− test/Test/Toml/PrefixTree/Unit.hs
@@ -1,43 +0,0 @@-{-# LANGUAGE PatternSynonyms  #-}-{-# LANGUAGE TypeApplications #-}--module Test.Toml.PrefixTree.Unit where--import Test.Tasty.Hspec (Spec, describe, it, shouldBe)--import Toml.PrefixTree (pattern (:||))--import qualified Toml.PrefixTree as Prefix--spec_PrefixTree :: Spec-spec_PrefixTree = do-  -- some test keys-  let a  = "a" :|| []-  let b  = "b" :|| []-  let c  = "c" :|| []-  let ab = "a" :|| ["b"]--  describe "Insert and lookup unit tests" $ do-    it "Lookup on empty map returns Nothing" $-      Prefix.lookup @Bool a mempty `shouldBe` Nothing-    it "Lookup in single map returns this element" $ do-      let t = Prefix.single a True-      Prefix.lookup a t `shouldBe` Just True-      Prefix.lookup b t `shouldBe` Nothing-    it "Lookup after insert returns this element" $ do-      let t = Prefix.insert a True mempty-      Prefix.lookup a t `shouldBe` Just True-      Prefix.lookup b t `shouldBe` Nothing-    it "Lookup after multiple non-overlapping inserts" $ do-      let t = Prefix.insert a True $ Prefix.insert b False mempty-      Prefix.lookup a t `shouldBe` Just True-      Prefix.lookup b t `shouldBe` Just False-      Prefix.lookup c t `shouldBe` Nothing-    it "Prefix lookup" $ do-      let t = Prefix.insert ab True mempty-      Prefix.lookup a  t `shouldBe` Nothing-      Prefix.lookup ab t `shouldBe` Just True-    it "Composite key lookup" $ do-      let t = Prefix.insert a True $ Prefix.insert ab False mempty-      Prefix.lookup a  t `shouldBe` Just True-      Prefix.lookup ab t `shouldBe` Just False
− test/Test/Toml/Printer/Golden.hs
@@ -1,64 +0,0 @@--- | This module contains golden tests for @Toml.Printer@.--module Test.Toml.Printer.Golden-       ( test_prettyGolden-       ) where--import Data.List.NonEmpty (NonEmpty ((:|)))-import Data.Ord (comparing)-import Test.Tasty (TestTree, testGroup)-import Test.Tasty.Silver (goldenVsAction)--import Toml (TOML, Value (..))-import Toml.Edsl (empty, mkToml, table, tableArray, (=:))-import Toml.PrefixTree (Key (..), (<|))-import Toml.Printer (PrintOptions (..), defaultOptions, prettyOptions)--example :: TOML-example = mkToml $ do-    "b" =: "bb"-    "a" =: "a"-    "d" =: "ddd"-    "c" =: "cccc"-    table ("qux" <| "doo") $ do-      "spam" =: "!"-      "egg" =: "?"-    table "foo" empty-    table "doo" empty-    table "baz" empty-    tableArray "deepest" $-      "ping" =: "pong"-      :| [empty]-    tableArray "deeper" $-      "green" =: Bool True-      :| [table "blue" ("red" =: Integer 255)]--noFormatting :: PrintOptions-noFormatting = PrintOptions-    { printOptionsSorting = Nothing-    , printOptionsIndent  = 0-    }---- | Decorate keys as tuples so spam comes before egg-spamEggDecorate :: Key -> (Int, Key)-spamEggDecorate k-    | k == "spam" = (0, "spam")-    | k == "egg" = (1, "egg")-    | otherwise = (2, k)--spamEgg :: Key -> Key -> Ordering-spamEgg = comparing spamEggDecorate--test_prettyGolden :: TestTree-test_prettyGolden =-    testGroup "Toml.Printer"-        [ test "pretty_default" defaultOptions-        , test "pretty_sorted_only" noFormatting { printOptionsSorting = Just compare }-        , test "pretty_indented_only" noFormatting { printOptionsIndent = 4 }-        , test "pretty_unformatted" noFormatting-        , test "pretty_custom_sorted" noFormatting { printOptionsSorting = Just spamEgg }-        ]-  where-    test name options =-        goldenVsAction name ("test/golden/" ++ name ++ ".golden")-            (pure $ prettyOptions options example) id
test/Test/Toml/Property.hs view
@@ -1,39 +1,55 @@ module Test.Toml.Property-       ( assocLaw-       , identityLaw+       ( assocSemigroup+       , rightIdentityMonoid+       , leftIdentityMonoid        ) where -import Data.Semigroup (Semigroup ((<>))) import Hedgehog (Gen, forAll, (===))+import Test.Hspec (Arg, Expectation, SpecWith, it)+import Test.Hspec.Hedgehog (hedgehog) -import Test.Toml.Gen (PropertyTest, prop)  {- | The semigroup associativity axiom:  @ x <> (y <> z) ≡ (x <> y) <> z @- -}-assocLaw :: (Eq a, Show a, Semigroup a) => Gen a -> PropertyTest-assocLaw gen = prop "Semigroup associativity law" $ do+assocSemigroup+    :: (Eq a, Show a, Semigroup a)+    => Gen a+    -> SpecWith (Arg Expectation)+assocSemigroup gen = it "Semigroup associativity: x <> (y <> z) ≡ (x <> y) <> z" $ hedgehog $ do     x <- forAll gen     y <- forAll gen     z <- forAll gen      (x <> (y <> z)) === ((x <> y) <> z) -{- | Identity law for Monoid+{- | Right Identity law for Monoid  @-mempty <> x = x-x <> mempty = x+x <> mempty ≡ x @- -}-identityLaw :: (Eq a, Show a, Semigroup a, Monoid a) => Gen a -> PropertyTest-identityLaw gen = prop "Monoid identity laws" $ do+rightIdentityMonoid+    :: (Eq a, Show a, Monoid a)+    => Gen a+    -> SpecWith (Arg Expectation)+rightIdentityMonoid gen = it "Monoid Right Identity: x <> mempty ≡ x" $ hedgehog $ do     x <- forAll gen-     x <> mempty === x++{- | Left Identity law for Monoid++@+mempty <> x ≡ x+@+-}+leftIdentityMonoid+    :: (Eq a, Show a, Monoid a)+    => Gen a+    -> SpecWith (Arg Expectation)+leftIdentityMonoid gen = it "Monoid Right Identity: mempty <> x ≡ x" $ hedgehog $ do+    x <- forAll gen     mempty <> x === x
− test/Test/Toml/TOML/Property.hs
@@ -1,15 +0,0 @@-module Test.Toml.TOML.Property where--import Test.Toml.Gen (PropertyTest, genToml)-import Test.Toml.Property (assocLaw, identityLaw)---------------------------------------------------------------------------------- Laws-------------------------------------------------------------------------------test_TomlAssocLaw :: PropertyTest-test_TomlAssocLaw = assocLaw genToml--test_TomlIdentityLaw :: PropertyTest-test_TomlIdentityLaw = identityLaw genToml
+ test/Test/Toml/Type.hs view
@@ -0,0 +1,18 @@+module Test.Toml.Type+    ( typeSpec+    ) where++import Test.Hspec (Spec, describe, parallel)++import Test.Toml.Type.Key (keySpec)+import Test.Toml.Type.PrefixTree (prefixTreeSpec)+import Test.Toml.Type.Printer (printerSpec)+import Test.Toml.Type.TOML (tomlSpec)+++typeSpec :: Spec+typeSpec = parallel $ describe "Toml.Type tests" $ do+    keySpec+    prefixTreeSpec+    printerSpec+    tomlSpec
+ test/Test/Toml/Type/Key.hs view
@@ -0,0 +1,47 @@+module Test.Toml.Type.Key+    ( keySpec+    ) where++import Hedgehog (forAll, tripping)+import Test.Hspec (Arg, Expectation, Spec, SpecWith, describe, it, shouldBe)+import Test.Hspec.Hedgehog (hedgehog)++import Test.Toml.Gen (genKey)+import Toml.Type.Key (KeysDiff (..), keysDiff)++import qualified Toml.Parser as Parser+import qualified Toml.Type.Printer as Printer+++keySpec :: Spec+keySpec = describe "TOML Key" $ do+    keyRoundtripSpec+    keysDiffSpec++keyRoundtripSpec :: SpecWith (Arg Expectation)+keyRoundtripSpec = it "Key printing: fromString . prettyKey ≡ id" $ hedgehog $ do+    key <- forAll genKey+    tripping key Printer.prettyKey Parser.parseKey++keysDiffSpec :: Spec+keysDiffSpec = describe "Key difference" $ do+    it "Equal: Simple" $+        keysDiff "key" "key" `shouldBe` Equal+    it "Equal: Complex" $+        keysDiff "foo.bar.baz" "foo.bar.baz" `shouldBe` Equal+    it "NoPrefix: Simple" $+        keysDiff "foo" "bar" `shouldBe` NoPrefix+    it "NoPrefix: Only common suffix" $+        keysDiff "foo.key" "bar.key" `shouldBe` NoPrefix+    it "FstIsPref" $+        keysDiff "key" "key.nest" `shouldBe` FstIsPref "nest"+    it "SndIsPref" $+        keysDiff "key.nest" "key" `shouldBe` SndIsPref "nest"+    it "Diff: Simple" $+        keysDiff "key.foo" "key.bar" `shouldBe` Diff "key" "foo" "bar"+    it "Diff: Two components" $+        keysDiff "key.nest.foo" "key.nest.bar" `shouldBe` Diff "key.nest" "foo" "bar"+    it "Diff: Two diff components" $+        keysDiff "key.foo.nest" "key.bar.nest" `shouldBe` Diff "key" "foo.nest" "bar.nest"+    it "Diff: Different diff length" $+        keysDiff "key.foo" "key.bar.nest" `shouldBe` Diff "key" "foo" "bar.nest"
+ test/Test/Toml/Type/PrefixTree.hs view
@@ -0,0 +1,95 @@+{-# LANGUAGE PatternSynonyms #-}++module Test.Toml.Type.PrefixTree+    ( prefixTreeSpec+    ) where++import Hedgehog (forAll, (===))+import Test.Hspec (Arg, Expectation, Spec, SpecWith, describe, it, parallel, shouldBe)+import Test.Hspec.Hedgehog (hedgehog)++import Test.Toml.Gen (genKey, genPrefixMap, genVal)+import Test.Toml.Property (assocSemigroup, leftIdentityMonoid, rightIdentityMonoid)+import Toml.Type.Key (pattern (:||))++import qualified Toml.Type.PrefixTree as Prefix+++prefixTreeSpec :: Spec+prefixTreeSpec = describe "PrefixTree unit and property tests" $ do+    prefixTreeUnitSpec+    prefixTreePropertySpec++prefixTreeUnitSpec :: Spec+prefixTreeUnitSpec = describe "Unit tests for basic cases" $ do+    -- some test keys+    let a  = "a" :|| []+    let b  = "b" :|| []+    let c  = "c" :|| []+    let ab = "a" :|| ["b"]++    it "Lookup on empty map returns Nothing" $+        Prefix.lookup @Bool a mempty `shouldBe` Nothing+    it "Lookup in single map returns this element" $ do+        let t = Prefix.single a True+        Prefix.lookup a t `shouldBe` Just True+        Prefix.lookup b t `shouldBe` Nothing+    it "Lookup after insert returns this element" $ do+        let t = Prefix.insert a True mempty+        Prefix.lookup a t `shouldBe` Just True+        Prefix.lookup b t `shouldBe` Nothing+    it "Lookup after multiple non-overlapping inserts" $ do+        let t = Prefix.insert a True $ Prefix.insert b False mempty+        Prefix.lookup a t `shouldBe` Just True+        Prefix.lookup b t `shouldBe` Just False+        Prefix.lookup c t `shouldBe` Nothing+    it "Prefix lookup" $ do+        let t = Prefix.insert ab True mempty+        Prefix.lookup a  t `shouldBe` Nothing+        Prefix.lookup ab t `shouldBe` Just True+    it "Composite key lookup" $ do+        let t = Prefix.insert a True $ Prefix.insert ab False mempty+        Prefix.lookup a  t `shouldBe` Just True+        Prefix.lookup ab t `shouldBe` Just False++prefixTreePropertySpec :: Spec+prefixTreePropertySpec = parallel $ describe "Laws and behaviour property tests" $ do+    insertLookupSpec+    insertInsertSpec+    assocSemigroup      genPrefixMap+    leftIdentityMonoid  genPrefixMap+    rightIdentityMonoid genPrefixMap++insertLookupSpec :: SpecWith (Arg Expectation)+insertLookupSpec = it "lookup k (insert k v m) ≡ Just v" $ hedgehog $ do+    t   <- forAll genPrefixMap+    key <- forAll genKey+    val <- forAll genVal++    Prefix.lookup key (Prefix.insert key val t) === Just val++    -- DEBUG: ensures that trees of depth at least 5 are generated+    -- assert $ depth prefMap < 5++insertInsertSpec :: SpecWith (Arg Expectation)+insertInsertSpec = it "insert x a . insert x b ≡ insert x a" $ hedgehog $ do+    t <- forAll genPrefixMap+    x <- forAll genKey+    a <- forAll genVal+    b <- forAll genVal++    Prefix.lookup x (Prefix.insert x a $ Prefix.insert x b t) === Just a++----------------------------------------------------------------------------+-- DEBUG+----------------------------------------------------------------------------++-- useful functions to test generators+-- uncomment when you need them++-- depth :: PrefixMap a -> Int+-- depth = HashMap.foldl' (\acc t -> max acc (depthT t)) 0+--+-- depthT :: PrefixTree a -> Int+-- depthT (Leaf _ _)           = 1+-- depthT (Branch _ _ prefMap) = 1 + depth prefMap
+ test/Test/Toml/Type/Printer.hs view
@@ -0,0 +1,68 @@+-- | This module contains golden tests for @Toml.Printer@.++module Test.Toml.Type.Printer+       ( printerSpec+       ) where++import Data.List.NonEmpty (NonEmpty ((:|)))+import Data.Ord (comparing)+import Test.Hspec (Arg, Expectation, Spec, SpecWith, describe, it)+import Test.Hspec.Golden (defaultGolden)++import Toml.Type.Edsl (empty, mkToml, table, tableArray, (=:))+import Toml.Type.Key (Key (..), (<|))+import Toml.Type.Printer (PrintOptions (..), defaultOptions, prettyOptions)+import Toml.Type.TOML (TOML)+import Toml.Type.Value (Value (..))++import qualified Data.Text as T+++printerSpec :: Spec+printerSpec = describe "Toml.Type.Printer: Golden tests for pretty-printing" $ do+    test "pretty_default" defaultOptions+    test "pretty_sorted_only" noFormatting { printOptionsSorting = Just compare }+    test "pretty_indented_only" noFormatting { printOptionsIndent = 4 }+    test "pretty_unformatted" noFormatting+    test "pretty_custom_sorted" noFormatting { printOptionsSorting = Just spamEgg }+  where+    test :: String -> PrintOptions -> SpecWith (Arg Expectation)+    test name options = it ("Golden " ++ name) $+        defaultGolden+            ("test/golden/" ++ name ++ ".golden")+            (T.unpack $ prettyOptions options example)++example :: TOML+example = mkToml $ do+    "b" =: "bb"+    "a" =: "a"+    "d" =: "ddd"+    "c" =: "cccc"+    table ("qux" <| "doo") $ do+      "spam" =: "!"+      "egg" =: "?"+    table "foo" empty+    table "doo" empty+    table "baz" empty+    tableArray "deepest" $+      "ping" =: "pong"+      :| [empty]+    tableArray "deeper" $+      "green" =: Bool True+      :| [table "blue" ("red" =: Integer 255)]++noFormatting :: PrintOptions+noFormatting = PrintOptions+    { printOptionsSorting = Nothing+    , printOptionsIndent  = 0+    }++-- | Decorate keys as tuples so spam comes before egg+spamEggDecorate :: Key -> (Int, Key)+spamEggDecorate k+    | k == "spam" = (0, "spam")+    | k == "egg" = (1, "egg")+    | otherwise = (2, k)++spamEgg :: Key -> Key -> Ordering+spamEgg = comparing spamEggDecorate
+ test/Test/Toml/Type/TOML.hs view
@@ -0,0 +1,18 @@+{- | Property tests for @TOML@ data type.+-}++module Test.Toml.Type.TOML+    ( tomlSpec+    ) where++import Test.Hspec (Spec, describe, parallel)++import Test.Toml.Gen (genToml)+import Test.Toml.Property (assocSemigroup, leftIdentityMonoid, rightIdentityMonoid)+++tomlSpec :: Spec+tomlSpec = parallel $ describe "TOML laws" $ do+    assocSemigroup genToml+    rightIdentityMonoid genToml+    leftIdentityMonoid genToml
tomland.cabal view
@@ -1,6 +1,6 @@ cabal-version:       2.4 name:                tomland-version:             1.2.1.0+version:             1.3.0.0 synopsis:            Bidirectional TOML serialization description:     Implementation of bidirectional TOML serialization. Simple codecs look like this:@@ -27,37 +27,41 @@ license-file:        LICENSE author:              Dmitrii Kovanikov, Veronika Romashkina maintainer:          Kowainik <xrom.xkov@gmail.com>-copyright:           2018-2019 Kowainik+copyright:           2018-2020 Kowainik category:            TOML, Text, Configuration build-type:          Simple extra-doc-files:     README.md                      CHANGELOG.md extra-source-files:  test/golden/*.golden                      test/examples/*.toml-tested-with:         GHC == 8.2.2-                     GHC == 8.4.4+tested-with:         GHC == 8.4.4                      GHC == 8.6.5-                     GHC == 8.8.1+                     GHC == 8.8.3+                     GHC == 8.10.1  source-repository head   type:                git   location:            https://github.com/kowainik/tomland.git  common common-options-  build-depends:       base >= 4.10 && < 4.14+  build-depends:       base >= 4.11 && < 4.15    ghc-options:         -Wall-                       -Wincomplete-uni-patterns-                       -Wincomplete-record-updates                        -Wcompat                        -Widentities+                       -Wincomplete-uni-patterns+                       -Wincomplete-record-updates                        -Wredundant-constraints-                       -Wpartial-fields                        -fhide-source-paths                        -freverse-errors+  if impl(ghc >= 8.4)+    ghc-options:       -Wmissing-export-lists+                       -Wpartial-fields   if impl(ghc >= 8.8.1)     ghc-options:       -Wmissing-deriving-strategies                        -Werror=missing-deriving-strategies+  if impl(ghc >= 8.10)+    ghc-options:       -Wunused-packages    default-language:    Haskell2010   default-extensions:  DeriveGeneric@@ -75,13 +79,25 @@   hs-source-dirs:      src    exposed-modules:     Toml-                         Toml.Bi-                           Toml.Bi.Code-                           Toml.Bi.Combinators-                           Toml.Bi.Monad-                           Toml.Bi.Map-                         Toml.Edsl-                         Toml.Generic+                         Toml.Codec+                           Toml.Codec.BiMap+                             Toml.Codec.BiMap.Conversion+                           Toml.Codec.Code+                           Toml.Codec.Combinator+                             Toml.Codec.Combinator.Common+                             Toml.Codec.Combinator.Custom+                             Toml.Codec.Combinator.List+                             Toml.Codec.Combinator.Map+                             Toml.Codec.Combinator.Monoid+                             Toml.Codec.Combinator.Primitive+                             Toml.Codec.Combinator.Set+                             Toml.Codec.Combinator.Table+                             Toml.Codec.Combinator.Time+                             Toml.Codec.Combinator.Tuple+                           Toml.Codec.Di+                           Toml.Codec.Error+                           Toml.Codec.Generic+                           Toml.Codec.Types                          Toml.Parser                            Toml.Parser.Core                            Toml.Parser.Item@@ -89,10 +105,12 @@                            Toml.Parser.String                            Toml.Parser.Validate                            Toml.Parser.Value-                         Toml.PrefixTree-                         Toml.Printer                          Toml.Type                            Toml.Type.AnyValue+                           Toml.Type.Edsl+                           Toml.Type.Key+                           Toml.Type.PrefixTree+                           Toml.Type.Printer                            Toml.Type.TOML                            Toml.Type.UValue                            Toml.Type.Value@@ -101,13 +119,14 @@                      , containers >= 0.5.7 && < 0.7                      , deepseq ^>= 1.4                      , hashable >= 1.2 && < 1.4-                     , megaparsec ^>= 7.0.5+                     , megaparsec >= 7.0.5 && < 8.1                      , mtl ^>= 2.2                      , parser-combinators >= 1.1.0 && < 1.3                      , text ^>= 1.2-                     , time >= 1.8 && < 1.10+                     , time >= 1.8 && < 1.11                      , transformers ^>= 0.5                      , unordered-containers ^>= 0.2.7+                     , validation-selective ^>= 0.1.0.0  executable readme   import:              common-options@@ -115,16 +134,21 @@   if os(windows)     buildable: False   main-is:             README.lhs-  build-depends:       text-                     , tomland+  build-depends:       tomland+                     , text+                     , time    build-tool-depends:  markdown-unlit:markdown-unlit   ghc-options:         -pgmL markdown-unlit  executable play-tomland   import:              common-options+  -- We are using DerivingVia that works only with > 8.6+  if impl(ghc < 8.6)+    buildable: False   main-is:             Main.hs   build-depends:       tomland+                     , bytestring                      , containers                      , hashable                      , text@@ -140,38 +164,59 @@   hs-source-dirs:      test   main-is:             Spec.hs -  other-modules:       Test.Toml.BiMap.Property-                       Test.Toml.BiCode.Property+  other-modules:       Test.Toml.Codec+                         Test.Toml.Codec.BiMap+                           Test.Toml.Codec.BiMap.Conversion+                         Test.Toml.Codec.Code+                         Test.Toml.Codec.Combinator+                           Test.Toml.Codec.Combinator.Common+                           Test.Toml.Codec.Combinator.Custom+                           Test.Toml.Codec.Combinator.List+                           Test.Toml.Codec.Combinator.Map+                           Test.Toml.Codec.Combinator.Monoid+                           Test.Toml.Codec.Combinator.Primitive+                           Test.Toml.Codec.Combinator.Set+                           Test.Toml.Codec.Combinator.Table+                           Test.Toml.Codec.Combinator.Time+                           Test.Toml.Codec.Combinator.Tuple+                         Test.Toml.Codec.Di+                         Test.Toml.Codec.Generic+                         Test.Toml.Codec.SmallType++                       Test.Toml.Parser+                         Test.Toml.Parser.Examples+                         Test.Toml.Parser.Property+                         Test.Toml.Parser.Validate+                         -- unit tests for parsing different parts of TOML ast+                         Test.Toml.Parser.Array+                         Test.Toml.Parser.Bool+                         Test.Toml.Parser.Common+                         Test.Toml.Parser.Date+                         Test.Toml.Parser.Double+                         Test.Toml.Parser.Integer+                         Test.Toml.Parser.Key+                         Test.Toml.Parser.Text+                         Test.Toml.Parser.Toml++                       Test.Toml.Type+                         Test.Toml.Type.Key+                         Test.Toml.Type.PrefixTree+                         Test.Toml.Type.Printer+                         Test.Toml.Type.TOML++                       -- helpers                        Test.Toml.Gen                        Test.Toml.Property-                       Test.Toml.Parsing.Property-                       Test.Toml.Parsing.Unit-                       Test.Toml.Parsing.Unit.Array-                       Test.Toml.Parsing.Unit.Bool-                       Test.Toml.Parsing.Unit.Common-                       Test.Toml.Parsing.Unit.Date-                       Test.Toml.Parsing.Unit.Double-                       Test.Toml.Parsing.Unit.Integer-                       Test.Toml.Parsing.Unit.Key-                       Test.Toml.Parsing.Unit.Text-                       Test.Toml.Parsing.Unit.Toml-                       Test.Toml.Parsing.Examples-                       Test.Toml.PrefixTree.Property-                       Test.Toml.PrefixTree.Unit-                       Test.Toml.Printer.Golden-                       Test.Toml.TOML.Property -  build-tool-depends:  tasty-discover:tasty-discover ^>= 4.2.1   build-depends:       bytestring ^>= 0.10                      , containers >= 0.5.7 && < 0.7                      , hashable                      , hedgehog ^>= 1.0.1-                     , hspec-megaparsec ^>= 2.0.0+                     , hspec ^>= 2.7.1+                     , hspec-hedgehog ^>= 0.0.1+                     , hspec-golden ^>= 0.1.0+                     , hspec-megaparsec >= 2.0.0 && < 2.2.0                      , megaparsec-                     , tasty ^>= 1.2-                     , tasty-hedgehog ^>= 1.0.0.0-                     , tasty-hspec ^>= 1.1.5.1-                     , tasty-silver ^>= 3.1.11                      , directory ^>= 1.3                      , text                      , time