{-# 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.HashSet (HashSet) import Data.Hashable (Hashable) 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 (TomlCodec, TomlParseError (..), pretty, (.=), ()) import Toml.Codec.Generic (ByteStringAsBytes (..), HasCodec (..), TomlTableStrip (..), 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 insideCodec = Toml.dimap unInside TestInside $ Toml.text "inside" data User = User { userName :: !Text , userAge :: !Int } deriving stock (Eq, Ord, Generic) deriving anyclass (Hashable) userCodec :: TomlCodec User userCodec = User <$> Toml.text "name" .= userName <*> Toml.int "age" .= userAge newtype N = N { unN :: Text } data ColorScheme = Light | Dark | HighContrast deriving stock (Show, Read, Enum, Bounded) data UserStatus = Registered Text Text | Anonymous Text matchRegistered :: UserStatus -> Maybe (Text, Text) matchRegistered (Registered username password) = Just (username, password) matchRegistered _ = Nothing matchAnonymous :: UserStatus -> Maybe Text matchAnonymous (Anonymous username) = Just username matchAnonymous _ = Nothing userPassCodec :: TomlCodec (Text, Text) userPassCodec = Toml.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 newtype Inner = Inner { val :: Text } deriving stock (Show) innerCodec :: TomlCodec Inner innerCodec = Inner <$> Toml.text "val" .= val newtype MapWithList = MapWithList { mapList :: Map Text [Inner] } deriving stock (Show) mapWithListCodec :: TomlCodec MapWithList mapWithListCodec = MapWithList <$> Toml.tableMap Toml._KeyText (Toml.list innerCodec) "mapList" .= mapList 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) , tableList :: !MapWithList , testHardcoded :: !Text } testT :: TomlCodec Test testT = Test <$> Toml.bool "testB" .= testB <*> Toml.int "testI" .= testI <*> Toml.double "testF" .= testF <*> Toml.text "testS" .= testS <*> Toml.arrayOf Toml._Text "testA" .= testA <*> Toml.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.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 <*> mapWithListCodec .= tableList <*> Toml.hardcoded "abc" Toml._Text "testHardcoded" .= testHardcoded 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" <|> Toml.match (Toml._Right >>> Toml._String) "either.Right" -- same key for sum type; -- doesn't work if you have something like `Either String String`, -- you should distinguish these cases by different keys like in `eitherT1` example eitherT2 :: TomlCodec (Either String Double) eitherT2 = ( Toml.match (Toml._Left >>> Toml._String) Toml.match (Toml._Right >>> Toml._Double) ) "either" data GenericPerson = GenericPerson { genericPersonName :: !Text , genericPersonAddress :: !Address } deriving stock (Generic) data Address = Address { addressStreet :: !Text , addressHouse :: !Int } deriving stock (Generic) deriving HasCodec via (TomlTableStrip 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 ===" TIO.putStrLn $ pretty myToml TIO.putStrLn "=== Trying to print invalid TOML ===" content <- TIO.readFile "examples/invalid.toml" TIO.putStrLn $ case Toml.parse content of Left (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 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 "a" =: Bool True "list" =: Array ["one", "two"] "time" =: Array [Day (fromGregorian 2018 3 29)] table "table.name.1" $ do "aInner" =: 1 "listInner" =: Array [Bool True, Bool False] table "1" $ do "aInner11" =: 11 "listInner11" =: Array [0, 1] table "2" $ "Inner12" =: "12" table "table.name.2" $ "Inner2" =: 42