keter/Data/Yaml/FilePath.hs

68 lines
2.8 KiB
Haskell
Raw Normal View History

2013-07-10 10:57:38 +04:00
{-# LANGUAGE NoImplicitPrelude #-}
-- | Utilities for dealing with YAML config files which contain relative file
-- paths.
module Data.Yaml.FilePath
( decodeFileRelative
2013-07-14 14:02:18 +04:00
, lookupBase
, lookupBaseMaybe
2013-07-10 10:57:38 +04:00
, BaseDir
, ParseYamlFile (..)
2013-07-26 11:27:06 +04:00
, NonEmptyVector (..)
2013-07-10 10:57:38 +04:00
) where
import Control.Applicative ((<$>))
import Filesystem.Path.CurrentOS (FilePath, encodeString, directory, fromText, (</>))
2013-07-14 14:02:18 +04:00
import Data.Yaml (decodeFileEither, ParseException (AesonException), parseJSON)
2013-07-26 11:27:06 +04:00
import Prelude (($!), ($), Either (..), return, IO, (.), (>>=), Maybe (..), maybe, mapM, Ord, fail)
2013-07-14 14:02:18 +04:00
import Data.Aeson.Types ((.:), (.:?), Object, Parser, Value, parseEither)
2013-07-10 10:57:38 +04:00
import Data.Text (Text)
2013-07-14 14:02:18 +04:00
import qualified Data.Set as Set
2013-07-25 15:10:09 +04:00
import qualified Data.Vector as V
2013-07-10 10:57:38 +04:00
-- | The directory from which we're reading the config file.
newtype BaseDir = BaseDir FilePath
-- | Parse a config file, using the 'ParseYamlFile' typeclass.
decodeFileRelative :: ParseYamlFile a
=> FilePath
-> IO (Either ParseException a)
decodeFileRelative fp = do
evalue <- decodeFileEither $ encodeString fp
return $! case evalue of
Left e -> Left e
Right value ->
case parseEither (parseYamlFile basedir) value of
Left s -> Left $! AesonException s
Right x -> Right $! x
where
basedir = BaseDir $ directory fp
-- | A replacement for the @.:@ operator which will both parse a file path and
-- apply the relative file logic.
2013-07-14 14:02:18 +04:00
lookupBase :: ParseYamlFile a => BaseDir -> Object -> Text -> Parser a
lookupBase basedir o t = (o .: t) >>= parseYamlFile basedir
-- | A replacement for the @.:?@ operator which will both parse a file path and
-- apply the relative file logic.
lookupBaseMaybe :: ParseYamlFile a => BaseDir -> Object -> Text -> Parser (Maybe a)
lookupBaseMaybe basedir o t = (o .:? t) >>= maybe (return Nothing) ((Just <$>) . parseYamlFile basedir)
2013-07-10 10:57:38 +04:00
-- | A replacement for the standard @FromJSON@ typeclass which can handle relative filepaths.
class ParseYamlFile a where
parseYamlFile :: BaseDir -> Value -> Parser a
2013-07-14 14:02:18 +04:00
instance ParseYamlFile FilePath where
parseYamlFile (BaseDir dir) o = ((dir </>) . fromText) <$> parseJSON o
instance (ParseYamlFile a, Ord a) => ParseYamlFile (Set.Set a) where
parseYamlFile base o = parseJSON o >>= ((Set.fromList <$>) . mapM (parseYamlFile base))
2013-07-25 15:10:09 +04:00
instance ParseYamlFile a => ParseYamlFile (V.Vector a) where
parseYamlFile base o = parseJSON o >>= ((V.fromList <$>) . mapM (parseYamlFile base))
2013-07-26 11:27:06 +04:00
data NonEmptyVector a = NonEmptyVector !a !(V.Vector a)
instance ParseYamlFile a => ParseYamlFile (NonEmptyVector a) where
parseYamlFile base o = do
v <- parseYamlFile base o
if V.null v
then fail "NonEmptyVector: Expected at least one value"
else return $ NonEmptyVector (V.head v) (V.tail v)