on de41665 help sign in
186 lines 6.3 kB history raw
1{-# LANGUAGE CPP #-}
2{-# LANGUAGE ScopedTypeVariables #-}
3{-# LANGUAGE OverloadedStrings #-}
5-- | Provides a high-level interface for processing YAML files.
6--
7-- This module reuses most of the infrastructure from the @aeson@ package.
8-- This means that you can use all of the existing tools for JSON
9-- processing for processing YAML files. As a result, much of the
10-- documentation below mentions JSON; do not let that confuse you, it's
11-- intentional.
12--
13-- For the most part, YAML content translates directly into JSON, and
14-- therefore there is very little data loss. If you need to deal with YAML
15-- more directly (e.g., directly deal with aliases), you should use the
16-- "Text.Libyaml" module instead.
17--
18-- For documentation on the @aeson@ types, functions, classes, and
19-- operators, please see the @Data.Aeson@ module of the @aeson@ package.
20--
21-- Look in the examples directory of the source repository for some initial
22-- pointers on how to use this library.
24#if (defined (ghcjs_HOST_OS))
25module Data.Yaml {-# WARNING "GHCJS is not supported yet (will break at runtime once called)." #-}
26#else
27module Data.Yaml
28#endif
29 ( -- * Types
30 Value (..)
31 , Parser
32 , Object
33 , Array
34 , ParseException(..)
35 , prettyPrintParseException
36 , YamlException (..)
37 , YamlMark (..)
38 -- * Constructors and accessors
39 , object
40 , array
41 , (.=)
42 , (.:)
43 , (.:?)
44 , (.!=)
45 -- * Parsing
46 , parseMonad
47 , parseEither
48 , parseMaybe
49 -- * Classes
50 , ToJSON (..)
51 , FromJSON (..)
52 -- * Encoding/decoding
53 , encode
54 , encodeFile
55 , decode
56 , decodeFile
57 -- ** Better error information
58 , decodeEither
59 , decodeEither'
60 , decodeFileEither
61 -- ** More control over decoding
62 , decodeHelper
63 ) where
64#if !MIN_VERSION_base(4,8,0)
65import Control.Applicative((<$>))
66#endif
67import Control.Exception
68import Control.Monad.Trans.Resource (runResourceT)
69import Data.Aeson
70 ( Value (..), ToJSON (..), FromJSON (..), object
71 , (.=) , (.:) , (.:?) , (.!=)
72 , Object, Array
73 )
74import Data.Aeson.Encode (encodeToTextBuilder)
75import Data.Aeson.Types (Pair, parseMaybe, parseEither, Parser)
76import Data.ByteString (ByteString)
77import qualified Data.Conduit as C
78import qualified Data.Conduit.List as CL
79import qualified Data.HashMap.Strict as M
80import qualified Data.HashSet as HashSet
81import Data.Text.Encoding (encodeUtf8)
82import qualified Data.Text.Encoding as TE
83import qualified Data.Text.Lazy as TL
84import Data.Text.Lazy.Builder (toLazyText)
85import qualified Data.Vector as V
86import System.IO.Unsafe (unsafePerformIO)
88import Data.Yaml.Internal
89import Text.Libyaml hiding (encode, decode, encodeFile, decodeFile)
90import qualified Text.Libyaml as Y
92encode :: ToJSON a => a -> ByteString
93encode obj = unsafePerformIO $
94 runResourceT $ CL.sourceList (objToEvents $ toJSON obj)
95 C.$$ Y.encode
97encodeFile :: ToJSON a => FilePath -> a -> IO ()
98encodeFile fp obj = runResourceT
99 $ CL.sourceList (objToEvents $ toJSON obj)
100 C.$$ Y.encodeFile fp
102objToEvents :: Value -> [Y.Event]
103objToEvents o = (:) EventStreamStart
104 . (:) EventDocumentStart
105 $ objToEvents' o
106 [ EventDocumentEnd
107 , EventStreamEnd
108 ]
110{- FIXME
111scalarToEvent :: YamlScalar -> Event
112scalarToEvent (YamlScalar v t s) = EventScalar v t s Nothing
113-}
115objToEvents' :: Value -> [Y.Event] -> [Y.Event]
116--objToEvents' (Scalar s) rest = scalarToEvent s : rest
117objToEvents' (Array list) rest =
118 EventSequenceStart Nothing
119 : foldr objToEvents' (EventSequenceEnd : rest) (V.toList list)
120objToEvents' (Object pairs) rest =
121 EventMappingStart Nothing
122 : foldr pairToEvents (EventMappingEnd : rest) (M.toList pairs)
124-- Empty strings need special handling to ensure they get quoted. This avoids:
125-- https://github.com/snoyberg/yaml/issues/24
126objToEvents' (String "") rest = EventScalar "" NoTag SingleQuoted Nothing : rest
128objToEvents' (String s) rest =
129 event : rest
130 where
131 event
132 -- Make sure that special strings are encoded as strings properly.
133 -- See: https://github.com/snoyberg/yaml/issues/31
134 | s `HashSet.member` specialStrings || isNumeric s = EventScalar (encodeUtf8 s) NoTag SingleQuoted Nothing
135 | otherwise = EventScalar (encodeUtf8 s) StrTag PlainNoTag Nothing
136objToEvents' Null rest = EventScalar "null" NullTag PlainNoTag Nothing : rest
137objToEvents' (Bool True) rest = EventScalar "true" BoolTag PlainNoTag Nothing : rest
138objToEvents' (Bool False) rest = EventScalar "false" BoolTag PlainNoTag Nothing : rest
139-- Use aeson's implementation which gets rid of annoying decimal points
140objToEvents' n@Number{} rest = EventScalar (TE.encodeUtf8 $ TL.toStrict $ toLazyText $ encodeToTextBuilder n) IntTag PlainNoTag Nothing : rest
142pairToEvents :: Pair -> [Y.Event] -> [Y.Event]
143pairToEvents (k, v) rest =
144 EventScalar (encodeUtf8 k) StrTag PlainNoTag Nothing
145 : objToEvents' v rest
147decode :: FromJSON a
148 => ByteString
149 -> Maybe a
150decode bs = unsafePerformIO
151 $ either (const Nothing) id
152 <$> decodeHelper_ (Y.decode bs)
154decodeFile :: FromJSON a
155 => FilePath
156 -> IO (Maybe a)
157decodeFile fp = decodeHelper (Y.decodeFile fp) >>= either throwIO (return . either (const Nothing) id)
159-- | A version of 'decodeFile' which should not throw runtime exceptions.
160--
161-- Since 0.8.4
162decodeFileEither
163 :: FromJSON a
164 => FilePath
165 -> IO (Either ParseException a)
166decodeFileEither = decodeHelper_ . Y.decodeFile
168decodeEither :: FromJSON a => ByteString -> Either String a
169decodeEither bs = unsafePerformIO
170 $ either (Left . prettyPrintParseException) id
171 <$> decodeHelper (Y.decode bs)
173-- | More helpful version of 'decodeEither' which returns the 'YamlException'.
174--
175-- Since 0.8.3
176decodeEither' :: FromJSON a => ByteString -> Either ParseException a
177decodeEither' = either Left (either (Left . AesonException) Right)
178 . unsafePerformIO
179 . decodeHelper
180 . Y.decode
182array :: [Value] -> Value
183array = Array . V.fromList
185parseMonad :: Monad m => (a -> Parser b) -> a -> m b
186parseMonad p = either fail return . parseEither p