{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE CPP #-}
module Data.Yaml.Builder
( YamlBuilder (..)
, ToYaml (..)
, mapping
, namedMapping
, maybeNamedMapping
, array
, namedArray
, maybeNamedArray
, string
, namedString
, maybeNamedString
, bool
, namedBool
, maybeNamedBool
, null
, namedNull
, maybeNamedNull
, scientific
, namedScientific
, maybeNamedScientific
, alias
, number
, toByteString
, toByteStringWith
, writeYamlFile
, writeYamlFileWith
, (.=)
, FormatOptions
, setWidth
) where
import Prelude hiding (null)
import Control.Arrow (second)
#if MIN_VERSION_aeson(1,0,0)
import Data.Aeson.Text (encodeToTextBuilder)
#else
import Data.Aeson.Encode (encodeToTextBuilder)
#endif
import Data.Aeson.Types (Value(..))
import Data.ByteString (ByteString)
import qualified Data.ByteString.Char8 as S8
import Data.Conduit
import qualified Data.HashSet as HashSet
import Data.Scientific (Scientific)
import Data.Text (Text, unpack)
import Data.Text.Encoding (encodeUtf8)
import qualified Data.Text.Encoding as TE
import qualified Data.Text.Lazy as TL
import Data.Text.Lazy.Builder (toLazyText)
import System.IO.Unsafe (unsafePerformIO)
import Data.Yaml.Internal
import Text.Libyaml
(.=) :: ToYaml a => Text -> a -> (Text, YamlBuilder)
k .= v = (k, toYaml v)
newtype YamlBuilder = YamlBuilder { unYamlBuilder :: [Event] -> [Event] }
class ToYaml a where
toYaml :: a -> YamlBuilder
instance ToYaml YamlBuilder where
toYaml = id
instance ToYaml a => ToYaml [(Text, a)] where
toYaml = mapping . map (second toYaml)
instance ToYaml a => ToYaml [a] where
toYaml = array . map toYaml
instance ToYaml Text where
toYaml = string
instance ToYaml Int where
toYaml i = YamlBuilder (EventScalar (S8.pack $ show i) IntTag PlainNoTag Nothing:)
maybeNamedMapping :: Maybe Text -> [(Text, YamlBuilder)] -> YamlBuilder
maybeNamedMapping anchor pairs = YamlBuilder $ \rest ->
EventMappingStart NoTag AnyMapping (unpack <$> anchor) : foldr addPair (EventMappingEnd : rest) pairs
where
addPair (key, YamlBuilder value) after
= EventScalar (encodeUtf8 key) StrTag PlainNoTag Nothing
: value after
mapping :: [(Text, YamlBuilder)] -> YamlBuilder
mapping = maybeNamedMapping Nothing
namedMapping :: Text -> [(Text, YamlBuilder)] -> YamlBuilder
namedMapping name = maybeNamedMapping $ Just name
maybeNamedArray :: Maybe Text -> [YamlBuilder] -> YamlBuilder
maybeNamedArray anchor bs =
YamlBuilder $ (EventSequenceStart NoTag AnySequence (unpack <$> anchor):) . flip (foldr go) bs . (EventSequenceEnd:)
where
go (YamlBuilder b) = b
array :: [YamlBuilder] -> YamlBuilder
array = maybeNamedArray Nothing
namedArray :: Text -> [YamlBuilder] -> YamlBuilder
namedArray name = maybeNamedArray $ Just name
maybeNamedString :: Maybe Text -> Text -> YamlBuilder
maybeNamedString anchor "" = YamlBuilder (EventScalar "" NoTag SingleQuoted (unpack <$> anchor) :)
maybeNamedString anchor s =
YamlBuilder (event :)
where
event
| s `HashSet.member` specialStrings || isNumeric s = EventScalar (encodeUtf8 s) NoTag SingleQuoted $ unpack <$> anchor
| otherwise = EventScalar (encodeUtf8 s) StrTag PlainNoTag $ unpack <$> anchor
string :: Text -> YamlBuilder
string = maybeNamedString Nothing
namedString :: Text -> Text -> YamlBuilder
namedString name = maybeNamedString $ Just name
maybeNamedScientific :: Maybe Text -> Scientific -> YamlBuilder
maybeNamedScientific anchor n = YamlBuilder (EventScalar (TE.encodeUtf8 $ TL.toStrict $ toLazyText $ encodeToTextBuilder (Number n)) IntTag PlainNoTag (unpack <$> anchor) :)
scientific :: Scientific -> YamlBuilder
scientific = maybeNamedScientific Nothing
namedScientific :: Text -> Scientific -> YamlBuilder
namedScientific name = maybeNamedScientific $ Just name
{-# DEPRECATED number "Use scientific" #-}
number :: Scientific -> YamlBuilder
number = scientific
maybeNamedBool :: Maybe Text -> Bool -> YamlBuilder
maybeNamedBool anchor True = YamlBuilder (EventScalar "true" BoolTag PlainNoTag (unpack <$> anchor) :)
maybeNamedBool anchor False = YamlBuilder (EventScalar "false" BoolTag PlainNoTag (unpack <$> anchor) :)
bool :: Bool -> YamlBuilder
bool = maybeNamedBool Nothing
namedBool :: Text -> Bool -> YamlBuilder
namedBool name = maybeNamedBool $ Just name
maybeNamedNull :: Maybe Text -> YamlBuilder
maybeNamedNull anchor = YamlBuilder (EventScalar "null" NullTag PlainNoTag (unpack <$> anchor) :)
null :: YamlBuilder
null = maybeNamedNull Nothing
namedNull :: Text -> YamlBuilder
namedNull name = maybeNamedNull $ Just name
alias :: Text -> YamlBuilder
alias anchor = YamlBuilder (EventAlias (unpack anchor) :)
toEvents :: YamlBuilder -> [Event]
toEvents (YamlBuilder front) =
EventStreamStart : EventDocumentStart : front [EventDocumentEnd, EventStreamEnd]
toSource :: (Monad m, ToYaml a) => a -> ConduitM i Event m ()
toSource = mapM_ yield . toEvents . toYaml
toByteString :: ToYaml a => a -> ByteString
toByteString = toByteStringWith defaultFormatOptions
toByteStringWith :: ToYaml a => FormatOptions -> a -> ByteString
toByteStringWith opts yb = unsafePerformIO $ runConduitRes $ toSource yb .| encodeWith opts
writeYamlFile :: ToYaml a => FilePath -> a -> IO ()
writeYamlFile = writeYamlFileWith defaultFormatOptions
writeYamlFileWith :: ToYaml a => FormatOptions -> FilePath -> a -> IO ()
writeYamlFileWith opts fp yb = runConduitRes $ toSource yb .| encodeFileWith opts fp