1{-# LANGUAGE FlexibleContexts #-}
2{-# LANGUAGE FlexibleInstances #-}
4{-# LANGUAGE DeriveDataTypeable #-}
5{-# LANGUAGE MultiParamTypeClasses #-}
6{-# LANGUAGE PatternGuards #-}
7{-# LANGUAGE ScopedTypeVariables #-}
8{-# LANGUAGE OverloadedStrings #-}
9-- | Provides a high-level interface for processing YAML files.
11-- This module reuses most of the infrastructure from the @aeson@ package.
12-- This means that you can use all of the existing tools for JSON
13-- processing for processing YAML files. As a result, much of the
14-- documentation below mentions JSON; do not let that confuse you, it's
17-- For the most part, YAML content translates directly into JSON, and
18-- therefore there is very little data loss. If you need to deal with YAML
19-- more directly (e.g., directly deal with aliases), you should use the
20-- "Text.Libyaml" module instead.
22-- For documentation on the @aeson@ types, functions, classes, and
23-- operators, please see the @Data.Aeson@ module of the @aeson@ package.
31 -- * Constructors and accessors
45 -- * Encoding/decoding
50 -- ** Better error information
52 -- ** More control over decoding
56import qualified Text.Libyaml as Y
58 ( Value (..), ToJSON (..), FromJSON (..), object
59 , (.=) , (.:) , (.:?) , (.!=)
62import Data.Aeson.Types (Pair, parseMaybe, parseEither, Parser)
63import Text.Libyaml hiding (encode, decode, encodeFile, decodeFile)
64import Data.ByteString (ByteString)
65import qualified Data.ByteString.Char8 as S8
66import qualified Data.Map as Map
67import System.IO.Unsafe (unsafePerformIO)
68import Control.Exception (try, throwIO, fromException, Exception)
69import Control.Monad.Trans.State
70import qualified Data.Conduit as C
71import qualified Data.Conduit.List as CL
72import Control.Monad.Trans.Class (MonadTrans, lift)
73import Control.Monad.IO.Class (MonadIO, liftIO)
74import Control.Monad (liftM)
75import Data.Char (toUpper)
76import qualified Data.Vector as V
77import Data.Text (Text, pack)
78import qualified Data.Text as T
79import Data.Text.Read (signed, decimal, double)
80import Data.Text.Encoding (encodeUtf8, decodeUtf8With)
81import Data.Text.Encoding.Error (lenientDecode)
82import qualified Data.HashMap.Strict as M
84import Data.Attoparsec.Number
86encode :: ToJSON a => a -> ByteString
87encode obj = unsafePerformIO $
88 C.runResourceT $ CL.sourceList (objToEvents $ toJSON obj)
91encodeFile :: ToJSON a => FilePath -> a -> IO ()
92encodeFile fp obj = C.runResourceT
93 $ CL.sourceList (objToEvents $ toJSON obj)
96objToEvents :: Value -> [Y.Event]
97objToEvents o = (:) EventStreamStart
98 . (:) EventDocumentStart
105scalarToEvent :: YamlScalar -> Event
106scalarToEvent (YamlScalar v t s) = EventScalar v t s Nothing
109objToEvents' :: Value -> [Y.Event] -> [Y.Event]
110--objToEvents' (Scalar s) rest = scalarToEvent s : rest
111objToEvents' (Array list) rest =
112 EventSequenceStart Nothing
113 : foldr ($) (EventSequenceEnd : rest) (map objToEvents' $ V.toList list)
114objToEvents' (Object pairs) rest =
115 EventMappingStart Nothing
116 : foldr ($) (EventMappingEnd : rest) (map pairToEvents $ M.toList pairs)
118-- Empty strings need special handling to ensure they get quoted. This avoids:
119-- https://github.com/snoyberg/yaml/issues/24
120objToEvents' (String "") rest = EventScalar "" NoTag SingleQuoted Nothing : rest
122objToEvents' (String s) rest = EventScalar (encodeUtf8 s) StrTag PlainNoTag Nothing : rest
123objToEvents' Null rest = EventScalar "null" NullTag PlainNoTag Nothing : rest
124objToEvents' (Bool True) rest = EventScalar "true" BoolTag PlainNoTag Nothing : rest
125objToEvents' (Bool False) rest = EventScalar "false" BoolTag PlainNoTag Nothing : rest
126objToEvents' (Number n) rest = EventScalar (S8.pack $ show n) IntTag PlainNoTag Nothing : rest
128pairToEvents :: Pair -> [Y.Event] -> [Y.Event]
129pairToEvents (k, v) rest =
130 EventScalar (encodeUtf8 k) StrTag PlainNoTag Nothing
131 : objToEvents' v rest
135data ParseException = NonScalarKey
136 | UnknownAlias { _anchorName :: Y.AnchorName }
137 | UnexpectedEvent { _received :: Maybe Event
138 , _expected :: Maybe Event
140 | InvalidYaml (Maybe YamlException)
141 deriving (Show, Typeable)
142instance Exception ParseException
144newtype PErrorT m a = PErrorT { runPErrorT :: m (Either ParseException a) }
145instance Monad m => Monad (PErrorT m) where
146 return = PErrorT . return . Right
147 (PErrorT m) >>= f = PErrorT $ do
150 Left e' -> return $ Left e'
151 Right a -> runPErrorT $ f a
152instance MonadTrans PErrorT where
153 lift = PErrorT . liftM Right
154instance MonadIO m => MonadIO (PErrorT m) where
155 liftIO = lift . liftIO
157type Parse = StateT (Map.Map String Value) (C.ResourceT IO)
159requireEvent :: Event -> C.Sink Event Parse ()
164 else liftIO $ throwIO $ UnexpectedEvent f $ Just e
166parse :: C.Sink Event Parse Value
168 requireEvent EventStreamStart
169 requireEvent EventDocumentStart
171 requireEvent EventDocumentEnd
172 requireEvent EventStreamEnd
175parseScalar :: ByteString -> Anchor -> Style -> Tag
176 -> C.Sink Event Parse Text
177parseScalar v a style tag = do
178 let res = decodeUtf8With lenientDecode v
180 Nothing -> return res
182 lift $ modify (Map.insert an $ textToValue style tag res)
185textToValue :: Style -> Tag -> Text -> Value
186textToValue SingleQuoted _ t = String t
187textToValue DoubleQuoted _ t = String t
188textToValue _ StrTag t = String t
189textToValue Folded _ t = String t
191 | t `elem` ["null", "Null", "NULL", "~", ""] = Null
192 | any (t `isLike`) ["y", "yes", "on", "true"] = Bool True
193 | any (t `isLike`) ["n", "no", "off", "false"] = Bool False
194 | Right (x, "") <- signed decimal t = Number $ I x
195 | Right (x, "") <- double t = Number $ D x
196 | otherwise = String t
197 where x `isLike` ref = x `elem` [ref, T.toUpper ref, titleCased]
198 where titleCased = toUpper (T.head ref) `T.cons` T.tail ref
201parseO :: C.Sink Event Parse Value
205 Just (EventScalar v tag style a) -> fmap (textToValue style tag) $ parseScalar v a style tag
206 Just (EventSequenceStart a) -> parseS a id
207 Just (EventMappingStart a) -> parseM a M.empty
208 Just (EventAlias an) -> do
210 case Map.lookup an m of
211 Nothing -> liftIO $ throwIO $ UnknownAlias an
213 _ -> liftIO $ throwIO $ UnexpectedEvent me Nothing
216 -> ([Value] -> [Value])
217 -> C.Sink Event Parse Value
221 Just EventSequenceEnd -> do
223 let res = Array $ V.fromList $ front []
225 Nothing -> return res
227 lift $ modify $ Map.insert an res
231 parseS a $ front . (:) o
234 -> M.HashMap Text Value
235 -> C.Sink Event Parse Value
239 Just EventMappingEnd -> do
241 let res = Object front
243 Nothing -> return res
245 lift $ modify $ Map.insert an res
250 Just (EventScalar v tag style a') -> parseScalar v a' style tag
251 _ -> liftIO $ throwIO $ UnexpectedEvent me Nothing
254 let al = M.insert s o front
255 al' = if s == pack "<<"
257 Object l -> M.union al l
258 Array l -> M.union al $ foldl merge' M.empty $ V.toList l
261 parseM a $ M.insert s o al'
262 where merge' al (Object om) = M.union al om
268decode bs = unsafePerformIO
269 $ fmap (either (const Nothing) (either (const Nothing) Just))
270 $ decodeHelper (Y.decode bs)
272decodeFile :: FromJSON a
275decodeFile fp = decodeHelper (Y.decodeFile fp) >>= either throwIO (return . either (const Nothing) id)
277decodeEither :: FromJSON a => ByteString -> Either String a
278decodeEither bs = unsafePerformIO
279 $ fmap (either (Left . show) id)
280 $ decodeHelper (Y.decode bs)
282decodeHelper :: FromJSON a
283 => C.Source Parse Y.Event
284 -> IO (Either ParseException (Either String a))
286 x <- try $ C.runResourceT $ flip evalStateT Map.empty $ src C.$$ parse
289 | Just pe <- fromException e -> return $ Left pe
290 | Just ye <- fromException e -> return $ Left $ InvalidYaml $ Just (ye :: YamlException)
291 | otherwise -> throwIO e
292 Right y -> return $ Right $ parseEither parseJSON y
294array :: [Value] -> Value
295array = Array . V.fromList
297parseMonad :: Monad m => (a -> Parser b) -> a -> m b
298parseMonad p = either fail return . parseEither p
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list