{-# LANGUAGE CPP #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE Safe #-}
module Data.YAML
(
decode
, decode1
, decodeStrict
, decode1Strict
, FromYAML(..)
, Parser
, parseEither
, failAtNode
, typeMismatch
, Mapping
, (.:), (.:?), (.:!), (.!=)
, encode
, encode1
, encodeStrict
, encode1Strict
, ToYAML(..)
, Pair
, mapping
, (.=)
, withScalar
, withSeq
, withBool
, withFloat
, withInt
, withNull
, withStr
, withMap
, decodeNode
, decodeNode'
, encodeNode
, encodeNode'
, Doc(Doc,docRoot)
, Node(..)
, Scalar(..)
, Pos(..)
, prettyPosWithSource
, SchemaResolver
, failsafeSchemaResolver
, jsonSchemaResolver
, coreSchemaResolver
, SchemaEncoder
, failsafeSchemaEncoder
, jsonSchemaEncoder
, coreSchemaEncoder
, decodeLoader
, Loader(..)
, LoaderT
, NodeId
) where
import qualified Control.Monad.Fail as Fail
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BS.L
import qualified Data.Map as Map
import qualified Data.Text as T
import Data.YAML.Dumper
import Data.YAML.Event (isUntagged, tagToText)
import Data.YAML.Internal
import Data.YAML.Loader
import Data.YAML.Pos
import Data.YAML.Schema.Internal
import Util
(.:) :: FromYAML a => Mapping Pos -> Text -> Parser a
m .: k = maybe (fail $ "key " ++ show k ++ " not found") parseYAML (Map.lookup (Scalar fakePos (SStr k)) m)
(.:?) :: FromYAML a => Mapping Pos -> Text -> Parser (Maybe a)
m .:? k = maybe (pure Nothing) parseYAML (Map.lookup (Scalar fakePos (SStr k)) m)
(.:!) :: FromYAML a => Mapping Pos -> Text -> Parser (Maybe a)
m .:! k = maybe (pure Nothing) (fmap Just . parseYAML) (Map.lookup (Scalar fakePos (SStr k)) m)
(.!=) :: Parser (Maybe a) -> a -> Parser a
mv .!= def = fmap (maybe def id) mv
fakePos :: Pos
fakePos = Pos { posByteOffset = -1 , posCharOffset = -1 , posLine = 1 , posColumn = 0 }
decodeNode :: BS.L.ByteString -> Either (Pos, String) [Doc (Node Pos)]
decodeNode = decodeNode' coreSchemaResolver False False
decodeNode' :: SchemaResolver
-> Bool
-> Bool
-> BS.L.ByteString
-> Either (Pos, String) [Doc (Node Pos)]
decodeNode' SchemaResolver{..} anchorNodes allowCycles bs0
= map Doc <$> runIdentity (decodeLoader failsafeLoader bs0)
where
failsafeLoader = Loader { yScalar = \t s v pos-> pure $ case schemaResolverScalar t s v of
Left e -> Left (pos,e)
Right v' -> Right (Scalar pos v')
, ySequence = \t vs pos -> pure $ case schemaResolverSequence t of
Left e -> Left (pos,e)
Right t' -> Right (Sequence pos t' vs)
, yMapping = \t kvs pos-> pure $ case schemaResolverMapping t of
Left e -> Left (pos,e)
Right t' -> Mapping pos t' <$> mkMap kvs
, yAlias = if allowCycles
then \_ _ n _-> pure $ Right n
else \_ c n pos -> pure $ if c then Left (pos,"cycle detected") else Right n
, yAnchor = if anchorNodes
then \j n pos -> pure $ Right (Anchor pos j n)
else \_ n _ -> pure $ Right n
}
mkMap :: [(Node Pos, Node Pos)] -> Either (Pos, String) (Map (Node Pos) (Node Pos))
mkMap kvs
| schemaResolverMappingDuplicates = Right $! Map.fromList kvs
| otherwise = case mapFromListNoDupes kvs of
Left (k,_) -> Left (nodeLoc k,"Duplicate key in mapping: " ++ show k)
Right m -> Right m
newtype Parser a = P { unP :: Either (Pos, String) a }
instance Functor Parser where
fmap f (P x) = P (fmap f x)
x <$ P (Right _) = P (Right x)
_ <$ P (Left e) = P (Left e)
instance Applicative Parser where
pure = P . Right
P (Left e) <*> _ = P (Left e)
P (Right f) <*> P r = P (fmap f r)
P (Left e) *> _ = P (Left e)
P (Right _) *> p = p
instance Monad Parser where
return = pure
P m >>= k = P (m >>= unP . k)
(>>) = (*>)
#if !(MIN_VERSION_base(4,13,0))
fail = Fail.fail
#endif
instance Fail.MonadFail Parser where
fail s = P (Left (fakePos, s))
failAtNode :: Node Pos -> String -> Parser a
failAtNode n s = P (Left (nodeLoc n, s))
instance Alternative Parser where
empty = fail "empty"
P (Left _) <|> y = y
x <|> _ = x
instance MonadPlus Parser where
mzero = empty
mplus = (<|>)
parseEither :: Parser a -> Either (Pos, String) a
parseEither = unP
typeMismatch :: String
-> Node Pos
-> Parser a
typeMismatch expected node = failAtNode node ("expected " ++ expected ++ " instead of " ++ got)
where
got = case node of
Scalar _ (SBool _) -> "!!bool"
Scalar _ (SInt _) -> "!!int"
Scalar _ SNull -> "!!null"
Scalar _ (SStr _) -> "!!str"
Scalar _ (SFloat _) -> "!!float"
Scalar _ (SUnknown t v)
| isUntagged t -> tagged t ++ show v
| otherwise -> "(unsupported) " ++ tagged t ++ "scalar"
Anchor _ _ _ -> "anchor"
Mapping _ t _ -> tagged t ++ " mapping"
Sequence _ t _ -> tagged t ++ " sequence"
tagged t0 = case tagToText t0 of
Nothing -> "non-specifically ? tagged (i.e. unresolved) "
Just t -> T.unpack t ++ " tagged"
class FromYAML a where
parseYAML :: Node Pos -> Parser a
{-# INLINE fixupFailPos #-}
fixupFailPos :: Pos -> Parser a -> Parser a
fixupFailPos pos (P (Left (pos0,emsg)))
| pos0 == fakePos = P (Left (pos,emsg))
fixupFailPos _ p = p
withNull :: String -> Parser a -> Node Pos -> Parser a
withNull _ f (Scalar pos SNull) = fixupFailPos pos f
withNull expected _ v = typeMismatch expected v
withScalar :: String -> (Scalar -> Parser a) -> Node Pos -> Parser a
withScalar _ f (Scalar pos sca) = fixupFailPos pos (f sca)
withScalar expected _ v = typeMismatch expected v
instance (loc ~ Pos) => FromYAML (Node loc) where
parseYAML = pure
instance FromYAML Scalar where
parseYAML = withScalar "scalar" pure
instance FromYAML Bool where
parseYAML = withBool "!!bool" pure
withBool :: String -> (Bool -> Parser a) -> Node Pos -> Parser a
withBool _ f (Scalar pos (SBool b)) = fixupFailPos pos (f b)
withBool expected _ v = typeMismatch expected v
instance FromYAML Text where
parseYAML = withStr "!!str" pure
withStr :: String -> (Text -> Parser a) -> Node Pos -> Parser a
withStr _ f (Scalar pos (SStr b)) = fixupFailPos pos (f b)
withStr expected _ v = typeMismatch expected v
instance FromYAML Integer where
parseYAML = withInt "!!int" pure
withInt :: String -> (Integer -> Parser a) -> Node Pos -> Parser a
withInt _ f (Scalar pos (SInt b)) = fixupFailPos pos (f b)
withInt expected _ v = typeMismatch expected v
instance FromYAML Natural where
parseYAML = withInt "!!int" $ \b -> if b < 0 then fail ("!!int " ++ show b ++ " out of range for 'Natural'")
else pure (fromInteger b)
{-# INLINE parseInt #-}
parseInt :: (Integral a, Bounded a) => [Char] -> Node Pos -> Parser a
parseInt name = withInt "!!int" $ \b -> maybe (fail $ "!!int " ++ show b ++ " out of range for '" ++ name ++ "'") pure $
fromIntegerMaybe b
instance FromYAML Int where parseYAML = parseInt "Int"
instance FromYAML Int8 where parseYAML = parseInt "Int8"
instance FromYAML Int16 where parseYAML = parseInt "Int16"
instance FromYAML Int32 where parseYAML = parseInt "Int32"
instance FromYAML Int64 where parseYAML = parseInt "Int64"
instance FromYAML Word where parseYAML = parseInt "Word"
instance FromYAML Word8 where parseYAML = parseInt "Word8"
instance FromYAML Word16 where parseYAML = parseInt "Word16"
instance FromYAML Word32 where parseYAML = parseInt "Word32"
instance FromYAML Word64 where parseYAML = parseInt "Word64"
instance FromYAML Double where
parseYAML = withFloat "!!float" pure
withFloat :: String -> (Double -> Parser a) -> Node Pos -> Parser a
withFloat _ f (Scalar pos (SFloat b)) = fixupFailPos pos (f b)
withFloat expected _ v = typeMismatch expected v
instance (Ord k, FromYAML k, FromYAML v) => FromYAML (Map k v) where
parseYAML = withMap "!!map" $ \xs -> Map.fromList <$> mapM (\(a,b) -> (,) <$> parseYAML a <*> parseYAML b) (Map.toList xs)
withMap :: String -> (Mapping Pos -> Parser a) -> Node Pos -> Parser a
withMap _ f (Mapping pos tag xs)
| tag == tagMap = fixupFailPos pos (f xs)
withMap expected _ v = typeMismatch expected v
instance FromYAML v => FromYAML [v] where
parseYAML = withSeq "!!seq" (mapM parseYAML)
withSeq :: String -> ([Node Pos] -> Parser a) -> Node Pos-> Parser a
withSeq _ f (Sequence pos tag xs)
| tag == tagSeq = fixupFailPos pos (f xs)
withSeq expected _ v = typeMismatch expected v
instance FromYAML a => FromYAML (Maybe a) where
parseYAML (Scalar _ SNull) = pure Nothing
parseYAML j = Just <$> parseYAML j
instance (FromYAML a, FromYAML b) => FromYAML (a,b) where
parseYAML = withSeq "!!seq" $ \xs ->
case xs of
[a,b] -> (,) <$> parseYAML a
<*> parseYAML b
_ -> fail ("expected 2-sequence but got " ++ show (length xs) ++ "-sequence instead")
instance (FromYAML a, FromYAML b, FromYAML c) => FromYAML (a,b,c) where
parseYAML = withSeq "!!seq" $ \xs ->
case xs of
[a,b,c] -> (,,) <$> parseYAML a
<*> parseYAML b
<*> parseYAML c
_ -> fail ("expected 3-sequence but got " ++ show (length xs) ++ "-sequence instead")
instance (FromYAML a, FromYAML b, FromYAML c, FromYAML d) => FromYAML (a,b,c,d) where
parseYAML = withSeq "!!seq" $ \xs ->
case xs of
[a,b,c,d] -> (,,,) <$> parseYAML a
<*> parseYAML b
<*> parseYAML c
<*> parseYAML d
_ -> fail ("expected 4-sequence but got " ++ show (length xs) ++ "-sequence instead")
instance (FromYAML a, FromYAML b, FromYAML c, FromYAML d, FromYAML e) => FromYAML (a,b,c,d,e) where
parseYAML = withSeq "!!seq" $ \xs ->
case xs of
[a,b,c,d,e] -> (,,,,) <$> parseYAML a
<*> parseYAML b
<*> parseYAML c
<*> parseYAML d
<*> parseYAML e
_ -> fail ("expected 5-sequence but got " ++ show (length xs) ++ "-sequence instead")
instance (FromYAML a, FromYAML b, FromYAML c, FromYAML d, FromYAML e, FromYAML f) => FromYAML (a,b,c,d,e,f) where
parseYAML = withSeq "!!seq" $ \xs ->
case xs of
[a,b,c,d,e,f] -> (,,,,,) <$> parseYAML a
<*> parseYAML b
<*> parseYAML c
<*> parseYAML d
<*> parseYAML e
<*> parseYAML f
_ -> fail ("expected 6-sequence but got " ++ show (length xs) ++ "-sequence instead")
instance (FromYAML a, FromYAML b, FromYAML c, FromYAML d, FromYAML e, FromYAML f, FromYAML g) => FromYAML (a,b,c,d,e,f,g) where
parseYAML = withSeq "!!seq" $ \xs ->
case xs of
[a,b,c,d,e,f,g] -> (,,,,,,) <$> parseYAML a
<*> parseYAML b
<*> parseYAML c
<*> parseYAML d
<*> parseYAML e
<*> parseYAML f
<*> parseYAML g
_ -> fail ("expected 7-sequence but got " ++ show (length xs) ++ "-sequence instead")
decode :: FromYAML v => BS.L.ByteString -> Either (Pos, String) [v]
decode bs0 = decodeNode bs0 >>= mapM (parseEither . parseYAML . (\(Doc x) -> x))
decode1 :: FromYAML v => BS.L.ByteString -> Either (Pos, String) v
decode1 bs0 = do
docs <- decodeNode bs0
case docs of
[] -> Left (Pos { posByteOffset = 0, posCharOffset = 0, posLine = 1, posColumn = 0 }, "empty YAML stream")
[Doc v] -> parseEither $ parseYAML $ v
(_:Doc n:_) -> Left (nodeLoc n, "unexpected multiple YAML documents")
decodeStrict :: FromYAML v => BS.ByteString -> Either (Pos, String) [v]
decodeStrict = decode . BS.L.fromChunks . (:[])
decode1Strict :: FromYAML v => BS.ByteString -> Either (Pos, String) v
decode1Strict = decode1 . BS.L.fromChunks . (:[])
class ToYAML a where
toYAML :: a -> Node ()
instance Loc loc => ToYAML (Node loc) where
toYAML = toUnit
instance ToYAML Bool where
toYAML = Scalar () . SBool
instance ToYAML Double where
toYAML = Scalar () . SFloat
instance ToYAML Int where toYAML = Scalar () . SInt . toInteger
instance ToYAML Int8 where toYAML = Scalar () . SInt . toInteger
instance ToYAML Int16 where toYAML = Scalar () . SInt . toInteger
instance ToYAML Int32 where toYAML = Scalar () . SInt . toInteger
instance ToYAML Int64 where toYAML = Scalar () . SInt . toInteger
instance ToYAML Word where toYAML = Scalar () . SInt . toInteger
instance ToYAML Word8 where toYAML = Scalar () . SInt . toInteger
instance ToYAML Word16 where toYAML = Scalar () . SInt . toInteger
instance ToYAML Word32 where toYAML = Scalar () . SInt . toInteger
instance ToYAML Word64 where toYAML = Scalar () . SInt . toInteger
instance ToYAML Natural where toYAML = Scalar () . SInt . toInteger
instance ToYAML Integer where toYAML = Scalar () . SInt
instance ToYAML Text where
toYAML = Scalar () . SStr
instance ToYAML Scalar where
toYAML = Scalar ()
instance ToYAML a => ToYAML (Maybe a) where
toYAML Nothing = Scalar () SNull
toYAML (Just a) = toYAML a
instance ToYAML a => ToYAML [a] where
toYAML = Sequence () tagSeq . map toYAML
instance (Ord k, ToYAML k, ToYAML v) => ToYAML (Map k v) where
toYAML kv = Mapping () tagMap (Map.fromList $ map (\(k,v) -> (toYAML k , toYAML v)) (Map.toList kv))
instance (ToYAML a, ToYAML b) => ToYAML (a, b) where
toYAML (a,b) = toYAML [toYAML a, toYAML b]
instance (ToYAML a, ToYAML b, ToYAML c) => ToYAML (a, b, c) where
toYAML (a,b,c) = toYAML [toYAML a, toYAML b, toYAML c]
instance (ToYAML a, ToYAML b, ToYAML c, ToYAML d) => ToYAML (a, b, c, d) where
toYAML (a,b,c,d) = toYAML [toYAML a, toYAML b, toYAML c, toYAML d]
instance (ToYAML a, ToYAML b, ToYAML c, ToYAML d, ToYAML e) => ToYAML (a, b, c, d, e) where
toYAML (a,b,c,d,e) = toYAML [toYAML a, toYAML b, toYAML c, toYAML d, toYAML e]
instance (ToYAML a, ToYAML b, ToYAML c, ToYAML d, ToYAML e, ToYAML f) => ToYAML (a, b, c, d, e, f) where
toYAML (a,b,c,d,e,f) = toYAML [toYAML a, toYAML b, toYAML c, toYAML d, toYAML e, toYAML f]
instance (ToYAML a, ToYAML b, ToYAML c, ToYAML d, ToYAML e, ToYAML f, ToYAML g) => ToYAML (a, b, c, d, e, f, g) where
toYAML (a,b,c,d,e,f,g) = toYAML [toYAML a, toYAML b, toYAML c, toYAML d, toYAML e, toYAML f, toYAML g]
encode :: ToYAML v => [v] -> BS.L.ByteString
encode vList = encodeNode $ map (Doc . toYAML) vList
encode1 :: ToYAML v => v -> BS.L.ByteString
encode1 a = encode [a]
encodeStrict :: ToYAML v => [v] -> BS.ByteString
encodeStrict = bsToStrict . encode
encode1Strict :: ToYAML v => v -> BS.ByteString
encode1Strict = bsToStrict . encode1
class Loc loc where
toUnit :: Functor f => f loc -> f ()
toUnit = (() <$)
instance Loc Pos
instance Loc () where toUnit = id
type Pair = (Node (), Node ())
(.=) :: ToYAML a => Text -> a -> Pair
name .= node = (toYAML name, toYAML node)
mapping :: [Pair] -> Node ()
mapping = Mapping () tagMap . Map.fromList