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
|
module Text.Hakyll.Internal.Template
( Template
, fromString
, readTemplate
, substitute
, regularSubstitute
, finalSubstitute
) where
import qualified Data.Map as M
import Data.List (isPrefixOf)
import Data.Char (isAlphaNum)
import Data.Binary
import Control.Monad (liftM, liftM2, replicateM)
import Control.Applicative ((<$>))
import Data.Maybe (fromMaybe)
import System.FilePath ((</>))
import Control.Monad.Reader (liftIO)
import Test.QuickCheck
import Text.Hakyll.Hakyll (Hakyll)
import Text.Hakyll.Context (Context)
import Text.Hakyll.Internal.Cache
-- | Datatype used for template substitutions.
data Template = Chunk String Template
| Identifier String Template
| EscapeCharacter Template
| End
deriving (Show, Read, Eq)
-- | Construct a @Template@ from a string.
fromString :: String -> Template
fromString [] = End
fromString string
| "$$" `isPrefixOf` string = EscapeCharacter (fromString $ tail tail')
| "$" `isPrefixOf` string = let (key, rest) = span isAlphaNum tail'
in Identifier key (fromString rest)
| otherwise = let (chunk, rest) = break (== '$') string
in Chunk chunk (fromString rest)
where
tail' = tail string
-- | Read a @Template@ from a file. This function might fetch the @Template@
-- from the cache, if available.
readTemplate :: FilePath -> Hakyll Template
readTemplate path = do
isCacheMoreRecent' <- isCacheMoreRecent fileName [path]
if isCacheMoreRecent' then getFromCache fileName
else do content <- liftIO $ readFile path
let template = fromString content
storeInCache template fileName
return template
where
fileName = "templates" </> path
-- | Substitutes @$identifiers@ in the given @Template@ by values from the given
-- "Context". When a key is not found, it is left as it is. You can specify
-- the characters used to replace escaped dollars (@$$@) here.
substitute :: String -> Template -> Context -> String
substitute escaper (Chunk chunk template) context =
chunk ++ substitute escaper template context
substitute escaper (Identifier key template) context =
replacement ++ substitute escaper template context
where
replacement = fromMaybe ('$' : key) $ M.lookup key context
substitute escaper (EscapeCharacter template) context =
escaper ++ substitute escaper template context
substitute _ End _ = []
-- | @substitute@ for use during a chain. This will leave escaped characters as
-- they are.
regularSubstitute :: Template -> Context -> String
regularSubstitute = substitute "$$"
-- | @substitute@ for the end of a chain (just before writing). This renders
-- escaped characters.
finalSubstitute :: Template -> Context -> String
finalSubstitute = substitute "$"
instance Binary Template where
put (Chunk string template) = put (0 :: Word8) >> put string >> put template
put (Identifier key template) = put (1 :: Word8) >> put key >> put template
put (EscapeCharacter template) = put (2 :: Word8) >> put template
put (End) = put (3 :: Word8)
get = do tag <- getWord8
case tag of 0 -> liftM2 Chunk get get
1 -> liftM2 Identifier get get
2 -> liftM EscapeCharacter get
3 -> return End
_ -> error "Error reading template"
-- | Generate arbitrary templates from a given length.
arbitraryTemplate :: Int -> Gen Template
arbitraryTemplate 0 = return End
arbitraryTemplate length' = oneof [ do chunk <- chunk'
Chunk chunk <$> template'
, do key <- key'
Identifier key <$> template'
, EscapeCharacter <$> template'
]
where
template' = arbitraryTemplate (length' - 1)
-- Generate keys.
key' = do l <- choose (5, 10)
replicateM l $ choose ('a', 'z')
-- Generate non-empty chunks.
chunk' = do string <- arbitrary
let sanitized = filter (/= '$') string
return $ if null sanitized then "foo"
else sanitized
-- | Make @Template@ testable.
instance Arbitrary Template where
arbitrary = choose (0, 20) >>= arbitraryTemplate
shrink (Chunk chunk template) = [template, Chunk chunk End]
shrink (Identifier key template) = [template, Identifier key End]
shrink (EscapeCharacter template) = [template, EscapeCharacter End]
shrink End = []
|