-- | -- Module : Text.Mustache.Compile.TH -- Copyright : © 2016–present Stack Builders -- License : BSD 3 clause -- -- Maintainer : Mark Karpov -- Stability : experimental -- Portability : portable -- -- Template Haskell helpers to compile Mustache templates at compile time. -- This module is not imported as part of "Text.Mustache", so you need to -- import it yourself. Qualified import is recommended, but not necessary. -- -- At the moment, functions in this module only work with GHC 8 (they -- require at least @template-haskell-2.11@). {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TemplateHaskell #-} module Text.Mustache.Compile.TH ( compileMustacheDir , compileMustacheDir' , compileMustacheFile , compileMustacheText , mustache ) where import Control.Exception import Data.Text (Text) import Language.Haskell.TH hiding (Dec) import Language.Haskell.TH.Quote (QuasiQuoter (..)) import Language.Haskell.TH.Syntax (lift, addDependentFile) import System.Directory import Text.Mustache.Type import qualified Data.Text as T import qualified Text.Mustache.Compile as C -- | Compile all templates in specified directory and select one. Template -- files should have the extension @mustache@, (e.g. @foo.mustache@) to be -- recognized. This function /does not/ scan the directory recursively. -- -- This version compiles the templates at compile time. -- -- > compileMustacheDir = compileMustacheDir' isMustacheFile compileMustacheDir :: PName -- ^ Which template to select after compiling -> FilePath -- ^ Directory with templates -> Q Exp -- ^ The resulting template compileMustacheDir = compileMustacheDir' C.isMustacheFile -- | The same as 'compileMustacheDir', but allows using a custom predicate -- for template selection. -- -- This version compiles the templates at compile time. -- -- @since 1.2.0 compileMustacheDir' :: (FilePath -> Bool) -- ^ Template selection predicate -> PName -- ^ Which template to select after compiling -> FilePath -- ^ Directory with templates -> Q Exp -- ^ The resulting template compileMustacheDir' predicate pname path = do runIO (C.getMustacheFilesInDir' predicate path) >>= mapM_ addDependentFile (runIO . try) (C.compileMustacheDir' predicate pname path) >>= handleEither -- | Compile single Mustache template and select it. -- -- This version compiles the template at compile time. compileMustacheFile :: FilePath -- ^ Location of the file -> Q Exp compileMustacheFile path = do runIO (makeAbsolute path) >>= addDependentFile (runIO . try) (C.compileMustacheFile path) >>= handleEither -- | Compile Mustache template from 'Text' value. The cache will contain -- only this template named according to given 'Key'. -- -- This version compiles the template at compile time. compileMustacheText :: PName -- ^ How to name the template? -> Text -- ^ The template to compile -> Q Exp compileMustacheText pname text = (handleEither . either (Left . MustacheParserException) Right) (C.compileMustacheText pname text) -- | Compile Mustache using QuasiQuoter. Usage: -- -- > {-# LANGUAGE QuasiQuotes #-} -- > import Text.Mustache.Compile.TH (mustache) -- > -- > foo :: Template -- > foo = [mustache|This is my inline {{ template }}.|] -- -- Name of created partial is set to @"quasi-quoted"@. You can extend cache -- of 'Template' created this way using @('Data.Semigroup.<>')@ and so work -- with partials as usual. -- -- @since 0.1.7 mustache :: QuasiQuoter mustache = QuasiQuoter { quoteExp = compileMustacheText "quasi-quoted" . T.pack , quotePat = error "This usage is not supported." , quoteType = error "This usage is not supported." , quoteDec = error "This usage is not supported." } -- | Given an 'Either' result return 'Right' and signal pretty-printed error -- if we have a 'Left'. handleEither :: Either MustacheException Template -> Q Exp handleEither val = case val of Left err -> (fail . indentNicely . displayException) err Right template -> lift template where -- NOTE Since the feature requires GHC 8 anyway, we follow the -- indentation style of that version of compiler. This makes it look -- consistent with other error messages and allows Emacs and similar -- tools to parse the errors correctly. indentNicely x' = case lines x' of [] -> "" (x:xs) -> unlines (x : fmap (replicate 8 ' ' ++) xs)