on master help sign in
.circleci/config.yml+5−11
since splitsnapshots
unifiedsplit
@@ -6,20 +6,14 @@ jobs:
6 steps:
6 steps:
7 - checkout
7 - checkout
8 - restore_cache:
8 - restore_cache:
9− key: 'v2-hakyll-{{ arch }}-{{ .Branch }}'
9+ key: 'v3-hakyll-{{ arch }}-{{ .Branch }}'
10− - run:
11− name: 'Update cabal indices'
12− command: 'cabal update'
13 - run:
10 - run:
14 # We set jobs to 1 here because that prevents Out-Of-Memory exceptions
11 # We set jobs to 1 here because that prevents Out-Of-Memory exceptions
15 # while compiling dependencies.
12 # while compiling dependencies.
16 name: 'Install dependencies'
13 name: 'Install dependencies'
17− command: '.circleci/tickle.sh cabal install --only-dependencies --enable-tests --jobs=1 --flags="+buildWebsite"'
14+ command: '.circleci/tickle.sh stack build --test --copy-bins --jobs=1'
18− - run:
19− name: 'Build and run tests'
20− command: 'cabal test'
21 - save_cache:
15 - save_cache:
22− key: 'v2-hakyll-{{ arch }}-{{ .Branch }}-{{ .Revision }}'
16+ key: 'v3-hakyll-{{ arch }}-{{ .Branch }}-{{ .Revision }}'
23 paths:
17 paths:
24− - '~/.cabal'
18+ - '~/.stack-work'
25− - '~/.ghc'
19+ - '~/.stack'
CHANGELOG.md+85
@@ -4,6 +4,91 @@ title: Releases
4
4
5# Releases
5# Releases
6
6
7+## Hakyll 4.13.0.0 (2019-08-30)
8+
9+- Improved documentation in many places (contribution by Bergi)
10+- Significantly improve error messages when reading and applying templates
11+ (contribution by Bergi)
12+ * `empty` and `fail` for `Compiler` now fail without or with a message,
13+ allowing for much better debug output
14+ * `renderFeed`, `renderRssWithTemplates` and `renderAtomWithTemplates` now
15+ take `Template` rather than `String` arguments
16+- Add option to specify date in directory structure,e.g.
17+ `posts/2019/05/10/tomorrow.md` (contribution by Taeer Bar-Ya)
18+- Bump QuickCheck to 2.13
19+
20+## Hakyll 4.12.5.2 (2019-05-09)
21+
22+- Bump pandoc to 2.7
23+- Add `srcset` to the list of URL attributes (contribution by c50a326)
24+- Expose `getCategory` in `Hakyll.Web.Tags` (contribution by Ng Wei En)
25+- Fix issue where `published` overwrites the user's context (contribution by
26+ ncaq)
27+
28+## Hakyll 4.12.5.1 (2019-02-03)
29+
30+- Bump pandoc to 2.6
31+- Bump pandoc-citeproc to 0.16
32+
33+## Hakyll 4.12.5.0 (2019-01-12)
34+
35+- Update dependencies (contribution by Hexirp):
36+ * Bump containers to 0.6
37+ * Bump yaml to 0.11
38+ * Bump pandoc to 2.5
39+ * Bump pandoc-citeproc to 0.15
40+ * Bump tasty to 1.2
41+- Correct assertion in unixFilterError test
42+ (contribution by Jim McStanton)
43+- Add renderRssWithTemplates, renderAtomWithTemplates
44+ (contribution by Abhinav Sarkar)
45+- Speed up hashing in cache (contribution by 0xd34df00d)
46+- Update type of fromFilePath to use FilePath instead of String
47+ (contribution by Jim McStanton)
48+- Drop extension when parsing dates in filepaths (contribution by
49+ Gabriel Aumala)
50+
51+## Hakyll 4.12.4.0 (2018-08-13)
52+
53+- Bump yaml to 0.10
54+- Bump file-embed to 0.0.11
55+- Bump QuickCheck to 2.12
56+- Use makeRelativeToProject with embedFile for stack ghci (contribution by
57+ Michael Sloan)
58+
59+## Hakyll 4.12.3.0 (2018-05-29)
60+
61+- Bump tasty to 1.1
62+- Bump fsnotify to 0.3
63+
64+## Hakyll 4.12.2.0
65+
66+- Bump pandoc to 2.2
67+- Fix path resolution in Pandoc.Biblio (contribution by knih)
68+
69+## Hakyll 4.12.1.0
70+
71+- Fix hakyll-init on older GHC versions
72+- Make the Pandoc dependency optional
73+
74+## Hakyll 4.12.0.1
75+
76+- Bump resourcet to 1.2 in test section
77+
78+## Hakyll 4.12.0.0
79+
80+- Add Semigroup instances for existing Monoids (contribution by
81+ Christian Barcenas)
82+- Fix issue with CPP and comment containing `/*`
83+- Add `withTagList` (contribution by Oleg Grenrus)
84+- Improve CSS compression (contribution by Bergi)
85+- Bump pandoc-citeproc to 0.14
86+- Bump resourcet to 1.2
87+- Bump time to 1.9
88+- Bump http-types to 0.12
89+- Bump http-conduit to 2.3
90+- Bump tasty-quickcheck to 0.10
91+
7## Hakyll 4.11.0.0
92## Hakyll 4.11.0.0
8
93
9- Bump binary to 0.9
94- Bump binary to 0.9
Makefile+6−6
@@ -1,9 +1,9 @@
1+PACKAGE="$(shell stack list-dependencies --separator='-' | grep hakyll)"
2+LOCAL_DOC_ROOT="$(shell stack path --local-doc-root)"
3+
1# Generate the docs and copy them to the website dir
4# Generate the docs and copy them to the website dir
2haddock:
5haddock:
3− cabal haddock --hyperlink-source
6+ stack build --haddock --no-haddock-deps
4− rm -rf web/reference/
7+ rsync -r "$(LOCAL_DOC_ROOT)/$(PACKAGE)/" web/reference/
5− cp -r dist/doc/html/hakyll/ web/reference/
6
8
7−# Run the tests
9+.PHONY: haddock
8−test:
9− runghc -isrc -itests tests/TestSuite.hs
hakyll.cabal+46−63
@@ -1,5 +1,5 @@
1Name: hakyll
1Name: hakyll
2−Version: 4.11.0.0
2+Version: 4.13.0.0
3
3
4Synopsis: A static website compiler library
4Synopsis: A static website compiler library
5Description:
5Description:
@@ -68,6 +68,7 @@ Extra-source-files:
68 tests/data/partial.html
68 tests/data/partial.html
69 tests/data/partial.html.out
69 tests/data/partial.html.out
70 tests/data/posts/2010-08-26-birthday.md
70 tests/data/posts/2010-08-26-birthday.md
71+ tests/data/posts/2018-09-26.md
71 tests/data/russian.md
72 tests/data/russian.md
72 tests/data/strip.html
73 tests/data/strip.html
73 tests/data/strip.html.out
74 tests/data/strip.html.out
@@ -99,6 +100,11 @@ Flag buildWebsite
99 Default: False
100 Default: False
100 Manual: True
101 Manual: True
101
102
103+Flag usePandoc
104+ Description: Include Pandoc support
105+ Default: True
106+ Manual: True
107+
102Library
108Library
103 Ghc-Options: -Wall
109 Ghc-Options: -Wall
104 Hs-Source-Dirs: lib
110 Hs-Source-Dirs: lib
@@ -134,9 +140,6 @@ Library
134 Hakyll.Web.Html
140 Hakyll.Web.Html
135 Hakyll.Web.Html.RelativizeUrls
141 Hakyll.Web.Html.RelativizeUrls
136 Hakyll.Web.Paginate
142 Hakyll.Web.Paginate
137− Hakyll.Web.Pandoc
138− Hakyll.Web.Pandoc.Biblio
139− Hakyll.Web.Pandoc.FileType
140 Hakyll.Web.Redirect
143 Hakyll.Web.Redirect
141 Hakyll.Web.Tags
144 Hakyll.Web.Tags
142 Hakyll.Web.Template
145 Hakyll.Web.Template
@@ -151,11 +154,11 @@ Library
151 Data.Yaml.Extended
154 Data.Yaml.Extended
152 Hakyll.Check
155 Hakyll.Check
153 Hakyll.Core.Compiler.Require
156 Hakyll.Core.Compiler.Require
157+ Hakyll.Core.Identifier.Pattern.Internal
154 Hakyll.Core.Item.SomeItem
158 Hakyll.Core.Item.SomeItem
155 Hakyll.Core.Provider.Internal
159 Hakyll.Core.Provider.Internal
156 Hakyll.Core.Provider.MetadataCache
160 Hakyll.Core.Provider.MetadataCache
157 Hakyll.Core.Util.Parser
161 Hakyll.Core.Util.Parser
158− Hakyll.Web.Pandoc.Binary
159 Paths_hakyll
162 Paths_hakyll
160
163
161 Build-Depends:
164 Build-Depends:
@@ -164,42 +167,39 @@ Library
164 blaze-html >= 0.5 && < 0.10,
167 blaze-html >= 0.5 && < 0.10,
165 blaze-markup >= 0.5.1 && < 0.9,
168 blaze-markup >= 0.5.1 && < 0.9,
166 bytestring >= 0.9 && < 0.11,
169 bytestring >= 0.9 && < 0.11,
167− containers >= 0.3 && < 0.6,
170+ containers >= 0.3 && < 0.7,
168 cryptohash >= 0.7 && < 0.12,
171 cryptohash >= 0.7 && < 0.12,
169 data-default >= 0.4 && < 0.8,
172 data-default >= 0.4 && < 0.8,
170 deepseq >= 1.3 && < 1.5,
173 deepseq >= 1.3 && < 1.5,
171 directory >= 1.0 && < 1.4,
174 directory >= 1.0 && < 1.4,
175+ file-embed >= 0.0.10.1 && < 0.0.12,
172 filepath >= 1.0 && < 1.5,
176 filepath >= 1.0 && < 1.5,
173 lrucache >= 1.1.1 && < 1.3,
177 lrucache >= 1.1.1 && < 1.3,
174 mtl >= 1 && < 2.3,
178 mtl >= 1 && < 2.3,
175− network >= 2.6 && < 2.7,
176 network-uri >= 2.6 && < 2.7,
179 network-uri >= 2.6 && < 2.7,
177− pandoc >= 2.0.5 && < 2.2,
180+ optparse-applicative >= 0.12 && < 0.15,
178− pandoc-citeproc >= 0.13 && < 0.14,
179 parsec >= 3.0 && < 3.2,
181 parsec >= 3.0 && < 3.2,
180 process >= 1.6 && < 1.7,
182 process >= 1.6 && < 1.7,
181 random >= 1.0 && < 1.2,
183 random >= 1.0 && < 1.2,
182− regex-base >= 0.93 && < 0.94,
183 regex-tdfa >= 1.1 && < 1.3,
184 regex-tdfa >= 1.1 && < 1.3,
184− resourcet >= 1.1 && < 1.2,
185+ resourcet >= 1.1 && < 1.3,
185 scientific >= 0.3.4 && < 0.4,
186 scientific >= 0.3.4 && < 0.4,
186 tagsoup >= 0.13.1 && < 0.15,
187 tagsoup >= 0.13.1 && < 0.15,
188+ template-haskell >= 2.14 && < 2.15,
187 text >= 0.11 && < 1.3,
189 text >= 0.11 && < 1.3,
188− time >= 1.8 && < 1.9,
190+ time >= 1.8 && < 1.10,
189 time-locale-compat >= 0.1 && < 0.2,
191 time-locale-compat >= 0.1 && < 0.2,
190 unordered-containers >= 0.2 && < 0.3,
192 unordered-containers >= 0.2 && < 0.3,
191 vector >= 0.11 && < 0.13,
193 vector >= 0.11 && < 0.13,
192− yaml >= 0.8.11 && < 0.9,
194+ yaml >= 0.8.11 && < 0.12
193− optparse-applicative >= 0.12 && < 0.15,
194− file-embed >= 0.0.10.1 && < 0.0.11
195
195
196 If flag(previewServer)
196 If flag(previewServer)
197 Build-depends:
197 Build-depends:
198 wai >= 3.2 && < 3.3,
198 wai >= 3.2 && < 3.3,
199 warp >= 3.2 && < 3.3,
199 warp >= 3.2 && < 3.3,
200 wai-app-static >= 3.1 && < 3.2,
200 wai-app-static >= 3.1 && < 3.2,
201− http-types >= 0.9 && < 0.12,
201+ http-types >= 0.9 && < 0.13,
202− fsnotify >= 0.2 && < 0.3
202+ fsnotify >= 0.2 && < 0.4
203 Cpp-options:
203 Cpp-options:
204 -DPREVIEW_SERVER
204 -DPREVIEW_SERVER
205 Other-modules:
205 Other-modules:
@@ -208,7 +208,7 @@ Library
208
208
209 If flag(watchServer)
209 If flag(watchServer)
210 Build-depends:
210 Build-depends:
211− fsnotify >= 0.2 && < 0.3
211+ fsnotify >= 0.2 && < 0.4
212 Cpp-options:
212 Cpp-options:
213 -DWATCH_SERVER
213 -DWATCH_SERVER
214 Other-modules:
214 Other-modules:
@@ -216,11 +216,24 @@ Library
216
216
217 If flag(checkExternal)
217 If flag(checkExternal)
218 Build-depends:
218 Build-depends:
219− http-conduit >= 2.2 && < 2.3,
219+ http-conduit >= 2.2 && < 2.4,
220− http-types >= 0.7 && < 0.12
220+ http-types >= 0.7 && < 0.13
221 Cpp-options:
221 Cpp-options:
222 -DCHECK_EXTERNAL
222 -DCHECK_EXTERNAL
223
223
224+ If flag(usePandoc)
225+ Exposed-Modules:
226+ Hakyll.Web.Pandoc
227+ Hakyll.Web.Pandoc.Biblio
228+ Hakyll.Web.Pandoc.FileType
229+ Other-Modules:
230+ Hakyll.Web.Pandoc.Binary
231+ Build-Depends:
232+ pandoc >= 2.0.5 && < 2.8,
233+ pandoc-citeproc >= 0.14 && < 0.17
234+ Cpp-options:
235+ -DUSE_PANDOC
236+
224Test-suite hakyll-tests
237Test-suite hakyll-tests
225 Type: exitcode-stdio-1.0
238 Type: exitcode-stdio-1.0
226 Hs-source-dirs: tests
239 Hs-source-dirs: tests
@@ -242,74 +255,44 @@ Test-suite hakyll-tests
242 Hakyll.Web.CompressCss.Tests
255 Hakyll.Web.CompressCss.Tests
243 Hakyll.Web.Html.RelativizeUrls.Tests
256 Hakyll.Web.Html.RelativizeUrls.Tests
244 Hakyll.Web.Html.Tests
257 Hakyll.Web.Html.Tests
245− Hakyll.Web.Pandoc.FileType.Tests
258+ Hakyll.Web.Tags.Tests
246 Hakyll.Web.Template.Context.Tests
259 Hakyll.Web.Template.Context.Tests
247 Hakyll.Web.Template.Tests
260 Hakyll.Web.Template.Tests
248 TestSuite.Util
261 TestSuite.Util
249
262
250 Build-Depends:
263 Build-Depends:
251 hakyll,
264 hakyll,
252− QuickCheck >= 2.8 && < 2.12,
265+ QuickCheck >= 2.8 && < 2.14,
253− tasty >= 0.11 && < 1.1,
266+ tasty >= 0.11 && < 1.3,
254 tasty-hunit >= 0.9 && < 0.11,
267 tasty-hunit >= 0.9 && < 0.11,
255− tasty-quickcheck >= 0.8 && < 0.10,
268+ tasty-quickcheck >= 0.8 && < 0.11,
256 -- Copy pasted from hakyll dependencies:
269 -- Copy pasted from hakyll dependencies:
257 base >= 4.8 && < 5,
270 base >= 4.8 && < 5,
258− binary >= 0.5 && < 0.10,
259− blaze-html >= 0.5 && < 0.10,
260− blaze-markup >= 0.5.1 && < 0.9,
261 bytestring >= 0.9 && < 0.11,
271 bytestring >= 0.9 && < 0.11,
262− containers >= 0.3 && < 0.6,
272+ containers >= 0.3 && < 0.7,
263− cryptohash >= 0.7 && < 0.12,
264− data-default >= 0.4 && < 0.8,
265− deepseq >= 1.3 && < 1.5,
266− directory >= 1.0 && < 1.4,
267 filepath >= 1.0 && < 1.5,
273 filepath >= 1.0 && < 1.5,
268− lrucache >= 1.1.1 && < 1.3,
269− mtl >= 1 && < 2.3,
270− network >= 2.6 && < 2.7,
271− network-uri >= 2.6 && < 2.7,
272− pandoc >= 2.0.5 && < 2.2,
273− pandoc-citeproc >= 0.13 && < 0.14,
274− parsec >= 3.0 && < 3.2,
275− process >= 1.6 && < 1.7,
276− random >= 1.0 && < 1.2,
277− regex-base >= 0.93 && < 0.94,
278− regex-tdfa >= 1.1 && < 1.3,
279− resourcet >= 1.1 && < 1.2,
280− scientific >= 0.3.4 && < 0.4,
281− tagsoup >= 0.13.1 && < 0.15,
282 text >= 0.11 && < 1.3,
274 text >= 0.11 && < 1.3,
283− time >= 1.8 && < 1.9,
284− time-locale-compat >= 0.1 && < 0.2,
285 unordered-containers >= 0.2 && < 0.3,
275 unordered-containers >= 0.2 && < 0.3,
286− vector >= 0.11 && < 0.13,
287− yaml >= 0.8.11 && < 0.9,
276+ yaml >= 0.8.11 && < 0.12
288− optparse-applicative >= 0.12 && < 0.15
289
277
290 If flag(previewServer)
278 If flag(previewServer)
291− Build-depends:
292− wai >= 3.2 && < 3.3,
293− warp >= 3.2 && < 3.3,
294− wai-app-static >= 3.1 && < 3.2,
295− http-types >= 0.9 && < 0.12,
296− fsnotify >= 0.2 && < 0.3
297 Cpp-options:
279 Cpp-options:
298 -DPREVIEW_SERVER
280 -DPREVIEW_SERVER
299
281
300 If flag(watchServer)
282 If flag(watchServer)
301− Build-depends:
302− fsnotify >= 0.2 && < 0.3
303 Cpp-options:
283 Cpp-options:
304 -DWATCH_SERVER
284 -DWATCH_SERVER
305
285
306 If flag(checkExternal)
286 If flag(checkExternal)
307− Build-depends:
308− http-conduit >= 2.2 && < 2.3,
309− http-types >= 0.7 && < 0.12
310 Cpp-options:
287 Cpp-options:
311 -DCHECK_EXTERNAL
288 -DCHECK_EXTERNAL
312
289
290+ If flag(usePandoc)
291+ Other-modules:
292+ Hakyll.Web.Pandoc.FileType.Tests
293+ Cpp-options:
294+ -DUSE_PANDOC
295+
313Executable hakyll-init
296Executable hakyll-init
314 Main-is: Init.hs
297 Main-is: Init.hs
315 Ghc-options: -Wall -threaded
298 Ghc-options: -Wall -threaded
@@ -341,4 +324,4 @@ Executable hakyll-website
341 base >= 4 && < 5,
324 base >= 4 && < 5,
342 directory >= 1.0 && < 1.4,
325 directory >= 1.0 && < 1.4,
343 filepath >= 1.0 && < 1.5,
326 filepath >= 1.0 && < 1.5,
344− pandoc >= 2.0.5 && < 2.2
327+ pandoc >= 2.0.5 && < 2.8
lib/Hakyll.hs+5−1
@@ -20,10 +20,12 @@ module Hakyll
20 , module Hakyll.Web.Feed
20 , module Hakyll.Web.Feed
21 , module Hakyll.Web.Html
21 , module Hakyll.Web.Html
22 , module Hakyll.Web.Html.RelativizeUrls
22 , module Hakyll.Web.Html.RelativizeUrls
23− , module Hakyll.Web.Pandoc
24 , module Hakyll.Web.Paginate
23 , module Hakyll.Web.Paginate
24+#ifdef USE_PANDOC
25+ , module Hakyll.Web.Pandoc
25 , module Hakyll.Web.Pandoc.Biblio
26 , module Hakyll.Web.Pandoc.Biblio
26 , module Hakyll.Web.Pandoc.FileType
27 , module Hakyll.Web.Pandoc.FileType
28+#endif
27 , module Hakyll.Web.Redirect
29 , module Hakyll.Web.Redirect
28 , module Hakyll.Web.Tags
30 , module Hakyll.Web.Tags
29 , module Hakyll.Web.Template
31 , module Hakyll.Web.Template
@@ -52,9 +54,11 @@ import Hakyll.Web.Feed
52import Hakyll.Web.Html
54import Hakyll.Web.Html
53import Hakyll.Web.Html.RelativizeUrls
55import Hakyll.Web.Html.RelativizeUrls
54import Hakyll.Web.Paginate
56import Hakyll.Web.Paginate
57+#ifdef USE_PANDOC
55import Hakyll.Web.Pandoc
58import Hakyll.Web.Pandoc
56import Hakyll.Web.Pandoc.Biblio
59import Hakyll.Web.Pandoc.Biblio
57import Hakyll.Web.Pandoc.FileType
60import Hakyll.Web.Pandoc.FileType
61+#endif
58import Hakyll.Web.Redirect
62import Hakyll.Web.Redirect
59import Hakyll.Web.Tags
63import Hakyll.Web.Tags
60import Hakyll.Web.Template
64import Hakyll.Web.Template
lib/Hakyll/Check.hs+13
@@ -19,6 +19,9 @@ import Control.Monad.Trans (liftIO)
19import Control.Monad.Trans.Resource (runResourceT)
19import Control.Monad.Trans.Resource (runResourceT)
20import Data.List (isPrefixOf)
20import Data.List (isPrefixOf)
21import qualified Data.Map.Lazy as Map
21import qualified Data.Map.Lazy as Map
22+#if MIN_VERSION_base(4,9,0)
23+import Data.Semigroup (Semigroup (..))
24+#endif
22import Network.URI (unEscapeString)
25import Network.URI (unEscapeString)
23import System.Directory (doesDirectoryExist,
26import System.Directory (doesDirectoryExist,
24 doesFileExist)
27 doesFileExist)
@@ -85,10 +88,20 @@ data CheckerWrite = CheckerWrite
85
88
86
89
87--------------------------------------------------------------------------------
90--------------------------------------------------------------------------------
91+#if MIN_VERSION_base(4,9,0)
92+instance Semigroup CheckerWrite where
93+ (<>) (CheckerWrite f1 o1) (CheckerWrite f2 o2) =
94+ CheckerWrite (f1 + f2) (o1 + o2)
95+
96+instance Monoid CheckerWrite where
97+ mempty = CheckerWrite 0 0
98+ mappend = (<>)
99+#else
88instance Monoid CheckerWrite where
100instance Monoid CheckerWrite where
89 mempty = CheckerWrite 0 0
101 mempty = CheckerWrite 0 0
90 mappend (CheckerWrite f1 o1) (CheckerWrite f2 o2) =
102 mappend (CheckerWrite f1 o1) (CheckerWrite f2 o2) =
91 CheckerWrite (f1 + f2) (o1 + o2)
103 CheckerWrite (f1 + f2) (o1 + o2)
104+#endif
92
105
93
106
94--------------------------------------------------------------------------------
107--------------------------------------------------------------------------------
lib/Hakyll/Core/Compiler.hs+35−2
@@ -24,13 +24,16 @@ module Hakyll.Core.Compiler
24 , cached
24 , cached
25 , unsafeCompiler
25 , unsafeCompiler
26 , debugCompiler
26 , debugCompiler
27+ , noResult
28+ , withErrorMessage
27 ) where
29 ) where
28
30
29
31
30--------------------------------------------------------------------------------
32--------------------------------------------------------------------------------
31−import Control.Monad (when, unless)
33+import Control.Monad (unless, when, (>=>))
32import Data.Binary (Binary)
34import Data.Binary (Binary)
33import Data.ByteString.Lazy (ByteString)
35import Data.ByteString.Lazy (ByteString)
36+import qualified Data.List.NonEmpty as NonEmpty
34import Data.Typeable (Typeable)
37import Data.Typeable (Typeable)
35import System.Environment (getProgName)
38import System.Environment (getProgName)
36import System.FilePath (takeExtension)
39import System.FilePath (takeExtension)
@@ -62,6 +65,7 @@ getUnderlyingExtension = takeExtension . toFilePath <$> getUnderlying
62
65
63
66
64--------------------------------------------------------------------------------
67--------------------------------------------------------------------------------
68+-- | Create an item from the underlying identifier and a given value.
65makeItem :: a -> Compiler (Item a)
69makeItem :: a -> Compiler (Item a)
66makeItem x = do
70makeItem x = do
67 identifier <- getUnderlying
71 identifier <- getUnderlying
@@ -141,6 +145,10 @@ saveSnapshot snapshot item = do
141
145
142
146
143--------------------------------------------------------------------------------
147--------------------------------------------------------------------------------
148+-- | Turn on caching for a compilation value to avoid recomputing it
149+-- on subsequent Hakyll runs.
150+-- The storage key consists of the underlying identifier of the compiled
151+-- ressource and the given name.
144cached :: (Binary a, Typeable a)
152cached :: (Binary a, Typeable a)
145 => String
153 => String
146 -> Compiler a
154 -> Compiler a
@@ -177,12 +185,37 @@ cached name compiler = do
177
185
178
186
179--------------------------------------------------------------------------------
187--------------------------------------------------------------------------------
188+-- | Run an IO computation without dependencies in a Compiler
180unsafeCompiler :: IO a -> Compiler a
189unsafeCompiler :: IO a -> Compiler a
181unsafeCompiler = compilerUnsafeIO
190unsafeCompiler = compilerUnsafeIO
182
191
183
192
184--------------------------------------------------------------------------------
193--------------------------------------------------------------------------------
194+-- | Fail so that it is treated as non-defined in an @\$if()\$@ branching
195+-- "Hakyll.Web.Template" macro, and alternative
196+-- 'Hakyll.Web.Template.Context.Context's are tried
197+--
198+-- @since 4.13.0
199+noResult :: String -> Compiler a
200+noResult = compilerNoResult . return
201+
202+
203+--------------------------------------------------------------------------------
204+-- | Prepend an error line to the error, if there is one. This allows you to
205+-- add helpful context to error messages.
206+--
207+-- @since 4.13.0
208+withErrorMessage :: String -> Compiler a -> Compiler a
209+withErrorMessage x = do
210+ compilerTry >=> either (compilerResult . CompilerError . prepend) return
211+ where
212+ prepend (CompilationFailure es) = CompilationFailure (x `NonEmpty.cons` es)
213+ prepend (CompilationNoResult es) = CompilationNoResult (x : es)
214+
215+
216+--------------------------------------------------------------------------------
185−-- | Compiler for debugging purposes
217+-- | Compiler for debugging purposes.
218+-- Passes a message to the debug logger that is printed in verbose mode.
186debugCompiler :: String -> Compiler ()
219debugCompiler :: String -> Compiler ()
187debugCompiler msg = do
220debugCompiler msg = do
188 logger <- compilerLogger <$> compilerAsk
221 logger <- compilerLogger <$> compilerAsk
lib/Hakyll/Core/Compiler/Internal.hs+140−41
@@ -1,5 +1,7 @@
1--------------------------------------------------------------------------------
1--------------------------------------------------------------------------------
2-- | Internally used compiler module
2-- | Internally used compiler module
3+{-# LANGUAGE CPP #-}
4+{-# LANGUAGE DeriveFunctor #-}
3{-# LANGUAGE FlexibleInstances #-}
5{-# LANGUAGE FlexibleInstances #-}
4{-# LANGUAGE GADTs #-}
6{-# LANGUAGE GADTs #-}
5{-# LANGUAGE GeneralizedNewtypeDeriving #-}
7{-# LANGUAGE GeneralizedNewtypeDeriving #-}
@@ -9,19 +11,26 @@ module Hakyll.Core.Compiler.Internal
9 Snapshot
11 Snapshot
10 , CompilerRead (..)
12 , CompilerRead (..)
11 , CompilerWrite (..)
13 , CompilerWrite (..)
14+ , CompilerErrors (..)
12 , CompilerResult (..)
15 , CompilerResult (..)
13 , Compiler (..)
16 , Compiler (..)
14 , runCompiler
17 , runCompiler
15
18
16 -- * Core operations
19 -- * Core operations
20+ , compilerResult
17 , compilerTell
21 , compilerTell
18 , compilerAsk
22 , compilerAsk
23+ , compilerUnsafeIO
24+
25+ -- * Error operations
19 , compilerThrow
26 , compilerThrow
27+ , compilerNoResult
20 , compilerCatch
28 , compilerCatch
21− , compilerResult
29+ , compilerTry
22− , compilerUnsafeIO
30+ , compilerErrorMessages
23
31
24 -- * Utilities
32 -- * Utilities
33+ , compilerDebugEntries
25 , compilerTellDependencies
34 , compilerTellDependencies
26 , compilerTellCacheHits
35 , compilerTellCacheHits
27 ) where
36 ) where
@@ -31,7 +40,12 @@ module Hakyll.Core.Compiler.Internal
31import Control.Applicative (Alternative (..))
40import Control.Applicative (Alternative (..))
32import Control.Exception (SomeException, handle)
41import Control.Exception (SomeException, handle)
33import Control.Monad (forM_)
42import Control.Monad (forM_)
34−import Control.Monad.Except (MonadError (..))
43+import Control.Monad.Except (MonadError (..))
44+import Data.List.NonEmpty (NonEmpty (..))
45+import qualified Data.List.NonEmpty as NonEmpty
46+#if MIN_VERSION_base(4,9,0)
47+import Data.Semigroup (Semigroup (..))
48+#endif
35import Data.Set (Set)
49import Data.Set (Set)
36import qualified Data.Set as S
50import qualified Data.Set as S
37
51
@@ -41,7 +55,6 @@ import Hakyll.Core.Configuration
41import Hakyll.Core.Dependencies
55import Hakyll.Core.Dependencies
42import Hakyll.Core.Identifier
56import Hakyll.Core.Identifier
43import Hakyll.Core.Identifier.Pattern
57import Hakyll.Core.Identifier.Pattern
44−import Hakyll.Core.Logger (Logger)
45import qualified Hakyll.Core.Logger as Logger
58import qualified Hakyll.Core.Logger as Logger
46import Hakyll.Core.Metadata
59import Hakyll.Core.Metadata
47import Hakyll.Core.Provider
60import Hakyll.Core.Provider
@@ -71,7 +84,7 @@ data CompilerRead = CompilerRead
71 , -- | Compiler store
84 , -- | Compiler store
72 compilerStore :: Store
85 compilerStore :: Store
73 , -- | Logger
86 , -- | Logger
74− compilerLogger :: Logger
87+ compilerLogger :: Logger.Logger
75 }
88 }
76
89
77
90
@@ -83,18 +96,46 @@ data CompilerWrite = CompilerWrite
83
96
84
97
85--------------------------------------------------------------------------------
98--------------------------------------------------------------------------------
99+#if MIN_VERSION_base(4,9,0)
100+instance Semigroup CompilerWrite where
101+ (<>) (CompilerWrite d1 h1) (CompilerWrite d2 h2) =
102+ CompilerWrite (d1 ++ d2) (h1 + h2)
103+
104+instance Monoid CompilerWrite where
105+ mempty = CompilerWrite [] 0
106+ mappend = (<>)
107+#else
86instance Monoid CompilerWrite where
108instance Monoid CompilerWrite where
87 mempty = CompilerWrite [] 0
109 mempty = CompilerWrite [] 0
88 mappend (CompilerWrite d1 h1) (CompilerWrite d2 h2) =
110 mappend (CompilerWrite d1 h1) (CompilerWrite d2 h2) =
89 CompilerWrite (d1 ++ d2) (h1 + h2)
111 CompilerWrite (d1 ++ d2) (h1 + h2)
112+#endif
90
113
91
114
92--------------------------------------------------------------------------------
115--------------------------------------------------------------------------------
93−data CompilerResult a where
116+-- | Distinguishes reasons in a 'CompilerError'
117+data CompilerErrors a
118+ -- | One or more exceptions occured during compilation
119+ = CompilationFailure (NonEmpty a)
120+ -- | Absence of any result, most notably in template contexts. May still
121+ -- have error messages.
122+ | CompilationNoResult [a]
123+ deriving Functor
124+
125+
126+-- | Unwrap a `CompilerErrors`
127+compilerErrorMessages :: CompilerErrors a -> [a]
128+compilerErrorMessages (CompilationFailure x) = NonEmpty.toList x
129+compilerErrorMessages (CompilationNoResult x) = x
130+
131+
132+--------------------------------------------------------------------------------
133+-- | An intermediate result of a compilation step
134+data CompilerResult a
94− CompilerDone :: a -> CompilerWrite -> CompilerResult a
135+ = CompilerDone a CompilerWrite
95− CompilerSnapshot :: Snapshot -> Compiler a -> CompilerResult a
136+ | CompilerSnapshot Snapshot (Compiler a)
96− CompilerError :: [String] -> CompilerResult a
97− CompilerRequire :: (Identifier, Snapshot) -> Compiler a -> CompilerResult a
137+ | CompilerRequire (Identifier, Snapshot) (Compiler a)
138+ | CompilerError (CompilerErrors String)
98
139
99
140
100--------------------------------------------------------------------------------
141--------------------------------------------------------------------------------
@@ -112,14 +153,14 @@ instance Functor Compiler where
112 return $ case res of
153 return $ case res of
113 CompilerDone x w -> CompilerDone (f x) w
154 CompilerDone x w -> CompilerDone (f x) w
114 CompilerSnapshot s c' -> CompilerSnapshot s (fmap f c')
155 CompilerSnapshot s c' -> CompilerSnapshot s (fmap f c')
115− CompilerError e -> CompilerError e
116 CompilerRequire i c' -> CompilerRequire i (fmap f c')
156 CompilerRequire i c' -> CompilerRequire i (fmap f c')
157+ CompilerError e -> CompilerError e
117 {-# INLINE fmap #-}
158 {-# INLINE fmap #-}
118
159
119
160
120--------------------------------------------------------------------------------
161--------------------------------------------------------------------------------
121instance Monad Compiler where
162instance Monad Compiler where
122− return x = Compiler $ \_ -> return $ CompilerDone x mempty
163+ return x = compilerResult $ CompilerDone x mempty
123 {-# INLINE return #-}
164 {-# INLINE return #-}
124
165
125 Compiler c >>= f = Compiler $ \r -> do
166 Compiler c >>= f = Compiler $ \r -> do
@@ -132,14 +173,14 @@ instance Monad Compiler where
132 CompilerSnapshot s c' -> CompilerSnapshot s $ do
173 CompilerSnapshot s c' -> CompilerSnapshot s $ do
133 compilerTell w -- Save dependencies!
174 compilerTell w -- Save dependencies!
134 c'
175 c'
135− CompilerError e -> CompilerError e
136 CompilerRequire i c' -> CompilerRequire i $ do
176 CompilerRequire i c' -> CompilerRequire i $ do
137 compilerTell w -- Save dependencies!
177 compilerTell w -- Save dependencies!
138 c'
178 c'
179+ CompilerError e -> CompilerError e
139
180
140 CompilerSnapshot s c' -> return $ CompilerSnapshot s (c' >>= f)
181 CompilerSnapshot s c' -> return $ CompilerSnapshot s (c' >>= f)
141− CompilerError e -> return $ CompilerError e
142 CompilerRequire i c' -> return $ CompilerRequire i (c' >>= f)
182 CompilerRequire i c' -> return $ CompilerRequire i (c' >>= f)
183+ CompilerError e -> return $ CompilerError e
143 {-# INLINE (>>=) #-}
184 {-# INLINE (>>=) #-}
144
185
145 fail = compilerThrow . return
186 fail = compilerThrow . return
@@ -156,87 +197,145 @@ instance Applicative Compiler where
156
197
157
198
158--------------------------------------------------------------------------------
199--------------------------------------------------------------------------------
200+-- | Access provided metadata from anywhere
159instance MonadMetadata Compiler where
201instance MonadMetadata Compiler where
160 getMetadata = compilerGetMetadata
202 getMetadata = compilerGetMetadata
161 getMatches = compilerGetMatches
203 getMatches = compilerGetMatches
162
204
163
205
164--------------------------------------------------------------------------------
206--------------------------------------------------------------------------------
207+-- | Compilation may fail with multiple error messages.
208+-- 'catchError' handles errors from 'throwError', 'fail' and 'Hakyll.Core.Compiler.noResult'
165instance MonadError [String] Compiler where
209instance MonadError [String] Compiler where
166− throwError = compilerThrow
210+ throwError = compilerThrow
167− catchError = compilerCatch
211+ catchError c = compilerCatch c . (. compilerErrorMessages)
168
212
169
213
170--------------------------------------------------------------------------------
214--------------------------------------------------------------------------------
215+-- | Like 'unCompiler' but treating IO exceptions as 'CompilerError's
171runCompiler :: Compiler a -> CompilerRead -> IO (CompilerResult a)
216runCompiler :: Compiler a -> CompilerRead -> IO (CompilerResult a)
172runCompiler compiler read' = handle handler $ unCompiler compiler read'
217runCompiler compiler read' = handle handler $ unCompiler compiler read'
173 where
218 where
174 handler :: SomeException -> IO (CompilerResult a)
219 handler :: SomeException -> IO (CompilerResult a)
175− handler e = return $ CompilerError [show e]
220+ handler e = return $ CompilerError $ CompilationFailure $ show e :| []
176
221
177
222
178--------------------------------------------------------------------------------
223--------------------------------------------------------------------------------
224+-- | Trying alternative compilers if the first fails, regardless whether through
225+-- 'fail', 'throwError' or 'Hakyll.Core.Compiler.noResult'.
226+-- Aggregates error messages if all fail.
179instance Alternative Compiler where
227instance Alternative Compiler where
180− empty = compilerThrow []
228+ empty = compilerNoResult []
181− x <|> y = compilerCatch x $ \es -> do
229+ x <|> y = x `compilerCatch` (\rx -> y `compilerCatch` (\ry ->
182− logger <- compilerLogger <$> compilerAsk
230+ case (rx, ry) of
183− forM_ es $ \e -> compilerUnsafeIO $ Logger.debug logger $
231+ (CompilationFailure xs, CompilationFailure ys) ->
232+ compilerThrow $ NonEmpty.toList xs ++ NonEmpty.toList ys
233+ (CompilationFailure xs, CompilationNoResult ys) ->
234+ debug ys >> compilerThrow (NonEmpty.toList xs)
235+ (CompilationNoResult xs, CompilationFailure ys) ->
236+ debug xs >> compilerThrow (NonEmpty.toList ys)
237+ (CompilationNoResult xs, CompilationNoResult ys) -> compilerNoResult $ xs ++ ys
238+ ))
239+ where
184− "Hakyll.Core.Compiler.Internal: Alternative failed: " ++ e
240+ debug = compilerDebugEntries "Hakyll.Core.Compiler.Internal: Alternative fail suppressed"
185− y
186 {-# INLINE (<|>) #-}
241 {-# INLINE (<|>) #-}
187
242
188
243
189--------------------------------------------------------------------------------
244--------------------------------------------------------------------------------
245+-- | Put the result back in a compiler
246+compilerResult :: CompilerResult a -> Compiler a
247+compilerResult x = Compiler $ \_ -> return x
248+{-# INLINE compilerResult #-}
249+
250+
251+--------------------------------------------------------------------------------
252+-- | Get the current environment
190compilerAsk :: Compiler CompilerRead
253compilerAsk :: Compiler CompilerRead
191compilerAsk = Compiler $ \r -> return $ CompilerDone r mempty
254compilerAsk = Compiler $ \r -> return $ CompilerDone r mempty
192{-# INLINE compilerAsk #-}
255{-# INLINE compilerAsk #-}
193
256
194
257
195--------------------------------------------------------------------------------
258--------------------------------------------------------------------------------
259+-- | Put a 'CompilerWrite'
196compilerTell :: CompilerWrite -> Compiler ()
260compilerTell :: CompilerWrite -> Compiler ()
197−compilerTell deps = Compiler $ \_ -> return $ CompilerDone () deps
261+compilerTell = compilerResult . CompilerDone ()
198{-# INLINE compilerTell #-}
262{-# INLINE compilerTell #-}
199
263
200
264
201--------------------------------------------------------------------------------
265--------------------------------------------------------------------------------
266+-- | Run an IO computation without dependencies in a Compiler
267+compilerUnsafeIO :: IO a -> Compiler a
268+compilerUnsafeIO io = Compiler $ \_ -> do
269+ x <- io
270+ return $ CompilerDone x mempty
271+{-# INLINE compilerUnsafeIO #-}
272+
273+
274+--------------------------------------------------------------------------------
275+-- | Throw errors in the 'Compiler'.
276+--
277+-- If no messages are given, this is considered a 'CompilationNoResult' error.
278+-- Otherwise, it is treated as a proper compilation failure.
202compilerThrow :: [String] -> Compiler a
279compilerThrow :: [String] -> Compiler a
203−compilerThrow es = Compiler $ \_ -> return $ CompilerError es
280+compilerThrow = compilerResult . CompilerError .
204−{-# INLINE compilerThrow #-}
281+ maybe (CompilationNoResult []) CompilationFailure .
282+ NonEmpty.nonEmpty
283+
284+-- | Put a 'CompilerError' with multiple messages as 'CompilationNoResult'
285+compilerNoResult :: [String] -> Compiler a
286+compilerNoResult = compilerResult . CompilerError . CompilationNoResult
205
287
206
288
207--------------------------------------------------------------------------------
289--------------------------------------------------------------------------------
208−compilerCatch :: Compiler a -> ([String] -> Compiler a) -> Compiler a
290+-- | Allows to distinguish 'CompilerError's and branch on them with 'Either'
291+--
292+-- prop> compilerTry = (`compilerCatch` return . Left) . fmap Right
293+compilerTry :: Compiler a -> Compiler (Either (CompilerErrors String) a)
294+compilerTry (Compiler x) = Compiler $ \r -> do
295+ res <- x r
296+ case res of
297+ CompilerDone res' w -> return (CompilerDone (Right res') w)
298+ CompilerSnapshot s c -> return (CompilerSnapshot s (compilerTry c))
299+ CompilerRequire i c -> return (CompilerRequire i (compilerTry c))
300+ CompilerError e -> return (CompilerDone (Left e) mempty)
301+{-# INLINE compilerTry #-}
302+
303+
304+--------------------------------------------------------------------------------
305+-- | Allows you to recover from 'CompilerError's.
306+-- Uses the same parameter order as 'catchError' so that it can be used infix.
307+--
308+-- prop> c `compilerCatch` f = compilerTry c >>= either f return
309+compilerCatch :: Compiler a -> (CompilerErrors String -> Compiler a) -> Compiler a
209compilerCatch (Compiler x) f = Compiler $ \r -> do
310compilerCatch (Compiler x) f = Compiler $ \r -> do
210 res <- x r
311 res <- x r
211 case res of
312 case res of
212 CompilerDone res' w -> return (CompilerDone res' w)
313 CompilerDone res' w -> return (CompilerDone res' w)
213 CompilerSnapshot s c -> return (CompilerSnapshot s (compilerCatch c f))
314 CompilerSnapshot s c -> return (CompilerSnapshot s (compilerCatch c f))
214− CompilerError e -> unCompiler (f e) r
215 CompilerRequire i c -> return (CompilerRequire i (compilerCatch c f))
315 CompilerRequire i c -> return (CompilerRequire i (compilerCatch c f))
316+ CompilerError e -> unCompiler (f e) r
216{-# INLINE compilerCatch #-}
317{-# INLINE compilerCatch #-}
217
318
218
319
219--------------------------------------------------------------------------------
320--------------------------------------------------------------------------------
220−-- | Put the result back in a compiler
321+compilerDebugLog :: [String] -> Compiler ()
221−compilerResult :: CompilerResult a -> Compiler a
322+compilerDebugLog ms = do
222−compilerResult x = Compiler $ \_ -> return x
323+ logger <- compilerLogger <$> compilerAsk
223−{-# INLINE compilerResult #-}
324+ compilerUnsafeIO $ forM_ ms $ Logger.debug logger
224−
225
325
226--------------------------------------------------------------------------------
326--------------------------------------------------------------------------------
227−compilerUnsafeIO :: IO a -> Compiler a
327+-- | Pass a list of messages with a heading to the debug logger
228−compilerUnsafeIO io = Compiler $ \_ -> do
328+compilerDebugEntries :: String -> [String] -> Compiler ()
229− x <- io
329+compilerDebugEntries msg = compilerDebugLog . (msg:) . map indent
230− return $ CompilerDone x mempty
330+ where
231−{-# INLINE compilerUnsafeIO #-}
331+ indent = unlines . map (" "++) . lines
232
332
233
333
234--------------------------------------------------------------------------------
334--------------------------------------------------------------------------------
235compilerTellDependencies :: [Dependency] -> Compiler ()
335compilerTellDependencies :: [Dependency] -> Compiler ()
236compilerTellDependencies ds = do
336compilerTellDependencies ds = do
237− logger <- compilerLogger <$> compilerAsk
337+ compilerDebugLog $ map (\d ->
238− forM_ ds $ \d -> compilerUnsafeIO $ Logger.debug logger $
239− "Hakyll.Core.Compiler.Internal: Adding dependency: " ++ show d
338+ "Hakyll.Core.Compiler.Internal: Adding dependency: " ++ show d) ds
240 compilerTell mempty {compilerDependencies = ds}
339 compilerTell mempty {compilerDependencies = ds}
241{-# INLINE compilerTellDependencies #-}
340{-# INLINE compilerTellDependencies #-}
242
341
lib/Hakyll/Core/Compiler/Require.hs+2
@@ -91,6 +91,7 @@ loadBody id' = loadSnapshotBody id' final
91
91
92
92
93--------------------------------------------------------------------------------
93--------------------------------------------------------------------------------
94+-- | A shortcut for only requiring the body for a specific snapshot of an item
94loadSnapshotBody :: (Binary a, Typeable a)
95loadSnapshotBody :: (Binary a, Typeable a)
95 => Identifier -> Snapshot -> Compiler a
96 => Identifier -> Snapshot -> Compiler a
96loadSnapshotBody id' snapshot = fmap itemBody $ loadSnapshot id' snapshot
97loadSnapshotBody id' snapshot = fmap itemBody $ loadSnapshot id' snapshot
@@ -103,6 +104,7 @@ loadAll pattern = loadAllSnapshots pattern final
103
104
104
105
105--------------------------------------------------------------------------------
106--------------------------------------------------------------------------------
107+-- | Load a specific snapshot for each of dynamic list of items
106loadAllSnapshots :: (Binary a, Typeable a)
108loadAllSnapshots :: (Binary a, Typeable a)
107 => Pattern -> Snapshot -> Compiler [Item a]
109 => Pattern -> Snapshot -> Compiler [Item a]
108loadAllSnapshots pattern snapshot = do
110loadAllSnapshots pattern snapshot = do
lib/Hakyll/Core/Identifier.hs+1−1
@@ -62,7 +62,7 @@ instance Show Identifier where
62
62
63--------------------------------------------------------------------------------
63--------------------------------------------------------------------------------
64-- | Parse an identifier from a string
64-- | Parse an identifier from a string
65−fromFilePath :: String -> Identifier
65+fromFilePath :: FilePath -> Identifier
66fromFilePath = Identifier Nothing .
66fromFilePath = Identifier Nothing .
67 intercalate "/" . filter (not . null) . split'
67 intercalate "/" . filter (not . null) . split'
68 where
68 where
lib/Hakyll/Core/Identifier/Pattern.hs+10−70
@@ -28,6 +28,7 @@
28--
28--
29-- The 'capture' function allows the user to get access to the elements captured
29-- The 'capture' function allows the user to get access to the elements captured
30-- by the capture elements in a glob or regex pattern.
30-- by the capture elements in a glob or regex pattern.
31+{-# OPTIONS_GHC -fno-warn-orphans #-}
31module Hakyll.Core.Identifier.Pattern
32module Hakyll.Core.Identifier.Pattern
32 ( -- * The pattern type
33 ( -- * The pattern type
33 Pattern
34 Pattern
@@ -57,77 +58,22 @@ module Hakyll.Core.Identifier.Pattern
57
58
58
59
59--------------------------------------------------------------------------------
60--------------------------------------------------------------------------------
60−import Control.Arrow ((&&&), (>>>))
61+import Control.Arrow ((&&&), (>>>))
61−import Control.Monad (msum)
62+import Control.Monad (msum)
62−import Data.Binary (Binary (..), getWord8, putWord8)
63−import Data.List (inits, isPrefixOf, tails)
63+import Data.List (inits, isPrefixOf,
64+ tails)
64−import Data.Maybe (isJust)
65+import Data.Maybe (isJust)
65−import Data.Set (Set)
66−import qualified Data.Set as S
66+import qualified Data.Set as S
67
67
68
68
69--------------------------------------------------------------------------------
69--------------------------------------------------------------------------------
70−import GHC.Exts (IsString, fromString)
70+import GHC.Exts (IsString, fromString)
71−import Text.Regex.TDFA ((=~))
71+import Text.Regex.TDFA ((=~))
72
72
73
73
74--------------------------------------------------------------------------------
74--------------------------------------------------------------------------------
75import Hakyll.Core.Identifier
75import Hakyll.Core.Identifier
76−
76+import Hakyll.Core.Identifier.Pattern.Internal
77−
78−--------------------------------------------------------------------------------
79−-- | Elements of a glob pattern
80−data GlobComponent
81− = Capture
82− | CaptureMany
83− | Literal String
84− deriving (Eq, Show)
85−
86−
87−--------------------------------------------------------------------------------
88−instance Binary GlobComponent where
89− put Capture = putWord8 0
90− put CaptureMany = putWord8 1
91− put (Literal s) = putWord8 2 >> put s
92−
93− get = getWord8 >>= \t -> case t of
94− 0 -> pure Capture
95− 1 -> pure CaptureMany
96− 2 -> Literal <$> get
97− _ -> error "Data.Binary.get: Invalid GlobComponent"
98−
99−
100−--------------------------------------------------------------------------------
101−-- | Type that allows matching on identifiers
102−data Pattern
103− = Everything
104− | Complement Pattern
105− | And Pattern Pattern
106− | Glob [GlobComponent]
107− | List (Set Identifier)
108− | Regex String
109− | Version (Maybe String)
110− deriving (Show)
111−
112−
113−--------------------------------------------------------------------------------
114−instance Binary Pattern where
115− put Everything = putWord8 0
116− put (Complement p) = putWord8 1 >> put p
117− put (And x y) = putWord8 2 >> put x >> put y
118− put (Glob g) = putWord8 3 >> put g
119− put (List is) = putWord8 4 >> put is
120− put (Regex r) = putWord8 5 >> put r
121− put (Version v) = putWord8 6 >> put v
122−
123− get = getWord8 >>= \t -> case t of
124− 0 -> pure Everything
125− 1 -> Complement <$> get
126− 2 -> And <$> get <*> get
127− 3 -> Glob <$> get
128− 4 -> List <$> get
129− 5 -> Regex <$> get
130− _ -> Version <$> get
131
77
132
78
133--------------------------------------------------------------------------------
79--------------------------------------------------------------------------------
@@ -135,12 +81,6 @@ instance IsString Pattern where
135 fromString = fromGlob
81 fromString = fromGlob
136
82
137
83
138−--------------------------------------------------------------------------------
139−instance Monoid Pattern where
140− mempty = Everything
141− mappend = (.&&.)
142−
143−
144--------------------------------------------------------------------------------
84--------------------------------------------------------------------------------
145-- | Parse a pattern from a string
85-- | Parse a pattern from a string
146fromGlob :: String -> Pattern
86fromGlob :: String -> Pattern
lib/Hakyll/Core/Identifier/Pattern/Internal.hsnew file+92
@@ -0,0 +1,92 @@
1+-- | This internal module is mostly here to prevent CPP conflicting with Haskell
2+-- comments.
3+{-# LANGUAGE CPP #-}
4+module Hakyll.Core.Identifier.Pattern.Internal
5+ ( GlobComponent (..)
6+ , Pattern (..)
7+ ) where
8+
9+
10+--------------------------------------------------------------------------------
11+import Data.Binary (Binary (..), getWord8, putWord8)
12+import Data.Set (Set)
13+
14+
15+--------------------------------------------------------------------------------
16+#if MIN_VERSION_base(4,9,0)
17+import Data.Semigroup (Semigroup (..))
18+#endif
19+
20+
21+--------------------------------------------------------------------------------
22+import Hakyll.Core.Identifier
23+
24+
25+--------------------------------------------------------------------------------
26+-- | Elements of a glob pattern
27+data GlobComponent
28+ = Capture
29+ | CaptureMany
30+ | Literal String
31+ deriving (Eq, Show)
32+
33+
34+--------------------------------------------------------------------------------
35+instance Binary GlobComponent where
36+ put Capture = putWord8 0
37+ put CaptureMany = putWord8 1
38+ put (Literal s) = putWord8 2 >> put s
39+
40+ get = getWord8 >>= \t -> case t of
41+ 0 -> pure Capture
42+ 1 -> pure CaptureMany
43+ 2 -> Literal <$> get
44+ _ -> error "Data.Binary.get: Invalid GlobComponent"
45+
46+
47+--------------------------------------------------------------------------------
48+-- | Type that allows matching on identifiers
49+data Pattern
50+ = Everything
51+ | Complement Pattern
52+ | And Pattern Pattern
53+ | Glob [GlobComponent]
54+ | List (Set Identifier)
55+ | Regex String
56+ | Version (Maybe String)
57+ deriving (Show)
58+
59+
60+--------------------------------------------------------------------------------
61+instance Binary Pattern where
62+ put Everything = putWord8 0
63+ put (Complement p) = putWord8 1 >> put p
64+ put (And x y) = putWord8 2 >> put x >> put y
65+ put (Glob g) = putWord8 3 >> put g
66+ put (List is) = putWord8 4 >> put is
67+ put (Regex r) = putWord8 5 >> put r
68+ put (Version v) = putWord8 6 >> put v
69+
70+ get = getWord8 >>= \t -> case t of
71+ 0 -> pure Everything
72+ 1 -> Complement <$> get
73+ 2 -> And <$> get <*> get
74+ 3 -> Glob <$> get
75+ 4 -> List <$> get
76+ 5 -> Regex <$> get
77+ _ -> Version <$> get
78+
79+
80+--------------------------------------------------------------------------------
81+#if MIN_VERSION_base(4,9,0)
82+instance Semigroup Pattern where
83+ (<>) = And
84+
85+instance Monoid Pattern where
86+ mempty = Everything
87+ mappend = (<>)
88+#else
89+instance Monoid Pattern where
90+ mempty = Everything
91+ mappend = And
92+#endif
lib/Hakyll/Core/Logger.hs+9−3
@@ -18,6 +18,7 @@ import Control.Concurrent.Chan (Chan, newChan, readChan, writeChan)
18import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, takeMVar)
18import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, takeMVar)
19import Control.Monad (forever)
19import Control.Monad (forever)
20import Control.Monad.Trans (MonadIO, liftIO)
20import Control.Monad.Trans (MonadIO, liftIO)
21+import Data.List (intercalate)
21import Prelude hiding (error)
22import Prelude hiding (error)
22
23
23
24
@@ -79,7 +80,7 @@ string l v m
79
80
80--------------------------------------------------------------------------------
81--------------------------------------------------------------------------------
81error :: MonadIO m => Logger -> String -> m ()
82error :: MonadIO m => Logger -> String -> m ()
82−error l m = string l Error $ " [ERROR] " ++ m
83+error l m = string l Error $ " [ERROR] " ++ indent m
83
84
84
85
85--------------------------------------------------------------------------------
86--------------------------------------------------------------------------------
@@ -89,9 +90,14 @@ header l = string l Message
89
90
90--------------------------------------------------------------------------------
91--------------------------------------------------------------------------------
91message :: MonadIO m => Logger -> String -> m ()
92message :: MonadIO m => Logger -> String -> m ()
92−message l m = string l Message $ " " ++ m
93+message l m = string l Message $ " " ++ indent m
93
94
94
95
95--------------------------------------------------------------------------------
96--------------------------------------------------------------------------------
96debug :: MonadIO m => Logger -> String -> m ()
97debug :: MonadIO m => Logger -> String -> m ()
97−debug l m = string l Debug $ " [DEBUG] " ++ m
98+debug l m = string l Debug $ " [DEBUG] " ++ indent m
99+
100+
101+--------------------------------------------------------------------------------
102+indent :: String -> String
103+indent = intercalate "\n " . lines
lib/Hakyll/Core/Provider/Metadata.hs+3−2
@@ -27,6 +27,7 @@ import Hakyll.Core.Identifier
27import Hakyll.Core.Metadata
27import Hakyll.Core.Metadata
28import Hakyll.Core.Provider.Internal
28import Hakyll.Core.Provider.Internal
29import System.IO as IO
29import System.IO as IO
30+import System.IO.Error (modifyIOError, ioeSetLocation)
30
31
31
32
32--------------------------------------------------------------------------------
33--------------------------------------------------------------------------------
@@ -51,7 +52,7 @@ loadMetadata p identifier = do
51--------------------------------------------------------------------------------
52--------------------------------------------------------------------------------
52loadMetadataHeader :: FilePath -> IO (Metadata, String)
53loadMetadataHeader :: FilePath -> IO (Metadata, String)
53loadMetadataHeader fp = do
54loadMetadataHeader fp = do
54− fileContent <- readFile fp
55+ fileContent <- modifyIOError (`ioeSetLocation` "loadMetadataHeader") $ readFile fp
55 case parsePage fileContent of
56 case parsePage fileContent of
56 Right x -> return x
57 Right x -> return x
57 Left err -> throwIO $ MetadataException fp err
58 Left err -> throwIO $ MetadataException fp err
@@ -60,7 +61,7 @@ loadMetadataHeader fp = do
60--------------------------------------------------------------------------------
61--------------------------------------------------------------------------------
61loadMetadataFile :: FilePath -> IO Metadata
62loadMetadataFile :: FilePath -> IO Metadata
62loadMetadataFile fp = do
63loadMetadataFile fp = do
63− fileContent <- B.readFile fp
64+ fileContent <- modifyIOError (`ioeSetLocation` "loadMetadataFile") $ B.readFile fp
64 let errOrMeta = Yaml.decodeEither' fileContent
65 let errOrMeta = Yaml.decodeEither' fileContent
65 either (fail . show) return errOrMeta
66 either (fail . show) return errOrMeta
66
67
lib/Hakyll/Core/Routes.hs+17
@@ -25,6 +25,7 @@
25-- not appear in your site directory.
25-- not appear in your site directory.
26--
26--
27-- * If an item matches multiple routes, the first rule will be chosen.
27-- * If an item matches multiple routes, the first rule will be chosen.
28+{-# LANGUAGE CPP #-}
28{-# LANGUAGE Rank2Types #-}
29{-# LANGUAGE Rank2Types #-}
29module Hakyll.Core.Routes
30module Hakyll.Core.Routes
30 ( UsedMetadata
31 ( UsedMetadata
@@ -42,6 +43,9 @@ module Hakyll.Core.Routes
42
43
43
44
44--------------------------------------------------------------------------------
45--------------------------------------------------------------------------------
46+#if MIN_VERSION_base(4,9,0)
47+import Data.Semigroup (Semigroup (..))
48+#endif
45import System.FilePath (replaceExtension)
49import System.FilePath (replaceExtension)
46
50
47
51
@@ -74,6 +78,18 @@ newtype Routes = Routes
74
78
75
79
76--------------------------------------------------------------------------------
80--------------------------------------------------------------------------------
81+#if MIN_VERSION_base(4,9,0)
82+instance Semigroup Routes where
83+ (<>) (Routes f) (Routes g) = Routes $ \p id' -> do
84+ (mfp, um) <- f p id'
85+ case mfp of
86+ Nothing -> g p id'
87+ Just _ -> return (mfp, um)
88+
89+instance Monoid Routes where
90+ mempty = Routes $ \_ _ -> return (Nothing, False)
91+ mappend = (<>)
92+#else
77instance Monoid Routes where
93instance Monoid Routes where
78 mempty = Routes $ \_ _ -> return (Nothing, False)
94 mempty = Routes $ \_ _ -> return (Nothing, False)
79 mappend (Routes f) (Routes g) = Routes $ \p id' -> do
95 mappend (Routes f) (Routes g) = Routes $ \p id' -> do
@@ -81,6 +97,7 @@ instance Monoid Routes where
81 case mfp of
97 case mfp of
82 Nothing -> g p id'
98 Nothing -> g p id'
83 Just _ -> return (mfp, um)
99 Just _ -> return (mfp, um)
100+#endif
84
101
85
102
86--------------------------------------------------------------------------------
103--------------------------------------------------------------------------------
lib/Hakyll/Core/Rules/Internal.hs+14
@@ -1,4 +1,5 @@
1--------------------------------------------------------------------------------
1--------------------------------------------------------------------------------
2+{-# LANGUAGE CPP #-}
2{-# LANGUAGE GeneralizedNewtypeDeriving #-}
3{-# LANGUAGE GeneralizedNewtypeDeriving #-}
3{-# LANGUAGE Rank2Types #-}
4{-# LANGUAGE Rank2Types #-}
4module Hakyll.Core.Rules.Internal
5module Hakyll.Core.Rules.Internal
@@ -16,6 +17,9 @@ import Control.Monad.Reader (ask)
16import Control.Monad.RWS (RWST, runRWST)
17import Control.Monad.RWS (RWST, runRWST)
17import Control.Monad.Trans (liftIO)
18import Control.Monad.Trans (liftIO)
18import qualified Data.Map as M
19import qualified Data.Map as M
20+#if MIN_VERSION_base(4,9,0)
21+import Data.Semigroup (Semigroup (..))
22+#endif
19import Data.Set (Set)
23import Data.Set (Set)
20
24
21
25
@@ -52,10 +56,20 @@ data RuleSet = RuleSet
52
56
53
57
54--------------------------------------------------------------------------------
58--------------------------------------------------------------------------------
59+#if MIN_VERSION_base(4,9,0)
60+instance Semigroup RuleSet where
61+ (<>) (RuleSet r1 c1 s1 p1) (RuleSet r2 c2 s2 p2) =
62+ RuleSet (mappend r1 r2) (mappend c1 c2) (mappend s1 s2) (p1 .||. p2)
63+
64+instance Monoid RuleSet where
65+ mempty = RuleSet mempty mempty mempty mempty
66+ mappend = (<>)
67+#else
55instance Monoid RuleSet where
68instance Monoid RuleSet where
56 mempty = RuleSet mempty mempty mempty mempty
69 mempty = RuleSet mempty mempty mempty mempty
57 mappend (RuleSet r1 c1 s1 p1) (RuleSet r2 c2 s2 p2) =
70 mappend (RuleSet r1 c1 s1 p1) (RuleSet r2 c2 s2 p2) =
58 RuleSet (mappend r1 r2) (mappend c1 c2) (mappend s1 s2) (p1 .||. p2)
71 RuleSet (mappend r1 r2) (mappend c1 c2) (mappend s1 s2) (p1 .||. p2)
72+#endif
59
73
60
74
61--------------------------------------------------------------------------------
75--------------------------------------------------------------------------------
lib/Hakyll/Core/Runtime.hs+3−3
@@ -199,9 +199,9 @@ chase trail id'
199 result <- liftIO $ runCompiler compiler read'
199 result <- liftIO $ runCompiler compiler read'
200 case result of
200 case result of
201 -- Rethrow error
201 -- Rethrow error
202− CompilerError [] -> throwError
202+ CompilerError e -> throwError $ case compilerErrorMessages e of
203− "Compiler failed but no info given, try running with -v?"
203+ [] -> "Compiler failed but no info given, try running with -v?"
204− CompilerError es -> throwError $ intercalate "; " es
204+ es -> intercalate "; " es
205
205
206 -- Signal that a snapshot was saved ->
206 -- Signal that a snapshot was saved ->
207 CompilerSnapshot snapshot c -> do
207 CompilerSnapshot snapshot c -> do
lib/Hakyll/Core/Store.hs+26−23
@@ -16,7 +16,6 @@ module Hakyll.Core.Store
16
16
17
17
18--------------------------------------------------------------------------------
18--------------------------------------------------------------------------------
19−import Control.Exception (IOException, handle)
20import qualified Crypto.Hash.MD5 as MD5
19import qualified Crypto.Hash.MD5 as MD5
21import Data.Binary (Binary, decode, encodeFile)
20import Data.Binary (Binary, decode, encodeFile)
22import qualified Data.ByteString as B
21import qualified Data.ByteString as B
@@ -27,11 +26,13 @@ import Data.Maybe (isJust)
27import qualified Data.Text as T
26import qualified Data.Text as T
28import qualified Data.Text.Encoding as T
27import qualified Data.Text.Encoding as T
29import Data.Typeable (TypeRep, Typeable, cast, typeOf)
28import Data.Typeable (TypeRep, Typeable, cast, typeOf)
29+import Numeric (showHex)
30import System.Directory (createDirectoryIfMissing)
30import System.Directory (createDirectoryIfMissing)
31import System.Directory (doesFileExist, removeFile)
31import System.Directory (doesFileExist, removeFile)
32import System.FilePath ((</>))
32import System.FilePath ((</>))
33import System.IO (IOMode (..), hClose, openFile)
33import System.IO (IOMode (..), hClose, openFile)
34−import Text.Printf (printf)
34+import System.IO.Error (catchIOError, ioeSetFileName,
35+ ioeSetLocation, modifyIOError)
35
36
36
37
37--------------------------------------------------------------------------------
38--------------------------------------------------------------------------------
@@ -84,6 +85,14 @@ new inMemory directory = do
84 where
85 where
85 csize = Just 500
86 csize = Just 500
86
87
88+--------------------------------------------------------------------------------
89+withStore :: Store -> String -> (String -> FilePath -> IO a) -> [String] -> IO a
90+withStore store loc run identifier = modifyIOError handle $ run key path
91+ where
92+ key = hash identifier
93+ path = storeDirectory store </> key
94+ handle e = e `ioeSetFileName` (path ++ " for " ++ intercalate "/" identifier)
95+ `ioeSetLocation` ("Store." ++ loc)
87
96
88--------------------------------------------------------------------------------
97--------------------------------------------------------------------------------
89-- | Auxiliary: add an item to the in-memory cache
98-- | Auxiliary: add an item to the in-memory cache
@@ -124,17 +133,16 @@ cacheDelete (Store _ (Just lru)) key = do
124--------------------------------------------------------------------------------
133--------------------------------------------------------------------------------
125-- | Store an item
134-- | Store an item
126set :: (Binary a, Typeable a) => Store -> [String] -> a -> IO ()
135set :: (Binary a, Typeable a) => Store -> [String] -> a -> IO ()
127−set store identifier value = do
136+set store identifier value = withStore store "set" (\key path -> do
128− encodeFile (storeDirectory store </> key) value
137+ encodeFile path value
129 cacheInsert store key value
138 cacheInsert store key value
130− where
131− key = hash identifier
139+ ) identifier
132
140
133
141
134--------------------------------------------------------------------------------
142--------------------------------------------------------------------------------
135-- | Load an item
143-- | Load an item
136get :: (Binary a, Typeable a) => Store -> [String] -> IO (Result a)
144get :: (Binary a, Typeable a) => Store -> [String] -> IO (Result a)
137−get store identifier = do
145+get store = withStore store "get" $ \key path -> do
138 -- First check the in-memory map
146 -- First check the in-memory map
139 ref <- cacheLookup store key
147 ref <- cacheLookup store key
140 case ref of
148 case ref of
@@ -146,17 +154,14 @@ get store identifier = do
146 then return NotFound
154 then return NotFound
147 -- Found in the filesystem
155 -- Found in the filesystem
148 else do
156 else do
149− v <- decodeClose
157+ v <- decodeClose path
150 cacheInsert store key v
158 cacheInsert store key v
151 return $ Found v
159 return $ Found v
152 -- Found in the in-memory map (or wrong type), just return
160 -- Found in the in-memory map (or wrong type), just return
153 s -> return s
161 s -> return s
154 where
162 where
155− key = hash identifier
156− path = storeDirectory store </> key
157−
158 -- 'decodeFile' from Data.Binary which closes the file ASAP
163 -- 'decodeFile' from Data.Binary which closes the file ASAP
159− decodeClose = do
164+ decodeClose path = do
160 h <- openFile path ReadMode
165 h <- openFile path ReadMode
161 lbs <- BL.hGetContents h
166 lbs <- BL.hGetContents h
162 BL.length lbs `seq` hClose h
167 BL.length lbs `seq` hClose h
@@ -166,32 +171,30 @@ get store identifier = do
166--------------------------------------------------------------------------------
171--------------------------------------------------------------------------------
167-- | Strict function
172-- | Strict function
168isMember :: Store -> [String] -> IO Bool
173isMember :: Store -> [String] -> IO Bool
169−isMember store identifier = do
174+isMember store = withStore store "isMember" $ \key path -> do
170 inCache <- cacheIsMember store key
175 inCache <- cacheIsMember store key
171 if inCache then return True else doesFileExist path
176 if inCache then return True else doesFileExist path
172− where
173− key = hash identifier
174− path = storeDirectory store </> key
175
177
176
178
177--------------------------------------------------------------------------------
179--------------------------------------------------------------------------------
178-- | Delete an item
180-- | Delete an item
179delete :: Store -> [String] -> IO ()
181delete :: Store -> [String] -> IO ()
180−delete store identifier = do
182+delete store = withStore store "delete" $ \key path -> do
181 cacheDelete store key
183 cacheDelete store key
182− deleteFile $ storeDirectory store </> key
184+ deleteFile path
183− where
184− key = hash identifier
185
185
186
186
187--------------------------------------------------------------------------------
187--------------------------------------------------------------------------------
188-- | Delete a file unless it doesn't exist...
188-- | Delete a file unless it doesn't exist...
189deleteFile :: FilePath -> IO ()
189deleteFile :: FilePath -> IO ()
190−deleteFile = handle (\(_ :: IOException) -> return ()) . removeFile
190+deleteFile = (`catchIOError` \_ -> return ()) . removeFile
191
191
192
192
193--------------------------------------------------------------------------------
193--------------------------------------------------------------------------------
194-- | Mostly meant for internal usage
194-- | Mostly meant for internal usage
195hash :: [String] -> String
195hash :: [String] -> String
196−hash = concatMap (printf "%02x") . B.unpack .
197− MD5.hash . T.encodeUtf8 . T.pack . intercalate "/"
196+hash = toHex . B.unpack . MD5.hash . T.encodeUtf8 . T.pack . intercalate "/"
197+ where
198+ toHex [] = ""
199+ toHex (x : xs) | x < 16 = '0' : showHex x (toHex xs)
200+ | otherwise = showHex x (toHex xs)
lib/Hakyll/Core/Util/Parser.hs−1
@@ -2,7 +2,6 @@
2-- | Parser utilities
2-- | Parser utilities
3module Hakyll.Core.Util.Parser
3module Hakyll.Core.Util.Parser
4 ( metadataKey
4 ( metadataKey
5− , reservedKeys
6 ) where
5 ) where
7
6
8
7
lib/Hakyll/Core/Util/String.hs+1−1
@@ -27,7 +27,7 @@ trim = reverse . trim' . reverse . trim'
27--------------------------------------------------------------------------------
27--------------------------------------------------------------------------------
28-- | A simple (but inefficient) regex replace funcion
28-- | A simple (but inefficient) regex replace funcion
29replaceAll :: String -- ^ Pattern
29replaceAll :: String -- ^ Pattern
30− -> (String -> String) -- ^ Replacement (called on capture)
30+ -> (String -> String) -- ^ Replacement (called on match)
31 -> String -- ^ Source string
31 -> String -- ^ Source string
32 -> String -- ^ Result
32 -> String -- ^ Result
33replaceAll pattern f source = replaceAll' source
33replaceAll pattern f source = replaceAll' source
lib/Hakyll/Preview/Poll.hs+8−18
@@ -14,8 +14,7 @@ import Control.Exception (AsyncException, fromException,
14import Control.Monad (forever, void, when)
14import Control.Monad (forever, void, when)
15import System.Directory (canonicalizePath)
15import System.Directory (canonicalizePath)
16import System.FilePath (pathSeparators)
16import System.FilePath (pathSeparators)
17−import System.FSNotify (Event (..), startManager,
17+import qualified System.FSNotify as FSNotify
18− watchTree)
19
18
20#ifdef mingw32_HOST_OS
19#ifdef mingw32_HOST_OS
21import Control.Concurrent (threadDelay)
20import Control.Concurrent (threadDelay)
@@ -44,12 +43,12 @@ watchUpdates conf update = do
44 shouldBuild <- newEmptyMVar
43 shouldBuild <- newEmptyMVar
45 pattern <- update
44 pattern <- update
46 fullProviderDir <- canonicalizePath $ providerDirectory conf
45 fullProviderDir <- canonicalizePath $ providerDirectory conf
47− manager <- startManager
46+ manager <- FSNotify.startManager
48
47
49 let allowed event = do
48 let allowed event = do
50 -- Absolute path of the changed file. This must be inside provider
49 -- Absolute path of the changed file. This must be inside provider
51 -- dir, since that's the only dir we're watching.
50 -- dir, since that's the only dir we're watching.
52− let path = eventPath event
51+ let path = FSNotify.eventPath event
53 relative = dropWhile (`elem` pathSeparators) $
52 relative = dropWhile (`elem` pathSeparators) $
54 drop (length fullProviderDir) path
53 drop (length fullProviderDir) path
55 identifier = fromFilePath relative
54 identifier = fromFilePath relative
@@ -69,7 +68,7 @@ watchUpdates conf update = do
69
68
70 -- Send an event whenever something occurs so that the thread described
69 -- Send an event whenever something occurs so that the thread described
71 -- above will do a build.
70 -- above will do a build.
72− void $ watchTree manager providerDir (not . isRemove) $ \event -> do
71+ void $ FSNotify.watchTree manager providerDir (not . isRemove) $ \event -> do
73 allowed' <- allowed event
72 allowed' <- allowed event
74 when allowed' $ void $ tryPutMVar shouldBuild event
73 when allowed' $ void $ tryPutMVar shouldBuild event
75 where
74 where
@@ -77,7 +76,7 @@ watchUpdates conf update = do
77 update' _ _ = void update
76 update' _ _ = void update
78#else
77#else
79 update' event provider = do
78 update' event provider = do
80− let path = provider </> eventPath event
79+ let path = provider </> FSNotify.eventPath event
81 -- on windows, a 'Modified' event is also sent on file deletion
80 -- on windows, a 'Modified' event is also sent on file deletion
82 fileExists <- doesFileExist path
81 fileExists <- doesFileExist path
83
82
@@ -105,15 +104,6 @@ watchUpdates conf update = do
105
104
106
105
107--------------------------------------------------------------------------------
106--------------------------------------------------------------------------------
108−eventPath :: Event -> FilePath
109−eventPath evt = evtPath evt
110− where
111− evtPath (Added p _) = p
112− evtPath (Modified p _) = p
113− evtPath (Removed p _) = p
114−
115−
116−--------------------------------------------------------------------------------
117−isRemove :: Event -> Bool
107+isRemove :: FSNotify.Event -> Bool
118−isRemove (Removed _ _) = True
108+isRemove (FSNotify.Removed {}) = True
119−isRemove _ = False
109+isRemove _ = False
lib/Hakyll/Web/CompressCss.hs+59−62
@@ -1,7 +1,6 @@
1--------------------------------------------------------------------------------
1--------------------------------------------------------------------------------
2-- | Module used for CSS compression. The compression is currently in a simple
2-- | Module used for CSS compression. The compression is currently in a simple
3-- state, but would typically reduce the number of bytes by about 25%.
3-- state, but would typically reduce the number of bytes by about 25%.
4−{-# LANGUAGE PatternGuards #-}
5module Hakyll.Web.CompressCss
4module Hakyll.Web.CompressCss
6 ( compressCssCompiler
5 ( compressCssCompiler
7 , compressCss
6 , compressCss
@@ -9,12 +8,14 @@ module Hakyll.Web.CompressCss
9
8
10
9
11--------------------------------------------------------------------------------
10--------------------------------------------------------------------------------
11+import Data.Char (isSpace)
12−import Data.List (isPrefixOf)
12+import Data.List (dropWhileEnd, isPrefixOf)
13
13
14
14
15--------------------------------------------------------------------------------
15--------------------------------------------------------------------------------
16import Hakyll.Core.Compiler
16import Hakyll.Core.Compiler
17import Hakyll.Core.Item
17import Hakyll.Core.Item
18+import Hakyll.Core.Util.String
18
19
19
20
20--------------------------------------------------------------------------------
21--------------------------------------------------------------------------------
@@ -26,79 +27,75 @@ compressCssCompiler = fmap compressCss <$> getResourceString
26--------------------------------------------------------------------------------
27--------------------------------------------------------------------------------
27-- | Compress CSS to speed up your site.
28-- | Compress CSS to speed up your site.
28compressCss :: String -> String
29compressCss :: String -> String
29−compressCss = compressSeparators . stripComments . compressWhitespace
30+compressCss = withoutStrings (handleCalcExpressions compressSeparators . compressWhitespace)
31+ . dropWhileEnd isSpace
32+ . dropWhile isSpace
33+ . stripComments
30
34
31
35
32--------------------------------------------------------------------------------
36--------------------------------------------------------------------------------
33-- | Compresses certain forms of separators.
37-- | Compresses certain forms of separators.
34compressSeparators :: String -> String
38compressSeparators :: String -> String
35−compressSeparators [] = []
39+compressSeparators =
36−compressSeparators str
40+ replaceAll "; *}" (const "}") .
37− | isConstant = head str : retainConstants compressSeparators (head str) (drop 1 str)
41+ replaceAll ";+" (const ";") .
38− | isPrefixOf "calc( " str = "calc(" ++ compressCalcSeparators 1 (drop 6 str)
42+ replaceAll " *[{};,>+~!] *" (take 1 . dropWhile isSpace) .
39− | isPrefixOf "calc(" str = "calc(" ++ compressCalcSeparators 1 (drop 5 str)
43+ replaceAll ": *" (take 1) -- not destroying pseudo selectors (#323)
40− | stripFirst = compressSeparators (drop 1 str)
44+
41− | stripSecond = compressSeparators (head str : (drop 2 str))
45+-- | Uses `compressCalcExpression` on all parenthesised calc expressions
42− | otherwise = head str : compressSeparators (drop 1 str)
46+-- and applies `transform` to all parts outside of them
43− where
47+handleCalcExpressions :: (String -> String) -> String -> String
44− isConstant = or $ map (isOfPrefix str) ["\"", "'"]
48+handleCalcExpressions transform = top transform
45− stripFirst = or $ map (isOfPrefix str) $ [";;", ";}"] ++ (map (\c -> " " ++ c) separators)
46− stripSecond = or $ map (isOfPrefix str) $ map (\c -> c ++ " ") separators
47− separators = [" ", "{", "}", ":", ";", ",", ">", "+", "!"]
48−
49−-- | Compresses separators when starting inside calc().
50−compressCalcSeparators :: Int -> String -> String
51−compressCalcSeparators 0 str = compressSeparators str
52−compressCalcSeparators depth str
53− | stripFirst = compressCalcSeparators depth (tail str)
54− | stripSecond = compressCalcSeparators depth (head str : (drop 2 str))
55− | ('(' : xs) <- str = '(' : compressCalcSeparators (depth + 1) xs
56− | isPrefixOf "calc( " str = compressCalcSeparators depth ("calc(" ++ (drop 6 str))
57− | isPrefixOf "calc(" str = '(' : compressCalcSeparators (depth + 1) (drop 5 str)
58− | (')' : xs) <- str = ')' : compressCalcSeparators (depth - 1) xs
59− | otherwise = head str : compressCalcSeparators depth (tail str)
60 where
49 where
61− stripFirst = or $ map (isOfPrefix str) $ map (\c -> " " ++ c) ["*", "/", ")"]
50+ top f "" = f ""
62− stripSecond = or $ map (isOfPrefix str) $ map (\c -> c ++ " ") ["*", "/", "("]
51+ top f str | "calc(" `isPrefixOf` str = f "calc" ++ nested 0 compressCalcExpression (drop 4 str)
52+ top f (x:xs) = top (f . (x:)) xs
53+
54+ -- when called with depth=0, the first character must be a '('
55+ nested :: Int -> (String -> String) -> String -> String
56+ nested _ f "" = f "" -- shouldn't happen, mismatched nesting
57+ nested depth f str | "calc(" `isPrefixOf` str = nested depth f (drop 4 str)
58+ nested 1 f (')':xs) = f ")" ++ top transform xs
59+ nested depth f (x:xs) = nested (case x of
60+ '(' -> depth + 1
61+ ')' -> depth - 1 -- assert: depth > 1
62+ _ -> depth
63+ ) (f . (x:)) xs
64+
65+-- | does not remove whitespace around + and -, which is important in calc() expressions
66+compressCalcExpression :: String -> String
67+compressCalcExpression =
68+ replaceAll " *[*/] *| *\\)|\\( *" (take 1 . dropWhile isSpace)
63
69
64--------------------------------------------------------------------------------
70--------------------------------------------------------------------------------
65-- | Compresses all whitespace.
71-- | Compresses all whitespace.
66compressWhitespace :: String -> String
72compressWhitespace :: String -> String
67−compressWhitespace [] = []
73+compressWhitespace = replaceAll "[ \t\n\r]+" (const " ")
68−compressWhitespace str
69− | isConstant = head str : retainConstants compressWhitespace (head str) (drop 1 str)
70− | replaceOne = compressWhitespace (' ' : (drop 1 str))
71− | replaceTwo = compressWhitespace (' ' : (drop 2 str))
72− | otherwise = head str : compressWhitespace (drop 1 str)
73− where
74− isConstant = or $ map (isOfPrefix str) ["\"", "'"]
75− replaceOne = or $ map (isOfPrefix str) ["\t", "\n", "\r"]
76− replaceTwo = or $ map (isOfPrefix str) [" \t", " \n", " \r", " "]
77
74
78--------------------------------------------------------------------------------
75--------------------------------------------------------------------------------
79−-- | Function that strips CSS comments away.
76+-- | Function that strips CSS comments away (outside of strings).
80stripComments :: String -> String
77stripComments :: String -> String
81−stripComments [] = []
78+stripComments "" = ""
82−stripComments str
79+stripComments ('/':'*':str) = stripComments $ eatComment str
83− | isConstant = head str : retainConstants stripComments (head str) (drop 1 str)
80+stripComments (x:xs) | x `elem` "\"'" = retainString x xs stripComments
84− | isPrefixOf "/*" str = stripComments $ eatComments $ drop 2 str
81+ | otherwise = x : stripComments xs
85− | otherwise = head str : stripComments (drop 1 str)
82+
86− where
83+eatComment :: String -> String
87− isConstant = or $ map (isOfPrefix str) ["\"", "'"]
84+eatComment "" = ""
88− eatComments str'
85+eatComment ('*':'/':str) = str
89− | null str' = []
86+eatComment (_:str) = eatComment str
90− | isPrefixOf "*/" str' = drop 2 str'
91− | otherwise = eatComments $ drop 1 str'
92
87
93−--------------------------------------------------------------------------------
94−-- | Helper function to handle string constants correctly.
95−retainConstants :: (String -> String) -> Char -> String -> String
96−retainConstants f delim str
97− | null str = []
98− | isPrefixOf [delim] str = head str : f (drop 1 str)
99− | otherwise = head str : retainConstants f delim (drop 1 str)
100
88
101--------------------------------------------------------------------------------
89--------------------------------------------------------------------------------
102−-- | Helper function to determine whether a string is a substring.
90+-- | Helper functions to handle string tokens correctly.
103−isOfPrefix :: String -> String -> Bool
91+
104−isOfPrefix = flip isPrefixOf
92+-- TODO: handle backslash escapes
93+withoutStrings :: (String -> String) -> String -> String
94+withoutStrings f str = case span (`notElem` "\"'") str of
95+ (text, "") -> f text
96+ (text, d:rest) -> f text ++ retainString d rest (withoutStrings f)
97+
98+retainString :: Char -> String -> (String -> String) -> String
99+retainString delim str cont = case span (/= delim) str of
100+ (val, "") -> delim : val
101+ (val, _:rest) -> delim : val ++ delim : cont rest
lib/Hakyll/Web/Feed.hs+50−32
@@ -1,4 +1,5 @@
1+{-# LANGUAGE OverloadedStrings #-}
1−{-# LANGUAGE TemplateHaskell #-}
2+{-# LANGUAGE TemplateHaskell #-}
2
3
3--------------------------------------------------------------------------------
4--------------------------------------------------------------------------------
4-- | A Module that allows easy rendering of RSS feeds.
5-- | A Module that allows easy rendering of RSS feeds.
@@ -22,6 +23,8 @@ module Hakyll.Web.Feed
22 ( FeedConfiguration (..)
23 ( FeedConfiguration (..)
23 , renderRss
24 , renderRss
24 , renderAtom
25 , renderAtom
26+ , renderRssWithTemplates
27+ , renderAtomWithTemplates
25 ) where
28 ) where
26
29
27
30
@@ -35,27 +38,25 @@ import Hakyll.Web.Template.List
35
38
36
39
37--------------------------------------------------------------------------------
40--------------------------------------------------------------------------------
38−import Data.FileEmbed (embedFile)
41+import Data.FileEmbed (makeRelativeToProject)
39−import qualified Data.Text as T
40−import qualified Data.Text.Encoding as T
41
42
42
43
43--------------------------------------------------------------------------------
44--------------------------------------------------------------------------------
44−rssTemplate :: String
45+rssTemplate :: Template
45−rssTemplate = T.unpack $
46+rssTemplate =
46− T.decodeUtf8 $(embedFile "data/templates/rss.xml")
47+ $(makeRelativeToProject "data/templates/rss.xml" >>= embedTemplate)
47
48
48−rssItemTemplate :: String
49+rssItemTemplate :: Template
49−rssItemTemplate = T.unpack $
50+rssItemTemplate =
50− T.decodeUtf8 $(embedFile "data/templates/rss-item.xml")
51+ $(makeRelativeToProject "data/templates/rss-item.xml" >>= embedTemplate)
51
52
52−atomTemplate :: String
53+atomTemplate :: Template
53−atomTemplate = T.unpack $
54+atomTemplate =
54− T.decodeUtf8 $(embedFile "data/templates/atom.xml")
55+ $(makeRelativeToProject "data/templates/atom.xml" >>= embedTemplate)
55
56
56−atomItemTemplate :: String
57+atomItemTemplate :: Template
57−atomItemTemplate = T.unpack $
58+atomItemTemplate =
58− T.decodeUtf8 $(embedFile "data/templates/atom-item.xml")
59+ $(makeRelativeToProject "data/templates/atom-item.xml" >>= embedTemplate)
59
60
60
61
61--------------------------------------------------------------------------------
62--------------------------------------------------------------------------------
@@ -76,16 +77,13 @@ data FeedConfiguration = FeedConfiguration
76
77
77--------------------------------------------------------------------------------
78--------------------------------------------------------------------------------
78-- | Abstract function to render any feed.
79-- | Abstract function to render any feed.
79−renderFeed :: String -- ^ Default feed template
80+renderFeed :: Template -- ^ Default feed template
80− -> String -- ^ Default item template
81+ -> Template -- ^ Default item template
81 -> FeedConfiguration -- ^ Feed configuration
82 -> FeedConfiguration -- ^ Feed configuration
82 -> Context String -- ^ Context for the items
83 -> Context String -- ^ Context for the items
83 -> [Item String] -- ^ Input items
84 -> [Item String] -- ^ Input items
84 -> Compiler (Item String) -- ^ Resulting item
85 -> Compiler (Item String) -- ^ Resulting item
85−renderFeed defFeed defItem config itemContext items = do
86+renderFeed feedTpl itemTpl config itemContext items = do
86− feedTpl <- readTemplateFile defFeed
87− itemTpl <- readTemplateFile defItem
88−
89 protectedItems <- mapM (applyFilter protectCDATA) items
87 protectedItems <- mapM (applyFilter protectCDATA) items
90 body <- makeItem =<< applyTemplateList itemTpl itemContext' protectedItems
88 body <- makeItem =<< applyTemplateList itemTpl itemContext' protectedItems
91 applyTemplate feedTpl feedContext body
89 applyTemplate feedTpl feedContext body
@@ -119,11 +117,35 @@ renderFeed defFeed defItem config itemContext items = do
119 updatedField = field "updated" $ \_ -> case items of
117 updatedField = field "updated" $ \_ -> case items of
120 [] -> return "Unknown"
118 [] -> return "Unknown"
121 (x : _) -> unContext itemContext' "updated" [] x >>= \cf -> case cf of
119 (x : _) -> unContext itemContext' "updated" [] x >>= \cf -> case cf of
122− ListField _ _ -> fail "Hakyll.Web.Feed.renderFeed: Internal error"
123 StringField s -> return s
120 StringField s -> return s
121+ _ -> fail "Hakyll.Web.Feed.renderFeed: Internal error"
124
122
125− readTemplateFile :: String -> Compiler Template
123+--------------------------------------------------------------------------------
126− readTemplateFile value = pure $ template $ readTemplateElems value
124+-- | Render an RSS feed using given templates with a number of items.
125+renderRssWithTemplates ::
126+ Template -- ^ Feed template
127+ -> Template -- ^ Item template
128+ -> FeedConfiguration -- ^ Feed configuration
129+ -> Context String -- ^ Item context
130+ -> [Item String] -- ^ Feed items
131+ -> Compiler (Item String) -- ^ Resulting feed
132+renderRssWithTemplates feedTemplate itemTemplate config context = renderFeed
133+ feedTemplate itemTemplate config
134+ (makeItemContext "%a, %d %b %Y %H:%M:%S UT" context)
135+
136+
137+--------------------------------------------------------------------------------
138+-- | Render an Atom feed using given templates with a number of items.
139+renderAtomWithTemplates ::
140+ Template -- ^ Feed template
141+ -> Template -- ^ Item template
142+ -> FeedConfiguration -- ^ Feed configuration
143+ -> Context String -- ^ Item context
144+ -> [Item String] -- ^ Feed items
145+ -> Compiler (Item String) -- ^ Resulting feed
146+renderAtomWithTemplates feedTemplate itemTemplate config context = renderFeed
147+ feedTemplate itemTemplate config
148+ (makeItemContext "%Y-%m-%dT%H:%M:%SZ" context)
127
149
128
150
129--------------------------------------------------------------------------------
151--------------------------------------------------------------------------------
@@ -132,9 +154,7 @@ renderRss :: FeedConfiguration -- ^ Feed configuration
132 -> Context String -- ^ Item context
154 -> Context String -- ^ Item context
133 -> [Item String] -- ^ Feed items
155 -> [Item String] -- ^ Feed items
134 -> Compiler (Item String) -- ^ Resulting feed
156 -> Compiler (Item String) -- ^ Resulting feed
135−renderRss config context = renderFeed
136− rssTemplate rssItemTemplate config
157+renderRss = renderRssWithTemplates rssTemplate rssItemTemplate
137− (makeItemContext "%a, %d %b %Y %H:%M:%S UT" context)
138
158
139
159
140--------------------------------------------------------------------------------
160--------------------------------------------------------------------------------
@@ -143,13 +163,11 @@ renderAtom :: FeedConfiguration -- ^ Feed configuration
143 -> Context String -- ^ Item context
163 -> Context String -- ^ Item context
144 -> [Item String] -- ^ Feed items
164 -> [Item String] -- ^ Feed items
145 -> Compiler (Item String) -- ^ Resulting feed
165 -> Compiler (Item String) -- ^ Resulting feed
146−renderAtom config context = renderFeed
147− atomTemplate atomItemTemplate config
166+renderAtom = renderAtomWithTemplates atomTemplate atomItemTemplate
148− (makeItemContext "%Y-%m-%dT%H:%M:%SZ" context)
149
167
150
168
151--------------------------------------------------------------------------------
169--------------------------------------------------------------------------------
152-- | Copies @$updated$@ from @$published$@ if it is not already set.
170-- | Copies @$updated$@ from @$published$@ if it is not already set.
153makeItemContext :: String -> Context a -> Context a
171makeItemContext :: String -> Context a -> Context a
154makeItemContext fmt context = mconcat
172makeItemContext fmt context = mconcat
155− [dateField "published" fmt, context, dateField "updated" fmt]
173+ [context, dateField "published" fmt, dateField "updated" fmt]
lib/Hakyll/Web/Html.hs+1−1
@@ -59,7 +59,7 @@ demoteHeaders = withTags $ \tag -> case tag of
59
59
60--------------------------------------------------------------------------------
60--------------------------------------------------------------------------------
61isUrlAttribute :: String -> Bool
61isUrlAttribute :: String -> Bool
62−isUrlAttribute = (`elem` ["src", "href", "data", "poster"])
62+isUrlAttribute = (`elem` ["src", "href", "data", "poster", "srcset"])
63
63
64
64
65--------------------------------------------------------------------------------
65--------------------------------------------------------------------------------
lib/Hakyll/Web/Pandoc/Biblio.hs+13−5
@@ -25,17 +25,19 @@ module Hakyll.Web.Pandoc.Biblio
25--------------------------------------------------------------------------------
25--------------------------------------------------------------------------------
26import Control.Monad (liftM, replicateM)
26import Control.Monad (liftM, replicateM)
27import Data.Binary (Binary (..))
27import Data.Binary (Binary (..))
28−import Data.Default (def)
29import Data.Typeable (Typeable)
28import Data.Typeable (Typeable)
30import Hakyll.Core.Compiler
29import Hakyll.Core.Compiler
30+import Hakyll.Core.Compiler.Internal
31import Hakyll.Core.Identifier
31import Hakyll.Core.Identifier
32import Hakyll.Core.Item
32import Hakyll.Core.Item
33+import Hakyll.Core.Provider
33import Hakyll.Core.Writable
34import Hakyll.Core.Writable
34import Hakyll.Web.Pandoc
35import Hakyll.Web.Pandoc
35import Hakyll.Web.Pandoc.Binary ()
36import Hakyll.Web.Pandoc.Binary ()
36import qualified Text.CSL as CSL
37import qualified Text.CSL as CSL
37import Text.CSL.Pandoc (processCites)
38import Text.CSL.Pandoc (processCites)
38−import Text.Pandoc (Pandoc, ReaderOptions (..))
39+import Text.Pandoc (Pandoc, ReaderOptions (..),
40+ enableExtension, Extension (..))
39
41
40
42
41--------------------------------------------------------------------------------
43--------------------------------------------------------------------------------
@@ -83,7 +85,7 @@ instance Writable Biblio where
83--------------------------------------------------------------------------------
85--------------------------------------------------------------------------------
84biblioCompiler :: Compiler (Item Biblio)
86biblioCompiler :: Compiler (Item Biblio)
85biblioCompiler = do
87biblioCompiler = do
86− filePath <- toFilePath <$> getUnderlying
88+ filePath <- getResourceFilePath
87 makeItem =<< unsafeCompiler (Biblio <$> CSL.readBiblioFile idpred filePath)
89 makeItem =<< unsafeCompiler (Biblio <$> CSL.readBiblioFile idpred filePath)
88 where
90 where
89 -- This is a filter on citations. We include all citations.
91 -- This is a filter on citations. We include all citations.
@@ -98,7 +100,9 @@ readPandocBiblio :: ReaderOptions
98 -> Compiler (Item Pandoc)
100 -> Compiler (Item Pandoc)
99readPandocBiblio ropt csl biblio item = do
101readPandocBiblio ropt csl biblio item = do
100 -- Parse CSL file, if given
102 -- Parse CSL file, if given
103+ provider <- compilerProvider <$> compilerAsk
104+ style <- unsafeCompiler $
101− style <- unsafeCompiler $ CSL.readCSLFile Nothing . toFilePath . itemIdentifier $ csl
105+ CSL.readCSLFile Nothing . (resourceFilePath provider) . itemIdentifier $ csl
102
106
103 -- We need to know the citation keys, add then *before* actually parsing the
107 -- We need to know the citation keys, add then *before* actually parsing the
104 -- actual page. If we don't do this, pandoc won't even consider them
108 -- actual page. If we don't do this, pandoc won't even consider them
@@ -115,4 +119,8 @@ pandocBiblioCompiler cslFileName bibFileName = do
115 csl <- load $ fromFilePath cslFileName
119 csl <- load $ fromFilePath cslFileName
116 bib <- load $ fromFilePath bibFileName
120 bib <- load $ fromFilePath bibFileName
117 liftM writePandoc
121 liftM writePandoc
118− (getResourceBody >>= readPandocBiblio def csl bib)
122+ (getResourceBody >>= readPandocBiblio ropt csl bib)
123+ where ropt = defaultHakyllReaderOptions
124+ { -- The following option enables citation rendering
125+ readerExtensions = enableExtension Ext_citations $ readerExtensions defaultHakyllReaderOptions
126+ }
lib/Hakyll/Web/Pandoc/Binary.hs+1
@@ -28,5 +28,6 @@ instance Binary REF.CNum
28instance Binary REF.Literal
28instance Binary REF.Literal
29instance Binary REF.RefDate
29instance Binary REF.RefDate
30instance Binary REF.RefType
30instance Binary REF.RefType
31+instance Binary REF.Season
31instance Binary STY.Agent
32instance Binary STY.Agent
32instance Binary STY.Formatted
33instance Binary STY.Formatted
lib/Hakyll/Web/Tags.hs+2−1
@@ -43,6 +43,7 @@
43module Hakyll.Web.Tags
43module Hakyll.Web.Tags
44 ( Tags (..)
44 ( Tags (..)
45 , getTags
45 , getTags
46+ , getCategory
46 , buildTagsWith
47 , buildTagsWith
47 , buildTags
48 , buildTags
48 , buildCategories
49 , buildCategories
@@ -112,7 +113,7 @@ getTags identifier = do
112
113
113
114
114--------------------------------------------------------------------------------
115--------------------------------------------------------------------------------
115−-- | Obtain categories from a page.
116+-- | Obtain category from a page.
116getCategory :: MonadMetadata m => Identifier -> m [String]
117getCategory :: MonadMetadata m => Identifier -> m [String]
117getCategory = return . return . takeBaseName . takeDirectory . toFilePath
118getCategory = return . return . takeBaseName . takeDirectory . toFilePath
118
119
lib/Hakyll/Web/Template.hs+24−2
@@ -138,19 +138,41 @@
138-- > 3...2...1
138-- > 3...2...1
139-- > </p>
139-- > </p>
140--
140--
141+{-# LANGUAGE TemplateHaskell #-}
141module Hakyll.Web.Template
142module Hakyll.Web.Template
142 ( Template
143 ( Template
143− , template
144− , readTemplateElems
145 , templateBodyCompiler
144 , templateBodyCompiler
146 , templateCompiler
145 , templateCompiler
147 , applyTemplate
146 , applyTemplate
148 , loadAndApplyTemplate
147 , loadAndApplyTemplate
149 , applyAsTemplate
148 , applyAsTemplate
150 , readTemplate
149 , readTemplate
150+ , compileTemplateItem
151 , unsafeReadTemplateFile
151 , unsafeReadTemplateFile
152+ , embedTemplate
152 ) where
153 ) where
153
154
154
155
155--------------------------------------------------------------------------------
156--------------------------------------------------------------------------------
156import Hakyll.Web.Template.Internal
157import Hakyll.Web.Template.Internal
158+
159+
160+--------------------------------------------------------------------------------
161+import Data.FileEmbed (embedFile)
162+import qualified Data.Text as T
163+import qualified Data.Text.Encoding as T
164+import Language.Haskell.TH (Exp, Q)
165+
166+
167+--------------------------------------------------------------------------------
168+-- | Embed template allows you embed a template within the Haskell binary.
169+-- Example:
170+--
171+-- > myTemplate :: Template
172+-- > myTemplate = $(embedTemplate "test.html")
173+embedTemplate :: FilePath -> Q Exp
174+embedTemplate filePath = [|
175+ let source = T.unpack $ T.decodeUtf8 $(embedFile filePath) in
176+ case parseTemplateElemsFile filePath source of
177+ Left err -> error err
178+ Right tpl -> template filePath tpl |]
lib/Hakyll/Web/Template/Context.hs+135−33
@@ -1,6 +1,27 @@
1+-- | This module provides 'Context's which are used to expand expressions in
2+-- templates and allow for arbitrary customisation.
3+--
4+-- 'Template's define a small expression DSL which consists of strings,
5+-- identifiers and function application. There is no type system, every value is
6+-- a string and on the top level they get substituted verbatim into the page.
7+--
8+-- For example, you can build a context that contains
9+--
10+-- > … <> functionField "concat" (const . concat) <> …
11+--
12+-- which will allow you to use the @concat@ identifier as a function that takes
13+-- arbitrarily many stings and concatenates them to a new string:
14+--
15+-- > $partial(concat("templates/categories/", category))$
16+--
17+-- This will evaluate the @category@ field in the context, then prepend he path,
18+-- and include the referenced file as a template.
19+
20+
1--------------------------------------------------------------------------------
21--------------------------------------------------------------------------------
2{-# LANGUAGE CPP #-}
22{-# LANGUAGE CPP #-}
3{-# LANGUAGE ExistentialQuantification #-}
23{-# LANGUAGE ExistentialQuantification #-}
24+{-# LANGUAGE RankNTypes #-}
4module Hakyll.Web.Template.Context
25module Hakyll.Web.Template.Context
5 ( ContextField (..)
26 ( ContextField (..)
6 , Context (..)
27 , Context (..)
@@ -28,13 +49,17 @@ module Hakyll.Web.Template.Context
28 , teaserField
49 , teaserField
29 , teaserFieldWithSeparator
50 , teaserFieldWithSeparator
30 , missingField
51 , missingField
52+ , bindItem
31 ) where
53 ) where
32
54
33
55
34--------------------------------------------------------------------------------
56--------------------------------------------------------------------------------
35import Control.Applicative (Alternative (..))
57import Control.Applicative (Alternative (..))
36import Control.Monad (msum)
58import Control.Monad (msum)
37−import Data.List (intercalate)
59+import Data.List (intercalate, tails)
60+#if MIN_VERSION_base(4,9,0)
61+import Data.Semigroup (Semigroup (..))
62+#endif
38import Data.Time.Clock (UTCTime (..))
63import Data.Time.Clock (UTCTime (..))
39import Data.Time.Format (formatTime)
64import Data.Time.Format (formatTime)
40import qualified Data.Time.Format as TF
65import qualified Data.Time.Format as TF
@@ -47,13 +72,16 @@ import Hakyll.Core.Metadata
47import Hakyll.Core.Provider
72import Hakyll.Core.Provider
48import Hakyll.Core.Util.String (needlePrefix, splitAll)
73import Hakyll.Core.Util.String (needlePrefix, splitAll)
49import Hakyll.Web.Html
74import Hakyll.Web.Html
75+import Prelude hiding (id)
50−import System.FilePath (splitDirectories, takeBaseName)
76+import System.FilePath (dropExtension, splitDirectories,
77+ takeBaseName)
51
78
52
79
53--------------------------------------------------------------------------------
80--------------------------------------------------------------------------------
54-- | Mostly for internal usage
81-- | Mostly for internal usage
55data ContextField
82data ContextField
83+ = EmptyField
56− = StringField String
84+ | StringField String
57 | forall a. ListField (Context a) [Item a]
85 | forall a. ListField (Context a) [Item a]
58
86
59
87
@@ -78,71 +106,123 @@ newtype Context a = Context
78
106
79
107
80--------------------------------------------------------------------------------
108--------------------------------------------------------------------------------
109+-- | Tries to find a key in the left context,
110+-- or when that fails in the right context.
111+#if MIN_VERSION_base(4,9,0)
112+instance Semigroup (Context a) where
113+ (<>) (Context f) (Context g) = Context $ \k a i -> f k a i <|> g k a i
114+
115+instance Monoid (Context a) where
116+ mempty = missingField
117+ mappend = (<>)
118+#else
81instance Monoid (Context a) where
119instance Monoid (Context a) where
82 mempty = missingField
120 mempty = missingField
83 mappend (Context f) (Context g) = Context $ \k a i -> f k a i <|> g k a i
121 mappend (Context f) (Context g) = Context $ \k a i -> f k a i <|> g k a i
122+#endif
84
123
85
124
86--------------------------------------------------------------------------------
125--------------------------------------------------------------------------------
87field' :: String -> (Item a -> Compiler ContextField) -> Context a
126field' :: String -> (Item a -> Compiler ContextField) -> Context a
88−field' key value = Context $ \k _ i -> if k == key then value i else empty
127+field' key value = Context $ \k _ i ->
128+ if k == key
129+ then value i
130+ else noResult $ "Tried field " ++ key
89
131
90
132
91--------------------------------------------------------------------------------
133--------------------------------------------------------------------------------
92−-- | Constructs a new field in the 'Context.'
134+-- | Constructs a new field for a 'Context'.
135+-- If the key matches, the compiler is run and its result is substituted in the
136+-- template.
137+--
138+-- If the compiler fails, the field will be considered non-existent
139+-- in an @$if()$@ macro or ultimately break the template application
140+-- (unless the key is found in another context when using '<>').
141+-- Use 'empty' or 'noResult' for intentional failures of fields used in
142+-- @$if()$@, to distinguish them from exceptions thrown with 'fail'.
93field
143field
94 :: String -- ^ Key
144 :: String -- ^ Key
95 -> (Item a -> Compiler String) -- ^ Function that constructs a value based
145 -> (Item a -> Compiler String) -- ^ Function that constructs a value based
96− -- on the item
146+ -- on the item (e.g. accessing metadata)
97 -> Context a
147 -> Context a
98field key value = field' key (fmap StringField . value)
148field key value = field' key (fmap StringField . value)
99
149
100
150
101--------------------------------------------------------------------------------
151--------------------------------------------------------------------------------
102-- | Creates a 'field' to use with the @$if()$@ template macro.
152-- | Creates a 'field' to use with the @$if()$@ template macro.
153+-- Attempting to substitute the field into the template will cause an error.
103boolField
154boolField
104 :: String
155 :: String
105 -> (Item a -> Bool)
156 -> (Item a -> Bool)
106 -> Context a
157 -> Context a
107−boolField name f = field name (\i -> if f i
158+boolField name f = field' name (\i -> if f i
108− then pure (error $ unwords ["no string value for bool field:",name])
159+ then return EmptyField
109− else empty)
160+ else noResult $ "Field " ++ name ++ " is false")
110
161
111
162
112--------------------------------------------------------------------------------
163--------------------------------------------------------------------------------
113−-- | Creates a 'field' that does not depend on the 'Item'
164+-- | Creates a 'field' that does not depend on the 'Item' but always yields
165+-- the same string
114−constField :: String -> String -> Context a
166+constField :: String -- ^ Key
167+ -> String -- ^ Value
168+ -> Context a
115constField key = field key . const . return
169constField key = field key . const . return
116
170
117
171
118--------------------------------------------------------------------------------
172--------------------------------------------------------------------------------
173+-- | Creates a list field to be consumed by a @$for(…)$@ expression.
174+-- The compiler returns multiple items which are rendered in the loop body
175+-- with the supplied context.
119listField :: String -> Context a -> Compiler [Item a] -> Context b
176listField :: String -> Context a -> Compiler [Item a] -> Context b
120listField key c xs = listFieldWith key c (const xs)
177listField key c xs = listFieldWith key c (const xs)
121
178
122
179
123--------------------------------------------------------------------------------
180--------------------------------------------------------------------------------
181+-- | Produces a new 'Context' which has list field 'key'. All fields from
182+-- 'c' are also accessible from the produced context.
183+-- Be careful when doing @listFieldWith k ca f <> cb@ as any fields in @ca@
184+-- will override fields in @cb@ with the same name.
185+-- Creates a list field like 'listField', but supplies the current page
186+-- to the compiler.
124listFieldWith
187listFieldWith
125 :: String -> Context a -> (Item b -> Compiler [Item a]) -> Context b
188 :: String -> Context a -> (Item b -> Compiler [Item a]) -> Context b
126listFieldWith key c f = field' key $ fmap (ListField c) . f
189listFieldWith key c f = field' key $ fmap (ListField c) . f
127
190
128
191
129--------------------------------------------------------------------------------
192--------------------------------------------------------------------------------
193+-- | Creates a variadic function field.
194+--
195+-- The function will be called with the dynamically evaluated string arguments
196+-- from the template as well as the page that is currently rendered.
197+functionField :: String -- ^ Key
130−functionField :: String -> ([String] -> Item a -> Compiler String) -> Context a
198+ -> ([String] -> Item a -> Compiler String) -- ^ Function
199+ -> Context a
131functionField name value = Context $ \k args i ->
200functionField name value = Context $ \k args i ->
132 if k == name
201 if k == name
133 then StringField <$> value args i
202 then StringField <$> value args i
134− else empty
203+ else noResult $ "Tried function field " ++ name
135
204
136
205
137--------------------------------------------------------------------------------
206--------------------------------------------------------------------------------
207+-- | Transform the respective string results of all fields in a context.
208+-- For example,
209+--
210+-- > mapContext (++"c") (constField "x" "a" <> constField "y" "b")
211+--
212+-- is equivalent to
213+--
214+-- > constField "x" "ac" <> constField "y" "bc"
215+--
138mapContext :: (String -> String) -> Context a -> Context a
216mapContext :: (String -> String) -> Context a -> Context a
139mapContext f (Context c) = Context $ \k a i -> do
217mapContext f (Context c) = Context $ \k a i -> do
140 fld <- c k a i
218 fld <- c k a i
141 case fld of
219 case fld of
220+ EmptyField -> wrongType "boolField"
142 StringField str -> return $ StringField (f str)
221 StringField str -> return $ StringField (f str)
143− ListField _ _ -> fail $
222+ _ -> wrongType "ListField"
223+ where
144− "Hakyll.Web.Template.Context.mapContext: " ++
224+ wrongType typ = fail $ "Hakyll.Web.Template.Context.mapContext: " ++
145− "can't map over a ListField!"
225+ "can't map over a " ++ typ ++ "!"
146
226
147--------------------------------------------------------------------------------
227--------------------------------------------------------------------------------
148-- | A context that allows snippet inclusion. In processed file, use as:
228-- | A context that allows snippet inclusion. In processed file, use as:
@@ -151,15 +231,15 @@ mapContext f (Context c) = Context $ \k a i -> do
151-- > $snippet("path/to/snippet/")$
231-- > $snippet("path/to/snippet/")$
152-- > ...
232-- > ...
153--
233--
154−-- The contents of the included file will not be interpolated.
234+-- The contents of the included file will not be interpolated like @partial@
235+-- does it.
155--
236--
156snippetField :: Context String
237snippetField :: Context String
157snippetField = functionField "snippet" f
238snippetField = functionField "snippet" f
158 where
239 where
159 f [contentsPath] _ = loadBody (fromFilePath contentsPath)
240 f [contentsPath] _ = loadBody (fromFilePath contentsPath)
160− f _ i = error $
241+ f [] _ = fail "No argument to function 'snippet()'"
161− "Too many arguments to function 'snippet()' in item " ++
242+ f _ _ = fail "Too many arguments to function 'snippet()'"
162− show (itemIdentifier i)
163
243
164--------------------------------------------------------------------------------
244--------------------------------------------------------------------------------
165-- | A context that contains (in that order)
245-- | A context that contains (in that order)
@@ -179,8 +259,7 @@ defaultContext =
179 metadataField `mappend`
259 metadataField `mappend`
180 urlField "url" `mappend`
260 urlField "url" `mappend`
181 pathField "path" `mappend`
261 pathField "path" `mappend`
182− titleField "title" `mappend`
262+ titleField "title"
183− missingField
184
263
185
264
186--------------------------------------------------------------------------------
265--------------------------------------------------------------------------------
@@ -198,15 +277,20 @@ bodyField key = field key $ return . itemBody
198-- | Map any field to its metadata value, if present
277-- | Map any field to its metadata value, if present
199metadataField :: Context a
278metadataField :: Context a
200metadataField = Context $ \k _ i -> do
279metadataField = Context $ \k _ i -> do
201− value <- getMetadataField (itemIdentifier i) k
280+ let id = itemIdentifier i
281+ empty' = noResult $ "No '" ++ k ++ "' field in metadata " ++
282+ "of item " ++ show id
283+ value <- getMetadataField id k
202− maybe empty (return . StringField) value
284+ maybe empty' (return . StringField) value
203
285
204
286
205--------------------------------------------------------------------------------
287--------------------------------------------------------------------------------
206-- | Absolute url to the resulting item
288-- | Absolute url to the resulting item
207urlField :: String -> Context a
289urlField :: String -> Context a
208−urlField key = field key $
290+urlField key = field key $ \i -> do
291+ let id = itemIdentifier i
292+ empty' = fail $ "No route url found for item " ++ show id
209− fmap (maybe empty toUrl) . getRoute . itemIdentifier
293+ fmap (maybe empty' toUrl) $ getRoute id
210
294
211
295
212--------------------------------------------------------------------------------
296--------------------------------------------------------------------------------
@@ -257,6 +341,12 @@ titleField = mapContext takeBaseName . pathField
257-- that begins with @yyyy-mm-dd@ . For example:
341-- that begins with @yyyy-mm-dd@ . For example:
258-- @folder//yyyy-mm-dd-title//dist//main.extension@ .
342-- @folder//yyyy-mm-dd-title//dist//main.extension@ .
259-- In case of multiple matches, the rightmost one is used.
343-- In case of multiple matches, the rightmost one is used.
344+--
345+-- As another alternative, if none of the above matches, and the file has a
346+-- path which contains nested directories specifying a date, then that date
347+-- will be used. In other words, if the path is of the form
348+-- @**//yyyy//mm//dd//**//main.extension@ .
349+-- As above, in case of multiple matches, the rightmost one is used.
260
350
261dateField :: String -- ^ Key in which the rendered date should be placed
351dateField :: String -- ^ Key in which the rendered date should be placed
262 -> String -- ^ Format to use on the date
352 -> String -- ^ Format to use on the date
@@ -267,7 +357,7 @@ dateField = dateFieldWith defaultTimeLocale
267--------------------------------------------------------------------------------
357--------------------------------------------------------------------------------
268-- | This is an extended version of 'dateField' that allows you to
358-- | This is an extended version of 'dateField' that allows you to
269-- specify a time locale that is used for outputting the date. For more
359-- specify a time locale that is used for outputting the date. For more
270−-- details, see 'dateField'.
360+-- details, see 'dateField' and 'formatTime'.
271dateFieldWith :: TimeLocale -- ^ Output time locale
361dateFieldWith :: TimeLocale -- ^ Output time locale
272 -> String -- ^ Destination key
362 -> String -- ^ Destination key
273 -> String -- ^ Format to use on the date
363 -> String -- ^ Format to use on the date
@@ -288,12 +378,13 @@ getItemUTC :: MonadMetadata m
288getItemUTC locale id' = do
378getItemUTC locale id' = do
289 metadata <- getMetadata id'
379 metadata <- getMetadata id'
290 let tryField k fmt = lookupString k metadata >>= parseTime' fmt
380 let tryField k fmt = lookupString k metadata >>= parseTime' fmt
291− paths = splitDirectories $ toFilePath id'
381+ paths = splitDirectories $ (dropExtension . toFilePath) id'
292
382
293 maybe empty' return $ msum $
383 maybe empty' return $ msum $
294 [tryField "published" fmt | fmt <- formats] ++
384 [tryField "published" fmt | fmt <- formats] ++
295 [tryField "date" fmt | fmt <- formats] ++
385 [tryField "date" fmt | fmt <- formats] ++
296− [parseTime' "%Y-%m-%d" $ intercalate "-" $ take 3 $ splitAll "-" fnCand | fnCand <- reverse paths]
386+ [parseTime' "%Y-%m-%d" $ intercalate "-" $ take 3 $ splitAll "-" fnCand | fnCand <- reverse paths] ++
387+ [parseTime' "%Y-%m-%d" $ intercalate "-" $ fnCand | fnCand <- map (take 3) $ reverse . tails $ paths]
297 where
388 where
298 empty' = fail $ "Hakyll.Web.Template.Context.getItemUTC: " ++
389 empty' = fail $ "Hakyll.Web.Template.Context.getItemUTC: " ++
299 "could not parse time for " ++ show id'
390 "could not parse time for " ++ show id'
@@ -321,13 +412,16 @@ getItemModificationTime identifier = do
321
412
322
413
323--------------------------------------------------------------------------------
414--------------------------------------------------------------------------------
415+-- | Creates a field with the last modification date of the underlying item.
324modificationTimeField :: String -- ^ Key
416modificationTimeField :: String -- ^ Key
325 -> String -- ^ Format
417 -> String -- ^ Format
326− -> Context a -- ^ Resuting context
418+ -> Context a -- ^ Resulting context
327modificationTimeField = modificationTimeFieldWith defaultTimeLocale
419modificationTimeField = modificationTimeFieldWith defaultTimeLocale
328
420
329
421
330--------------------------------------------------------------------------------
422--------------------------------------------------------------------------------
423+-- | Creates a field with the last modification date of the underlying item
424+-- in a custom localisation format (see 'formatTime').
331modificationTimeFieldWith :: TimeLocale -- ^ Time output locale
425modificationTimeFieldWith :: TimeLocale -- ^ Time output locale
332 -> String -- ^ Key
426 -> String -- ^ Key
333 -> String -- ^ Format
427 -> String -- ^ Format
@@ -366,10 +460,11 @@ teaserFieldWithSeparator separator key snapshot = field key $ \item -> do
366
460
367
461
368--------------------------------------------------------------------------------
462--------------------------------------------------------------------------------
463+-- | Constantly reports any field as missing. Mostly for internal usage,
464+-- it is the last choice in every context used in a template application.
369missingField :: Context a
465missingField :: Context a
370−missingField = Context $ \k _ i -> fail $
466+missingField = Context $ \k _ _ -> noResult $
371− "Missing field $" ++ k ++ "$ in context for item " ++
467+ "Missing field '" ++ k ++ "' in context"
372− show (itemIdentifier i)
373
468
374parseTimeM :: Bool -> TimeLocale -> String -> String -> Maybe UTCTime
469parseTimeM :: Bool -> TimeLocale -> String -> String -> Maybe UTCTime
375#if MIN_VERSION_time(1,5,0)
470#if MIN_VERSION_time(1,5,0)
@@ -377,3 +472,10 @@ parseTimeM = TF.parseTimeM
377#else
472#else
378parseTimeM _ = TF.parseTime
473parseTimeM _ = TF.parseTime
379#endif
474#endif
475+
476+--------------------------------------------------------------------------------
477+
478+-- | Binds an 'Item' to a given 'Context', allowing it to be combined with any
479+-- other 'Context' of any type.
480+bindItem :: Context a -> Item a -> forall b. Context b
481+bindItem (Context ctx) ia = Context $ \k args _ -> ctx k args ia
lib/Hakyll/Web/Template/Internal.hs+103−44
@@ -1,15 +1,18 @@
1+{-# LANGUAGE DeriveAnyClass #-}
2+{-# LANGUAGE DeriveGeneric #-}
1{-# LANGUAGE GeneralizedNewtypeDeriving #-}
3{-# LANGUAGE GeneralizedNewtypeDeriving #-}
2{-# LANGUAGE ScopedTypeVariables #-}
4{-# LANGUAGE ScopedTypeVariables #-}
5+
3module Hakyll.Web.Template.Internal
6module Hakyll.Web.Template.Internal
4 ( Template (..)
7 ( Template (..)
5 , template
8 , template
6 , templateBodyCompiler
9 , templateBodyCompiler
7 , templateCompiler
10 , templateCompiler
8 , applyTemplate
11 , applyTemplate
9− , applyTemplate'
10 , loadAndApplyTemplate
12 , loadAndApplyTemplate
11 , applyAsTemplate
13 , applyAsTemplate
12 , readTemplate
14 , readTemplate
15+ , compileTemplateItem
13 , unsafeReadTemplateFile
16 , unsafeReadTemplateFile
14
17
15 , module Hakyll.Web.Template.Internal.Element
18 , module Hakyll.Web.Template.Internal.Element
@@ -18,16 +21,19 @@ module Hakyll.Web.Template.Internal
18
21
19
22
20--------------------------------------------------------------------------------
23--------------------------------------------------------------------------------
21−import Control.Monad.Except (MonadError (..))
24+import Control.Monad (forM)
25+import Control.Monad.Except (MonadError (..), catchError)
22import Data.Binary (Binary)
26import Data.Binary (Binary)
23import Data.List (intercalate)
27import Data.List (intercalate)
28+import qualified Data.List.NonEmpty as NonEmpty
24import Data.Typeable (Typeable)
29import Data.Typeable (Typeable)
25import GHC.Exts (IsString (..))
30import GHC.Exts (IsString (..))
26−import Prelude hiding (id)
31+import GHC.Generics (Generic)
27
32
28
33
29--------------------------------------------------------------------------------
34--------------------------------------------------------------------------------
30import Hakyll.Core.Compiler
35import Hakyll.Core.Compiler
36+import Hakyll.Core.Compiler.Internal
31import Hakyll.Core.Identifier
37import Hakyll.Core.Identifier
32import Hakyll.Core.Item
38import Hakyll.Core.Item
33import Hakyll.Core.Writable
39import Hakyll.Core.Writable
@@ -38,9 +44,10 @@ import Hakyll.Web.Template.Internal.Trim
38
44
39--------------------------------------------------------------------------------
45--------------------------------------------------------------------------------
40-- | Datatype used for template substitutions.
46-- | Datatype used for template substitutions.
41−newtype Template = Template
47+data Template = Template
42− { unTemplate :: [TemplateElement]
48+ { tplElements :: [TemplateElement]
49+ , tplOrigin :: FilePath -- Only for error messages.
43− } deriving (Show, Eq, Binary, Typeable)
50+ } deriving (Show, Eq, Generic, Binary, Typeable)
44
51
45
52
46--------------------------------------------------------------------------------
53--------------------------------------------------------------------------------
@@ -56,39 +63,68 @@ instance IsString Template where
56
63
57--------------------------------------------------------------------------------
64--------------------------------------------------------------------------------
58-- | Wrap the constructor to ensure trim is called.
65-- | Wrap the constructor to ensure trim is called.
59−template :: [TemplateElement] -> Template
66+template :: FilePath -> [TemplateElement] -> Template
60−template = Template . trim
67+template p = flip Template p . trim
61
68
62
69
63--------------------------------------------------------------------------------
70--------------------------------------------------------------------------------
71+-- | Parse a string into a template.
72+-- You should prefer 'compileTemplateItem' over this.
64readTemplate :: String -> Template
73readTemplate :: String -> Template
65−readTemplate = Template . trim . readTemplateElems
74+readTemplate = either error (template origin) . parseTemplateElemsFile origin
75+ where
76+ origin = "{literal}"
77+{-# DEPRECATED readTemplate "Use templateCompiler instead" #-}
78+
79+--------------------------------------------------------------------------------
80+-- | Parse an item body into a template.
81+-- Provides useful error messages in the 'Compiler' monad.
82+compileTemplateItem :: Item String -> Compiler Template
83+compileTemplateItem item = let file = itemIdentifier item
84+ in compileTemplateFile file (itemBody item)
85+
86+--------------------------------------------------------------------------------
87+compileTemplateFile :: Identifier -> String -> Compiler Template
88+compileTemplateFile file = either fail (return . template origin)
89+ . parseTemplateElemsFile origin
90+ where
91+ origin = show file
66
92
67--------------------------------------------------------------------------------
93--------------------------------------------------------------------------------
68-- | Read a template, without metadata header
94-- | Read a template, without metadata header
69templateBodyCompiler :: Compiler (Item Template)
95templateBodyCompiler :: Compiler (Item Template)
70templateBodyCompiler = cached "Hakyll.Web.Template.templateBodyCompiler" $ do
96templateBodyCompiler = cached "Hakyll.Web.Template.templateBodyCompiler" $ do
71 item <- getResourceBody
97 item <- getResourceBody
72− file <- getResourceFilePath
98+ file <- getUnderlying
73− return $ fmap (template . readTemplateElemsFile file) item
99+ withItemBody (compileTemplateFile file) item
74
100
75--------------------------------------------------------------------------------
101--------------------------------------------------------------------------------
76-- | Read complete file contents as a template
102-- | Read complete file contents as a template
77templateCompiler :: Compiler (Item Template)
103templateCompiler :: Compiler (Item Template)
78templateCompiler = cached "Hakyll.Web.Template.templateCompiler" $ do
104templateCompiler = cached "Hakyll.Web.Template.templateCompiler" $ do
79 item <- getResourceString
105 item <- getResourceString
80− file <- getResourceFilePath
106+ file <- getUnderlying
81− return $ fmap (template . readTemplateElemsFile file) item
107+ withItemBody (compileTemplateFile file) item
82
108
83
109
84--------------------------------------------------------------------------------
110--------------------------------------------------------------------------------
111+-- | Interpolate template expressions from context values in a page
85applyTemplate :: Template -- ^ Template
112applyTemplate :: Template -- ^ Template
86 -> Context a -- ^ Context
113 -> Context a -- ^ Context
87 -> Item a -- ^ Page
114 -> Item a -- ^ Page
88 -> Compiler (Item String) -- ^ Resulting item
115 -> Compiler (Item String) -- ^ Resulting item
89applyTemplate tpl context item = do
116applyTemplate tpl context item = do
90− body <- applyTemplate' (unTemplate tpl) context item
117+ body <- applyTemplate' (tplElements tpl) context item `catchError` handler
91 return $ itemSetBody body item
118 return $ itemSetBody body item
119+ where
120+ tplName = tplOrigin tpl
121+ itemName = show $ itemIdentifier item
122+ handler es = fail $ "Hakyll.Web.Template.applyTemplate: Failed to " ++
123+ (if tplName == itemName
124+ then "interpolate template in item " ++ itemName
125+ else "apply template " ++ tplName ++ " to item " ++ itemName) ++
126+ ":\n" ++ intercalate ",\n" es
127+
92
128
93
129
94--------------------------------------------------------------------------------
130--------------------------------------------------------------------------------
@@ -105,9 +141,6 @@ applyTemplate' tes context x = go tes
105
141
106 go = fmap concat . mapM applyElem
142 go = fmap concat . mapM applyElem
107
143
108− trimError = error $ "Hakyll.Web.Template.applyTemplate: template not " ++
109− "fully trimmed."
110−
111 ---------------------------------------------------------------------------
144 ---------------------------------------------------------------------------
112
145
113 applyElem :: TemplateElement -> Compiler String
146 applyElem :: TemplateElement -> Compiler String
@@ -118,29 +151,43 @@ applyTemplate' tes context x = go tes
118
151
119 applyElem (Chunk c) = return c
152 applyElem (Chunk c) = return c
120
153
121− applyElem (Expr e) = applyExpr e >>= getString e
154+ applyElem (Expr e) = withErrorMessage evalMsg (applyStringExpr typeMsg e)
155+ where
156+ evalMsg = "In expr '$" ++ show e ++ "$'"
157+ typeMsg = "expr '$" ++ show e ++ "$'"
122
158
123 applyElem Escaped = return "$"
159 applyElem Escaped = return "$"
124
160
125− applyElem (If e t mf) = (applyExpr e >> go t) `catchError` handler
161+ applyElem (If e t mf) = compilerTry (applyExpr e) >>= handle
126 where
162 where
127− handler _ = case mf of
163+ f = maybe (return "") go mf
128− Nothing -> return ""
164+ handle (Right _) = go t
129− Just f -> go f
165+ handle (Left (CompilationNoResult _)) = f
130−
166+ handle (Left (CompilationFailure es)) = debug (NonEmpty.toList es) >> f
167+ debug = compilerDebugEntries ("Hakyll.Web.Template.applyTemplate: " ++
168+ "[ERROR] in 'if' condition on expr '" ++ show e ++ "':")
169+
131− applyElem (For e b s) = applyExpr e >>= \cf -> case cf of
170+ applyElem (For e b s) = withErrorMessage headMsg (applyExpr e) >>= \cf -> case cf of
132− StringField _ -> fail $
171+ EmptyField -> expected "list" "boolean" typeMsg
133− "Hakyll.Web.Template.applyTemplateWith: expected ListField but " ++
172+ StringField _ -> expected "list" "string" typeMsg
134− "got StringField for expr " ++ show e
135− ListField c xs -> do
173+ ListField c xs -> withErrorMessage bodyMsg $ do
136 sep <- maybe (return "") go s
174 sep <- maybe (return "") go s
137− bs <- mapM (applyTemplate' b c) xs
175+ bs <- forM xs $ applyTemplate' b $ c <> bindItem context x
138 return $ intercalate sep bs
176 return $ intercalate sep bs
139−
177+ where
178+ headMsg = "In expr '$for(" ++ show e ++ ")$'"
179+ typeMsg = "loop expr '" ++ show e ++ "'"
180+ bodyMsg = "In loop context of '$for(" ++ show e ++ ")$'"
181+
140− applyElem (Partial e) = do
182+ applyElem (Partial e) = withErrorMessage headMsg $
141− p <- applyExpr e >>= getString e
183+ applyStringExpr typeMsg e >>= \p ->
184+ withErrorMessage inclMsg $ do
142− Template tpl' <- loadBody (fromFilePath p)
185+ tpl' <- loadBody (fromFilePath p)
143− applyTemplate' tpl' context x
186+ itemBody <$> applyTemplate tpl' context x
187+ where
188+ headMsg = "In expr '$partial(" ++ show e ++ ")$'"
189+ typeMsg = "partial expr '" ++ show e ++ "'"
190+ inclMsg = "In inclusion of '$partial(" ++ show e ++ ")$'"
144
191
145 ---------------------------------------------------------------------------
192 ---------------------------------------------------------------------------
146
193
@@ -149,17 +196,29 @@ applyTemplate' tes context x = go tes
149 applyExpr (Ident (TemplateKey k)) = context' k [] x
196 applyExpr (Ident (TemplateKey k)) = context' k [] x
150
197
151 applyExpr (Call (TemplateKey k) args) = do
198 applyExpr (Call (TemplateKey k) args) = do
152− args' <- mapM (\e -> applyExpr e >>= getString e) args
199+ args' <- mapM (\e -> applyStringExpr (typeMsg e) e) args
153 context' k args' x
200 context' k args' x
201+ where
202+ typeMsg e = "argument '" ++ show e ++ "'"
154
203
155 applyExpr (StringLiteral s) = return (StringField s)
204 applyExpr (StringLiteral s) = return (StringField s)
156
205
157 ----------------------------------------------------------------------------
206 ----------------------------------------------------------------------------
158
207
208+ applyStringExpr :: String -> TemplateExpr -> Compiler String
209+ applyStringExpr msg expr =
210+ applyExpr expr >>= getString
211+ where
212+ getString EmptyField = expected "string" "boolean" msg
159− getString _ (StringField s) = return s
213+ getString (StringField s) = return s
160− getString e (ListField _ _) = fail $
214+ getString (ListField _ _) = expected "string" "list" msg
161− "Hakyll.Web.Template.applyTemplateWith: expected StringField but " ++
215+
162− "got ListField for expr " ++ show e
216+ expected typ act expr = fail $ unwords ["Hakyll.Web.Template.applyTemplate:",
217+ "expected", typ, "but got", act, "for", expr]
218+
219+ -- expected to never happen with all templates constructed by 'template'
220+ trimError = fail $
221+ "Hakyll.Web.Template.applyTemplate: template not fully trimmed."
163
222
164
223
165--------------------------------------------------------------------------------
224--------------------------------------------------------------------------------
@@ -189,14 +248,14 @@ loadAndApplyTemplate identifier context item = do
189applyAsTemplate :: Context String -- ^ Context
248applyAsTemplate :: Context String -- ^ Context
190 -> Item String -- ^ Item and template
249 -> Item String -- ^ Item and template
191 -> Compiler (Item String) -- ^ Resulting item
250 -> Compiler (Item String) -- ^ Resulting item
192−applyAsTemplate context item =
251+applyAsTemplate context item = do
193− let tpl = template $ readTemplateElemsFile file (itemBody item)
252+ tpl <- compileTemplateItem item
194− file = toFilePath $ itemIdentifier item
195− in applyTemplate tpl context item
253+ applyTemplate tpl context item
196
254
197
255
198--------------------------------------------------------------------------------
256--------------------------------------------------------------------------------
199unsafeReadTemplateFile :: FilePath -> Compiler Template
257unsafeReadTemplateFile :: FilePath -> Compiler Template
200unsafeReadTemplateFile file = do
258unsafeReadTemplateFile file = do
201 tpl <- unsafeCompiler $ readFile file
259 tpl <- unsafeCompiler $ readFile file
202− pure $ template $ readTemplateElemsFile file tpl
260+ compileTemplateFile (fromFilePath file) tpl
261+{-# DEPRECATED unsafeReadTemplateFile "Use templateCompiler" #-}
lib/Hakyll/Web/Template/Internal/Element.hs+7−14
@@ -7,14 +7,14 @@ module Hakyll.Web.Template.Internal.Element
7 , TemplateExpr (..)
7 , TemplateExpr (..)
8 , TemplateElement (..)
8 , TemplateElement (..)
9 , templateElems
9 , templateElems
10− , readTemplateElems
10+ , parseTemplateElemsFile
11− , readTemplateElemsFile
12 ) where
11 ) where
13
12
14
13
15--------------------------------------------------------------------------------
14--------------------------------------------------------------------------------
16−import Control.Applicative ((<|>))
15+import Control.Applicative ((<|>), (<*))
17import Control.Monad (void)
16import Control.Monad (void)
17+import Control.Arrow (left)
18import Data.Binary (Binary, get, getWord8, put, putWord8)
18import Data.Binary (Binary, get, getWord8, put, putWord8)
19import Data.List (intercalate)
19import Data.List (intercalate)
20import Data.Maybe (isJust)
20import Data.Maybe (isJust)
@@ -107,17 +107,10 @@ instance Binary TemplateExpr where
107 2 -> StringLiteral <$> get
107 2 -> StringLiteral <$> get
108 _ -> error "Hakyll.Web.Template.Internal: Error reading cached template"
108 _ -> error "Hakyll.Web.Template.Internal: Error reading cached template"
109
109
110−
111−--------------------------------------------------------------------------------
112−readTemplateElems :: String -> [TemplateElement]
113−readTemplateElems = readTemplateElemsFile "{literal}"
114−
115−
116--------------------------------------------------------------------------------
110--------------------------------------------------------------------------------
117−readTemplateElemsFile :: FilePath -> String -> [TemplateElement]
111+parseTemplateElemsFile :: FilePath -> String -> Either String [TemplateElement]
118−readTemplateElemsFile file input = case P.parse templateElems file input of
119− Left err -> error $ "Cannot parse template: " ++ show err
112+parseTemplateElemsFile file = left (\e -> "Cannot parse template " ++ show e)
120− Right t -> t
113+ . P.parse (templateElems <* P.eof) file
121
114
122
115
123--------------------------------------------------------------------------------
116--------------------------------------------------------------------------------
@@ -167,7 +160,7 @@ trimOpen = do
167--------------------------------------------------------------------------------
160--------------------------------------------------------------------------------
168trimClose :: P.Parser Bool
161trimClose :: P.Parser Bool
169trimClose = do
162trimClose = do
170− trimIfR <- P.optionMaybe $ P.try (P.char '-')
163+ trimIfR <- P.optionMaybe $ (P.char '-')
171 void $ P.char '$'
164 void $ P.char '$'
172 pure $ isJust trimIfR
165 pure $ isJust trimIfR
173
166
src/Init.hs+7−4
@@ -6,7 +6,7 @@ module Main
6
6
7--------------------------------------------------------------------------------
7--------------------------------------------------------------------------------
8import Control.Arrow (first)
8import Control.Arrow (first)
9−import Control.Monad (forM_)
9+import Control.Monad (forM, forM_)
10import Data.Char (isAlphaNum, isNumber)
10import Data.Char (isAlphaNum, isNumber)
11import Data.List (foldl', intercalate, isPrefixOf)
11import Data.List (foldl', intercalate, isPrefixOf)
12import Data.Version (Version (..))
12import Data.Version (Version (..))
@@ -22,6 +22,10 @@ import Hakyll.Core.Util.File
22import Paths_hakyll
22import Paths_hakyll
23
23
24
24
25+--------------------------------------------------------------------------------
26+import Prelude
27+
28+
25--------------------------------------------------------------------------------
29--------------------------------------------------------------------------------
26main :: IO ()
30main :: IO ()
27main = do
31main = do
@@ -66,17 +70,16 @@ main = do
66 createCabal cabalPath name
70 createCabal cabalPath name
67 fs -> do
71 fs -> do
68 putStrLn $ "The following files will be overwritten:"
72 putStrLn $ "The following files will be overwritten:"
69− foldMap putStrLn fs
73+ mapM_ putStrLn fs
70 putStrLn $ "Use -f to overwrite them"
74 putStrLn $ "Use -f to overwrite them"
71 exitFailure
75 exitFailure
72
76
73existingFiles :: FilePath -> [FilePath] -> IO [FilePath]
77existingFiles :: FilePath -> [FilePath] -> IO [FilePath]
74−existingFiles dstDir = foldMap $ \file -> do
78+existingFiles dstDir files = fmap concat $ forM files $ \file -> do
75 let dst = dstDir </> file
79 let dst = dstDir </> file
76 exists <- doesFileExist dst
80 exists <- doesFileExist dst
77 return $ if exists then [dst] else []
81 return $ if exists then [dst] else []
78
82
79−
80-- | Figure out a good cabal package name from the given (existing) directory
83-- | Figure out a good cabal package name from the given (existing) directory
81-- name
84-- name
82makeName :: FilePath -> IO String
85makeName :: FilePath -> IO String
stack.yaml+22−17
@@ -1,28 +1,33 @@
1−resolver: nightly-2018-01-15
1+resolver: lts-13.29
2−extra-package-dbs: []
2+save-hackage-creds: false
3
3
4flags:
4flags:
5 hakyll:
5 hakyll:
6− previewServer: True
6+ previewServer: true
7− watchServer: True
7+ watchServer: true
8− checkExternal: True
8+ checkExternal: true
9+ usePandoc: true
9− buildWebsite: True
10+ buildWebsite: true
10
11
11packages:
12packages:
12 - '.'
13 - '.'
13
14
14−allow-newer: True
15−
16−extra-deps:
17− - 'binary-0.9.0.0'
18− - 'http-types-0.11'
19− - 'pandoc-2.1'
20− - 'pandoc-citeproc-0.13'
21− - 'QuickCheck-2.11.3'
22− - 'tasty-1.0.0.1'
23− - 'tasty-hunit-0.10.0.1'
24−
25nix:
15nix:
26 enable: false
16 enable: false
27 packages:
17 packages:
28 - zlib
18 - zlib
19+
20+build:
21+ haddock: true
22+ haddock-hyperlink-source: true
23+ haddock-deps: false
24+
25+extra-deps:
26+- 'cmark-gfm-0.2.0'
27+- 'hslua-module-system-0.2.1'
28+- 'ipynb-0.1'
29+- 'lrucache-1.2.0.1'
30+- 'pandoc-2.7.3'
31+- 'pandoc-citeproc-0.16.2'
32+- 'skylighting-0.8.2'
33+- 'skylighting-core-0.8.2'
tests/Hakyll/Core/Rules/Tests.hs+1−2
@@ -17,7 +17,6 @@ import Hakyll.Core.Metadata
17import Hakyll.Core.Routes
17import Hakyll.Core.Routes
18import Hakyll.Core.Rules
18import Hakyll.Core.Rules
19import Hakyll.Core.Rules.Internal
19import Hakyll.Core.Rules.Internal
20−import Hakyll.Web.Pandoc
21import System.FilePath ((</>))
20import System.FilePath ((</>))
22import Test.Tasty (TestTree, testGroup)
21import Test.Tasty (TestTree, testGroup)
23import Test.Tasty.HUnit (Assertion, (@=?))
22import Test.Tasty.HUnit (Assertion, (@=?))
@@ -75,7 +74,7 @@ rules01 ioref = do
75 -- Compile some posts
74 -- Compile some posts
76 match "*.md" $ do
75 match "*.md" $ do
77 route $ setExtension "html"
76 route $ setExtension "html"
78− compile pandocCompiler
77+ compile copyFileCompiler
79
78
80 -- Yeah. I don't know how else to test this stuff?
79 -- Yeah. I don't know how else to test this stuff?
81 preprocess $ writeIORef ioref True
80 preprocess $ writeIORef ioref True
tests/Hakyll/Core/Runtime/Tests.hs+1−1
@@ -39,7 +39,7 @@ case01 = do
39 compile $ do
39 compile $ do
40 getResourceBody
40 getResourceBody
41 >>= saveSnapshot "raw"
41 >>= saveSnapshot "raw"
42− >>= renderPandoc
42+ >>= renderParagraphs
43
43
44 match (fromList ["partial.html", "partial-helper.html"]) $
44 match (fromList ["partial.html", "partial-helper.html"]) $
45 compile templateCompiler
45 compile templateCompiler
tests/Hakyll/Core/UnixFilter/Tests.hs+6−14
@@ -6,18 +6,16 @@ module Hakyll.Core.UnixFilter.Tests
6
6
7
7
8--------------------------------------------------------------------------------
8--------------------------------------------------------------------------------
9−import Data.List (isInfixOf)
10−import Test.Tasty (TestTree, testGroup)
9+import Test.Tasty (TestTree, testGroup)
11−import Test.Tasty.HUnit (testCase)
10+import Test.Tasty.HUnit (testCase)
12−import qualified Test.Tasty.HUnit as H
11+import qualified Test.Tasty.HUnit as H
13
12
14
13
15--------------------------------------------------------------------------------
14--------------------------------------------------------------------------------
16import Hakyll.Core.Compiler
15import Hakyll.Core.Compiler
17−import Hakyll.Core.Compiler.Internal
16+import Hakyll.Core.Identifier
18import Hakyll.Core.Item
17import Hakyll.Core.Item
19import Hakyll.Core.UnixFilter
18import Hakyll.Core.UnixFilter
20−import Hakyll.Core.Identifier
21import TestSuite.Util
19import TestSuite.Util
22
20
23
21
@@ -51,10 +49,7 @@ unixFilterFalse :: H.Assertion
51unixFilterFalse = do
49unixFilterFalse = do
52 store <- newTestStore
50 store <- newTestStore
53 provider <- newTestProvider store
51 provider <- newTestProvider store
54− result <- testCompiler store provider testMarkdown compiler
52+ testCompilerError store provider testMarkdown compiler "exit code"
55− case result of
56− CompilerError es -> True H.@=? any ("exit code" `isInfixOf`) es
57− _ -> H.assertFailure "Expecting CompilerError"
58 cleanTestEnv
53 cleanTestEnv
59 where
54 where
60 compiler = getResourceString >>= withItemBody (unixFilter "false" [])
55 compiler = getResourceString >>= withItemBody (unixFilter "false" [])
@@ -65,10 +60,7 @@ unixFilterError :: H.Assertion
65unixFilterError = do
60unixFilterError = do
66 store <- newTestStore
61 store <- newTestStore
67 provider <- newTestProvider store
62 provider <- newTestProvider store
68− result <- testCompiler store provider testMarkdown compiler
63+ testCompilerError store provider testMarkdown compiler "option"
69− case result of
70− CompilerError es -> True H.@=? any ("invalid option" `isInfixOf`) es
71− _ -> H.assertFailure "Expecting CompilerError"
72 cleanTestEnv
64 cleanTestEnv
73 where
65 where
74 compiler = getResourceString >>= withItemBody (unixFilter "head" ["-#"])
66 compiler = getResourceString >>= withItemBody (unixFilter "head" ["-#"])
tests/Hakyll/Web/CompressCss/Tests.hs+9−9
@@ -20,9 +20,9 @@ tests = testGroup "Hakyll.Web.CompressCss.Tests" $ concat
20 [ fromAssertions "compressCss"
20 [ fromAssertions "compressCss"
21 [
21 [
22 -- compress whitespace
22 -- compress whitespace
23− " something something " @=?
23+ "something something" @=?
24 compressCss " something \n\t\r something "
24 compressCss " something \n\t\r something "
25− -- do not compress whitespace in constants
25+ -- do not compress whitespace in string tokens
26 , "abc \" \t\n\r \" xyz" @=?
26 , "abc \" \t\n\r \" xyz" @=?
27 compressCss "abc \" \t\n\r \" xyz"
27 compressCss "abc \" \t\n\r \" xyz"
28 , "abc ' \t\n\r ' xyz" @=?
28 , "abc ' \t\n\r ' xyz" @=?
@@ -30,7 +30,7 @@ tests = testGroup "Hakyll.Web.CompressCss.Tests" $ concat
30
30
31 -- strip comments
31 -- strip comments
32 , "before after" @=? compressCss "before /* abc { } ;; \n\t\r */ after"
32 , "before after" @=? compressCss "before /* abc { } ;; \n\t\r */ after"
33− -- don't strip comments inside constants
33+ -- don't strip comments inside string tokens
34 , "before \"/* abc { } ;; \n\t\r */\" after"
34 , "before \"/* abc { } ;; \n\t\r */\" after"
35 @=? compressCss "before \"/* abc { } ;; \n\t\r */\" after"
35 @=? compressCss "before \"/* abc { } ;; \n\t\r */\" after"
36
36
@@ -45,19 +45,19 @@ tests = testGroup "Hakyll.Web.CompressCss.Tests" $ concat
45 , "calc(1px + 100%/(5 + 3) - (3px + 2px)*5)" @=? compressCss "calc( 1px + 100% / ( 5 + 3) - calc( 3px + 2px ) * 5 )"
45 , "calc(1px + 100%/(5 + 3) - (3px + 2px)*5)" @=? compressCss "calc( 1px + 100% / ( 5 + 3) - calc( 3px + 2px ) * 5 )"
46 -- compress whitespace even after this curly brace
46 -- compress whitespace even after this curly brace
47 , "}" @=? compressCss "; } "
47 , "}" @=? compressCss "; } "
48− -- but do not compress separators inside of constants
48+ -- but do not compress separators inside string tokens
49 , "\" { } ; , \"" @=? compressCss "\" { } ; , \""
49 , "\" { } ; , \"" @=? compressCss "\" { } ; , \""
50− -- don't compress separators at the start or end of constants
50+ -- don't compress separators at the start or end of string tokens
51 , "\" }\"" @=? compressCss "\" }\""
51 , "\" }\"" @=? compressCss "\" }\""
52 , "\"{ \"" @=? compressCss "\"{ \""
52 , "\"{ \"" @=? compressCss "\"{ \""
53− -- don't get irritated by the wrong constant terminator
53+ -- don't get irritated by the wrong token delimiter
54 , "\" ' \"" @=? compressCss "\" ' \""
54 , "\" ' \"" @=? compressCss "\" ' \""
55 , "' \" '" @=? compressCss "' \" '"
55 , "' \" '" @=? compressCss "' \" '"
56− -- don't compress whitespace around separators in constants in the middle of a string
56+ -- don't compress whitespace in the middle of a string
57 , "abc '{ '" @=? compressCss "abc '{ '"
57 , "abc '{ '" @=? compressCss "abc '{ '"
58 , "abc \"{ \"" @=? compressCss "abc \"{ \""
58 , "abc \"{ \"" @=? compressCss "abc \"{ \""
59− -- compress whitespace around colons
59+ -- compress whitespace after colons (but not before)
60− , "abc:xyz" @=? compressCss "abc : xyz"
60+ , "abc :xyz" @=? compressCss "abc : xyz"
61 -- compress multiple semicolons
61 -- compress multiple semicolons
62 , ";" @=? compressCss ";;;;;;;"
62 , ";" @=? compressCss ";;;;;;;"
63 ]
63 ]
tests/Hakyll/Web/Template/Context/Tests.hs+40−3
@@ -13,16 +13,19 @@ import Test.Tasty.HUnit (Assertion, testCase, (@=?))
13--------------------------------------------------------------------------------
13--------------------------------------------------------------------------------
14import Hakyll.Core.Compiler
14import Hakyll.Core.Compiler
15import Hakyll.Core.Identifier
15import Hakyll.Core.Identifier
16+import Hakyll.Core.Item
16import Hakyll.Core.Provider
17import Hakyll.Core.Provider
17import Hakyll.Core.Store (Store)
18import Hakyll.Core.Store (Store)
18import Hakyll.Web.Template.Context
19import Hakyll.Web.Template.Context
20+import Hakyll.Web.Template.Internal
19import TestSuite.Util
21import TestSuite.Util
20
22
21
23
22--------------------------------------------------------------------------------
24--------------------------------------------------------------------------------
23tests :: TestTree
25tests :: TestTree
24−tests = testGroup "Hakyll.Core.Template.Context.Tests"
26+tests = testGroup "Hakyll.Web.Template.Context.Tests"
25 [ testCase "testDateField" testDateField
27 [ testCase "testDateField" testDateField
28+ , testCase "testOuerLoopContextAccess" testOuerLoopContextAccess
26 ]
29 ]
27
30
28
31
@@ -41,6 +44,15 @@ testDateField = do
41 dateField "date" "%B %e, %Y"
44 dateField "date" "%B %e, %Y"
42 date2 @=? "August 26, 2010"
45 date2 @=? "August 26, 2010"
43
46
47+ date3 <- testContextDone store provider
48+ "posts/2018-09-26.md" "date" $
49+ dateField "date" "%B %e, %Y"
50+ date3 @=? "September 26, 2018"
51+
52+ date4 <- testContextDone store provider
53+ "posts/2019/05/10/tomorrow.md" "date" $
54+ dateField "date" "%B %e, %Y"
55+ date4 @=? "May 10, 2019"
44 cleanTestEnv
56 cleanTestEnv
45
57
46
58
@@ -53,6 +65,31 @@ testContextDone store provider identifier key context =
53 cf <- unContext context key [] item
65 cf <- unContext context key [] item
54 case cf of
66 case cf of
55 StringField str -> return str
67 StringField str -> return str
56− ListField _ _ -> error $
68+ _ -> error $
57 "Hakyll.Web.Template.Context.Tests.testContextDone: " ++
69 "Hakyll.Web.Template.Context.Tests.testContextDone: " ++
58− "Didn't expect ListField"
70+ "expected StringField"
71+
72+--------------------------------------------------------------------------------
73+
74+testOuerLoopContextAccess :: Assertion
75+testOuerLoopContextAccess = do
76+ store <- newTestStore
77+ provider <- newTestProvider store
78+ test store provider ctx "baz"
79+ test store provider (ctx' <> ctx) "not baz"
80+ test store provider (ctx <> ctx') "baz"
81+
82+ cleanTestEnv
83+ where
84+ tpl = readTemplate "$for(foo)$$for(bar)$$qux$$endfor$$endfor$"
85+ ctx = mconcat [
86+ field "qux" $ const $ return "baz"
87+ , listField "foo" (listField "bar" mempty $ return [mockItem])
88+ $ return [mockItem]
89+ ]
90+ ctx' = field "qux" $ const $ return "not baz"
91+ mockItem = Item "" ()
92+ test store provider context str = do
93+ str' <- testCompilerDone store provider ""
94+ $ applyTemplate tpl context mockItem
95+ str @=? itemBody str'
tests/Hakyll/Web/Template/Tests.hs+52−19
@@ -1,5 +1,6 @@
1--------------------------------------------------------------------------------
1--------------------------------------------------------------------------------
2{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE OverloadedStrings #-}
3+{-# LANGUAGE TemplateHaskell #-}
3module Hakyll.Web.Template.Tests
4module Hakyll.Web.Template.Tests
4 ( tests
5 ( tests
5 ) where
6 ) where
@@ -7,16 +8,16 @@ module Hakyll.Web.Template.Tests
7
8
8--------------------------------------------------------------------------------
9--------------------------------------------------------------------------------
9import Test.Tasty (TestTree, testGroup)
10import Test.Tasty (TestTree, testGroup)
10−import Test.Tasty.HUnit (Assertion, testCase, (@=?),
11+import Test.Tasty.HUnit (Assertion, assertBool, testCase,
11− (@?=))
12+ (@=?), (@?=))
12
13
14+import Data.Either (isLeft)
13
15
14--------------------------------------------------------------------------------
16--------------------------------------------------------------------------------
15import Hakyll.Core.Compiler
17import Hakyll.Core.Compiler
16import Hakyll.Core.Identifier
18import Hakyll.Core.Identifier
17import Hakyll.Core.Item
19import Hakyll.Core.Item
18import Hakyll.Core.Provider
20import Hakyll.Core.Provider
19−import Hakyll.Web.Pandoc
20import Hakyll.Web.Template
21import Hakyll.Web.Template
21import Hakyll.Web.Template.Context
22import Hakyll.Web.Template.Context
22import Hakyll.Web.Template.Internal
23import Hakyll.Web.Template.Internal
@@ -26,20 +27,20 @@ import TestSuite.Util
26
27
27--------------------------------------------------------------------------------
28--------------------------------------------------------------------------------
28tests :: TestTree
29tests :: TestTree
29−tests = testGroup "Hakyll.Core.Template.Tests" $ concat
30+tests = testGroup "Hakyll.Web.Template.Tests" $ concat
30 [ [ testCase "case01" $ test ("template.html.out", "template.html", "example.md")
31 [ [ testCase "case01" $ test ("template.html.out", "template.html", "example.md")
31 , testCase "case02" $ test ("strip.html.out", "strip.html", "example.md")
32 , testCase "case02" $ test ("strip.html.out", "strip.html", "example.md")
32 , testCase "case03" $ test ("just-meta.html.out", "just-meta.html", "example.md")
33 , testCase "case03" $ test ("just-meta.html.out", "just-meta.html", "example.md")
33 , testCase "applyJoinTemplateList" testApplyJoinTemplateList
34 , testCase "applyJoinTemplateList" testApplyJoinTemplateList
34 ]
35 ]
35
36
36− , fromAssertions "readTemplate"
37+ , fromAssertions "parseTemplate"
37− [ [Chunk "Hello ", Expr (Call "guest" [])]
38+ [ Right [Chunk "Hello ", Expr (Call "guest" [])]
38− @=? readTemplateElems "Hello $guest()$"
39+ @=? parse "Hello $guest()$"
39− , [If (Call "a" [StringLiteral "bar"]) [Chunk "foo"] Nothing]
40+ , Right [If (Call "a" [StringLiteral "bar"]) [Chunk "foo"] Nothing]
40− @=? readTemplateElems "$if(a(\"bar\"))$foo$endif$"
41+ @=? parse "$if(a(\"bar\"))$foo$endif$"
41 -- 'If' trim check.
42 -- 'If' trim check.
42− , [ TrimL
43+ , Right [ TrimL
43 , If (Ident (TemplateKey "body"))
44 , If (Ident (TemplateKey "body"))
44 [ TrimR
45 [ TrimR
45 , Chunk "\n"
46 , Chunk "\n"
@@ -55,29 +56,39 @@ tests = testGroup "Hakyll.Core.Template.Tests" $ concat
55 ])
56 ])
56 , TrimR
57 , TrimR
57 ]
58 ]
58− @=? readTemplateElems "$-if(body)-$\n$body$\n$-else-$\n$body$\n$-endif-$"
59+ @=? parse "$-if(body)-$\n$body$\n$-else-$\n$body$\n$-endif-$"
59 -- 'For' trim check.
60 -- 'For' trim check.
60− , [ TrimL
61+ , Right [ TrimL
61 , For (Ident (TemplateKey "authors"))
62 , For (Ident (TemplateKey "authors"))
62 [TrimR, Chunk "\n body \n", TrimL]
63 [TrimR, Chunk "\n body \n", TrimL]
63 Nothing
64 Nothing
64 , TrimR
65 , TrimR
65 ]
66 ]
66− @=? readTemplateElems "$-for(authors)-$\n body \n$-endfor-$"
67+ @=? parse "$-for(authors)-$\n body \n$-endfor-$"
67 -- 'Partial' trim check.
68 -- 'Partial' trim check.
68− , [ TrimL
69+ , Right [ TrimL
69 , Partial (StringLiteral "path")
70 , Partial (StringLiteral "path")
70 , TrimR
71 , TrimR
71 ]
72 ]
72− @=? readTemplateElems "$-partial(\"path\")-$"
73+ @=? parse "$-partial(\"path\")-$"
73 -- 'Expr' trim check.
74 -- 'Expr' trim check.
74− , [ TrimL
75+ , Right [ TrimL
75 , Expr (Ident (TemplateKey "foo"))
76 , Expr (Ident (TemplateKey "foo"))
76 , TrimR
77 , TrimR
77 ]
78 ]
78− @=? readTemplateElems "$-foo-$"
79+ @=? parse "$-foo-$"
80+ -- fail on incomplete template.
81+ , assertBool "did not yield error" $ isLeft $
82+ parse "a$b"
83+ -- fail on mismatched template syntax.
84+ , assertBool "did not fail to parse" $ isLeft $
85+ parse "$for(xs)$\n <p>foo</p>\n$endif$"
79 ]
86 ]
87+
88+ , [testCase "embeddedTemplate" testEmbeddedTemplate]
80 ]
89 ]
90+ where
91+ parse = parseTemplateElemsFile ""
81
92
82
93
83--------------------------------------------------------------------------------
94--------------------------------------------------------------------------------
@@ -89,7 +100,9 @@ test (outf, tplf, itemf) = do
89 out <- resourceString provider outf
100 out <- resourceString provider outf
90 tpl <- testCompilerDone store provider tplf templateBodyCompiler
101 tpl <- testCompilerDone store provider tplf templateBodyCompiler
91 item <- testCompilerDone store provider itemf $
102 item <- testCompilerDone store provider itemf $
103+ getResourceBody
104+ >>= renderParagraphs
92− pandocCompiler >>= applyTemplate (itemBody tpl) testContext
105+ >>= applyTemplate (itemBody tpl) testContext
93
106
94 out @=? itemBody item
107 out @=? itemBody item
95 cleanTestEnv
108 cleanTestEnv
@@ -112,6 +125,8 @@ testApplyJoinTemplateList :: Assertion
112testApplyJoinTemplateList = do
125testApplyJoinTemplateList = do
113 store <- newTestStore
126 store <- newTestStore
114 provider <- newTestProvider store
127 provider <- newTestProvider store
128+ tpl <- testCompilerDone store provider "tpl" $
129+ compileTemplateItem (Item "tpl" "<b>$body$</b>")
115 str <- testCompilerDone store provider "item3" $
130 str <- testCompilerDone store provider "item3" $
116 applyJoinTemplateList ", " tpl defaultContext [i1, i2]
131 applyJoinTemplateList ", " tpl defaultContext [i1, i2]
117
132
@@ -120,4 +135,22 @@ testApplyJoinTemplateList = do
120 where
135 where
121 i1 = Item "item1" "Hello"
136 i1 = Item "item1" "Hello"
122 i2 = Item "item2" "World"
137 i2 = Item "item2" "World"
123− tpl = readTemplate "<b>$body$</b>"
138+
139+
140+--------------------------------------------------------------------------------
141+embeddedTemplate :: Template
142+embeddedTemplate = $(embedTemplate "tests/data/embed.html")
143+
144+--------------------------------------------------------------------------------
145+testEmbeddedTemplate :: Assertion
146+testEmbeddedTemplate = do
147+ store <- newTestStore
148+ provider <- newTestProvider store
149+ str <- testCompilerDone store provider "item3" $
150+ applyTemplate embeddedTemplate defaultContext item
151+
152+ itemBody str @?= "<p>Hello, world</p>\n"
153+ cleanTestEnv
154+ where
155+ item = Item "item1" "Hello, world"
156+
tests/TestSuite.hs+7
@@ -1,4 +1,5 @@
1--------------------------------------------------------------------------------
1--------------------------------------------------------------------------------
2+{-# LANGUAGE CPP #-}
2module Main
3module Main
3 ( main
4 ( main
4 ) where
5 ) where
@@ -22,9 +23,12 @@ import qualified Hakyll.Core.Util.String.Tests
22import qualified Hakyll.Web.CompressCss.Tests
23import qualified Hakyll.Web.CompressCss.Tests
23import qualified Hakyll.Web.Html.RelativizeUrls.Tests
24import qualified Hakyll.Web.Html.RelativizeUrls.Tests
24import qualified Hakyll.Web.Html.Tests
25import qualified Hakyll.Web.Html.Tests
26+#ifdef USE_PANDOC
25import qualified Hakyll.Web.Pandoc.FileType.Tests
27import qualified Hakyll.Web.Pandoc.FileType.Tests
28+#endif
26import qualified Hakyll.Web.Template.Context.Tests
29import qualified Hakyll.Web.Template.Context.Tests
27import qualified Hakyll.Web.Template.Tests
30import qualified Hakyll.Web.Template.Tests
31+import qualified Hakyll.Web.Tags.Tests
28
32
29
33
30--------------------------------------------------------------------------------
34--------------------------------------------------------------------------------
@@ -43,7 +47,10 @@ main = defaultMain $ testGroup "Hakyll"
43 , Hakyll.Web.CompressCss.Tests.tests
47 , Hakyll.Web.CompressCss.Tests.tests
44 , Hakyll.Web.Html.RelativizeUrls.Tests.tests
48 , Hakyll.Web.Html.RelativizeUrls.Tests.tests
45 , Hakyll.Web.Html.Tests.tests
49 , Hakyll.Web.Html.Tests.tests
50+#ifdef USE_PANDOC
46 , Hakyll.Web.Pandoc.FileType.Tests.tests
51 , Hakyll.Web.Pandoc.FileType.Tests.tests
52+#endif
53+ , Hakyll.Web.Tags.Tests.tests
47 , Hakyll.Web.Template.Context.Tests.tests
54 , Hakyll.Web.Template.Context.Tests.tests
48 , Hakyll.Web.Template.Tests.tests
55 , Hakyll.Web.Template.Tests.tests
49 ]
56 ]
tests/TestSuite/Util.hs+23−2
@@ -6,13 +6,15 @@ module TestSuite.Util
6 , newTestProvider
6 , newTestProvider
7 , testCompiler
7 , testCompiler
8 , testCompilerDone
8 , testCompilerDone
9+ , testCompilerError
9 , testConfiguration
10 , testConfiguration
10 , cleanTestEnv
11 , cleanTestEnv
12+ , renderParagraphs
11 ) where
13 ) where
12
14
13
15
14--------------------------------------------------------------------------------
16--------------------------------------------------------------------------------
15−import Data.List (intercalate)
17+import Data.List (intercalate, isInfixOf)
16import Data.Monoid (mempty)
18import Data.Monoid (mempty)
17import qualified Data.Set as S
19import qualified Data.Set as S
18import Test.Tasty
20import Test.Tasty
@@ -29,6 +31,7 @@ import Hakyll.Core.Provider
29import Hakyll.Core.Store (Store)
31import Hakyll.Core.Store (Store)
30import qualified Hakyll.Core.Store as Store
32import qualified Hakyll.Core.Store as Store
31import Hakyll.Core.Util.File
33import Hakyll.Core.Util.File
34+import Hakyll.Core.Item
32
35
33
36
34--------------------------------------------------------------------------------
37--------------------------------------------------------------------------------
@@ -78,13 +81,21 @@ testCompilerDone store provider underlying compiler = do
78 CompilerDone x _ -> return x
81 CompilerDone x _ -> return x
79 CompilerError e -> fail $
82 CompilerError e -> fail $
80 "TestSuite.Util.testCompilerDone: compiler " ++ show underlying ++
83 "TestSuite.Util.testCompilerDone: compiler " ++ show underlying ++
81− " threw: " ++ intercalate "; " e
84+ " threw: " ++ intercalate "; " (compilerErrorMessages e)
82 CompilerRequire i _ -> fail $
85 CompilerRequire i _ -> fail $
83 "TestSuite.Util.testCompilerDone: compiler " ++ show underlying ++
86 "TestSuite.Util.testCompilerDone: compiler " ++ show underlying ++
84 " requires: " ++ show i
87 " requires: " ++ show i
85 CompilerSnapshot _ _ -> fail
88 CompilerSnapshot _ _ -> fail
86 "TestSuite.Util.testCompilerDone: unexpected CompilerSnapshot"
89 "TestSuite.Util.testCompilerDone: unexpected CompilerSnapshot"
87
90
91+testCompilerError :: Store -> Provider -> Identifier -> Compiler a -> String -> IO ()
92+testCompilerError store provider underlying compiler expectedMessage = do
93+ result <- testCompiler store provider underlying compiler
94+ case result of
95+ CompilerError e ->
96+ any (expectedMessage `isInfixOf`) (compilerErrorMessages e) @?
97+ "Expecting '" ++ expectedMessage ++ "' error"
98+ _ -> assertFailure "Expecting CompilerError"
88
99
89--------------------------------------------------------------------------------
100--------------------------------------------------------------------------------
90testConfiguration :: Configuration
101testConfiguration :: Configuration
@@ -102,3 +113,13 @@ cleanTestEnv = do
102 removeDirectory $ destinationDirectory testConfiguration
113 removeDirectory $ destinationDirectory testConfiguration
103 removeDirectory $ storeDirectory testConfiguration
114 removeDirectory $ storeDirectory testConfiguration
104 removeDirectory $ tmpDirectory testConfiguration
115 removeDirectory $ tmpDirectory testConfiguration
116+
117+
118+--------------------------------------------------------------------------------
119+-- | like 'Hakyll.Web.Pandoc.renderPandoc'
120+-- | but allowing to test without the @usePandoc@ flag
121+renderParagraphs :: Item String -> Compiler (Item String)
122+renderParagraphs = withItemBody (return
123+ . intercalate "\n" -- no trailing line
124+ . map (("<p>"++) . (++"</p>"))
125+ . lines)
tests/data/embed.htmlnew file+1
@@ -0,0 +1 @@
1+<p>$body$</p>
tests/data/posts/2018-09-26.mdnew file+1
@@ -0,0 +1 @@
1+Something happened today.
tests/data/posts/2019/05/10/tomorrow.mdnew file+1
@@ -0,0 +1 @@
1+This day hasn't happened yet (as of writing this).
web/css/syntax.css+61−18
@@ -1,18 +1,61 @@
1−/* Generated by pandoc. */
1+a.sourceLine { display: inline-block; line-height: 1.25; }
2−table.sourceCode, tr.sourceCode, td.lineNumbers, td.sourceCode, table.sourceCode pre
2+a.sourceLine { pointer-events: none; color: inherit; text-decoration: inherit; }
3− { margin: 0; padding: 0; border: 0; vertical-align: baseline; border: none; }
3+a.sourceLine:empty { height: 1.2em; }
4−td.lineNumbers { border-right: 1px solid #AAAAAA; text-align: right; color: #AAAAAA; padding-right: 5px; padding-left: 5px; }
4+.sourceCode { overflow: visible; }
5−td.sourceCode { padding-left: 5px; }
5+code.sourceCode { white-space: pre; position: relative; }
6−.sourceCode span.kw { color: #007020; font-weight: bold; }
6+div.sourceCode { margin: 1em 0; }
7+pre.sourceCode { margin: 0; }
8+@media screen {
9+div.sourceCode { overflow: auto; }
10+}
11+@media print {
12+code.sourceCode { white-space: pre-wrap; }
13+a.sourceLine { text-indent: -1em; padding-left: 1em; }
14+}
15+pre.numberSource a.sourceLine
16+ { position: relative; left: -4em; }
17+pre.numberSource a.sourceLine::before
18+ { content: attr(title);
19+ position: relative; left: -1em; text-align: right; vertical-align: baseline;
20+ border: none; pointer-events: all; display: inline-block;
21+ -webkit-touch-callout: none; -webkit-user-select: none;
22+ -khtml-user-select: none; -moz-user-select: none;
23+ -ms-user-select: none; user-select: none;
24+ padding: 0 4px; width: 4em;
25+ color: #aaaaaa;
26+ }
27+pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
28+div.sourceCode
29+ { }
30+@media screen {
31+a.sourceLine::before { text-decoration: underline; }
32+}
33+code span.al { color: #ff0000; font-weight: bold; } /* Alert */
34+code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
35+code span.at { color: #7d9029; } /* Attribute */
36+code span.bn { color: #40a070; } /* BaseN */
37+code span.bu { } /* BuiltIn */
38+code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
39+code span.ch { color: #4070a0; } /* Char */
40+code span.cn { color: #880000; } /* Constant */
41+code span.co { color: #60a0b0; font-style: italic; } /* Comment */
42+code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
43+code span.do { color: #ba2121; font-style: italic; } /* Documentation */
7−.sourceCode span.dt { color: #902000; }
44+code span.dt { color: #902000; } /* DataType */
8−.sourceCode span.dv { color: #40a070; }
45+code span.dv { color: #40a070; } /* DecVal */
9−.sourceCode span.bn { color: #40a070; }
46+code span.er { color: #ff0000; font-weight: bold; } /* Error */
47+code span.ex { } /* Extension */
10−.sourceCode span.fl { color: #40a070; }
48+code span.fl { color: #40a070; } /* Float */
11−.sourceCode span.ch { color: #4070a0; }
12−.sourceCode span.st { color: #4070a0; }
13−.sourceCode span.co { color: #60a0b0; font-style: italic; }
14−.sourceCode span.ot { color: #007020; }
15−.sourceCode span.al { color: red; font-weight: bold; }
16−.sourceCode span.fu { color: #06287e; }
49+code span.fu { color: #06287e; } /* Function */
17−.sourceCode span.re { }
50+code span.im { } /* Import */
18−.sourceCode span.er { color: red; font-weight: bold; }
51+code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
52+code span.kw { color: #007020; font-weight: bold; } /* Keyword */
53+code span.op { color: #666666; } /* Operator */
54+code span.ot { color: #007020; } /* Other */
55+code span.pp { color: #bc7a00; } /* Preprocessor */
56+code span.sc { color: #4070a0; } /* SpecialChar */
57+code span.ss { color: #bb6688; } /* SpecialString */
58+code span.st { color: #4070a0; } /* String */
59+code span.va { color: #19177c; } /* Variable */
60+code span.vs { color: #4070a0; } /* VerbatimString */
61+code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
web/examples.markdown+26−22
@@ -45,7 +45,7 @@ directly with the default Hakyll site.
45- <http://www.regisfoucault.com/>,
45- <http://www.regisfoucault.com/>,
46 [source](https://github.com/regisfoucault/blog)
46 [source](https://github.com/regisfoucault/blog)
47- <http://www.chaoxuprime.com/>,
47- <http://www.chaoxuprime.com/>,
48− [source](https://github.com/Mgccl/blog)
48+ [source](https://github.com/chaoxu/chaoxu.github.io/tree/develop)
49- <http://www.skybluetrades.net/>,
49- <http://www.skybluetrades.net/>,
50 [source](https://github.com/ian-ross/blog)
50 [source](https://github.com/ian-ross/blog)
51- <http://chromaticleaves.com/>,
51- <http://chromaticleaves.com/>,
@@ -68,16 +68,10 @@ directly with the default Hakyll site.
68 [source](https://github.com/walkie/WebPage)
68 [source](https://github.com/walkie/WebPage)
69- <http://techblog.rosedu.org/>,
69- <http://techblog.rosedu.org/>,
70 [source](https://github.com/rosedu/techblog)
70 [source](https://github.com/rosedu/techblog)
71−- <http://abizern.org>,
72− [source](https://github.com/Abizern/hblog)
73- <http://freizl.github.io>,
71- <http://freizl.github.io>,
74 [source](https://github.com/freizl/freizl.github.com/tree/master/build)
72 [source](https://github.com/freizl/freizl.github.com/tree/master/build)
75−- <http://covariant.me>,
76− [source](http://hub.darcs.net/co-dan/website)
77- <http://www.gwern.net/>,
73- <http://www.gwern.net/>,
78 [source](https://github.com/gwern/gwern.net)
74 [source](https://github.com/gwern/gwern.net)
79−- <http://www.rohanjain.in/>,
80− [source](https://github.com/crodjer/rohanjain.in)
81- <http://www.corentindupont.info/>,
75- <http://www.corentindupont.info/>,
82 [source](https://github.com/cdupont/CorentinDupont-WebPage)
76 [source](https://github.com/cdupont/CorentinDupont-WebPage)
83- <http://jelv.is>,
77- <http://jelv.is>,
@@ -86,8 +80,8 @@ directly with the default Hakyll site.
86 [source](https://github.com/plaimi/www)
80 [source](https://github.com/plaimi/www)
87- <http://duplode.github.io/>,
81- <http://duplode.github.io/>,
88 [source](https://github.com/duplode/duplode.github.io/tree/sources)
82 [source](https://github.com/duplode/duplode.github.io/tree/sources)
89−- <http://maseek.codes>,
83+- <http://tzemanovic.github.io/>,
90− [source](https://github.com/maseek/maseek-codes)
84+ [source](https://github.com/tzemanovic/tzemanovic)
91- <http://listx.github.io>,
85- <http://listx.github.io>,
92 [source](https://github.com/listx/listx_blog)
86 [source](https://github.com/listx/listx_blog)
93- <http://kyle.marek-spartz.org>,
87- <http://kyle.marek-spartz.org>,
@@ -98,26 +92,20 @@ directly with the default Hakyll site.
98 [source](https://github.com/rbros/reichertbrothers.com)
92 [source](https://github.com/rbros/reichertbrothers.com)
99- <http://alemedeiros.sdf.org>,
93- <http://alemedeiros.sdf.org>,
100 [source](https://github.com/alemedeiros/homepage)
94 [source](https://github.com/alemedeiros/homepage)
101−- <https://ruudvanasseldonk.com/>,
102− [source](https://github.com/ruud-v-a/ruudvanasseldonk.com)
103- <http://www.haskell.mn>,
95- <http://www.haskell.mn>,
104 [source](https://github.com/HaskellMN/www.haskell.mn)
96 [source](https://github.com/HaskellMN/www.haskell.mn)
105- <https://axiomatic.neophilus.net/>,
97- <https://axiomatic.neophilus.net/>,
106 [source](https://github.com/Libbum/AxiomaticSemantics)
98 [source](https://github.com/Libbum/AxiomaticSemantics)
107−- <https://tesser.org/>,
99+- [https://tesser.org/](http://web.archive.org/web/20161022221713/https://tesser.org/),
108 [source](https://github.com/fractalcat/tesser.org)
100 [source](https://github.com/fractalcat/tesser.org)
109−- <https://wei2912.github.io/>,
101+- <https://weien.io/>,
110− [source](https://github.com/wei2912/blog_src)
102+  [source](https://github.com/wei2912/blog-src)
111- <http://www.metasepi.org/>,
103- <http://www.metasepi.org/>,
112 [source](https://github.com/metasepi/metasepi-website)
104 [source](https://github.com/metasepi/metasepi-website)
113- <http://gisli.hamstur.is/>,
105- <http://gisli.hamstur.is/>,
114 [source](https://github.com/gislik/gisli.hamstur.is)
106 [source](https://github.com/gislik/gisli.hamstur.is)
115- <http://blog.ssanj.net/>,
107- <http://blog.ssanj.net/>,
116 [source](https://github.com/ssanj/babyloncandle)
108 [source](https://github.com/ssanj/babyloncandle)
117−- <https://rockymadden.com/>,
118− [source](https://github.com/rockymadden/rockymadden-web)
119−- <http://blog.dshevchenko.biz/>,
120− [source](https://github.com/denisshevchenko/blog)
121- <http://ruhaskell.org/>,
109- <http://ruhaskell.org/>,
122 [source](https://github.com/ruHaskell/ruhaskell)
110 [source](https://github.com/ruHaskell/ruhaskell)
123- <http://www.haskell.od.ua/>,
111- <http://www.haskell.od.ua/>,
@@ -138,8 +126,6 @@ directly with the default Hakyll site.
138 [source](https://github.com/ismailmustafa/ismailmustafa.github.io/tree/hakyll)
126 [source](https://github.com/ismailmustafa/ismailmustafa.github.io/tree/hakyll)
139- <http://lettier.github.io/>,
127- <http://lettier.github.io/>,
140 [source](https://github.com/lettier/lettier.github.io)
128 [source](https://github.com/lettier/lettier.github.io)
141−- <http://jozefg.bitbucket.org/>,
142− [source](https://github.com/jozefg/blog)
143- <http://raindev.io>,
129- <http://raindev.io>,
144 [source](https://github.com/raindev/raindev.github.io)
130 [source](https://github.com/raindev/raindev.github.io)
145- <http://hal2016.haskell.org/>,
131- <http://hal2016.haskell.org/>,
@@ -168,8 +154,6 @@ directly with the default Hakyll site.
168 [source](https://github.com/beerendlauwers/HaskAnything)
154 [source](https://github.com/beerendlauwers/HaskAnything)
169- <https://anler.me/>,
155- <https://anler.me/>,
170 [source](https://github.com/anler/anler.me)
156 [source](https://github.com/anler/anler.me)
171−- <https://www.brandonstil.es/>,
172− [source](https://github.com/stilesb/brandonstil.es)
173- <http://argumatronic.com/>,
157- <http://argumatronic.com/>,
174 [source](https://gitlab.com/GinBaby/argumatronic/tree/master)
158 [source](https://gitlab.com/GinBaby/argumatronic/tree/master)
175- <http://sillybytes.net/>,
159- <http://sillybytes.net/>,
@@ -180,6 +164,26 @@ directly with the default Hakyll site.
180 [source](https://github.com/thunky-monk/spyhop-cafe)
164 [source](https://github.com/thunky-monk/spyhop-cafe)
181- <https://bendo.github.io/>,
165- <https://bendo.github.io/>,
182 [source](https://github.com/bendo/bendo.github.io)
166 [source](https://github.com/bendo/bendo.github.io)
167+- <http://adarsh.cc/>,
168+ [source](https://github.com/adarshp/adarsh.cc)
169+- <http://0a.io>,
170+ [source](https://github.com/arrchyy/0a.io)
171+- <https://nasy.moe/>,
172+ [source](https://github.com/nasyxx/NasyMoe)
173+- <http://uniprocess.org/>,
174+ [source](https://gitlab.com/uniprocess/uniprocess.org)
175+- <https://regularflolloping.com/>,
176+ [source](https://github.com/techieAgnostic/rf)
177+- <http://enter-haken.github.io/>,
178+ [source](https://github.com/enter-haken/blog)
179+- <https://modular.blind.guru/>,
180+ [source](https://github.com/mlang/eurorack-metadata)
181+- <http://www.jonashietala.se/>,
182+ [source](https://github.com/treeman/jonashietala)
183+- <https://abhinavsarkar.net/>,
184+ [source](https://github.com/abhin4v/abhin4v.github.io)
185+- <https://dailyreadingnotes.com/>,
186+ [source](https://github.com/hackeryarn/daily-reading-notes)
183
187
184## Hakyll 3.X
188## Hakyll 3.X
185
189
web/libraries.markdown+1
@@ -18,3 +18,4 @@ Here is a list of the ones I know of (I would be happy to hear about others).
18 a conversion tool from other blog engines to Hakyll.
18 a conversion tool from other blog engines to Hakyll.
19- [hakyll-elm](https://hackage.haskell.org/package/hakyll-elm),
19- [hakyll-elm](https://hackage.haskell.org/package/hakyll-elm),
20 Hakyll wrapper for the Elm compiler.
20 Hakyll wrapper for the Elm compiler.
21+- [hakyll-sass](http://hackage.haskell.org/package/hakyll-sass), a compiler for SASS templates.
web/site.hs+14−32
@@ -1,8 +1,7 @@
1--------------------------------------------------------------------------------
1--------------------------------------------------------------------------------
2{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE OverloadedStrings #-}
3import Control.Arrow (second)
3import Control.Arrow (second)
4−import Control.Monad (forM_)
4+import Control.Monad (filterM, forM_)
5−import Data.Char (isDigit)
6import Data.List (isPrefixOf, sortBy)
5import Data.List (isPrefixOf, sortBy)
7import Data.Monoid ((<>))
6import Data.Monoid ((<>))
8import Data.Ord (comparing)
7import Data.Ord (comparing)
@@ -57,17 +56,11 @@ main = hakyllWith config $ do
57 create ["tutorials.html"] $ do
56 create ["tutorials.html"] $ do
58 route idRoute
57 route idRoute
59 compile $ do
58 compile $ do
60− tuts <-
59+ ctx <- tutorialsCtx <$>
61 sortBy (comparing itemIdentifier) <$> loadAll "tutorials/*"
60 sortBy (comparing itemIdentifier) <$> loadAll "tutorials/*"
62−
63− let tutorialsCtx =
64− constField "title" "Tutorials" `mappend`
65− listField "tutorials" tutorialCtx (return tuts) `mappend`
66− defaultContext
67−
68 makeItem ""
61 makeItem ""
69− >>= loadAndApplyTemplate "templates/tutorials.html" tutorialsCtx
62+ >>= loadAndApplyTemplate "templates/tutorials.html" ctx
70− >>= loadAndApplyTemplate "templates/default.html" tutorialsCtx
63+ >>= loadAndApplyTemplate "templates/default.html" ctx
71 >>= relativizeUrls
64 >>= relativizeUrls
72
65
73 -- Templates
66 -- Templates
@@ -108,28 +101,17 @@ hackage url
108 reverse $ splitPath url
101 reverse $ splitPath url
109
102
110
103
111−--------------------------------------------------------------------------------
112−data TutorialType = SeriesTutorial | ArticlesTutorial | ExternalTutorial
113− deriving (Eq)
114−
115−
116--------------------------------------------------------------------------------
104--------------------------------------------------------------------------------
117-- | Partition tutorials into tutorial series, other articles, external articles
105-- | Partition tutorials into tutorial series, other articles, external articles
118−tutorialCtx :: Context String
106+tutorialsCtx :: [Item String] -> Context String
119−tutorialCtx =
107+tutorialsCtx tuts =
120− field "isSeries" (isTutorialType SeriesTutorial) <>
108+ constField "title" "Tutorials" <>
121− field "isArticle" (isTutorialType ArticlesTutorial) <>
109+ listField "main" defaultContext (ofType "main") <>
122− field "isExternal" (isTutorialType ExternalTutorial) <>
110+ listField "articles" defaultContext (ofType "article") <>
111+ listField "externals" defaultContext (ofType "external") <>
112+ listField "robertwpearce" defaultContext (ofType "robertwpearce") <>
123 defaultContext
113 defaultContext
124 where
114 where
125− getTutorialType item = do
115+ ofType ty = filterM (\item -> do
126− mbExternal <- getMetadataField (itemIdentifier item) "external"
116+ mbType <- getMetadataField (itemIdentifier item) "type"
127− return $ case mbExternal of
117+ return $ Just ty == mbType) tuts
128− Just _ -> ExternalTutorial
129− _ -> case splitPath (toFilePath $ itemIdentifier item) of
130− [_, (x : _)] -> if isDigit x then SeriesTutorial else ArticlesTutorial
131− _ -> ArticlesTutorial
132−
133− isTutorialType ty0 item = do
134− ty1 <- getTutorialType item
135− if ty0 == ty1 then return "yes" else fail "no"
web/templates/tutorials.html+24−18
@@ -1,23 +1,19 @@
1<h1>Tutorials about Hakyll</h1>
1<h1>Tutorials about Hakyll</h1>
2<h2 id="series">Tutorial series</h2>
2<h2 id="series">Tutorial series</h2>
3<ul>
3<ul>
4− $for(tutorials)$
4+ $for(main)$
5− $if(isSeries)$
6− <li>
5+ <li>
7− <a href="$url$">$title$</a>
6+ <a href="$url$">$title$</a>
8− </li>
7+ </li>
9− $endif$
10 $endfor$
8 $endfor$
11</ul>
9</ul>
12<h2 id="articles">Other articles</h2>
10<h2 id="articles">Other articles</h2>
13<p>In no particular order:</p>
11<p>In no particular order:</p>
14<ul>
12<ul>
15− $for(tutorials)$
13+ $for(articles)$
16− $if(isArticle)$
17− <li>
14+ <li>
18− <a href="$url$">$title$</a> by <em>$author$</em>
15+ <a href="$url$">$title$</a> by <em>$author$</em>
19− </li>
16+ </li>
20− $endif$
21 $endfor$
17 $endfor$
22</ul>
18</ul>
23<h2 id="external-articles">External articles</h2>
19<h2 id="external-articles">External articles</h2>
@@ -28,12 +24,22 @@
28</p>
24</p>
29<p>In no particular order:</p>
25<p>In no particular order:</p>
30<ul>
26<ul>
31− $for(tutorials)$
27+ $for(externals)$
32− $if(isExternal)$
33− <li>
28+ <li>
34− <a href="$url$">$title$</a> by <em>$author$</em>
29+ <a href="$url$">$title$</a> by <em>$author$</em>
35− </li>
30+ </li>
36− $endif$
31+ $endfor$
32+</ul>
33+<h2 id="robertwpearce">Robert Pearce's series</h2>
34+<p>
35+ <a href="https://robertwpearce.com/">Robert Pearce</a> has been writing an
36+ excellent tutorial-style series on Hakyll as well. You can find those here:
37+</p>
38+<ul>
39+ $for(robertwpearce)$
40+ <li>
41+ <a href="$url$">$title$</a>
42+ </li>
37 $endfor$
43 $endfor$
38</ul>
44</ul>
39<p>
45<p>
web/tutorials/01-installation.markdown+4−2
@@ -1,6 +1,7 @@
1---
1---
2title: Installation
2title: Installation
3author: Jasper Van der Jeugt
3author: Jasper Van der Jeugt
4+type: main
4---
5---
5
6
6Installation
7Installation
@@ -17,7 +18,8 @@ different distributions. For installation from source (i.e. via Hackage),
17Linux distro packages:
18Linux distro packages:
18
19
19- [Debian unstable](http://packages.debian.org/source/sid/haskell-hakyll)
20- [Debian unstable](http://packages.debian.org/source/sid/haskell-hakyll)
20−- [Fedora](https://apps.fedoraproject.org/packages/ghc-hakyll-devel)
21+- [Fedora](https://apps.fedoraproject.org/packages/ghc-hakyll)
22+- [Nix](https://nixos.org/nixos/packages.html#hakyll): `$ nix-env -i hakyll`
21
23
22Building the example site
24Building the example site
23-------------------------
25-------------------------
@@ -26,7 +28,7 @@ Apart from the main Hakyll library, the cabal package also provides you with an
26executable `hakyll-init` to create an example site. This is an easy way to get
28executable `hakyll-init` to create an example site. This is an easy way to get
27started:
29started:
28
30
29− $ hakyll-init my-site
31+ $ stack exec hakyll-init my-site
30
32
31This creates a folder `my-site` in the current directory, with some example
33This creates a folder `my-site` in the current directory, with some example
32content and a generic configuration.
34content and a generic configuration.
web/tutorials/02-basics.markdown+1
@@ -1,6 +1,7 @@
1---
1---
2title: The basics
2title: The basics
3author: Jasper Van der Jeugt
3author: Jasper Van der Jeugt
4+type: main
4---
5---
5
6
6Building and cleaning
7Building and cleaning
web/tutorials/03-rules-routes-compilers.markdown+1
@@ -1,6 +1,7 @@
1---
1---
2title: Rules, routes and compilers
2title: Rules, routes and compilers
3author: Jasper Van der Jeugt
3author: Jasper Van der Jeugt
4+type: main
4---
5---
5
6
6Basic rules
7Basic rules
web/tutorials/04-compilers.markdown+3−2
@@ -1,6 +1,7 @@
1---
1---
2title: 'More on compilers: load, and templates'
2title: 'More on compilers: load, and templates'
3author: Jasper Van der Jeugt
3author: Jasper Van der Jeugt
4+type: main
4---
5---
5
6
6Loading items
7Loading items
@@ -92,7 +93,7 @@ And `$title$` like this:
92titleContext :: Context a
93titleContext :: Context a
93titleContext = field "title" $ \item -> do
94titleContext = field "title" $ \item -> do
94 metadata <- getMetadata (itemIdentifier item)
95 metadata <- getMetadata (itemIdentifier item)
95− return $ fromMaybe "No title" $ M.lookup "title" metadata
96+ return $ fromMaybe "No title" $ lookupString "title" metadata
96```
97```
97
98
98And compose them using the `Monoid` instance:
99And compose them using the `Monoid` instance:
@@ -151,7 +152,7 @@ Control flow in templates
151-------------------------
152-------------------------
152
153
153Sometimes string interpolation does not suffice, and you want a little more
154Sometimes string interpolation does not suffice, and you want a little more
154−control over how your templates are layed out. Hakyll provides a few control
155+control over how your templates are laid out. Hakyll provides a few control
155structures for this. The syntax for these structures was based on the syntax
156structures for this. The syntax for these structures was based on the syntax
156used in pandoc templates, since Hakyll already has tight integration with
157used in pandoc templates, since Hakyll already has tight integration with
157pandoc.
158pandoc.
web/tutorials/05-snapshots-feeds.markdown+1
@@ -1,6 +1,7 @@
1---
1---
2title: Snapshots, and how to produce an RSS/Atom feed
2title: Snapshots, and how to produce an RSS/Atom feed
3author: Jasper Van der Jeugt
3author: Jasper Van der Jeugt
4+type: main
4---
5---
5
6
6Basic feed configuration
7Basic feed configuration
web/tutorials/06-versions.markdown+1
@@ -1,6 +1,7 @@
1---
1---
2title: Producing multiple versions of a single file
2title: Producing multiple versions of a single file
3author: Jasper Van der Jeugt
3author: Jasper Van der Jeugt
4+type: main
4---
5---
5
6
6Basics
7Basics
web/tutorials/a-guide-to-the-hakyll-module-zoo.markdown+1
@@ -1,6 +1,7 @@
1---
1---
2title: A Guide to the Hakyll Module Zoo
2title: A Guide to the Hakyll Module Zoo
3author: Brent Yorgey
3author: Brent Yorgey
4+type: article
4---
5---
5
6
6The hakyll package [contains a bewildering array](/reference/) of modules, and
7The hakyll package [contains a bewildering array](/reference/) of modules, and
web/tutorials/external-add-tags-to-your-hakyll-blog.md+1−1
@@ -2,5 +2,5 @@
2title: Add tags to your Hakyll blog
2title: Add tags to your Hakyll blog
3author: Javran Cheng
3author: Javran Cheng
4url: https://javran.github.io/posts/2014-03-01-add-tags-to-your-hakyll-blog.html
4url: https://javran.github.io/posts/2014-03-01-add-tags-to-your-hakyll-blog.html
5−external: true
5+type: external
6---
6---
web/tutorials/external-blogger-port.md+1−1
@@ -2,5 +2,5 @@
2title: Port an existing Blogger blog to Hakyll
2title: Port an existing Blogger blog to Hakyll
3author: Daniel Campoverde
3author: Daniel Campoverde
4url: 'http://www.sillybytes.net/posts/from_blogger_to_hakyll.html'
4url: 'http://www.sillybytes.net/posts/from_blogger_to_hakyll.html'
5−external: true
5+type: external
6---
6---
web/tutorials/external-cassius-template.md+1−1
@@ -2,5 +2,5 @@
2title: Using Cassius templating with Hakyll
2title: Using Cassius templating with Hakyll
3author: Daniel Campoverde
3author: Daniel Campoverde
4url: 'http://www.sillybytes.net/posts/using_hakyll_with_cassius.html'
4url: 'http://www.sillybytes.net/posts/using_hakyll_with_cassius.html'
5−external: true
5+type: external
6---
6---
web/tutorials/external-clean-urls-with-hakyll.md+1−1
@@ -2,5 +2,5 @@
2title: Clean URLs with Hakyll
2title: Clean URLs with Hakyll
3author: Rohan Jain
3author: Rohan Jain
4url: http://www.rohanjain.in/hakyll-clean-urls/
4url: http://www.rohanjain.in/hakyll-clean-urls/
5−external: true
5+type: external
6---
6---
web/tutorials/external-deploying-with-widely.md+1−1
@@ -2,5 +2,5 @@
2title: Deploying with Widely
2title: Deploying with Widely
3author: Kyle Marek-Spartz
3author: Kyle Marek-Spartz
4url: http://kyle.marek-spartz.org/posts/2013-12-09-widely-and-hakyll.html
4url: http://kyle.marek-spartz.org/posts/2013-12-09-widely-and-hakyll.html
5−external: true
5+type: external
6---
6---
web/tutorials/external-external-code-inclusion.mdnew file+6
@@ -0,0 +1,6 @@
1+---
2+title: 'External code inclusion with Hakyll'
3+author: 'Tobias Pleyer'
4+url: 'http://blog.tpleyer.de/posts/2019-04-21-external-code-inclusion-with-hakyll.html'
5+type: external
6+---
web/tutorials/external-functionfield.md+1−1
@@ -2,5 +2,5 @@
2title: Hakyll's functionField
2title: Hakyll's functionField
3author: Beerend Lauwers
3author: Beerend Lauwers
4url: http://beerendlauwers.be/posts/2015-09-21-hakylls-functionfield.html
4url: http://beerendlauwers.be/posts/2015-09-21-hakylls-functionfield.html
5−external: true
5+type: external
6---
6---
web/tutorials/external-github-pages-circle-ci.md+1−1
@@ -2,5 +2,5 @@
2title: Create a GitHub page with Hakyll and CircleCI
2title: Create a GitHub page with Hakyll and CircleCI
3author: Juan Pedro Villa
3author: Juan Pedro Villa
4url: https://www.stackbuilders.com/news/dr-hakyll-create-a-github-page-with-hakyll-and-circleci
4url: https://www.stackbuilders.com/news/dr-hakyll-create-a-github-page-with-hakyll-and-circleci
5−external: true
5+type: external
6---
6---
web/tutorials/external-inlining-and-compressing-css.md+1−1
@@ -2,5 +2,5 @@
2title: Inlining and compressing CSS
2title: Inlining and compressing CSS
3author: Kyle Marek-Spartz
3author: Kyle Marek-Spartz
4url: http://kyle.marek-spartz.org/posts/2014-12-09-hakyll-css-template-compiler.html
4url: http://kyle.marek-spartz.org/posts/2014-12-09-hakyll-css-template-compiler.html
5−external: true
5+type: external
6---
6---
web/tutorials/faq.markdown+1
@@ -1,6 +1,7 @@
1---
1---
2title: FAQ
2title: FAQ
3author: Jasper Van der Jeugt
3author: Jasper Van der Jeugt
4+type: article
4---
5---
5
6
6## "hGetContents: invalid argument" or "commitBuffer: invalid argument"
7## "hGetContents: invalid argument" or "commitBuffer: invalid argument"
web/tutorials/github-pages-tutorial.md+1
@@ -1,6 +1,7 @@
1---
1---
2title: Using Hakyll with GitHub Pages
2title: Using Hakyll with GitHub Pages
3author: Erik Stevenson
3author: Erik Stevenson
4+type: article
4---
5---
5
6
6## Introduction
7## Introduction
web/tutorials/hakyll-3-to-hakyll4-migration-guide.markdown+1
@@ -1,6 +1,7 @@
1---
1---
2title: Hakyll 3 to Hakyll 4 migration guide
2title: Hakyll 3 to Hakyll 4 migration guide
3author: Jasper Van der Jeugt
3author: Jasper Van der Jeugt
4+type: article
4---
5---
5
6
6Introduction
7Introduction
web/tutorials/robertwpearce-01-setup-and-initial-customization.htmlnew file+5
@@ -0,0 +1,5 @@
1+---
2+title: 'Pt. 1: Setup & Initial Customization'
3+url: 'https://robertwpearce.com/hakyll-pt-1-setup-and-initial-customization.html'
4+type: robertwpearce
5+---
web/tutorials/robertwpearce-02-generating-a-sitemap-xml-file.htmlnew file+5
@@ -0,0 +1,5 @@
1+---
2+title: 'Pt. 2: Generating a Sitemap XML File'
3+url: 'https://robertwpearce.com/hakyll-pt-2-generating-a-sitemap-xml-file.html'
4+type: robertwpearce
5+---
web/tutorials/robertwpearce-03-generating-rss-and-atom-xml-feeds.htmlnew file+5
@@ -0,0 +1,5 @@
1+---
2+title: 'Pt. 3: Generating RSS and Atom XML Feeds'
3+url: 'https://robertwpearce.com/hakyll-pt-3-generating-rss-and-atom-xml-feeds.html'
4+type: robertwpearce
5+---
web/tutorials/robertwpearce-04-copying-static-files-for-your-build.htmlnew file+5
@@ -0,0 +1,5 @@
1+---
2+title: 'Pt. 4: Copying Static Files For Your Build'
3+url: 'https://robertwpearce.com/hakyll-pt-4-copying-static-files-for-your-build.html'
4+type: robertwpearce
5+---
web/tutorials/robertwpearce-05-generating-custom-post-filenames-from-a-title-slug.htmlnew file+5
@@ -0,0 +1,5 @@
1+---
2+title: 'Pt. 5 – Generating Custom Post Filenames From a Title Slug'
3+url: 'https://robertwpearce.com/hakyll-pt-5-generating-custom-post-filenames-from-a-title-slug.html'
4+type: robertwpearce
5+---
web/tutorials/using-clay-with-hakyll.markdown+1
@@ -1,6 +1,7 @@
1---
1---
2title: Using Clay with Hakyll
2title: Using Clay with Hakyll
3author: Jasper Van der Jeugt
3author: Jasper Van der Jeugt
4+type: article
4---
5---
5
6
6[Clay](http://sebastiaanvisser.github.com/clay/) is a nice CSS preprocesser
7[Clay](http://sebastiaanvisser.github.com/clay/) is a nice CSS preprocesser
web/tutorials/using-teasers-in-hakyll.markdown+1
@@ -1,6 +1,7 @@
1---
1---
2title: Using teasers in Hakyll
2title: Using teasers in Hakyll
3author: Ivan Veselov
3author: Ivan Veselov
4+type: article
4---
5---
5
6
6## Introduction
7## Introduction