{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE ParallelListComp #-}
module Data.YamlDir ( -- * Only YAML files
-- $yaml
decodeYamlPath
, decodeYamlPathEither
-- * Only Text files
-- $text
, decodeTextPath
, decodeTextPathEither
-- * Files Based On Extension
-- $extension
, decodeExtnPath
, decodeExtnPathEither
) where
import Data.Either (either)
import Data.HashMap.Strict (fromList)
import Data.List (isSuffixOf)
import Data.Text (pack)
import qualified Data.Text.IO as T
import Data.Yaml
import System.Directory ( doesDirectoryExist
, getDirectoryContents
)
import System.FilePath ((</>), takeBaseName)
filterDir :: IO [String] -> IO [String]
filterDir = fmap (filter go)
where go d = d /= "." && d /= ".."
toMaybe :: Either a b -> Maybe b
toMaybe (Left _) = Nothing
toMaybe (Right x) = Just x
decodePath :: FromJSON t => FileFunc -> FilePath -> IO (Maybe t)
decodePath func path = fmap toMaybe (decodeEitherPath func path)
decodeEitherPath :: FromJSON t => FileFunc -> FilePath -> IO (Either String t)
decodeEitherPath func path = do
val <- parsePath func path
return (val >>= parseEither parseJSON)
-- as-yaml functions
{- $yaml
These functions read in a directory as a YAML object and assume that
each file within the directory itself contains a YAML value. For example,
if we create the directory
> $ mkdir mydir
> $ cat >mydir/foo <<EOF
> > [1,2,3]
> > EOF
> $ cat >mydir/bar <<EOF
> > baz: true
> > quux: false
> > EOF
then @decodeYamlPath "mydir"@ will return
> Just (Object (fromList [ ("foo", Array (fromList [ Number 1.0
> , Number 2.0
> , Number 3.0
> ]))
> , ("bar", Object (fromList [ ("baz", Bool True)
> , ("quux", Bool False)
> ]))
> ]))
-}
decodeYamlPath :: FromJSON t => FilePath -> IO (Maybe t)
decodeYamlPath = fmap toMaybe . decodeYamlPathEither
decodeYamlPathEither :: FromJSON t => FilePath -> IO (Either String t)
decodeYamlPathEither = decodeEitherPath go
where go = fmap (either (Left . show) (Right)) . decodeFileEither
-- as-text functions
{- $text
These functions read in a directory as a YAML object and treat each
file in the directory as containing a YAML string value. For example,
if we create the directory
> $ mkdir mydir
> $ cat >mydir/foo <<EOF
> > [1,2,3]
> > EOF
> $ cat >mydir/bar <<EOF
> > baz: true
> > quux: false
> > EOF
then @decodeTextPath "mydir"@ will return
> Just (Object (fromList [ ("foo", String "[1,2,3]\n")
> , ("bar", String "baz: true\nquux: false\n")
> ]))
-}
decodeTextPath :: FromJSON t => FilePath -> IO (Maybe t)
decodeTextPath = fmap toMaybe . decodeTextPathEither
decodeTextPathEither :: FromJSON t => FilePath -> IO (Either String t)
decodeTextPathEither = decodeEitherPath go
where go path = fmap (Right . String) (T.readFile path)
-- extension functions
{- $extension
These functions read in a directory as a YAML object and relies on
the extension of a file to determine whether it is YAML or non-YAML.
For example, if we create the directory
> $ mkdir mydir
> $ cat >mydir/foo.yaml <<EOF
> > [1,2,3]
> > EOF
> $ cat >mydir/bar.text <<EOF
> > baz: true
> > quux: false
> > EOF
then @decodeExtnPath "mydir"@ will return
> Just (Object (fromList [ ("foo.yaml", Array (fromList [ Number 1.0
> , Number 2.0
> , Number 3.0
> ]))
> , ("bar.text", String "baz: true\nquux: false\n")
> ]))
-}
decodeExtnPath :: FromJSON t => FilePath -> IO (Maybe t)
decodeExtnPath = fmap toMaybe . decodeExtnPathEither
decodeExtnPathEither :: FromJSON t => FilePath -> IO (Either String t)
decodeExtnPathEither = decodeEitherPath go
where go p | ".yaml" `isSuffixOf` p
= fmap (either (Left . show) Right) (decodeFileEither p)
| otherwise
= fmap (Right . String) (T.readFile p)
-- implementations
type FileFunc = FilePath -> IO (Either String Value)
parsePath :: FileFunc -> FilePath -> IO (Either String Value)
parsePath withFile path = do
isDir <- doesDirectoryExist path
if | isDir -> parseDir withFile path
| otherwise -> withFile path
parseDir :: FileFunc -> FilePath -> IO (Either String Value)
parseDir withFile path = do
ks <- filterDir (getDirectoryContents path)
vs <- fmap sequence (mapM (parsePath withFile) [path </> k | k <- ks])
case vs of
Left s -> return (Left s)
Right vs' ->
return (Right (Object (fromList [ (pack (takeBaseName k), v)
| k <- ks
| v <- vs'
])))