-- | -- Module : Text.Microstache.Compile -- Copyright : © 2016–2017 Stack Builders -- License : BSD 3 clause -- -- Maintainer : Mark Karpov -- Stability : experimental -- Portability : portable -- -- Mustache 'Template' creation from file or a 'Text' value. You don't -- usually need to import the module, because "Text.Microstache" re-exports -- everything you may need, import that module instead. {-# LANGUAGE CPP #-} module Text.Microstache.Compile ( compileMustacheDir , getMustacheFilesInDir , compileMustacheFile , compileMustacheText ) where import Control.Exception (throwIO) import Control.Monad (foldM, filterM) import Data.Text.Lazy (Text) import System.Directory import Text.Parsec import Text.Microstache.Parser import Text.Microstache.Type import qualified Data.Map as M import qualified Data.Text as T import qualified Data.Text.Lazy.IO as TL import qualified System.FilePath as F #if !MIN_VERSION_base(4,8,0) import Control.Applicative ((<$>)) #endif -- | Compile all templates in specified directory and select one. Template -- files should have extension @mustache@, (e.g. @foo.mustache@) to be -- recognized. This function /does not/ scan the directory recursively. -- -- The action can throw the same exceptions as 'getDirectoryContents', and -- 'T.readFile'. compileMustacheDir :: PName -- ^ Which template to select after compiling -> FilePath -- ^ Directory with templates -> IO Template -- ^ The resulting template compileMustacheDir pname path = getMustacheFilesInDir path >>= fmap selectKey . foldM f (Template undefined M.empty) where selectKey t = t { templateActual = pname } f (Template _ old) fp = do Template _ new <- compileMustacheFile fp return (Template undefined (M.union new old)) -- | Return a list of templates found in given directory. The returned paths -- are absolute. getMustacheFilesInDir :: FilePath -- ^ Directory with templates -> IO [FilePath] getMustacheFilesInDir path = (getDirectoryContents path) >>= filterM isMustacheFile . fmap (F.combine path) >>= mapM makeAbsolute' -- | Compile single Mustache template and select it. -- -- The action can throw the same exceptions as 'T.readFile'. compileMustacheFile :: FilePath -- ^ Location of the file -> IO Template compileMustacheFile path = (TL.readFile path) >>= withException . compile where pname = pathToPName path compile = fmap (Template pname . M.singleton pname) . parseMustache path -- | Compile Mustache template from a lazy 'Text' value. The cache will -- contain only this template named according to given 'PName'. compileMustacheText :: PName -- ^ How to name the template? -> Text -- ^ The template to compile -> Either ParseError Template -- ^ The result compileMustacheText pname txt = Template pname . M.singleton pname <$> parseMustache "" txt ---------------------------------------------------------------------------- -- Helpers -- | Check if given 'FilePath' points to a mustache file. isMustacheFile :: FilePath -> IO Bool isMustacheFile path = do exists <- doesFileExist path let rightExtension = F.takeExtension path == ".mustache" return (exists && rightExtension) -- | Build a 'PName' from given 'FilePath'. pathToPName :: FilePath -> PName pathToPName = PName . T.pack . F.takeBaseName -- | Throw 'MustacheException' if argument is 'Left' or return the result -- inside 'Right'. withException :: Either ParseError Template -- ^ Value to process -> IO Template -- ^ The result withException = either (throwIO . MustacheParserException) return makeAbsolute' :: FilePath -> IO FilePath makeAbsolute' path0 = fmap (matchTrailingSeparator path0 . F.normalise) (prependCurrentDirectory path0) where prependCurrentDirectory :: FilePath -> IO FilePath prependCurrentDirectory path = if F.isRelative path -- avoid the call to `getCurrentDirectory` if we can then (F. path) <$> getCurrentDirectory else return path matchTrailingSeparator :: FilePath -> FilePath -> FilePath matchTrailingSeparator path | F.hasTrailingPathSeparator path = F.addTrailingPathSeparator | otherwise = F.dropTrailingPathSeparator