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 +100/−0
- README.lhs +104/−52
- README.md +104/−52
- examples/Main.hs +93/−9
- src/Toml.hs +39/−15
- src/Toml/Bi.hs +0/−27
- src/Toml/Bi/Code.hs +0/−144
- src/Toml/Bi/Combinators.hs +0/−515
- src/Toml/Bi/Map.hs +0/−577
- src/Toml/Bi/Monad.hs +0/−298
- src/Toml/Codec.hs +122/−0
- src/Toml/Codec/BiMap.hs +237/−0
- src/Toml/Codec/BiMap/Conversion.hs +619/−0
- src/Toml/Codec/Code.hs +122/−0
- src/Toml/Codec/Combinator.hs +152/−0
- src/Toml/Codec/Combinator/Common.hs +77/−0
- src/Toml/Codec/Combinator/Custom.hs +237/−0
- src/Toml/Codec/Combinator/List.hs +222/−0
- src/Toml/Codec/Combinator/Map.hs +399/−0
- src/Toml/Codec/Combinator/Monoid.hs +114/−0
- src/Toml/Codec/Combinator/Primitive.hs +205/−0
- src/Toml/Codec/Combinator/Set.hs +224/−0
- src/Toml/Codec/Combinator/Table.hs +92/−0
- src/Toml/Codec/Combinator/Time.hs +71/−0
- src/Toml/Codec/Combinator/Tuple.hs +100/−0
- src/Toml/Codec/Di.hs +210/−0
- src/Toml/Codec/Error.hs +85/−0
- src/Toml/Codec/Generic.hs +776/−0
- src/Toml/Codec/Types.hs +220/−0
- src/Toml/Edsl.hs +0/−86
- src/Toml/Generic.hs +0/−419
- src/Toml/Parser.hs +31/−14
- src/Toml/Parser/Core.hs +1/−1
- src/Toml/Parser/Item.hs +20/−17
- src/Toml/Parser/Key.hs +2/−3
- src/Toml/Parser/String.hs +1/−2
- src/Toml/Parser/Validate.hs +6/−5
- src/Toml/Parser/Value.hs +1/−2
- src/Toml/PrefixTree.hs +0/−231
- src/Toml/Printer.hs +0/−208
- src/Toml/Type.hs +60/−6
- src/Toml/Type/AnyValue.hs +7/−2
- src/Toml/Type/Edsl.hs +107/−0
- src/Toml/Type/Key.hs +138/−0
- src/Toml/Type/PrefixTree.hs +167/−0
- src/Toml/Type/Printer.hs +229/−0
- src/Toml/Type/TOML.hs +8/−4
- src/Toml/Type/UValue.hs +13/−4
- src/Toml/Type/Value.hs +33/−8
- test/Spec.hs +20/−1
- test/Test/Toml/BiCode/Property.hs +0/−206
- test/Test/Toml/BiMap/Property.hs +0/−67
- test/Test/Toml/Codec.hs +22/−0
- test/Test/Toml/Codec/BiMap.hs +67/−0
- test/Test/Toml/Codec/BiMap/Conversion.hs +71/−0
- test/Test/Toml/Codec/Code.hs +110/−0
- test/Test/Toml/Codec/Combinator.hs +28/−0
- test/Test/Toml/Codec/Combinator/Common.hs +61/−0
- test/Test/Toml/Codec/Combinator/Custom.hs +27/−0
- test/Test/Toml/Codec/Combinator/List.hs +28/−0
- test/Test/Toml/Codec/Combinator/Map.hs +23/−0
- test/Test/Toml/Codec/Combinator/Monoid.hs +24/−0
- test/Test/Toml/Codec/Combinator/Primitive.hs +30/−0
- test/Test/Toml/Codec/Combinator/Set.hs +30/−0
- test/Test/Toml/Codec/Combinator/Table.hs +16/−0
- test/Test/Toml/Codec/Combinator/Time.hs +18/−0
- test/Test/Toml/Codec/Combinator/Tuple.hs +24/−0
- test/Test/Toml/Codec/Di.hs +57/−0
- test/Test/Toml/Codec/Generic.hs +33/−0
- test/Test/Toml/Codec/SmallType.hs +58/−0
- test/Test/Toml/Gen.hs +122/−90
- test/Test/Toml/Parser.hs +34/−0
- test/Test/Toml/Parser/Array.hs +74/−0
- test/Test/Toml/Parser/Bool.hs +22/−0
- test/Test/Toml/Parser/Common.hs +135/−0
- test/Test/Toml/Parser/Date.hs +63/−0
- test/Test/Toml/Parser/Double.hs +45/−0
- test/Test/Toml/Parser/Examples.hs +25/−0
- test/Test/Toml/Parser/Integer.hs +83/−0
- test/Test/Toml/Parser/Key.hs +48/−0
- test/Test/Toml/Parser/Property.hs +21/−0
- test/Test/Toml/Parser/Text.hs +96/−0
- test/Test/Toml/Parser/Toml.hs +163/−0
- test/Test/Toml/Parser/Validate.hs +79/−0
- test/Test/Toml/Parsing/Examples.hs +0/−21
- test/Test/Toml/Parsing/Property.hs +0/−13
- test/Test/Toml/Parsing/Unit.hs +0/−23
- test/Test/Toml/Parsing/Unit/Array.hs +0/−45
- test/Test/Toml/Parsing/Unit/Bool.hs +0/−21
- test/Test/Toml/Parsing/Unit/Common.hs +0/−140
- test/Test/Toml/Parsing/Unit/Date.hs +0/−61
- test/Test/Toml/Parsing/Unit/Double.hs +0/−45
- test/Test/Toml/Parsing/Unit/Integer.hs +0/−82
- test/Test/Toml/Parsing/Unit/Key.hs +0/−35
- test/Test/Toml/Parsing/Unit/Text.hs +0/−95
- test/Test/Toml/Parsing/Unit/Toml.hs +0/−162
- test/Test/Toml/PrefixTree/Property.hs +0/−73
- test/Test/Toml/PrefixTree/Unit.hs +0/−43
- test/Test/Toml/Printer/Golden.hs +0/−64
- test/Test/Toml/Property.hs +30/−14
- test/Test/Toml/TOML/Property.hs +0/−15
- test/Test/Toml/Type.hs +18/−0
- test/Test/Toml/Type/Key.hs +47/−0
- test/Test/Toml/Type/PrefixTree.hs +95/−0
- test/Test/Toml/Type/Printer.hs +68/−0
- test/Test/Toml/Type/TOML.hs +18/−0
- tomland.cabal +91/−46
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 + [](https://github.com/kowainik/tomland/actions)-[](https://travis-ci.org/kowainik/tomland) [](https://ci.appveyor.com/project/kowainik/tomland) [](https://hackage.haskell.org/package/tomland) [](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 + [](https://github.com/kowainik/tomland/actions)-[](https://travis-ci.org/kowainik/tomland) [](https://ci.appveyor.com/project/kowainik/tomland) [](https://hackage.haskell.org/package/tomland) [](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' also implements 'Cat.Category' typeclass. And this instance can be described-clearly by this illustration:----}-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' also implements 'Cat.Category' typeclass. And this instance can be described+clearly by this illustration:++++@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