yaml-streamly-0.12.0: src/Data/Yaml/Include.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE FlexibleContexts #-}
module Data.Yaml.Include (
decodeFile
, decodeFileEither
, decodeFileWithWarnings
) where
#if !MIN_VERSION_directory(1, 2, 3)
import Control.Exception (handleJust)
import Control.Monad (guard)
import System.IO.Error (ioeGetFileName, ioeGetLocation, isDoesNotExistError)
#endif
import Control.Monad (when)
import Control.Monad.IO.Class (liftIO)
import Data.Aeson (FromJSON)
import Data.Text (unpack)
import Data.Text.Encoding (decodeUtf8)
import System.Directory
import System.FilePath
import Data.Yaml.Internal (ParseException(..), Warning(..), decodeHelper_, decodeHelper)
import Text.Libyaml hiding (decodeFile)
import qualified Text.Libyaml as Y
import Control.Exception.Safe
import qualified Streamly.Prelude as S
import Streamly.Prelude (SerialT, MonadAsync)
eventsFromFile
:: (MonadCatch m, MonadAsync m, MonadMask m)
=> FilePath
-> SerialT m Event
eventsFromFile = go []
where
-- decodeFile :: (MonadCatch m, MonadAsync m, MonadMask m) => FilePath -> SerialT m Event
go :: (MonadCatch m, MonadAsync m, MonadMask m) => [FilePath] -> FilePath -> SerialT m Event
go seen fp = do
cfp <- liftIO $ handleNotFound $ canonicalizePath fp
when (cfp `elem` seen) $ do
liftIO $ throwIO CyclicIncludes
Y.decodeFile cfp >>= \event -> case event of
EventScalar f (UriTag "!include") _ _ -> do
let includeFile = takeDirectory cfp </> unpack (decodeUtf8 f)
S.filter (`notElem` irrelevantEvents) $ go (cfp : seen) includeFile
_ -> pure event
irrelevantEvents = [EventStreamStart, EventDocumentStart, EventDocumentEnd, EventStreamEnd]
#if !MIN_VERSION_directory(1, 2, 3)
handleNotFound = handleJust
(\e -> do
guard (isDoesNotExistError e)
guard (ioeGetLocation e == "canonicalizePath")
ioeGetFileName e)
(throwIO . YamlException . ("Yaml file not found: " ++))
#else
handleNotFound = id
#endif
-- | Like `Data.Yaml.decodeFile` but with support for relative and absolute
-- includes.
--
-- The syntax for includes follows the form:
--
-- > somekey: !include ./somefile.yaml
decodeFile
:: FromJSON a
=> FilePath
-> IO (Maybe a)
decodeFile fp = (fmap snd <$> decodeHelper (eventsFromFile fp)) >>= either throwIO (return . either (const Nothing) id)
-- | Like `Data.Yaml.decodeFileEither` but with support for relative and
-- absolute includes.
--
-- The syntax for includes follows the form:
--
-- > somekey: !include ./somefile.yaml
decodeFileEither
:: FromJSON a
=> FilePath
-> IO (Either ParseException a)
decodeFileEither = fmap (fmap snd) . decodeFileWithWarnings
-- | A version of `decodeFileEither` that returns warnings along with the parse
-- result.
--
-- @since 0.10.0
decodeFileWithWarnings
:: FromJSON a
=> FilePath
-> IO (Either ParseException ([Warning], a))
decodeFileWithWarnings = decodeHelper_ . eventsFromFile