/
Compile.hs
145 lines (123 loc) · 4.61 KB
/
Compile.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
-- |
-- Module : Text.Mustache.Compile
-- Copyright : © 2016–2017 Stack Builders
-- License : BSD 3 clause
--
-- Maintainer : Mark Karpov <markkarpov92@gmail.com>
-- Stability : experimental
-- Portability : portable
--
-- Mustache 'Template' creation from file or a 'Text' value. You don't
-- usually need to import the module, because "Text.Mustache" re-exports
-- everything you may need, import that module instead.
{-# LANGUAGE CPP #-}
module Text.Mustache.Compile
( compileMustacheDir
, compileMustacheDir'
, getMustacheFilesInDir
, getMustacheFilesInDir'
, isMustacheFile
, compileMustacheFile
, compileMustacheText )
where
import Control.Exception
import Control.Monad.Except
import Data.Text (Text)
import Data.Void
import System.Directory
import Text.Megaparsec
import Text.Mustache.Parser
import Text.Mustache.Type
import qualified Data.Map as M
import qualified Data.Text as T
import qualified Data.Text.IO as T
import qualified System.FilePath as F
-- | 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.
--
-- The action can throw the same exceptions as 'getDirectoryContents', and
-- 'T.readFile'.
--
-- > compileMustacheDir = complieMustacheDir' isMustacheFile
compileMustacheDir :: MonadIO m
=> PName -- ^ Which template to select after compiling
-> FilePath -- ^ Directory with templates
-> m Template -- ^ The resulting template
compileMustacheDir = compileMustacheDir' isMustacheFile
-- | The same as 'compileMustacheDir', but allows using a custom predicate
-- for template selection.
--
-- @since 1.2.0
compileMustacheDir' :: MonadIO m
=> (FilePath -> Bool) -- ^ Template selection predicate
-> PName -- ^ Which template to select after compiling
-> FilePath -- ^ Directory with templates
-> m Template -- ^ The resulting template
compileMustacheDir' predicate pname path =
getMustacheFilesInDir' predicate 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.
--
-- @since 0.2.2
getMustacheFilesInDir :: MonadIO m
=> FilePath -- ^ Directory with templates
-> m [FilePath]
getMustacheFilesInDir = getMustacheFilesInDir' isMustacheFile
-- | Return a list of templates found via a predicate in given directory.
-- The returned paths are absolute.
--
-- @since 1.2.0
getMustacheFilesInDir' :: MonadIO m
=> (FilePath -> Bool) -- ^ Mustache file selection predicate
-> FilePath -- ^ Directory with templates
-> m [FilePath]
getMustacheFilesInDir' predicate path = liftIO $
getDirectoryContents path >>=
filterM f . fmap (F.combine path) >>=
mapM makeAbsolute
where
f p = (&& predicate p) <$> doesFileExist p
-- | The default Mustache file predicate.
--
-- @since 1.2.0
isMustacheFile :: FilePath -> Bool
isMustacheFile path = F.takeExtension path == ".mustache"
-- | Compile a single Mustache template and select it.
--
-- The action can throw the same exceptions as 'T.readFile'.
compileMustacheFile :: MonadIO m
=> FilePath -- ^ Location of the file
-> m Template
compileMustacheFile path = liftIO $ do
input <- T.readFile path
withException input (compile input)
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 Char Void) Template -- ^ The result
compileMustacheText pname txt =
Template pname . M.singleton pname <$> parseMustache "" txt
----------------------------------------------------------------------------
-- Helpers
-- | 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
:: Text -- ^ Original input
-> Either (ParseError Char Void) Template -- ^ Value to process
-> IO Template -- ^ The result
withException input = either (throwIO . MustacheParserException input) return