@@ -28,7 +28,9 @@ module Data.Yaml
29 ( -- * Encoding
29 ( -- * Encoding
31 , encodeFile
32 , encodeFile
32 -- * Decoding
34 -- * Decoding
33 , decodeEither'
35 , decodeEither'
34 , decodeFileEither
36 , decodeFileEither
@@ -66,6 +68,15 @@ module Data.Yaml
66 -- * Classes
68 -- * Classes
67 , ToJSON (..)
69 , ToJSON (..)
68 , FromJSON (..)
70 , FromJSON (..)
74+ , defaultEncodeOptions
78+ , defaultFormatOptions
69 -- * Deprecated
80 -- * Deprecated
71 , decodeFile
82 , decodeFile
@@ -100,65 +111,117 @@ import qualified Data.Text.Lazy as TL
100import Data.Text.Lazy.Builder (toLazyText)
111import Data.Text.Lazy.Builder (toLazyText)
101import qualified Data.Vector as V
112import qualified Data.Vector as V
102import System.IO.Unsafe (unsafePerformIO)
113import System.IO.Unsafe (unsafePerformIO)
114+import Data.Text (Text)
104import Data.Yaml.Internal
116import Data.Yaml.Internal
105−import Text.Libyaml hiding (encode, decode, encodeFile, decodeFile)
117+import Text.Libyaml hiding (encode, decode, encodeFile, decodeFile, encodeWith, encodeFileWith)
106import qualified Text.Libyaml as Y
118import qualified Text.Libyaml as Y
122+data EncodeOptions = EncodeOptions
123+ { encodeOptionsStringStyle :: Text -> ( Tag, Style )
124+ , encodeOptionsFormat :: FormatOptions
127+-- | Set the string style in the encoded YAML. This is a function that decides
128+-- for each string the type of YAML string to output.
130+-- __WARNING__: You must ensure that special strings (like @"yes"@\/@"no"@\/@"null"@\/@"1234"@) are not encoded with the 'Plain' style, because
131+-- then they will be decoded as boolean, null or numeric values. You can use 'isSpecialString' to detect them.
133+-- By default, strings are encoded with the `Plain` style, except special strings, which are encoded with `SingleQuoted`.
136+setStringStyle :: (Text -> ( Tag, Style )) -> EncodeOptions -> EncodeOptions
137+setStringStyle s opts = opts { encodeOptionsStringStyle = s }
139+-- | Set the encoding formatting for the encoded YAML. By default, this is `defaultFormatOptions`.
142+setFormat :: FormatOptions -> EncodeOptions -> EncodeOptions
143+setFormat f opts = opts { encodeOptionsFormat = f }
145+-- | Determine whether a string must be quoted in YAML and can't appear as plain text.
146+-- Useful if you want to use 'setStringStyle'.
149+isSpecialString :: Text -> Bool
150+isSpecialString s = s `HashSet.member` specialStrings || isNumeric s
154+defaultEncodeOptions :: EncodeOptions
155+defaultEncodeOptions = EncodeOptions
156+ { encodeOptionsStringStyle = \s ->
157+ -- Empty strings need special handling to ensure they get quoted. This avoids:
158+ -- https://github.com/snoyberg/yaml/issues/24
159+ if isSpecialString s
160+ then ( NoTag, SingleQuoted )
161+ else ( StrTag, PlainNoTag )
162+ , encodeOptionsFormat = defaultFormatOptions
108-- | Encode a value into its YAML representation.
165-- | Encode a value into its YAML representation.
109encode :: ToJSON a => a -> ByteString
166encode :: ToJSON a => a -> ByteString
167+encode = encodeWith defaultEncodeOptions
169+-- | Encode a value into its YAML representation with custom styling.
172+encodeWith :: ToJSON a => EncodeOptions -> a -> ByteString
110−encode obj = unsafePerformIO $ runConduitRes
173+encodeWith opts obj = unsafePerformIO $ runConduitRes
111− $ CL.sourceList (objToEvents $ toJSON obj)
174+ $ CL.sourceList (objToEvents opts $ toJSON obj)
112− .| Y.encode
175+ .| Y.encodeWith (encodeOptionsFormat opts)
114-- | Encode a value into its YAML representation and save to the given file.
177-- | Encode a value into its YAML representation and save to the given file.
115encodeFile :: ToJSON a => FilePath -> a -> IO ()
178encodeFile :: ToJSON a => FilePath -> a -> IO ()
116−encodeFile fp obj = runConduitRes
179+encodeFile = encodeFileWith defaultEncodeOptions
117− $ CL.sourceList (objToEvents $ toJSON obj)
120−objToEvents :: Value -> [Y.Event]
181+-- | Encode a value into its YAML representation with custom styling and save to the given file.
184+encodeFileWith :: ToJSON a => EncodeOptions -> FilePath -> a -> IO ()
185+encodeFileWith opts fp obj = runConduitRes
186+ $ CL.sourceList (objToEvents opts $ toJSON obj)
187+ .| Y.encodeFileWith (encodeOptionsFormat opts) fp
189+objToEvents :: EncodeOptions -> Value -> [Y.Event]
121−objToEvents o = (:) EventStreamStart
190+objToEvents opts o = (:) EventStreamStart
122 . (:) EventDocumentStart
191 . (:) EventDocumentStart
123 $ objToEvents' o
192 $ objToEvents' o
124 [ EventDocumentEnd
193 [ EventDocumentEnd
125 , EventStreamEnd
194 , EventStreamEnd
197+ objToEvents' :: Value -> [Y.Event] -> [Y.Event]
198+ --objToEvents' (Scalar s) rest = scalarToEvent s : rest
199+ objToEvents' (Array list) rest =
200+ EventSequenceStart NoTag AnySequence Nothing
201+ : foldr objToEvents' (EventSequenceEnd : rest) (V.toList list)
202+ objToEvents' (Object pairs) rest =
203+ EventMappingStart NoTag AnyMapping Nothing
204+ : foldr pairToEvents (EventMappingEnd : rest) (M.toList pairs)
206+ objToEvents' (String "") rest = EventScalar "" NoTag SingleQuoted Nothing : rest
208+ objToEvents' (String s) rest = EventScalar (encodeUtf8 s) tag style Nothing : rest
210+ ( tag, style ) = encodeOptionsStringStyle opts s
211+ objToEvents' Null rest = EventScalar "null" NullTag PlainNoTag Nothing : rest
212+ objToEvents' (Bool True) rest = EventScalar "true" BoolTag PlainNoTag Nothing : rest
213+ objToEvents' (Bool False) rest = EventScalar "false" BoolTag PlainNoTag Nothing : rest
214+ -- Use aeson's implementation which gets rid of annoying decimal points
215+ objToEvents' n@Number{} rest = EventScalar (TE.encodeUtf8 $ TL.toStrict $ toLazyText $ encodeToTextBuilder n) IntTag PlainNoTag Nothing : rest
217+ pairToEvents :: Pair -> [Y.Event] -> [Y.Event]
218+ pairToEvents (k, v) = objToEvents' (String k) . objToEvents' v
129scalarToEvent :: YamlScalar -> Event
221scalarToEvent :: YamlScalar -> Event
130scalarToEvent (YamlScalar v t s) = EventScalar v t s Nothing
222scalarToEvent (YamlScalar v t s) = EventScalar v t s Nothing
133−objToEvents' :: Value -> [Y.Event] -> [Y.Event]
134−--objToEvents' (Scalar s) rest = scalarToEvent s : rest
135−objToEvents' (Array list) rest =
136− EventSequenceStart NoTag AnySequence Nothing
137− : foldr objToEvents' (EventSequenceEnd : rest) (V.toList list)
138−objToEvents' (Object pairs) rest =
139− EventMappingStart NoTag AnyMapping Nothing
140− : foldr pairToEvents (EventMappingEnd : rest) (M.toList pairs)
142−-- Empty strings need special handling to ensure they get quoted. This avoids:
143−-- https://github.com/snoyberg/yaml/issues/24
144−objToEvents' (String "") rest = EventScalar "" NoTag SingleQuoted Nothing : rest
146−objToEvents' (String s) rest =
150− -- Make sure that special strings are encoded as strings properly.
151− -- See: https://github.com/snoyberg/yaml/issues/31
152− | s `HashSet.member` specialStrings || isNumeric s = EventScalar (encodeUtf8 s) NoTag SingleQuoted Nothing
153− | otherwise = EventScalar (encodeUtf8 s) StrTag PlainNoTag Nothing
154−objToEvents' Null rest = EventScalar "null" NullTag PlainNoTag Nothing : rest
155−objToEvents' (Bool True) rest = EventScalar "true" BoolTag PlainNoTag Nothing : rest
156−objToEvents' (Bool False) rest = EventScalar "false" BoolTag PlainNoTag Nothing : rest
157−-- Use aeson's implementation which gets rid of annoying decimal points
158−objToEvents' n@Number{} rest = EventScalar (TE.encodeUtf8 $ TL.toStrict $ toLazyText $ encodeToTextBuilder n) IntTag PlainNoTag Nothing : rest
160−pairToEvents :: Pair -> [Y.Event] -> [Y.Event]
161−pairToEvents (k, v) = objToEvents' (String k) . objToEvents' v
163decode :: FromJSON a
226decode :: FromJSON a
164 => ByteString
227 => ByteString