on master help sign in
78 files changed+1331−629
since splitsnapshots
unifiedsplit
.circleci/config.yml+5−11 CHANGELOG.md+100 Makefile+6−6 hakyll.cabal+55−75 lib/Hakyll.hs+5−1 lib/Hakyll/Check.hs+13 lib/Hakyll/Core/Compiler.hs+35−2 lib/Hakyll/Core/Compiler/Internal.hs+140−41 lib/Hakyll/Core/Compiler/Require.hs+2 lib/Hakyll/Core/Identifier.hs+1−1 lib/Hakyll/Core/Identifier/Pattern.hs+10−70 lib/Hakyll/Core/Identifier/Pattern/Internal.hs+92 lib/Hakyll/Core/Logger.hs+9−3 lib/Hakyll/Core/Provider/Metadata.hs+3−2 lib/Hakyll/Core/Routes.hs+17 lib/Hakyll/Core/Rules/Internal.hs+14 lib/Hakyll/Core/Runtime.hs+3−3 lib/Hakyll/Core/Store.hs+26−23 lib/Hakyll/Core/Util/Parser.hs−1 lib/Hakyll/Core/Util/String.hs+1−1 lib/Hakyll/Preview/Poll.hs+8−18 lib/Hakyll/Web/CompressCss.hs+59−62 lib/Hakyll/Web/Feed.hs+59−21 lib/Hakyll/Web/Html.hs+6−2 lib/Hakyll/Web/Pandoc/Biblio.hs+17−6 lib/Hakyll/Web/Pandoc/Binary.hs+1 lib/Hakyll/Web/Tags.hs+2−1 lib/Hakyll/Web/Template.hs+24 lib/Hakyll/Web/Template/Context.hs+135−33 lib/Hakyll/Web/Template/Internal.hs+103−45 lib/Hakyll/Web/Template/Internal/Element.hs+7−14 src/Init.hs+7−4 stack.yaml+22−11 tests/Hakyll/Core/Provider/Tests.hs+2−2 tests/Hakyll/Core/Rules/Tests.hs+3−4 tests/Hakyll/Core/Runtime/Tests.hs+1−1 tests/Hakyll/Core/Store/Tests.hs+7−7 tests/Hakyll/Core/UnixFilter/Tests.hs+8−16 tests/Hakyll/Web/CompressCss/Tests.hs+9−9 tests/Hakyll/Web/Html/Tests.hs+6−6 tests/Hakyll/Web/Template/Context/Tests.hs+40−3 tests/Hakyll/Web/Template/Tests.hs+52−19 tests/TestSuite.hs+7 tests/TestSuite/Util.hs+23−2 tests/data/embed.html+1 tests/data/posts/2018-09-26.md+1 tests/data/posts/2019/05/10/tomorrow.md+1 web/css/syntax.css+61−18 web/examples.markdown+26−22 web/libraries.markdown+1 web/site.hs+14−32 web/templates/tutorials.html+24−18 web/tutorials/01-installation.markdown+4−2 web/tutorials/02-basics.markdown+1 web/tutorials/03-rules-routes-compilers.markdown+1 web/tutorials/04-compilers.markdown+4−3 web/tutorials/05-snapshots-feeds.markdown+1 web/tutorials/06-versions.markdown+1 web/tutorials/a-guide-to-the-hakyll-module-zoo.markdown+1 web/tutorials/external-add-tags-to-your-hakyll-blog.md+1−1 web/tutorials/external-blogger-port.md+1−1 web/tutorials/external-cassius-template.md+1−1 web/tutorials/external-clean-urls-with-hakyll.md+1−1 web/tutorials/external-deploying-with-widely.md+1−1 web/tutorials/external-external-code-inclusion.md+6 web/tutorials/external-functionfield.md+1−1 web/tutorials/external-github-pages-circle-ci.md+1−1 web/tutorials/external-inlining-and-compressing-css.md+1−1 web/tutorials/faq.markdown+1 web/tutorials/github-pages-tutorial.md+1 web/tutorials/hakyll-3-to-hakyll4-migration-guide.markdown+1 web/tutorials/robertwpearce-01-setup-and-initial-customization.html+5 web/tutorials/robertwpearce-02-generating-a-sitemap-xml-file.html+5 web/tutorials/robertwpearce-03-generating-rss-and-atom-xml-feeds.html+5 web/tutorials/robertwpearce-04-copying-static-files-for-your-build.html+5 web/tutorials/robertwpearce-05-generating-custom-post-filenames-from-a-title-slug.html+5 web/tutorials/using-clay-with-hakyll.markdown+1 web/tutorials/using-teasers-in-hakyll.markdown+1
.circleci/config.yml+5−11
@@ -6,20 +6,14 @@ jobs:
66 steps:
77 - checkout
88 - restore_cache:
9− key: 'v1-hakyll-{{ arch }}-{{ .Branch }}'
10− - run:
11− name: 'Update cabal indices'
12− command: 'cabal update'
9+ key: 'v3-hakyll-{{ arch }}-{{ .Branch }}'
1310 - run:
1411 # We set jobs to 1 here because that prevents Out-Of-Memory exceptions
1512 # while compiling dependencies.
1613 name: 'Install dependencies'
17− command: '.circleci/tickle.sh cabal install --only-dependencies --enable-tests --jobs=1 --flags="+buildWebsite"'
18− - run:
19− name: 'Build and run tests'
20− command: 'cabal test'
14+ command: '.circleci/tickle.sh stack build --test --copy-bins --jobs=1'
2115 - save_cache:
22− key: 'v1-hakyll-{{ arch }}-{{ .Branch }}-{{ .Revision }}'
16+ key: 'v3-hakyll-{{ arch }}-{{ .Branch }}-{{ .Revision }}'
2317 paths:
24− - '~/.cabal'
25− - '~/.ghc'
18+ - '~/.stack-work'
19+ - '~/.stack'
CHANGELOG.md+100
@@ -4,6 +4,106 @@ title: Releases
44
55# Releases
66
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+
92+## Hakyll 4.11.0.0
93+
94+- Bump binary to 0.9
95+- Bump pandoc to 2.1
96+- Bump pandoc-citeproc to 0.13
97+- Bump http-types to 0.11
98+- Bump QuickCheck to 2.11
99+- Bump tasty to 1.0
100+- Bump tasty-hunit to 0.10
101+- Remove system-filepath dependency
102+- Embed feed templates rather than using data-files (contribution by Roman
103+ Kuznetsov)
104+- Fix pthread link error on GHC-8.2.2 (contribution by Shinya Yamaguchi)
105+- Extend capture with Regex handling (contribution by frederik-h)
106+
7107## Hakyll 4.10.0.0
8108
9109- Bump Pandoc to 2.0 (contribution by Vaibhav Sagar)
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+
14# Generate the docs and copy them to the website dir
25haddock:
3− cabal haddock --hyperlink-source
4− rm -rf web/reference/
5− cp -r dist/doc/html/hakyll/ web/reference/
6+ stack build --haddock --no-haddock-deps
7+ rsync -r "$(LOCAL_DOC_ROOT)/$(PACKAGE)/" web/reference/
68
7−# Run the tests
8−test:
9− runghc -isrc -itests tests/TestSuite.hs
9+.PHONY: haddock
hakyll.cabal+55−75
@@ -1,5 +1,5 @@
11Name: hakyll
2−Version: 4.10.0.0
2+Version: 4.13.0.0
33
44Synopsis: A static website compiler library
55Description:
@@ -56,10 +56,6 @@ Data-files:
5656 example/index.html
5757 example/about.rst
5858 example/contact.markdown
59− templates/atom-item.xml
60− templates/atom.xml
61− templates/rss-item.xml
62− templates/rss.xml
6359
6460Extra-source-files:
6561 CHANGELOG.md
@@ -72,11 +68,16 @@ Extra-source-files:
7268 tests/data/partial.html
7369 tests/data/partial.html.out
7470 tests/data/posts/2010-08-26-birthday.md
71+ tests/data/posts/2018-09-26.md
7572 tests/data/russian.md
7673 tests/data/strip.html
7774 tests/data/strip.html.out
7875 tests/data/template.html
7976 tests/data/template.html.out
77+ data/templates/atom-item.xml
78+ data/templates/atom.xml
79+ data/templates/rss-item.xml
80+ data/templates/rss.xml
8081
8182Source-Repository head
8283 Type: git
@@ -99,6 +100,11 @@ Flag buildWebsite
99100 Default: False
100101 Manual: True
101102
103+Flag usePandoc
104+ Description: Include Pandoc support
105+ Default: True
106+ Manual: True
107+
102108Library
103109 Ghc-Options: -Wall
104110 Hs-Source-Dirs: lib
@@ -134,9 +140,6 @@ Library
134140 Hakyll.Web.Html
135141 Hakyll.Web.Html.RelativizeUrls
136142 Hakyll.Web.Paginate
137− Hakyll.Web.Pandoc
138− Hakyll.Web.Pandoc.Biblio
139− Hakyll.Web.Pandoc.FileType
140143 Hakyll.Web.Redirect
141144 Hakyll.Web.Tags
142145 Hakyll.Web.Template
@@ -151,55 +154,52 @@ Library
151154 Data.Yaml.Extended
152155 Hakyll.Check
153156 Hakyll.Core.Compiler.Require
157+ Hakyll.Core.Identifier.Pattern.Internal
154158 Hakyll.Core.Item.SomeItem
155159 Hakyll.Core.Provider.Internal
156160 Hakyll.Core.Provider.MetadataCache
157161 Hakyll.Core.Util.Parser
158− Hakyll.Web.Pandoc.Binary
159162 Paths_hakyll
160163
161164 Build-Depends:
162165 base >= 4.8 && < 5,
163− binary >= 0.5 && < 0.9,
166+ binary >= 0.5 && < 0.10,
164167 blaze-html >= 0.5 && < 0.10,
165168 blaze-markup >= 0.5.1 && < 0.9,
166169 bytestring >= 0.9 && < 0.11,
167− containers >= 0.3 && < 0.6,
170+ containers >= 0.3 && < 0.7,
168171 cryptohash >= 0.7 && < 0.12,
169172 data-default >= 0.4 && < 0.8,
170173 deepseq >= 1.3 && < 1.5,
171174 directory >= 1.0 && < 1.4,
175+ file-embed >= 0.0.10.1 && < 0.0.12,
172176 filepath >= 1.0 && < 1.5,
173177 lrucache >= 1.1.1 && < 1.3,
174178 mtl >= 1 && < 2.3,
175− network >= 2.6 && < 2.7,
176179 network-uri >= 2.6 && < 2.7,
177− pandoc >= 2.0.5 && < 2.1,
178− pandoc-citeproc >= 0.12.1.1 && < 0.13,
180+ optparse-applicative >= 0.12 && < 0.15,
179181 parsec >= 3.0 && < 3.2,
180182 process >= 1.6 && < 1.7,
181183 random >= 1.0 && < 1.2,
182− regex-base >= 0.93 && < 0.94,
183184 regex-tdfa >= 1.1 && < 1.3,
184− resourcet >= 1.1 && < 1.2,
185+ resourcet >= 1.1 && < 1.3,
185186 scientific >= 0.3.4 && < 0.4,
186187 tagsoup >= 0.13.1 && < 0.15,
188+ template-haskell >= 2.14 && < 2.15,
187189 text >= 0.11 && < 1.3,
188− time >= 1.8 && < 1.9,
190+ time >= 1.8 && < 1.10,
189191 time-locale-compat >= 0.1 && < 0.2,
190192 unordered-containers >= 0.2 && < 0.3,
191193 vector >= 0.11 && < 0.13,
192− yaml >= 0.8.11 && < 0.9,
193− optparse-applicative >= 0.12 && < 0.15
194+ yaml >= 0.8.11 && < 0.12
194195
195196 If flag(previewServer)
196197 Build-depends:
197198 wai >= 3.2 && < 3.3,
198199 warp >= 3.2 && < 3.3,
199200 wai-app-static >= 3.1 && < 3.2,
200− http-types >= 0.9 && < 0.10,
201− fsnotify >= 0.2 && < 0.3,
202− system-filepath >= 0.4.6 && <= 0.5
201+ http-types >= 0.9 && < 0.13,
202+ fsnotify >= 0.2 && < 0.4
203203 Cpp-options:
204204 -DPREVIEW_SERVER
205205 Other-modules:
@@ -208,8 +208,7 @@ Library
208208
209209 If flag(watchServer)
210210 Build-depends:
211− fsnotify >= 0.2 && < 0.3,
212− system-filepath >= 0.4.6 && <= 0.5
211+ fsnotify >= 0.2 && < 0.4
213212 Cpp-options:
214213 -DWATCH_SERVER
215214 Other-modules:
@@ -217,16 +216,29 @@ Library
217216
218217 If flag(checkExternal)
219218 Build-depends:
220− http-conduit >= 2.2 && < 2.3,
221− http-types >= 0.7 && < 0.10
219+ http-conduit >= 2.2 && < 2.4,
220+ http-types >= 0.7 && < 0.13
222221 Cpp-options:
223222 -DCHECK_EXTERNAL
224223
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+
225237Test-suite hakyll-tests
226238 Type: exitcode-stdio-1.0
227239 Hs-source-dirs: tests
228240 Main-is: TestSuite.hs
229− Ghc-options: -Wall
241+ Ghc-options: -Wall -threaded
230242 Default-language: Haskell2010
231243
232244 Other-modules:
@@ -243,79 +255,47 @@ Test-suite hakyll-tests
243255 Hakyll.Web.CompressCss.Tests
244256 Hakyll.Web.Html.RelativizeUrls.Tests
245257 Hakyll.Web.Html.Tests
246− Hakyll.Web.Pandoc.FileType.Tests
258+ Hakyll.Web.Tags.Tests
247259 Hakyll.Web.Template.Context.Tests
248260 Hakyll.Web.Template.Tests
249261 TestSuite.Util
250262
251263 Build-Depends:
252264 hakyll,
253− QuickCheck >= 2.8 && < 2.11,
254− tasty >= 0.11 && < 0.12,
255− tasty-hunit >= 0.9 && < 0.10,
256− tasty-quickcheck >= 0.8 && < 0.10,
265+ QuickCheck >= 2.8 && < 2.14,
266+ tasty >= 0.11 && < 1.3,
267+ tasty-hunit >= 0.9 && < 0.11,
268+ tasty-quickcheck >= 0.8 && < 0.11,
257269 -- Copy pasted from hakyll dependencies:
258270 base >= 4.8 && < 5,
259− binary >= 0.5 && < 0.9,
260− blaze-html >= 0.5 && < 0.10,
261− blaze-markup >= 0.5.1 && < 0.9,
262271 bytestring >= 0.9 && < 0.11,
263− containers >= 0.3 && < 0.6,
264− cryptohash >= 0.7 && < 0.12,
265− data-default >= 0.4 && < 0.8,
266− deepseq >= 1.3 && < 1.5,
267− directory >= 1.0 && < 1.4,
272+ containers >= 0.3 && < 0.7,
268273 filepath >= 1.0 && < 1.5,
269− lrucache >= 1.1.1 && < 1.3,
270− mtl >= 1 && < 2.3,
271− network >= 2.6 && < 2.7,
272− network-uri >= 2.6 && < 2.7,
273− pandoc >= 2.0.5 && < 2.1,
274− pandoc-citeproc >= 0.12.1.1 && < 0.13,
275− parsec >= 3.0 && < 3.2,
276− process >= 1.6 && < 1.7,
277− random >= 1.0 && < 1.2,
278− regex-base >= 0.93 && < 0.94,
279− regex-tdfa >= 1.1 && < 1.3,
280− resourcet >= 1.1 && < 1.2,
281− scientific >= 0.3.4 && < 0.4,
282− tagsoup >= 0.13.1 && < 0.15,
283274 text >= 0.11 && < 1.3,
284− time >= 1.8 && < 1.9,
285− time-locale-compat >= 0.1 && < 0.2,
286275 unordered-containers >= 0.2 && < 0.3,
287− vector >= 0.11 && < 0.13,
288− yaml >= 0.8.11 && < 0.9,
289− optparse-applicative >= 0.12 && < 0.15
276+ yaml >= 0.8.11 && < 0.12
290277
291278 If flag(previewServer)
292− Build-depends:
293− wai >= 3.2 && < 3.3,
294− warp >= 3.2 && < 3.3,
295− wai-app-static >= 3.1 && < 3.2,
296− http-types >= 0.9 && < 0.10,
297− fsnotify >= 0.2 && < 0.3,
298− system-filepath >= 0.4.6 && <= 0.5
299279 Cpp-options:
300280 -DPREVIEW_SERVER
301281
302282 If flag(watchServer)
303− Build-depends:
304− fsnotify >= 0.2 && < 0.3,
305− system-filepath >= 0.4.6 && <= 0.5
306283 Cpp-options:
307284 -DWATCH_SERVER
308285
309286 If flag(checkExternal)
310− Build-depends:
311− http-conduit >= 2.2 && < 2.3,
312− http-types >= 0.7 && < 0.10
313287 Cpp-options:
314288 -DCHECK_EXTERNAL
315289
290+ If flag(usePandoc)
291+ Other-modules:
292+ Hakyll.Web.Pandoc.FileType.Tests
293+ Cpp-options:
294+ -DUSE_PANDOC
295+
316296Executable hakyll-init
317297 Main-is: Init.hs
318− Ghc-options: -Wall
298+ Ghc-options: -Wall -threaded
319299 Hs-source-dirs: src
320300 Default-language: Haskell2010
321301
@@ -330,7 +310,7 @@ Executable hakyll-init
330310
331311Executable hakyll-website
332312 Main-is: site.hs
333− Ghc-options: -Wall
313+ Ghc-options: -Wall -threaded
334314 Hs-source-dirs: web
335315 Default-language: Haskell2010
336316
@@ -344,4 +324,4 @@ Executable hakyll-website
344324 base >= 4 && < 5,
345325 directory >= 1.0 && < 1.4,
346326 filepath >= 1.0 && < 1.5,
347− pandoc >= 2.0.5 && < 2.1
327+ pandoc >= 2.0.5 && < 2.8
lib/Hakyll.hs+5−1
@@ -20,10 +20,12 @@ module Hakyll
2020 , module Hakyll.Web.Feed
2121 , module Hakyll.Web.Html
2222 , module Hakyll.Web.Html.RelativizeUrls
23− , module Hakyll.Web.Pandoc
2423 , module Hakyll.Web.Paginate
24+#ifdef USE_PANDOC
25+ , module Hakyll.Web.Pandoc
2526 , module Hakyll.Web.Pandoc.Biblio
2627 , module Hakyll.Web.Pandoc.FileType
28+#endif
2729 , module Hakyll.Web.Redirect
2830 , module Hakyll.Web.Tags
2931 , module Hakyll.Web.Template
@@ -52,9 +54,11 @@ import Hakyll.Web.Feed
5254import Hakyll.Web.Html
5355import Hakyll.Web.Html.RelativizeUrls
5456import Hakyll.Web.Paginate
57+#ifdef USE_PANDOC
5558import Hakyll.Web.Pandoc
5659import Hakyll.Web.Pandoc.Biblio
5760import Hakyll.Web.Pandoc.FileType
61+#endif
5862import Hakyll.Web.Redirect
5963import Hakyll.Web.Tags
6064import Hakyll.Web.Template
lib/Hakyll/Check.hs+13
@@ -19,6 +19,9 @@ import Control.Monad.Trans (liftIO)
1919import Control.Monad.Trans.Resource (runResourceT)
2020import Data.List (isPrefixOf)
2121import qualified Data.Map.Lazy as Map
22+#if MIN_VERSION_base(4,9,0)
23+import Data.Semigroup (Semigroup (..))
24+#endif
2225import Network.URI (unEscapeString)
2326import System.Directory (doesDirectoryExist,
2427 doesFileExist)
@@ -85,10 +88,20 @@ data CheckerWrite = CheckerWrite
8588
8689
8790--------------------------------------------------------------------------------
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
88100instance Monoid CheckerWrite where
89101 mempty = CheckerWrite 0 0
90102 mappend (CheckerWrite f1 o1) (CheckerWrite f2 o2) =
91103 CheckerWrite (f1 + f2) (o1 + o2)
104+#endif
92105
93106
94107--------------------------------------------------------------------------------
lib/Hakyll/Core/Compiler.hs+35−2
@@ -24,13 +24,16 @@ module Hakyll.Core.Compiler
2424 , cached
2525 , unsafeCompiler
2626 , debugCompiler
27+ , noResult
28+ , withErrorMessage
2729 ) where
2830
2931
3032--------------------------------------------------------------------------------
31−import Control.Monad (when, unless)
33+import Control.Monad (unless, when, (>=>))
3234import Data.Binary (Binary)
3335import Data.ByteString.Lazy (ByteString)
36+import qualified Data.List.NonEmpty as NonEmpty
3437import Data.Typeable (Typeable)
3538import System.Environment (getProgName)
3639import System.FilePath (takeExtension)
@@ -62,6 +65,7 @@ getUnderlyingExtension = takeExtension . toFilePath <$> getUnderlying
6265
6366
6467--------------------------------------------------------------------------------
68+-- | Create an item from the underlying identifier and a given value.
6569makeItem :: a -> Compiler (Item a)
6670makeItem x = do
6771 identifier <- getUnderlying
@@ -141,6 +145,10 @@ saveSnapshot snapshot item = do
141145
142146
143147--------------------------------------------------------------------------------
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.
144152cached :: (Binary a, Typeable a)
145153 => String
146154 -> Compiler a
@@ -177,12 +185,37 @@ cached name compiler = do
177185
178186
179187--------------------------------------------------------------------------------
188+-- | Run an IO computation without dependencies in a Compiler
180189unsafeCompiler :: IO a -> Compiler a
181190unsafeCompiler = compilerUnsafeIO
182191
183192
184193--------------------------------------------------------------------------------
185−-- | Compiler for debugging purposes
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+--------------------------------------------------------------------------------
217+-- | Compiler for debugging purposes.
218+-- Passes a message to the debug logger that is printed in verbose mode.
186219debugCompiler :: String -> Compiler ()
187220debugCompiler msg = do
188221 logger <- compilerLogger <$> compilerAsk
lib/Hakyll/Core/Compiler/Internal.hs+140−41
@@ -1,5 +1,7 @@
11--------------------------------------------------------------------------------
22-- | Internally used compiler module
3+{-# LANGUAGE CPP #-}
4+{-# LANGUAGE DeriveFunctor #-}
35{-# LANGUAGE FlexibleInstances #-}
46{-# LANGUAGE GADTs #-}
57{-# LANGUAGE GeneralizedNewtypeDeriving #-}
@@ -9,19 +11,26 @@ module Hakyll.Core.Compiler.Internal
911 Snapshot
1012 , CompilerRead (..)
1113 , CompilerWrite (..)
14+ , CompilerErrors (..)
1215 , CompilerResult (..)
1316 , Compiler (..)
1417 , runCompiler
1518
1619 -- * Core operations
20+ , compilerResult
1721 , compilerTell
1822 , compilerAsk
23+ , compilerUnsafeIO
24+
25+ -- * Error operations
1926 , compilerThrow
27+ , compilerNoResult
2028 , compilerCatch
21− , compilerResult
22− , compilerUnsafeIO
29+ , compilerTry
30+ , compilerErrorMessages
2331
2432 -- * Utilities
33+ , compilerDebugEntries
2534 , compilerTellDependencies
2635 , compilerTellCacheHits
2736 ) where
@@ -31,7 +40,12 @@ module Hakyll.Core.Compiler.Internal
3140import Control.Applicative (Alternative (..))
3241import Control.Exception (SomeException, handle)
3342import 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
3549import Data.Set (Set)
3650import qualified Data.Set as S
3751
@@ -41,7 +55,6 @@ import Hakyll.Core.Configuration
4155import Hakyll.Core.Dependencies
4256import Hakyll.Core.Identifier
4357import Hakyll.Core.Identifier.Pattern
44−import Hakyll.Core.Logger (Logger)
4558import qualified Hakyll.Core.Logger as Logger
4659import Hakyll.Core.Metadata
4760import Hakyll.Core.Provider
@@ -71,7 +84,7 @@ data CompilerRead = CompilerRead
7184 , -- | Compiler store
7285 compilerStore :: Store
7386 , -- | Logger
74− compilerLogger :: Logger
87+ compilerLogger :: Logger.Logger
7588 }
7689
7790
@@ -83,18 +96,46 @@ data CompilerWrite = CompilerWrite
8396
8497
8598--------------------------------------------------------------------------------
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
86108instance Monoid CompilerWrite where
87109 mempty = CompilerWrite [] 0
88110 mappend (CompilerWrite d1 h1) (CompilerWrite d2 h2) =
89111 CompilerWrite (d1 ++ d2) (h1 + h2)
112+#endif
90113
91114
92115--------------------------------------------------------------------------------
93−data CompilerResult a where
94− CompilerDone :: a -> CompilerWrite -> CompilerResult a
95− CompilerSnapshot :: Snapshot -> Compiler a -> CompilerResult a
96− CompilerError :: [String] -> CompilerResult a
97− CompilerRequire :: (Identifier, Snapshot) -> Compiler a -> CompilerResult a
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
135+ = CompilerDone a CompilerWrite
136+ | CompilerSnapshot Snapshot (Compiler a)
137+ | CompilerRequire (Identifier, Snapshot) (Compiler a)
138+ | CompilerError (CompilerErrors String)
98139
99140
100141--------------------------------------------------------------------------------
@@ -112,14 +153,14 @@ instance Functor Compiler where
112153 return $ case res of
113154 CompilerDone x w -> CompilerDone (f x) w
114155 CompilerSnapshot s c' -> CompilerSnapshot s (fmap f c')
115− CompilerError e -> CompilerError e
116156 CompilerRequire i c' -> CompilerRequire i (fmap f c')
157+ CompilerError e -> CompilerError e
117158 {-# INLINE fmap #-}
118159
119160
120161--------------------------------------------------------------------------------
121162instance Monad Compiler where
122− return x = Compiler $ \_ -> return $ CompilerDone x mempty
163+ return x = compilerResult $ CompilerDone x mempty
123164 {-# INLINE return #-}
124165
125166 Compiler c >>= f = Compiler $ \r -> do
@@ -132,14 +173,14 @@ instance Monad Compiler where
132173 CompilerSnapshot s c' -> CompilerSnapshot s $ do
133174 compilerTell w -- Save dependencies!
134175 c'
135− CompilerError e -> CompilerError e
136176 CompilerRequire i c' -> CompilerRequire i $ do
137177 compilerTell w -- Save dependencies!
138178 c'
179+ CompilerError e -> CompilerError e
139180
140181 CompilerSnapshot s c' -> return $ CompilerSnapshot s (c' >>= f)
141− CompilerError e -> return $ CompilerError e
142182 CompilerRequire i c' -> return $ CompilerRequire i (c' >>= f)
183+ CompilerError e -> return $ CompilerError e
143184 {-# INLINE (>>=) #-}
144185
145186 fail = compilerThrow . return
@@ -156,87 +197,145 @@ instance Applicative Compiler where
156197
157198
158199--------------------------------------------------------------------------------
200+-- | Access provided metadata from anywhere
159201instance MonadMetadata Compiler where
160202 getMetadata = compilerGetMetadata
161203 getMatches = compilerGetMatches
162204
163205
164206--------------------------------------------------------------------------------
207+-- | Compilation may fail with multiple error messages.
208+-- 'catchError' handles errors from 'throwError', 'fail' and 'Hakyll.Core.Compiler.noResult'
165209instance MonadError [String] Compiler where
166− throwError = compilerThrow
167− catchError = compilerCatch
210+ throwError = compilerThrow
211+ catchError c = compilerCatch c . (. compilerErrorMessages)
168212
169213
170214--------------------------------------------------------------------------------
215+-- | Like 'unCompiler' but treating IO exceptions as 'CompilerError's
171216runCompiler :: Compiler a -> CompilerRead -> IO (CompilerResult a)
172217runCompiler compiler read' = handle handler $ unCompiler compiler read'
173218 where
174219 handler :: SomeException -> IO (CompilerResult a)
175− handler e = return $ CompilerError [show e]
220+ handler e = return $ CompilerError $ CompilationFailure $ show e :| []
176221
177222
178223--------------------------------------------------------------------------------
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.
179227instance Alternative Compiler where
180− empty = compilerThrow []
181− x <|> y = compilerCatch x $ \es -> do
182− logger <- compilerLogger <$> compilerAsk
183− forM_ es $ \e -> compilerUnsafeIO $ Logger.debug logger $
184− "Hakyll.Core.Compiler.Internal: Alternative failed: " ++ e
185− y
228+ empty = compilerNoResult []
229+ x <|> y = x `compilerCatch` (\rx -> y `compilerCatch` (\ry ->
230+ case (rx, ry) of
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
240+ debug = compilerDebugEntries "Hakyll.Core.Compiler.Internal: Alternative fail suppressed"
186241 {-# INLINE (<|>) #-}
187242
188243
189244--------------------------------------------------------------------------------
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
190253compilerAsk :: Compiler CompilerRead
191254compilerAsk = Compiler $ \r -> return $ CompilerDone r mempty
192255{-# INLINE compilerAsk #-}
193256
194257
195258--------------------------------------------------------------------------------
259+-- | Put a 'CompilerWrite'
196260compilerTell :: CompilerWrite -> Compiler ()
197−compilerTell deps = Compiler $ \_ -> return $ CompilerDone () deps
261+compilerTell = compilerResult . CompilerDone ()
198262{-# INLINE compilerTell #-}
199263
200264
201265--------------------------------------------------------------------------------
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.
202279compilerThrow :: [String] -> Compiler a
203−compilerThrow es = Compiler $ \_ -> return $ CompilerError es
204−{-# INLINE compilerThrow #-}
280+compilerThrow = compilerResult . CompilerError .
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
205287
206288
207289--------------------------------------------------------------------------------
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
209310compilerCatch (Compiler x) f = Compiler $ \r -> do
210311 res <- x r
211312 case res of
212313 CompilerDone res' w -> return (CompilerDone res' w)
213314 CompilerSnapshot s c -> return (CompilerSnapshot s (compilerCatch c f))
214− CompilerError e -> unCompiler (f e) r
215315 CompilerRequire i c -> return (CompilerRequire i (compilerCatch c f))
316+ CompilerError e -> unCompiler (f e) r
216317{-# INLINE compilerCatch #-}
217318
218319
219320--------------------------------------------------------------------------------
220−-- | Put the result back in a compiler
221−compilerResult :: CompilerResult a -> Compiler a
222−compilerResult x = Compiler $ \_ -> return x
223−{-# INLINE compilerResult #-}
224−
321+compilerDebugLog :: [String] -> Compiler ()
322+compilerDebugLog ms = do
323+ logger <- compilerLogger <$> compilerAsk
324+ compilerUnsafeIO $ forM_ ms $ Logger.debug logger
225325
226326--------------------------------------------------------------------------------
227−compilerUnsafeIO :: IO a -> Compiler a
228−compilerUnsafeIO io = Compiler $ \_ -> do
229− x <- io
230− return $ CompilerDone x mempty
231−{-# INLINE compilerUnsafeIO #-}
327+-- | Pass a list of messages with a heading to the debug logger
328+compilerDebugEntries :: String -> [String] -> Compiler ()
329+compilerDebugEntries msg = compilerDebugLog . (msg:) . map indent
330+ where
331+ indent = unlines . map (" "++) . lines
232332
233333
234334--------------------------------------------------------------------------------
235335compilerTellDependencies :: [Dependency] -> Compiler ()
236336compilerTellDependencies ds = do
237− logger <- compilerLogger <$> compilerAsk
238− forM_ ds $ \d -> compilerUnsafeIO $ Logger.debug logger $
239− "Hakyll.Core.Compiler.Internal: Adding dependency: " ++ show d
337+ compilerDebugLog $ map (\d ->
338+ "Hakyll.Core.Compiler.Internal: Adding dependency: " ++ show d) ds
240339 compilerTell mempty {compilerDependencies = ds}
241340{-# INLINE compilerTellDependencies #-}
242341
lib/Hakyll/Core/Compiler/Require.hs+2
@@ -91,6 +91,7 @@ loadBody id' = loadSnapshotBody id' final
9191
9292
9393--------------------------------------------------------------------------------
94+-- | A shortcut for only requiring the body for a specific snapshot of an item
9495loadSnapshotBody :: (Binary a, Typeable a)
9596 => Identifier -> Snapshot -> Compiler a
9697loadSnapshotBody id' snapshot = fmap itemBody $ loadSnapshot id' snapshot
@@ -103,6 +104,7 @@ loadAll pattern = loadAllSnapshots pattern final
103104
104105
105106--------------------------------------------------------------------------------
107+-- | Load a specific snapshot for each of dynamic list of items
106108loadAllSnapshots :: (Binary a, Typeable a)
107109 => Pattern -> Snapshot -> Compiler [Item a]
108110loadAllSnapshots pattern snapshot = do
lib/Hakyll/Core/Identifier.hs+1−1
@@ -62,7 +62,7 @@ instance Show Identifier where
6262
6363--------------------------------------------------------------------------------
6464-- | Parse an identifier from a string
65−fromFilePath :: String -> Identifier
65+fromFilePath :: FilePath -> Identifier
6666fromFilePath = Identifier Nothing .
6767 intercalate "/" . filter (not . null) . split'
6868 where
lib/Hakyll/Core/Identifier/Pattern.hs+10−70
@@ -28,6 +28,7 @@
2828--
2929-- The 'capture' function allows the user to get access to the elements captured
3030-- by the capture elements in a glob or regex pattern.
31+{-# OPTIONS_GHC -fno-warn-orphans #-}
3132module Hakyll.Core.Identifier.Pattern
3233 ( -- * The pattern type
3334 Pattern
@@ -57,77 +58,22 @@ module Hakyll.Core.Identifier.Pattern
5758
5859
5960--------------------------------------------------------------------------------
60−import Control.Arrow ((&&&), (>>>))
61−import Control.Monad (msum)
62−import Data.Binary (Binary (..), getWord8, putWord8)
63−import Data.List (inits, isPrefixOf, tails)
64−import Data.Maybe (isJust)
65−import Data.Set (Set)
66−import qualified Data.Set as S
61+import Control.Arrow ((&&&), (>>>))
62+import Control.Monad (msum)
63+import Data.List (inits, isPrefixOf,
64+ tails)
65+import Data.Maybe (isJust)
66+import qualified Data.Set as S
6767
6868
6969--------------------------------------------------------------------------------
70−import GHC.Exts (IsString, fromString)
71−import Text.Regex.TDFA ((=~))
70+import GHC.Exts (IsString, fromString)
71+import Text.Regex.TDFA ((=~))
7272
7373
7474--------------------------------------------------------------------------------
7575import Hakyll.Core.Identifier
76−
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
76+import Hakyll.Core.Identifier.Pattern.Internal
13177
13278
13379--------------------------------------------------------------------------------
@@ -135,12 +81,6 @@ instance IsString Pattern where
13581 fromString = fromGlob
13682
13783
138−--------------------------------------------------------------------------------
139−instance Monoid Pattern where
140− mempty = Everything
141− mappend = (.&&.)
142−
143−
14484--------------------------------------------------------------------------------
14585-- | Parse a pattern from a string
14686fromGlob :: 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)
1818import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, takeMVar)
1919import Control.Monad (forever)
2020import Control.Monad.Trans (MonadIO, liftIO)
21+import Data.List (intercalate)
2122import Prelude hiding (error)
2223
2324
@@ -79,7 +80,7 @@ string l v m
7980
8081--------------------------------------------------------------------------------
8182error :: MonadIO m => Logger -> String -> m ()
82−error l m = string l Error $ " [ERROR] " ++ m
83+error l m = string l Error $ " [ERROR] " ++ indent m
8384
8485
8586--------------------------------------------------------------------------------
@@ -89,9 +90,14 @@ header l = string l Message
8990
9091--------------------------------------------------------------------------------
9192message :: MonadIO m => Logger -> String -> m ()
92−message l m = string l Message $ " " ++ m
93+message l m = string l Message $ " " ++ indent m
9394
9495
9596--------------------------------------------------------------------------------
9697debug :: 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
2727import Hakyll.Core.Metadata
2828import Hakyll.Core.Provider.Internal
2929import System.IO as IO
30+import System.IO.Error (modifyIOError, ioeSetLocation)
3031
3132
3233--------------------------------------------------------------------------------
@@ -51,7 +52,7 @@ loadMetadata p identifier = do
5152--------------------------------------------------------------------------------
5253loadMetadataHeader :: FilePath -> IO (Metadata, String)
5354loadMetadataHeader fp = do
54− fileContent <- readFile fp
55+ fileContent <- modifyIOError (`ioeSetLocation` "loadMetadataHeader") $ readFile fp
5556 case parsePage fileContent of
5657 Right x -> return x
5758 Left err -> throwIO $ MetadataException fp err
@@ -60,7 +61,7 @@ loadMetadataHeader fp = do
6061--------------------------------------------------------------------------------
6162loadMetadataFile :: FilePath -> IO Metadata
6263loadMetadataFile fp = do
63− fileContent <- B.readFile fp
64+ fileContent <- modifyIOError (`ioeSetLocation` "loadMetadataFile") $ B.readFile fp
6465 let errOrMeta = Yaml.decodeEither' fileContent
6566 either (fail . show) return errOrMeta
6667
lib/Hakyll/Core/Routes.hs+17
@@ -25,6 +25,7 @@
2525-- not appear in your site directory.
2626--
2727-- * If an item matches multiple routes, the first rule will be chosen.
28+{-# LANGUAGE CPP #-}
2829{-# LANGUAGE Rank2Types #-}
2930module Hakyll.Core.Routes
3031 ( UsedMetadata
@@ -42,6 +43,9 @@ module Hakyll.Core.Routes
4243
4344
4445--------------------------------------------------------------------------------
46+#if MIN_VERSION_base(4,9,0)
47+import Data.Semigroup (Semigroup (..))
48+#endif
4549import System.FilePath (replaceExtension)
4650
4751
@@ -74,6 +78,18 @@ newtype Routes = Routes
7478
7579
7680--------------------------------------------------------------------------------
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
7793instance Monoid Routes where
7894 mempty = Routes $ \_ _ -> return (Nothing, False)
7995 mappend (Routes f) (Routes g) = Routes $ \p id' -> do
@@ -81,6 +97,7 @@ instance Monoid Routes where
8197 case mfp of
8298 Nothing -> g p id'
8399 Just _ -> return (mfp, um)
100+#endif
84101
85102
86103--------------------------------------------------------------------------------
lib/Hakyll/Core/Rules/Internal.hs+14
@@ -1,4 +1,5 @@
11--------------------------------------------------------------------------------
2+{-# LANGUAGE CPP #-}
23{-# LANGUAGE GeneralizedNewtypeDeriving #-}
34{-# LANGUAGE Rank2Types #-}
45module Hakyll.Core.Rules.Internal
@@ -16,6 +17,9 @@ import Control.Monad.Reader (ask)
1617import Control.Monad.RWS (RWST, runRWST)
1718import Control.Monad.Trans (liftIO)
1819import qualified Data.Map as M
20+#if MIN_VERSION_base(4,9,0)
21+import Data.Semigroup (Semigroup (..))
22+#endif
1923import Data.Set (Set)
2024
2125
@@ -52,10 +56,20 @@ data RuleSet = RuleSet
5256
5357
5458--------------------------------------------------------------------------------
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
5568instance Monoid RuleSet where
5669 mempty = RuleSet mempty mempty mempty mempty
5770 mappend (RuleSet r1 c1 s1 p1) (RuleSet r2 c2 s2 p2) =
5871 RuleSet (mappend r1 r2) (mappend c1 c2) (mappend s1 s2) (p1 .||. p2)
72+#endif
5973
6074
6175--------------------------------------------------------------------------------
lib/Hakyll/Core/Runtime.hs+3−3
@@ -199,9 +199,9 @@ chase trail id'
199199 result <- liftIO $ runCompiler compiler read'
200200 case result of
201201 -- Rethrow error
202− CompilerError [] -> throwError
203− "Compiler failed but no info given, try running with -v?"
204− CompilerError es -> throwError $ intercalate "; " es
202+ CompilerError e -> throwError $ case compilerErrorMessages e of
203+ [] -> "Compiler failed but no info given, try running with -v?"
204+ es -> intercalate "; " es
205205
206206 -- Signal that a snapshot was saved ->
207207 CompilerSnapshot snapshot c -> do
lib/Hakyll/Core/Store.hs+26−23
@@ -16,7 +16,6 @@ module Hakyll.Core.Store
1616
1717
1818--------------------------------------------------------------------------------
19−import Control.Exception (IOException, handle)
2019import qualified Crypto.Hash.MD5 as MD5
2120import Data.Binary (Binary, decode, encodeFile)
2221import qualified Data.ByteString as B
@@ -27,11 +26,13 @@ import Data.Maybe (isJust)
2726import qualified Data.Text as T
2827import qualified Data.Text.Encoding as T
2928import Data.Typeable (TypeRep, Typeable, cast, typeOf)
29+import Numeric (showHex)
3030import System.Directory (createDirectoryIfMissing)
3131import System.Directory (doesFileExist, removeFile)
3232import System.FilePath ((</>))
3333import System.IO (IOMode (..), hClose, openFile)
34−import Text.Printf (printf)
34+import System.IO.Error (catchIOError, ioeSetFileName,
35+ ioeSetLocation, modifyIOError)
3536
3637
3738--------------------------------------------------------------------------------
@@ -84,6 +85,14 @@ new inMemory directory = do
8485 where
8586 csize = Just 500
8687
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)
8796
8897--------------------------------------------------------------------------------
8998-- | Auxiliary: add an item to the in-memory cache
@@ -124,17 +133,16 @@ cacheDelete (Store _ (Just lru)) key = do
124133--------------------------------------------------------------------------------
125134-- | Store an item
126135set :: (Binary a, Typeable a) => Store -> [String] -> a -> IO ()
127−set store identifier value = do
128− encodeFile (storeDirectory store </> key) value
136+set store identifier value = withStore store "set" (\key path -> do
137+ encodeFile path value
129138 cacheInsert store key value
130− where
131− key = hash identifier
139+ ) identifier
132140
133141
134142--------------------------------------------------------------------------------
135143-- | Load an item
136144get :: (Binary a, Typeable a) => Store -> [String] -> IO (Result a)
137−get store identifier = do
145+get store = withStore store "get" $ \key path -> do
138146 -- First check the in-memory map
139147 ref <- cacheLookup store key
140148 case ref of
@@ -146,17 +154,14 @@ get store identifier = do
146154 then return NotFound
147155 -- Found in the filesystem
148156 else do
149− v <- decodeClose
157+ v <- decodeClose path
150158 cacheInsert store key v
151159 return $ Found v
152160 -- Found in the in-memory map (or wrong type), just return
153161 s -> return s
154162 where
155− key = hash identifier
156− path = storeDirectory store </> key
157−
158163 -- 'decodeFile' from Data.Binary which closes the file ASAP
159− decodeClose = do
164+ decodeClose path = do
160165 h <- openFile path ReadMode
161166 lbs <- BL.hGetContents h
162167 BL.length lbs `seq` hClose h
@@ -166,32 +171,30 @@ get store identifier = do
166171--------------------------------------------------------------------------------
167172-- | Strict function
168173isMember :: Store -> [String] -> IO Bool
169−isMember store identifier = do
174+isMember store = withStore store "isMember" $ \key path -> do
170175 inCache <- cacheIsMember store key
171176 if inCache then return True else doesFileExist path
172− where
173− key = hash identifier
174− path = storeDirectory store </> key
175177
176178
177179--------------------------------------------------------------------------------
178180-- | Delete an item
179181delete :: Store -> [String] -> IO ()
180−delete store identifier = do
182+delete store = withStore store "delete" $ \key path -> do
181183 cacheDelete store key
182− deleteFile $ storeDirectory store </> key
183− where
184− key = hash identifier
184+ deleteFile path
185185
186186
187187--------------------------------------------------------------------------------
188188-- | Delete a file unless it doesn't exist...
189189deleteFile :: FilePath -> IO ()
190−deleteFile = handle (\(_ :: IOException) -> return ()) . removeFile
190+deleteFile = (`catchIOError` \_ -> return ()) . removeFile
191191
192192
193193--------------------------------------------------------------------------------
194194-- | Mostly meant for internal usage
195195hash :: [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 @@
22-- | Parser utilities
33module Hakyll.Core.Util.Parser
44 ( metadataKey
5− , reservedKeys
65 ) where
76
87
lib/Hakyll/Core/Util/String.hs+1−1
@@ -27,7 +27,7 @@ trim = reverse . trim' . reverse . trim'
2727--------------------------------------------------------------------------------
2828-- | A simple (but inefficient) regex replace funcion
2929replaceAll :: String -- ^ Pattern
30− -> (String -> String) -- ^ Replacement (called on capture)
30+ -> (String -> String) -- ^ Replacement (called on match)
3131 -> String -- ^ Source string
3232 -> String -- ^ Result
3333replaceAll pattern f source = replaceAll' source
lib/Hakyll/Preview/Poll.hs+8−18
@@ -14,8 +14,7 @@ import Control.Exception (AsyncException, fromException,
1414import Control.Monad (forever, void, when)
1515import System.Directory (canonicalizePath)
1616import System.FilePath (pathSeparators)
17−import System.FSNotify (Event (..), startManager,
18− watchTree)
17+import qualified System.FSNotify as FSNotify
1918
2019#ifdef mingw32_HOST_OS
2120import Control.Concurrent (threadDelay)
@@ -44,12 +43,12 @@ watchUpdates conf update = do
4443 shouldBuild <- newEmptyMVar
4544 pattern <- update
4645 fullProviderDir <- canonicalizePath $ providerDirectory conf
47− manager <- startManager
46+ manager <- FSNotify.startManager
4847
4948 let allowed event = do
5049 -- Absolute path of the changed file. This must be inside provider
5150 -- dir, since that's the only dir we're watching.
52− let path = eventPath event
51+ let path = FSNotify.eventPath event
5352 relative = dropWhile (`elem` pathSeparators) $
5453 drop (length fullProviderDir) path
5554 identifier = fromFilePath relative
@@ -69,7 +68,7 @@ watchUpdates conf update = do
6968
7069 -- Send an event whenever something occurs so that the thread described
7170 -- above will do a build.
72− void $ watchTree manager providerDir (not . isRemove) $ \event -> do
71+ void $ FSNotify.watchTree manager providerDir (not . isRemove) $ \event -> do
7372 allowed' <- allowed event
7473 when allowed' $ void $ tryPutMVar shouldBuild event
7574 where
@@ -77,7 +76,7 @@ watchUpdates conf update = do
7776 update' _ _ = void update
7877#else
7978 update' event provider = do
80− let path = provider </> eventPath event
79+ let path = provider </> FSNotify.eventPath event
8180 -- on windows, a 'Modified' event is also sent on file deletion
8281 fileExists <- doesFileExist path
8382
@@ -105,15 +104,6 @@ watchUpdates conf update = do
105104
106105
107106--------------------------------------------------------------------------------
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
118−isRemove (Removed _ _) = True
119−isRemove _ = False
107+isRemove :: FSNotify.Event -> Bool
108+isRemove (FSNotify.Removed {}) = True
109+isRemove _ = False
lib/Hakyll/Web/CompressCss.hs+59−62
@@ -1,7 +1,6 @@
11--------------------------------------------------------------------------------
22-- | Module used for CSS compression. The compression is currently in a simple
33-- state, but would typically reduce the number of bytes by about 25%.
4−{-# LANGUAGE PatternGuards #-}
54module Hakyll.Web.CompressCss
65 ( compressCssCompiler
76 , compressCss
@@ -9,12 +8,14 @@ module Hakyll.Web.CompressCss
98
109
1110--------------------------------------------------------------------------------
12−import Data.List (isPrefixOf)
11+import Data.Char (isSpace)
12+import Data.List (dropWhileEnd, isPrefixOf)
1313
1414
1515--------------------------------------------------------------------------------
1616import Hakyll.Core.Compiler
1717import Hakyll.Core.Item
18+import Hakyll.Core.Util.String
1819
1920
2021--------------------------------------------------------------------------------
@@ -26,79 +27,75 @@ compressCssCompiler = fmap compressCss <$> getResourceString
2627--------------------------------------------------------------------------------
2728-- | Compress CSS to speed up your site.
2829compressCss :: String -> String
29−compressCss = compressSeparators . stripComments . compressWhitespace
30+compressCss = withoutStrings (handleCalcExpressions compressSeparators . compressWhitespace)
31+ . dropWhileEnd isSpace
32+ . dropWhile isSpace
33+ . stripComments
3034
3135
3236--------------------------------------------------------------------------------
3337-- | Compresses certain forms of separators.
3438compressSeparators :: String -> String
35−compressSeparators [] = []
36−compressSeparators str
37− | isConstant = head str : retainConstants compressSeparators (head str) (drop 1 str)
38− | isPrefixOf "calc( " str = "calc(" ++ compressCalcSeparators 1 (drop 6 str)
39− | isPrefixOf "calc(" str = "calc(" ++ compressCalcSeparators 1 (drop 5 str)
40− | stripFirst = compressSeparators (drop 1 str)
41− | stripSecond = compressSeparators (head str : (drop 2 str))
42− | otherwise = head str : compressSeparators (drop 1 str)
43− where
44− isConstant = or $ map (isOfPrefix str) ["\"", "'"]
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)
39+compressSeparators =
40+ replaceAll "; *}" (const "}") .
41+ replaceAll ";+" (const ";") .
42+ replaceAll " *[{};,>+~!] *" (take 1 . dropWhile isSpace) .
43+ replaceAll ": *" (take 1) -- not destroying pseudo selectors (#323)
44+
45+-- | Uses `compressCalcExpression` on all parenthesised calc expressions
46+-- and applies `transform` to all parts outside of them
47+handleCalcExpressions :: (String -> String) -> String -> String
48+handleCalcExpressions transform = top transform
6049 where
61− stripFirst = or $ map (isOfPrefix str) $ map (\c -> " " ++ c) ["*", "/", ")"]
62− stripSecond = or $ map (isOfPrefix str) $ map (\c -> c ++ " ") ["*", "/", "("]
50+ top f "" = f ""
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)
6369
6470--------------------------------------------------------------------------------
6571-- | Compresses all whitespace.
6672compressWhitespace :: String -> String
67−compressWhitespace [] = []
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", " "]
73+compressWhitespace = replaceAll "[ \t\n\r]+" (const " ")
7774
7875--------------------------------------------------------------------------------
79−-- | Function that strips CSS comments away.
76+-- | Function that strips CSS comments away (outside of strings).
8077stripComments :: String -> String
81−stripComments [] = []
82−stripComments str
83− | isConstant = head str : retainConstants stripComments (head str) (drop 1 str)
84− | isPrefixOf "/*" str = stripComments $ eatComments $ drop 2 str
85− | otherwise = head str : stripComments (drop 1 str)
86− where
87− isConstant = or $ map (isOfPrefix str) ["\"", "'"]
88− eatComments str'
89− | null str' = []
90− | isPrefixOf "*/" str' = drop 2 str'
91− | otherwise = eatComments $ drop 1 str'
78+stripComments "" = ""
79+stripComments ('/':'*':str) = stripComments $ eatComment str
80+stripComments (x:xs) | x `elem` "\"'" = retainString x xs stripComments
81+ | otherwise = x : stripComments xs
82+
83+eatComment :: String -> String
84+eatComment "" = ""
85+eatComment ('*':'/':str) = str
86+eatComment (_:str) = eatComment str
9287
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)
10088
10189--------------------------------------------------------------------------------
102−-- | Helper function to determine whether a string is a substring.
103−isOfPrefix :: String -> String -> Bool
104−isOfPrefix = flip isPrefixOf
90+-- | Helper functions to handle string tokens correctly.
91+
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+59−21
@@ -1,3 +1,6 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+{-# LANGUAGE TemplateHaskell #-}
3+
14--------------------------------------------------------------------------------
25-- | A Module that allows easy rendering of RSS feeds.
36--
@@ -20,21 +23,40 @@ module Hakyll.Web.Feed
2023 ( FeedConfiguration (..)
2124 , renderRss
2225 , renderAtom
26+ , renderRssWithTemplates
27+ , renderAtomWithTemplates
2328 ) where
2429
2530
2631--------------------------------------------------------------------------------
2732import Hakyll.Core.Compiler
28−import Hakyll.Core.Compiler.Internal
2933import Hakyll.Core.Item
30−import Hakyll.Core.Util.String (replaceAll)
34+import Hakyll.Core.Util.String (replaceAll)
3135import Hakyll.Web.Template
3236import Hakyll.Web.Template.Context
3337import Hakyll.Web.Template.List
3438
3539
3640--------------------------------------------------------------------------------
37−import Paths_hakyll
41+import Data.FileEmbed (makeRelativeToProject)
42+
43+
44+--------------------------------------------------------------------------------
45+rssTemplate :: Template
46+rssTemplate =
47+ $(makeRelativeToProject "data/templates/rss.xml" >>= embedTemplate)
48+
49+rssItemTemplate :: Template
50+rssItemTemplate =
51+ $(makeRelativeToProject "data/templates/rss-item.xml" >>= embedTemplate)
52+
53+atomTemplate :: Template
54+atomTemplate =
55+ $(makeRelativeToProject "data/templates/atom.xml" >>= embedTemplate)
56+
57+atomItemTemplate :: Template
58+atomItemTemplate =
59+ $(makeRelativeToProject "data/templates/atom-item.xml" >>= embedTemplate)
3860
3961
4062--------------------------------------------------------------------------------
@@ -55,16 +77,13 @@ data FeedConfiguration = FeedConfiguration
5577
5678--------------------------------------------------------------------------------
5779-- | Abstract function to render any feed.
58−renderFeed :: FilePath -- ^ Feed template
59− -> FilePath -- ^ Item template
80+renderFeed :: Template -- ^ Default feed template
81+ -> Template -- ^ Default item template
6082 -> FeedConfiguration -- ^ Feed configuration
6183 -> Context String -- ^ Context for the items
6284 -> [Item String] -- ^ Input items
6385 -> Compiler (Item String) -- ^ Resulting item
64−renderFeed feedPath itemPath config itemContext items = do
65− feedTpl <- loadTemplate feedPath
66− itemTpl <- loadTemplate itemPath
67−
86+renderFeed feedTpl itemTpl config itemContext items = do
6887 protectedItems <- mapM (applyFilter protectCDATA) items
6988 body <- makeItem =<< applyTemplateList itemTpl itemContext' protectedItems
7089 applyTemplate feedTpl feedContext body
@@ -73,10 +92,6 @@ renderFeed feedPath itemPath config itemContext items = do
7392 applyFilter tr str = return $ fmap tr str
7493 protectCDATA :: String -> String
7594 protectCDATA = replaceAll "]]>" (const "]]&gt;")
76− -- Auxiliary: load a template from a datafile
77− loadTemplate path = do
78− file <- compilerUnsafeIO $ getDataFileName path
79− unsafeReadTemplateFile file
8095
8196 itemContext' = mconcat
8297 [ itemContext
@@ -102,8 +117,35 @@ renderFeed feedPath itemPath config itemContext items = do
102117 updatedField = field "updated" $ \_ -> case items of
103118 [] -> return "Unknown"
104119 (x : _) -> unContext itemContext' "updated" [] x >>= \cf -> case cf of
105− ListField _ _ -> fail "Hakyll.Web.Feed.renderFeed: Internal error"
106120 StringField s -> return s
121+ _ -> fail "Hakyll.Web.Feed.renderFeed: Internal error"
122+
123+--------------------------------------------------------------------------------
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)
107149
108150
109151--------------------------------------------------------------------------------
@@ -112,9 +154,7 @@ renderRss :: FeedConfiguration -- ^ Feed configuration
112154 -> Context String -- ^ Item context
113155 -> [Item String] -- ^ Feed items
114156 -> Compiler (Item String) -- ^ Resulting feed
115−renderRss config context = renderFeed
116− "templates/rss.xml" "templates/rss-item.xml" config
117− (makeItemContext "%a, %d %b %Y %H:%M:%S UT" context)
157+renderRss = renderRssWithTemplates rssTemplate rssItemTemplate
118158
119159
120160--------------------------------------------------------------------------------
@@ -123,13 +163,11 @@ renderAtom :: FeedConfiguration -- ^ Feed configuration
123163 -> Context String -- ^ Item context
124164 -> [Item String] -- ^ Feed items
125165 -> Compiler (Item String) -- ^ Resulting feed
126−renderAtom config context = renderFeed
127− "templates/atom.xml" "templates/atom-item.xml" config
128− (makeItemContext "%Y-%m-%dT%H:%M:%SZ" context)
166+renderAtom = renderAtomWithTemplates atomTemplate atomItemTemplate
129167
130168
131169--------------------------------------------------------------------------------
132170-- | Copies @$updated$@ from @$published$@ if it is not already set.
133171makeItemContext :: String -> Context a -> Context a
134172makeItemContext fmt context = mconcat
135− [dateField "published" fmt, context, dateField "updated" fmt]
173+ [context, dateField "published" fmt, dateField "updated" fmt]
lib/Hakyll/Web/Html.hs+6−2
@@ -3,6 +3,7 @@
33module Hakyll.Web.Html
44 ( -- * Generic
55 withTags
6+ , withTagList
67
78 -- * Headers
89 , demoteHeaders
@@ -36,8 +37,11 @@ import Network.URI (isUnreserved, escapeURIString)
3637--------------------------------------------------------------------------------
3738-- | Map over all tags in the document
3839withTags :: (TS.Tag String -> TS.Tag String) -> String -> String
39−withTags f = renderTags' . map f . parseTags'
40+withTags = withTagList . map
4041
42+-- | Map over all tags (as list) in the document
43+withTagList :: ([TS.Tag String] -> [TS.Tag String]) -> String -> String
44+withTagList f = renderTags' . f . parseTags'
4145
4246--------------------------------------------------------------------------------
4347-- | Map every @h1@ to an @h2@, @h2@ to @h3@, etc.
@@ -55,7 +59,7 @@ demoteHeaders = withTags $ \tag -> case tag of
5559
5660--------------------------------------------------------------------------------
5761isUrlAttribute :: String -> Bool
58−isUrlAttribute = (`elem` ["src", "href", "data", "poster"])
62+isUrlAttribute = (`elem` ["src", "href", "data", "poster", "srcset"])
5963
6064
6165--------------------------------------------------------------------------------
lib/Hakyll/Web/Pandoc/Biblio.hs+17−6
@@ -25,17 +25,19 @@ module Hakyll.Web.Pandoc.Biblio
2525--------------------------------------------------------------------------------
2626import Control.Monad (liftM, replicateM)
2727import Data.Binary (Binary (..))
28−import Data.Default (def)
2928import Data.Typeable (Typeable)
3029import Hakyll.Core.Compiler
30+import Hakyll.Core.Compiler.Internal
3131import Hakyll.Core.Identifier
3232import Hakyll.Core.Item
33+import Hakyll.Core.Provider
3334import Hakyll.Core.Writable
3435import Hakyll.Web.Pandoc
3536import Hakyll.Web.Pandoc.Binary ()
3637import qualified Text.CSL as CSL
3738import Text.CSL.Pandoc (processCites)
38−import Text.Pandoc (Pandoc, ReaderOptions (..))
39+import Text.Pandoc (Pandoc, ReaderOptions (..),
40+ enableExtension, Extension (..))
3941
4042
4143--------------------------------------------------------------------------------
@@ -83,8 +85,11 @@ instance Writable Biblio where
8385--------------------------------------------------------------------------------
8486biblioCompiler :: Compiler (Item Biblio)
8587biblioCompiler = do
86− filePath <- toFilePath <$> getUnderlying
87− makeItem =<< unsafeCompiler (Biblio <$> CSL.readBiblioFile filePath)
88+ filePath <- getResourceFilePath
89+ makeItem =<< unsafeCompiler (Biblio <$> CSL.readBiblioFile idpred filePath)
90+ where
91+ -- This is a filter on citations. We include all citations.
92+ idpred = const True
8893
8994
9095--------------------------------------------------------------------------------
@@ -95,7 +100,9 @@ readPandocBiblio :: ReaderOptions
95100 -> Compiler (Item Pandoc)
96101readPandocBiblio ropt csl biblio item = do
97102 -- Parse CSL file, if given
98− style <- unsafeCompiler $ CSL.readCSLFile Nothing . toFilePath . itemIdentifier $ csl
103+ provider <- compilerProvider <$> compilerAsk
104+ style <- unsafeCompiler $
105+ CSL.readCSLFile Nothing . (resourceFilePath provider) . itemIdentifier $ csl
99106
100107 -- We need to know the citation keys, add then *before* actually parsing the
101108 -- actual page. If we don't do this, pandoc won't even consider them
@@ -112,4 +119,8 @@ pandocBiblioCompiler cslFileName bibFileName = do
112119 csl <- load $ fromFilePath cslFileName
113120 bib <- load $ fromFilePath bibFileName
114121 liftM writePandoc
115− (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
2828instance Binary REF.Literal
2929instance Binary REF.RefDate
3030instance Binary REF.RefType
31+instance Binary REF.Season
3132instance Binary STY.Agent
3233instance Binary STY.Formatted
lib/Hakyll/Web/Tags.hs+2−1
@@ -43,6 +43,7 @@
4343module Hakyll.Web.Tags
4444 ( Tags (..)
4545 , getTags
46+ , getCategory
4647 , buildTagsWith
4748 , buildTags
4849 , buildCategories
@@ -112,7 +113,7 @@ getTags identifier = do
112113
113114
114115--------------------------------------------------------------------------------
115−-- | Obtain categories from a page.
116+-- | Obtain category from a page.
116117getCategory :: MonadMetadata m => Identifier -> m [String]
117118getCategory = return . return . takeBaseName . takeDirectory . toFilePath
118119
lib/Hakyll/Web/Template.hs+24
@@ -138,6 +138,7 @@
138138-- > 3...2...1
139139-- > </p>
140140--
141+{-# LANGUAGE TemplateHaskell #-}
141142module Hakyll.Web.Template
142143 ( Template
143144 , templateBodyCompiler
@@ -146,9 +147,32 @@ module Hakyll.Web.Template
146147 , loadAndApplyTemplate
147148 , applyAsTemplate
148149 , readTemplate
150+ , compileTemplateItem
149151 , unsafeReadTemplateFile
152+ , embedTemplate
150153 ) where
151154
152155
153156--------------------------------------------------------------------------------
154157import 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+
121--------------------------------------------------------------------------------
222{-# LANGUAGE CPP #-}
323{-# LANGUAGE ExistentialQuantification #-}
24+{-# LANGUAGE RankNTypes #-}
425module Hakyll.Web.Template.Context
526 ( ContextField (..)
627 , Context (..)
@@ -28,13 +49,17 @@ module Hakyll.Web.Template.Context
2849 , teaserField
2950 , teaserFieldWithSeparator
3051 , missingField
52+ , bindItem
3153 ) where
3254
3355
3456--------------------------------------------------------------------------------
3557import Control.Applicative (Alternative (..))
3658import 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
3863import Data.Time.Clock (UTCTime (..))
3964import Data.Time.Format (formatTime)
4065import qualified Data.Time.Format as TF
@@ -47,13 +72,16 @@ import Hakyll.Core.Metadata
4772import Hakyll.Core.Provider
4873import Hakyll.Core.Util.String (needlePrefix, splitAll)
4974import Hakyll.Web.Html
50−import System.FilePath (splitDirectories, takeBaseName)
75+import Prelude hiding (id)
76+import System.FilePath (dropExtension, splitDirectories,
77+ takeBaseName)
5178
5279
5380--------------------------------------------------------------------------------
5481-- | Mostly for internal usage
5582data ContextField
56− = StringField String
83+ = EmptyField
84+ | StringField String
5785 | forall a. ListField (Context a) [Item a]
5886
5987
@@ -78,71 +106,123 @@ newtype Context a = Context
78106
79107
80108--------------------------------------------------------------------------------
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
81119instance Monoid (Context a) where
82120 mempty = missingField
83121 mappend (Context f) (Context g) = Context $ \k a i -> f k a i <|> g k a i
122+#endif
84123
85124
86125--------------------------------------------------------------------------------
87126field' :: 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
89131
90132
91133--------------------------------------------------------------------------------
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'.
93143field
94144 :: String -- ^ Key
95145 -> (Item a -> Compiler String) -- ^ Function that constructs a value based
96− -- on the item
146+ -- on the item (e.g. accessing metadata)
97147 -> Context a
98148field key value = field' key (fmap StringField . value)
99149
100150
101151--------------------------------------------------------------------------------
102152-- | Creates a 'field' to use with the @$if()$@ template macro.
153+-- Attempting to substitute the field into the template will cause an error.
103154boolField
104155 :: String
105156 -> (Item a -> Bool)
106157 -> Context a
107−boolField name f = field name (\i -> if f i
108− then pure (error $ unwords ["no string value for bool field:",name])
109− else empty)
158+boolField name f = field' name (\i -> if f i
159+ then return EmptyField
160+ else noResult $ "Field " ++ name ++ " is false")
110161
111162
112163--------------------------------------------------------------------------------
113−-- | Creates a 'field' that does not depend on the 'Item'
114−constField :: String -> String -> Context a
164+-- | Creates a 'field' that does not depend on the 'Item' but always yields
165+-- the same string
166+constField :: String -- ^ Key
167+ -> String -- ^ Value
168+ -> Context a
115169constField key = field key . const . return
116170
117171
118172--------------------------------------------------------------------------------
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.
119176listField :: String -> Context a -> Compiler [Item a] -> Context b
120177listField key c xs = listFieldWith key c (const xs)
121178
122179
123180--------------------------------------------------------------------------------
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.
124187listFieldWith
125188 :: String -> Context a -> (Item b -> Compiler [Item a]) -> Context b
126189listFieldWith key c f = field' key $ fmap (ListField c) . f
127190
128191
129192--------------------------------------------------------------------------------
130−functionField :: String -> ([String] -> Item a -> Compiler String) -> Context a
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
198+ -> ([String] -> Item a -> Compiler String) -- ^ Function
199+ -> Context a
131200functionField name value = Context $ \k args i ->
132201 if k == name
133202 then StringField <$> value args i
134− else empty
203+ else noResult $ "Tried function field " ++ name
135204
136205
137206--------------------------------------------------------------------------------
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+--
138216mapContext :: (String -> String) -> Context a -> Context a
139217mapContext f (Context c) = Context $ \k a i -> do
140218 fld <- c k a i
141219 case fld of
220+ EmptyField -> wrongType "boolField"
142221 StringField str -> return $ StringField (f str)
143− ListField _ _ -> fail $
144− "Hakyll.Web.Template.Context.mapContext: " ++
145− "can't map over a ListField!"
222+ _ -> wrongType "ListField"
223+ where
224+ wrongType typ = fail $ "Hakyll.Web.Template.Context.mapContext: " ++
225+ "can't map over a " ++ typ ++ "!"
146226
147227--------------------------------------------------------------------------------
148228-- | A context that allows snippet inclusion. In processed file, use as:
@@ -151,15 +231,15 @@ mapContext f (Context c) = Context $ \k a i -> do
151231-- > $snippet("path/to/snippet/")$
152232-- > ...
153233--
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.
155236--
156237snippetField :: Context String
157238snippetField = functionField "snippet" f
158239 where
159240 f [contentsPath] _ = loadBody (fromFilePath contentsPath)
160− f _ i = error $
161− "Too many arguments to function 'snippet()' in item " ++
162− show (itemIdentifier i)
241+ f [] _ = fail "No argument to function 'snippet()'"
242+ f _ _ = fail "Too many arguments to function 'snippet()'"
163243
164244--------------------------------------------------------------------------------
165245-- | A context that contains (in that order)
@@ -179,8 +259,7 @@ defaultContext =
179259 metadataField `mappend`
180260 urlField "url" `mappend`
181261 pathField "path" `mappend`
182− titleField "title" `mappend`
183− missingField
262+ titleField "title"
184263
185264
186265--------------------------------------------------------------------------------
@@ -198,15 +277,20 @@ bodyField key = field key $ return . itemBody
198277-- | Map any field to its metadata value, if present
199278metadataField :: Context a
200279metadataField = Context $ \k _ i -> do
201− value <- getMetadataField (itemIdentifier i) k
202− maybe empty (return . StringField) value
280+ let id = itemIdentifier i
281+ empty' = noResult $ "No '" ++ k ++ "' field in metadata " ++
282+ "of item " ++ show id
283+ value <- getMetadataField id k
284+ maybe empty' (return . StringField) value
203285
204286
205287--------------------------------------------------------------------------------
206288-- | Absolute url to the resulting item
207289urlField :: String -> Context a
208−urlField key = field key $
209− fmap (maybe empty toUrl) . getRoute . itemIdentifier
290+urlField key = field key $ \i -> do
291+ let id = itemIdentifier i
292+ empty' = fail $ "No route url found for item " ++ show id
293+ fmap (maybe empty' toUrl) $ getRoute id
210294
211295
212296--------------------------------------------------------------------------------
@@ -257,6 +341,12 @@ titleField = mapContext takeBaseName . pathField
257341-- that begins with @yyyy-mm-dd@ . For example:
258342-- @folder//yyyy-mm-dd-title//dist//main.extension@ .
259343-- 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.
260350
261351dateField :: String -- ^ Key in which the rendered date should be placed
262352 -> String -- ^ Format to use on the date
@@ -267,7 +357,7 @@ dateField = dateFieldWith defaultTimeLocale
267357--------------------------------------------------------------------------------
268358-- | This is an extended version of 'dateField' that allows you to
269359-- specify a time locale that is used for outputting the date. For more
270−-- details, see 'dateField'.
360+-- details, see 'dateField' and 'formatTime'.
271361dateFieldWith :: TimeLocale -- ^ Output time locale
272362 -> String -- ^ Destination key
273363 -> String -- ^ Format to use on the date
@@ -288,12 +378,13 @@ getItemUTC :: MonadMetadata m
288378getItemUTC locale id' = do
289379 metadata <- getMetadata id'
290380 let tryField k fmt = lookupString k metadata >>= parseTime' fmt
291− paths = splitDirectories $ toFilePath id'
381+ paths = splitDirectories $ (dropExtension . toFilePath) id'
292382
293383 maybe empty' return $ msum $
294384 [tryField "published" fmt | fmt <- formats] ++
295385 [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]
297388 where
298389 empty' = fail $ "Hakyll.Web.Template.Context.getItemUTC: " ++
299390 "could not parse time for " ++ show id'
@@ -321,13 +412,16 @@ getItemModificationTime identifier = do
321412
322413
323414--------------------------------------------------------------------------------
415+-- | Creates a field with the last modification date of the underlying item.
324416modificationTimeField :: String -- ^ Key
325417 -> String -- ^ Format
326− -> Context a -- ^ Resuting context
418+ -> Context a -- ^ Resulting context
327419modificationTimeField = modificationTimeFieldWith defaultTimeLocale
328420
329421
330422--------------------------------------------------------------------------------
423+-- | Creates a field with the last modification date of the underlying item
424+-- in a custom localisation format (see 'formatTime').
331425modificationTimeFieldWith :: TimeLocale -- ^ Time output locale
332426 -> String -- ^ Key
333427 -> String -- ^ Format
@@ -366,10 +460,11 @@ teaserFieldWithSeparator separator key snapshot = field key $ \item -> do
366460
367461
368462--------------------------------------------------------------------------------
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.
369465missingField :: Context a
370−missingField = Context $ \k _ i -> fail $
371− "Missing field $" ++ k ++ "$ in context for item " ++
372− show (itemIdentifier i)
466+missingField = Context $ \k _ _ -> noResult $
467+ "Missing field '" ++ k ++ "' in context"
373468
374469parseTimeM :: Bool -> TimeLocale -> String -> String -> Maybe UTCTime
375470#if MIN_VERSION_time(1,5,0)
@@ -377,3 +472,10 @@ parseTimeM = TF.parseTimeM
377472#else
378473parseTimeM _ = TF.parseTime
379474#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−45
@@ -1,15 +1,18 @@
1+{-# LANGUAGE DeriveAnyClass #-}
2+{-# LANGUAGE DeriveGeneric #-}
13{-# LANGUAGE GeneralizedNewtypeDeriving #-}
24{-# LANGUAGE ScopedTypeVariables #-}
5+
36module Hakyll.Web.Template.Internal
47 ( Template (..)
58 , template
69 , templateBodyCompiler
710 , templateCompiler
811 , applyTemplate
9− , applyTemplate'
1012 , loadAndApplyTemplate
1113 , applyAsTemplate
1214 , readTemplate
15+ , compileTemplateItem
1316 , unsafeReadTemplateFile
1417
1518 , module Hakyll.Web.Template.Internal.Element
@@ -18,16 +21,19 @@ module Hakyll.Web.Template.Internal
1821
1922
2023--------------------------------------------------------------------------------
21−import Control.Monad.Except (MonadError (..))
24+import Control.Monad (forM)
25+import Control.Monad.Except (MonadError (..), catchError)
2226import Data.Binary (Binary)
2327import Data.List (intercalate)
28+import qualified Data.List.NonEmpty as NonEmpty
2429import Data.Typeable (Typeable)
2530import GHC.Exts (IsString (..))
26−import Prelude hiding (id)
31+import GHC.Generics (Generic)
2732
2833
2934--------------------------------------------------------------------------------
3035import Hakyll.Core.Compiler
36+import Hakyll.Core.Compiler.Internal
3137import Hakyll.Core.Identifier
3238import Hakyll.Core.Item
3339import Hakyll.Core.Writable
@@ -38,9 +44,10 @@ import Hakyll.Web.Template.Internal.Trim
3844
3945--------------------------------------------------------------------------------
4046-- | Datatype used for template substitutions.
41−newtype Template = Template
42− { unTemplate :: [TemplateElement]
43− } deriving (Show, Eq, Binary, Typeable)
47+data Template = Template
48+ { tplElements :: [TemplateElement]
49+ , tplOrigin :: FilePath -- Only for error messages.
50+ } deriving (Show, Eq, Generic, Binary, Typeable)
4451
4552
4653--------------------------------------------------------------------------------
@@ -56,39 +63,68 @@ instance IsString Template where
5663
5764--------------------------------------------------------------------------------
5865-- | Wrap the constructor to ensure trim is called.
59−template :: [TemplateElement] -> Template
60−template = Template . trim
66+template :: FilePath -> [TemplateElement] -> Template
67+template p = flip Template p . trim
6168
6269
6370--------------------------------------------------------------------------------
71+-- | Parse a string into a template.
72+-- You should prefer 'compileTemplateItem' over this.
6473readTemplate :: 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
6692
6793--------------------------------------------------------------------------------
6894-- | Read a template, without metadata header
6995templateBodyCompiler :: Compiler (Item Template)
7096templateBodyCompiler = cached "Hakyll.Web.Template.templateBodyCompiler" $ do
7197 item <- getResourceBody
72− file <- getResourceFilePath
73− return $ fmap (template . readTemplateElemsFile file) item
98+ file <- getUnderlying
99+ withItemBody (compileTemplateFile file) item
74100
75101--------------------------------------------------------------------------------
76102-- | Read complete file contents as a template
77103templateCompiler :: Compiler (Item Template)
78104templateCompiler = cached "Hakyll.Web.Template.templateCompiler" $ do
79105 item <- getResourceString
80− file <- getResourceFilePath
81− return $ fmap (template . readTemplateElemsFile file) item
106+ file <- getUnderlying
107+ withItemBody (compileTemplateFile file) item
82108
83109
84110--------------------------------------------------------------------------------
111+-- | Interpolate template expressions from context values in a page
85112applyTemplate :: Template -- ^ Template
86113 -> Context a -- ^ Context
87114 -> Item a -- ^ Page
88115 -> Compiler (Item String) -- ^ Resulting item
89116applyTemplate tpl context item = do
90− body <- applyTemplate' (unTemplate tpl) context item
117+ body <- applyTemplate' (tplElements tpl) context item `catchError` handler
91118 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+
92128
93129
94130--------------------------------------------------------------------------------
@@ -105,9 +141,6 @@ applyTemplate' tes context x = go tes
105141
106142 go = fmap concat . mapM applyElem
107143
108− trimError = error $ "Hakyll.Web.Template.applyTemplate: template not " ++
109− "fully trimmed."
110−
111144 ---------------------------------------------------------------------------
112145
113146 applyElem :: TemplateElement -> Compiler String
@@ -118,29 +151,43 @@ applyTemplate' tes context x = go tes
118151
119152 applyElem (Chunk c) = return c
120153
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 ++ "$'"
122158
123159 applyElem Escaped = return "$"
124160
125− applyElem (If e t mf) = (applyExpr e >> go t) `catchError` handler
161+ applyElem (If e t mf) = compilerTry (applyExpr e) >>= handle
126162 where
127− handler _ = case mf of
128− Nothing -> return ""
129− Just f -> go f
130−
131− applyElem (For e b s) = applyExpr e >>= \cf -> case cf of
132− StringField _ -> fail $
133− "Hakyll.Web.Template.applyTemplateWith: expected ListField but " ++
134− "got StringField for expr " ++ show e
135− ListField c xs -> do
163+ f = maybe (return "") go mf
164+ handle (Right _) = go t
165+ handle (Left (CompilationNoResult _)) = f
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+
170+ applyElem (For e b s) = withErrorMessage headMsg (applyExpr e) >>= \cf -> case cf of
171+ EmptyField -> expected "list" "boolean" typeMsg
172+ StringField _ -> expected "list" "string" typeMsg
173+ ListField c xs -> withErrorMessage bodyMsg $ do
136174 sep <- maybe (return "") go s
137− bs <- mapM (applyTemplate' b c) xs
175+ bs <- forM xs $ applyTemplate' b $ c <> bindItem context x
138176 return $ intercalate sep bs
139−
140− applyElem (Partial e) = do
141− p <- applyExpr e >>= getString e
142− Template tpl' <- loadBody (fromFilePath p)
143− applyTemplate' tpl' context x
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+
182+ applyElem (Partial e) = withErrorMessage headMsg $
183+ applyStringExpr typeMsg e >>= \p ->
184+ withErrorMessage inclMsg $ do
185+ tpl' <- loadBody (fromFilePath p)
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 ++ ")$'"
144191
145192 ---------------------------------------------------------------------------
146193
@@ -149,17 +196,29 @@ applyTemplate' tes context x = go tes
149196 applyExpr (Ident (TemplateKey k)) = context' k [] x
150197
151198 applyExpr (Call (TemplateKey k) args) = do
152− args' <- mapM (\e -> applyExpr e >>= getString e) args
199+ args' <- mapM (\e -> applyStringExpr (typeMsg e) e) args
153200 context' k args' x
201+ where
202+ typeMsg e = "argument '" ++ show e ++ "'"
154203
155204 applyExpr (StringLiteral s) = return (StringField s)
156205
157206 ----------------------------------------------------------------------------
158207
159− getString _ (StringField s) = return s
160− getString e (ListField _ _) = fail $
161− "Hakyll.Web.Template.applyTemplateWith: expected StringField but " ++
162− "got ListField for expr " ++ show e
208+ applyStringExpr :: String -> TemplateExpr -> Compiler String
209+ applyStringExpr msg expr =
210+ applyExpr expr >>= getString
211+ where
212+ getString EmptyField = expected "string" "boolean" msg
213+ getString (StringField s) = return s
214+ getString (ListField _ _) = expected "string" "list" msg
215+
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."
163222
164223
165224--------------------------------------------------------------------------------
@@ -189,15 +248,14 @@ loadAndApplyTemplate identifier context item = do
189248applyAsTemplate :: Context String -- ^ Context
190249 -> Item String -- ^ Item and template
191250 -> Compiler (Item String) -- ^ Resulting item
192−applyAsTemplate context item =
193− let tpl = template $ readTemplateElemsFile file (itemBody item)
194− file = toFilePath $ itemIdentifier item
195− in applyTemplate tpl context item
251+applyAsTemplate context item = do
252+ tpl <- compileTemplateItem item
253+ applyTemplate tpl context item
196254
197255
198256--------------------------------------------------------------------------------
199257unsafeReadTemplateFile :: FilePath -> Compiler Template
200258unsafeReadTemplateFile file = do
201259 tpl <- unsafeCompiler $ readFile file
202− pure $ template $ readTemplateElemsFile file tpl
203−
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
77 , TemplateExpr (..)
88 , TemplateElement (..)
99 , templateElems
10− , readTemplateElems
11− , readTemplateElemsFile
10+ , parseTemplateElemsFile
1211 ) where
1312
1413
1514--------------------------------------------------------------------------------
16−import Control.Applicative ((<|>))
15+import Control.Applicative ((<|>), (<*))
1716import Control.Monad (void)
17+import Control.Arrow (left)
1818import Data.Binary (Binary, get, getWord8, put, putWord8)
1919import Data.List (intercalate)
2020import Data.Maybe (isJust)
@@ -107,17 +107,10 @@ instance Binary TemplateExpr where
107107 2 -> StringLiteral <$> get
108108 _ -> error "Hakyll.Web.Template.Internal: Error reading cached template"
109109
110−
111−--------------------------------------------------------------------------------
112−readTemplateElems :: String -> [TemplateElement]
113−readTemplateElems = readTemplateElemsFile "{literal}"
114−
115−
116110--------------------------------------------------------------------------------
117−readTemplateElemsFile :: FilePath -> String -> [TemplateElement]
118−readTemplateElemsFile file input = case P.parse templateElems file input of
119− Left err -> error $ "Cannot parse template: " ++ show err
120− Right t -> t
111+parseTemplateElemsFile :: FilePath -> String -> Either String [TemplateElement]
112+parseTemplateElemsFile file = left (\e -> "Cannot parse template " ++ show e)
113+ . P.parse (templateElems <* P.eof) file
121114
122115
123116--------------------------------------------------------------------------------
@@ -167,7 +160,7 @@ trimOpen = do
167160--------------------------------------------------------------------------------
168161trimClose :: P.Parser Bool
169162trimClose = do
170− trimIfR <- P.optionMaybe $ P.try (P.char '-')
163+ trimIfR <- P.optionMaybe $ (P.char '-')
171164 void $ P.char '$'
172165 pure $ isJust trimIfR
173166
src/Init.hs+7−4
@@ -6,7 +6,7 @@ module Main
66
77--------------------------------------------------------------------------------
88import Control.Arrow (first)
9−import Control.Monad (forM_)
9+import Control.Monad (forM, forM_)
1010import Data.Char (isAlphaNum, isNumber)
1111import Data.List (foldl', intercalate, isPrefixOf)
1212import Data.Version (Version (..))
@@ -22,6 +22,10 @@ import Hakyll.Core.Util.File
2222import Paths_hakyll
2323
2424
25+--------------------------------------------------------------------------------
26+import Prelude
27+
28+
2529--------------------------------------------------------------------------------
2630main :: IO ()
2731main = do
@@ -66,17 +70,16 @@ main = do
6670 createCabal cabalPath name
6771 fs -> do
6872 putStrLn $ "The following files will be overwritten:"
69− foldMap putStrLn fs
73+ mapM_ putStrLn fs
7074 putStrLn $ "Use -f to overwrite them"
7175 exitFailure
7276
7377existingFiles :: FilePath -> [FilePath] -> IO [FilePath]
74−existingFiles dstDir = foldMap $ \file -> do
78+existingFiles dstDir files = fmap concat $ forM files $ \file -> do
7579 let dst = dstDir </> file
7680 exists <- doesFileExist dst
7781 return $ if exists then [dst] else []
7882
79−
8083-- | Figure out a good cabal package name from the given (existing) directory
8184-- name
8285makeName :: FilePath -> IO String
stack.yaml+22−11
@@ -1,22 +1,33 @@
1−resolver: nightly-2017-12-14
2−extra-package-dbs: []
1+resolver: lts-13.29
2+save-hackage-creds: false
33
44flags:
55 hakyll:
6− previewServer: True
7− watchServer: True
8− checkExternal: True
9− buildWebsite: True
6+ previewServer: true
7+ watchServer: true
8+ checkExternal: true
9+ usePandoc: true
10+ buildWebsite: true
1011
1112packages:
1213 - '.'
1314
14−extra-deps:
15− - hslua-module-text-0.1.2
16− - skylighting-0.5
17− - pandoc-citeproc-0.12.1.1
18−
1915nix:
2016 enable: false
2117 packages:
2218 - 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/Provider/Tests.hs+2−2
@@ -9,7 +9,7 @@ module Hakyll.Core.Provider.Tests
99import Hakyll.Core.Metadata
1010import Hakyll.Core.Provider
1111import Test.Tasty (TestTree, testGroup)
12−import Test.Tasty.HUnit (Assertion, assert, testCase, (@=?))
12+import Test.Tasty.HUnit (Assertion, testCase, (@=?))
1313import TestSuite.Util
1414
1515
@@ -25,7 +25,7 @@ case01 :: Assertion
2525case01 = do
2626 store <- newTestStore
2727 provider <- newTestProvider store
28− assert $ resourceExists provider "example.md"
28+ True @=? resourceExists provider "example.md"
2929
3030 metadata <- resourceMetadata provider "example.md"
3131 Just "An example" @=? lookupString "title" metadata
tests/Hakyll/Core/Rules/Tests.hs+3−4
@@ -17,10 +17,9 @@ import Hakyll.Core.Metadata
1717import Hakyll.Core.Routes
1818import Hakyll.Core.Rules
1919import Hakyll.Core.Rules.Internal
20−import Hakyll.Web.Pandoc
2120import System.FilePath ((</>))
2221import Test.Tasty (TestTree, testGroup)
23−import Test.Tasty.HUnit (Assertion, assert, (@=?))
22+import Test.Tasty.HUnit (Assertion, (@=?))
2423import TestSuite.Util
2524
2625
@@ -50,7 +49,7 @@ case01 = do
5049 checkRoute "example.mv1" (sv "mv1" "example.md")
5150 checkRoute "example.mv2" (sv "mv2" "example.md")
5251 checkRoute "food/example.md" (sv "metadataMatch" "example.md")
53− readIORef ioref >>= assert
52+ readIORef ioref >>= (True @=?)
5453 cleanTestEnv
5554 where
5655 sv g = setVersion (Just g)
@@ -75,7 +74,7 @@ rules01 ioref = do
7574 -- Compile some posts
7675 match "*.md" $ do
7776 route $ setExtension "html"
78− compile pandocCompiler
77+ compile copyFileCompiler
7978
8079 -- Yeah. I don't know how else to test this stuff?
8180 preprocess $ writeIORef ioref True
tests/Hakyll/Core/Runtime/Tests.hs+1−1
@@ -39,7 +39,7 @@ case01 = do
3939 compile $ do
4040 getResourceBody
4141 >>= saveSnapshot "raw"
42− >>= renderPandoc
42+ >>= renderParagraphs
4343
4444 match (fromList ["partial.html", "partial-helper.html"]) $
4545 compile templateCompiler
tests/Hakyll/Core/Store/Tests.hs+7−7
@@ -63,11 +63,11 @@ wrongType = do
6363 -- Store a string and try to fetch an int
6464 Store.set store ["foo", "bar"] ("qux" :: String)
6565 value <- Store.get store ["foo", "bar"] :: IO (Store.Result Int)
66− H.assert $ case value of
67− Store.WrongType e t ->
68− e == typeOf (undefined :: Int) &&
69− t == typeOf (undefined :: String)
70− _ -> False
66+ case value of
67+ Store.WrongType e t -> do
68+ typeOf (undefined :: Int) H.@=? e
69+ typeOf (undefined :: String) H.@=? t
70+ _ -> H.assertFailure "Expecting WrongType"
7171 cleanTestEnv
7272
7373
@@ -78,6 +78,6 @@ isMembertest = do
7878 Store.set store ["foo", "bar"] ("qux" :: String)
7979 good <- Store.isMember store ["foo", "bar"]
8080 bad <- Store.isMember store ["foo", "baz"]
81− H.assert good
82− H.assert (not bad)
81+ True H.@=? good
82+ False H.@=? bad
8383 cleanTestEnv
tests/Hakyll/Core/UnixFilter/Tests.hs+8−16
@@ -6,18 +6,16 @@ module Hakyll.Core.UnixFilter.Tests
66
77
88--------------------------------------------------------------------------------
9−import Data.List (isInfixOf)
10−import Test.Tasty (TestTree, testGroup)
11−import Test.Tasty.HUnit (testCase)
12−import qualified Test.Tasty.HUnit as H
9+import Test.Tasty (TestTree, testGroup)
10+import Test.Tasty.HUnit (testCase)
11+import qualified Test.Tasty.HUnit as H
1312
1413
1514--------------------------------------------------------------------------------
1615import Hakyll.Core.Compiler
17−import Hakyll.Core.Compiler.Internal
16+import Hakyll.Core.Identifier
1817import Hakyll.Core.Item
1918import Hakyll.Core.UnixFilter
20−import Hakyll.Core.Identifier
2119import TestSuite.Util
2220
2321
@@ -39,7 +37,7 @@ unixFilterRev = do
3937 provider <- newTestProvider store
4038 output <- testCompilerDone store provider testMarkdown compiler
4139 expected <- testCompilerDone store provider testMarkdown getResourceString
42− H.assert $ rev (itemBody expected) == lines (itemBody output)
40+ rev (itemBody expected) H.@=? lines (itemBody output)
4341 cleanTestEnv
4442 where
4543 compiler = getResourceString >>= withItemBody (unixFilter "rev" [])
@@ -51,10 +49,7 @@ unixFilterFalse :: H.Assertion
5149unixFilterFalse = do
5250 store <- newTestStore
5351 provider <- newTestProvider store
54− result <- testCompiler store provider testMarkdown compiler
55− H.assert $ case result of
56− CompilerError es -> any ("exit code" `isInfixOf`) es
57− _ -> False
52+ testCompilerError store provider testMarkdown compiler "exit code"
5853 cleanTestEnv
5954 where
6055 compiler = getResourceString >>= withItemBody (unixFilter "false" [])
@@ -65,10 +60,7 @@ unixFilterError :: H.Assertion
6560unixFilterError = do
6661 store <- newTestStore
6762 provider <- newTestProvider store
68− result <- testCompiler store provider testMarkdown compiler
69− H.assert $ case result of
70− CompilerError es -> any ("invalid option" `isInfixOf`) es
71− _ -> False
63+ testCompilerError store provider testMarkdown compiler "option"
7264 cleanTestEnv
7365 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
2020 [ fromAssertions "compressCss"
2121 [
2222 -- compress whitespace
23− " something something " @=?
23+ "something something" @=?
2424 compressCss " something \n\t\r something "
25− -- do not compress whitespace in constants
25+ -- do not compress whitespace in string tokens
2626 , "abc \" \t\n\r \" xyz" @=?
2727 compressCss "abc \" \t\n\r \" xyz"
2828 , "abc ' \t\n\r ' xyz" @=?
@@ -30,7 +30,7 @@ tests = testGroup "Hakyll.Web.CompressCss.Tests" $ concat
3030
3131 -- strip comments
3232 , "before after" @=? compressCss "before /* abc { } ;; \n\t\r */ after"
33− -- don't strip comments inside constants
33+ -- don't strip comments inside string tokens
3434 , "before \"/* abc { } ;; \n\t\r */\" after"
3535 @=? compressCss "before \"/* abc { } ;; \n\t\r */\" after"
3636
@@ -45,19 +45,19 @@ tests = testGroup "Hakyll.Web.CompressCss.Tests" $ concat
4545 , "calc(1px + 100%/(5 + 3) - (3px + 2px)*5)" @=? compressCss "calc( 1px + 100% / ( 5 + 3) - calc( 3px + 2px ) * 5 )"
4646 -- compress whitespace even after this curly brace
4747 , "}" @=? compressCss "; } "
48− -- but do not compress separators inside of constants
48+ -- but do not compress separators inside string tokens
4949 , "\" { } ; , \"" @=? 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
5151 , "\" }\"" @=? compressCss "\" }\""
5252 , "\"{ \"" @=? compressCss "\"{ \""
53− -- don't get irritated by the wrong constant terminator
53+ -- don't get irritated by the wrong token delimiter
5454 , "\" ' \"" @=? compressCss "\" ' \""
5555 , "' \" '" @=? 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
5757 , "abc '{ '" @=? compressCss "abc '{ '"
5858 , "abc \"{ \"" @=? compressCss "abc \"{ \""
59− -- compress whitespace around colons
60− , "abc:xyz" @=? compressCss "abc : xyz"
59+ -- compress whitespace after colons (but not before)
60+ , "abc :xyz" @=? compressCss "abc : xyz"
6161 -- compress multiple semicolons
6262 , ";" @=? compressCss ";;;;;;;"
6363 ]
tests/Hakyll/Web/Html/Tests.hs+6−6
@@ -7,7 +7,7 @@ module Hakyll.Web.Html.Tests
77--------------------------------------------------------------------------------
88import Data.Char (toUpper)
99import Test.Tasty (TestTree, testGroup)
10−import Test.Tasty.HUnit (assert, (@=?))
10+import Test.Tasty.HUnit ((@=?))
1111
1212
1313--------------------------------------------------------------------------------
@@ -64,11 +64,11 @@ tests = testGroup "Hakyll.Web.Html.Tests" $ concat
6464 ]
6565
6666 , fromAssertions "isExternal"
67− [ assert (isExternal "http://reddit.com")
68− , assert (isExternal "https://mail.google.com")
69− , assert (isExternal "//ajax.googleapis.com")
70− , assert (not (isExternal "../header.png"))
71− , assert (not (isExternal "/foo/index.html"))
67+ [ True @=? isExternal "http://reddit.com"
68+ , True @=? isExternal "https://mail.google.com"
69+ , True @=? isExternal "//ajax.googleapis.com"
70+ , False @=? isExternal "../header.png"
71+ , False @=? isExternal "/foo/index.html"
7272 ]
7373
7474 , fromAssertions "stripTags"
tests/Hakyll/Web/Template/Context/Tests.hs+40−3
@@ -13,16 +13,19 @@ import Test.Tasty.HUnit (Assertion, testCase, (@=?))
1313--------------------------------------------------------------------------------
1414import Hakyll.Core.Compiler
1515import Hakyll.Core.Identifier
16+import Hakyll.Core.Item
1617import Hakyll.Core.Provider
1718import Hakyll.Core.Store (Store)
1819import Hakyll.Web.Template.Context
20+import Hakyll.Web.Template.Internal
1921import TestSuite.Util
2022
2123
2224--------------------------------------------------------------------------------
2325tests :: TestTree
24−tests = testGroup "Hakyll.Core.Template.Context.Tests"
26+tests = testGroup "Hakyll.Web.Template.Context.Tests"
2527 [ testCase "testDateField" testDateField
28+ , testCase "testOuerLoopContextAccess" testOuerLoopContextAccess
2629 ]
2730
2831
@@ -41,6 +44,15 @@ testDateField = do
4144 dateField "date" "%B %e, %Y"
4245 date2 @=? "August 26, 2010"
4346
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"
4456 cleanTestEnv
4557
4658
@@ -53,6 +65,31 @@ testContextDone store provider identifier key context =
5365 cf <- unContext context key [] item
5466 case cf of
5567 StringField str -> return str
56− ListField _ _ -> error $
68+ _ -> error $
5769 "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 @@
11--------------------------------------------------------------------------------
22{-# LANGUAGE OverloadedStrings #-}
3+{-# LANGUAGE TemplateHaskell #-}
34module Hakyll.Web.Template.Tests
45 ( tests
56 ) where
@@ -7,16 +8,16 @@ module Hakyll.Web.Template.Tests
78
89--------------------------------------------------------------------------------
910import Test.Tasty (TestTree, testGroup)
10−import Test.Tasty.HUnit (Assertion, testCase, (@=?),
11− (@?=))
11+import Test.Tasty.HUnit (Assertion, assertBool, testCase,
12+ (@=?), (@?=))
1213
14+import Data.Either (isLeft)
1315
1416--------------------------------------------------------------------------------
1517import Hakyll.Core.Compiler
1618import Hakyll.Core.Identifier
1719import Hakyll.Core.Item
1820import Hakyll.Core.Provider
19−import Hakyll.Web.Pandoc
2021import Hakyll.Web.Template
2122import Hakyll.Web.Template.Context
2223import Hakyll.Web.Template.Internal
@@ -26,20 +27,20 @@ import TestSuite.Util
2627
2728--------------------------------------------------------------------------------
2829tests :: TestTree
29−tests = testGroup "Hakyll.Core.Template.Tests" $ concat
30+tests = testGroup "Hakyll.Web.Template.Tests" $ concat
3031 [ [ testCase "case01" $ test ("template.html.out", "template.html", "example.md")
3132 , testCase "case02" $ test ("strip.html.out", "strip.html", "example.md")
3233 , testCase "case03" $ test ("just-meta.html.out", "just-meta.html", "example.md")
3334 , testCase "applyJoinTemplateList" testApplyJoinTemplateList
3435 ]
3536
36− , fromAssertions "readTemplate"
37− [ [Chunk "Hello ", Expr (Call "guest" [])]
38− @=? readTemplateElems "Hello $guest()$"
39− , [If (Call "a" [StringLiteral "bar"]) [Chunk "foo"] Nothing]
40− @=? readTemplateElems "$if(a(\"bar\"))$foo$endif$"
37+ , fromAssertions "parseTemplate"
38+ [ Right [Chunk "Hello ", Expr (Call "guest" [])]
39+ @=? parse "Hello $guest()$"
40+ , Right [If (Call "a" [StringLiteral "bar"]) [Chunk "foo"] Nothing]
41+ @=? parse "$if(a(\"bar\"))$foo$endif$"
4142 -- 'If' trim check.
42− , [ TrimL
43+ , Right [ TrimL
4344 , If (Ident (TemplateKey "body"))
4445 [ TrimR
4546 , Chunk "\n"
@@ -55,29 +56,39 @@ tests = testGroup "Hakyll.Core.Template.Tests" $ concat
5556 ])
5657 , TrimR
5758 ]
58− @=? readTemplateElems "$-if(body)-$\n$body$\n$-else-$\n$body$\n$-endif-$"
59+ @=? parse "$-if(body)-$\n$body$\n$-else-$\n$body$\n$-endif-$"
5960 -- 'For' trim check.
60− , [ TrimL
61+ , Right [ TrimL
6162 , For (Ident (TemplateKey "authors"))
6263 [TrimR, Chunk "\n body \n", TrimL]
6364 Nothing
6465 , TrimR
6566 ]
66− @=? readTemplateElems "$-for(authors)-$\n body \n$-endfor-$"
67+ @=? parse "$-for(authors)-$\n body \n$-endfor-$"
6768 -- 'Partial' trim check.
68− , [ TrimL
69+ , Right [ TrimL
6970 , Partial (StringLiteral "path")
7071 , TrimR
7172 ]
72− @=? readTemplateElems "$-partial(\"path\")-$"
73+ @=? parse "$-partial(\"path\")-$"
7374 -- 'Expr' trim check.
74− , [ TrimL
75+ , Right [ TrimL
7576 , Expr (Ident (TemplateKey "foo"))
7677 , TrimR
7778 ]
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$"
7986 ]
87+
88+ , [testCase "embeddedTemplate" testEmbeddedTemplate]
8089 ]
90+ where
91+ parse = parseTemplateElemsFile ""
8192
8293
8394--------------------------------------------------------------------------------
@@ -89,7 +100,9 @@ test (outf, tplf, itemf) = do
89100 out <- resourceString provider outf
90101 tpl <- testCompilerDone store provider tplf templateBodyCompiler
91102 item <- testCompilerDone store provider itemf $
92− pandocCompiler >>= applyTemplate (itemBody tpl) testContext
103+ getResourceBody
104+ >>= renderParagraphs
105+ >>= applyTemplate (itemBody tpl) testContext
93106
94107 out @=? itemBody item
95108 cleanTestEnv
@@ -112,6 +125,8 @@ testApplyJoinTemplateList :: Assertion
112125testApplyJoinTemplateList = do
113126 store <- newTestStore
114127 provider <- newTestProvider store
128+ tpl <- testCompilerDone store provider "tpl" $
129+ compileTemplateItem (Item "tpl" "<b>$body$</b>")
115130 str <- testCompilerDone store provider "item3" $
116131 applyJoinTemplateList ", " tpl defaultContext [i1, i2]
117132
@@ -120,4 +135,22 @@ testApplyJoinTemplateList = do
120135 where
121136 i1 = Item "item1" "Hello"
122137 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 @@
11--------------------------------------------------------------------------------
2+{-# LANGUAGE CPP #-}
23module Main
34 ( main
45 ) where
@@ -22,9 +23,12 @@ import qualified Hakyll.Core.Util.String.Tests
2223import qualified Hakyll.Web.CompressCss.Tests
2324import qualified Hakyll.Web.Html.RelativizeUrls.Tests
2425import qualified Hakyll.Web.Html.Tests
26+#ifdef USE_PANDOC
2527import qualified Hakyll.Web.Pandoc.FileType.Tests
28+#endif
2629import qualified Hakyll.Web.Template.Context.Tests
2730import qualified Hakyll.Web.Template.Tests
31+import qualified Hakyll.Web.Tags.Tests
2832
2933
3034--------------------------------------------------------------------------------
@@ -43,7 +47,10 @@ main = defaultMain $ testGroup "Hakyll"
4347 , Hakyll.Web.CompressCss.Tests.tests
4448 , Hakyll.Web.Html.RelativizeUrls.Tests.tests
4549 , Hakyll.Web.Html.Tests.tests
50+#ifdef USE_PANDOC
4651 , Hakyll.Web.Pandoc.FileType.Tests.tests
52+#endif
53+ , Hakyll.Web.Tags.Tests.tests
4754 , Hakyll.Web.Template.Context.Tests.tests
4855 , Hakyll.Web.Template.Tests.tests
4956 ]
tests/TestSuite/Util.hs+23−2
@@ -6,13 +6,15 @@ module TestSuite.Util
66 , newTestProvider
77 , testCompiler
88 , testCompilerDone
9+ , testCompilerError
910 , testConfiguration
1011 , cleanTestEnv
12+ , renderParagraphs
1113 ) where
1214
1315
1416--------------------------------------------------------------------------------
15−import Data.List (intercalate)
17+import Data.List (intercalate, isInfixOf)
1618import Data.Monoid (mempty)
1719import qualified Data.Set as S
1820import Test.Tasty
@@ -29,6 +31,7 @@ import Hakyll.Core.Provider
2931import Hakyll.Core.Store (Store)
3032import qualified Hakyll.Core.Store as Store
3133import Hakyll.Core.Util.File
34+import Hakyll.Core.Item
3235
3336
3437--------------------------------------------------------------------------------
@@ -78,13 +81,21 @@ testCompilerDone store provider underlying compiler = do
7881 CompilerDone x _ -> return x
7982 CompilerError e -> fail $
8083 "TestSuite.Util.testCompilerDone: compiler " ++ show underlying ++
81− " threw: " ++ intercalate "; " e
84+ " threw: " ++ intercalate "; " (compilerErrorMessages e)
8285 CompilerRequire i _ -> fail $
8386 "TestSuite.Util.testCompilerDone: compiler " ++ show underlying ++
8487 " requires: " ++ show i
8588 CompilerSnapshot _ _ -> fail
8689 "TestSuite.Util.testCompilerDone: unexpected CompilerSnapshot"
8790
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"
8899
89100--------------------------------------------------------------------------------
90101testConfiguration :: Configuration
@@ -102,3 +113,13 @@ cleanTestEnv = do
102113 removeDirectory $ destinationDirectory testConfiguration
103114 removeDirectory $ storeDirectory testConfiguration
104115 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. */
2−table.sourceCode, tr.sourceCode, td.lineNumbers, td.sourceCode, table.sourceCode pre
3− { margin: 0; padding: 0; border: 0; vertical-align: baseline; border: none; }
4−td.lineNumbers { border-right: 1px solid #AAAAAA; text-align: right; color: #AAAAAA; padding-right: 5px; padding-left: 5px; }
5−td.sourceCode { padding-left: 5px; }
6−.sourceCode span.kw { color: #007020; font-weight: bold; }
7−.sourceCode span.dt { color: #902000; }
8−.sourceCode span.dv { color: #40a070; }
9−.sourceCode span.bn { color: #40a070; }
10−.sourceCode span.fl { color: #40a070; }
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; }
17−.sourceCode span.re { }
18−.sourceCode span.er { color: red; font-weight: bold; }
1+a.sourceLine { display: inline-block; line-height: 1.25; }
2+a.sourceLine { pointer-events: none; color: inherit; text-decoration: inherit; }
3+a.sourceLine:empty { height: 1.2em; }
4+.sourceCode { overflow: visible; }
5+code.sourceCode { white-space: pre; position: relative; }
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 */
44+code span.dt { color: #902000; } /* DataType */
45+code span.dv { color: #40a070; } /* DecVal */
46+code span.er { color: #ff0000; font-weight: bold; } /* Error */
47+code span.ex { } /* Extension */
48+code span.fl { color: #40a070; } /* Float */
49+code span.fu { color: #06287e; } /* Function */
50+code span.im { } /* Import */
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.
4545- <http://www.regisfoucault.com/>,
4646 [source](https://github.com/regisfoucault/blog)
4747- <http://www.chaoxuprime.com/>,
48− [source](https://github.com/Mgccl/blog)
48+ [source](https://github.com/chaoxu/chaoxu.github.io/tree/develop)
4949- <http://www.skybluetrades.net/>,
5050 [source](https://github.com/ian-ross/blog)
5151- <http://chromaticleaves.com/>,
@@ -68,16 +68,10 @@ directly with the default Hakyll site.
6868 [source](https://github.com/walkie/WebPage)
6969- <http://techblog.rosedu.org/>,
7070 [source](https://github.com/rosedu/techblog)
71−- <http://abizern.org>,
72− [source](https://github.com/Abizern/hblog)
7371- <http://freizl.github.io>,
7472 [source](https://github.com/freizl/freizl.github.com/tree/master/build)
75−- <http://covariant.me>,
76− [source](http://hub.darcs.net/co-dan/website)
7773- <http://www.gwern.net/>,
7874 [source](https://github.com/gwern/gwern.net)
79−- <http://www.rohanjain.in/>,
80− [source](https://github.com/crodjer/rohanjain.in)
8175- <http://www.corentindupont.info/>,
8276 [source](https://github.com/cdupont/CorentinDupont-WebPage)
8377- <http://jelv.is>,
@@ -86,8 +80,8 @@ directly with the default Hakyll site.
8680 [source](https://github.com/plaimi/www)
8781- <http://duplode.github.io/>,
8882 [source](https://github.com/duplode/duplode.github.io/tree/sources)
89−- <http://maseek.codes>,
90− [source](https://github.com/maseek/maseek-codes)
83+- <http://tzemanovic.github.io/>,
84+ [source](https://github.com/tzemanovic/tzemanovic)
9185- <http://listx.github.io>,
9286 [source](https://github.com/listx/listx_blog)
9387- <http://kyle.marek-spartz.org>,
@@ -98,26 +92,20 @@ directly with the default Hakyll site.
9892 [source](https://github.com/rbros/reichertbrothers.com)
9993- <http://alemedeiros.sdf.org>,
10094 [source](https://github.com/alemedeiros/homepage)
101−- <https://ruudvanasseldonk.com/>,
102− [source](https://github.com/ruud-v-a/ruudvanasseldonk.com)
10395- <http://www.haskell.mn>,
10496 [source](https://github.com/HaskellMN/www.haskell.mn)
10597- <https://axiomatic.neophilus.net/>,
10698 [source](https://github.com/Libbum/AxiomaticSemantics)
107−- <https://tesser.org/>,
99+- [https://tesser.org/](http://web.archive.org/web/20161022221713/https://tesser.org/),
108100 [source](https://github.com/fractalcat/tesser.org)
109−- <https://wei2912.github.io/>,
110− [source](https://github.com/wei2912/blog_src)
101+- <https://weien.io/>,
102+  [source](https://github.com/wei2912/blog-src)
111103- <http://www.metasepi.org/>,
112104 [source](https://github.com/metasepi/metasepi-website)
113105- <http://gisli.hamstur.is/>,
114106 [source](https://github.com/gislik/gisli.hamstur.is)
115107- <http://blog.ssanj.net/>,
116108 [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)
121109- <http://ruhaskell.org/>,
122110 [source](https://github.com/ruHaskell/ruhaskell)
123111- <http://www.haskell.od.ua/>,
@@ -138,8 +126,6 @@ directly with the default Hakyll site.
138126 [source](https://github.com/ismailmustafa/ismailmustafa.github.io/tree/hakyll)
139127- <http://lettier.github.io/>,
140128 [source](https://github.com/lettier/lettier.github.io)
141−- <http://jozefg.bitbucket.org/>,
142− [source](https://github.com/jozefg/blog)
143129- <http://raindev.io>,
144130 [source](https://github.com/raindev/raindev.github.io)
145131- <http://hal2016.haskell.org/>,
@@ -168,8 +154,6 @@ directly with the default Hakyll site.
168154 [source](https://github.com/beerendlauwers/HaskAnything)
169155- <https://anler.me/>,
170156 [source](https://github.com/anler/anler.me)
171−- <https://www.brandonstil.es/>,
172− [source](https://github.com/stilesb/brandonstil.es)
173157- <http://argumatronic.com/>,
174158 [source](https://gitlab.com/GinBaby/argumatronic/tree/master)
175159- <http://sillybytes.net/>,
@@ -180,6 +164,26 @@ directly with the default Hakyll site.
180164 [source](https://github.com/thunky-monk/spyhop-cafe)
181165- <https://bendo.github.io/>,
182166 [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)
183187
184188## Hakyll 3.X
185189
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).
1818 a conversion tool from other blog engines to Hakyll.
1919- [hakyll-elm](https://hackage.haskell.org/package/hakyll-elm),
2020 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 @@
11--------------------------------------------------------------------------------
22{-# LANGUAGE OverloadedStrings #-}
33import Control.Arrow (second)
4−import Control.Monad (forM_)
5−import Data.Char (isDigit)
4+import Control.Monad (filterM, forM_)
65import Data.List (isPrefixOf, sortBy)
76import Data.Monoid ((<>))
87import Data.Ord (comparing)
@@ -57,17 +56,11 @@ main = hakyllWith config $ do
5756 create ["tutorials.html"] $ do
5857 route idRoute
5958 compile $ do
60− tuts <-
59+ ctx <- tutorialsCtx <$>
6160 sortBy (comparing itemIdentifier) <$> loadAll "tutorials/*"
62−
63− let tutorialsCtx =
64− constField "title" "Tutorials" `mappend`
65− listField "tutorials" tutorialCtx (return tuts) `mappend`
66− defaultContext
67−
6861 makeItem ""
69− >>= loadAndApplyTemplate "templates/tutorials.html" tutorialsCtx
70− >>= loadAndApplyTemplate "templates/default.html" tutorialsCtx
62+ >>= loadAndApplyTemplate "templates/tutorials.html" ctx
63+ >>= loadAndApplyTemplate "templates/default.html" ctx
7164 >>= relativizeUrls
7265
7366 -- Templates
@@ -108,28 +101,17 @@ hackage url
108101 reverse $ splitPath url
109102
110103
111−--------------------------------------------------------------------------------
112−data TutorialType = SeriesTutorial | ArticlesTutorial | ExternalTutorial
113− deriving (Eq)
114−
115−
116104--------------------------------------------------------------------------------
117105-- | Partition tutorials into tutorial series, other articles, external articles
118−tutorialCtx :: Context String
119−tutorialCtx =
120− field "isSeries" (isTutorialType SeriesTutorial) <>
121− field "isArticle" (isTutorialType ArticlesTutorial) <>
122− field "isExternal" (isTutorialType ExternalTutorial) <>
106+tutorialsCtx :: [Item String] -> Context String
107+tutorialsCtx tuts =
108+ constField "title" "Tutorials" <>
109+ listField "main" defaultContext (ofType "main") <>
110+ listField "articles" defaultContext (ofType "article") <>
111+ listField "externals" defaultContext (ofType "external") <>
112+ listField "robertwpearce" defaultContext (ofType "robertwpearce") <>
123113 defaultContext
124114 where
125− getTutorialType item = do
126− mbExternal <- getMetadataField (itemIdentifier item) "external"
127− return $ case mbExternal of
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"
115+ ofType ty = filterM (\item -> do
116+ mbType <- getMetadataField (itemIdentifier item) "type"
117+ return $ Just ty == mbType) tuts
web/templates/tutorials.html+24−18
@@ -1,23 +1,19 @@
11<h1>Tutorials about Hakyll</h1>
22<h2 id="series">Tutorial series</h2>
33<ul>
4− $for(tutorials)$
5− $if(isSeries)$
6− <li>
7− <a href="$url$">$title$</a>
8− </li>
9− $endif$
4+ $for(main)$
5+ <li>
6+ <a href="$url$">$title$</a>
7+ </li>
108 $endfor$
119</ul>
1210<h2 id="articles">Other articles</h2>
1311<p>In no particular order:</p>
1412<ul>
15− $for(tutorials)$
16− $if(isArticle)$
17− <li>
18− <a href="$url$">$title$</a> by <em>$author$</em>
19− </li>
20− $endif$
13+ $for(articles)$
14+ <li>
15+ <a href="$url$">$title$</a> by <em>$author$</em>
16+ </li>
2117 $endfor$
2218</ul>
2319<h2 id="external-articles">External articles</h2>
@@ -28,12 +24,22 @@
2824</p>
2925<p>In no particular order:</p>
3026<ul>
31− $for(tutorials)$
32− $if(isExternal)$
33− <li>
34− <a href="$url$">$title$</a> by <em>$author$</em>
35− </li>
36− $endif$
27+ $for(externals)$
28+ <li>
29+ <a href="$url$">$title$</a> by <em>$author$</em>
30+ </li>
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>
3743 $endfor$
3844</ul>
3945<p>
web/tutorials/01-installation.markdown+4−2
@@ -1,6 +1,7 @@
11---
22title: Installation
33author: Jasper Van der Jeugt
4+type: main
45---
56
67Installation
@@ -17,7 +18,8 @@ different distributions. For installation from source (i.e. via Hackage),
1718Linux distro packages:
1819
1920- [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`
2123
2224Building the example site
2325-------------------------
@@ -26,7 +28,7 @@ Apart from the main Hakyll library, the cabal package also provides you with an
2628executable `hakyll-init` to create an example site. This is an easy way to get
2729started:
2830
29− $ hakyll-init my-site
31+ $ stack exec hakyll-init my-site
3032
3133This creates a folder `my-site` in the current directory, with some example
3234content and a generic configuration.
web/tutorials/02-basics.markdown+1
@@ -1,6 +1,7 @@
11---
22title: The basics
33author: Jasper Van der Jeugt
4+type: main
45---
56
67Building and cleaning
web/tutorials/03-rules-routes-compilers.markdown+1
@@ -1,6 +1,7 @@
11---
22title: Rules, routes and compilers
33author: Jasper Van der Jeugt
4+type: main
45---
56
67Basic rules
web/tutorials/04-compilers.markdown+4−3
@@ -1,6 +1,7 @@
11---
22title: 'More on compilers: load, and templates'
33author: Jasper Van der Jeugt
4+type: main
45---
56
67Loading items
@@ -92,7 +93,7 @@ And `$title$` like this:
9293titleContext :: Context a
9394titleContext = field "title" $ \item -> do
9495 metadata <- getMetadata (itemIdentifier item)
95− return $ fromMaybe "No title" $ M.lookup "title" metadata
96+ return $ fromMaybe "No title" $ lookupString "title" metadata
9697```
9798
9899And compose them using the `Monoid` instance:
@@ -130,7 +131,7 @@ postCtx =
130131### Loading and applying templates
131132
132133Now we know about templates, context and how to load arbitrary items. This gives
133−us enough background information in order to understand you can apply a
134+us enough background information in order to understand how you can apply a
134135template:
135136
136137```haskell
@@ -151,7 +152,7 @@ Control flow in templates
151152-------------------------
152153
153154Sometimes 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
155156structures for this. The syntax for these structures was based on the syntax
156157used in pandoc templates, since Hakyll already has tight integration with
157158pandoc.
web/tutorials/05-snapshots-feeds.markdown+1
@@ -1,6 +1,7 @@
11---
22title: Snapshots, and how to produce an RSS/Atom feed
33author: Jasper Van der Jeugt
4+type: main
45---
56
67Basic feed configuration
web/tutorials/06-versions.markdown+1
@@ -1,6 +1,7 @@
11---
22title: Producing multiple versions of a single file
33author: Jasper Van der Jeugt
4+type: main
45---
56
67Basics
web/tutorials/a-guide-to-the-hakyll-module-zoo.markdown+1
@@ -1,6 +1,7 @@
11---
22title: A Guide to the Hakyll Module Zoo
33author: Brent Yorgey
4+type: article
45---
56
67The 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 @@
22title: Add tags to your Hakyll blog
33author: Javran Cheng
44url: https://javran.github.io/posts/2014-03-01-add-tags-to-your-hakyll-blog.html
5−external: true
5+type: external
66---
web/tutorials/external-blogger-port.md+1−1
@@ -2,5 +2,5 @@
22title: Port an existing Blogger blog to Hakyll
33author: Daniel Campoverde
44url: 'http://www.sillybytes.net/posts/from_blogger_to_hakyll.html'
5−external: true
5+type: external
66---
web/tutorials/external-cassius-template.md+1−1
@@ -2,5 +2,5 @@
22title: Using Cassius templating with Hakyll
33author: Daniel Campoverde
44url: 'http://www.sillybytes.net/posts/using_hakyll_with_cassius.html'
5−external: true
5+type: external
66---
web/tutorials/external-clean-urls-with-hakyll.md+1−1
@@ -2,5 +2,5 @@
22title: Clean URLs with Hakyll
33author: Rohan Jain
44url: http://www.rohanjain.in/hakyll-clean-urls/
5−external: true
5+type: external
66---
web/tutorials/external-deploying-with-widely.md+1−1
@@ -2,5 +2,5 @@
22title: Deploying with Widely
33author: Kyle Marek-Spartz
44url: http://kyle.marek-spartz.org/posts/2013-12-09-widely-and-hakyll.html
5−external: true
5+type: external
66---
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 @@
22title: Hakyll's functionField
33author: Beerend Lauwers
44url: http://beerendlauwers.be/posts/2015-09-21-hakylls-functionfield.html
5−external: true
5+type: external
66---
web/tutorials/external-github-pages-circle-ci.md+1−1
@@ -2,5 +2,5 @@
22title: Create a GitHub page with Hakyll and CircleCI
33author: Juan Pedro Villa
44url: https://www.stackbuilders.com/news/dr-hakyll-create-a-github-page-with-hakyll-and-circleci
5−external: true
5+type: external
66---
web/tutorials/external-inlining-and-compressing-css.md+1−1
@@ -2,5 +2,5 @@
22title: Inlining and compressing CSS
33author: Kyle Marek-Spartz
44url: http://kyle.marek-spartz.org/posts/2014-12-09-hakyll-css-template-compiler.html
5−external: true
5+type: external
66---
web/tutorials/faq.markdown+1
@@ -1,6 +1,7 @@
11---
22title: FAQ
33author: Jasper Van der Jeugt
4+type: article
45---
56
67## "hGetContents: invalid argument" or "commitBuffer: invalid argument"
web/tutorials/github-pages-tutorial.md+1
@@ -1,6 +1,7 @@
11---
22title: Using Hakyll with GitHub Pages
33author: Erik Stevenson
4+type: article
45---
56
67## Introduction
web/tutorials/hakyll-3-to-hakyll4-migration-guide.markdown+1
@@ -1,6 +1,7 @@
11---
22title: Hakyll 3 to Hakyll 4 migration guide
33author: Jasper Van der Jeugt
4+type: article
45---
56
67Introduction
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 @@
11---
22title: Using Clay with Hakyll
33author: Jasper Van der Jeugt
4+type: article
45---
56
67[Clay](http://sebastiaanvisser.github.com/clay/) is a nice CSS preprocesser
web/tutorials/using-teasers-in-hakyll.markdown+1
@@ -1,6 +1,7 @@
11---
22title: Using teasers in Hakyll
33author: Ivan Veselov
4+type: article
45---
56
67## Introduction