2{-# LANGUAGE DeriveDataTypeable #-}
3{-# LANGUAGE PatternGuards #-}
4{-# LANGUAGE OverloadedStrings #-}
5module Data.Yaml.Internal
8 , prettyPrintParseException
16#if !MIN_VERSION_base(4,8,0)
17import Control.Applicative ((<$>), Applicative(..))
19import Control.Exception
20import Control.Exception.Enclosed
21import Control.Monad (liftM, ap, unless)
22import Control.Monad.IO.Class (MonadIO, liftIO)
23import Control.Monad.Trans.Class (MonadTrans, lift)
24import Control.Monad.Trans.Resource (ResourceT, runResourceT)
25import Control.Monad.Trans.State
27import Data.Aeson.Types hiding (parse)
28import qualified Data.Attoparsec.Text as Atto
29import Data.ByteString (ByteString)
30import Data.Char (toUpper)
31import qualified Data.Conduit as C
32import qualified Data.Conduit.List as CL
33import qualified Data.HashMap.Strict as M
34import qualified Data.HashSet as HashSet
35import qualified Data.Map as Map
36import Data.Scientific (Scientific)
37import Data.Text (Text, pack)
38import qualified Data.Text as T
39import Data.Text.Encoding (decodeUtf8With)
40import Data.Text.Encoding.Error (lenientDecode)
42import qualified Data.Vector as V
44import qualified Text.Libyaml as Y
45import Text.Libyaml hiding (encode, decode, encodeFile, decodeFile)
47data ParseException = NonScalarKey
48 | UnknownAlias { _anchorName :: Y.AnchorName }
49 | UnexpectedEvent { _received :: Maybe Event
50 , _expected :: Maybe Event
52 | InvalidYaml (Maybe YamlException)
53 | AesonException String
54 | OtherParseException SomeException
55 | NonStringKeyAlias Y.AnchorName Value
57 deriving (Show, Typeable)
59instance Exception ParseException where
60#if MIN_VERSION_base(4, 8, 0)
61 displayException = prettyPrintParseException
64-- | Alternative to 'show' to display a 'ParseException' on the screen.
65-- Instead of displaying the data constructors applied to their arguments,
66-- a more textual output is returned. For example, instead of printing:
68-- > InvalidYaml (Just (YamlParseException {yamlProblem = "did not find expected ',' or '}'", yamlContext = "while parsing a flow mapping", yamlProblemMark = YamlMark {yamlIndex = 42, yamlLine = 2, yamlColumn = 12}})))
70-- It looks more pleasant to print:
72-- > YAML parse exception at line 2, column 12,
73-- > while parsing a flow mapping:
74-- > did not find expected ',' or '}'
77prettyPrintParseException :: ParseException -> String
78prettyPrintParseException pe = case pe of
79 NonScalarKey -> "Non scalar key"
80 UnknownAlias anchor -> "Unknown alias `" ++ anchor ++ "`"
81 UnexpectedEvent { _expected = mbExpected, _received = mbUnexpected } -> unlines
82 [ "Unexpected event: expected"
83 , " " ++ show mbExpected
85 , " " ++ show mbUnexpected
87 InvalidYaml mbYamlError -> case mbYamlError of
88 Nothing -> "Unspecified YAML error"
89 Just yamlError -> case yamlError of
90 YamlException s -> "YAML exception:\n" ++ s
91 YamlParseException problem context mark -> concat
92 [ "YAML parse exception at line " ++ show (yamlLine mark) ++
93 ", column " ++ show (yamlColumn mark)
96 -- The context seems to include a leading "while" or similar.
97 _ -> ",\n" ++ context ++ ":\n"
100 AesonException s -> "Aeson exception:\n" ++ s
101 OtherParseException exc -> "Generic parse exception:\n" ++ show exc
102 NonStringKeyAlias anchor value -> unlines
103 [ "Non-string key alias:"
104 , " Anchor name: " ++ anchor
105 , " Value: " ++ show value
107 CyclicIncludes -> "Cyclic includes"
109newtype PErrorT m a = PErrorT { runPErrorT :: m (Either ParseException a) }
110instance Monad m => Functor (PErrorT m) where
112instance Monad m => Applicative (PErrorT m) where
113 pure = PErrorT . return . Right
115instance Monad m => Monad (PErrorT m) where
117 (PErrorT m) >>= f = PErrorT $ do
120 Left e' -> return $ Left e'
121 Right a -> runPErrorT $ f a
122instance MonadTrans PErrorT where
123 lift = PErrorT . liftM Right
124instance MonadIO m => MonadIO (PErrorT m) where
125 liftIO = lift . liftIO
127type Parse = StateT (Map.Map String Value) (ResourceT IO)
129requireEvent :: Event -> C.Sink Event Parse ()
132 unless (f == Just e) $ liftIO $ throwIO $ UnexpectedEvent f $ Just e
134parse :: C.Sink Event Parse Value
136 streamStart <- CL.head
139 -- empty string input
141 Just EventStreamStart -> do
142 documentStart <- CL.head
143 case documentStart of
144 Just EventStreamEnd ->
145 -- empty file input, comment only string/file input
147 Just EventDocumentStart -> do
149 requireEvent EventDocumentEnd
150 requireEvent EventStreamEnd
152 _ -> liftIO $ throwIO $ UnexpectedEvent documentStart Nothing
153 _ -> liftIO $ throwIO $ UnexpectedEvent streamStart Nothing
155parseScalar :: ByteString -> Anchor -> Style -> Tag
156 -> C.Sink Event Parse Text
157parseScalar v a style tag = do
158 let res = decodeUtf8With lenientDecode v
160 Nothing -> return res
162 lift $ modify (Map.insert an $ textToValue style tag res)
165textToValue :: Style -> Tag -> Text -> Value
166textToValue SingleQuoted _ t = String t
167textToValue DoubleQuoted _ t = String t
168textToValue _ StrTag t = String t
169textToValue Folded _ t = String t
171 | t `elem` ["null", "Null", "NULL", "~", ""] = Null
172 | any (t `isLike`) ["y", "yes", "on", "true"] = Bool True
173 | any (t `isLike`) ["n", "no", "off", "false"] = Bool False
174 | Right x <- textToScientific t = Number x
175 | otherwise = String t
176 where x `isLike` ref = x `elem` [ref, T.toUpper ref, titleCased]
177 where titleCased = toUpper (T.head ref) `T.cons` T.tail ref
179textToScientific :: Text -> Either String Scientific
180textToScientific = Atto.parseOnly (Atto.scientific <* Atto.endOfInput)
182parseO :: C.Sink Event Parse Value
186 Just (EventScalar v tag style a) -> textToValue style tag <$> parseScalar v a style tag
187 Just (EventSequenceStart a) -> parseS a id
188 Just (EventMappingStart a) -> parseM a M.empty
189 Just (EventAlias an) -> do
191 case Map.lookup an m of
192 Nothing -> liftIO $ throwIO $ UnknownAlias an
194 _ -> liftIO $ throwIO $ UnexpectedEvent me Nothing
197 -> ([Value] -> [Value])
198 -> C.Sink Event Parse Value
202 Just EventSequenceEnd -> do
204 let res = Array $ V.fromList $ front []
206 Nothing -> return res
208 lift $ modify $ Map.insert an res
212 parseS a $ front . (:) o
215 -> M.HashMap Text Value
216 -> C.Sink Event Parse Value
220 Just EventMappingEnd -> do
222 let res = Object front
224 Nothing -> return res
226 lift $ modify $ Map.insert an res
231 Just (EventScalar v tag style a') -> parseScalar v a' style tag
232 Just (EventAlias an) -> do
234 case Map.lookup an m of
235 Nothing -> liftIO $ throwIO $ UnknownAlias an
236 Just (String t) -> return t
237 Just v -> liftIO $ throwIO $ NonStringKeyAlias an v
238 _ -> liftIO $ throwIO $ UnexpectedEvent me Nothing
241 let al = M.insert s o front
242 al' = if s == pack "<<"
244 Object l -> M.union front l
245 Array l -> M.union front $ foldl merge' M.empty $ V.toList l
249 where merge' al (Object om) = M.union al om
252decodeHelper :: FromJSON a
253 => C.Source Parse Y.Event
254 -> IO (Either ParseException (Either String a))
256 x <- tryAny $ runResourceT $ flip evalStateT Map.empty $ src C.$$ parse
259 | Just pe <- fromException e -> return $ Left pe
260 | Just ye <- fromException e -> return $ Left $ InvalidYaml $ Just (ye :: YamlException)
261 | otherwise -> throwIO e
262 Right y -> return $ Right $ parseEither parseJSON y
264decodeHelper_ :: FromJSON a
265 => C.Source Parse Event
266 -> IO (Either ParseException a)
267decodeHelper_ src = do
268 x <- tryAny $ runResourceT $ flip evalStateT Map.empty $ src C.$$ parse
271 | Just pe <- fromException e -> Left pe
272 | Just ye <- fromException e -> Left $ InvalidYaml $ Just (ye :: YamlException)
273 | otherwise -> Left $ OtherParseException e
275 (Left . AesonException)
277 (parseEither parseJSON y)
279-- | Strings which must be escaped so as not to be treated as non-string scalars.
280specialStrings :: HashSet.HashSet Text
281specialStrings = HashSet.fromList $ T.words
282 "y Y yes Yes YES n N no No NO true True TRUE false False FALSE on On ON off Off OFF null Null NULL ~"
284isNumeric :: Text -> Bool
288 isNumeric' c = ('0' <= c && c <= '9')
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