on cd5c78c help sign in
156 files changed+2566−5826
since splitsnapshots
unifiedsplit
.circleci/config.yml−19 .circleci/tickle.sh−24 .ghci+2−1 .gitignore+1−7 .travis.yml+1 CHANGELOG.md−575 CODE_OF_CONDUCT.md−45 LICENSE+1−1 Makefile+6−6 README.markdown+2−6 data/example/css/default.css+37−122 data/example/index.html+1−1 data/example/posts/2015-08-12-spqr.markdown → data/example/posts/2012-08-12-spqr.markdown data/example/posts/2015-10-07-rosa-rosa-rosam.markdown → data/example/posts/2012-10-07-rosa-rosa-rosam.markdown data/example/posts/2015-11-28-carpe-diem.markdown → data/example/posts/2012-11-28-carpe-diem.markdown data/example/posts/2015-12-07-tu-quoque.markdown → data/example/posts/2012-12-07-tu-quoque.markdown data/example/site.hs+1−1 data/example/templates/default.html+16−16 data/example/templates/post.html+8−11 data/templates/rss-item.xml−1 data/templates/rss.xml+2−3 hakyll.cabal+121−154 lib/Data/List/Extended.hs−15 lib/Data/Yaml/Extended.hs−24 lib/Hakyll/Check.hs−302 lib/Hakyll/Core/Identifier/Pattern/Internal.hs−92 lib/Hakyll/Core/Metadata.hs−138 lib/Hakyll/Core/Provider/Metadata.hs−152 lib/Hakyll/Main.hs−180 lib/Hakyll/Preview/Poll.hs−109 lib/Hakyll/Preview/Server.hs−35 lib/Hakyll/Web/CompressCss.hs−101 lib/Hakyll/Web/Paginate.hs−153 lib/Hakyll/Web/Pandoc/Binary.hs−33 lib/Hakyll/Web/Redirect.hs−100 lib/Hakyll/Web/Template.hs−178 lib/Hakyll/Web/Template/Context.hs−481 lib/Hakyll/Web/Template/Internal.hs−261 lib/Hakyll/Web/Template/Internal/Element.hs−291 lib/Hakyll/Web/Template/Internal/Trim.hs−95 lib/Hakyll.hs → src/Hakyll.hs+3−7 src/Hakyll/Check.hs+251 lib/Hakyll/Commands.hs → src/Hakyll/Commands.hs+36−48 lib/Hakyll/Core/Compiler.hs → src/Hakyll/Core/Compiler.hs+7−51 lib/Hakyll/Core/Compiler/Internal.hs → src/Hakyll/Core/Compiler/Internal.hs+52−164 lib/Hakyll/Core/Compiler/Require.hs → src/Hakyll/Core/Compiler/Require.hs+8−3 lib/Hakyll/Core/Configuration.hs → src/Hakyll/Core/Configuration.hs−6 lib/Hakyll/Core/Dependencies.hs → src/Hakyll/Core/Dependencies.hs+4−3 lib/Hakyll/Core/File.hs → src/Hakyll/Core/File.hs+4−11 lib/Hakyll/Core/Identifier.hs → src/Hakyll/Core/Identifier.hs+2−1 lib/Hakyll/Core/Identifier/Pattern.hs → src/Hakyll/Core/Identifier/Pattern.hs+74−14 lib/Hakyll/Core/Item.hs → src/Hakyll/Core/Item.hs+2 lib/Hakyll/Core/Item/SomeItem.hs → src/Hakyll/Core/Item/SomeItem.hs lib/Hakyll/Core/Logger.hs → src/Hakyll/Core/Logger.hs+4−9 src/Hakyll/Core/Metadata.hs+63 lib/Hakyll/Core/Provider.hs → src/Hakyll/Core/Provider.hs lib/Hakyll/Core/Provider/Internal.hs → src/Hakyll/Core/Provider/Internal.hs+7−6 src/Hakyll/Core/Provider/Metadata.hs+135 lib/Hakyll/Core/Provider/MetadataCache.hs → src/Hakyll/Core/Provider/MetadataCache.hs+6−3 lib/Hakyll/Core/Routes.hs → src/Hakyll/Core/Routes.hs+6−30 lib/Hakyll/Core/Rules.hs → src/Hakyll/Core/Rules.hs+8−39 lib/Hakyll/Core/Rules/Internal.hs → src/Hakyll/Core/Rules/Internal.hs+2−14 lib/Hakyll/Core/Runtime.hs → src/Hakyll/Core/Runtime.hs+21−40 lib/Hakyll/Core/Store.hs → src/Hakyll/Core/Store.hs+24−26 lib/Hakyll/Core/UnixFilter.hs → src/Hakyll/Core/UnixFilter.hs+5−20 lib/Hakyll/Core/Util/File.hs → src/Hakyll/Core/Util/File.hs+1 lib/Hakyll/Core/Util/Parser.hs → src/Hakyll/Core/Util/Parser.hs+4−10 lib/Hakyll/Core/Util/String.hs → src/Hakyll/Core/Util/String.hs+1−2 lib/Hakyll/Core/Writable.hs → src/Hakyll/Core/Writable.hs src/Hakyll/Init.hs+37 src/Hakyll/Main.hs+116 src/Hakyll/Preview/Poll.hs+104 src/Hakyll/Preview/Server.hs+43 src/Hakyll/Web/CompressCss.hs+60 lib/Hakyll/Web/Feed.hs → src/Hakyll/Web/Feed.hs+28−70 lib/Hakyll/Web/Html.hs → src/Hakyll/Web/Html.hs+3−18 lib/Hakyll/Web/Html/RelativizeUrls.hs → src/Hakyll/Web/Html/RelativizeUrls.hs src/Hakyll/Web/Paginate.hs+134 lib/Hakyll/Web/Pandoc.hs → src/Hakyll/Web/Pandoc.hs+20−51 lib/Hakyll/Web/Pandoc/Biblio.hs → src/Hakyll/Web/Pandoc/Biblio.hs+16−39 lib/Hakyll/Web/Pandoc/FileType.hs → src/Hakyll/Web/Pandoc/FileType.hs+19−24 lib/Hakyll/Web/Tags.hs → src/Hakyll/Web/Tags.hs+10−15 src/Hakyll/Web/Template.hs+150 src/Hakyll/Web/Template/Context.hs+280 src/Hakyll/Web/Template/Internal.hs+63 lib/Hakyll/Web/Template/List.hs → src/Hakyll/Web/Template/List.hs+3−23 src/Hakyll/Web/Template/Read.hs+93 src/Init.hs−123 stack.yaml−33 test.hs−30 tests/Hakyll/Core/Dependencies/Tests.hs+4−5 tests/Hakyll/Core/Identifier/Tests.hs+5−6 tests/Hakyll/Core/Provider/Metadata/Tests.hs+15−24 tests/Hakyll/Core/Provider/Tests.hs+12−8 tests/Hakyll/Core/Routes/Tests.hs+8−14 tests/Hakyll/Core/Rules/Tests.hs+19−29 tests/Hakyll/Core/Runtime/Tests.hs+6−21 tests/Hakyll/Core/Store/Tests.hs+16−16 tests/Hakyll/Core/UnixFilter/Tests.hs+13−23 tests/Hakyll/Core/Util/String/Tests.hs+3−3 tests/Hakyll/Web/CompressCss/Tests.hs−64 tests/Hakyll/Web/Html/RelativizeUrls/Tests.hs+3−7 tests/Hakyll/Web/Html/Tests.hs+9−11 tests/Hakyll/Web/Pandoc/FileType/Tests.hs+3−3 tests/Hakyll/Web/Template/Context/Tests.hs+9−45 tests/Hakyll/Web/Template/Tests.hs+20−97 tests/TestSuite.hs+2−11 tests/TestSuite/Util.hs+15−36 tests/data/embed.html−1 tests/data/example.md.metadata−3 tests/data/just-meta.html−7 tests/data/just-meta.html.out−7 tests/data/partial-helper.html−3 tests/data/partial.html−3 tests/data/partial.html.out−7 tests/data/posts/2018-09-26.md−1 tests/data/posts/2019/05/10/tomorrow.md−1 tests/data/strip.html−34 tests/data/strip.html.out−18 tests/data/template.html+1−2 tests/data/template.html.out−1 web/css/syntax.css+18−61 web/examples.markdown+17−128 web/index.markdown+2−4 web/libraries.markdown−21 web/releases.markdown+164 web/site.hs+32−29 web/templates/default.html+7−2 web/templates/tutorial-item.html+1 web/templates/tutorials.html+7−50 web/tutorials/01-installation.markdown+23−16 web/tutorials/02-basics.markdown+11−12 web/tutorials/03-rules-routes-compilers.markdown+1−2 web/tutorials/04-compilers.markdown+9−26 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−6 web/tutorials/external-blogger-port.md−6 web/tutorials/external-cassius-template.md−6 web/tutorials/external-clean-urls-with-hakyll.md−6 web/tutorials/external-deploying-with-widely.md−6 web/tutorials/external-external-code-inclusion.md−6 web/tutorials/external-functionfield.md−6 web/tutorials/external-github-pages-circle-ci.md−6 web/tutorials/external-inlining-and-compressing-css.md−6 web/tutorials/faq.markdown−16 web/tutorials/github-pages-tutorial.md−181 web/tutorials/hakyll-3-to-hakyll4-migration-guide.markdown+1−2 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+2−16 web/tutorials/using-teasers-in-hakyll.markdown−1
.circleci/config.ymldeleted−19
@@ -1,19 +0,0 @@
1−version: 2
2−jobs:
3− build:
4− docker:
5− - image: 'fpco/stack-build:latest'
6− steps:
7− - checkout
8− - restore_cache:
9− key: 'v3-hakyll-{{ arch }}-{{ .Branch }}'
10− - run:
11− # We set jobs to 1 here because that prevents Out-Of-Memory exceptions
12− # while compiling dependencies.
13− name: 'Install dependencies'
14− command: '.circleci/tickle.sh stack build --test --copy-bins --jobs=1'
15− - save_cache:
16− key: 'v3-hakyll-{{ arch }}-{{ .Branch }}-{{ .Revision }}'
17− paths:
18− - '~/.stack-work'
19− - '~/.stack'
.circleci/tickle.shdeleted−24
@@ -1,24 +0,0 @@
1−#!/bin/bash
2−set -o nounset -o errexit -o pipefail
3−
4−function tickle() {
5− while [ true ]; do
6− echo "[$(date +%H:%M:%S)] Tickling..."
7− sleep 60
8− done
9−}
10−
11−echo "Forking tickle process..."
12−tickle &
13−TICKLE_PID=$!
14−
15−echo "Forking build process..."
16−eval $@ &
17−BUILD_PID=$!
18−
19−echo "Waiting for build thread ($BUILD_PID)..."
20−wait $BUILD_PID
21−
22−echo "Killing tickle thread ($TICKLE_PID)..."
23−kill $TICKLE_PID
24−echo "All done!"
.ghci+2−1
@@ -1 +1,2 @@
1−:set -isrc -itests -idist/build/autogen -DPREVIEW_SERVER
1+:set -isrc -isrc-interval -itests -idist/build/autogen -DPREVIEW_SERVER
2+:set -optP-include -optPdist/build/autogen/cabal_macros.h
.gitignore+1−7
@@ -1,4 +1,4 @@
1−# Ignore swap files and stack/cabal output.
1+# Ignore swap files and cabal output.
22*.hi
33*.o
44*.swo
@@ -8,9 +8,6 @@
88TAGS
99dist
1010tags
11−cabal.sandbox.config
12−.cabal-sandbox/
13−.stack-work
1411
1512# Ignore test builds.
1613tests/Main
@@ -23,6 +20,3 @@ web/_cache
2320web/_site
2421web/site
2522web/reference
26−
27−# This is generated from the CHANGELOG
28−web/releases.markdown
.travis.ymlnew file+1
@@ -0,0 +1 @@
1+language: haskell
CHANGELOG.mddeleted−575
@@ -1,575 +0,0 @@
1−---
2−title: Releases
3−---
4−
5−# Releases
6−
7−## Hakyll 4.13.0.0 (2019-08-30)
8−
9−- Improved documentation in many places (contribution by Bergi)
10−- Significantly improve error messages when reading and applying templates
11− (contribution by Bergi)
12− * `empty` and `fail` for `Compiler` now fail without or with a message,
13− allowing for much better debug output
14− * `renderFeed`, `renderRssWithTemplates` and `renderAtomWithTemplates` now
15− take `Template` rather than `String` arguments
16−- Add option to specify date in directory structure,e.g.
17− `posts/2019/05/10/tomorrow.md` (contribution by Taeer Bar-Ya)
18−- Bump QuickCheck to 2.13
19−
20−## Hakyll 4.12.5.2 (2019-05-09)
21−
22−- Bump pandoc to 2.7
23−- Add `srcset` to the list of URL attributes (contribution by c50a326)
24−- Expose `getCategory` in `Hakyll.Web.Tags` (contribution by Ng Wei En)
25−- Fix issue where `published` overwrites the user's context (contribution by
26− ncaq)
27−
28−## Hakyll 4.12.5.1 (2019-02-03)
29−
30−- Bump pandoc to 2.6
31−- Bump pandoc-citeproc to 0.16
32−
33−## Hakyll 4.12.5.0 (2019-01-12)
34−
35−- Update dependencies (contribution by Hexirp):
36− * Bump containers to 0.6
37− * Bump yaml to 0.11
38− * Bump pandoc to 2.5
39− * Bump pandoc-citeproc to 0.15
40− * Bump tasty to 1.2
41−- Correct assertion in unixFilterError test
42− (contribution by Jim McStanton)
43−- Add renderRssWithTemplates, renderAtomWithTemplates
44− (contribution by Abhinav Sarkar)
45−- Speed up hashing in cache (contribution by 0xd34df00d)
46−- Update type of fromFilePath to use FilePath instead of String
47− (contribution by Jim McStanton)
48−- Drop extension when parsing dates in filepaths (contribution by
49− Gabriel Aumala)
50−
51−## Hakyll 4.12.4.0 (2018-08-13)
52−
53−- Bump yaml to 0.10
54−- Bump file-embed to 0.0.11
55−- Bump QuickCheck to 2.12
56−- Use makeRelativeToProject with embedFile for stack ghci (contribution by
57− Michael Sloan)
58−
59−## Hakyll 4.12.3.0 (2018-05-29)
60−
61−- Bump tasty to 1.1
62−- Bump fsnotify to 0.3
63−
64−## Hakyll 4.12.2.0
65−
66−- Bump pandoc to 2.2
67−- Fix path resolution in Pandoc.Biblio (contribution by knih)
68−
69−## Hakyll 4.12.1.0
70−
71−- Fix hakyll-init on older GHC versions
72−- Make the Pandoc dependency optional
73−
74−## Hakyll 4.12.0.1
75−
76−- Bump resourcet to 1.2 in test section
77−
78−## Hakyll 4.12.0.0
79−
80−- Add Semigroup instances for existing Monoids (contribution by
81− Christian Barcenas)
82−- Fix issue with CPP and comment containing `/*`
83−- Add `withTagList` (contribution by Oleg Grenrus)
84−- Improve CSS compression (contribution by Bergi)
85−- Bump pandoc-citeproc to 0.14
86−- Bump resourcet to 1.2
87−- Bump time to 1.9
88−- Bump http-types to 0.12
89−- Bump http-conduit to 2.3
90−- Bump tasty-quickcheck to 0.10
91−
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−
107−## Hakyll 4.10.0.0
108−
109−- Bump Pandoc to 2.0 (contribution by Vaibhav Sagar)
110−- Fix compression of calc() in CSS (contribution by Krzysztof Jurewicz)
111−- Make unixFilter output stderr when failing (contribution by Nick Boultbee)
112−- Export Check type from `Hakyll.Commands` (contribution by Futtetennista)
113−- Add overwritten files check to `hakyll-init` (contribution by Ilya Murzinov)
114−- Expose & document `hakyllWithExitCodeAndArgs`, `Options`, and `Command`
115− (contribution by Michael Walker)
116−- Add check for non-overlapping redirects (contribution by gwern)
117−- Fix early exit when calling check as a library
118−
119−## Hakyll 4.9.8.0
120−
121−- Bump pandoc-citeproc to 0.10.5
122−- Bump optparse-applicative to 0.14
123−- Bump QuickCheck to 2.10
124−- Bump tasty-quickcheck to 0.9
125−- Restructure .cabal to avoid redundant compilation (contribution by
126− Christopher League)
127−
128−## Hakyll 4.9.7.0
129−
130−- Fix compilation trouble with `Options.Applicative`
131−- Some small CSS compression improvements (contribution by Nicole Rauch)
132−
133−## Hakyll 4.9.6.0
134−
135−- Tighten dependency on `pandoc-citeproc` (contribution by Mikhail Glushenkov)
136−- Enable using a custom parser for command line arguments (contribution by
137− Alberto)
138−- Update examples to semantic HTML (contribution by Elie Génard)
139−- Better error for `cached` on non-existing file
140−- Provide an `$allPages$` key when doing pagination
141−- Preserve file metadata in `copyFileCompiler` (contribution by frederik-h)
142−
143−## Hakyll 4.9.5.1
144−
145−- Bump blaze-html dependency to 0.9
146−- Bump blaze-markup dependency to 0.8
147−- Bump process dependency to 1.5
148−
149−## Hakyll 4.9.5.0
150−
151−- Fix compilation issue with `Hakyll.Check` if `checkExternal` is disabled
152− (Fix by Magnus Therning)
153−
154−## Hakyll 4.9.4.0
155−
156−- Make `./site check` concurrent
157−- Bump directory dependency to 1.3
158−- Bump time dependency to 1.7
159−- Bump vector dependency to 0.12
160−
161−## Hakyll 4.9.3.0
162−
163−- Add a `Hakyll.Web.Redirect` module (contribution by gwern)
164−- Expose `Hakyll.Commands`
165−- Fix the exit code behaviour of `./site check`
166−
167−## Hakyll 4.9.2.0
168−
169−- Fix integer fields in YAML metadata (Fix by Nikolaos S. Papaspyrou)
170−- Bump pandoc dependency to 1.19
171−
172−## Hakyll 4.9.1.0
173−
174−- Allow optparse-applicative 0.13, QuickCheck 2.9, and pandoc 1.18
175− (contributions by Chris Wong and Felix Yan)
176−- Fix extra test files for packaging source files (contribution by Julien
177− Langlois)
178−
179−## Hakyll 4.9.0.0
180−
181−This release switches over some dependencies to alternatives, in order to clean
182−up some stuff and build on a wider variety of setups (stack/cabal).
183−
184−- Move from `test-framework` to `tasty`
185−- Fix feed generator when item contains CDATA (contribution by Yann Esposito)
186−- Fix CompressCSS to not modify string constants (contribution by Nicole Rauch)
187−- Fix YAML dependency issue (contribution by Jens Peterson)
188−- Move from `cmdargs` to `optparse-applicative` (contribution by sk3r)
189−- Allow for trimming whitespace in templates (contribution by Sam Davis)
190−- Improve error messages for template parsing (contribution by Lorenzo
191− Tabacchini)
192−- Improvements to the installation instructions (contribution by Thomas Koch)
193−- Move from `snap` to `warp` for preview servier (contribution by Arguggi)
194−- Fix error in CompressCSS (contribution by Luca Molteni)
195−- Move example from XHTML to HTML5 (contribution by Peter Doherty)
196−- Make errors in check less verbose (contribution by Jan Tojnar)
197−- Work on building with GHC 8.0.1 (contribution by Rohan Jain)
198−
199−## Hakyll 4.8.3.2
200−
201−This release is compatible with GHC 8.0.1, although `previewServer` might not
202−work yet on some setups.
203−
204−- Allow data-default 0.7, pandoc-citeproc 0.10, and tagsoup 0.14 (contributions
205− by Paul van der Walt and Felix Yan)
206−- Allow binary 0.8, process 1.4, time 1.6 (contribution by Sergei Trofimovich)
207−- Fix issue with `.metadata` file reading
208−
209−## Hakyll 4.8.3.1
210−
211−- Bump scientific dependency to 0.3.4
212−
213−## Hakyll 4.8.3.0
214−
215−- Fix another compilation issue wrt. orphan `Show` instance from regex-tdfa
216− (contribution by Sergei Trofimovich)
217−
218−## Hakyll 4.8.2.0
219−
220−- Fix compilation issue wrt. orphan `Show` instance from regex-tdfa
221−
222−## Hakyll 4.8.1.0
223−
224−- Fix compilation on windows
225−
226−## Hakyll 4.8.0.1
227−
228−- Fix issue with test suite
229−
230−## Hakyll 4.8.0.0
231−
232−- Support full YAML in page metadata
233−- Bump data-default dependency to 0.6
234−- Add snippet field for literal includes in templates (contribution by Nicolas
235− Mattia)
236−
237−## Hakyll 4.7.5.2
238−
239−- Bump pandoc dependency to 1.17 (contribution by Felix Yan)
240−- Fix `unixFilter` documentation (contribution by Richard Cook)
241−- Bump example posts (contribution by Andrew Barchuk)
242−- Add a template compiler that only uses the template body (contribution by
243− Bergi)
244−
245−## Hakyll 4.7.5.1
246−
247−- Bump pandoc and pandoc-citeproc dependencies to 1.16 and 0.9 respectively
248−
249−## Hakyll 4.7.5.0
250−
251−- Expose templating engine
252−- Fix bug in feed context precedence (contribution by Yuriy Syrovetskiy)
253−- Bump http-types dependency to 0.9
254−
255−## Hakyll 4.7.4.0
256−
257−- Expose `getItemModificationTime`
258−
259−## Hakyll 4.7.3.1
260−
261−- Bump pandoc-citeproc dependency to 0.8
262−
263−## Hakyll 4.7.3.0
264−
265−- Bump HUnit dependency to 1.3
266−- Add `poster` as an URL attribute (contribution by vtduncan)
267−- Prevent `hakyll-init` from generating directories with leading hyphen
268− (contribution by Javran Cheng)
269−
270−## Hakyll 4.7.2.3
271−
272−- Fix time dependency in tests
273−
274−## Hakyll 4.7.2.2
275−
276−- Relax time dependency
277−
278−## Hakyll 4.7.2.1
279−
280−- Bump fsnotify dependency to 0.2
281−
282−## Hakyll 4.7.2.0
283−
284−- Improve documentation of `getResourceXXX` functions (contribution by Matthias
285− C. M. Troffaes)
286−- Allow for empty templates
287−- Bump pandoc dependency to 1.15
288−
289−## Hakyll 4.7.1.0
290−
291−- Drop old-time, old-locale, time-locale-compat dependencies
292−- Add convenicence `pandocBiblioCompiler` (contribution by
293− Matthias C. M. Troffaes)
294−- Add support for mediawiki (contribution by Chen Lei)
295−
296−## Hakyll 4.7.0.0
297−
298−- Bump pandoc to 1.14. This will break a lot of sites: since the pandoc parser
299− might now return an error message, it is ran inside the `Compiler` monad where
300− we can nicely handle the error.
301−
302−## Hakyll 4.6.9.0
303−
304−- Let caller decide exit (fix by Erik Dominikus)
305−- Bump pandoc-citeproc dependency
306−
307−## Hakyll 4.6.8.1
308−
309−- Fix test suite dependencies
310−
311−## Hakyll 4.6.8.0
312−
313−- Fix building on GHC 7.10 (fix by Charles Strahan)
314−- Add support for a custom teaser separator (contribution by Tom Sydney
315− Kerckhove)
316−- Let Pandoc handle DocBook files (contribution by Joshua SImmons)
317−
318−## Hakyll 4.6.7.1
319−
320−- Bump dependencies
321−
322−## Hakyll 4.6.7.0
323−
324−- Bump dependencies
325−- Fix bug where hakyll-init would create a file called `name.cabal.cabal` (fix
326− by Hans-Peter Deifel)
327−
328−## Hakyll 4.6.6.0
329−
330−- Fix compilation error when preview server is disabled (fix by Magnus Therning)
331−- Add author name by default to RSS feeds (contribution by Calen Pennington)
332−
333−## Hakyll 4.6.5.0
334−
335−- Bump dependencies
336−- Fix garbled "Listening on 0.0.0.0:8000" message
337−- Add `boolField` (contribution by Ferenc Wágner)
338−
339−## Hakyll 4.6.4.0
340−
341−- Fix another dependency handling bug when using snapshots
342−- Add `matchMetadata` for examining metadata when defining rules
343−
344−## Hakyll 4.6.3.0
345−
346−- Fix dependency handling bug
347−
348−## Hakyll 4.6.2.0
349−
350−- Loosen `binary` dependency
351−- Make dependency handling more granular so you can depend on specific snapshots
352− of an item
353−
354−## Hakyll 4.6.1.0
355−
356−- Bump `fsnotify` and `pandoc-citeproc` dependencies
357−- Rewrite polling code a bit
358−
359−## Hakyll 4.6.0.0
360−
361−- Added `listFieldWith` function
362−- Improved `rulesExtraDependencies` behaviour
363−- Changed function syntax in templates from `$foo arg1 arg2$` to
364− `$foo("arg1", "arg2")$`
365−- Support parsing date from directory names in addition to file names
366−
367−## Hakyll 4.5.5.0
368−
369−- Fix Binary instances for `pandoc` and `pandoc-citeproc`
370−- Fix `network-uri` dependency issue
371−
372−## Hakyll 4.5.4.0
373−
374−- Fix issue with HTML entities when running `withUrls` and `demoteHeaders`.
375−- Generate a cabal file for the initialised site.
376−- Add pagination support.
377−
378−## Hakyll 4.5.3.0
379−
380−- Bump Pandoc to 1.12.4 to include the org-mode reader.
381−
382−## Hakyll 4.5.2.0
383−
384−- Fix rebuilding everything issue with latest directory (contribution by Jorge
385− Israel Peña)
386−- Fix issue with `toSiteRoot` (contribution by Izzy Cecil)
387−- Fix issue with tag dependencies, slightly improve caching
388−
389−## Hakyll 4.5.0.0
390−
391−- Fix issue with syntax highlighting and line numbers (contribution by Adelbert
392− Chang)
393−- Improve documentation for `Context` (contribution by Daniil Frumin)
394−- Added `IsString` instance for `Template`
395−- Added the `pandocCompilerWithTransformM` function (contribution by Daniil
396− Frumin)
397−- Make `./site check` return the right exit code (contribution by Andres Loeh)
398−- Use OS threads to make `./site watch` work nicely on Windows (contribution by
399− Simonas Kazlauskas)
400−- Make the `unixFilter` function work better on windows by calling `shell`
401− (contribution by Collin J. Doering)
402−- Add a command-line flag to bind on a user-specified host (contribution by
403− chrisdotcode)
404−
405−## Hakyll 4.4.3.0
406−
407−- Fix issue when using `metadataRoute` after other custom routes
408−
409−## Hakyll 4.4.2.0
410−
411−- Fix issue where Hakyll would not detect a change if a `.metadata` file was
412− deleted
413−
414−## Hakyll 4.4.1.0
415−
416−- Use Pandoc 1.12 highlighting by default
417−
418−## Hakyll 4.4.0.0
419−
420−- Update to work with Pandoc 1.12. This changes the type of `readPandocBibilio`:
421− the `CSL` argument is no longer optional (contribution by Jorge Israel Peña)
422−
423−- Fix incorrect output of `toSiteRoot` on windows (contribution by Saeid
424− Al-Wazzan)
425−
426−- Add a preview port option to `Configuration` (contribution by Jorge Israel
427− Peña)
428−
429−- Add `watch` command that polls for changes but does not necessarily launch a
430− server (contribution by Eric Stolten)
431−
432−- Generalise type of `metadataField`
433−
434−- Fix issue where metadata was not correctly loaded when using versions
435−
436−## Hakyll 4.3.3.0
437−
438−- Re-add the `functionField` function
439−
440−## Hakyll 4.3.2.0
441−
442−- Re-add the `mapContext` function
443−
444−- Unescape internal URLs when using `./site check` (contribution by Marc-Antoine
445− Perennou)
446−
447−## Hakyll 4.3.1.0
448−
449−- Make teasers undefined if no `<!--more-->` comment is found
450−
451−- Sanitize tag URLs (contribution by Simonas Kazlauskas)
452−
453−## Hakyll 4.3.0.0
454−
455−- Add conditionals, partials and for loops to the template system (includes a
456− contribution by Ivan N. Veselov)
457−
458−- Improvements to the preview functionality on windows (contribution by Jorge
459− Israel Peña)
460−
461−- Add pagination support (contribution by Anton Dubovik)
462−
463−- Slight speedup for the Hakyll cache (contribution by justnoxx)
464−
465−- Add teaser functionality (contribution by Ivan N. Veselov)
466−
467−- Make `./site check` work with scheme-relative URLs (contribution by Simonas
468− Kazlauskas)
469−
470−- The `./site deploy` command can now be customized with Haskell code
471− (contribution by Samuel Tardieu)
472−
473−- Use `hsnotify` for proper polling instead of sleep loop on all platforms
474− (contribution by Simonas Kazlauskas)
475−
476−- More useful debug info available
477−
478−## Hakyll 4.2.2.0
479−
480−- Fix issue with `Alternative` instance of `Compiler`
481−
482−## Hakyll 4.2.1.1
483−
484−*March 9, 2013*
485−
486−- Make `http-conduit` dependency optional by adding a `checkExternal` cabal flag
487−
488−## Hakyll 4.2.1.0
489−
490−*March 7, 2013*
491−
492−- Fix issue where `copyFileCompiler` ignored `providerDirectory`
493−
494−## Hakyll 4.2.0.0
495−
496−*March 7, 2013*
497−
498−- Read second extension for `.lhs`, e.g. `.md.lhs` or `.tex.lhs` (contribution
499− by Alexander Vershilov)
500−
501−- Speedup initialization by using modification times instead of hashing files
502−
503−- Speedup initialization with a rewritten resource provider
504−
505−- Fix `./site check` not working with sites that require a user agent (e.g.
506− <http://www.wikipedia.org/>)
507−
508−- Change `chronological` and `recentFirst` to actually look at the dates of
509− items. This changes their types from:
510−
511− chronological, recentFirst :: [Item a] -> [Item a]
512−
513− to:
514−
515− chronological, recentFirst
516− :: MonadMetadata m => [Item a] -> m [Item a]
517−
518− (contribution by Simonas Kazlauskas)
519−
520−- Add `metadataRoute`, so it is now possible to use metadata when determining
521− routes
522−
523−- Improve metadata parser for multiline metadata fields (contribution by Peter
524− Jones)
525−
526−- Add the `getMetadataField` utility
527−
528−## Hakyll 4.1.4.0
529−
530−*January 26, 2013*
531−
532−- Export the flexible `renderTags` function
533−
534−## Hakyll 4.1.3.0
535−
536−*January 26, 2013*
537−
538−- Export the constructor of the `Tags` datatype
539−
540−## Hakyll 4.1.2.0
541−
542−*January 20, 2013*
543−
544−- Fix an issue where a dependency cycle would lead to infinite recursion/stack
545− overflow
546−
547−## Hakyll 4.1.1.0
548−
549−*January 20, 2013*
550−
551−- Fix an issue regarding `relativizeUrls` expanding `<meta />` to
552− `<meta></meta>`
553−
554−## Hakyll 4.1.0.0
555−
556−*January 20, 2013*
557−
558−Update to use Pandoc 1.10, this requires changes to your `site.hs` if you're
559−using custom Pandoc options or the `Hakyll.Web.Pandoc.Biblio` module.
560−
561−- `defaultHakyllParserState` renamed to `defaultHakyllReaderOptions`
562−
563−- The type of `readPandocBiblio` changed
564−
565−Because of the many changes, this release is no longer compatible with Pandoc
566−1.9.
567−
568−## Hakyll 4.0.0.0
569−
570−*January 16, 2013*
571−
572−The Initial release of Hakyll 4, see
573−[this blogpost](http://jaspervdj.be/posts/2013-01-16-hakyll-4.0.html) and
574−[the migration guide](/tutorials/hakyll-3-to-hakyll4-migration-guide.html) for
575−an overview of changes.
CODE_OF_CONDUCT.mddeleted−45
@@ -1,45 +0,0 @@
1−# Contributor Covenant Code of Conduct
2−
3−## Our Pledge
4−
5−In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6−
7−## Our Standards
8−
9−Examples of behavior that contributes to creating a positive environment include:
10−* Using welcoming and inclusive language
11−* Being respectful of differing viewpoints and experiences
12−* Gracefully accepting constructive criticism
13−* Focusing on what is best for the community
14−* Showing empathy towards other community members
15−
16−Examples of unacceptable behavior by participants include:
17−
18−* The use of sexualized language or imagery and unwelcome sexual attention or advances
19−* Trolling, insulting/derogatory comments, and personal or political attacks
20−* Public or private harassment
21−* Publishing others' private information, such as a physical or electronic address, without explicit permission
22−* Other conduct which could reasonably be considered inappropriate in a professional setting
23−
24−## Our Responsibilities
25−
26−Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
27−
28−Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
29−
30−## Scope
31−
32−This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
33−
34−## Enforcement
35−
36−Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by [contacting the project team at m@jaspervdj.be](mailto:m@jaspervdj.be). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
37−
38−Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
39−
40−## Attribution
41−
42−This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
43−
44−[homepage]: http://contributor-covenant.org
45−[version]: http://contributor-covenant.org/version/1/4/
LICENSE+1−1
@@ -1,4 +1,4 @@
1−Copyright (c) 2009 - 2017, Jasper Van der Jeugt
1+Copyright (c) 2009, Jasper Van der Jeugt
22 
33All rights reserved.
44 
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−
41# Generate the docs and copy them to the website dir
52haddock:
6− stack build --haddock --no-haddock-deps
7− rsync -r "$(LOCAL_DOC_ROOT)/$(PACKAGE)/" web/reference/
3+ cabal haddock --hyperlink-source
4+ rm -rf web/reference/
5+ cp -r dist/doc/html/hakyll/ web/reference/
86
9−.PHONY: haddock
7+# Run the tests
8+test:
9+ runghc -isrc -itests tests/TestSuite.hs
README.markdown+2−6
@@ -1,15 +1,11 @@
11# hakyll
22
3−[![Build Status](https://img.shields.io/circleci/project/github/jaspervdj/hakyll.svg)](https://circleci.com/gh/jaspervdj/hakyll)
3+[![Build Status](https://secure.travis-ci.org/jaspervdj/hakyll.png?branch=master)](http://travis-ci.org/jaspervdj/hakyll)
44
55Hakyll is a static site generator library in Haskell. More information
6−(including a tutorial) can be found on
6+(including and a tutorial) can be found on
77[the hakyll homepage](http://jaspervdj.be/hakyll).
88
99You can install this library using cabal:
1010
1111 cabal install hakyll
12−
13−Or using stack:
14−
15− stack install hakyll
data/example/css/default.css+37−122
@@ -1,141 +1,56 @@
1−html {
2− font-size: 62.5%;
1+body {
2+ color: black;
3+ font-size: 16px;
4+ margin: 0px auto 0px auto;
5+ width: 600px;
36}
47
5−body {
6− font-size: 1.6rem;
7− color: #000;
8+div#header {
9+ border-bottom: 2px solid black;
10+ margin-bottom: 30px;
11+ padding: 12px 0px 12px 0px;
812}
913
10−header {
11− border-bottom: 0.2rem solid #000;
14+div#logo a {
15+ color: black;
16+ float: left;
17+ font-size: 18px;
18+ font-weight: bold;
19+ text-decoration: none;
1220}
1321
14−nav {
15− text-align: right;
22+div#header #navigation {
23+ text-align: right;
1624}
1725
18−nav a {
19− font-size: 1.8rem;
20− font-weight: bold;
21− color: black;
22− text-decoration: none;
23− text-transform: uppercase;
26+div#header #navigation a {
27+ color: black;
28+ font-size: 18px;
29+ font-weight: bold;
30+ margin-left: 12px;
31+ text-decoration: none;
32+ text-transform: uppercase;
2433}
2534
26−footer {
27− margin-top: 3rem;
28− padding: 1.2rem 0;
29− border-top: 0.2rem solid #000;
30− font-size: 1.2rem;
31− color: #555;
35+div#footer {
36+ border-top: solid 2px black;
37+ color: #555;
38+ font-size: 12px;
39+ margin-top: 30px;
40+ padding: 12px 0px 12px 0px;
41+ text-align: right;
3242}
3343
3444h1 {
35− font-size: 2.4rem;
45+ font-size: 24px;
3646}
3747
3848h2 {
39− font-size: 2rem;
40−}
41−
42−article .header {
43− font-size: 1.4rem;
44− font-style: italic;
45− color: #555;
46−}
47−
48−.logo a {
49− font-weight: bold;
50− color: #000;
51− text-decoration: none;
52−}
53−
54−@media (max-width: 319px) {
55− body {
56− width: 90%;
57− margin: 0;
58− padding: 0 5%;
59− }
60− header {
61− margin: 4.2rem 0;
62− }
63− nav {
64− margin: 0 auto 3rem;
65− text-align: center;
66− }
67− footer {
68− text-align: center;
69− }
70− .logo {
71− text-align: center;
72− margin: 1rem auto 3rem;
73− }
74− .logo a {
75− font-size: 2.4rem;
76− }
77− nav a {
78− display: block;
79− line-height: 1.6;
80− }
81−}
82−
83−@media (min-width: 320px) {
84− body {
85− width: 90%;
86− margin: 0;
87− padding: 0 5%;
88− }
89− header {
90− margin: 4.2rem 0;
91− }
92− nav {
93− margin: 0 auto 3rem;
94− text-align: center;
95− }
96− footer {
97− text-align: center;
98− }
99− .logo {
100− text-align: center;
101− margin: 1rem auto 3rem;
102− }
103− .logo a {
104− font-size: 2.4rem;
105− }
106− nav a {
107− display: inline;
108− margin: 0 0.6rem;
109− }
49+ font-size: 20px;
11050}
11151
112−@media (min-width: 640px) {
113− body {
114− width: 60rem;
115− margin: 0 auto;
116− padding: 0;
117− }
118− header {
119− margin: 0 0 3rem;
120− padding: 1.2rem 0;
121− }
122− nav {
123− margin: 0;
124− text-align: right;
125− }
126− nav a {
127− margin: 0 0 0 1.2rem;
128− display: inline;
129− }
130− footer {
131− text-align: right;
132− }
133− .logo {
134− margin: 0;
135− text-align: left;
136− }
137− .logo a {
138− float: left;
139− font-size: 1.8rem;
140− }
52+div.info {
53+ color: #555;
54+ font-size: 14px;
55+ font-style: italic;
14156}
data/example/index.html+1−1
@@ -13,4 +13,4 @@ title: Home
1313<h2>Posts</h2>
1414$partial("templates/post-list.html")$
1515
16−<p>…or you can find more in the <a href="/archive.html">archives</a>.</p>
16+<p>…or you can find more in the <a href="/archive.html">archives</a>.
data/example/posts/2015-08-12-spqr.markdown → data/example/posts/2012-08-12-spqr.markdownrenamed

The content is the same.

data/example/posts/2015-10-07-rosa-rosa-rosam.markdown → data/example/posts/2012-10-07-rosa-rosa-rosam.markdownrenamed

The content is the same.

data/example/posts/2015-11-28-carpe-diem.markdown → data/example/posts/2012-11-28-carpe-diem.markdownrenamed

The content is the same.

data/example/posts/2015-12-07-tu-quoque.markdown → data/example/posts/2012-12-07-tu-quoque.markdownrenamed

The content is the same.

data/example/site.hs+1−1
@@ -57,7 +57,7 @@ main = hakyll $ do
5757 >>= loadAndApplyTemplate "templates/default.html" indexCtx
5858 >>= relativizeUrls
5959
60− match "templates/*" $ compile templateBodyCompiler
60+ match "templates/*" $ compile templateCompiler
6161
6262
6363--------------------------------------------------------------------------------
data/example/templates/default.html+16−16
@@ -1,33 +1,33 @@
1−<!doctype html>
2−<html lang="en">
1+<?xml version="1.0" encoding="UTF-8"?>
2+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
3+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
4+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
35 <head>
4− <meta charset="utf-8">
5− <meta http-equiv="x-ua-compatible" content="ie=edge">
6− <meta name="viewport" content="width=device-width, initial-scale=1">
6+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
77 <title>My Hakyll Blog - $title$</title>
8− <link rel="stylesheet" href="/css/default.css" />
8+ <link rel="stylesheet" type="text/css" href="/css/default.css" />
99 </head>
1010 <body>
11− <header>
12− <div class="logo">
11+ <div id="header">
12+ <div id="logo">
1313 <a href="/">My Hakyll Blog</a>
1414 </div>
15− <nav>
15+ <div id="navigation">
1616 <a href="/">Home</a>
1717 <a href="/about.html">About</a>
1818 <a href="/contact.html">Contact</a>
1919 <a href="/archive.html">Archive</a>
20− </nav>
21− </header>
20+ </div>
21+ </div>
2222
23− <main role="main">
23+ <div id="content">
2424 <h1>$title$</h1>
25− $body$
26− </main>
2725
28− <footer>
26+ $body$
27+ </div>
28+ <div id="footer">
2929 Site proudly generated by
3030 <a href="http://jaspervdj.be/hakyll">Hakyll</a>
31− </footer>
31+ </div>
3232 </body>
3333</html>
data/example/templates/post.html+8−11
@@ -1,11 +1,8 @@
1−<article>
2− <section class="header">
3− Posted on $date$
4− $if(author)$
5− by $author$
6− $endif$
7− </section>
8− <section>
9− $body$
10− </section>
11−</article>
1+<div class="info">
2+ Posted on $date$
3+ $if(author)$
4+ by $author$
5+ $endif$
6+</div>
7+
8+$body$
data/templates/rss-item.xml−1
@@ -4,5 +4,4 @@
44 <description><![CDATA[$description$]]></description>
55 <pubDate>$published$</pubDate>
66 <guid>$root$$url$</guid>
7− <dc:creator>$authorName$</dc:creator>
87</item>
data/templates/rss.xml+2−3
@@ -1,6 +1,5 @@
11<?xml version="1.0" encoding="utf-8"?>
2−<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"
3− xmlns:dc="http://purl.org/dc/elements/1.1/">
2+<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
43 <channel>
54 <title>$title$</title>
65 <link>$root$</link>
@@ -9,5 +8,5 @@
98 type="application/rss+xml" />
109 <lastBuildDate>$updated$</lastBuildDate>
1110 $body$
12− </channel>
11+ </channel>
1312</rss>
hakyll.cabal+121−154
@@ -1,5 +1,5 @@
11Name: hakyll
2−Version: 4.13.0.0
2+Version: 4.4.0.0
33
44Synopsis: A static website compiler library
55Description:
@@ -37,15 +37,15 @@ License: BSD3
3737License-File: LICENSE
3838Category: Web
3939
40−Cabal-Version: >= 1.10
40+Cabal-Version: >= 1.8
4141Build-Type: Simple
4242Data-Dir: data
4343
4444Data-files:
45− example/posts/2015-11-28-carpe-diem.markdown
46− example/posts/2015-10-07-rosa-rosa-rosam.markdown
47− example/posts/2015-12-07-tu-quoque.markdown
48− example/posts/2015-08-12-spqr.markdown
45+ example/posts/2012-11-28-carpe-diem.markdown
46+ example/posts/2012-10-07-rosa-rosa-rosam.markdown
47+ example/posts/2012-12-07-tu-quoque.markdown
48+ example/posts/2012-08-12-spqr.markdown
4949 example/site.hs
5050 example/images/haskell-logo.png
5151 example/templates/post-list.html
@@ -56,28 +56,19 @@ 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
5963
6064Extra-source-files:
61− CHANGELOG.md
6265 tests/data/example.md
6366 tests/data/example.md.metadata
6467 tests/data/images/favicon.ico
65− tests/data/just-meta.html
66− tests/data/just-meta.html.out
67− tests/data/partial-helper.html
68− tests/data/partial.html
69− tests/data/partial.html.out
7068 tests/data/posts/2010-08-26-birthday.md
71− tests/data/posts/2018-09-26.md
7269 tests/data/russian.md
73− tests/data/strip.html
74− tests/data/strip.html.out
7570 tests/data/template.html
7671 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
8172
8273Source-Repository head
8374 Type: git
@@ -95,43 +86,24 @@ Flag checkExternal
9586 Description: Include external link checking
9687 Default: True
9788
98−Flag buildWebsite
99− Description: Build the hakyll website
100− Default: False
101− Manual: True
102−
103−Flag usePandoc
104− Description: Include Pandoc support
105− Default: True
106− Manual: True
107−
10889Library
109− Ghc-Options: -Wall
110− Hs-Source-Dirs: lib
111− Default-language: Haskell2010
90+ Ghc-Options: -Wall
91+ Ghc-Prof-Options: -auto-all -caf-all
92+ Hs-Source-Dirs: src
11293
11394 Exposed-Modules:
11495 Hakyll
115− Hakyll.Commands
11696 Hakyll.Core.Compiler
117− Hakyll.Core.Compiler.Internal
11897 Hakyll.Core.Configuration
11998 Hakyll.Core.Dependencies
12099 Hakyll.Core.File
121100 Hakyll.Core.Identifier
122101 Hakyll.Core.Identifier.Pattern
123102 Hakyll.Core.Item
124− Hakyll.Core.Logger
125103 Hakyll.Core.Metadata
126− Hakyll.Core.Provider
127− Hakyll.Core.Provider.Metadata
128104 Hakyll.Core.Routes
129105 Hakyll.Core.Rules
130− Hakyll.Core.Rules.Internal
131− Hakyll.Core.Runtime
132− Hakyll.Core.Store
133106 Hakyll.Core.UnixFilter
134− Hakyll.Core.Util.File
135107 Hakyll.Core.Util.String
136108 Hakyll.Core.Writable
137109 Hakyll.Main
@@ -139,67 +111,70 @@ Library
139111 Hakyll.Web.Feed
140112 Hakyll.Web.Html
141113 Hakyll.Web.Html.RelativizeUrls
142− Hakyll.Web.Paginate
143− Hakyll.Web.Redirect
114+ Hakyll.Web.Pandoc
115+ Hakyll.Web.Pandoc.Biblio
116+ Hakyll.Web.Pandoc.FileType
144117 Hakyll.Web.Tags
118+ Hakyll.Web.Paginate
145119 Hakyll.Web.Template
146120 Hakyll.Web.Template.Context
147− Hakyll.Web.Template.Internal
148− Hakyll.Web.Template.Internal.Element
149− Hakyll.Web.Template.Internal.Trim
150121 Hakyll.Web.Template.List
122+ Hakyll.Web.Template.Read
151123
152124 Other-Modules:
153− Data.List.Extended
154− Data.Yaml.Extended
155125 Hakyll.Check
126+ Hakyll.Commands
127+ Hakyll.Core.Compiler.Internal
156128 Hakyll.Core.Compiler.Require
157− Hakyll.Core.Identifier.Pattern.Internal
158129 Hakyll.Core.Item.SomeItem
130+ Hakyll.Core.Logger
131+ Hakyll.Core.Provider
159132 Hakyll.Core.Provider.Internal
133+ Hakyll.Core.Provider.Metadata
160134 Hakyll.Core.Provider.MetadataCache
135+ Hakyll.Core.Rules.Internal
136+ Hakyll.Core.Runtime
137+ Hakyll.Core.Store
138+ Hakyll.Core.Util.File
161139 Hakyll.Core.Util.Parser
140+ Hakyll.Web.Template.Internal
162141 Paths_hakyll
163142
164143 Build-Depends:
165− base >= 4.8 && < 5,
166− binary >= 0.5 && < 0.10,
167− blaze-html >= 0.5 && < 0.10,
168− blaze-markup >= 0.5.1 && < 0.9,
169− bytestring >= 0.9 && < 0.11,
170− containers >= 0.3 && < 0.7,
171− cryptohash >= 0.7 && < 0.12,
172− data-default >= 0.4 && < 0.8,
173− deepseq >= 1.3 && < 1.5,
174− directory >= 1.0 && < 1.4,
175− file-embed >= 0.0.10.1 && < 0.0.12,
176− filepath >= 1.0 && < 1.5,
177− lrucache >= 1.1.1 && < 1.3,
178− mtl >= 1 && < 2.3,
179− network-uri >= 2.6 && < 2.7,
180− optparse-applicative >= 0.12 && < 0.15,
181− parsec >= 3.0 && < 3.2,
182− process >= 1.6 && < 1.7,
183− random >= 1.0 && < 1.2,
184− regex-tdfa >= 1.1 && < 1.3,
185− resourcet >= 1.1 && < 1.3,
186− scientific >= 0.3.4 && < 0.4,
187− tagsoup >= 0.13.1 && < 0.15,
188− template-haskell >= 2.14 && < 2.15,
189− text >= 0.11 && < 1.3,
190− time >= 1.8 && < 1.10,
191− time-locale-compat >= 0.1 && < 0.2,
192− unordered-containers >= 0.2 && < 0.3,
193− vector >= 0.11 && < 0.13,
194− yaml >= 0.8.11 && < 0.12
144+ base >= 4 && < 5,
145+ binary >= 0.5 && < 0.8,
146+ blaze-html >= 0.5 && < 0.7,
147+ blaze-markup >= 0.5.1 && < 0.6,
148+ bytestring >= 0.9 && < 0.11,
149+ cmdargs >= 0.10 && < 0.11,
150+ containers >= 0.3 && < 0.6,
151+ cryptohash >= 0.7 && < 0.11,
152+ data-default >= 0.4 && < 0.6,
153+ deepseq >= 1.3 && < 1.4,
154+ directory >= 1.0 && < 1.3,
155+ filepath >= 1.0 && < 1.4,
156+ lrucache >= 1.1.1 && < 1.2,
157+ mtl >= 1 && < 2.2,
158+ network >= 2.4 && < 2.5,
159+ old-locale >= 1.0 && < 1.1,
160+ old-time >= 1.0 && < 1.2,
161+ pandoc >= 1.12 && < 1.13,
162+ pandoc-citeproc >= 0.1 && < 0.2,
163+ parsec >= 3.0 && < 3.2,
164+ process >= 1.0 && < 1.2,
165+ random >= 1.0 && < 1.1,
166+ regex-base >= 0.93 && < 0.94,
167+ regex-tdfa >= 1.1 && < 1.2,
168+ tagsoup >= 0.12.6 && < 0.13,
169+ text >= 0.11 && < 1.12,
170+ time >= 1.1 && < 1.5
195171
196172 If flag(previewServer)
197173 Build-depends:
198− wai >= 3.2 && < 3.3,
199− warp >= 3.2 && < 3.3,
200− wai-app-static >= 3.1 && < 3.2,
201− http-types >= 0.9 && < 0.13,
202− fsnotify >= 0.2 && < 0.4
174+ snap-core >= 0.6 && < 0.10,
175+ snap-server >= 0.6 && < 0.10,
176+ fsnotify >= 0.0.6 && < 0.1,
177+ system-filepath >= 0.4.6 && <= 0.5
203178 Cpp-options:
204179 -DPREVIEW_SERVER
205180 Other-modules:
@@ -208,7 +183,8 @@ Library
208183
209184 If flag(watchServer)
210185 Build-depends:
211− fsnotify >= 0.2 && < 0.4
186+ fsnotify >= 0.0.6 && < 0.1,
187+ system-filepath >= 0.4.6 && <= 0.5
212188 Cpp-options:
213189 -DWATCH_SERVER
214190 Other-modules:
@@ -216,31 +192,16 @@ Library
216192
217193 If flag(checkExternal)
218194 Build-depends:
219− http-conduit >= 2.2 && < 2.4,
220− http-types >= 0.7 && < 0.13
195+ http-conduit >= 1.8 && < 1.10,
196+ http-types >= 0.7 && < 0.9
221197 Cpp-options:
222198 -DCHECK_EXTERNAL
223199
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−
237200Test-suite hakyll-tests
238− Type: exitcode-stdio-1.0
239− Hs-source-dirs: tests
240− Main-is: TestSuite.hs
241− Ghc-options: -Wall -threaded
242− Default-language: Haskell2010
243−
201+ Type: exitcode-stdio-1.0
202+ Hs-source-dirs: src tests
203+ Main-is: TestSuite.hs
204+ Ghc-options: -Wall
244205 Other-modules:
245206 Hakyll.Core.Dependencies.Tests
246207 Hakyll.Core.Identifier.Tests
@@ -252,76 +213,82 @@ Test-suite hakyll-tests
252213 Hakyll.Core.Store.Tests
253214 Hakyll.Core.UnixFilter.Tests
254215 Hakyll.Core.Util.String.Tests
255− Hakyll.Web.CompressCss.Tests
256216 Hakyll.Web.Html.RelativizeUrls.Tests
257217 Hakyll.Web.Html.Tests
258− Hakyll.Web.Tags.Tests
218+ Hakyll.Web.Pandoc.FileType.Tests
259219 Hakyll.Web.Template.Context.Tests
260220 Hakyll.Web.Template.Tests
261221 TestSuite.Util
262222
263223 Build-Depends:
264− hakyll,
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,
224+ HUnit >= 1.2 && < 1.3,
225+ QuickCheck >= 2.4 && < 2.7,
226+ test-framework >= 0.4 && < 0.9,
227+ test-framework-hunit >= 0.3 && < 0.4,
228+ test-framework-quickcheck2 >= 0.3 && < 0.4,
269229 -- Copy pasted from hakyll dependencies:
270− base >= 4.8 && < 5,
271− bytestring >= 0.9 && < 0.11,
272− containers >= 0.3 && < 0.7,
273− filepath >= 1.0 && < 1.5,
274− text >= 0.11 && < 1.3,
275− unordered-containers >= 0.2 && < 0.3,
276− yaml >= 0.8.11 && < 0.12
230+ base >= 4 && < 5,
231+ binary >= 0.5 && < 0.8,
232+ blaze-html >= 0.5 && < 0.7,
233+ blaze-markup >= 0.5.1 && < 0.6,
234+ bytestring >= 0.9 && < 0.11,
235+ cmdargs >= 0.10 && < 0.11,
236+ containers >= 0.3 && < 0.6,
237+ cryptohash >= 0.7 && < 0.11,
238+ data-default >= 0.4 && < 0.6,
239+ deepseq >= 1.3 && < 1.4,
240+ directory >= 1.0 && < 1.3,
241+ filepath >= 1.0 && < 1.4,
242+ lrucache >= 1.1.1 && < 1.2,
243+ mtl >= 1 && < 2.2,
244+ network >= 2.4 && < 2.5,
245+ old-locale >= 1.0 && < 1.1,
246+ old-time >= 1.0 && < 1.2,
247+ pandoc >= 1.12 && < 1.13,
248+ pandoc-citeproc >= 0.1 && < 0.2,
249+ parsec >= 3.0 && < 3.2,
250+ process >= 1.0 && < 1.2,
251+ random >= 1.0 && < 1.1,
252+ regex-base >= 0.93 && < 0.94,
253+ regex-tdfa >= 1.1 && < 1.2,
254+ tagsoup >= 0.12.6 && < 0.13,
255+ text >= 0.11 && < 1.12,
256+ time >= 1.1 && < 1.5
277257
278258 If flag(previewServer)
259+ Build-depends:
260+ snap-core >= 0.6 && < 0.10,
261+ snap-server >= 0.6 && < 0.10,
262+ fsnotify >= 0.0.6 && < 0.1,
263+ system-filepath >= 0.4.6 && <= 0.5
279264 Cpp-options:
280265 -DPREVIEW_SERVER
266+ Other-modules:
267+ Hakyll.Preview.Poll
268+ Hakyll.Preview.Server
281269
282270 If flag(watchServer)
271+ Build-depends:
272+ fsnotify >= 0.0.6 && < 0.1,
273+ system-filepath >= 0.4.6 && <= 0.5
283274 Cpp-options:
284275 -DWATCH_SERVER
276+ Other-modules:
277+ Hakyll.Preview.Poll
285278
286279 If flag(checkExternal)
280+ Build-depends:
281+ http-conduit >= 1.8 && < 1.10,
282+ http-types >= 0.7 && < 0.9
287283 Cpp-options:
288284 -DCHECK_EXTERNAL
289285
290− If flag(usePandoc)
291− Other-modules:
292− Hakyll.Web.Pandoc.FileType.Tests
293− Cpp-options:
294− -DUSE_PANDOC
295−
296286Executable hakyll-init
297− Main-is: Init.hs
298− Ghc-options: -Wall -threaded
299− Hs-source-dirs: src
300− Default-language: Haskell2010
301−
302− Other-modules:
303− Paths_hakyll
287+ Ghc-options: -Wall
288+ Hs-source-dirs: src
289+ Main-is: Hakyll/Init.hs
304290
305291 Build-depends:
306− hakyll,
307292 base >= 4 && < 5,
308− directory >= 1.0 && < 1.4,
309− filepath >= 1.0 && < 1.5
310−
311−Executable hakyll-website
312− Main-is: site.hs
313− Ghc-options: -Wall -threaded
314− Hs-source-dirs: web
315− Default-language: Haskell2010
316−
317− If flag(buildWebsite)
318− Buildable: True
319− Else
320− Buildable: False
321−
322− Build-depends:
323− hakyll,
324− base >= 4 && < 5,
325− directory >= 1.0 && < 1.4,
326− filepath >= 1.0 && < 1.5,
327− pandoc >= 2.0.5 && < 2.8
293+ directory >= 1.0 && < 1.3,
294+ filepath >= 1.0 && < 1.4
lib/Data/List/Extended.hsdeleted−15
@@ -1,15 +0,0 @@
1−module Data.List.Extended
2− ( module Data.List
3− , breakWhen
4− ) where
5−
6−import Data.List
7−
8−-- | Like 'break', but can act on the entire tail of the list.
9−breakWhen :: ([a] -> Bool) -> [a] -> ([a], [a])
10−breakWhen predicate = go []
11− where
12− go buf [] = (reverse buf, [])
13− go buf (x : xs)
14− | predicate (x : xs) = (reverse buf, x : xs)
15− | otherwise = go (x : buf) xs
lib/Data/Yaml/Extended.hsdeleted−24
@@ -1,24 +0,0 @@
1−module Data.Yaml.Extended
2− ( module Data.Yaml
3− , toString
4− , toList
5− ) where
6−
7−import qualified Data.Text as T
8−import qualified Data.Vector as V
9−import Data.Yaml
10−import Data.Scientific
11−
12−toString :: Value -> Maybe String
13−toString (String t) = Just (T.unpack t)
14−toString (Bool True) = Just "true"
15−toString (Bool False) = Just "false"
16−-- | Make sure that numeric fields containing integer numbers are shown as
17−-- | integers (i.e., "42" instead of "42.0").
18−toString (Number d) | isInteger d = Just (formatScientific Fixed (Just 0) d)
19− | otherwise = Just (show d)
20−toString _ = Nothing
21−
22−toList :: Value -> Maybe [Value]
23−toList (Array a) = Just (V.toList a)
24−toList _ = Nothing
lib/Hakyll/Check.hsdeleted−302
@@ -1,302 +0,0 @@
1−--------------------------------------------------------------------------------
2−{-# LANGUAGE CPP #-}
3−{-# LANGUAGE OverloadedStrings #-}
4−module Hakyll.Check
5− ( Check (..)
6− , check
7− ) where
8−
9−
10−--------------------------------------------------------------------------------
11−import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar,
12− readMVar)
13−import Control.Exception (SomeAsyncException (..),
14− SomeException (..), throw, try)
15−import Control.Monad (foldM, forM_)
16−import Control.Monad.Reader (ReaderT, ask, runReaderT)
17−import Control.Monad.State (StateT, get, modify, runStateT)
18−import Control.Monad.Trans (liftIO)
19−import Control.Monad.Trans.Resource (runResourceT)
20−import Data.List (isPrefixOf)
21−import qualified Data.Map.Lazy as Map
22−#if MIN_VERSION_base(4,9,0)
23−import Data.Semigroup (Semigroup (..))
24−#endif
25−import Network.URI (unEscapeString)
26−import System.Directory (doesDirectoryExist,
27− doesFileExist)
28−import System.Exit (ExitCode (..))
29−import System.FilePath (takeDirectory, takeExtension,
30− (</>))
31−import qualified Text.HTML.TagSoup as TS
32−
33−
34−--------------------------------------------------------------------------------
35−#ifdef CHECK_EXTERNAL
36−import Data.List (intercalate)
37−import Data.Typeable (cast)
38−import Data.Version (versionBranch)
39−import GHC.Exts (fromString)
40−import qualified Network.HTTP.Conduit as Http
41−import qualified Network.HTTP.Types as Http
42−import qualified Paths_hakyll as Paths_hakyll
43−#endif
44−
45−
46−--------------------------------------------------------------------------------
47−import Hakyll.Core.Configuration
48−import Hakyll.Core.Logger (Logger)
49−import qualified Hakyll.Core.Logger as Logger
50−import Hakyll.Core.Util.File
51−import Hakyll.Web.Html
52−
53−
54−--------------------------------------------------------------------------------
55−data Check = All | InternalLinks
56− deriving (Eq, Ord, Show)
57−
58−
59−--------------------------------------------------------------------------------
60−check :: Configuration -> Logger -> Check -> IO ExitCode
61−check config logger check' = do
62− ((), state) <- runChecker checkDestination config logger check'
63− failed <- countFailedLinks state
64− return $ if failed > 0 then ExitFailure 1 else ExitSuccess
65−
66−
67−--------------------------------------------------------------------------------
68−countFailedLinks :: CheckerState -> IO Int
69−countFailedLinks state = foldM addIfFailure 0 (Map.elems state)
70− where addIfFailure failures mvar = do
71− checkerWrite <- readMVar mvar
72− return $ failures + checkerFaulty checkerWrite
73−
74−
75−--------------------------------------------------------------------------------
76−data CheckerRead = CheckerRead
77− { checkerConfig :: Configuration
78− , checkerLogger :: Logger
79− , checkerCheck :: Check
80− }
81−
82−
83−--------------------------------------------------------------------------------
84−data CheckerWrite = CheckerWrite
85− { checkerFaulty :: Int
86− , checkerOk :: Int
87− } deriving (Show)
88−
89−
90−--------------------------------------------------------------------------------
91−#if MIN_VERSION_base(4,9,0)
92−instance Semigroup CheckerWrite where
93− (<>) (CheckerWrite f1 o1) (CheckerWrite f2 o2) =
94− CheckerWrite (f1 + f2) (o1 + o2)
95−
96−instance Monoid CheckerWrite where
97− mempty = CheckerWrite 0 0
98− mappend = (<>)
99−#else
100−instance Monoid CheckerWrite where
101− mempty = CheckerWrite 0 0
102− mappend (CheckerWrite f1 o1) (CheckerWrite f2 o2) =
103− CheckerWrite (f1 + f2) (o1 + o2)
104−#endif
105−
106−
107−--------------------------------------------------------------------------------
108−type CheckerState = Map.Map URL (MVar CheckerWrite)
109−
110−
111−--------------------------------------------------------------------------------
112−type Checker a = ReaderT CheckerRead (StateT CheckerState IO) a
113−
114−
115−--------------------------------------------------------------------------------
116−type URL = String
117−
118−
119−--------------------------------------------------------------------------------
120−runChecker :: Checker a -> Configuration -> Logger -> Check
121− -> IO (a, CheckerState)
122−runChecker checker config logger check' = do
123− let read' = CheckerRead
124− { checkerConfig = config
125− , checkerLogger = logger
126− , checkerCheck = check'
127− }
128− Logger.flush logger
129− runStateT (runReaderT checker read') Map.empty
130−
131−
132−--------------------------------------------------------------------------------
133−checkDestination :: Checker ()
134−checkDestination = do
135− config <- checkerConfig <$> ask
136− files <- liftIO $ getRecursiveContents
137− (const $ return False) (destinationDirectory config)
138−
139− let htmls =
140− [ destinationDirectory config </> file
141− | file <- files
142− , takeExtension file == ".html"
143− ]
144−
145− forM_ htmls checkFile
146−
147−
148−--------------------------------------------------------------------------------
149−checkFile :: FilePath -> Checker ()
150−checkFile filePath = do
151− logger <- checkerLogger <$> ask
152− contents <- liftIO $ readFile filePath
153− Logger.header logger $ "Checking file " ++ filePath
154−
155− let urls = getUrls $ TS.parseTags contents
156− forM_ urls $ \url -> do
157− Logger.debug logger $ "Checking link " ++ url
158− m <- liftIO newEmptyMVar
159− checkUrlIfNeeded filePath (canonicalizeUrl url) m
160− where
161− -- Check scheme-relative links
162− canonicalizeUrl url = if schemeRelative url then "http:" ++ url else url
163− schemeRelative = isPrefixOf "//"
164−
165−
166−--------------------------------------------------------------------------------
167−checkUrlIfNeeded :: FilePath -> URL -> MVar CheckerWrite -> Checker ()
168−checkUrlIfNeeded filepath url m = do
169− logger <- checkerLogger <$> ask
170− needsCheck <- (== All) . checkerCheck <$> ask
171− checked <- (url `Map.member`) <$> get
172− if not needsCheck || checked
173− then Logger.debug logger "Already checked, skipping"
174− else do modify $ Map.insert url m
175− checkUrl filepath url
176−
177−
178−--------------------------------------------------------------------------------
179−checkUrl :: FilePath -> URL -> Checker ()
180−checkUrl filePath url
181− | isExternal url = checkExternalUrl url
182− | hasProtocol url = skip url $ Just "Unknown protocol, skipping"
183− | otherwise = checkInternalUrl filePath url
184− where
185− validProtoChars = ['A'..'Z'] ++ ['a'..'z'] ++ ['0'..'9'] ++ "+-."
186− hasProtocol str = case break (== ':') str of
187− (proto, ':' : _) -> all (`elem` validProtoChars) proto
188− _ -> False
189−
190−
191−--------------------------------------------------------------------------------
192−ok :: URL -> Checker ()
193−ok url = putCheckResult url mempty {checkerOk = 1}
194−
195−
196−--------------------------------------------------------------------------------
197−skip :: URL -> Maybe String -> Checker ()
198−skip url maybeReason = do
199− logger <- checkerLogger <$> ask
200− case maybeReason of
201− Nothing -> return ()
202− Just reason -> Logger.debug logger reason
203− putCheckResult url mempty {checkerOk = 1}
204−
205−
206−--------------------------------------------------------------------------------
207−faulty :: URL -> Maybe String -> Checker ()
208−faulty url reason = do
209− logger <- checkerLogger <$> ask
210− Logger.error logger $ "Broken link to " ++ show url ++ explanation
211− putCheckResult url mempty {checkerFaulty = 1}
212− where
213− formatExplanation = (" (" ++) . (++ ")")
214− explanation = maybe "" formatExplanation reason
215−
216−
217−--------------------------------------------------------------------------------
218−putCheckResult :: URL -> CheckerWrite -> Checker ()
219−putCheckResult url result = do
220− state <- get
221− let maybeMVar = Map.lookup url state
222− case maybeMVar of
223− Just m -> liftIO $ putMVar m result
224− Nothing -> do
225− logger <- checkerLogger <$> ask
226− Logger.debug logger "Failed to find existing entry for checked URL"
227−
228−
229−--------------------------------------------------------------------------------
230−checkInternalUrl :: FilePath -> URL -> Checker ()
231−checkInternalUrl base url = case url' of
232− "" -> ok url
233− _ -> do
234− config <- checkerConfig <$> ask
235− let dest = destinationDirectory config
236− dir = takeDirectory base
237− filePath
238− | "/" `isPrefixOf` url' = dest ++ url'
239− | otherwise = dir </> url'
240−
241− exists <- checkFileExists filePath
242− if exists then ok url else faulty url Nothing
243− where
244− url' = stripFragments $ unEscapeString url
245−
246−
247−--------------------------------------------------------------------------------
248−checkExternalUrl :: URL -> Checker ()
249−#ifdef CHECK_EXTERNAL
250−checkExternalUrl url = do
251− result <- requestExternalUrl url
252− case result of
253− Left (SomeException e) ->
254− case (cast e :: Maybe SomeAsyncException) of
255− Just ae -> throw ae
256− _ -> faulty url (Just $ showException e)
257− Right _ -> ok url
258− where
259− -- Convert exception to a concise form
260− showException e = case cast e of
261− Just (Http.HttpExceptionRequest _ e') -> show e'
262− _ -> head $ words $ show e
263−
264−requestExternalUrl :: URL -> Checker (Either SomeException Bool)
265−requestExternalUrl url = liftIO $ try $ do
266− mgr <- Http.newManager Http.tlsManagerSettings
267− runResourceT $ do
268− request <- Http.parseRequest url
269− response <- Http.http (settings request) mgr
270− let code = Http.statusCode (Http.responseStatus response)
271− return $ code >= 200 && code < 300
272− where
273− -- Add additional request info
274− settings r = r
275− { Http.method = "HEAD"
276− , Http.redirectCount = 10
277− , Http.requestHeaders = ("User-Agent", ua) : Http.requestHeaders r
278− }
279−
280− -- Nice user agent info
281− ua = fromString $ "hakyll-check/" ++
282− (intercalate "." $ map show $ versionBranch Paths_hakyll.version)
283−#else
284−checkExternalUrl url = skip url Nothing
285−#endif
286−
287−
288−--------------------------------------------------------------------------------
289−-- | Wraps doesFileExist, also checks for index.html
290−checkFileExists :: FilePath -> Checker Bool
291−checkFileExists filePath = liftIO $ do
292− file <- doesFileExist filePath
293− dir <- doesDirectoryExist filePath
294− case (file, dir) of
295− (True, _) -> return True
296− (_, True) -> doesFileExist $ filePath </> "index.html"
297− _ -> return False
298−
299−
300−--------------------------------------------------------------------------------
301−stripFragments :: String -> String
302−stripFragments = takeWhile (not . flip elem ['?', '#'])
lib/Hakyll/Core/Identifier/Pattern/Internal.hsdeleted−92
@@ -1,92 +0,0 @@
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/Metadata.hsdeleted−138
@@ -1,138 +0,0 @@
1−--------------------------------------------------------------------------------
2−module Hakyll.Core.Metadata
3− ( Metadata
4− , lookupString
5− , lookupStringList
6−
7− , MonadMetadata (..)
8− , getMetadataField
9− , getMetadataField'
10− , makePatternDependency
11−
12− , BinaryMetadata (..)
13− ) where
14−
15−
16−--------------------------------------------------------------------------------
17−import Control.Arrow (second)
18−import Control.Monad (forM)
19−import Data.Binary (Binary (..), getWord8,
20− putWord8, Get)
21−import qualified Data.HashMap.Strict as HMS
22−import qualified Data.Set as S
23−import qualified Data.Text as T
24−import qualified Data.Vector as V
25−import qualified Data.Yaml.Extended as Yaml
26−import Hakyll.Core.Dependencies
27−import Hakyll.Core.Identifier
28−import Hakyll.Core.Identifier.Pattern
29−
30−
31−--------------------------------------------------------------------------------
32−type Metadata = Yaml.Object
33−
34−
35−--------------------------------------------------------------------------------
36−lookupString :: String -> Metadata -> Maybe String
37−lookupString key meta = HMS.lookup (T.pack key) meta >>= Yaml.toString
38−
39−
40−--------------------------------------------------------------------------------
41−lookupStringList :: String -> Metadata -> Maybe [String]
42−lookupStringList key meta =
43− HMS.lookup (T.pack key) meta >>= Yaml.toList >>= mapM Yaml.toString
44−
45−
46−--------------------------------------------------------------------------------
47−class Monad m => MonadMetadata m where
48− getMetadata :: Identifier -> m Metadata
49− getMatches :: Pattern -> m [Identifier]
50−
51− getAllMetadata :: Pattern -> m [(Identifier, Metadata)]
52− getAllMetadata pattern = do
53− matches' <- getMatches pattern
54− forM matches' $ \id' -> do
55− metadata <- getMetadata id'
56− return (id', metadata)
57−
58−
59−--------------------------------------------------------------------------------
60−getMetadataField :: MonadMetadata m => Identifier -> String -> m (Maybe String)
61−getMetadataField identifier key = do
62− metadata <- getMetadata identifier
63− return $ lookupString key metadata
64−
65−
66−--------------------------------------------------------------------------------
67−-- | Version of 'getMetadataField' which throws an error if the field does not
68−-- exist.
69−getMetadataField' :: MonadMetadata m => Identifier -> String -> m String
70−getMetadataField' identifier key = do
71− field <- getMetadataField identifier key
72− case field of
73− Just v -> return v
74− Nothing -> fail $ "Hakyll.Core.Metadata.getMetadataField': " ++
75− "Item " ++ show identifier ++ " has no metadata field " ++ show key
76−
77−
78−--------------------------------------------------------------------------------
79−makePatternDependency :: MonadMetadata m => Pattern -> m Dependency
80−makePatternDependency pattern = do
81− matches' <- getMatches pattern
82− return $ PatternDependency pattern (S.fromList matches')
83−
84−
85−--------------------------------------------------------------------------------
86−-- | Newtype wrapper for serialization.
87−newtype BinaryMetadata = BinaryMetadata
88− {unBinaryMetadata :: Metadata}
89−
90−
91−instance Binary BinaryMetadata where
92− put (BinaryMetadata obj) = put (BinaryYaml $ Yaml.Object obj)
93− get = do
94− BinaryYaml (Yaml.Object obj) <- get
95− return $ BinaryMetadata obj
96−
97−
98−--------------------------------------------------------------------------------
99−newtype BinaryYaml = BinaryYaml {unBinaryYaml :: Yaml.Value}
100−
101−
102−--------------------------------------------------------------------------------
103−instance Binary BinaryYaml where
104− put (BinaryYaml yaml) = case yaml of
105− Yaml.Object obj -> do
106− putWord8 0
107− let list :: [(T.Text, BinaryYaml)]
108− list = map (second BinaryYaml) $ HMS.toList obj
109− put list
110−
111− Yaml.Array arr -> do
112− putWord8 1
113− let list = map BinaryYaml (V.toList arr) :: [BinaryYaml]
114− put list
115−
116− Yaml.String s -> putWord8 2 >> put s
117− Yaml.Number n -> putWord8 3 >> put n
118− Yaml.Bool b -> putWord8 4 >> put b
119− Yaml.Null -> putWord8 5
120−
121− get = do
122− tag <- getWord8
123− case tag of
124− 0 -> do
125− list <- get :: Get [(T.Text, BinaryYaml)]
126− return $ BinaryYaml $ Yaml.Object $
127− HMS.fromList $ map (second unBinaryYaml) list
128−
129− 1 -> do
130− list <- get :: Get [BinaryYaml]
131− return $ BinaryYaml $
132− Yaml.Array $ V.fromList $ map unBinaryYaml list
133−
134− 2 -> BinaryYaml . Yaml.String <$> get
135− 3 -> BinaryYaml . Yaml.Number <$> get
136− 4 -> BinaryYaml . Yaml.Bool <$> get
137− 5 -> return $ BinaryYaml Yaml.Null
138− _ -> fail "Data.Binary.get: Invalid Binary Metadata"
lib/Hakyll/Core/Provider/Metadata.hsdeleted−152
@@ -1,152 +0,0 @@
1−--------------------------------------------------------------------------------
2−-- | Internal module to parse metadata
3−{-# LANGUAGE BangPatterns #-}
4−{-# LANGUAGE RecordWildCards #-}
5−module Hakyll.Core.Provider.Metadata
6− ( loadMetadata
7− , parsePage
8−
9− , MetadataException (..)
10− ) where
11−
12−
13−--------------------------------------------------------------------------------
14−import Control.Arrow (second)
15−import Control.Exception (Exception, throwIO)
16−import Control.Monad (guard)
17−import qualified Data.ByteString as B
18−import qualified Data.ByteString.Char8 as BC
19−import Data.List.Extended (breakWhen)
20−import qualified Data.Map as M
21−import Data.Maybe (fromMaybe)
22−import Data.Monoid ((<>))
23−import qualified Data.Text as T
24−import qualified Data.Text.Encoding as T
25−import qualified Data.Yaml as Yaml
26−import Hakyll.Core.Identifier
27−import Hakyll.Core.Metadata
28−import Hakyll.Core.Provider.Internal
29−import System.IO as IO
30−import System.IO.Error (modifyIOError, ioeSetLocation)
31−
32−
33−--------------------------------------------------------------------------------
34−loadMetadata :: Provider -> Identifier -> IO (Metadata, Maybe String)
35−loadMetadata p identifier = do
36− hasHeader <- probablyHasMetadataHeader fp
37− (md, body) <- if hasHeader
38− then second Just <$> loadMetadataHeader fp
39− else return (mempty, Nothing)
40−
41− emd <- case mi of
42− Nothing -> return mempty
43− Just mi' -> loadMetadataFile $ resourceFilePath p mi'
44−
45− return (md <> emd, body)
46− where
47− normal = setVersion Nothing identifier
48− fp = resourceFilePath p identifier
49− mi = M.lookup normal (providerFiles p) >>= resourceInfoMetadata
50−
51−
52−--------------------------------------------------------------------------------
53−loadMetadataHeader :: FilePath -> IO (Metadata, String)
54−loadMetadataHeader fp = do
55− fileContent <- modifyIOError (`ioeSetLocation` "loadMetadataHeader") $ readFile fp
56− case parsePage fileContent of
57− Right x -> return x
58− Left err -> throwIO $ MetadataException fp err
59−
60−
61−--------------------------------------------------------------------------------
62−loadMetadataFile :: FilePath -> IO Metadata
63−loadMetadataFile fp = do
64− fileContent <- modifyIOError (`ioeSetLocation` "loadMetadataFile") $ B.readFile fp
65− let errOrMeta = Yaml.decodeEither' fileContent
66− either (fail . show) return errOrMeta
67−
68−
69−--------------------------------------------------------------------------------
70−-- | Check if a file "probably" has a metadata header. The main goal of this is
71−-- to exclude binary files (which are unlikely to start with "---").
72−probablyHasMetadataHeader :: FilePath -> IO Bool
73−probablyHasMetadataHeader fp = do
74− handle <- IO.openFile fp IO.ReadMode
75− bs <- BC.hGet handle 1024
76− IO.hClose handle
77− return $ isMetadataHeader bs
78− where
79− isMetadataHeader bs =
80− let pre = BC.takeWhile (\x -> x /= '\n' && x /= '\r') bs
81− in BC.length pre >= 3 && BC.all (== '-') pre
82−
83−
84−--------------------------------------------------------------------------------
85−-- | Parse the page metadata and body.
86−splitMetadata :: String -> (Maybe String, String)
87−splitMetadata str0 = fromMaybe (Nothing, str0) $ do
88− guard $ leading >= 3
89− let !str1 = drop leading str0
90− guard $ all isNewline (take 1 str1)
91− let !(!meta, !content0) = breakWhen isTrailing str1
92− guard $ not $ null content0
93− let !content1 = drop (leading + 1) content0
94− !content2 = dropWhile isNewline $ dropWhile isInlineSpace content1
95− -- Adding this newline fixes the line numbers reported by the YAML parser.
96− -- It's a bit ugly but it works.
97− return (Just ('\n' : meta), content2)
98− where
99− -- Parse the leading "---"
100− !leading = length $ takeWhile (== '-') str0
101−
102− -- Predicate to recognize the trailing "---" or "..."
103− isTrailing [] = False
104− isTrailing (x : xs) =
105− isNewline x && length (takeWhile isDash xs) == leading
106−
107− -- Characters
108− isNewline c = c == '\n' || c == '\r'
109− isDash c = c == '-' || c == '.'
110− isInlineSpace c = c == '\t' || c == ' '
111−
112−
113−--------------------------------------------------------------------------------
114−parseMetadata :: String -> Either Yaml.ParseException Metadata
115−parseMetadata = Yaml.decodeEither' . T.encodeUtf8 . T.pack
116−
117−
118−--------------------------------------------------------------------------------
119−parsePage :: String -> Either Yaml.ParseException (Metadata, String)
120−parsePage fileContent = case mbMetaBlock of
121− Nothing -> return (mempty, content)
122− Just metaBlock -> case parseMetadata metaBlock of
123− Left err -> Left err
124− Right meta -> return (meta, content)
125− where
126− !(!mbMetaBlock, !content) = splitMetadata fileContent
127−
128−
129−--------------------------------------------------------------------------------
130−-- | Thrown in the IO monad if things go wrong. Provides a nice-ish error
131−-- message.
132−data MetadataException = MetadataException FilePath Yaml.ParseException
133−
134−
135−--------------------------------------------------------------------------------
136−instance Exception MetadataException
137−
138−
139−--------------------------------------------------------------------------------
140−instance Show MetadataException where
141− show (MetadataException fp err) =
142− fp ++ ": " ++ Yaml.prettyPrintParseException err ++ hint
143−
144− where
145− hint = case err of
146− Yaml.InvalidYaml (Just (Yaml.YamlParseException {..}))
147− | yamlProblem == problem -> "\n" ++
148− "Hint: if the metadata value contains characters such\n" ++
149− "as ':' or '-', try enclosing it in quotes."
150− _ -> ""
151−
152− problem = "mapping values are not allowed in this context"
lib/Hakyll/Main.hsdeleted−180
@@ -1,180 +0,0 @@
1−--------------------------------------------------------------------------------
2−-- | Module providing the main hakyll function and command-line argument parsing
3−{-# LANGUAGE CPP #-}
4−
5−module Hakyll.Main
6− ( hakyll
7− , hakyllWith
8− , hakyllWithArgs
9− , hakyllWithExitCode
10− , hakyllWithExitCodeAndArgs
11− , Options(..)
12− , Command(..)
13− ) where
14−
15−
16−--------------------------------------------------------------------------------
17−import System.Environment (getProgName)
18−import System.Exit (ExitCode (ExitSuccess), exitWith)
19−import System.IO.Unsafe (unsafePerformIO)
20−
21−
22−--------------------------------------------------------------------------------
23−import Data.Monoid ((<>))
24−import qualified Options.Applicative as OA
25−
26−
27−--------------------------------------------------------------------------------
28−import qualified Hakyll.Check as Check
29−import qualified Hakyll.Commands as Commands
30−import qualified Hakyll.Core.Configuration as Config
31−import qualified Hakyll.Core.Logger as Logger
32−import Hakyll.Core.Rules
33−
34−
35−--------------------------------------------------------------------------------
36−-- | This usually is the function with which the user runs the hakyll compiler
37−hakyll :: Rules a -> IO ()
38−hakyll = hakyllWith Config.defaultConfiguration
39−
40−--------------------------------------------------------------------------------
41−-- | A variant of 'hakyll' which allows the user to specify a custom
42−-- configuration
43−hakyllWith :: Config.Configuration -> Rules a -> IO ()
44−hakyllWith conf rules = hakyllWithExitCode conf rules >>= exitWith
45−
46−--------------------------------------------------------------------------------
47−-- | A variant of 'hakyll' which returns an 'ExitCode'
48−hakyllWithExitCode :: Config.Configuration -> Rules a -> IO ExitCode
49−hakyllWithExitCode conf rules = do
50− args <- defaultParser conf
51− hakyllWithExitCodeAndArgs conf args rules
52−
53−--------------------------------------------------------------------------------
54−-- | A variant of 'hakyll' which expects a 'Configuration' and command-line
55−-- 'Options'. This gives freedom to implement your own parsing.
56−hakyllWithArgs :: Config.Configuration -> Options -> Rules a -> IO ()
57−hakyllWithArgs conf args rules =
58− hakyllWithExitCodeAndArgs conf args rules >>= exitWith
59−
60−--------------------------------------------------------------------------------
61−hakyllWithExitCodeAndArgs :: Config.Configuration ->
62− Options -> Rules a -> IO ExitCode
63−hakyllWithExitCodeAndArgs conf args rules = do
64− let args' = optCommand args
65− verbosity' = if verbosity args then Logger.Debug else Logger.Message
66− check =
67− if internal_links args' then Check.InternalLinks else Check.All
68−
69− logger <- Logger.new verbosity'
70− invokeCommands args' conf check logger rules
71−
72−--------------------------------------------------------------------------------
73−defaultParser :: Config.Configuration -> IO Options
74−defaultParser conf =
75− OA.customExecParser (OA.prefs OA.showHelpOnError)
76− (OA.info (OA.helper <*> optionParser conf)
77− (OA.fullDesc <> OA.progDesc
78− (progName ++ " - Static site compiler created with Hakyll")))
79−
80−
81−--------------------------------------------------------------------------------
82−invokeCommands :: Command -> Config.Configuration ->
83− Check.Check -> Logger.Logger -> Rules a -> IO ExitCode
84−invokeCommands args conf check logger rules =
85− case args of
86− Build -> Commands.build conf logger rules
87− Check _ -> Commands.check conf logger check
88− Clean -> Commands.clean conf logger >> ok
89− Deploy -> Commands.deploy conf
90− Preview p -> Commands.preview conf logger rules p >> ok
91− Rebuild -> Commands.rebuild conf logger rules
92− Server _ _ -> Commands.server conf logger (host args) (port args) >> ok
93− Watch _ p s -> Commands.watch conf logger (host args) p (not s) rules >> ok
94− where
95− ok = return ExitSuccess
96−
97−
98−--------------------------------------------------------------------------------
99−
100−-- | The parsed command-line options.
101−data Options = Options {verbosity :: Bool, optCommand :: Command}
102− deriving (Show)
103−
104−-- | The command to run.
105−data Command
106− = Build
107− -- ^ Generate the site.
108− | Check {internal_links :: Bool}
109− -- ^ Validate the site output.
110− | Clean
111− -- ^ Clean up and remove cache.
112− | Deploy
113− -- ^ Upload/deploy your site.
114− | Preview {port :: Int}
115− -- ^ [DEPRECATED] Please use the watch command.
116− | Rebuild
117− -- ^ Clean and build again.
118− | Server {host :: String, port :: Int}
119− -- ^ Start a preview server.
120− | Watch {host :: String, port :: Int, no_server :: Bool }
121− -- ^ Autocompile on changes and start a preview server.
122− deriving (Show)
123−
124−{-# DEPRECATED Preview "Use Watch instead." #-}
125−
126−optionParser :: Config.Configuration -> OA.Parser Options
127−optionParser conf = Options <$> verboseParser <*> commandParser conf
128− where
129− verboseParser = OA.switch (OA.long "verbose" <> OA.short 'v' <> OA.help "Run in verbose mode")
130−
131−
132−commandParser :: Config.Configuration -> OA.Parser Command
133−commandParser conf = OA.subparser $ foldr ((<>) . produceCommand) mempty commands
134− where
135− portParser = OA.option OA.auto (OA.long "port" <> OA.help "Port to listen on" <> OA.value (Config.previewPort conf))
136− hostParser = OA.strOption (OA.long "host" <> OA.help "Host to bind on" <> OA.value (Config.previewHost conf))
137−
138− produceCommand (c,a,b) = OA.command c (OA.info (OA.helper <*> a) (b))
139−
140− commands =
141− [ ( "build"
142− , pure Build
143− , OA.fullDesc <> OA.progDesc "Generate the site"
144− )
145− , ( "check"
146− , pure Check <*> OA.switch (OA.long "internal-links" <> OA.help "Check internal links only")
147− , OA.fullDesc <> OA.progDesc "Validate the site output"
148− )
149− , ( "clean"
150− , pure Clean
151− , OA.fullDesc <> OA.progDesc "Clean up and remove cache"
152− )
153− , ( "deploy"
154− , pure Deploy
155− , OA.fullDesc <> OA.progDesc "Upload/deploy your site"
156− )
157− , ( "preview"
158− , pure Preview <*> portParser
159− , OA.fullDesc <> OA.progDesc "[DEPRECATED] Please use the watch command"
160− )
161− , ( "rebuild"
162− , pure Rebuild
163− , OA.fullDesc <> OA.progDesc "Clean and build again"
164− )
165− , ( "server"
166− , pure Server <*> hostParser <*> portParser
167− , OA.fullDesc <> OA.progDesc "Start a preview server"
168− )
169− , ( "watch"
170− , pure Watch <*> hostParser <*> portParser <*> OA.switch (OA.long "no-server" <> OA.help "Disable the built-in web server")
171− , OA.fullDesc <> OA.progDesc "Autocompile on changes and start a preview server. You can watch and recompile without running a server with --no-server."
172− )
173− ]
174−
175−
176−--------------------------------------------------------------------------------
177−-- | This is necessary because not everyone calls their program the same...
178−progName :: String
179−progName = unsafePerformIO getProgName
180−{-# NOINLINE progName #-}
lib/Hakyll/Preview/Poll.hsdeleted−109
@@ -1,109 +0,0 @@
1−--------------------------------------------------------------------------------
2−{-# LANGUAGE CPP #-}
3−module Hakyll.Preview.Poll
4− ( watchUpdates
5− ) where
6−
7−
8−--------------------------------------------------------------------------------
9−import Control.Concurrent (forkIO)
10−import Control.Concurrent.MVar (newEmptyMVar, takeMVar,
11− tryPutMVar)
12−import Control.Exception (AsyncException, fromException,
13− handle, throw)
14−import Control.Monad (forever, void, when)
15−import System.Directory (canonicalizePath)
16−import System.FilePath (pathSeparators)
17−import qualified System.FSNotify as FSNotify
18−
19−#ifdef mingw32_HOST_OS
20−import Control.Concurrent (threadDelay)
21−import Control.Exception (IOException, throw, try)
22−import System.Directory (doesFileExist)
23−import System.Exit (exitFailure)
24−import System.FilePath ((</>))
25−import System.IO (Handle, IOMode (ReadMode),
26− hClose, openFile)
27−import System.IO.Error (isPermissionError)
28−#endif
29−
30−
31−--------------------------------------------------------------------------------
32−import Hakyll.Core.Configuration
33−import Hakyll.Core.Identifier
34−import Hakyll.Core.Identifier.Pattern
35−
36−
37−--------------------------------------------------------------------------------
38−-- | A thread that watches for updates in a 'providerDirectory' and recompiles
39−-- a site as soon as any changes occur
40−watchUpdates :: Configuration -> IO Pattern -> IO ()
41−watchUpdates conf update = do
42− let providerDir = providerDirectory conf
43− shouldBuild <- newEmptyMVar
44− pattern <- update
45− fullProviderDir <- canonicalizePath $ providerDirectory conf
46− manager <- FSNotify.startManager
47−
48− let allowed event = do
49− -- Absolute path of the changed file. This must be inside provider
50− -- dir, since that's the only dir we're watching.
51− let path = FSNotify.eventPath event
52− relative = dropWhile (`elem` pathSeparators) $
53− drop (length fullProviderDir) path
54− identifier = fromFilePath relative
55−
56− shouldIgnore <- shouldIgnoreFile conf path
57− return $ not shouldIgnore && matches pattern identifier
58−
59− -- This thread continually watches the `shouldBuild` MVar and builds
60− -- whenever a value is present.
61− _ <- forkIO $ forever $ do
62− event <- takeMVar shouldBuild
63− handle
64− (\e -> case fromException e of
65− Nothing -> putStrLn (show e)
66− Just async -> throw (async :: AsyncException))
67− (update' event providerDir)
68−
69− -- Send an event whenever something occurs so that the thread described
70− -- above will do a build.
71− void $ FSNotify.watchTree manager providerDir (not . isRemove) $ \event -> do
72− allowed' <- allowed event
73− when allowed' $ void $ tryPutMVar shouldBuild event
74− where
75−#ifndef mingw32_HOST_OS
76− update' _ _ = void update
77−#else
78− update' event provider = do
79− let path = provider </> FSNotify.eventPath event
80− -- on windows, a 'Modified' event is also sent on file deletion
81− fileExists <- doesFileExist path
82−
83− when fileExists . void $ waitOpen path ReadMode (\_ -> update) 10
84−
85− -- continuously attempts to open the file in between sleep intervals
86− -- handler is run only once it is able to open the file
87− waitOpen :: FilePath -> IOMode -> (Handle -> IO r) -> Integer -> IO r
88− waitOpen _ _ _ 0 = do
89− putStrLn "[ERROR] Failed to retrieve modified file for regeneration"
90− exitFailure
91− waitOpen path mode handler retries = do
92− res <- try $ openFile path mode :: IO (Either IOException Handle)
93− case res of
94− Left ex -> if isPermissionError ex
95− then do
96− threadDelay 100000
97− waitOpen path mode handler (retries - 1)
98− else throw ex
99− Right h -> do
100− handled <- handler h
101− hClose h
102− return handled
103−#endif
104−
105−
106−--------------------------------------------------------------------------------
107−isRemove :: FSNotify.Event -> Bool
108−isRemove (FSNotify.Removed {}) = True
109−isRemove _ = False
lib/Hakyll/Preview/Server.hsdeleted−35
@@ -1,35 +0,0 @@
1−--------------------------------------------------------------------------------
2−-- | Implements a basic static file server for previewing options
3−{-# LANGUAGE OverloadedStrings #-}
4−module Hakyll.Preview.Server
5− ( staticServer
6− ) where
7−
8−
9−--------------------------------------------------------------------------------
10−import Data.String
11−import qualified Network.Wai.Handler.Warp as Warp
12−import qualified Network.Wai.Application.Static as Static
13−import qualified Network.Wai as Wai
14−import Network.HTTP.Types.Status (Status)
15−
16−--------------------------------------------------------------------------------
17−import Hakyll.Core.Logger (Logger)
18−import qualified Hakyll.Core.Logger as Logger
19−
20−staticServer :: Logger -- ^ Logger
21− -> FilePath -- ^ Directory to serve
22− -> String -- ^ Host to bind on
23− -> Int -- ^ Port to listen on
24− -> IO () -- ^ Blocks forever
25−staticServer logger directory host port = do
26− Logger.header logger $ "Listening on http://" ++ host ++ ":" ++ show port
27− Warp.runSettings warpSettings $
28− Static.staticApp (Static.defaultFileServerSettings directory)
29− where
30− warpSettings = Warp.setLogger noLog
31− $ Warp.setHost (fromString host)
32− $ Warp.setPort port Warp.defaultSettings
33−
34−noLog :: Wai.Request -> Status -> Maybe Integer -> IO ()
35−noLog _ _ _ = return ()
lib/Hakyll/Web/CompressCss.hsdeleted−101
@@ -1,101 +0,0 @@
1−--------------------------------------------------------------------------------
2−-- | Module used for CSS compression. The compression is currently in a simple
3−-- state, but would typically reduce the number of bytes by about 25%.
4−module Hakyll.Web.CompressCss
5− ( compressCssCompiler
6− , compressCss
7− ) where
8−
9−
10−--------------------------------------------------------------------------------
11−import Data.Char (isSpace)
12−import Data.List (dropWhileEnd, isPrefixOf)
13−
14−
15−--------------------------------------------------------------------------------
16−import Hakyll.Core.Compiler
17−import Hakyll.Core.Item
18−import Hakyll.Core.Util.String
19−
20−
21−--------------------------------------------------------------------------------
22−-- | Compiler form of 'compressCss'
23−compressCssCompiler :: Compiler (Item String)
24−compressCssCompiler = fmap compressCss <$> getResourceString
25−
26−
27−--------------------------------------------------------------------------------
28−-- | Compress CSS to speed up your site.
29−compressCss :: String -> String
30−compressCss = withoutStrings (handleCalcExpressions compressSeparators . compressWhitespace)
31− . dropWhileEnd isSpace
32− . dropWhile isSpace
33− . stripComments
34−
35−
36−--------------------------------------------------------------------------------
37−-- | Compresses certain forms of separators.
38−compressSeparators :: String -> String
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
49− where
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)
69−
70−--------------------------------------------------------------------------------
71−-- | Compresses all whitespace.
72−compressWhitespace :: String -> String
73−compressWhitespace = replaceAll "[ \t\n\r]+" (const " ")
74−
75−--------------------------------------------------------------------------------
76−-- | Function that strips CSS comments away (outside of strings).
77−stripComments :: String -> String
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
87−
88−
89−--------------------------------------------------------------------------------
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/Paginate.hsdeleted−153
@@ -1,153 +0,0 @@
1−--------------------------------------------------------------------------------
2−{-# LANGUAGE OverloadedStrings #-}
3−module Hakyll.Web.Paginate
4− ( PageNumber
5− , Paginate (..)
6− , buildPaginateWith
7− , paginateEvery
8− , paginateRules
9− , paginateContext
10− ) where
11−
12−
13−--------------------------------------------------------------------------------
14−import Control.Applicative (empty)
15−import Control.Monad (forM_, forM)
16−import qualified Data.Map as M
17−import qualified Data.Set as S
18−
19−
20−--------------------------------------------------------------------------------
21−import Hakyll.Core.Compiler
22−import Hakyll.Core.Identifier
23−import Hakyll.Core.Identifier.Pattern
24−import Hakyll.Core.Item
25−import Hakyll.Core.Metadata
26−import Hakyll.Core.Rules
27−import Hakyll.Web.Html
28−import Hakyll.Web.Template.Context
29−
30−
31−--------------------------------------------------------------------------------
32−type PageNumber = Int
33−
34−
35−--------------------------------------------------------------------------------
36−-- | Data about paginators
37−data Paginate = Paginate
38− { paginateMap :: M.Map PageNumber [Identifier]
39− , paginateMakeId :: PageNumber -> Identifier
40− , paginateDependency :: Dependency
41− }
42−
43−
44−--------------------------------------------------------------------------------
45−paginateNumPages :: Paginate -> Int
46−paginateNumPages = M.size . paginateMap
47−
48−
49−--------------------------------------------------------------------------------
50−paginateEvery :: Int -> [a] -> [[a]]
51−paginateEvery n = go
52− where
53− go [] = []
54− go xs = let (y, ys) = splitAt n xs in y : go ys
55−
56−
57−--------------------------------------------------------------------------------
58−buildPaginateWith
59− :: MonadMetadata m
60− => ([Identifier] -> m [[Identifier]]) -- ^ Group items into pages
61− -> Pattern -- ^ Select items to paginate
62− -> (PageNumber -> Identifier) -- ^ Identifiers for the pages
63− -> m Paginate
64−buildPaginateWith grouper pattern makeId = do
65− ids <- getMatches pattern
66− idGroups <- grouper ids
67− let idsSet = S.fromList ids
68− return Paginate
69− { paginateMap = M.fromList (zip [1 ..] idGroups)
70− , paginateMakeId = makeId
71− , paginateDependency = PatternDependency pattern idsSet
72− }
73−
74−
75−--------------------------------------------------------------------------------
76−paginateRules :: Paginate -> (PageNumber -> Pattern -> Rules ()) -> Rules ()
77−paginateRules paginator rules =
78− forM_ (M.toList $ paginateMap paginator) $ \(idx, identifiers) ->
79− rulesExtraDependencies [paginateDependency paginator] $
80− create [paginateMakeId paginator idx] $
81− rules idx $ fromList identifiers
82−
83−
84−--------------------------------------------------------------------------------
85−-- | Get the identifier for a certain page by passing in the page number.
86−paginatePage :: Paginate -> PageNumber -> Maybe Identifier
87−paginatePage pag pageNumber
88− | pageNumber < 1 = Nothing
89− | pageNumber > (paginateNumPages pag) = Nothing
90− | otherwise = Just $ paginateMakeId pag pageNumber
91−
92−
93−--------------------------------------------------------------------------------
94−-- | A default paginate context which provides the following keys:
95−--
96−--
97−-- * @firstPageNum@
98−-- * @firstPageUrl@
99−-- * @previousPageNum@
100−-- * @previousPageUrl@
101−-- * @nextPageNum@
102−-- * @nextPageUrl@
103−-- * @lastPageNum@
104−-- * @lastPageUrl@
105−-- * @currentPageNum@
106−-- * @currentPageUrl@
107−-- * @numPages@
108−-- * @allPages@
109−paginateContext :: Paginate -> PageNumber -> Context a
110−paginateContext pag currentPage = mconcat
111− [ field "firstPageNum" $ \_ -> otherPage 1 >>= num
112− , field "firstPageUrl" $ \_ -> otherPage 1 >>= url
113− , field "previousPageNum" $ \_ -> otherPage (currentPage - 1) >>= num
114− , field "previousPageUrl" $ \_ -> otherPage (currentPage - 1) >>= url
115− , field "nextPageNum" $ \_ -> otherPage (currentPage + 1) >>= num
116− , field "nextPageUrl" $ \_ -> otherPage (currentPage + 1) >>= url
117− , field "lastPageNum" $ \_ -> otherPage lastPage >>= num
118− , field "lastPageUrl" $ \_ -> otherPage lastPage >>= url
119− , field "currentPageNum" $ \i -> thisPage i >>= num
120− , field "currentPageUrl" $ \i -> thisPage i >>= url
121− , constField "numPages" $ show $ paginateNumPages pag
122− , Context $ \k _ i -> case k of
123− "allPages" -> do
124− let ctx =
125− field "isCurrent" (\n -> if fst (itemBody n) == currentPage then return "true" else empty) `mappend`
126− field "num" (num . itemBody) `mappend`
127− field "url" (url . itemBody)
128−
129− list <- forM [1 .. lastPage] $
130− \n -> if n == currentPage then thisPage i else otherPage n
131− items <- mapM makeItem list
132− return $ ListField ctx items
133− _ -> do
134− empty
135−
136− ]
137− where
138− lastPage = paginateNumPages pag
139−
140− thisPage i = return (currentPage, itemIdentifier i)
141− otherPage n
142− | n == currentPage = fail $ "This is the current page: " ++ show n
143− | otherwise = case paginatePage pag n of
144− Nothing -> fail $ "No such page: " ++ show n
145− Just i -> return (n, i)
146−
147− num :: (Int, Identifier) -> Compiler String
148− num = return . show . fst
149−
150− url :: (Int, Identifier) -> Compiler String
151− url (n, i) = getRoute i >>= \mbR -> case mbR of
152− Just r -> return $ toUrl r
153− Nothing -> fail $ "No URL for page: " ++ show n
lib/Hakyll/Web/Pandoc/Binary.hsdeleted−33
@@ -1,33 +0,0 @@
1−{-# OPTIONS_GHC -fno-warn-orphans #-}
2−{-# LANGUAGE DeriveGeneric #-}
3−module Hakyll.Web.Pandoc.Binary where
4−
5−import Data.Binary (Binary (..))
6−
7−import qualified Text.CSL as CSL
8−import qualified Text.CSL.Reference as REF
9−import qualified Text.CSL.Style as STY
10−import Text.Pandoc
11−
12−--------------------------------------------------------------------------------
13−-- orphans
14−
15−instance Binary Alignment
16−instance Binary Block
17−instance Binary CSL.Reference
18−instance Binary Citation
19−instance Binary CitationMode
20−instance Binary Format
21−instance Binary Inline
22−instance Binary ListNumberDelim
23−instance Binary ListNumberStyle
24−instance Binary MathType
25−instance Binary QuoteType
26−instance Binary REF.CLabel
27−instance Binary REF.CNum
28−instance Binary REF.Literal
29−instance Binary REF.RefDate
30−instance Binary REF.RefType
31−instance Binary REF.Season
32−instance Binary STY.Agent
33−instance Binary STY.Formatted
lib/Hakyll/Web/Redirect.hsdeleted−100
@@ -1,100 +0,0 @@
1−-- | Module used for generating HTML redirect pages. This allows renaming pages
2−-- to avoid breaking existing links without requiring server-side support for
3−-- formal 301 Redirect error codes
4−module Hakyll.Web.Redirect
5− ( Redirect (..)
6− , createRedirects
7− ) where
8−
9−import Control.Applicative ((<$>))
10−import Control.Monad (forM_, when)
11−import Data.Binary (Binary (..))
12−import Data.List (sort, group)
13−import Hakyll.Core.Compiler
14−import Hakyll.Core.Identifier
15−import Hakyll.Core.Routes
16−import Hakyll.Core.Rules
17−import Hakyll.Core.Writable (Writable (..))
18−
19−-- | This function exposes a higher-level interface compared to using the
20−-- 'Redirect' type manually.
21−--
22−-- This creates, using a database mapping broken URLs to working ones, HTML
23−-- files which will do HTML META tag redirect pages (since, as a static site, we
24−-- can't use web-server-level 301 redirects, and using JS is gross).
25−--
26−-- This is useful for sending people using old URLs to renamed versions, dealing
27−-- with common typos etc, and will increase site traffic. Such broken URLs can
28−-- be found by looking at server logs or by using Google Webmaster Tools.
29−-- Broken URLs must be valid Haskell strings, non-URL-escaped valid POSIX
30−-- filenames, and relative links, since they will be defined in a @hakyll.hs@
31−-- and during generation, written to disk with the filename corresponding to the
32−-- broken URLs. (Target URLs can be absolute or relative, but should be
33−-- URL-escaped.) So broken incoming links like <http://www.gwern.net/foo/> which
34−-- should be <http://www.gwern.net/foobar> cannot be fixed (since you cannot
35−-- create a HTML file named @"foo/"@ on disk, as that would be a directory).
36−--
37−-- An example of a valid association list would be:
38−--
39−-- > brokenLinks =
40−-- > [ ("projects.html", "http://github.com/gwern")
41−-- > , ("/Black-market archive", "Black-market%20archives")
42−-- > ]
43−--
44−-- In which case the functionality can then be used in `main` with a line like:
45−--
46−-- > version "redirects" $ createRedirects brokenLinks
47−--
48−-- The 'version' is recommended to separate these items from your other pages.
49−--
50−-- The on-disk files can then be uploaded with HTML mimetypes
51−-- (either explicitly by generating and uploading them separately, by
52−-- auto-detection of the filetype, or an upload tool defaulting to HTML
53−-- mimetype, such as calling @s3cmd@ with @--default-mime-type=text/html@) and
54−-- will redirect browsers and search engines going to the old/broken URLs.
55−--
56−-- See also <https://groups.google.com/d/msg/hakyll/sWc6zxfh-uM/fUpZPsFNDgAJ>.
57−createRedirects :: [(Identifier, String)] -> Rules ()
58−createRedirects redirects =
59− do -- redirects are many-to-fewer; keys must be unique, and must point somewhere else:
60− let gkeys = group $ sort $ map fst redirects
61− forM_ gkeys $ \gkey -> case gkey of
62− (k : _ : _) -> fail $
63− "Duplicate 301 redirects; " ++ show k ++ " is ambiguous."
64− _ -> return ()
65−
66− forM_ redirects $ \(r, t) ->
67− when (toFilePath r == t) $ fail $
68− "Self-redirect detected: " ++ show r ++ " points to itself."
69−
70− forM_ redirects $ \(ident, to) ->
71− create [ident] $ do
72− route idRoute
73− compile $ makeItem $! Redirect to
74−
75−-- | This datatype can be used directly if you want a lower-level interface to
76−-- generate redirects. For example, if you want to redirect @foo.html@ to
77−-- @bar.jpg@, you can use:
78−--
79−-- > create ["foo.html"] $ do
80−-- > route idRoute
81−-- > compile $ makeItem $ Redirect "bar.jpg"
82−data Redirect = Redirect
83− { redirectTo :: String
84− } deriving (Eq, Ord, Show)
85−
86−instance Binary Redirect where
87− put (Redirect to) = put to
88− get = Redirect <$> get
89−
90−instance Writable Redirect where
91− write path = write path . fmap redirectToHtml
92−
93−redirectToHtml :: Redirect -> String
94−redirectToHtml (Redirect working) =
95− "<!DOCTYPE html><html><head><meta charset=\"utf-8\"/><meta name=\"generator\" content=\"hakyll\"/>" ++
96− "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">" ++
97− "<meta http-equiv=\"refresh\" content=\"0; url=" ++ working ++
98− "\"><link rel=\"canonical\" href=\"" ++ working ++
99− "\"><title>Permanent Redirect</title></head><body><p>The page has moved to: <a href=\"" ++ working ++
100− "\">this page</a></p></body></html>"
lib/Hakyll/Web/Template.hsdeleted−178
@@ -1,178 +0,0 @@
1−-- | This module provides means for reading and applying 'Template's.
2−--
3−-- Templates are tools to convert items into a string. They are perfectly suited
4−-- for laying out your site.
5−--
6−-- Let's look at an example template:
7−--
8−-- > <html>
9−-- > <head>
10−-- > <title>My crazy homepage - $title$</title>
11−-- > </head>
12−-- > <body>
13−-- > <div id="header">
14−-- > <h1>My crazy homepage - $title$</h1>
15−-- > </div>
16−-- > <div id="content">
17−-- > $body$
18−-- > </div>
19−-- > <div id="footer">
20−-- > By reading this you agree that I now own your soul
21−-- > </div>
22−-- > </body>
23−-- > </html>
24−--
25−-- As you can see, the format is very simple -- @$key$@ is used to render the
26−-- @$key$@ field from the page, everything else is literally copied. If you want
27−-- to literally insert @\"$key$\"@ into your page (for example, when you're
28−-- writing a Hakyll tutorial) you can use
29−--
30−-- > <p>
31−-- > A literal $$key$$.
32−-- > </p>
33−--
34−-- Because of it's simplicity, these templates can be used for more than HTML:
35−-- you could make, for example, CSS or JS templates as well.
36−--
37−-- Apart from interpolating @$key$@s from the 'Context' you can also
38−-- use the following macros:
39−--
40−-- * @$if(key)$@
41−--
42−-- > $if(key)$
43−-- > <b> Defined </b>
44−-- > $else$
45−-- > <b> Non-defined </b>
46−-- > $endif$
47−--
48−-- This example will print @Defined@ if @key@ is defined in the
49−-- context and @Non-defined@ otherwise. The @$else$@ clause is
50−-- optional.
51−--
52−-- * @$for(key)$@
53−--
54−-- The @for@ macro is used for enumerating 'Context' elements that are
55−-- lists, i.e. constructed using the 'listField' function. Assume that
56−-- in a context we have an element @listField \"key\" c itms@. Then
57−-- the snippet
58−--
59−-- > $for(key)$
60−-- > $x$
61−-- > $sep$,
62−-- > $endfor$
63−--
64−-- would, for each item @i@ in 'itms', lookup @$x$@ in the context @c@
65−-- with item @i@, interpolate it, and join the resulting list with
66−-- @,@.
67−--
68−-- Another concrete example one may consider is the following. Given the
69−-- context
70−--
71−-- > listField "things" (field "thing" (return . itemBody))
72−-- > (sequence [makeItem "fruits", makeItem "vegetables"])
73−--
74−-- and a template
75−--
76−-- > I like
77−-- > $for(things)$
78−-- > fresh $thing$$sep$, and
79−-- > $endfor$
80−--
81−-- the resulting page would look like
82−--
83−-- > <p>
84−-- > I like
85−-- >
86−-- > fresh fruits, and
87−-- >
88−-- > fresh vegetables
89−-- > </p>
90−--
91−-- The @$sep$@ part can be omitted. Usually, you can get by using the
92−-- 'applyListTemplate' and 'applyJoinListTemplate' functions.
93−--
94−-- * @$partial(path)$@
95−--
96−-- Loads a template located in a separate file and interpolates it
97−-- under the current context.
98−--
99−-- Assuming that the file @test.html@ contains
100−--
101−-- > <b>$key$</b>
102−--
103−-- The result of rendering
104−--
105−-- > <p>
106−-- > $partial("test.html")$
107−-- > </p>
108−--
109−-- is the same as the result of rendering
110−--
111−-- > <p>
112−-- > <b>$key$</b>
113−-- > </p>
114−--
115−-- That is, calling @$partial$@ is equivalent to just copying and pasting
116−-- template code.
117−--
118−-- In the examples above you can see that the outputs contain a lot of leftover
119−-- whitespace that you may wish to remove. Using @'$-'@ or @'-$'@ instead of
120−-- @'$'@ in a macro strips all whitespace to the left or right of that clause
121−-- respectively. Given the context
122−--
123−-- > listField "counts" (field "count" (return . itemBody))
124−-- > (sequence [makeItem "3", makeItem "2", makeItem "1"])
125−--
126−-- and a template
127−--
128−-- > <p>
129−-- > $for(counts)-$
130−-- > $count$
131−-- > $-sep$...
132−-- > $-endfor$
133−-- > </p>
134−--
135−-- the resulting page would look like
136−--
137−-- > <p>
138−-- > 3...2...1
139−-- > </p>
140−--
141−{-# LANGUAGE TemplateHaskell #-}
142−module Hakyll.Web.Template
143− ( Template
144− , templateBodyCompiler
145− , templateCompiler
146− , applyTemplate
147− , loadAndApplyTemplate
148− , applyAsTemplate
149− , readTemplate
150− , compileTemplateItem
151− , unsafeReadTemplateFile
152− , embedTemplate
153− ) where
154−
155−
156−--------------------------------------------------------------------------------
157−import 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.hsdeleted−481
@@ -1,481 +0,0 @@
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−
21−--------------------------------------------------------------------------------
22−{-# LANGUAGE CPP #-}
23−{-# LANGUAGE ExistentialQuantification #-}
24−{-# LANGUAGE RankNTypes #-}
25−module Hakyll.Web.Template.Context
26− ( ContextField (..)
27− , Context (..)
28− , field
29− , boolField
30− , constField
31− , listField
32− , listFieldWith
33− , functionField
34− , mapContext
35−
36− , defaultContext
37− , bodyField
38− , metadataField
39− , urlField
40− , pathField
41− , titleField
42− , snippetField
43− , dateField
44− , dateFieldWith
45− , getItemUTC
46− , getItemModificationTime
47− , modificationTimeField
48− , modificationTimeFieldWith
49− , teaserField
50− , teaserFieldWithSeparator
51− , missingField
52− , bindItem
53− ) where
54−
55−
56−--------------------------------------------------------------------------------
57−import Control.Applicative (Alternative (..))
58−import Control.Monad (msum)
59−import Data.List (intercalate, tails)
60−#if MIN_VERSION_base(4,9,0)
61−import Data.Semigroup (Semigroup (..))
62−#endif
63−import Data.Time.Clock (UTCTime (..))
64−import Data.Time.Format (formatTime)
65−import qualified Data.Time.Format as TF
66−import Data.Time.Locale.Compat (TimeLocale, defaultTimeLocale)
67−import Hakyll.Core.Compiler
68−import Hakyll.Core.Compiler.Internal
69−import Hakyll.Core.Identifier
70−import Hakyll.Core.Item
71−import Hakyll.Core.Metadata
72−import Hakyll.Core.Provider
73−import Hakyll.Core.Util.String (needlePrefix, splitAll)
74−import Hakyll.Web.Html
75−import Prelude hiding (id)
76−import System.FilePath (dropExtension, splitDirectories,
77− takeBaseName)
78−
79−
80−--------------------------------------------------------------------------------
81−-- | Mostly for internal usage
82−data ContextField
83− = EmptyField
84− | StringField String
85− | forall a. ListField (Context a) [Item a]
86−
87−
88−--------------------------------------------------------------------------------
89−-- | The 'Context' monoid. Please note that the order in which you
90−-- compose the items is important. For example in
91−--
92−-- > field "A" f1 <> field "A" f2
93−--
94−-- the first context will overwrite the second. This is especially
95−-- important when something is being composed with
96−-- 'metadataField' (or 'defaultContext'). If you want your context to be
97−-- overwritten by the metadata fields, compose it from the right:
98−--
99−-- @
100−-- 'metadataField' \<\> field \"date\" fDate
101−-- @
102−--
103−newtype Context a = Context
104− { unContext :: String -> [String] -> Item a -> Compiler ContextField
105− }
106−
107−
108−--------------------------------------------------------------------------------
109−-- | Tries to find a key in the left context,
110−-- or when that fails in the right context.
111−#if MIN_VERSION_base(4,9,0)
112−instance Semigroup (Context a) where
113− (<>) (Context f) (Context g) = Context $ \k a i -> f k a i <|> g k a i
114−
115−instance Monoid (Context a) where
116− mempty = missingField
117− mappend = (<>)
118−#else
119−instance Monoid (Context a) where
120− mempty = missingField
121− mappend (Context f) (Context g) = Context $ \k a i -> f k a i <|> g k a i
122−#endif
123−
124−
125−--------------------------------------------------------------------------------
126−field' :: String -> (Item a -> Compiler ContextField) -> Context a
127−field' key value = Context $ \k _ i ->
128− if k == key
129− then value i
130− else noResult $ "Tried field " ++ key
131−
132−
133−--------------------------------------------------------------------------------
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'.
143−field
144− :: String -- ^ Key
145− -> (Item a -> Compiler String) -- ^ Function that constructs a value based
146− -- on the item (e.g. accessing metadata)
147− -> Context a
148−field key value = field' key (fmap StringField . value)
149−
150−
151−--------------------------------------------------------------------------------
152−-- | Creates a 'field' to use with the @$if()$@ template macro.
153−-- Attempting to substitute the field into the template will cause an error.
154−boolField
155− :: String
156− -> (Item a -> Bool)
157− -> Context a
158−boolField name f = field' name (\i -> if f i
159− then return EmptyField
160− else noResult $ "Field " ++ name ++ " is false")
161−
162−
163−--------------------------------------------------------------------------------
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
169−constField key = field key . const . return
170−
171−
172−--------------------------------------------------------------------------------
173−-- | Creates a list field to be consumed by a @$for(…)$@ expression.
174−-- The compiler returns multiple items which are rendered in the loop body
175−-- with the supplied context.
176−listField :: String -> Context a -> Compiler [Item a] -> Context b
177−listField key c xs = listFieldWith key c (const xs)
178−
179−
180−--------------------------------------------------------------------------------
181−-- | Produces a new 'Context' which has list field 'key'. All fields from
182−-- 'c' are also accessible from the produced context.
183−-- Be careful when doing @listFieldWith k ca f <> cb@ as any fields in @ca@
184−-- will override fields in @cb@ with the same name.
185−-- Creates a list field like 'listField', but supplies the current page
186−-- to the compiler.
187−listFieldWith
188− :: String -> Context a -> (Item b -> Compiler [Item a]) -> Context b
189−listFieldWith key c f = field' key $ fmap (ListField c) . f
190−
191−
192−--------------------------------------------------------------------------------
193−-- | Creates a variadic function field.
194−--
195−-- The function will be called with the dynamically evaluated string arguments
196−-- from the template as well as the page that is currently rendered.
197−functionField :: String -- ^ Key
198− -> ([String] -> Item a -> Compiler String) -- ^ Function
199− -> Context a
200−functionField name value = Context $ \k args i ->
201− if k == name
202− then StringField <$> value args i
203− else noResult $ "Tried function field " ++ name
204−
205−
206−--------------------------------------------------------------------------------
207−-- | Transform the respective string results of all fields in a context.
208−-- For example,
209−--
210−-- > mapContext (++"c") (constField "x" "a" <> constField "y" "b")
211−--
212−-- is equivalent to
213−--
214−-- > constField "x" "ac" <> constField "y" "bc"
215−--
216−mapContext :: (String -> String) -> Context a -> Context a
217−mapContext f (Context c) = Context $ \k a i -> do
218− fld <- c k a i
219− case fld of
220− EmptyField -> wrongType "boolField"
221− StringField str -> return $ StringField (f str)
222− _ -> wrongType "ListField"
223− where
224− wrongType typ = fail $ "Hakyll.Web.Template.Context.mapContext: " ++
225− "can't map over a " ++ typ ++ "!"
226−
227−--------------------------------------------------------------------------------
228−-- | A context that allows snippet inclusion. In processed file, use as:
229−--
230−-- > ...
231−-- > $snippet("path/to/snippet/")$
232−-- > ...
233−--
234−-- The contents of the included file will not be interpolated like @partial@
235−-- does it.
236−--
237−snippetField :: Context String
238−snippetField = functionField "snippet" f
239− where
240− f [contentsPath] _ = loadBody (fromFilePath contentsPath)
241− f [] _ = fail "No argument to function 'snippet()'"
242− f _ _ = fail "Too many arguments to function 'snippet()'"
243−
244−--------------------------------------------------------------------------------
245−-- | A context that contains (in that order)
246−--
247−-- 1. A @$body$@ field
248−--
249−-- 2. Metadata fields
250−--
251−-- 3. A @$url$@ 'urlField'
252−--
253−-- 4. A @$path$@ 'pathField'
254−--
255−-- 5. A @$title$@ 'titleField'
256−defaultContext :: Context String
257−defaultContext =
258− bodyField "body" `mappend`
259− metadataField `mappend`
260− urlField "url" `mappend`
261− pathField "path" `mappend`
262− titleField "title"
263−
264−
265−--------------------------------------------------------------------------------
266−teaserSeparator :: String
267−teaserSeparator = "<!--more-->"
268−
269−
270−--------------------------------------------------------------------------------
271−-- | Constructs a 'field' that contains the body of the item.
272−bodyField :: String -> Context String
273−bodyField key = field key $ return . itemBody
274−
275−
276−--------------------------------------------------------------------------------
277−-- | Map any field to its metadata value, if present
278−metadataField :: Context a
279−metadataField = Context $ \k _ i -> do
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
285−
286−
287−--------------------------------------------------------------------------------
288−-- | Absolute url to the resulting item
289−urlField :: String -> Context a
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
294−
295−
296−--------------------------------------------------------------------------------
297−-- | Filepath of the underlying file of the item
298−pathField :: String -> Context a
299−pathField key = field key $ return . toFilePath . itemIdentifier
300−
301−
302−--------------------------------------------------------------------------------
303−-- | This title 'field' takes the basename of the underlying file by default
304−titleField :: String -> Context a
305−titleField = mapContext takeBaseName . pathField
306−
307−
308−--------------------------------------------------------------------------------
309−-- | When the metadata has a field called @published@ in one of the
310−-- following formats then this function can render the date.
311−--
312−-- * @Mon, 06 Sep 2010 00:01:00 +0000@
313−--
314−-- * @Mon, 06 Sep 2010 00:01:00 UTC@
315−--
316−-- * @Mon, 06 Sep 2010 00:01:00@
317−--
318−-- * @2010-09-06T00:01:00+0000@
319−--
320−-- * @2010-09-06T00:01:00Z@
321−--
322−-- * @2010-09-06T00:01:00@
323−--
324−-- * @2010-09-06 00:01:00+0000@
325−--
326−-- * @2010-09-06 00:01:00@
327−--
328−-- * @September 06, 2010 00:01 AM@
329−--
330−-- Following date-only formats are supported too (@00:00:00@ for time is
331−-- assumed)
332−--
333−-- * @2010-09-06@
334−--
335−-- * @September 06, 2010@
336−--
337−-- Alternatively, when the metadata has a field called @path@ in a
338−-- @folder/yyyy-mm-dd-title.extension@ format (the convention for pages)
339−-- and no @published@ metadata field set, this function can render
340−-- the date. This pattern matches the file name or directory names
341−-- that begins with @yyyy-mm-dd@ . For example:
342−-- @folder//yyyy-mm-dd-title//dist//main.extension@ .
343−-- In case of multiple matches, the rightmost one is used.
344−--
345−-- As another alternative, if none of the above matches, and the file has a
346−-- path which contains nested directories specifying a date, then that date
347−-- will be used. In other words, if the path is of the form
348−-- @**//yyyy//mm//dd//**//main.extension@ .
349−-- As above, in case of multiple matches, the rightmost one is used.
350−
351−dateField :: String -- ^ Key in which the rendered date should be placed
352− -> String -- ^ Format to use on the date
353− -> Context a -- ^ Resulting context
354−dateField = dateFieldWith defaultTimeLocale
355−
356−
357−--------------------------------------------------------------------------------
358−-- | This is an extended version of 'dateField' that allows you to
359−-- specify a time locale that is used for outputting the date. For more
360−-- details, see 'dateField' and 'formatTime'.
361−dateFieldWith :: TimeLocale -- ^ Output time locale
362− -> String -- ^ Destination key
363− -> String -- ^ Format to use on the date
364− -> Context a -- ^ Resulting context
365−dateFieldWith locale key format = field key $ \i -> do
366− time <- getItemUTC locale $ itemIdentifier i
367− return $ formatTime locale format time
368−
369−
370−--------------------------------------------------------------------------------
371−-- | Parser to try to extract and parse the time from the @published@
372−-- field or from the filename. See 'dateField' for more information.
373−-- Exported for user convenience.
374−getItemUTC :: MonadMetadata m
375− => TimeLocale -- ^ Output time locale
376− -> Identifier -- ^ Input page
377− -> m UTCTime -- ^ Parsed UTCTime
378−getItemUTC locale id' = do
379− metadata <- getMetadata id'
380− let tryField k fmt = lookupString k metadata >>= parseTime' fmt
381− paths = splitDirectories $ (dropExtension . toFilePath) id'
382−
383− maybe empty' return $ msum $
384− [tryField "published" fmt | fmt <- formats] ++
385− [tryField "date" fmt | fmt <- formats] ++
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]
388− where
389− empty' = fail $ "Hakyll.Web.Template.Context.getItemUTC: " ++
390− "could not parse time for " ++ show id'
391− parseTime' = parseTimeM True locale
392− formats =
393− [ "%a, %d %b %Y %H:%M:%S %Z"
394− , "%Y-%m-%dT%H:%M:%S%Z"
395− , "%Y-%m-%d %H:%M:%S%Z"
396− , "%Y-%m-%d"
397− , "%B %e, %Y %l:%M %p"
398− , "%B %e, %Y"
399− , "%b %d, %Y"
400− ]
401−
402−
403−--------------------------------------------------------------------------------
404−-- | Get the time on which the actual file was last modified. This only works if
405−-- there actually is an underlying file, of couse.
406−getItemModificationTime
407− :: Identifier
408− -> Compiler UTCTime
409−getItemModificationTime identifier = do
410− provider <- compilerProvider <$> compilerAsk
411− return $ resourceModificationTime provider identifier
412−
413−
414−--------------------------------------------------------------------------------
415−-- | Creates a field with the last modification date of the underlying item.
416−modificationTimeField :: String -- ^ Key
417− -> String -- ^ Format
418− -> Context a -- ^ Resulting context
419−modificationTimeField = modificationTimeFieldWith defaultTimeLocale
420−
421−
422−--------------------------------------------------------------------------------
423−-- | Creates a field with the last modification date of the underlying item
424−-- in a custom localisation format (see 'formatTime').
425−modificationTimeFieldWith :: TimeLocale -- ^ Time output locale
426− -> String -- ^ Key
427− -> String -- ^ Format
428− -> Context a -- ^ Resulting context
429−modificationTimeFieldWith locale key fmt = field key $ \i -> do
430− mtime <- getItemModificationTime $ itemIdentifier i
431− return $ formatTime locale fmt mtime
432−
433−
434−--------------------------------------------------------------------------------
435−-- | A context with "teaser" key which contain a teaser of the item.
436−-- The item is loaded from the given snapshot (which should be saved
437−-- in the user code before any templates are applied).
438−teaserField :: String -- ^ Key to use
439− -> Snapshot -- ^ Snapshot to load
440− -> Context String -- ^ Resulting context
441−teaserField = teaserFieldWithSeparator teaserSeparator
442−
443−
444−--------------------------------------------------------------------------------
445−-- | A context with "teaser" key which contain a teaser of the item, defined as
446−-- the snapshot content before the teaser separator. The item is loaded from the
447−-- given snapshot (which should be saved in the user code before any templates
448−-- are applied).
449−teaserFieldWithSeparator :: String -- ^ Separator to use
450− -> String -- ^ Key to use
451− -> Snapshot -- ^ Snapshot to load
452− -> Context String -- ^ Resulting context
453−teaserFieldWithSeparator separator key snapshot = field key $ \item -> do
454− body <- itemBody <$> loadSnapshot (itemIdentifier item) snapshot
455− case needlePrefix separator body of
456− Nothing -> fail $
457− "Hakyll.Web.Template.Context: no teaser defined for " ++
458− show (itemIdentifier item)
459− Just t -> return t
460−
461−
462−--------------------------------------------------------------------------------
463−-- | Constantly reports any field as missing. Mostly for internal usage,
464−-- it is the last choice in every context used in a template application.
465−missingField :: Context a
466−missingField = Context $ \k _ _ -> noResult $
467− "Missing field '" ++ k ++ "' in context"
468−
469−parseTimeM :: Bool -> TimeLocale -> String -> String -> Maybe UTCTime
470−#if MIN_VERSION_time(1,5,0)
471−parseTimeM = TF.parseTimeM
472−#else
473−parseTimeM _ = TF.parseTime
474−#endif
475−
476−--------------------------------------------------------------------------------
477−
478−-- | Binds an 'Item' to a given 'Context', allowing it to be combined with any
479−-- other 'Context' of any type.
480−bindItem :: Context a -> Item a -> forall b. Context b
481−bindItem (Context ctx) ia = Context $ \k args _ -> ctx k args ia
lib/Hakyll/Web/Template/Internal.hsdeleted−261
@@ -1,261 +0,0 @@
1−{-# LANGUAGE DeriveAnyClass #-}
2−{-# LANGUAGE DeriveGeneric #-}
3−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4−{-# LANGUAGE ScopedTypeVariables #-}
5−
6−module Hakyll.Web.Template.Internal
7− ( Template (..)
8− , template
9− , templateBodyCompiler
10− , templateCompiler
11− , applyTemplate
12− , loadAndApplyTemplate
13− , applyAsTemplate
14− , readTemplate
15− , compileTemplateItem
16− , unsafeReadTemplateFile
17−
18− , module Hakyll.Web.Template.Internal.Element
19− , module Hakyll.Web.Template.Internal.Trim
20− ) where
21−
22−
23−--------------------------------------------------------------------------------
24−import Control.Monad (forM)
25−import Control.Monad.Except (MonadError (..), catchError)
26−import Data.Binary (Binary)
27−import Data.List (intercalate)
28−import qualified Data.List.NonEmpty as NonEmpty
29−import Data.Typeable (Typeable)
30−import GHC.Exts (IsString (..))
31−import GHC.Generics (Generic)
32−
33−
34−--------------------------------------------------------------------------------
35−import Hakyll.Core.Compiler
36−import Hakyll.Core.Compiler.Internal
37−import Hakyll.Core.Identifier
38−import Hakyll.Core.Item
39−import Hakyll.Core.Writable
40−import Hakyll.Web.Template.Context
41−import Hakyll.Web.Template.Internal.Element
42−import Hakyll.Web.Template.Internal.Trim
43−
44−
45−--------------------------------------------------------------------------------
46−-- | Datatype used for template substitutions.
47−data Template = Template
48− { tplElements :: [TemplateElement]
49− , tplOrigin :: FilePath -- Only for error messages.
50− } deriving (Show, Eq, Generic, Binary, Typeable)
51−
52−
53−--------------------------------------------------------------------------------
54−instance Writable Template where
55− -- Writing a template is impossible
56− write _ _ = return ()
57−
58−
59−--------------------------------------------------------------------------------
60−instance IsString Template where
61− fromString = readTemplate
62−
63−
64−--------------------------------------------------------------------------------
65−-- | Wrap the constructor to ensure trim is called.
66−template :: FilePath -> [TemplateElement] -> Template
67−template p = flip Template p . trim
68−
69−
70−--------------------------------------------------------------------------------
71−-- | Parse a string into a template.
72−-- You should prefer 'compileTemplateItem' over this.
73−readTemplate :: String -> Template
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
92−
93−--------------------------------------------------------------------------------
94−-- | Read a template, without metadata header
95−templateBodyCompiler :: Compiler (Item Template)
96−templateBodyCompiler = cached "Hakyll.Web.Template.templateBodyCompiler" $ do
97− item <- getResourceBody
98− file <- getUnderlying
99− withItemBody (compileTemplateFile file) item
100−
101−--------------------------------------------------------------------------------
102−-- | Read complete file contents as a template
103−templateCompiler :: Compiler (Item Template)
104−templateCompiler = cached "Hakyll.Web.Template.templateCompiler" $ do
105− item <- getResourceString
106− file <- getUnderlying
107− withItemBody (compileTemplateFile file) item
108−
109−
110−--------------------------------------------------------------------------------
111−-- | Interpolate template expressions from context values in a page
112−applyTemplate :: Template -- ^ Template
113− -> Context a -- ^ Context
114− -> Item a -- ^ Page
115− -> Compiler (Item String) -- ^ Resulting item
116−applyTemplate tpl context item = do
117− body <- applyTemplate' (tplElements tpl) context item `catchError` handler
118− return $ itemSetBody body item
119− where
120− tplName = tplOrigin tpl
121− itemName = show $ itemIdentifier item
122− handler es = fail $ "Hakyll.Web.Template.applyTemplate: Failed to " ++
123− (if tplName == itemName
124− then "interpolate template in item " ++ itemName
125− else "apply template " ++ tplName ++ " to item " ++ itemName) ++
126− ":\n" ++ intercalate ",\n" es
127−
128−
129−
130−--------------------------------------------------------------------------------
131−applyTemplate'
132− :: forall a.
133− [TemplateElement] -- ^ Unwrapped Template
134− -> Context a -- ^ Context
135− -> Item a -- ^ Page
136− -> Compiler String -- ^ Resulting item
137−applyTemplate' tes context x = go tes
138− where
139− context' :: String -> [String] -> Item a -> Compiler ContextField
140− context' = unContext (context `mappend` missingField)
141−
142− go = fmap concat . mapM applyElem
143−
144− ---------------------------------------------------------------------------
145−
146− applyElem :: TemplateElement -> Compiler String
147−
148− applyElem TrimL = trimError
149−
150− applyElem TrimR = trimError
151−
152− applyElem (Chunk c) = return c
153−
154− applyElem (Expr e) = withErrorMessage evalMsg (applyStringExpr typeMsg e)
155− where
156− evalMsg = "In expr '$" ++ show e ++ "$'"
157− typeMsg = "expr '$" ++ show e ++ "$'"
158−
159− applyElem Escaped = return "$"
160−
161− applyElem (If e t mf) = compilerTry (applyExpr e) >>= handle
162− where
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
174− sep <- maybe (return "") go s
175− bs <- forM xs $ applyTemplate' b $ c <> bindItem context x
176− return $ intercalate sep bs
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 ++ ")$'"
191−
192− ---------------------------------------------------------------------------
193−
194− applyExpr :: TemplateExpr -> Compiler ContextField
195−
196− applyExpr (Ident (TemplateKey k)) = context' k [] x
197−
198− applyExpr (Call (TemplateKey k) args) = do
199− args' <- mapM (\e -> applyStringExpr (typeMsg e) e) args
200− context' k args' x
201− where
202− typeMsg e = "argument '" ++ show e ++ "'"
203−
204− applyExpr (StringLiteral s) = return (StringField s)
205−
206− ----------------------------------------------------------------------------
207−
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."
222−
223−
224−--------------------------------------------------------------------------------
225−-- | The following pattern is so common:
226−--
227−-- > tpl <- loadBody "templates/foo.html"
228−-- > someCompiler
229−-- > >>= applyTemplate tpl context
230−--
231−-- That we have a single function which does this:
232−--
233−-- > someCompiler
234−-- > >>= loadAndApplyTemplate "templates/foo.html" context
235−loadAndApplyTemplate :: Identifier -- ^ Template identifier
236− -> Context a -- ^ Context
237− -> Item a -- ^ Page
238− -> Compiler (Item String) -- ^ Resulting item
239−loadAndApplyTemplate identifier context item = do
240− tpl <- loadBody identifier
241− applyTemplate tpl context item
242−
243−
244−--------------------------------------------------------------------------------
245−-- | It is also possible that you want to substitute @$key$@s within the body of
246−-- an item. This function does that by interpreting the item body as a template,
247−-- and then applying it to itself.
248−applyAsTemplate :: Context String -- ^ Context
249− -> Item String -- ^ Item and template
250− -> Compiler (Item String) -- ^ Resulting item
251−applyAsTemplate context item = do
252− tpl <- compileTemplateItem item
253− applyTemplate tpl context item
254−
255−
256−--------------------------------------------------------------------------------
257−unsafeReadTemplateFile :: FilePath -> Compiler Template
258−unsafeReadTemplateFile file = do
259− tpl <- unsafeCompiler $ readFile file
260− compileTemplateFile (fromFilePath file) tpl
261−{-# DEPRECATED unsafeReadTemplateFile "Use templateCompiler" #-}
lib/Hakyll/Web/Template/Internal/Element.hsdeleted−291
@@ -1,291 +0,0 @@
1−--------------------------------------------------------------------------------
2−-- | Module containing the elements used in a template. A template is generally
3−-- just a list of these elements.
4−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5−module Hakyll.Web.Template.Internal.Element
6− ( TemplateKey (..)
7− , TemplateExpr (..)
8− , TemplateElement (..)
9− , templateElems
10− , parseTemplateElemsFile
11− ) where
12−
13−
14−--------------------------------------------------------------------------------
15−import Control.Applicative ((<|>), (<*))
16−import Control.Monad (void)
17−import Control.Arrow (left)
18−import Data.Binary (Binary, get, getWord8, put, putWord8)
19−import Data.List (intercalate)
20−import Data.Maybe (isJust)
21−import Data.Typeable (Typeable)
22−import GHC.Exts (IsString (..))
23−import qualified Text.Parsec as P
24−import qualified Text.Parsec.String as P
25−
26−
27−--------------------------------------------------------------------------------
28−import Hakyll.Core.Util.Parser
29−
30−
31−--------------------------------------------------------------------------------
32−newtype TemplateKey = TemplateKey String
33− deriving (Binary, Show, Eq, Typeable)
34−
35−
36−--------------------------------------------------------------------------------
37−instance IsString TemplateKey where
38− fromString = TemplateKey
39−
40−
41−--------------------------------------------------------------------------------
42−-- | Elements of a template.
43−data TemplateElement
44− = Chunk String
45− | Expr TemplateExpr
46− | Escaped
47− -- expr, then, else
48− | If TemplateExpr [TemplateElement] (Maybe [TemplateElement])
49− -- expr, body, separator
50− | For TemplateExpr [TemplateElement] (Maybe [TemplateElement])
51− -- filename
52− | Partial TemplateExpr
53− | TrimL
54− | TrimR
55− deriving (Show, Eq, Typeable)
56−
57−
58−--------------------------------------------------------------------------------
59−instance Binary TemplateElement where
60− put (Chunk string) = putWord8 0 >> put string
61− put (Expr e) = putWord8 1 >> put e
62− put Escaped = putWord8 2
63− put (If e t f) = putWord8 3 >> put e >> put t >> put f
64− put (For e b s) = putWord8 4 >> put e >> put b >> put s
65− put (Partial e) = putWord8 5 >> put e
66− put TrimL = putWord8 6
67− put TrimR = putWord8 7
68−
69− get = getWord8 >>= \tag -> case tag of
70− 0 -> Chunk <$> get
71− 1 -> Expr <$> get
72− 2 -> pure Escaped
73− 3 -> If <$> get <*> get <*> get
74− 4 -> For <$> get <*> get <*> get
75− 5 -> Partial <$> get
76− 6 -> pure TrimL
77− 7 -> pure TrimR
78− _ -> error "Hakyll.Web.Template.Internal: Error reading cached template"
79−
80−
81−--------------------------------------------------------------------------------
82−-- | Expression in a template
83−data TemplateExpr
84− = Ident TemplateKey
85− | Call TemplateKey [TemplateExpr]
86− | StringLiteral String
87− deriving (Eq, Typeable)
88−
89−
90−--------------------------------------------------------------------------------
91−instance Show TemplateExpr where
92− show (Ident (TemplateKey k)) = k
93− show (Call (TemplateKey k) as) =
94− k ++ "(" ++ intercalate ", " (map show as) ++ ")"
95− show (StringLiteral s) = show s
96−
97−
98−--------------------------------------------------------------------------------
99−instance Binary TemplateExpr where
100− put (Ident k) = putWord8 0 >> put k
101− put (Call k as) = putWord8 1 >> put k >> put as
102− put (StringLiteral s) = putWord8 2 >> put s
103−
104− get = getWord8 >>= \tag -> case tag of
105− 0 -> Ident <$> get
106− 1 -> Call <$> get <*> get
107− 2 -> StringLiteral <$> get
108− _ -> error "Hakyll.Web.Template.Internal: Error reading cached template"
109−
110−--------------------------------------------------------------------------------
111−parseTemplateElemsFile :: FilePath -> String -> Either String [TemplateElement]
112−parseTemplateElemsFile file = left (\e -> "Cannot parse template " ++ show e)
113− . P.parse (templateElems <* P.eof) file
114−
115−
116−--------------------------------------------------------------------------------
117−templateElems :: P.Parser [TemplateElement]
118−templateElems = mconcat <$> P.many (P.choice [ lift chunk
119− , lift escaped
120− , conditional
121− , for
122− , partial
123− , expr
124− ])
125− where lift = fmap (:[])
126−
127−
128−--------------------------------------------------------------------------------
129−chunk :: P.Parser TemplateElement
130−chunk = Chunk <$> P.many1 (P.noneOf "$")
131−
132−
133−--------------------------------------------------------------------------------
134−expr :: P.Parser [TemplateElement]
135−expr = P.try $ do
136− trimLExpr <- trimOpen
137− e <- expr'
138− trimRExpr <- trimClose
139− return $ [TrimL | trimLExpr] ++ [Expr e] ++ [TrimR | trimRExpr]
140−
141−
142−--------------------------------------------------------------------------------
143−expr' :: P.Parser TemplateExpr
144−expr' = stringLiteral <|> call <|> ident
145−
146−
147−--------------------------------------------------------------------------------
148−escaped :: P.Parser TemplateElement
149−escaped = Escaped <$ P.try (P.string "$$")
150−
151−
152−--------------------------------------------------------------------------------
153−trimOpen :: P.Parser Bool
154−trimOpen = do
155− void $ P.char '$'
156− trimLIf <- P.optionMaybe $ P.try (P.char '-')
157− pure $ isJust trimLIf
158−
159−
160−--------------------------------------------------------------------------------
161−trimClose :: P.Parser Bool
162−trimClose = do
163− trimIfR <- P.optionMaybe $ (P.char '-')
164− void $ P.char '$'
165− pure $ isJust trimIfR
166−
167−
168−--------------------------------------------------------------------------------
169−conditional :: P.Parser [TemplateElement]
170−conditional = P.try $ do
171− -- if
172− trimLIf <- trimOpen
173− void $ P.string "if("
174− e <- expr'
175− void $ P.char ')'
176− trimRIf <- trimClose
177− -- then
178− thenBranch <- templateElems
179− -- else
180− elseParse <- opt "else"
181− -- endif
182− trimLEnd <- trimOpen
183− void $ P.string "endif"
184− trimREnd <- trimClose
185−
186− -- As else is optional we need to sort out where any Trim_s need to go.
187− let (thenBody, elseBody) = maybe (thenNoElse, Nothing) thenElse elseParse
188− where thenNoElse =
189− [TrimR | trimRIf] ++ thenBranch ++ [TrimL | trimLEnd]
190−
191− thenElse (trimLElse, elseBranch, trimRElse) = (thenB, elseB)
192− where thenB = [TrimR | trimRIf]
193− ++ thenBranch
194− ++ [TrimL | trimLElse]
195−
196− elseB = Just $ [TrimR | trimRElse]
197− ++ elseBranch
198− ++ [TrimL | trimLEnd]
199−
200− pure $ [TrimL | trimLIf] ++ [If e thenBody elseBody] ++ [TrimR | trimREnd]
201−
202−
203−--------------------------------------------------------------------------------
204−for :: P.Parser [TemplateElement]
205−for = P.try $ do
206− -- for
207− trimLFor <- trimOpen
208− void $ P.string "for("
209− e <- expr'
210− void $ P.char ')'
211− trimRFor <- trimClose
212− -- body
213− bodyBranch <- templateElems
214− -- sep
215− sepParse <- opt "sep"
216− -- endfor
217− trimLEnd <- trimOpen
218− void $ P.string "endfor"
219− trimREnd <- trimClose
220−
221− -- As sep is optional we need to sort out where any Trim_s need to go.
222− let (forBody, sepBody) = maybe (forNoSep, Nothing) forSep sepParse
223− where forNoSep =
224− [TrimR | trimRFor] ++ bodyBranch ++ [TrimL | trimLEnd]
225−
226− forSep (trimLSep, sepBranch, trimRSep) = (forB, sepB)
227− where forB = [TrimR | trimRFor]
228− ++ bodyBranch
229− ++ [TrimL | trimLSep]
230−
231− sepB = Just $ [TrimR | trimRSep]
232− ++ sepBranch
233− ++ [TrimL | trimLEnd]
234−
235− pure $ [TrimL | trimLFor] ++ [For e forBody sepBody] ++ [TrimR | trimREnd]
236−
237−
238−--------------------------------------------------------------------------------
239−partial :: P.Parser [TemplateElement]
240−partial = P.try $ do
241− trimLPart <- trimOpen
242− void $ P.string "partial("
243− e <- expr'
244− void $ P.char ')'
245− trimRPart <- trimClose
246−
247− pure $ [TrimL | trimLPart] ++ [Partial e] ++ [TrimR | trimRPart]
248−
249−
250−--------------------------------------------------------------------------------
251−ident :: P.Parser TemplateExpr
252−ident = P.try $ Ident <$> key
253−
254−
255−--------------------------------------------------------------------------------
256−call :: P.Parser TemplateExpr
257−call = P.try $ do
258− f <- key
259− void $ P.char '('
260− P.spaces
261− as <- P.sepBy expr' (P.spaces >> P.char ',' >> P.spaces)
262− P.spaces
263− void $ P.char ')'
264− return $ Call f as
265−
266−
267−--------------------------------------------------------------------------------
268−stringLiteral :: P.Parser TemplateExpr
269−stringLiteral = do
270− void $ P.char '\"'
271− str <- P.many $ do
272− x <- P.noneOf "\""
273− if x == '\\' then P.anyChar else return x
274− void $ P.char '\"'
275− return $ StringLiteral str
276−
277−
278−--------------------------------------------------------------------------------
279−key :: P.Parser TemplateKey
280−key = TemplateKey <$> metadataKey
281−
282−
283−--------------------------------------------------------------------------------
284−opt :: String -> P.Parser (Maybe (Bool, [TemplateElement], Bool))
285−opt clause = P.optionMaybe $ P.try $ do
286− trimL <- trimOpen
287− void $ P.string clause
288− trimR <- trimClose
289− branch <- templateElems
290− pure (trimL, branch, trimR)
291−
lib/Hakyll/Web/Template/Internal/Trim.hsdeleted−95
@@ -1,95 +0,0 @@
1−--------------------------------------------------------------------------------
2−-- | Module for trimming whitespace from tempaltes.
3−module Hakyll.Web.Template.Internal.Trim
4− ( trim
5− ) where
6−
7−
8−--------------------------------------------------------------------------------
9−import Data.Char (isSpace)
10−import Data.List (dropWhileEnd)
11−
12−
13−--------------------------------------------------------------------------------
14−import Hakyll.Web.Template.Internal.Element
15−
16−
17−--------------------------------------------------------------------------------
18−trim :: [TemplateElement] -> [TemplateElement]
19−trim = cleanse . canonicalize
20−
21−
22−--------------------------------------------------------------------------------
23−-- | Apply the Trim nodes to the Chunks.
24−cleanse :: [TemplateElement] -> [TemplateElement]
25−cleanse = recurse cleanse . process
26− where process [] = []
27− process (TrimR:Chunk str:ts) = let str' = dropWhile isSpace str
28− in if null str'
29− then process ts
30− -- Might need to TrimL.
31− else process $ Chunk str':ts
32−
33− process (Chunk str:TrimL:ts) = let str' = dropWhileEnd isSpace str
34− in if null str'
35− then process ts
36− else Chunk str':process ts
37−
38− process (t:ts) = t:process ts
39−
40−--------------------------------------------------------------------------------
41−-- | Enforce the invariant that:
42−--
43−-- * Every 'TrimL' has a 'Chunk' to its left.
44−-- * Every 'TrimR' has a 'Chunk' to its right.
45−--
46−canonicalize :: [TemplateElement] -> [TemplateElement]
47−canonicalize = go
48− where go t = let t' = redundant . swap $ dedupe t
49− in if t == t' then t else go t'
50−
51−
52−--------------------------------------------------------------------------------
53−-- | Remove the 'TrimR' and 'TrimL's that are no-ops.
54−redundant :: [TemplateElement] -> [TemplateElement]
55−redundant = recurse redundant . process
56− where -- Remove the leading 'TrimL's.
57− process (TrimL:ts) = process ts
58− -- Remove trailing 'TrimR's.
59− process ts = foldr trailing [] ts
60− where trailing TrimR [] = []
61− trailing x xs = x:xs
62−
63−
64−--------------------------------------------------------------------------------
65−-- >>> swap $ [TrimR, TrimL]
66−-- [TrimL, TrimR]
67−swap :: [TemplateElement] -> [TemplateElement]
68−swap = recurse swap . process
69− where process [] = []
70− process (TrimR:TrimL:ts) = TrimL:process (TrimR:ts)
71− process (t:ts) = t:process ts
72−
73−
74−--------------------------------------------------------------------------------
75−-- | Remove 'TrimR' and 'TrimL' duplication.
76−dedupe :: [TemplateElement] -> [TemplateElement]
77−dedupe = recurse dedupe . process
78− where process [] = []
79− process (TrimR:TrimR:ts) = process (TrimR:ts)
80− process (TrimL:TrimL:ts) = process (TrimL:ts)
81− process (t:ts) = t:process ts
82−
83−
84−--------------------------------------------------------------------------------
85−-- | @'recurse' f t@ applies f to every '[TemplateElement]' in t.
86−recurse :: ([TemplateElement] -> [TemplateElement])
87− -> [TemplateElement]
88− -> [TemplateElement]
89−recurse _ [] = []
90−recurse f (x:xs) = process x:recurse f xs
91− where process y = case y of
92− If e tb eb -> If e (f tb) (f <$> eb)
93− For e t s -> For e (f t) (f <$> s)
94− _ -> y
95−
lib/Hakyll.hs → src/Hakyll.hsrenamed+3−7
@@ -20,17 +20,15 @@ module Hakyll
2020 , module Hakyll.Web.Feed
2121 , module Hakyll.Web.Html
2222 , module Hakyll.Web.Html.RelativizeUrls
23− , module Hakyll.Web.Paginate
24−#ifdef USE_PANDOC
2523 , module Hakyll.Web.Pandoc
2624 , module Hakyll.Web.Pandoc.Biblio
2725 , module Hakyll.Web.Pandoc.FileType
28−#endif
29− , module Hakyll.Web.Redirect
3026 , module Hakyll.Web.Tags
27+ , module Hakyll.Web.Paginate
3128 , module Hakyll.Web.Template
3229 , module Hakyll.Web.Template.Context
3330 , module Hakyll.Web.Template.List
31+ , module Hakyll.Web.Template.Read
3432 ) where
3533
3634
@@ -54,13 +52,11 @@ import Hakyll.Web.Feed
5452import Hakyll.Web.Html
5553import Hakyll.Web.Html.RelativizeUrls
5654import Hakyll.Web.Paginate
57−#ifdef USE_PANDOC
5855import Hakyll.Web.Pandoc
5956import Hakyll.Web.Pandoc.Biblio
6057import Hakyll.Web.Pandoc.FileType
61−#endif
62−import Hakyll.Web.Redirect
6358import Hakyll.Web.Tags
6459import Hakyll.Web.Template
6560import Hakyll.Web.Template.Context
6661import Hakyll.Web.Template.List
62+import Hakyll.Web.Template.Read
src/Hakyll/Check.hsnew file+251
@@ -0,0 +1,251 @@
1+--------------------------------------------------------------------------------
2+{-# LANGUAGE CPP #-}
3+{-# LANGUAGE OverloadedStrings #-}
4+module Hakyll.Check
5+ ( Check (..)
6+ , check
7+ ) where
8+
9+
10+--------------------------------------------------------------------------------
11+import Control.Applicative ((<$>))
12+import Control.Monad (forM_)
13+import Control.Monad.Reader (ask)
14+import Control.Monad.RWS (RWST, runRWST)
15+import Control.Monad.Trans (liftIO)
16+import Control.Monad.Writer (tell)
17+import Data.List (isPrefixOf)
18+import Data.Monoid (Monoid (..))
19+import Data.Set (Set)
20+import qualified Data.Set as S
21+import Network.URI (unEscapeString)
22+import System.Directory (doesDirectoryExist, doesFileExist)
23+import System.Exit (ExitCode (..))
24+import System.FilePath (takeDirectory, takeExtension, (</>))
25+import qualified Text.HTML.TagSoup as TS
26+
27+
28+--------------------------------------------------------------------------------
29+#ifdef CHECK_EXTERNAL
30+import Control.Exception (AsyncException (..),
31+ SomeException (..), handle, throw)
32+import Control.Monad.State (get, modify)
33+import Data.List (intercalate)
34+import Data.Typeable (cast)
35+import Data.Version (versionBranch)
36+import GHC.Exts (fromString)
37+import qualified Network.HTTP.Conduit as Http
38+import qualified Network.HTTP.Types as Http
39+import qualified Paths_hakyll as Paths_hakyll
40+#endif
41+
42+
43+--------------------------------------------------------------------------------
44+import Hakyll.Core.Configuration
45+import Hakyll.Core.Logger (Logger, Verbosity)
46+import qualified Hakyll.Core.Logger as Logger
47+import Hakyll.Core.Util.File
48+import Hakyll.Web.Html
49+
50+
51+--------------------------------------------------------------------------------
52+data Check = All | InternalLinks
53+ deriving (Eq, Ord, Show)
54+
55+
56+--------------------------------------------------------------------------------
57+check :: Configuration -> Verbosity -> Check -> IO ExitCode
58+check config verbosity check' = do
59+ ((), write) <- runChecker checkDestination config verbosity check'
60+ return $ if checkerFaulty write >= 0 then ExitFailure 1 else ExitSuccess
61+
62+
63+--------------------------------------------------------------------------------
64+data CheckerRead = CheckerRead
65+ { checkerConfig :: Configuration
66+ , checkerLogger :: Logger
67+ , checkerCheck :: Check
68+ }
69+
70+
71+--------------------------------------------------------------------------------
72+data CheckerWrite = CheckerWrite
73+ { checkerFaulty :: Int
74+ , checkerOk :: Int
75+ } deriving (Show)
76+
77+
78+--------------------------------------------------------------------------------
79+instance Monoid CheckerWrite where
80+ mempty = CheckerWrite 0 0
81+ mappend (CheckerWrite f1 o1) (CheckerWrite f2 o2) =
82+ CheckerWrite (f1 + f2) (o1 + o2)
83+
84+
85+--------------------------------------------------------------------------------
86+type CheckerState = Set String
87+
88+
89+--------------------------------------------------------------------------------
90+type Checker a = RWST CheckerRead CheckerWrite CheckerState IO a
91+
92+
93+--------------------------------------------------------------------------------
94+runChecker :: Checker a -> Configuration -> Verbosity -> Check
95+ -> IO (a, CheckerWrite)
96+runChecker checker config verbosity check' = do
97+ logger <- Logger.new verbosity
98+ let read' = CheckerRead
99+ { checkerConfig = config
100+ , checkerLogger = logger
101+ , checkerCheck = check'
102+ }
103+
104+ (x, _, write) <- runRWST checker read' S.empty
105+ Logger.flush logger
106+ return (x, write)
107+
108+
109+--------------------------------------------------------------------------------
110+checkDestination :: Checker ()
111+checkDestination = do
112+ config <- checkerConfig <$> ask
113+ files <- liftIO $ getRecursiveContents
114+ (const $ return False) (destinationDirectory config)
115+
116+ let htmls =
117+ [ destinationDirectory config </> file
118+ | file <- files
119+ , takeExtension file == ".html"
120+ ]
121+
122+ forM_ htmls checkFile
123+
124+
125+--------------------------------------------------------------------------------
126+checkFile :: FilePath -> Checker ()
127+checkFile filePath = do
128+ logger <- checkerLogger <$> ask
129+ contents <- liftIO $ readFile filePath
130+ Logger.header logger $ "Checking file " ++ filePath
131+
132+ let urls = getUrls $ TS.parseTags contents
133+ forM_ urls $ \url -> do
134+ Logger.debug logger $ "Checking link " ++ url
135+ checkUrl filePath url
136+
137+
138+--------------------------------------------------------------------------------
139+checkUrl :: FilePath -> String -> Checker ()
140+checkUrl filePath url
141+ | isExternal url = checkExternalUrl url
142+ | hasProtocol url = skip "Unknown protocol, skipping"
143+ | otherwise = checkInternalUrl filePath url
144+ where
145+ validProtoChars = ['A'..'Z'] ++ ['a'..'z'] ++ ['0'..'9'] ++ "+-."
146+ hasProtocol str = case break (== ':') str of
147+ (proto, ':' : _) -> all (`elem` validProtoChars) proto
148+ _ -> False
149+
150+
151+--------------------------------------------------------------------------------
152+ok :: String -> Checker ()
153+ok _ = tell $ mempty {checkerOk = 1}
154+
155+
156+--------------------------------------------------------------------------------
157+skip :: String -> Checker ()
158+skip reason = do
159+ logger <- checkerLogger <$> ask
160+ Logger.debug logger $ reason
161+ tell $ mempty {checkerOk = 1}
162+
163+--------------------------------------------------------------------------------
164+faulty :: String -> Checker ()
165+faulty url = do
166+ logger <- checkerLogger <$> ask
167+ Logger.error logger $ "Broken link to " ++ show url
168+ tell $ mempty {checkerFaulty = 1}
169+
170+
171+--------------------------------------------------------------------------------
172+checkInternalUrl :: FilePath -> String -> Checker ()
173+checkInternalUrl base url = case url' of
174+ "" -> ok url
175+ _ -> do
176+ config <- checkerConfig <$> ask
177+ let dest = destinationDirectory config
178+ dir = takeDirectory base
179+ filePath
180+ | "/" `isPrefixOf` url' = dest ++ url'
181+ | otherwise = dir </> url'
182+
183+ exists <- checkFileExists filePath
184+ if exists then ok url else faulty url
185+ where
186+ url' = stripFragments $ unEscapeString url
187+
188+
189+--------------------------------------------------------------------------------
190+checkExternalUrl :: String -> Checker ()
191+#ifdef CHECK_EXTERNAL
192+checkExternalUrl url = do
193+ logger <- checkerLogger <$> ask
194+ needsCheck <- (== All) . checkerCheck <$> ask
195+ checked <- (url `S.member`) <$> get
196+
197+ if not needsCheck || checked
198+ then Logger.debug logger "Already checked, skipping"
199+ else do
200+ isOk <- liftIO $ handle (failure logger) $
201+ Http.withManager $ \mgr -> do
202+ request <- Http.parseUrl urlToCheck
203+ response <- Http.http (settings request) mgr
204+ let code = Http.statusCode (Http.responseStatus response)
205+ return $ code >= 200 && code < 300
206+
207+ modify $ if schemeRelative url
208+ then S.insert urlToCheck . S.insert url
209+ else S.insert url
210+ if isOk then ok url else faulty url
211+ where
212+ -- Add additional request info
213+ settings r = r
214+ { Http.method = "HEAD"
215+ , Http.redirectCount = 10
216+ , Http.requestHeaders = ("User-Agent", ua) : Http.requestHeaders r
217+ }
218+
219+ -- Nice user agent info
220+ ua = fromString $ "hakyll-check/" ++
221+ (intercalate "." $ map show $ versionBranch $ Paths_hakyll.version)
222+
223+ -- Catch all the things except UserInterrupt
224+ failure logger (SomeException e) = case cast e of
225+ Just UserInterrupt -> throw UserInterrupt
226+ _ -> Logger.error logger (show e) >> return False
227+
228+ -- Check scheme-relative links
229+ schemeRelative = isPrefixOf "//"
230+ urlToCheck = if schemeRelative url then "http:" ++ url else url
231+#else
232+checkExternalUrl _ = return ()
233+#endif
234+
235+
236+
237+--------------------------------------------------------------------------------
238+-- | Wraps doesFileExist, also checks for index.html
239+checkFileExists :: FilePath -> Checker Bool
240+checkFileExists filePath = liftIO $ do
241+ file <- doesFileExist filePath
242+ dir <- doesDirectoryExist filePath
243+ case (file, dir) of
244+ (True, _) -> return True
245+ (_, True) -> doesFileExist $ filePath </> "index.html"
246+ _ -> return False
247+
248+
249+--------------------------------------------------------------------------------
250+stripFragments :: String -> String
251+stripFragments = takeWhile (not . flip elem ['?', '#'])
lib/Hakyll/Commands.hs → src/Hakyll/Commands.hsrenamed+36−48
@@ -2,82 +2,72 @@
22-- | Implementation of Hakyll commands: build, preview...
33{-# LANGUAGE CPP #-}
44module Hakyll.Commands
5− ( Check(..)
6− , build
5+ ( build
76 , check
87 , clean
98 , preview
109 , rebuild
1110 , server
1211 , deploy
13− , watch
12+ , watch
1413 ) where
1514
1615
1716--------------------------------------------------------------------------------
17+import System.Exit (exitWith, ExitCode)
18+import Control.Applicative
1819import Control.Concurrent
19−import System.Exit (ExitCode)
20−
2120
2221--------------------------------------------------------------------------------
23−import Hakyll.Check (Check(..))
2422import qualified Hakyll.Check as Check
2523import Hakyll.Core.Configuration
26−import Hakyll.Core.Logger (Logger)
27−import qualified Hakyll.Core.Logger as Logger
24+import Hakyll.Core.Logger (Verbosity)
2825import Hakyll.Core.Rules
2926import Hakyll.Core.Rules.Internal
3027import Hakyll.Core.Runtime
3128import Hakyll.Core.Util.File
3229
33−
3430--------------------------------------------------------------------------------
3531#ifdef WATCH_SERVER
36−import Hakyll.Preview.Poll (watchUpdates)
32+import Hakyll.Preview.Poll (watchUpdates)
3733#endif
3834
3935#ifdef PREVIEW_SERVER
4036import Hakyll.Preview.Server
4137#endif
4238
43−#ifdef mingw32_HOST_OS
44−import Control.Monad (void)
45−import System.IO.Error (catchIOError)
46−#endif
47−
4839
4940--------------------------------------------------------------------------------
5041-- | Build the site
51−build :: Configuration -> Logger -> Rules a -> IO ExitCode
52−build conf logger rules = fst <$> run conf logger rules
53−
42+build :: Configuration -> Verbosity -> Rules a -> IO ExitCode
43+build conf verbosity rules = fst <$> run conf verbosity rules
5444
5545--------------------------------------------------------------------------------
5646-- | Run the checker and exit
57−check :: Configuration -> Logger -> Check.Check -> IO ExitCode
58−check = Check.check
47+check :: Configuration -> Verbosity -> Check.Check -> IO ()
48+check config verbosity check' = Check.check config verbosity check' >>= exitWith
5949
6050
6151--------------------------------------------------------------------------------
6252-- | Remove the output directories
63−clean :: Configuration -> Logger -> IO ()
64−clean conf logger = do
53+clean :: Configuration -> IO ()
54+clean conf = do
6555 remove $ destinationDirectory conf
6656 remove $ storeDirectory conf
6757 remove $ tmpDirectory conf
6858 where
6959 remove dir = do
70− Logger.header logger $ "Removing " ++ dir ++ "..."
60+ putStrLn $ "Removing " ++ dir ++ "..."
7161 removeDirectory dir
7262
7363
7464--------------------------------------------------------------------------------
7565-- | Preview the site
76−preview :: Configuration -> Logger -> Rules a -> Int -> IO ()
66+preview :: Configuration -> Verbosity -> Rules a -> Int -> IO ()
7767#ifdef PREVIEW_SERVER
78−preview conf logger rules port = do
68+preview conf verbosity rules port = do
7969 deprecatedMessage
80− watch conf logger "0.0.0.0" port True rules
70+ watch conf verbosity port True rules
8171 where
8272 deprecatedMessage = mapM_ putStrLn [ "The preview command has been deprecated."
8373 , "Use the watch command for recompilation and serving."
@@ -90,44 +80,41 @@ preview _ _ _ _ = previewServerDisabled
9080--------------------------------------------------------------------------------
9181-- | Watch and recompile for changes
9282
93−watch :: Configuration -> Logger -> String -> Int -> Bool -> Rules a -> IO ()
83+watch :: Configuration -> Verbosity -> Int -> Bool -> Rules a -> IO ()
9484#ifdef WATCH_SERVER
95−watch conf logger host port runServer rules = do
96−#ifndef mingw32_HOST_OS
97− _ <- forkIO $ watchUpdates conf update
98−#else
99− -- Force windows users to compile with -threaded flag, as otherwise
100− -- thread is blocked indefinitely.
101− catchIOError (void $ forkOS $ watchUpdates conf update) $ do
102− fail $ "Hakyll.Commands.watch: Could not start update watching " ++
103− "thread. Did you compile with -threaded flag?"
104−#endif
105− server'
85+watch conf verbosity port runServer rules = do
86+ watchUpdates conf update
87+ _ <- forkIO (server')
88+ loop
10689 where
10790 update = do
108− (_, ruleSet) <- run conf logger rules
91+ (_, ruleSet) <- run conf verbosity rules
10992 return $ rulesPattern ruleSet
93+
11094 loop = threadDelay 100000 >> loop
111− server' = if runServer then server conf logger host port else loop
95+
96+ server' = if runServer then server conf port else return ()
11297#else
113−watch _ _ _ _ _ _ = watchServerDisabled
98+watch _ _ _ _ _ = watchServerDisabled
11499#endif
115100
116101--------------------------------------------------------------------------------
117102-- | Rebuild the site
118−rebuild :: Configuration -> Logger -> Rules a -> IO ExitCode
119−rebuild conf logger rules =
120− clean conf logger >> build conf logger rules
103+rebuild :: Configuration -> Verbosity -> Rules a -> IO ExitCode
104+rebuild conf verbosity rules =
105+ clean conf >> build conf verbosity rules
121106
122107--------------------------------------------------------------------------------
123108-- | Start a server
124−server :: Configuration -> Logger -> String -> Int -> IO ()
109+server :: Configuration -> Int -> IO ()
125110#ifdef PREVIEW_SERVER
126−server conf logger host port = do
111+server conf port = do
127112 let destination = destinationDirectory conf
128− staticServer logger destination host port
113+ staticServer destination preServeHook port
114+ where
115+ preServeHook _ = return ()
129116#else
130−server _ _ _ _ = previewServerDisabled
117+server _ _ = previewServerDisabled
131118#endif
132119
133120
@@ -162,3 +149,4 @@ watchServerDisabled =
162149 , "Alternatively, use an external tool to serve your site directory."
163150 ]
164151#endif
152+
lib/Hakyll/Core/Compiler.hs → src/Hakyll/Core/Compiler.hsrenamed+7−51
@@ -24,16 +24,14 @@ module Hakyll.Core.Compiler
2424 , cached
2525 , unsafeCompiler
2626 , debugCompiler
27− , noResult
28− , withErrorMessage
2927 ) where
3028
3129
3230--------------------------------------------------------------------------------
33−import Control.Monad (unless, when, (>=>))
31+import Control.Applicative ((<$>))
32+import Control.Monad (when)
3433import Data.Binary (Binary)
3534import Data.ByteString.Lazy (ByteString)
36−import qualified Data.List.NonEmpty as NonEmpty
3735import Data.Typeable (Typeable)
3836import System.Environment (getProgName)
3937import System.FilePath (takeExtension)
@@ -65,7 +63,6 @@ getUnderlyingExtension = takeExtension . toFilePath <$> getUnderlying
6563
6664
6765--------------------------------------------------------------------------------
68−-- | Create an item from the underlying identifier and a given value.
6966makeItem :: a -> Compiler (Item a)
7067makeItem x = do
7168 identifier <- getUnderlying
@@ -86,20 +83,19 @@ getRoute identifier = do
8683
8784
8885--------------------------------------------------------------------------------
89−-- | Get the full contents of the matched source file as a string,
90−-- but without metadata preamble, if there was one.
86+-- | Get the body of the underlying resource
9187getResourceBody :: Compiler (Item String)
9288getResourceBody = getResourceWith resourceBody
9389
9490
9591--------------------------------------------------------------------------------
96−-- | Get the full contents of the matched source file as a string.
92+-- | Get the resource we are compiling as a string
9793getResourceString :: Compiler (Item String)
9894getResourceString = getResourceWith resourceString
9995
10096
10197--------------------------------------------------------------------------------
102−-- | Get the full contents of the matched source file as a lazy bytestring.
98+-- | Get the resource we are compiling as a lazy bytestring
10399getResourceLBS :: Compiler (Item ByteString)
104100getResourceLBS = getResourceWith resourceLBS
105101
@@ -139,16 +135,10 @@ saveSnapshot snapshot item = do
139135 compilerUnsafeIO $ do
140136 Logger.debug logger $ "Storing snapshot: " ++ snapshot
141137 Internal.saveSnapshot store snapshot item
142−
143− -- Signal that we saved the snapshot.
144− Compiler $ \_ -> return $ CompilerSnapshot snapshot (return item)
138+ return item
145139
146140
147141--------------------------------------------------------------------------------
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.
152142cached :: (Binary a, Typeable a)
153143 => String
154144 -> Compiler a
@@ -157,10 +147,6 @@ cached name compiler = do
157147 id' <- compilerUnderlying <$> compilerAsk
158148 store <- compilerStore <$> compilerAsk
159149 provider <- compilerProvider <$> compilerAsk
160−
161− -- Give a better error message when the resource is not there at all.
162− unless (resourceExists provider id') $ fail $ itDoesntEvenExist id'
163−
164150 let modified = resourceModified provider id'
165151 if modified
166152 then do
@@ -178,44 +164,14 @@ cached name compiler = do
178164 "Hakyll.Core.Compiler.cached: Cache corrupt! " ++
179165 "Try running: " ++ progName ++ " clean"
180166
181− itDoesntEvenExist id' =
182− "Hakyll.Core.Compiler.cached: You are trying to (perhaps " ++
183− "indirectly) use `cached` on a non-existing resource: there " ++
184− "is no file backing " ++ show id'
185−
186167
187168--------------------------------------------------------------------------------
188−-- | Run an IO computation without dependencies in a Compiler
189169unsafeCompiler :: IO a -> Compiler a
190170unsafeCompiler = compilerUnsafeIO
191171
192172
193173--------------------------------------------------------------------------------
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.
174+-- | Compiler for debugging purposes
219175debugCompiler :: String -> Compiler ()
220176debugCompiler msg = do
221177 logger <- compilerLogger <$> compilerAsk
lib/Hakyll/Core/Compiler/Internal.hs → src/Hakyll/Core/Compiler/Internal.hsrenamed+52−164
@@ -1,51 +1,38 @@
11--------------------------------------------------------------------------------
22-- | Internally used compiler module
3−{-# LANGUAGE CPP #-}
4−{-# LANGUAGE DeriveFunctor #-}
53{-# LANGUAGE FlexibleInstances #-}
64{-# LANGUAGE GADTs #-}
75{-# LANGUAGE GeneralizedNewtypeDeriving #-}
86{-# LANGUAGE MultiParamTypeClasses #-}
97module Hakyll.Core.Compiler.Internal
108 ( -- * Types
11− Snapshot
12− , CompilerRead (..)
9+ CompilerRead (..)
1310 , CompilerWrite (..)
14− , CompilerErrors (..)
1511 , CompilerResult (..)
1612 , Compiler (..)
1713 , runCompiler
1814
1915 -- * Core operations
20− , compilerResult
2116 , compilerTell
2217 , compilerAsk
23− , compilerUnsafeIO
24−
25− -- * Error operations
2618 , compilerThrow
27− , compilerNoResult
2819 , compilerCatch
29− , compilerTry
30− , compilerErrorMessages
20+ , compilerResult
21+ , compilerUnsafeIO
3122
3223 -- * Utilities
33− , compilerDebugEntries
3424 , compilerTellDependencies
3525 , compilerTellCacheHits
3626 ) where
3727
3828
3929--------------------------------------------------------------------------------
40−import Control.Applicative (Alternative (..))
30+import Control.Applicative (Alternative (..),
31+ Applicative (..), (<$>))
4132import Control.Exception (SomeException, handle)
4233import Control.Monad (forM_)
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
34+import Control.Monad.Error (MonadError (..))
35+import Data.Monoid (Monoid (..))
4936import Data.Set (Set)
5037import qualified Data.Set as S
5138
@@ -55,6 +42,7 @@ import Hakyll.Core.Configuration
5542import Hakyll.Core.Dependencies
5643import Hakyll.Core.Identifier
5744import Hakyll.Core.Identifier.Pattern
45+import Hakyll.Core.Logger (Logger)
5846import qualified Hakyll.Core.Logger as Logger
5947import Hakyll.Core.Metadata
6048import Hakyll.Core.Provider
@@ -62,12 +50,6 @@ import Hakyll.Core.Routes
6250import Hakyll.Core.Store
6351
6452
65−--------------------------------------------------------------------------------
66−-- | Whilst compiling an item, it possible to save multiple snapshots of it, and
67−-- not just the final result.
68−type Snapshot = String
69−
70−
7153--------------------------------------------------------------------------------
7254-- | Environment in which a compiler runs
7355data CompilerRead = CompilerRead
@@ -84,7 +66,7 @@ data CompilerRead = CompilerRead
8466 , -- | Compiler store
8567 compilerStore :: Store
8668 , -- | Logger
87− compilerLogger :: Logger.Logger
69+ compilerLogger :: Logger
8870 }
8971
9072
@@ -96,46 +78,17 @@ data CompilerWrite = CompilerWrite
9678
9779
9880--------------------------------------------------------------------------------
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
10881instance Monoid CompilerWrite where
10982 mempty = CompilerWrite [] 0
11083 mappend (CompilerWrite d1 h1) (CompilerWrite d2 h2) =
11184 CompilerWrite (d1 ++ d2) (h1 + h2)
112−#endif
113−
114−
115−--------------------------------------------------------------------------------
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
13085
13186
13287--------------------------------------------------------------------------------
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)
88+data CompilerResult a where
89+ CompilerDone :: a -> CompilerWrite -> CompilerResult a
90+ CompilerError :: [String] -> CompilerResult a
91+ CompilerRequire :: Identifier -> Compiler a -> CompilerResult a
13992
14093
14194--------------------------------------------------------------------------------
@@ -151,16 +104,15 @@ instance Functor Compiler where
151104 fmap f (Compiler c) = Compiler $ \r -> do
152105 res <- c r
153106 return $ case res of
154− CompilerDone x w -> CompilerDone (f x) w
155− CompilerSnapshot s c' -> CompilerSnapshot s (fmap f c')
156− CompilerRequire i c' -> CompilerRequire i (fmap f c')
157− CompilerError e -> CompilerError e
107+ CompilerDone x w -> CompilerDone (f x) w
108+ CompilerError e -> CompilerError e
109+ CompilerRequire i c' -> CompilerRequire i (fmap f c')
158110 {-# INLINE fmap #-}
159111
160112
161113--------------------------------------------------------------------------------
162114instance Monad Compiler where
163− return x = compilerResult $ CompilerDone x mempty
115+ return x = Compiler $ \_ -> return $ CompilerDone x mempty
164116 {-# INLINE return #-}
165117
166118 Compiler c >>= f = Compiler $ \r -> do
@@ -169,18 +121,14 @@ instance Monad Compiler where
169121 CompilerDone x w -> do
170122 res' <- unCompiler (f x) r
171123 return $ case res' of
172− CompilerDone y w' -> CompilerDone y (w `mappend` w')
173− CompilerSnapshot s c' -> CompilerSnapshot s $ do
124+ CompilerDone y w' -> CompilerDone y (w `mappend` w')
125+ CompilerError e -> CompilerError e
126+ CompilerRequire i c' -> CompilerRequire i $ do
174127 compilerTell w -- Save dependencies!
175128 c'
176− CompilerRequire i c' -> CompilerRequire i $ do
177− compilerTell w -- Save dependencies!
178− c'
179− CompilerError e -> CompilerError e
180129
181− CompilerSnapshot s c' -> return $ CompilerSnapshot s (c' >>= f)
182− CompilerRequire i c' -> return $ CompilerRequire i (c' >>= f)
183− CompilerError e -> return $ CompilerError e
130+ CompilerError e -> return $ CompilerError e
131+ CompilerRequire i c' -> return $ CompilerRequire i $ c' >>= f
184132 {-# INLINE (>>=) #-}
185133
186134 fail = compilerThrow . return
@@ -197,145 +145,86 @@ instance Applicative Compiler where
197145
198146
199147--------------------------------------------------------------------------------
200−-- | Access provided metadata from anywhere
201148instance MonadMetadata Compiler where
202149 getMetadata = compilerGetMetadata
203150 getMatches = compilerGetMatches
204151
205152
206153--------------------------------------------------------------------------------
207−-- | Compilation may fail with multiple error messages.
208−-- 'catchError' handles errors from 'throwError', 'fail' and 'Hakyll.Core.Compiler.noResult'
209154instance MonadError [String] Compiler where
210− throwError = compilerThrow
211− catchError c = compilerCatch c . (. compilerErrorMessages)
155+ throwError = compilerThrow
156+ catchError = compilerCatch
212157
213158
214159--------------------------------------------------------------------------------
215−-- | Like 'unCompiler' but treating IO exceptions as 'CompilerError's
216160runCompiler :: Compiler a -> CompilerRead -> IO (CompilerResult a)
217161runCompiler compiler read' = handle handler $ unCompiler compiler read'
218162 where
219163 handler :: SomeException -> IO (CompilerResult a)
220− handler e = return $ CompilerError $ CompilationFailure $ show e :| []
164+ handler e = return $ CompilerError [show e]
221165
222166
223167--------------------------------------------------------------------------------
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.
227168instance Alternative Compiler where
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"
169+ empty = compilerThrow []
170+ x <|> y = compilerCatch x $ \es -> do
171+ logger <- compilerLogger <$> compilerAsk
172+ forM_ es $ \e -> compilerUnsafeIO $ Logger.debug logger $
173+ "Hakyll.Core.Compiler.Internal: Alternative failed: " ++ e
174+ y
241175 {-# INLINE (<|>) #-}
242176
243177
244178--------------------------------------------------------------------------------
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
253179compilerAsk :: Compiler CompilerRead
254180compilerAsk = Compiler $ \r -> return $ CompilerDone r mempty
255181{-# INLINE compilerAsk #-}
256182
257183
258184--------------------------------------------------------------------------------
259−-- | Put a 'CompilerWrite'
260185compilerTell :: CompilerWrite -> Compiler ()
261−compilerTell = compilerResult . CompilerDone ()
186+compilerTell deps = Compiler $ \_ -> return $ CompilerDone () deps
262187{-# INLINE compilerTell #-}
263188
264189
265190--------------------------------------------------------------------------------
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.
279191compilerThrow :: [String] -> Compiler a
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
287−
288−
289−--------------------------------------------------------------------------------
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 #-}
192+compilerThrow es = Compiler $ \_ -> return $ CompilerError es
193+{-# INLINE compilerThrow #-}
302194
303195
304196--------------------------------------------------------------------------------
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
197+compilerCatch :: Compiler a -> ([String] -> Compiler a) -> Compiler a
310198compilerCatch (Compiler x) f = Compiler $ \r -> do
311199 res <- x r
312200 case res of
313− CompilerDone res' w -> return (CompilerDone res' w)
314− CompilerSnapshot s c -> return (CompilerSnapshot s (compilerCatch c f))
315− CompilerRequire i c -> return (CompilerRequire i (compilerCatch c f))
316− CompilerError e -> unCompiler (f e) r
201+ CompilerDone res' w -> return (CompilerDone res' w)
202+ CompilerError e -> unCompiler (f e) r
203+ CompilerRequire i c -> return (CompilerRequire i (compilerCatch c f))
317204{-# INLINE compilerCatch #-}
318205
319206
320207--------------------------------------------------------------------------------
321−compilerDebugLog :: [String] -> Compiler ()
322−compilerDebugLog ms = do
323− logger <- compilerLogger <$> compilerAsk
324− compilerUnsafeIO $ forM_ ms $ Logger.debug logger
208+-- | Put the result back in a compiler
209+compilerResult :: CompilerResult a -> Compiler a
210+compilerResult x = Compiler $ \_ -> return x
211+{-# INLINE compilerResult #-}
212+
325213
326214--------------------------------------------------------------------------------
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
215+compilerUnsafeIO :: IO a -> Compiler a
216+compilerUnsafeIO io = Compiler $ \_ -> do
217+ x <- io
218+ return $ CompilerDone x mempty
219+{-# INLINE compilerUnsafeIO #-}
332220
333221
334222--------------------------------------------------------------------------------
335223compilerTellDependencies :: [Dependency] -> Compiler ()
336224compilerTellDependencies ds = do
337− compilerDebugLog $ map (\d ->
338− "Hakyll.Core.Compiler.Internal: Adding dependency: " ++ show d) ds
225+ logger <- compilerLogger <$> compilerAsk
226+ forM_ ds $ \d -> compilerUnsafeIO $ Logger.debug logger $
227+ "Hakyll.Core.Compiler.Internal: Adding dependency: " ++ show d
339228 compilerTell mempty {compilerDependencies = ds}
340229{-# INLINE compilerTellDependencies #-}
341230
@@ -359,6 +248,5 @@ compilerGetMatches :: Pattern -> Compiler [Identifier]
359248compilerGetMatches pattern = do
360249 universe <- compilerUniverse <$> compilerAsk
361250 let matching = filterMatches pattern $ S.toList universe
362− set' = S.fromList matching
363− compilerTellDependencies [PatternDependency pattern set']
251+ compilerTellDependencies [PatternDependency pattern matching]
364252 return matching
lib/Hakyll/Core/Compiler/Require.hs → src/Hakyll/Core/Compiler/Require.hsrenamed+8−3
@@ -13,6 +13,7 @@ module Hakyll.Core.Compiler.Require
1313
1414
1515--------------------------------------------------------------------------------
16+import Control.Applicative ((<$>))
1617import Control.Monad (when)
1718import Data.Binary (Binary)
1819import qualified Data.Set as S
@@ -30,6 +31,12 @@ import Hakyll.Core.Store (Store)
3031import qualified Hakyll.Core.Store as Store
3132
3233
34+--------------------------------------------------------------------------------
35+-- | Whilst compiling an item, it possible to save multiple snapshots of it, and
36+-- not just the final result.
37+type Snapshot = String
38+
39+
3340--------------------------------------------------------------------------------
3441save :: (Binary a, Typeable a) => Store -> Item a -> IO ()
3542save store item = saveSnapshot store final item
@@ -63,7 +70,7 @@ loadSnapshot id' snapshot = do
6370 when (id' `S.notMember` universe) $ fail notFound
6471
6572 compilerTellDependencies [IdentifierDependency id']
66− compilerResult $ CompilerRequire (id', snapshot) $ do
73+ compilerResult $ CompilerRequire id' $ do
6774 result <- compilerUnsafeIO $ Store.get store (key id' snapshot)
6875 case result of
6976 Store.NotFound -> fail notFound
@@ -91,7 +98,6 @@ loadBody id' = loadSnapshotBody id' final
9198
9299
93100--------------------------------------------------------------------------------
94−-- | A shortcut for only requiring the body for a specific snapshot of an item
95101loadSnapshotBody :: (Binary a, Typeable a)
96102 => Identifier -> Snapshot -> Compiler a
97103loadSnapshotBody id' snapshot = fmap itemBody $ loadSnapshot id' snapshot
@@ -104,7 +110,6 @@ loadAll pattern = loadAllSnapshots pattern final
104110
105111
106112--------------------------------------------------------------------------------
107−-- | Load a specific snapshot for each of dynamic list of items
108113loadAllSnapshots :: (Binary a, Typeable a)
109114 => Pattern -> Snapshot -> Compiler [Item a]
110115loadAllSnapshots pattern snapshot = do
lib/Hakyll/Core/Configuration.hs → src/Hakyll/Core/Configuration.hsrenamed−6
@@ -69,11 +69,6 @@ data Configuration = Configuration
6969 , -- | Use an in-memory cache for items. This is faster but uses more
7070 -- memory.
7171 inMemoryCache :: Bool
72− , -- | Override default host for preview server. Default is "127.0.0.1",
73− -- which binds only on the loopback address.
74− -- One can also override the host as a command line argument:
75− -- ./site preview -h "0.0.0.0"
76− previewHost :: String
7772 , -- | Override default port for preview server. Default is 8000.
7873 -- One can also override the port as a command line argument:
7974 -- ./site preview -p 1234
@@ -96,7 +91,6 @@ defaultConfiguration = Configuration
9691 , deployCommand = "echo 'No deploy command specified' && exit 1"
9792 , deploySite = system . deployCommand
9893 , inMemoryCache = True
99− , previewHost = "127.0.0.1"
10094 , previewPort = 8000
10195 }
10296 where
lib/Hakyll/Core/Dependencies.hs → src/Hakyll/Core/Dependencies.hsrenamed+4−3
@@ -8,6 +8,7 @@ module Hakyll.Core.Dependencies
88
99
1010--------------------------------------------------------------------------------
11+import Control.Applicative ((<$>), (<*>))
1112import Control.Monad (foldM, forM_, unless, when)
1213import Control.Monad.Reader (ask)
1314import Control.Monad.RWS (RWS, runRWS)
@@ -31,7 +32,7 @@ import Hakyll.Core.Identifier.Pattern
3132
3233--------------------------------------------------------------------------------
3334data Dependency
34− = PatternDependency Pattern (Set Identifier)
35+ = PatternDependency Pattern [Identifier]
3536 | IdentifierDependency Identifier
3637 deriving (Show, Typeable)
3738
@@ -90,7 +91,7 @@ dependenciesFor id' = do
9091 return $ concatMap dependenciesFor' $ fromMaybe [] $ M.lookup id' facts
9192 where
9293 dependenciesFor' (IdentifierDependency i) = [i]
93− dependenciesFor' (PatternDependency _ is) = S.toList is
94+ dependenciesFor' (PatternDependency _ is) = is
9495
9596
9697--------------------------------------------------------------------------------
@@ -115,7 +116,7 @@ checkChangedPatterns = do
115116 go _ ds (IdentifierDependency i) = return $ IdentifierDependency i : ds
116117 go id' ds (PatternDependency p ls) = do
117118 universe <- ask
118− let ls' = S.fromList $ filterMatches p universe
119+ let ls' = filterMatches p universe
119120 if ls == ls'
120121 then return $ PatternDependency p ls : ds
121122 else do
lib/Hakyll/Core/File.hs → src/Hakyll/Core/File.hsrenamed+4−11
@@ -1,6 +1,5 @@
11--------------------------------------------------------------------------------
22-- | Exports simple compilers to just copy files
3−{-# LANGUAGE CPP #-}
43{-# LANGUAGE DeriveDataTypeable #-}
54{-# LANGUAGE GeneralizedNewtypeDeriving #-}
65module Hakyll.Core.File
@@ -12,14 +11,10 @@ module Hakyll.Core.File
1211
1312
1413--------------------------------------------------------------------------------
14+import Control.Applicative ((<$>))
1515import Data.Binary (Binary (..))
1616import Data.Typeable (Typeable)
17−#if MIN_VERSION_directory(1,2,6)
18−import System.Directory (copyFileWithMetadata)
19−#else
20−import System.Directory (copyFile)
21−#endif
22−import System.Directory (doesFileExist,
17+import System.Directory (copyFile, doesFileExist,
2318 renameFile)
2419import System.FilePath ((</>))
2520import System.Random (randomIO)
@@ -44,11 +39,9 @@ newtype CopyFile = CopyFile FilePath
4439
4540--------------------------------------------------------------------------------
4641instance Writable CopyFile where
47−#if MIN_VERSION_directory(1,2,6)
48− write dst (Item _ (CopyFile src)) = copyFileWithMetadata src dst
49−#else
5042 write dst (Item _ (CopyFile src)) = copyFile src dst
51−#endif
43+
44+
5245--------------------------------------------------------------------------------
5346copyFileCompiler :: Compiler (Item CopyFile)
5447copyFileCompiler = do
lib/Hakyll/Core/Identifier.hs → src/Hakyll/Core/Identifier.hsrenamed+2−1
@@ -19,6 +19,7 @@ module Hakyll.Core.Identifier
1919
2020
2121--------------------------------------------------------------------------------
22+import Control.Applicative ((<$>), (<*>))
2223import Control.DeepSeq (NFData (..))
2324import Data.List (intercalate)
2425import System.FilePath (dropTrailingPathSeparator, splitPath)
@@ -62,7 +63,7 @@ instance Show Identifier where
6263
6364--------------------------------------------------------------------------------
6465-- | Parse an identifier from a string
65−fromFilePath :: FilePath -> Identifier
66+fromFilePath :: String -> Identifier
6667fromFilePath = Identifier Nothing .
6768 intercalate "/" . filter (not . null) . split'
6869 where
lib/Hakyll/Core/Identifier/Pattern.hs → src/Hakyll/Core/Identifier/Pattern.hsrenamed+74−14
@@ -27,8 +27,7 @@
2727-- * @\"foo\/*.html\"@ will match all HTML files in the @\"foo\/\"@ directory.
2828--
2929-- The 'capture' function allows the user to get access to the elements captured
30−-- by the capture elements in a glob or regex pattern.
31−{-# OPTIONS_GHC -fno-warn-orphans #-}
30+-- by the capture elements in the pattern.
3231module Hakyll.Core.Identifier.Pattern
3332 ( -- * The pattern type
3433 Pattern
@@ -58,22 +57,79 @@ module Hakyll.Core.Identifier.Pattern
5857
5958
6059--------------------------------------------------------------------------------
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
60+import Control.Applicative (pure, (<$>), (<*>))
61+import Control.Arrow ((&&&), (>>>))
62+import Control.Monad (msum)
63+import Data.Binary (Binary (..), getWord8, putWord8)
64+import Data.List (inits, isPrefixOf, tails)
65+import Data.Maybe (isJust)
66+import Data.Monoid (Monoid, mappend, mempty)
67+import Data.Set (Set)
68+import qualified Data.Set as S
6769
6870
6971--------------------------------------------------------------------------------
70−import GHC.Exts (IsString, fromString)
71−import Text.Regex.TDFA ((=~))
72+import GHC.Exts (IsString, fromString)
73+import Text.Regex.TDFA ((=~))
7274
7375
7476--------------------------------------------------------------------------------
7577import Hakyll.Core.Identifier
76−import Hakyll.Core.Identifier.Pattern.Internal
78+
79+
80+--------------------------------------------------------------------------------
81+-- | Elements of a glob pattern
82+data GlobComponent
83+ = Capture
84+ | CaptureMany
85+ | Literal String
86+ deriving (Eq, Show)
87+
88+
89+--------------------------------------------------------------------------------
90+instance Binary GlobComponent where
91+ put Capture = putWord8 0
92+ put CaptureMany = putWord8 1
93+ put (Literal s) = putWord8 2 >> put s
94+
95+ get = getWord8 >>= \t -> case t of
96+ 0 -> pure Capture
97+ 1 -> pure CaptureMany
98+ 2 -> Literal <$> get
99+ _ -> error "Data.Binary.get: Invalid GlobComponent"
100+
101+
102+--------------------------------------------------------------------------------
103+-- | Type that allows matching on identifiers
104+data Pattern
105+ = Everything
106+ | Complement Pattern
107+ | And Pattern Pattern
108+ | Glob [GlobComponent]
109+ | List (Set Identifier)
110+ | Regex String
111+ | Version (Maybe String)
112+ deriving (Show)
113+
114+
115+--------------------------------------------------------------------------------
116+instance Binary Pattern where
117+ put Everything = putWord8 0
118+ put (Complement p) = putWord8 1 >> put p
119+ put (And x y) = putWord8 2 >> put x >> put y
120+ put (Glob g) = putWord8 3 >> put g
121+ put (List is) = putWord8 4 >> put is
122+ put (Regex r) = putWord8 5 >> put r
123+ put (Version v) = putWord8 6 >> put v
124+
125+ get = getWord8 >>= \t -> case t of
126+ 0 -> pure Everything
127+ 1 -> Complement <$> get
128+ 2 -> And <$> get <*> get
129+ 3 -> Glob <$> get
130+ 4 -> List <$> get
131+ 5 -> Regex <$> get
132+ _ -> Version <$> get
77133
78134
79135--------------------------------------------------------------------------------
@@ -81,6 +137,12 @@ instance IsString Pattern where
81137 fromString = fromGlob
82138
83139
140+--------------------------------------------------------------------------------
141+instance Monoid Pattern where
142+ mempty = Everything
143+ mappend = (.&&.)
144+
145+
84146--------------------------------------------------------------------------------
85147-- | Parse a pattern from a string
86148fromGlob :: String -> Pattern
@@ -200,11 +262,9 @@ splits = inits &&& tails >>> uncurry zip >>> reverse
200262
201263
202264--------------------------------------------------------------------------------
203−-- | Match a glob or regex pattern against an identifier, generating a list of captures
265+-- | Match a glob against a pattern, generating a list of captures
204266capture :: Pattern -> Identifier -> Maybe [String]
205267capture (Glob p) i = capture' p (toFilePath i)
206−capture (Regex pat) i = Just groups
207− where (_, _, _, groups) = ((toFilePath i) =~ pat) :: (String, String, String, [String])
208268capture _ _ = Nothing
209269
210270
lib/Hakyll/Core/Item.hs → src/Hakyll/Core/Item.hsrenamed+2
@@ -10,8 +10,10 @@ module Hakyll.Core.Item
1010
1111
1212--------------------------------------------------------------------------------
13+import Control.Applicative (Applicative, (<$>), (<*>))
1314import Data.Binary (Binary (..))
1415import Data.Foldable (Foldable (..))
16+import Data.Traversable (Traversable (..))
1517import Data.Typeable (Typeable)
1618import Prelude hiding (foldr)
1719
lib/Hakyll/Core/Item/SomeItem.hs → src/Hakyll/Core/Item/SomeItem.hsrenamed

The content is the same.

lib/Hakyll/Core/Logger.hs → src/Hakyll/Core/Logger.hsrenamed+4−9
@@ -13,12 +13,12 @@ module Hakyll.Core.Logger
1313
1414
1515--------------------------------------------------------------------------------
16+import Control.Applicative (pure, (<$>), (<*>))
1617import Control.Concurrent (forkIO)
1718import Control.Concurrent.Chan (Chan, newChan, readChan, writeChan)
1819import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, takeMVar)
1920import Control.Monad (forever)
2021import Control.Monad.Trans (MonadIO, liftIO)
21−import Data.List (intercalate)
2222import Prelude hiding (error)
2323
2424
@@ -80,7 +80,7 @@ string l v m
8080
8181--------------------------------------------------------------------------------
8282error :: MonadIO m => Logger -> String -> m ()
83−error l m = string l Error $ " [ERROR] " ++ indent m
83+error l m = string l Error $ " [ERROR] " ++ m
8484
8585
8686--------------------------------------------------------------------------------
@@ -90,14 +90,9 @@ header l = string l Message
9090
9191--------------------------------------------------------------------------------
9292message :: MonadIO m => Logger -> String -> m ()
93−message l m = string l Message $ " " ++ indent m
93+message l m = string l Message $ " " ++ m
9494
9595
9696--------------------------------------------------------------------------------
9797debug :: MonadIO m => Logger -> String -> m ()
98−debug l m = string l Debug $ " [DEBUG] " ++ indent m
99−
100−
101−--------------------------------------------------------------------------------
102−indent :: String -> String
103−indent = intercalate "\n " . lines
98+debug l m = string l Debug $ " [DEBUG] " ++ m
src/Hakyll/Core/Metadata.hsnew file+63
@@ -0,0 +1,63 @@
1+--------------------------------------------------------------------------------
2+module Hakyll.Core.Metadata
3+ ( Metadata
4+ , MonadMetadata (..)
5+ , getMetadataField
6+ , getMetadataField'
7+ , makePatternDependency
8+ ) where
9+
10+
11+--------------------------------------------------------------------------------
12+import Control.Monad (forM)
13+import Data.Map (Map)
14+import qualified Data.Map as M
15+
16+
17+--------------------------------------------------------------------------------
18+import Hakyll.Core.Dependencies
19+import Hakyll.Core.Identifier
20+import Hakyll.Core.Identifier.Pattern
21+
22+
23+--------------------------------------------------------------------------------
24+type Metadata = Map String String
25+
26+
27+--------------------------------------------------------------------------------
28+class Monad m => MonadMetadata m where
29+ getMetadata :: Identifier -> m Metadata
30+ getMatches :: Pattern -> m [Identifier]
31+
32+ getAllMetadata :: Pattern -> m [(Identifier, Metadata)]
33+ getAllMetadata pattern = do
34+ matches' <- getMatches pattern
35+ forM matches' $ \id' -> do
36+ metadata <- getMetadata id'
37+ return (id', metadata)
38+
39+
40+--------------------------------------------------------------------------------
41+getMetadataField :: MonadMetadata m => Identifier -> String -> m (Maybe String)
42+getMetadataField identifier key = do
43+ metadata <- getMetadata identifier
44+ return $ M.lookup key metadata
45+
46+
47+--------------------------------------------------------------------------------
48+-- | Version of 'getMetadataField' which throws an error if the field does not
49+-- exist.
50+getMetadataField' :: MonadMetadata m => Identifier -> String -> m String
51+getMetadataField' identifier key = do
52+ field <- getMetadataField identifier key
53+ case field of
54+ Just v -> return v
55+ Nothing -> fail $ "Hakyll.Core.Metadata.getMetadataField': " ++
56+ "Item " ++ show identifier ++ " has no metadata field " ++ show key
57+
58+
59+--------------------------------------------------------------------------------
60+makePatternDependency :: MonadMetadata m => Pattern -> m Dependency
61+makePatternDependency pattern = do
62+ matches' <- getMatches pattern
63+ return $ PatternDependency pattern matches'
lib/Hakyll/Core/Provider.hs → src/Hakyll/Core/Provider.hsrenamed

The content is the same.

lib/Hakyll/Core/Provider/Internal.hs → src/Hakyll/Core/Provider/Internal.hsrenamed+7−6
@@ -20,6 +20,7 @@ module Hakyll.Core.Provider.Internal
2020
2121
2222--------------------------------------------------------------------------------
23+import Control.Applicative ((<$>), (<*>))
2324import Control.DeepSeq (NFData (..), deepseq)
2425import Control.Monad (forM)
2526import Data.Binary (Binary (..))
@@ -27,9 +28,11 @@ import qualified Data.ByteString.Lazy as BL
2728import Data.Map (Map)
2829import qualified Data.Map as M
2930import Data.Maybe (fromMaybe)
31+import Data.Monoid (mempty)
3032import Data.Set (Set)
3133import qualified Data.Set as S
32−import Data.Time (Day (..), UTCTime (..))
34+import Data.Time (Day (..), UTCTime (..),
35+ secondsToDiffTime)
3336import Data.Typeable (Typeable)
3437import System.Directory (getModificationTime)
3538import System.FilePath (addExtension, (</>))
@@ -59,11 +62,11 @@ newtype BinaryTime = BinaryTime {unBinaryTime :: UTCTime}
5962--------------------------------------------------------------------------------
6063instance Binary BinaryTime where
6164 put (BinaryTime (UTCTime (ModifiedJulianDay d) dt)) =
62− put d >> put (toRational dt)
65+ put d >> put (floor dt :: Integer)
6366
6467 get = fmap BinaryTime $ UTCTime
6568 <$> (ModifiedJulianDay <$> get)
66− <*> (fromRational <$> get)
69+ <*> (secondsToDiffTime <$> get)
6770
6871
6972--------------------------------------------------------------------------------
@@ -171,9 +174,7 @@ resourceModified :: Provider -> Identifier -> Bool
171174resourceModified p r = case (ri, oldRi) of
172175 (Nothing, _) -> False
173176 (Just _, Nothing) -> True
174− (Just n, Just o) ->
175− resourceInfoModified n > resourceInfoModified o ||
176− resourceInfoMetadata n /= resourceInfoMetadata o
177+ (Just n, Just o) -> resourceInfoModified n > resourceInfoModified o
177178 where
178179 normal = setVersion Nothing r
179180 ri = M.lookup normal (providerFiles p)
src/Hakyll/Core/Provider/Metadata.hsnew file+135
@@ -0,0 +1,135 @@
1+--------------------------------------------------------------------------------
2+-- | Internal module to parse metadata
3+module Hakyll.Core.Provider.Metadata
4+ ( loadMetadata
5+ , metadata
6+ , page
7+
8+ -- This parser can be reused in some places
9+ , metadataKey
10+ ) where
11+
12+
13+--------------------------------------------------------------------------------
14+import Control.Applicative
15+import Control.Arrow (second)
16+import qualified Data.ByteString.Char8 as BC
17+import Data.List (intercalate)
18+import qualified Data.Map as M
19+import System.IO as IO
20+import Text.Parsec ((<?>))
21+import qualified Text.Parsec as P
22+import Text.Parsec.String (Parser)
23+
24+
25+--------------------------------------------------------------------------------
26+import Hakyll.Core.Identifier
27+import Hakyll.Core.Metadata
28+import Hakyll.Core.Provider.Internal
29+import Hakyll.Core.Util.Parser
30+import Hakyll.Core.Util.String
31+
32+
33+--------------------------------------------------------------------------------
34+loadMetadata :: Provider -> Identifier -> IO (Metadata, Maybe String)
35+loadMetadata p identifier = do
36+ hasHeader <- probablyHasMetadataHeader fp
37+ (md, body) <- if hasHeader
38+ then second Just <$> loadMetadataHeader fp
39+ else return (M.empty, Nothing)
40+
41+ emd <- case mi of
42+ Nothing -> return M.empty
43+ Just mi' -> loadMetadataFile $ resourceFilePath p mi'
44+
45+ return (M.union md emd, body)
46+ where
47+ normal = setVersion Nothing identifier
48+ fp = resourceFilePath p identifier
49+ mi = M.lookup normal (providerFiles p) >>= resourceInfoMetadata
50+
51+
52+--------------------------------------------------------------------------------
53+loadMetadataHeader :: FilePath -> IO (Metadata, String)
54+loadMetadataHeader fp = do
55+ contents <- readFile fp
56+ case P.parse page fp contents of
57+ Left err -> error (show err)
58+ Right (md, b) -> return (M.fromList md, b)
59+
60+
61+--------------------------------------------------------------------------------
62+loadMetadataFile :: FilePath -> IO Metadata
63+loadMetadataFile fp = do
64+ contents <- readFile fp
65+ case P.parse metadata fp contents of
66+ Left err -> error (show err)
67+ Right md -> return $ M.fromList md
68+
69+
70+--------------------------------------------------------------------------------
71+-- | Check if a file "probably" has a metadata header. The main goal of this is
72+-- to exclude binary files (which are unlikely to start with "---").
73+probablyHasMetadataHeader :: FilePath -> IO Bool
74+probablyHasMetadataHeader fp = do
75+ handle <- IO.openFile fp IO.ReadMode
76+ bs <- BC.hGet handle 1024
77+ IO.hClose handle
78+ return $ isMetadataHeader bs
79+ where
80+ isMetadataHeader bs =
81+ let pre = BC.takeWhile (\x -> x /= '\n' && x /= '\r') bs
82+ in BC.length pre >= 3 && BC.all (== '-') pre
83+
84+
85+--------------------------------------------------------------------------------
86+-- | Space or tab, no newline
87+inlineSpace :: Parser Char
88+inlineSpace = P.oneOf ['\t', ' '] <?> "space"
89+
90+
91+--------------------------------------------------------------------------------
92+-- | Parse Windows newlines as well (i.e. "\n" or "\r\n")
93+newline :: Parser String
94+newline = P.string "\n" <|> P.string "\r\n"
95+
96+
97+--------------------------------------------------------------------------------
98+-- | Parse a single metadata field
99+metadataField :: Parser (String, String)
100+metadataField = do
101+ key <- metadataKey
102+ _ <- P.char ':'
103+ P.skipMany1 inlineSpace <?> "space followed by metadata for: " ++ key
104+ value <- P.manyTill P.anyChar newline
105+ trailing' <- P.many trailing
106+ return (key, trim $ intercalate " " $ value : trailing')
107+ where
108+ trailing = P.many1 inlineSpace *> P.manyTill P.anyChar newline
109+
110+
111+--------------------------------------------------------------------------------
112+-- | Parse a metadata block
113+metadata :: Parser [(String, String)]
114+metadata = P.many metadataField
115+
116+
117+--------------------------------------------------------------------------------
118+-- | Parse a metadata block, including delimiters and trailing newlines
119+metadataBlock :: Parser [(String, String)]
120+metadataBlock = do
121+ open <- P.many1 (P.char '-') <* P.many inlineSpace <* newline
122+ metadata' <- metadata
123+ _ <- P.choice $ map (P.string . replicate (length open)) ['-', '.']
124+ P.skipMany inlineSpace
125+ P.skipMany1 newline
126+ return metadata'
127+
128+
129+--------------------------------------------------------------------------------
130+-- | Parse a page consisting of a metadata header and a body
131+page :: Parser ([(String, String)], String)
132+page = do
133+ metadata' <- P.option [] metadataBlock
134+ body <- P.many P.anyChar
135+ return (metadata', body)
lib/Hakyll/Core/Provider/MetadataCache.hs → src/Hakyll/Core/Provider/MetadataCache.hsrenamed+6−3
@@ -8,6 +8,9 @@ module Hakyll.Core.Provider.MetadataCache
88
99--------------------------------------------------------------------------------
1010import Control.Monad (unless)
11+import qualified Data.Map as M
12+
13+--------------------------------------------------------------------------------
1114import Hakyll.Core.Identifier
1215import Hakyll.Core.Metadata
1316import Hakyll.Core.Provider.Internal
@@ -18,11 +21,11 @@ import qualified Hakyll.Core.Store as Store
1821--------------------------------------------------------------------------------
1922resourceMetadata :: Provider -> Identifier -> IO Metadata
2023resourceMetadata p r
21− | not (resourceExists p r) = return mempty
24+ | not (resourceExists p r) = return M.empty
2225 | otherwise = do
2326 -- TODO keep time in md cache
2427 load p r
25− Store.Found (BinaryMetadata md) <- Store.get (providerStore p)
28+ Store.Found md <- Store.get (providerStore p)
2629 [name, toFilePath r, "metadata"]
2730 return md
2831
@@ -49,7 +52,7 @@ load p r = do
4952 mmof <- Store.isMember store mdk
5053 unless mmof $ do
5154 (md, body) <- loadMetadata p r
52− Store.set store mdk (BinaryMetadata md)
55+ Store.set store mdk md
5356 Store.set store bk body
5457 where
5558 store = providerStore p
lib/Hakyll/Core/Routes.hs → src/Hakyll/Core/Routes.hsrenamed+6−30
@@ -25,7 +25,6 @@
2525-- not appear in your site directory.
2626--
2727-- * If an item matches multiple routes, the first rule will be chosen.
28−{-# LANGUAGE CPP #-}
2928{-# LANGUAGE Rank2Types #-}
3029module Hakyll.Core.Routes
3130 ( UsedMetadata
@@ -43,9 +42,7 @@ module Hakyll.Core.Routes
4342
4443
4544--------------------------------------------------------------------------------
46−#if MIN_VERSION_base(4,9,0)
47−import Data.Semigroup (Semigroup (..))
48−#endif
45+import Data.Monoid (Monoid, mappend, mempty)
4946import System.FilePath (replaceExtension)
5047
5148
@@ -63,33 +60,14 @@ import Hakyll.Core.Util.String
6360type UsedMetadata = Bool
6461
6562
66−--------------------------------------------------------------------------------
67−data RoutesRead = RoutesRead
68− { routesProvider :: Provider
69− , routesUnderlying :: Identifier
70− }
71−
72−
7363--------------------------------------------------------------------------------
7464-- | Type used for a route
7565newtype Routes = Routes
76− { unRoutes :: RoutesRead -> Identifier -> IO (Maybe FilePath, UsedMetadata)
66+ { unRoutes :: Provider -> Identifier -> IO (Maybe FilePath, UsedMetadata)
7767 }
7868
7969
8070--------------------------------------------------------------------------------
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
9371instance Monoid Routes where
9472 mempty = Routes $ \_ _ -> return (Nothing, False)
9573 mappend (Routes f) (Routes g) = Routes $ \p id' -> do
@@ -97,15 +75,13 @@ instance Monoid Routes where
9775 case mfp of
9876 Nothing -> g p id'
9977 Just _ -> return (mfp, um)
100−#endif
10178
10279
10380--------------------------------------------------------------------------------
10481-- | Apply a route to an identifier
10582runRoutes :: Routes -> Provider -> Identifier
10683 -> IO (Maybe FilePath, UsedMetadata)
107−runRoutes routes provider identifier =
108− unRoutes routes (RoutesRead provider identifier) identifier
84+runRoutes = unRoutes
10985
11086
11187--------------------------------------------------------------------------------
@@ -180,9 +156,9 @@ gsubRoute pattern replacement = customRoute $
180156--------------------------------------------------------------------------------
181157-- | Get access to the metadata in order to determine the route
182158metadataRoute :: (Metadata -> Routes) -> Routes
183−metadataRoute f = Routes $ \r i -> do
184− metadata <- resourceMetadata (routesProvider r) (routesUnderlying r)
185− unRoutes (f metadata) r i
159+metadataRoute f = Routes $ \p i -> do
160+ metadata <- resourceMetadata p i
161+ unRoutes (f metadata) p i
186162
187163
188164--------------------------------------------------------------------------------
lib/Hakyll/Core/Rules.hs → src/Hakyll/Core/Rules.hsrenamed+8−39
@@ -19,7 +19,6 @@
1919module Hakyll.Core.Rules
2020 ( Rules
2121 , match
22− , matchMetadata
2322 , create
2423 , version
2524 , compile
@@ -33,11 +32,13 @@ module Hakyll.Core.Rules
3332
3433
3534--------------------------------------------------------------------------------
35+import Control.Applicative ((<$>))
3636import Control.Monad.Reader (ask, local)
3737import Control.Monad.State (get, modify, put)
3838import Control.Monad.Trans (liftIO)
3939import Control.Monad.Writer (censor, tell)
4040import Data.Maybe (fromMaybe)
41+import Data.Monoid (mempty)
4142import qualified Data.Set as S
4243
4344
@@ -119,26 +120,16 @@ flush = Rules $ do
119120
120121
121122--------------------------------------------------------------------------------
122−matchInternal :: Pattern -> Rules [Identifier] -> Rules () -> Rules ()
123−matchInternal pattern getIDs rules = do
123+match :: Pattern -> Rules () -> Rules ()
124+match pattern rules = do
124125 tellPattern pattern
125126 flush
126− ids <- getIDs
127+ ids <- getMatches pattern
127128 tellResources ids
128129 Rules $ local (setMatches ids) $ unRules $ rules >> flush
129130 where
130131 setMatches ids env = env {rulesMatches = ids}
131132
132−--------------------------------------------------------------------------------
133−match :: Pattern -> Rules () -> Rules ()
134−match pattern = matchInternal pattern $ getMatches pattern
135−
136−
137−--------------------------------------------------------------------------------
138−matchMetadata :: Pattern -> (Metadata -> Bool) -> Rules () -> Rules ()
139−matchMetadata pattern metadataPred = matchInternal pattern $
140− map fst . filter (metadataPred . snd) <$> getAllMetadata pattern
141−
142133
143134--------------------------------------------------------------------------------
144135create :: [Identifier] -> Rules () -> Rules ()
@@ -190,32 +181,10 @@ preprocess = Rules . liftIO
190181--
191182-- A useful utility for this purpose is 'makePatternDependency'.
192183rulesExtraDependencies :: [Dependency] -> Rules a -> Rules a
193−rulesExtraDependencies deps rules =
194− -- Note that we add the dependencies seemingly twice here. However, this is
195− -- done so that 'rulesExtraDependencies' works both if we have something
196− -- like:
197− --
198− -- > match "*.css" $ rulesExtraDependencies [foo] $ ...
199− --
200− -- and something like:
201− --
202− -- > rulesExtraDependencies [foo] $ match "*.css" $ ...
203− --
204− -- (1) takes care of the latter and (2) of the former.
205− Rules $ censor fixRuleSet $ do
206− x <- unRules rules
207− fixCompiler
208− return x
184+rulesExtraDependencies deps = Rules . censor addDependencies . unRules
209185 where
210− -- (1) Adds the dependencies to the compilers we are yet to create
211− fixCompiler = modify $ \s -> case rulesCompiler s of
212− Nothing -> s
213− Just c -> s
214− { rulesCompiler = Just $ compilerTellDependencies deps >> c
215− }
216−
217− -- (2) Adds the dependencies to the compilers that are already in the ruleset
218− fixRuleSet ruleSet = ruleSet
186+ -- Adds the dependencies to the compilers in the ruleset
187+ addDependencies ruleSet = ruleSet
219188 { rulesCompilers =
220189 [ (i, compilerTellDependencies deps >> c)
221190 | (i, c) <- rulesCompilers ruleSet
lib/Hakyll/Core/Rules/Internal.hs → src/Hakyll/Core/Rules/Internal.hsrenamed+2−14
@@ -1,5 +1,4 @@
11--------------------------------------------------------------------------------
2−{-# LANGUAGE CPP #-}
32{-# LANGUAGE GeneralizedNewtypeDeriving #-}
43{-# LANGUAGE Rank2Types #-}
54module Hakyll.Core.Rules.Internal
@@ -13,13 +12,12 @@ module Hakyll.Core.Rules.Internal
1312
1413
1514--------------------------------------------------------------------------------
15+import Control.Applicative (Applicative, (<$>))
1616import Control.Monad.Reader (ask)
1717import Control.Monad.RWS (RWST, runRWST)
1818import Control.Monad.Trans (liftIO)
1919import qualified Data.Map as M
20−#if MIN_VERSION_base(4,9,0)
21−import Data.Semigroup (Semigroup (..))
22−#endif
20+import Data.Monoid (Monoid, mappend, mempty)
2321import Data.Set (Set)
2422
2523
@@ -56,20 +54,10 @@ data RuleSet = RuleSet
5654
5755
5856--------------------------------------------------------------------------------
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
6857instance Monoid RuleSet where
6958 mempty = RuleSet mempty mempty mempty mempty
7059 mappend (RuleSet r1 c1 s1 p1) (RuleSet r2 c2 s2 p2) =
7160 RuleSet (mappend r1 r2) (mappend c1 c2) (mappend s1 s2) (p1 .||. p2)
72−#endif
7361
7462
7563--------------------------------------------------------------------------------
lib/Hakyll/Core/Runtime.hs → src/Hakyll/Core/Runtime.hsrenamed+21−40
@@ -5,8 +5,9 @@ module Hakyll.Core.Runtime
55
66
77--------------------------------------------------------------------------------
8+import Control.Applicative ((<$>))
89import Control.Monad (unless)
9−import Control.Monad.Except (ExceptT, runExceptT, throwError)
10+import Control.Monad.Error (ErrorT, runErrorT, throwError)
1011import Control.Monad.Reader (ask)
1112import Control.Monad.RWS (RWST, runRWST)
1213import Control.Monad.State (get, modify)
@@ -14,6 +15,7 @@ import Control.Monad.Trans (liftIO)
1415import Data.List (intercalate)
1516import Data.Map (Map)
1617import qualified Data.Map as M
18+import Data.Monoid (mempty)
1719import Data.Set (Set)
1820import qualified Data.Set as S
1921import System.Exit (ExitCode (..))
@@ -28,7 +30,7 @@ import Hakyll.Core.Dependencies
2830import Hakyll.Core.Identifier
2931import Hakyll.Core.Item
3032import Hakyll.Core.Item.SomeItem
31−import Hakyll.Core.Logger (Logger)
33+import Hakyll.Core.Logger (Logger, Verbosity)
3234import qualified Hakyll.Core.Logger as Logger
3335import Hakyll.Core.Provider
3436import Hakyll.Core.Routes
@@ -40,9 +42,10 @@ import Hakyll.Core.Writable
4042
4143
4244--------------------------------------------------------------------------------
43−run :: Configuration -> Logger -> Rules a -> IO (ExitCode, RuleSet)
44−run config logger rules = do
45+run :: Configuration -> Verbosity -> Rules a -> IO (ExitCode, RuleSet)
46+run config verbosity rules = do
4547 -- Initialization
48+ logger <- Logger.new verbosity
4649 Logger.header logger "Initialising..."
4750 Logger.message logger "Creating store..."
4851 store <- Store.new (inMemoryCache config) $ storeDirectory config
@@ -68,14 +71,13 @@ run config logger rules = do
6871 , runtimeUniverse = M.fromList compilers
6972 }
7073 state = RuntimeState
71− { runtimeDone = S.empty
72− , runtimeSnapshots = S.empty
73− , runtimeTodo = M.empty
74− , runtimeFacts = oldFacts
74+ { runtimeDone = S.empty
75+ , runtimeTodo = M.empty
76+ , runtimeFacts = oldFacts
7577 }
7678
7779 -- Run the program and fetch the resulting state
78− result <- runExceptT $ runRWST build read' state
80+ result <- runErrorT $ runRWST build read' state
7981 case result of
8082 Left e -> do
8183 Logger.error logger e
@@ -107,15 +109,14 @@ data RuntimeRead = RuntimeRead
107109
108110--------------------------------------------------------------------------------
109111data RuntimeState = RuntimeState
110− { runtimeDone :: Set Identifier
111− , runtimeSnapshots :: Set (Identifier, Snapshot)
112− , runtimeTodo :: Map Identifier (Compiler SomeItem)
113− , runtimeFacts :: DependencyFacts
112+ { runtimeDone :: Set Identifier
113+ , runtimeTodo :: Map Identifier (Compiler SomeItem)
114+ , runtimeFacts :: DependencyFacts
114115 }
115116
116117
117118--------------------------------------------------------------------------------
118−type Runtime a = RWST RuntimeRead () RuntimeState (ExceptT String IO) a
119+type Runtime a = RWST RuntimeRead () RuntimeState (ErrorT String IO) a
119120
120121
121122--------------------------------------------------------------------------------
@@ -199,19 +200,9 @@ chase trail id'
199200 result <- liftIO $ runCompiler compiler read'
200201 case result of
201202 -- Rethrow error
202− CompilerError e -> throwError $ case compilerErrorMessages e of
203− [] -> "Compiler failed but no info given, try running with -v?"
204− es -> intercalate "; " es
205−
206− -- Signal that a snapshot was saved ->
207− CompilerSnapshot snapshot c -> do
208− -- Update info. The next 'chase' will pick us again at some
209− -- point so we can continue then.
210− modify $ \s -> s
211− { runtimeSnapshots =
212− S.insert (id', snapshot) (runtimeSnapshots s)
213− , runtimeTodo = M.insert id' c (runtimeTodo s)
214− }
203+ CompilerError [] -> throwError
204+ "Compiler failed but no info given, try running with -v?"
205+ CompilerError es -> throwError $ intercalate "; " es
215206
216207 -- Huge success
217208 CompilerDone (SomeItem item) cwrite -> do
@@ -252,16 +243,7 @@ chase trail id'
252243 -- Try something else first
253244 CompilerRequire dep c -> do
254245 -- Update the compiler so we don't execute it twice
255− let (depId, depSnapshot) = dep
256− done <- runtimeDone <$> get
257− snapshots <- runtimeSnapshots <$> get
258−
259− -- Done if we either completed the entire item (runtimeDone) or
260− -- if we previously saved the snapshot (runtimeSnapshots).
261− let depDone =
262− depId `S.member` done ||
263− (depId, depSnapshot) `S.member` snapshots
264−
246+ depDone <- (dep `S.member`) . runtimeDone <$> get
265247 modify $ \s -> s
266248 { runtimeTodo = M.insert id'
267249 (if depDone then c else compilerResult result)
@@ -270,7 +252,6 @@ chase trail id'
270252
271253 -- If the required item is already compiled, continue, or, start
272254 -- chasing that
273− Logger.debug logger $ "Require " ++ show depId ++
274− " (snapshot " ++ depSnapshot ++ "): " ++
255+ Logger.debug logger $ "Require " ++ show dep ++ ": " ++
275256 (if depDone then "OK" else "chasing")
276− if depDone then chase trail id' else chase (id' : trail) depId
257+ if depDone then chase trail id' else chase (id' : trail) dep
lib/Hakyll/Core/Store.hs → src/Hakyll/Core/Store.hsrenamed+24−26
@@ -16,6 +16,8 @@ module Hakyll.Core.Store
1616
1717
1818--------------------------------------------------------------------------------
19+import Control.Applicative ((<$>))
20+import Control.Exception (IOException, handle)
1921import qualified Crypto.Hash.MD5 as MD5
2022import Data.Binary (Binary, decode, encodeFile)
2123import qualified Data.ByteString as B
@@ -26,13 +28,11 @@ import Data.Maybe (isJust)
2628import qualified Data.Text as T
2729import qualified Data.Text.Encoding as T
2830import Data.Typeable (TypeRep, Typeable, cast, typeOf)
29−import Numeric (showHex)
3031import System.Directory (createDirectoryIfMissing)
3132import System.Directory (doesFileExist, removeFile)
3233import System.FilePath ((</>))
3334import System.IO (IOMode (..), hClose, openFile)
34−import System.IO.Error (catchIOError, ioeSetFileName,
35− ioeSetLocation, modifyIOError)
35+import Text.Printf (printf)
3636
3737
3838--------------------------------------------------------------------------------
@@ -85,14 +85,6 @@ new inMemory directory = do
8585 where
8686 csize = Just 500
8787
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)
9688
9789--------------------------------------------------------------------------------
9890-- | Auxiliary: add an item to the in-memory cache
@@ -133,16 +125,17 @@ cacheDelete (Store _ (Just lru)) key = do
133125--------------------------------------------------------------------------------
134126-- | Store an item
135127set :: (Binary a, Typeable a) => Store -> [String] -> a -> IO ()
136−set store identifier value = withStore store "set" (\key path -> do
137− encodeFile path value
128+set store identifier value = do
129+ encodeFile (storeDirectory store </> key) value
138130 cacheInsert store key value
139− ) identifier
131+ where
132+ key = hash identifier
140133
141134
142135--------------------------------------------------------------------------------
143136-- | Load an item
144137get :: (Binary a, Typeable a) => Store -> [String] -> IO (Result a)
145−get store = withStore store "get" $ \key path -> do
138+get store identifier = do
146139 -- First check the in-memory map
147140 ref <- cacheLookup store key
148141 case ref of
@@ -154,14 +147,17 @@ get store = withStore store "get" $ \key path -> do
154147 then return NotFound
155148 -- Found in the filesystem
156149 else do
157− v <- decodeClose path
150+ v <- decodeClose
158151 cacheInsert store key v
159152 return $ Found v
160153 -- Found in the in-memory map (or wrong type), just return
161154 s -> return s
162155 where
156+ key = hash identifier
157+ path = storeDirectory store </> key
158+
163159 -- 'decodeFile' from Data.Binary which closes the file ASAP
164− decodeClose path = do
160+ decodeClose = do
165161 h <- openFile path ReadMode
166162 lbs <- BL.hGetContents h
167163 BL.length lbs `seq` hClose h
@@ -171,30 +167,32 @@ get store = withStore store "get" $ \key path -> do
171167--------------------------------------------------------------------------------
172168-- | Strict function
173169isMember :: Store -> [String] -> IO Bool
174−isMember store = withStore store "isMember" $ \key path -> do
170+isMember store identifier = do
175171 inCache <- cacheIsMember store key
176172 if inCache then return True else doesFileExist path
173+ where
174+ key = hash identifier
175+ path = storeDirectory store </> key
177176
178177
179178--------------------------------------------------------------------------------
180179-- | Delete an item
181180delete :: Store -> [String] -> IO ()
182−delete store = withStore store "delete" $ \key path -> do
181+delete store identifier = do
183182 cacheDelete store key
184− deleteFile path
183+ deleteFile $ storeDirectory store </> key
184+ where
185+ key = hash identifier
185186
186187
187188--------------------------------------------------------------------------------
188189-- | Delete a file unless it doesn't exist...
189190deleteFile :: FilePath -> IO ()
190−deleteFile = (`catchIOError` \_ -> return ()) . removeFile
191+deleteFile = handle (\(_ :: IOException) -> return ()) . removeFile
191192
192193
193194--------------------------------------------------------------------------------
194195-- | Mostly meant for internal usage
195196hash :: [String] -> String
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)
197+hash = concatMap (printf "%02x") . B.unpack .
198+ MD5.hash . T.encodeUtf8 . T.pack . intercalate "/"
lib/Hakyll/Core/UnixFilter.hs → src/Hakyll/Core/UnixFilter.hsrenamed+5−20
@@ -1,5 +1,3 @@
1−{-# LANGUAGE CPP #-}
2−
31--------------------------------------------------------------------------------
42-- | A Compiler that supports unix filters.
53module Hakyll.Core.UnixFilter
@@ -16,11 +14,13 @@ import Control.Monad (forM_)
1614import Data.ByteString.Lazy (ByteString)
1715import qualified Data.ByteString.Lazy as LB
1816import Data.IORef (newIORef, readIORef, writeIORef)
17+import Data.Monoid (Monoid, mempty)
1918import System.Exit (ExitCode (..))
2019import System.IO (Handle, hClose, hFlush, hGetContents,
2120 hPutStr, hSetEncoding, localeEncoding)
2221import System.Process
2322
23+
2424--------------------------------------------------------------------------------
2525import Hakyll.Core.Compiler
2626
@@ -29,7 +29,7 @@ import Hakyll.Core.Compiler
2929-- | Use a unix filter as compiler. For example, we could use the 'rev' program
3030-- as a compiler.
3131--
32−-- > rev :: Compiler (Item String)
32+-- > rev :: Compiler String
3333-- > rev = getResourceString >>= withItemBody (unixFilter "rev" [])
3434--
3535-- A more realistic example: one can use this to call, for example, the sass
@@ -92,8 +92,7 @@ unixFilterWith writer reader programName args input = do
9292 ExitSuccess -> return output
9393 ExitFailure e -> fail $
9494 "Hakyll.Core.UnixFilter.unixFilterWith: " ++
95− unwords (programName : args) ++ " gave exit code " ++ show e ++
96− ". (Error: " ++ err ++ ")"
95+ unwords (programName : args) ++ " gave exit code " ++ show e
9796
9897
9998--------------------------------------------------------------------------------
@@ -106,22 +105,8 @@ unixFilterIO :: Monoid o
106105 -> i
107106 -> IO (o, String, ExitCode)
108107unixFilterIO writer reader programName args input = do
109− -- The problem on Windows is that `proc` is unable to execute
110− -- batch stubs (eg. anything created using 'gem install ...') even if its in
111− -- `$PATH`. A solution to this issue is to execute the batch file explicitly
112− -- using `cmd /c batchfile` but there is no rational way to know where said
113− -- batchfile is on the system. Hence, we detect windows using the
114− -- CPP and instead of using `proc` to create the process, use `shell`
115− -- which will be able to execute everything `proc` can
116− -- as well as batch files.
117−#ifdef mingw32_HOST_OS
118− let pr = shell $ unwords (programName : args)
119−#else
120− let pr = proc programName args
121−#endif
122−
123108 (Just inh, Just outh, Just errh, pid) <-
124− createProcess pr
109+ createProcess (proc programName args)
125110 { std_in = CreatePipe
126111 , std_out = CreatePipe
127112 , std_err = CreatePipe
lib/Hakyll/Core/Util/File.hs → src/Hakyll/Core/Util/File.hsrenamed+1
@@ -8,6 +8,7 @@ module Hakyll.Core.Util.File
88
99
1010--------------------------------------------------------------------------------
11+import Control.Applicative ((<$>))
1112import Control.Monad (filterM, forM, when)
1213import System.Directory (createDirectoryIfMissing,
1314 doesDirectoryExist, getDirectoryContents,
lib/Hakyll/Core/Util/Parser.hs → src/Hakyll/Core/Util/Parser.hsrenamed+4−10
@@ -2,12 +2,13 @@
22-- | Parser utilities
33module Hakyll.Core.Util.Parser
44 ( metadataKey
5+ , reservedKeys
56 ) where
67
78
89--------------------------------------------------------------------------------
9−import Control.Applicative ((<|>))
10−import Control.Monad (guard, mzero, void)
10+import Control.Applicative ((<$>), (<*>), (<|>))
11+import Control.Monad (mzero)
1112import qualified Text.Parsec as P
1213import Text.Parsec.String (Parser)
1314
@@ -15,14 +16,7 @@ import Text.Parsec.String (Parser)
1516--------------------------------------------------------------------------------
1617metadataKey :: Parser String
1718metadataKey = do
18− -- Ensure trailing '-' binds to '$' if present.
19− let hyphon = P.try $ do
20− void $ P.char '-'
21− x <- P.lookAhead P.anyChar
22− guard $ x /= '$'
23− pure '-'
24−
25− i <- (:) <$> P.letter <*> P.many (P.alphaNum <|> P.oneOf "_." <|> hyphon)
19+ i <- (:) <$> P.letter <*> (P.many $ P.alphaNum <|> P.oneOf " _-.")
2620 if i `elem` reservedKeys then mzero else return i
2721
2822
lib/Hakyll/Core/Util/String.hs → src/Hakyll/Core/Util/String.hsrenamed+1−2
@@ -1,4 +1,3 @@
1−{-# LANGUAGE FlexibleContexts #-}
21--------------------------------------------------------------------------------
32-- | Miscellaneous string manipulation functions.
43module Hakyll.Core.Util.String
@@ -27,7 +26,7 @@ trim = reverse . trim' . reverse . trim'
2726--------------------------------------------------------------------------------
2827-- | A simple (but inefficient) regex replace funcion
2928replaceAll :: String -- ^ Pattern
30− -> (String -> String) -- ^ Replacement (called on match)
29+ -> (String -> String) -- ^ Replacement (called on capture)
3130 -> String -- ^ Source string
3231 -> String -- ^ Result
3332replaceAll pattern f source = replaceAll' source
lib/Hakyll/Core/Writable.hs → src/Hakyll/Core/Writable.hsrenamed

The content is the same.

src/Hakyll/Init.hsnew file+37
@@ -0,0 +1,37 @@
1+--------------------------------------------------------------------------------
2+module Main
3+ ( main
4+ ) where
5+
6+
7+--------------------------------------------------------------------------------
8+import Control.Monad (forM_)
9+import System.Directory (copyFile)
10+import System.Environment (getArgs, getProgName)
11+import System.Exit (exitFailure)
12+import System.FilePath ((</>))
13+
14+
15+--------------------------------------------------------------------------------
16+import Hakyll.Core.Util.File
17+import Paths_hakyll
18+
19+
20+--------------------------------------------------------------------------------
21+main :: IO ()
22+main = do
23+ progName <- getProgName
24+ args <- getArgs
25+ srcDir <- getDataFileName "example"
26+ files <- getRecursiveContents (const $ return False) srcDir
27+
28+ case args of
29+ [dstDir] -> forM_ files $ \file -> do
30+ let dst = dstDir </> file
31+ src = srcDir </> file
32+ putStrLn $ "Creating " ++ dst
33+ makeDirectories dst
34+ copyFile src dst
35+ _ -> do
36+ putStrLn $ "Usage: " ++ progName ++ " <directory>"
37+ exitFailure
src/Hakyll/Main.hsnew file+116
@@ -0,0 +1,116 @@
1+--------------------------------------------------------------------------------
2+-- | Module providing the main hakyll function and command-line argument parsing
3+{-# LANGUAGE CPP #-}
4+{-# LANGUAGE DeriveDataTypeable #-}
5+module Hakyll.Main
6+ ( hakyll
7+ , hakyllWith
8+ ) where
9+
10+
11+--------------------------------------------------------------------------------
12+import System.Console.CmdArgs
13+import qualified System.Console.CmdArgs.Explicit as CA
14+import System.Environment (getProgName)
15+import System.IO.Unsafe (unsafePerformIO)
16+import System.Exit (exitWith)
17+
18+--------------------------------------------------------------------------------
19+import qualified Hakyll.Check as Check
20+import qualified Hakyll.Commands as Commands
21+import qualified Hakyll.Core.Configuration as Config
22+import qualified Hakyll.Core.Logger as Logger
23+import Hakyll.Core.Rules
24+
25+
26+--------------------------------------------------------------------------------
27+-- | This usualy is the function with which the user runs the hakyll compiler
28+hakyll :: Rules a -> IO ()
29+hakyll = hakyllWith Config.defaultConfiguration
30+
31+
32+--------------------------------------------------------------------------------
33+-- | A variant of 'hakyll' which allows the user to specify a custom
34+-- configuration
35+hakyllWith :: Config.Configuration -> Rules a -> IO ()
36+hakyllWith conf rules = do
37+ args' <- cmdArgs (hakyllArgs conf)
38+
39+ let verbosity' = if verbose args' then Logger.Debug else Logger.Message
40+ check' =
41+ if internal_links args' then Check.InternalLinks else Check.All
42+
43+ case args' of
44+ Build _ -> Commands.build conf verbosity' rules >>= exitWith
45+ Check _ _ -> Commands.check conf verbosity' check'
46+ Clean _ -> Commands.clean conf
47+ Deploy _ -> Commands.deploy conf >>= exitWith
48+ Help _ -> showHelp
49+ Preview _ p -> Commands.preview conf verbosity' rules p
50+ Rebuild _ -> Commands.rebuild conf verbosity' rules >>= exitWith
51+ Server _ _ -> Commands.server conf (port args')
52+ Watch _ p s -> Commands.watch conf verbosity' p (not s) rules
53+
54+
55+--------------------------------------------------------------------------------
56+-- | Show usage information.
57+showHelp :: IO ()
58+showHelp = print $ CA.helpText [] CA.HelpFormatOne $ cmdArgsMode (hakyllArgs Config.defaultConfiguration)
59+
60+
61+--------------------------------------------------------------------------------
62+data HakyllArgs
63+ = Build {verbose :: Bool}
64+ | Check {verbose :: Bool, internal_links :: Bool}
65+ | Clean {verbose :: Bool}
66+ | Deploy {verbose :: Bool}
67+ | Help {verbose :: Bool}
68+ | Preview {verbose :: Bool, port :: Int}
69+ | Rebuild {verbose :: Bool}
70+ | Server {verbose :: Bool, port :: Int}
71+ | Watch {verbose :: Bool, port :: Int, no_server :: Bool }
72+ deriving (Data, Typeable, Show)
73+
74+
75+--------------------------------------------------------------------------------
76+hakyllArgs :: Config.Configuration -> HakyllArgs
77+hakyllArgs conf = modes
78+ [ (Build $ verboseFlag def) &= help "Generate the site"
79+ , (Check (verboseFlag def) (False &= help "Check internal links only")) &=
80+ help "Validate the site output"
81+ , (Clean $ verboseFlag def) &= help "Clean up and remove cache"
82+ , (Deploy $ verboseFlag def) &= help "Upload/deploy your site"
83+ , (Help $ verboseFlag def) &= help "Show this message" &= auto
84+ , (Preview (verboseFlag def) (portFlag defaultPort)) &=
85+ help "[Deprecated] Please use the watch command"
86+ , (Rebuild $ verboseFlag def) &= help "Clean and build again"
87+ , (Server (verboseFlag def) (portFlag defaultPort)) &=
88+ help "Start a preview server"
89+ , (Watch (verboseFlag def) (portFlag defaultPort) (noServerFlag False) &=
90+ help "Autocompile on changes and start a preview server. You can watch and recompile without running a server with --no-server.")
91+ ] &= help "Hakyll static site compiler" &= program progName
92+ where defaultPort = Config.previewPort conf
93+
94+
95+--------------------------------------------------------------------------------
96+verboseFlag :: Data a => a -> a
97+verboseFlag x = x &= help "Run in verbose mode"
98+{-# INLINE verboseFlag #-}
99+
100+
101+--------------------------------------------------------------------------------
102+noServerFlag :: Data a => a -> a
103+noServerFlag x = x &= help "Disable the built-in web server"
104+{-# INLINE noServerFlag #-}
105+
106+--------------------------------------------------------------------------------
107+portFlag :: Data a => a -> a
108+portFlag x = x &= help "Port to listen on"
109+{-# INLINE portFlag #-}
110+
111+
112+--------------------------------------------------------------------------------
113+-- | This is necessary because not everyone calls their program the same...
114+progName :: String
115+progName = unsafePerformIO getProgName
116+{-# NOINLINE progName #-}
src/Hakyll/Preview/Poll.hsnew file+104
@@ -0,0 +1,104 @@
1+{-# LANGUAGE CPP #-}
2+
3+--------------------------------------------------------------------------------
4+module Hakyll.Preview.Poll
5+ ( watchUpdates
6+ ) where
7+
8+
9+--------------------------------------------------------------------------------
10+import Control.Concurrent.MVar (newMVar, putMVar, takeMVar)
11+import Control.Monad (when, void)
12+import Filesystem.Path.CurrentOS (decodeString, encodeString)
13+import System.Directory (canonicalizePath)
14+import System.FilePath (pathSeparators, (</>))
15+import System.FSNotify (Event (..), WatchConfig (..),
16+ startManagerConf, watchTree)
17+
18+#ifdef mingw32_HOST_OS
19+import System.IO (IOMode(ReadMode), Handle, openFile,
20+ hClose)
21+import System.IO.Error (isPermissionError)
22+import Control.Concurrent (threadDelay)
23+import Control.Exception (IOException, throw, try)
24+import System.Exit (exitFailure)
25+import System.Directory (doesFileExist)
26+#endif
27+
28+--------------------------------------------------------------------------------
29+import Hakyll.Core.Configuration
30+import Hakyll.Core.Identifier
31+import Hakyll.Core.Identifier.Pattern
32+
33+
34+--------------------------------------------------------------------------------
35+-- | A thread that watches for updates in a 'providerDirectory' and recompiles
36+-- a site as soon as any changes occur
37+watchUpdates :: Configuration -> IO Pattern -> IO ()
38+watchUpdates conf update = do
39+ let providerDir = decodeString $ providerDirectory conf
40+ lock <- newMVar ()
41+ pattern <- update
42+ fullProviderDir <- canonicalizePath $ providerDirectory conf
43+ manager <- startManagerConf (Debounce 0.1)
44+
45+ let allowed event = do
46+ -- Absolute path of the changed file. This must be inside provider
47+ -- dir, since that's the only dir we're watching.
48+ let path = eventPath event
49+ relative = dropWhile (`elem` pathSeparators) $
50+ drop (length fullProviderDir) path
51+ identifier = fromFilePath relative
52+
53+ shouldIgnore <- shouldIgnoreFile conf path
54+ return $ not shouldIgnore && matches pattern identifier
55+
56+ watchTree manager providerDir (not . isRemove) $ \event -> do
57+ () <- takeMVar lock
58+ allowed' <- allowed event
59+ when allowed' $ update' event (encodeString providerDir)
60+ putMVar lock ()
61+ where
62+#ifndef mingw32_HOST_OS
63+ update' _ _ = void update
64+#else
65+ update' event provider = do
66+ let path = provider </> eventPath event
67+ -- on windows, a 'Modified' event is also sent on file deletion
68+ fileExists <- doesFileExist path
69+
70+ when fileExists . void $ waitOpen path ReadMode (\_ -> update) 10
71+
72+ -- continuously attempts to open the file in between sleep intervals
73+ -- handler is run only once it is able to open the file
74+ waitOpen :: FilePath -> IOMode -> (Handle -> IO r) -> Integer -> IO r
75+ waitOpen _ _ _ 0 = do
76+ putStrLn "[ERROR] Failed to retrieve modified file for regeneration"
77+ exitFailure
78+ waitOpen path mode handler retries = do
79+ res <- try $ openFile path mode :: IO (Either IOException Handle)
80+ case res of
81+ Left ex -> if isPermissionError ex
82+ then do
83+ threadDelay 100000
84+ waitOpen path mode handler (retries - 1)
85+ else throw ex
86+ Right h -> do
87+ handled <- handler h
88+ hClose h
89+ return handled
90+#endif
91+
92+--------------------------------------------------------------------------------
93+eventPath :: Event -> FilePath
94+eventPath evt = encodeString $ evtPath evt
95+ where
96+ evtPath (Added p _) = p
97+ evtPath (Modified p _) = p
98+ evtPath (Removed p _) = p
99+
100+
101+--------------------------------------------------------------------------------
102+isRemove :: Event -> Bool
103+isRemove (Removed _ _) = True
104+isRemove _ = False
src/Hakyll/Preview/Server.hsnew file+43
@@ -0,0 +1,43 @@
1+--------------------------------------------------------------------------------
2+-- | Implements a basic static file server for previewing options
3+{-# LANGUAGE OverloadedStrings #-}
4+module Hakyll.Preview.Server
5+ ( staticServer
6+ ) where
7+
8+
9+--------------------------------------------------------------------------------
10+import Control.Monad.Trans (liftIO)
11+import qualified Snap.Core as Snap
12+import qualified Snap.Http.Server as Snap
13+import qualified Snap.Util.FileServe as Snap
14+
15+
16+--------------------------------------------------------------------------------
17+-- | Serve a given directory
18+static :: FilePath -- ^ Directory to serve
19+ -> (FilePath -> IO ()) -- ^ Pre-serve hook
20+ -> Snap.Snap ()
21+static directory preServe =
22+ Snap.serveDirectoryWith directoryConfig directory
23+ where
24+ directoryConfig :: Snap.DirectoryConfig Snap.Snap
25+ directoryConfig = Snap.fancyDirectoryConfig
26+ { Snap.preServeHook = liftIO . preServe
27+ }
28+
29+
30+--------------------------------------------------------------------------------
31+-- | Main method, runs a static server in the given directory
32+staticServer :: FilePath -- ^ Directory to serve
33+ -> (FilePath -> IO ()) -- ^ Pre-serve hook
34+ -> Int -- ^ Port to listen on
35+ -> IO () -- ^ Blocks forever
36+staticServer directory preServe port =
37+ Snap.httpServe config $ static directory preServe
38+ where
39+ -- Snap server config
40+ config = Snap.setPort port
41+ $ Snap.setAccessLog Snap.ConfigNoLog
42+ $ Snap.setErrorLog Snap.ConfigNoLog
43+ $ Snap.emptyConfig
src/Hakyll/Web/CompressCss.hsnew file+60
@@ -0,0 +1,60 @@
1+--------------------------------------------------------------------------------
2+-- | Module used for CSS compression. The compression is currently in a simple
3+-- state, but would typically reduce the number of bytes by about 25%.
4+module Hakyll.Web.CompressCss
5+ ( compressCssCompiler
6+ , compressCss
7+ ) where
8+
9+
10+--------------------------------------------------------------------------------
11+import Control.Applicative ((<$>))
12+import Data.Char (isSpace)
13+import Data.List (isPrefixOf)
14+
15+
16+--------------------------------------------------------------------------------
17+import Hakyll.Core.Compiler
18+import Hakyll.Core.Item
19+import Hakyll.Core.Util.String
20+
21+
22+--------------------------------------------------------------------------------
23+-- | Compiler form of 'compressCss'
24+compressCssCompiler :: Compiler (Item String)
25+compressCssCompiler = fmap compressCss <$> getResourceString
26+
27+
28+--------------------------------------------------------------------------------
29+-- | Compress CSS to speed up your site.
30+compressCss :: String -> String
31+compressCss = compressSeparators . stripComments . compressWhitespace
32+
33+
34+--------------------------------------------------------------------------------
35+-- | Compresses certain forms of separators.
36+compressSeparators :: String -> String
37+compressSeparators =
38+ replaceAll "; *}" (const "}") .
39+ replaceAll " *([{};:]) *" (take 1 . dropWhile isSpace) .
40+ replaceAll ";+" (const ";")
41+
42+
43+--------------------------------------------------------------------------------
44+-- | Compresses all whitespace.
45+compressWhitespace :: String -> String
46+compressWhitespace = replaceAll "[ \t\n\r]+" (const " ")
47+
48+
49+--------------------------------------------------------------------------------
50+-- | Function that strips CSS comments away.
51+stripComments :: String -> String
52+stripComments [] = []
53+stripComments str
54+ | isPrefixOf "/*" str = stripComments $ eatComments $ drop 2 str
55+ | otherwise = head str : stripComments (drop 1 str)
56+ where
57+ eatComments str'
58+ | null str' = []
59+ | isPrefixOf "*/" str' = drop 2 str'
60+ | otherwise = eatComments $ drop 1 str'
lib/Hakyll/Web/Feed.hs → src/Hakyll/Web/Feed.hsrenamed+28−70
@@ -1,6 +1,3 @@
1−{-# LANGUAGE OverloadedStrings #-}
2−{-# LANGUAGE TemplateHaskell #-}
3−
41--------------------------------------------------------------------------------
52-- | A Module that allows easy rendering of RSS feeds.
63--
@@ -23,40 +20,26 @@ module Hakyll.Web.Feed
2320 ( FeedConfiguration (..)
2421 , renderRss
2522 , renderAtom
26− , renderRssWithTemplates
27− , renderAtomWithTemplates
2823 ) where
2924
3025
26+--------------------------------------------------------------------------------
27+import Control.Monad ((<=<))
28+import Data.Monoid (mconcat)
29+
30+
3131--------------------------------------------------------------------------------
3232import Hakyll.Core.Compiler
33+import Hakyll.Core.Compiler.Internal
3334import Hakyll.Core.Item
34−import Hakyll.Core.Util.String (replaceAll)
3535import Hakyll.Web.Template
3636import Hakyll.Web.Template.Context
3737import Hakyll.Web.Template.List
38+import Hakyll.Web.Template.Read
3839
3940
4041--------------------------------------------------------------------------------
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)
42+import Paths_hakyll
6043
6144
6245--------------------------------------------------------------------------------
@@ -77,27 +60,25 @@ data FeedConfiguration = FeedConfiguration
7760
7861--------------------------------------------------------------------------------
7962-- | Abstract function to render any feed.
80−renderFeed :: Template -- ^ Default feed template
81− -> Template -- ^ Default item template
63+renderFeed :: FilePath -- ^ Feed template
64+ -> FilePath -- ^ Item template
8265 -> FeedConfiguration -- ^ Feed configuration
8366 -> Context String -- ^ Context for the items
8467 -> [Item String] -- ^ Input items
8568 -> Compiler (Item String) -- ^ Resulting item
86−renderFeed feedTpl itemTpl config itemContext items = do
87− protectedItems <- mapM (applyFilter protectCDATA) items
88− body <- makeItem =<< applyTemplateList itemTpl itemContext' protectedItems
69+renderFeed feedPath itemPath config itemContext items = do
70+ feedTpl <- compilerUnsafeIO $ loadTemplate feedPath
71+ itemTpl <- compilerUnsafeIO $ loadTemplate itemPath
72+
73+ body <- makeItem =<< applyTemplateList itemTpl itemContext' items
8974 applyTemplate feedTpl feedContext body
9075 where
91− applyFilter :: (Monad m,Functor f) => (String -> String) -> f String -> m (f String)
92− applyFilter tr str = return $ fmap tr str
93− protectCDATA :: String -> String
94− protectCDATA = replaceAll "]]>" (const "]]&gt;")
76+ -- Auxiliary: load a template from a datafile
77+ loadTemplate = fmap readTemplate . readFile <=< getDataFileName
9578
9679 itemContext' = mconcat
97− [ itemContext
98− , constField "root" (feedRoot config)
99− , constField "authorName" (feedAuthorName config)
100− , constField "authorEmail" (feedAuthorEmail config)
80+ [ constField "root" (feedRoot config)
81+ , itemContext
10182 ]
10283
10384 feedContext = mconcat
@@ -116,36 +97,9 @@ renderFeed feedTpl itemTpl config itemContext items = do
11697 -- recent.
11798 updatedField = field "updated" $ \_ -> case items of
11899 [] -> return "Unknown"
119− (x : _) -> unContext itemContext' "updated" [] x >>= \cf -> case cf of
100+ (x : _) -> unContext itemContext' "updated" x >>= \cf -> case cf of
101+ ListField _ _ -> fail "Hakyll.Web.Feed.renderFeed: Internal error"
120102 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)
149103
150104
151105--------------------------------------------------------------------------------
@@ -154,7 +108,9 @@ renderRss :: FeedConfiguration -- ^ Feed configuration
154108 -> Context String -- ^ Item context
155109 -> [Item String] -- ^ Feed items
156110 -> Compiler (Item String) -- ^ Resulting feed
157−renderRss = renderRssWithTemplates rssTemplate rssItemTemplate
111+renderRss config context = renderFeed
112+ "templates/rss.xml" "templates/rss-item.xml" config
113+ (makeItemContext "%a, %d %b %Y %H:%M:%S UT" context)
158114
159115
160116--------------------------------------------------------------------------------
@@ -163,11 +119,13 @@ renderAtom :: FeedConfiguration -- ^ Feed configuration
163119 -> Context String -- ^ Item context
164120 -> [Item String] -- ^ Feed items
165121 -> Compiler (Item String) -- ^ Resulting feed
166−renderAtom = renderAtomWithTemplates atomTemplate atomItemTemplate
122+renderAtom config context = renderFeed
123+ "templates/atom.xml" "templates/atom-item.xml" config
124+ (makeItemContext "%Y-%m-%dT%H:%M:%SZ" context)
167125
168126
169127--------------------------------------------------------------------------------
170128-- | Copies @$updated$@ from @$published$@ if it is not already set.
171129makeItemContext :: String -> Context a -> Context a
172130makeItemContext fmt context = mconcat
173− [context, dateField "published" fmt, dateField "updated" fmt]
131+ [dateField "published" fmt, context, dateField "updated" fmt]
lib/Hakyll/Web/Html.hs → src/Hakyll/Web/Html.hsrenamed+3−18
@@ -3,7 +3,6 @@
33module Hakyll.Web.Html
44 ( -- * Generic
55 withTags
6− , withTagList
76
87 -- * Headers
98 , demoteHeaders
@@ -37,11 +36,8 @@ import Network.URI (isUnreserved, escapeURIString)
3736--------------------------------------------------------------------------------
3837-- | Map over all tags in the document
3938withTags :: (TS.Tag String -> TS.Tag String) -> String -> String
40−withTags = withTagList . map
39+withTags f = renderTags' . map f . TS.parseTags
4140
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'
4541
4642--------------------------------------------------------------------------------
4743-- | Map every @h1@ to an @h2@, @h2@ to @h3@, etc.
@@ -59,7 +55,7 @@ demoteHeaders = withTags $ \tag -> case tag of
5955
6056--------------------------------------------------------------------------------
6157isUrlAttribute :: String -> Bool
62−isUrlAttribute = (`elem` ["src", "href", "data", "poster", "srcset"])
58+isUrlAttribute = (`elem` ["src", "href", "data"])
6359
6460
6561--------------------------------------------------------------------------------
@@ -81,10 +77,9 @@ withUrls f = withTags tag
8177-- | Customized TagSoup renderer. The default TagSoup renderer escape CSS
8278-- within style tags, and doesn't properly minimize.
8379renderTags' :: [TS.Tag String] -> String
84−renderTags' = TS.renderTagsOptions TS.RenderOptions
80+renderTags' = TS.renderTagsOptions TS.renderOptions
8581 { TS.optRawTag = (`elem` ["script", "style"]) . map toLower
8682 , TS.optMinimize = (`S.member` minimize) . map toLower
87− , TS.optEscape = id
8883 }
8984 where
9085 -- A list of elements which must be minimized
@@ -94,15 +89,6 @@ renderTags' = TS.renderTagsOptions TS.RenderOptions
9489 ]
9590
9691
97−--------------------------------------------------------------------------------
98−-- | Customized TagSoup parser: do not decode any entities.
99−parseTags' :: String -> [TS.Tag String]
100−parseTags' = TS.parseTagsOptions (TS.parseOptions :: TS.ParseOptions String)
101− { TS.optEntityData = \(str, b) -> [TS.TagText $ "&" ++ str ++ [';' | b]]
102− , TS.optEntityAttrib = \(str, b) -> ("&" ++ str ++ [';' | b], [])
103− }
104−
105−
10692--------------------------------------------------------------------------------
10793-- | Convert a filepath to an URL starting from the site root
10894--
@@ -138,7 +124,6 @@ toSiteRoot = emptyException . joinPath . map parent
138124 emptyException x = x
139125 relevant "." = False
140126 relevant "/" = False
141− relevant "./" = False
142127 relevant _ = True
143128
144129
lib/Hakyll/Web/Html/RelativizeUrls.hs → src/Hakyll/Web/Html/RelativizeUrls.hsrenamed

The content is the same.

src/Hakyll/Web/Paginate.hsnew file+134
@@ -0,0 +1,134 @@
1+--------------------------------------------------------------------------------
2+{-# LANGUAGE OverloadedStrings #-}
3+module Hakyll.Web.Paginate
4+ ( PageNumber
5+ , Paginate (..)
6+ , buildPaginate
7+ , buildPaginateWith
8+ , paginateRules
9+ , paginateContext
10+ ) where
11+
12+
13+--------------------------------------------------------------------------------
14+import Control.Monad (forM_)
15+import Data.List (unfoldr)
16+import qualified Data.Map as M
17+import Data.Monoid (mconcat)
18+import Text.Printf (printf)
19+
20+
21+--------------------------------------------------------------------------------
22+import Hakyll.Core.Compiler
23+import Hakyll.Core.Identifier
24+import Hakyll.Core.Identifier.Pattern
25+import Hakyll.Core.Item
26+import Hakyll.Core.Metadata
27+import Hakyll.Core.Rules
28+import Hakyll.Web.Html
29+import Hakyll.Web.Template.Context
30+
31+
32+--------------------------------------------------------------------------------
33+type PageNumber = Int
34+
35+
36+--------------------------------------------------------------------------------
37+-- | Data about paginators
38+data Paginate = Paginate
39+ { paginatePages :: M.Map PageNumber [Identifier]
40+ , paginatePlaces :: M.Map Identifier PageNumber
41+ , paginateMakeId :: PageNumber -> Identifier
42+ , paginateDependency :: Dependency
43+ } deriving (Show)
44+
45+
46+--------------------------------------------------------------------------------
47+buildPaginate :: MonadMetadata m
48+ => Pattern
49+ -> m Paginate
50+buildPaginate pattern = do
51+ idents <- getMatches pattern
52+ let pagPages = M.fromList $ zip [1 ..] (map return idents)
53+ pagPlaces = M.fromList $ zip idents [1 ..]
54+ makeId pn = case M.lookup pn pagPages of
55+ Just [id'] -> id'
56+ _ -> error $
57+ "Hakyll.Web.Paginate.buildPaginate: " ++
58+ "invalid page number: " ++ show pn
59+
60+ return $ Paginate pagPages pagPlaces makeId
61+ (PatternDependency pattern idents)
62+
63+
64+--------------------------------------------------------------------------------
65+buildPaginateWith :: MonadMetadata m
66+ => Int
67+ -> (PageNumber -> Identifier)
68+ -> Pattern
69+ -> m Paginate
70+buildPaginateWith n makeId pattern = do
71+ -- TODO: there is no sensible order for `ids` here, for now it's random;
72+ -- but it should be `resectFirst` order because most recent posts should
73+ -- correspond to 1st paginator page and oldest one to last page
74+ idents <- getMatches pattern
75+ let pages = flip unfoldr idents $ \xs ->
76+ if null xs then Nothing else Just (splitAt n xs)
77+ nPages = length pages
78+ paginatePages' = zip [1..] pages
79+ pagPlaces' =
80+ [(ident, idx) | (idx,ids) <- paginatePages', ident <- ids] ++
81+ [(makeId i, i) | i <- [1 .. nPages]]
82+
83+ return $ Paginate (M.fromList paginatePages') (M.fromList pagPlaces') makeId
84+ (PatternDependency pattern idents)
85+
86+
87+--------------------------------------------------------------------------------
88+paginateRules :: Paginate -> (PageNumber -> Pattern -> Rules ()) -> Rules ()
89+paginateRules paginator rules =
90+ forM_ (M.toList $ paginatePages paginator) $ \(idx, identifiers) ->
91+ create [paginateMakeId paginator idx] $
92+ rulesExtraDependencies [paginateDependency paginator] $
93+ rules idx $ fromList identifiers
94+
95+
96+--------------------------------------------------------------------------------
97+-- | Takes first, current, last page and produces index of next page
98+type RelPage = PageNumber -> PageNumber -> PageNumber -> Maybe PageNumber
99+
100+
101+--------------------------------------------------------------------------------
102+paginateField :: Paginate -> String -> RelPage -> Context a
103+paginateField pag fieldName relPage = field fieldName $ \item ->
104+ let identifier = itemIdentifier item
105+ in case M.lookup identifier (paginatePlaces pag) of
106+ Nothing -> fail $ printf
107+ "Hakyll.Web.Paginate: there is no page %s in paginator map."
108+ (show identifier)
109+ Just pos -> case relPage 1 pos nPages of
110+ Nothing -> fail "Hakyll.Web.Paginate: No page here."
111+ Just pos' -> do
112+ let nextId = paginateMakeId pag pos'
113+ mroute <- getRoute nextId
114+ case mroute of
115+ Nothing -> fail $ printf
116+ "Hakyll.Web.Paginate: unable to get route for %s."
117+ (show nextId)
118+ Just rt -> return $ toUrl rt
119+ where
120+ nPages = M.size (paginatePages pag)
121+
122+
123+--------------------------------------------------------------------------------
124+paginateContext :: Paginate -> Context a
125+paginateContext pag = mconcat
126+ [ paginateField pag "firstPage"
127+ (\f c _ -> if c <= f then Nothing else Just f)
128+ , paginateField pag "previousPage"
129+ (\f c _ -> if c <= f then Nothing else Just (c - 1))
130+ , paginateField pag "nextPage"
131+ (\_ c l -> if c >= l then Nothing else Just (c + 1))
132+ , paginateField pag "lastPage"
133+ (\_ c l -> if c >= l then Nothing else Just l)
134+ ]
lib/Hakyll/Web/Pandoc.hs → src/Hakyll/Web/Pandoc.hsrenamed+20−51
@@ -13,7 +13,6 @@ module Hakyll.Web.Pandoc
1313 , pandocCompiler
1414 , pandocCompilerWith
1515 , pandocCompilerWithTransform
16− , pandocCompilerWithTransformM
1716
1817 -- * Default options
1918 , defaultHakyllReaderOptions
@@ -22,9 +21,9 @@ module Hakyll.Web.Pandoc
2221
2322
2423--------------------------------------------------------------------------------
25−import qualified Data.Text as T
24+import Control.Applicative ((<$>))
25+import qualified Data.Set as S
2626import Text.Pandoc
27−import Text.Pandoc.Highlighting (pygments)
2827
2928
3029--------------------------------------------------------------------------------
@@ -35,39 +34,30 @@ import Hakyll.Web.Pandoc.FileType
3534
3635--------------------------------------------------------------------------------
3736-- | Read a string using pandoc, with the default options
38−readPandoc
39− :: Item String -- ^ String to read
40− -> Compiler (Item Pandoc) -- ^ Resulting document
37+readPandoc :: Item String -- ^ String to read
38+ -> Item Pandoc -- ^ Resulting document
4139readPandoc = readPandocWith defaultHakyllReaderOptions
4240
4341
4442--------------------------------------------------------------------------------
4543-- | Read a string using pandoc, with the supplied options
46−readPandocWith
47− :: ReaderOptions -- ^ Parser options
48− -> Item String -- ^ String to read
49− -> Compiler (Item Pandoc) -- ^ Resulting document
50−readPandocWith ropt item =
51− case runPure $ traverse (reader ropt (itemFileType item)) (fmap T.pack item) of
52− Left err -> fail $
53− "Hakyll.Web.Pandoc.readPandocWith: parse failed: " ++ show err
54− Right item' -> return item'
44+readPandocWith :: ReaderOptions -- ^ Parser options
45+ -> Item String -- ^ String to read
46+ -> Item Pandoc -- ^ Resulting document
47+readPandocWith ropt item = fmap (reader ropt (itemFileType item)) item
5548 where
5649 reader ro t = case t of
57− DocBook -> readDocBook ro
5850 Html -> readHtml ro
5951 LaTeX -> readLaTeX ro
6052 LiterateHaskell t' -> reader (addExt ro Ext_literate_haskell) t'
6153 Markdown -> readMarkdown ro
62− MediaWiki -> readMediaWiki ro
63− OrgMode -> readOrg ro
6454 Rst -> readRST ro
6555 Textile -> readTextile ro
6656 _ -> error $
6757 "Hakyll.Web.readPandocWith: I don't know how to read a file of " ++
6858 "the type " ++ show t ++ " for: " ++ show (itemIdentifier item)
6959
70− addExt ro e = ro {readerExtensions = enableExtension e $ readerExtensions ro}
60+ addExt ro e = ro {readerExtensions = S.insert e $ readerExtensions ro}
7161
7262
7363--------------------------------------------------------------------------------
@@ -82,25 +72,20 @@ writePandoc = writePandocWith defaultHakyllWriterOptions
8272writePandocWith :: WriterOptions -- ^ Writer options for pandoc
8373 -> Item Pandoc -- ^ Document to write
8474 -> Item String -- ^ Resulting HTML
85−writePandocWith wopt (Item itemi doc) =
86− case runPure $ writeHtml5String wopt doc of
87− Left err -> error $ "Hakyll.Web.Pandoc.writePandocWith: " ++ show err
88− Right item' -> Item itemi $ T.unpack item'
75+writePandocWith wopt = fmap $ writeHtmlString wopt
8976
9077
9178--------------------------------------------------------------------------------
9279-- | Render the resource using pandoc
93−renderPandoc :: Item String -> Compiler (Item String)
80+renderPandoc :: Item String -> Item String
9481renderPandoc =
9582 renderPandocWith defaultHakyllReaderOptions defaultHakyllWriterOptions
9683
9784
9885--------------------------------------------------------------------------------
9986-- | Render the resource using pandoc
100−renderPandocWith
101− :: ReaderOptions -> WriterOptions -> Item String -> Compiler (Item String)
102−renderPandocWith ropt wopt item =
103− writePandocWith wopt <$> readPandocWith ropt item
87+renderPandocWith :: ReaderOptions -> WriterOptions -> Item String -> Item String
88+renderPandocWith ropt wopt = writePandocWith wopt . readPandocWith ropt
10489
10590
10691--------------------------------------------------------------------------------
@@ -114,9 +99,7 @@ pandocCompiler =
11499-- | A version of 'pandocCompiler' which allows you to specify your own pandoc
115100-- options
116101pandocCompilerWith :: ReaderOptions -> WriterOptions -> Compiler (Item String)
117−pandocCompilerWith ropt wopt =
118− cached "Hakyll.Web.Pandoc.pandocCompilerWith" $
119− pandocCompilerWithTransform ropt wopt id
102+pandocCompilerWith ropt wopt = pandocCompilerWithTransform ropt wopt id
120103
121104
122105--------------------------------------------------------------------------------
@@ -125,21 +108,10 @@ pandocCompilerWith ropt wopt =
125108pandocCompilerWithTransform :: ReaderOptions -> WriterOptions
126109 -> (Pandoc -> Pandoc)
127110 -> Compiler (Item String)
128−pandocCompilerWithTransform ropt wopt f =
129− pandocCompilerWithTransformM ropt wopt (return . f)
130−
131−
132−--------------------------------------------------------------------------------
133−-- | Similar to 'pandocCompilerWithTransform', but the transformation
134−-- function is monadic. This is useful when you want the pandoc
135−-- transformation to use the 'Compiler' information such as routes,
136−-- metadata, etc
137−pandocCompilerWithTransformM :: ReaderOptions -> WriterOptions
138− -> (Pandoc -> Compiler Pandoc)
139− -> Compiler (Item String)
140−pandocCompilerWithTransformM ropt wopt f =
141− writePandocWith wopt <$>
142− (traverse f =<< readPandocWith ropt =<< getResourceBody)
111+pandocCompilerWithTransform ropt wopt f = cached cacheName $
112+ writePandocWith wopt . fmap f . readPandocWith ropt <$> getResourceBody
113+ where
114+ cacheName = "Hakyll.Web.Page.pageCompilerWithPandoc"
143115
144116
145117--------------------------------------------------------------------------------
@@ -148,7 +120,7 @@ defaultHakyllReaderOptions :: ReaderOptions
148120defaultHakyllReaderOptions = def
149121 { -- The following option causes pandoc to read smart typography, a nice
150122 -- and free bonus.
151− readerExtensions = enableExtension Ext_smart pandocExtensions
123+ readerSmart = True
152124 }
153125
154126
@@ -158,8 +130,5 @@ defaultHakyllWriterOptions :: WriterOptions
158130defaultHakyllWriterOptions = def
159131 { -- This option causes literate haskell to be written using '>' marks in
160132 -- html, which I think is a good default.
161− writerExtensions = enableExtension Ext_smart pandocExtensions
162− , -- We want to have hightlighting by default, to be compatible with earlier
163− -- Hakyll releases
164− writerHighlightStyle = Just pygments
133+ writerExtensions = S.insert Ext_literate_haskell (writerExtensions def)
165134 }
lib/Hakyll/Web/Pandoc/Biblio.hs → src/Hakyll/Web/Pandoc/Biblio.hsrenamed+16−39
@@ -4,11 +4,9 @@
44-- In order to add a bibliography, you will need a bibliography file (e.g.
55-- @.bib@) and a CSL file (@.csl@). Both need to be compiled with their
66-- respective compilers ('biblioCompiler' and 'cslCompiler'). Then, you can
7−-- refer to these files when you use 'readPandocBiblio'. This function also
7+-- refer to these files when you use 'pageReadPandocBiblio'. This function also
88-- takes the reader options for completeness -- you can use
99-- 'defaultHakyllReaderOptions' if you're unsure.
10−-- 'pandocBiblioCompiler' is a convenience wrapper which works like 'pandocCompiler',
11−-- but also takes paths to compiled bibliography and csl files.
1210{-# LANGUAGE Arrows #-}
1311{-# LANGUAGE DeriveDataTypeable #-}
1412{-# LANGUAGE GeneralizedNewtypeDeriving #-}
@@ -18,26 +16,23 @@ module Hakyll.Web.Pandoc.Biblio
1816 , Biblio (..)
1917 , biblioCompiler
2018 , readPandocBiblio
21− , pandocBiblioCompiler
2219 ) where
2320
2421
2522--------------------------------------------------------------------------------
26−import Control.Monad (liftM, replicateM)
27−import Data.Binary (Binary (..))
28−import Data.Typeable (Typeable)
23+import Control.Applicative ((<$>))
24+import Data.Binary (Binary (..))
25+import Data.Typeable (Typeable)
26+import qualified Text.CSL as CSL
27+import Text.CSL.Pandoc (processCites)
28+import Text.Pandoc (Pandoc, ReaderOptions (..))
29+
30+--------------------------------------------------------------------------------
2931import Hakyll.Core.Compiler
30−import Hakyll.Core.Compiler.Internal
3132import Hakyll.Core.Identifier
3233import Hakyll.Core.Item
33−import Hakyll.Core.Provider
3434import Hakyll.Core.Writable
3535import Hakyll.Web.Pandoc
36−import Hakyll.Web.Pandoc.Binary ()
37−import qualified Text.CSL as CSL
38−import Text.CSL.Pandoc (processCites)
39−import Text.Pandoc (Pandoc, ReaderOptions (..),
40− enableExtension, Extension (..))
4136
4237
4338--------------------------------------------------------------------------------
@@ -70,10 +65,8 @@ newtype Biblio = Biblio [CSL.Reference]
7065--------------------------------------------------------------------------------
7166instance Binary Biblio where
7267 -- Ugly.
73− get = do
74− len <- get
75− Biblio <$> replicateM len get
76− put (Biblio rs) = put (length rs) >> mapM_ put rs
68+ get = Biblio . read <$> get
69+ put (Biblio rs) = put $ show rs
7770
7871
7972--------------------------------------------------------------------------------
@@ -85,11 +78,8 @@ instance Writable Biblio where
8578--------------------------------------------------------------------------------
8679biblioCompiler :: Compiler (Item Biblio)
8780biblioCompiler = do
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
81+ filePath <- toFilePath <$> getUnderlying
82+ makeItem =<< unsafeCompiler (Biblio <$> CSL.readBiblioFile filePath)
9383
9484
9585--------------------------------------------------------------------------------
@@ -100,27 +90,14 @@ readPandocBiblio :: ReaderOptions
10090 -> Compiler (Item Pandoc)
10191readPandocBiblio ropt csl biblio item = do
10292 -- Parse CSL file, if given
103− provider <- compilerProvider <$> compilerAsk
104− style <- unsafeCompiler $
105− CSL.readCSLFile Nothing . (resourceFilePath provider) . itemIdentifier $ csl
93+ style <- unsafeCompiler $ CSL.readCSLFile . toFilePath . itemIdentifier $ csl
10694
10795 -- We need to know the citation keys, add then *before* actually parsing the
10896 -- actual page. If we don't do this, pandoc won't even consider them
10997 -- citations!
11098 let Biblio refs = itemBody biblio
111− pandoc <- itemBody <$> readPandocWith ropt item
112− let pandoc' = processCites style refs pandoc
99+ pandoc = itemBody $ readPandocWith ropt item
100+ pandoc' = processCites style refs pandoc
113101
114102 return $ fmap (const pandoc') item
115103
116−--------------------------------------------------------------------------------
117−pandocBiblioCompiler :: String -> String -> Compiler (Item String)
118−pandocBiblioCompiler cslFileName bibFileName = do
119− csl <- load $ fromFilePath cslFileName
120− bib <- load $ fromFilePath bibFileName
121− liftM writePandoc
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/FileType.hs → src/Hakyll/Web/Pandoc/FileType.hsrenamed+19−24
@@ -22,12 +22,10 @@ import Hakyll.Core.Item
2222data FileType
2323 = Binary
2424 | Css
25− | DocBook
2625 | Html
2726 | LaTeX
2827 | LiterateHaskell FileType
2928 | Markdown
30− | MediaWiki
3129 | OrgMode
3230 | PlainText
3331 | Rst
@@ -40,32 +38,29 @@ data FileType
4038fileType :: FilePath -> FileType
4139fileType = uncurry fileType' . splitExtension
4240 where
43− fileType' _ ".css" = Css
44− fileType' _ ".dbk" = DocBook
45− fileType' _ ".htm" = Html
46− fileType' _ ".html" = Html
47− fileType' f ".lhs" = LiterateHaskell $ case fileType f of
41+ fileType' _ ".css" = Css
42+ fileType' _ ".htm" = Html
43+ fileType' _ ".html" = Html
44+ fileType' f ".lhs" = LiterateHaskell $ case fileType f of
4845 -- If no extension is given, default to Markdown + LiterateHaskell
4946 Binary -> Markdown
5047 -- Otherwise, LaTeX + LiterateHaskell or whatever the user specified
5148 x -> x
52− fileType' _ ".markdown" = Markdown
53− fileType' _ ".mediawiki" = MediaWiki
54− fileType' _ ".md" = Markdown
55− fileType' _ ".mdn" = Markdown
56− fileType' _ ".mdown" = Markdown
57− fileType' _ ".mdwn" = Markdown
58− fileType' _ ".mkd" = Markdown
59− fileType' _ ".mkdwn" = Markdown
60− fileType' _ ".org" = OrgMode
61− fileType' _ ".page" = Markdown
62− fileType' _ ".rst" = Rst
63− fileType' _ ".tex" = LaTeX
64− fileType' _ ".text" = PlainText
65− fileType' _ ".textile" = Textile
66− fileType' _ ".txt" = PlainText
67− fileType' _ ".wiki" = MediaWiki
68− fileType' _ _ = Binary -- Treat unknown files as binary
49+ fileType' _ ".markdown" = Markdown
50+ fileType' _ ".md" = Markdown
51+ fileType' _ ".mdn" = Markdown
52+ fileType' _ ".mdown" = Markdown
53+ fileType' _ ".mdwn" = Markdown
54+ fileType' _ ".mkd" = Markdown
55+ fileType' _ ".mkdwn" = Markdown
56+ fileType' _ ".org" = OrgMode
57+ fileType' _ ".page" = Markdown
58+ fileType' _ ".rst" = Rst
59+ fileType' _ ".tex" = LaTeX
60+ fileType' _ ".text" = PlainText
61+ fileType' _ ".textile" = Textile
62+ fileType' _ ".txt" = PlainText
63+ fileType' _ _ = Binary -- Treat unknown files as binary
6964
7065
7166--------------------------------------------------------------------------------
lib/Hakyll/Web/Tags.hs → src/Hakyll/Web/Tags.hsrenamed+10−15
@@ -43,7 +43,6 @@
4343module Hakyll.Web.Tags
4444 ( Tags (..)
4545 , getTags
46− , getCategory
4746 , buildTagsWith
4847 , buildTags
4948 , buildCategories
@@ -64,14 +63,14 @@ module Hakyll.Web.Tags
6463
6564--------------------------------------------------------------------------------
6665import Control.Arrow ((&&&))
67−import Control.Monad (foldM, forM, forM_, mplus)
66+import Control.Monad (foldM, forM, forM_)
6867import Data.Char (toLower)
6968import Data.List (intercalate, intersperse,
7069 sortBy)
7170import qualified Data.Map as M
7271import Data.Maybe (catMaybes, fromMaybe)
72+import Data.Monoid (mconcat)
7373import Data.Ord (comparing)
74−import qualified Data.Set as S
7574import System.FilePath (takeBaseName, takeDirectory)
7675import Text.Blaze.Html (toHtml, toValue, (!))
7776import Text.Blaze.Html.Renderer.String (renderHtml)
@@ -88,8 +87,8 @@ import Hakyll.Core.Item
8887import Hakyll.Core.Metadata
8988import Hakyll.Core.Rules
9089import Hakyll.Core.Util.String
91−import Hakyll.Web.Html
9290import Hakyll.Web.Template.Context
91+import Hakyll.Web.Html
9392
9493
9594--------------------------------------------------------------------------------
@@ -98,22 +97,19 @@ data Tags = Tags
9897 { tagsMap :: [(String, [Identifier])]
9998 , tagsMakeId :: String -> Identifier
10099 , tagsDependency :: Dependency
101− }
100+ } deriving (Show)
102101
103102
104103--------------------------------------------------------------------------------
105104-- | Obtain tags from a page in the default way: parse them from the @tags@
106−-- metadata field. This can either be a list or a comma-separated string.
105+-- metadata field.
107106getTags :: MonadMetadata m => Identifier -> m [String]
108107getTags identifier = do
109108 metadata <- getMetadata identifier
110− return $ fromMaybe [] $
111− (lookupStringList "tags" metadata) `mplus`
112− (map trim . splitAll "," <$> lookupString "tags" metadata)
113−
109+ return $ maybe [] (map trim . splitAll ",") $ M.lookup "tags" metadata
114110
115111--------------------------------------------------------------------------------
116−-- | Obtain category from a page.
112+-- | Obtain categories from a page.
117113getCategory :: MonadMetadata m => Identifier -> m [String]
118114getCategory = return . return . takeBaseName . takeDirectory . toFilePath
119115
@@ -128,8 +124,7 @@ buildTagsWith :: MonadMetadata m
128124buildTagsWith f pattern makeId = do
129125 ids <- getMatches pattern
130126 tagMap <- foldM addTags M.empty ids
131− let set' = S.fromList ids
132− return $ Tags (M.toList tagMap) makeId (PatternDependency pattern set')
127+ return $ Tags (M.toList tagMap) makeId (PatternDependency pattern ids)
133128 where
134129 -- Create a tag map for one page
135130 addTags tagMap id' = do
@@ -153,8 +148,8 @@ buildCategories = buildTagsWith getCategory
153148tagsRules :: Tags -> (String -> Pattern -> Rules ()) -> Rules ()
154149tagsRules tags rules =
155150 forM_ (tagsMap tags) $ \(tag, identifiers) ->
156− rulesExtraDependencies [tagsDependency tags] $
157− create [tagsMakeId tags tag] $
151+ create [tagsMakeId tags tag] $
152+ rulesExtraDependencies [tagsDependency tags] $
158153 rules tag $ fromList identifiers
159154
160155
src/Hakyll/Web/Template.hsnew file+150
@@ -0,0 +1,150 @@
1+-- | This module provides means for reading and applying 'Template's.
2+--
3+-- Templates are tools to convert items into a string. They are perfectly suited
4+-- for laying out your site.
5+--
6+-- Let's look at an example template:
7+--
8+-- > <html>
9+-- > <head>
10+-- > <title>My crazy homepage - $title$</title>
11+-- > </head>
12+-- > <body>
13+-- > <div id="header">
14+-- > <h1>My crazy homepage - $title$</h1>
15+-- > </div>
16+-- > <div id="content">
17+-- > $body$
18+-- > </div>
19+-- > <div id="footer">
20+-- > By reading this you agree that I now own your soul
21+-- > </div>
22+-- > </body>
23+-- > </html>
24+--
25+-- As you can see, the format is very simple -- @$key$@ is used to render the
26+-- @$key$@ field from the page, everything else is literally copied. If you want
27+-- to literally insert @\"$key$\"@ into your page (for example, when you're
28+-- writing a Hakyll tutorial) you can use
29+--
30+-- > <p>
31+-- > A literal $$key$$.
32+-- > </p>
33+--
34+-- Because of it's simplicity, these templates can be used for more than HTML:
35+-- you could make, for example, CSS or JS templates as well.
36+module Hakyll.Web.Template
37+ ( Template
38+ , templateCompiler
39+ , applyTemplate
40+ , loadAndApplyTemplate
41+ , applyAsTemplate
42+ ) where
43+
44+
45+--------------------------------------------------------------------------------
46+import Control.Monad (liftM)
47+import Control.Monad.Error (MonadError (..))
48+import Data.List (intercalate)
49+import Data.Monoid (mappend)
50+import Prelude hiding (id)
51+
52+
53+--------------------------------------------------------------------------------
54+import Hakyll.Core.Compiler
55+import Hakyll.Core.Identifier
56+import Hakyll.Core.Item
57+import Hakyll.Web.Template.Context
58+import Hakyll.Web.Template.Internal
59+import Hakyll.Web.Template.Read
60+
61+
62+--------------------------------------------------------------------------------
63+-- | Read a template.
64+templateCompiler :: Compiler (Item Template)
65+templateCompiler = cached "Hakyll.Web.Template.templateCompiler" $ do
66+ item <- getResourceString
67+ return $ fmap readTemplate item
68+
69+
70+--------------------------------------------------------------------------------
71+applyTemplate :: Template -- ^ Template
72+ -> Context a -- ^ Context
73+ -> Item a -- ^ Page
74+ -> Compiler (Item String) -- ^ Resulting item
75+applyTemplate tpl context item = do
76+ body <- applyTemplate' tpl context item
77+ return $ itemSetBody body item
78+
79+
80+--------------------------------------------------------------------------------
81+applyTemplate' :: Template -- ^ Template
82+ -> Context a -- ^ Context
83+ -> Item a -- ^ Page
84+ -> Compiler String -- ^ Resulting item
85+applyTemplate' tpl context x = go tpl
86+ where
87+ context' = unContext (context `mappend` missingField)
88+ go = liftM concat . mapM applyElem . unTemplate
89+
90+ applyElem (Chunk c) = return c
91+
92+ applyElem Escaped = return "$"
93+
94+ applyElem (Key k) = context' k x >>= getString k
95+
96+ applyElem (If k t mf) = (context' k x >> go t) `catchError` handler
97+ where
98+ handler _ = case mf of
99+ Nothing -> return ""
100+ Just f -> go f
101+
102+ applyElem (For k b s) = context' k x >>= \cf -> case cf of
103+ StringField _ -> fail $
104+ "Hakyll.Web.Template.applyTemplateWith: expected ListField but " ++
105+ "got StringField for key " ++ show k
106+ ListField c xs -> do
107+ sep <- maybe (return "") go s
108+ bs <- mapM (applyTemplate' b c) xs
109+ return $ intercalate sep bs
110+
111+ applyElem (Partial p) = do
112+ tpl' <- loadBody (fromFilePath p)
113+ applyTemplate' tpl' context x
114+
115+ getString _ (StringField s) = return s
116+ getString k (ListField _ _) = fail $
117+ "Hakyll.Web.Template.applyTemplateWith: expected StringField but " ++
118+ "got ListField for key " ++ show k
119+
120+
121+--------------------------------------------------------------------------------
122+-- | The following pattern is so common:
123+--
124+-- > tpl <- loadBody "templates/foo.html"
125+-- > someCompiler
126+-- > >>= applyTemplate tpl context
127+--
128+-- That we have a single function which does this:
129+--
130+-- > someCompiler
131+-- > >>= loadAndApplyTemplate "templates/foo.html" context
132+loadAndApplyTemplate :: Identifier -- ^ Template identifier
133+ -> Context a -- ^ Context
134+ -> Item a -- ^ Page
135+ -> Compiler (Item String) -- ^ Resulting item
136+loadAndApplyTemplate identifier context item = do
137+ tpl <- loadBody identifier
138+ applyTemplate tpl context item
139+
140+
141+--------------------------------------------------------------------------------
142+-- | It is also possible that you want to substitute @$key$@s within the body of
143+-- an item. This function does that by interpreting the item body as a template,
144+-- and then applying it to itself.
145+applyAsTemplate :: Context String -- ^ Context
146+ -> Item String -- ^ Item and template
147+ -> Compiler (Item String) -- ^ Resulting item
148+applyAsTemplate context item =
149+ let tpl = readTemplate $ itemBody item
150+ in applyTemplate tpl context item
src/Hakyll/Web/Template/Context.hsnew file+280
@@ -0,0 +1,280 @@
1+--------------------------------------------------------------------------------
2+{-# LANGUAGE ExistentialQuantification #-}
3+module Hakyll.Web.Template.Context
4+ ( ContextField (..)
5+ , Context (..)
6+ , field
7+ , constField
8+ , listField
9+ , functionField
10+ , mapContext
11+
12+ , defaultContext
13+ , bodyField
14+ , metadataField
15+ , urlField
16+ , pathField
17+ , titleField
18+ , dateField
19+ , dateFieldWith
20+ , getItemUTC
21+ , modificationTimeField
22+ , modificationTimeFieldWith
23+ , teaserField
24+ , missingField
25+ ) where
26+
27+
28+--------------------------------------------------------------------------------
29+import Control.Applicative (Alternative (..), (<$>))
30+import Control.Monad (msum)
31+import Data.List (intercalate)
32+import qualified Data.Map as M
33+import Data.Monoid (Monoid (..))
34+import Data.Time.Clock (UTCTime (..))
35+import Data.Time.Format (formatTime, parseTime)
36+import System.FilePath (takeBaseName, takeFileName)
37+import System.Locale (TimeLocale, defaultTimeLocale)
38+
39+
40+--------------------------------------------------------------------------------
41+import Hakyll.Core.Compiler
42+import Hakyll.Core.Compiler.Internal
43+import Hakyll.Core.Identifier
44+import Hakyll.Core.Item
45+import Hakyll.Core.Metadata
46+import Hakyll.Core.Provider
47+import Hakyll.Core.Util.String (splitAll, needlePrefix)
48+import Hakyll.Web.Html
49+
50+
51+--------------------------------------------------------------------------------
52+-- | Mostly for internal usage
53+data ContextField
54+ = StringField String
55+ | forall a. ListField (Context a) [Item a]
56+
57+
58+--------------------------------------------------------------------------------
59+newtype Context a = Context
60+ { unContext :: String -> Item a -> Compiler ContextField
61+ }
62+
63+
64+--------------------------------------------------------------------------------
65+instance Monoid (Context a) where
66+ mempty = missingField
67+ mappend (Context f) (Context g) = Context $ \k i -> f k i <|> g k i
68+
69+
70+--------------------------------------------------------------------------------
71+field' :: String -> (Item a -> Compiler ContextField) -> Context a
72+field' key value = Context $ \k i -> if k == key then value i else empty
73+
74+
75+--------------------------------------------------------------------------------
76+field :: String -> (Item a -> Compiler String) -> Context a
77+field key value = field' key (fmap StringField . value)
78+
79+
80+--------------------------------------------------------------------------------
81+constField :: String -> String -> Context a
82+constField key = field key . const . return
83+
84+
85+--------------------------------------------------------------------------------
86+listField :: String -> Context a -> Compiler [Item a] -> Context b
87+listField key c xs = field' key $ \_ -> fmap (ListField c) xs
88+
89+
90+--------------------------------------------------------------------------------
91+functionField :: String -> ([String] -> Item a -> Compiler String) -> Context a
92+functionField name value = Context $ \k i -> case words k of
93+ [] -> empty
94+ (n : args)
95+ | n == name -> StringField <$> value args i
96+ | otherwise -> empty
97+
98+
99+--------------------------------------------------------------------------------
100+mapContext :: (String -> String) -> Context a -> Context a
101+mapContext f (Context c) = Context $ \k i -> do
102+ fld <- c k i
103+ case fld of
104+ StringField str -> return $ StringField (f str)
105+ ListField _ _ -> fail $
106+ "Hakyll.Web.Template.Context.mapContext: " ++
107+ "can't map over a ListField!"
108+
109+
110+--------------------------------------------------------------------------------
111+defaultContext :: Context String
112+defaultContext =
113+ bodyField "body" `mappend`
114+ metadataField `mappend`
115+ urlField "url" `mappend`
116+ pathField "path" `mappend`
117+ titleField "title" `mappend`
118+ missingField
119+
120+
121+--------------------------------------------------------------------------------
122+teaserSeparator :: String
123+teaserSeparator = "<!--more-->"
124+
125+
126+--------------------------------------------------------------------------------
127+bodyField :: String -> Context String
128+bodyField key = field key $ return . itemBody
129+
130+
131+--------------------------------------------------------------------------------
132+-- | Map any field to its metadata value, if present
133+metadataField :: Context a
134+metadataField = Context $ \k i -> do
135+ value <- getMetadataField (itemIdentifier i) k
136+ maybe empty (return . StringField) value
137+
138+
139+--------------------------------------------------------------------------------
140+-- | Absolute url to the resulting item
141+urlField :: String -> Context a
142+urlField key = field key $
143+ fmap (maybe empty toUrl) . getRoute . itemIdentifier
144+
145+
146+--------------------------------------------------------------------------------
147+-- | Filepath of the underlying file of the item
148+pathField :: String -> Context a
149+pathField key = field key $ return . toFilePath . itemIdentifier
150+
151+
152+--------------------------------------------------------------------------------
153+-- | This title field takes the basename of the underlying file by default
154+titleField :: String -> Context a
155+titleField = mapContext takeBaseName . pathField
156+
157+
158+--------------------------------------------------------------------------------
159+-- | When the metadata has a field called @published@ in one of the
160+-- following formats then this function can render the date.
161+--
162+-- * @Mon, 06 Sep 2010 00:01:00 +0000@
163+--
164+-- * @Mon, 06 Sep 2010 00:01:00 UTC@
165+--
166+-- * @Mon, 06 Sep 2010 00:01:00@
167+--
168+-- * @2010-09-06T00:01:00+0000@
169+--
170+-- * @2010-09-06T00:01:00Z@
171+--
172+-- * @2010-09-06T00:01:00@
173+--
174+-- * @2010-09-06 00:01:00+0000@
175+--
176+-- * @2010-09-06 00:01:00@
177+--
178+-- * @September 06, 2010 00:01 AM@
179+--
180+-- Following date-only formats are supported too (@00:00:00@ for time is
181+-- assumed)
182+--
183+-- * @2010-09-06@
184+--
185+-- * @September 06, 2010@
186+--
187+-- Alternatively, when the metadata has a field called @path@ in a
188+-- @folder/yyyy-mm-dd-title.extension@ format (the convention for pages)
189+-- and no @published@ metadata field set, this function can render
190+-- the date.
191+dateField :: String -- ^ Key in which the rendered date should be placed
192+ -> String -- ^ Format to use on the date
193+ -> Context a -- ^ Resulting context
194+dateField = dateFieldWith defaultTimeLocale
195+
196+
197+--------------------------------------------------------------------------------
198+-- | This is an extended version of 'dateField' that allows you to
199+-- specify a time locale that is used for outputting the date. For more
200+-- details, see 'dateField'.
201+dateFieldWith :: TimeLocale -- ^ Output time locale
202+ -> String -- ^ Destination key
203+ -> String -- ^ Format to use on the date
204+ -> Context a -- ^ Resulting context
205+dateFieldWith locale key format = field key $ \i -> do
206+ time <- getItemUTC locale $ itemIdentifier i
207+ return $ formatTime locale format time
208+
209+
210+--------------------------------------------------------------------------------
211+-- | Parser to try to extract and parse the time from the @published@
212+-- field or from the filename. See 'dateField' for more information.
213+-- Exported for user convenience.
214+getItemUTC :: MonadMetadata m
215+ => TimeLocale -- ^ Output time locale
216+ -> Identifier -- ^ Input page
217+ -> m UTCTime -- ^ Parsed UTCTime
218+getItemUTC locale id' = do
219+ metadata <- getMetadata id'
220+ let tryField k fmt = M.lookup k metadata >>= parseTime' fmt
221+ fn = takeFileName $ toFilePath id'
222+
223+ maybe empty' return $ msum $
224+ [tryField "published" fmt | fmt <- formats] ++
225+ [tryField "date" fmt | fmt <- formats] ++
226+ [parseTime' "%Y-%m-%d" $ intercalate "-" $ take 3 $ splitAll "-" fn]
227+ where
228+ empty' = fail $ "Hakyll.Web.Template.Context.getItemUTC: " ++
229+ "could not parse time for " ++ show id'
230+ parseTime' = parseTime locale
231+ formats =
232+ [ "%a, %d %b %Y %H:%M:%S %Z"
233+ , "%Y-%m-%dT%H:%M:%S%Z"
234+ , "%Y-%m-%d %H:%M:%S%Z"
235+ , "%Y-%m-%d"
236+ , "%B %e, %Y %l:%M %p"
237+ , "%B %e, %Y"
238+ , "%b %d, %Y"
239+ ]
240+
241+
242+--------------------------------------------------------------------------------
243+modificationTimeField :: String -- ^ Key
244+ -> String -- ^ Format
245+ -> Context a -- ^ Resuting context
246+modificationTimeField = modificationTimeFieldWith defaultTimeLocale
247+
248+
249+--------------------------------------------------------------------------------
250+modificationTimeFieldWith :: TimeLocale -- ^ Time output locale
251+ -> String -- ^ Key
252+ -> String -- ^ Format
253+ -> Context a -- ^ Resulting context
254+modificationTimeFieldWith locale key fmt = field key $ \i -> do
255+ provider <- compilerProvider <$> compilerAsk
256+ let mtime = resourceModificationTime provider $ itemIdentifier i
257+ return $ formatTime locale fmt mtime
258+
259+
260+--------------------------------------------------------------------------------
261+-- | A context with "teaser" key which contain a teaser of the item.
262+-- The item is loaded from the given snapshot (which should be saved
263+-- in the user code before any templates are applied).
264+teaserField :: String -- ^ Key to use
265+ -> Snapshot -- ^ Snapshot to load
266+ -> Context String -- ^ Resulting context
267+teaserField key snapshot = field key $ \item -> do
268+ body <- itemBody <$> loadSnapshot (itemIdentifier item) snapshot
269+ case needlePrefix teaserSeparator body of
270+ Nothing -> fail $
271+ "Hakyll.Web.Template.Context: no teaser defined for " ++
272+ show (itemIdentifier item)
273+ Just t -> return t
274+
275+
276+--------------------------------------------------------------------------------
277+missingField :: Context a
278+missingField = Context $ \k i -> fail $
279+ "Missing field $" ++ k ++ "$ in context for item " ++
280+ show (itemIdentifier i)
src/Hakyll/Web/Template/Internal.hsnew file+63
@@ -0,0 +1,63 @@
1+--------------------------------------------------------------------------------
2+-- | Module containing the template data structure
3+{-# LANGUAGE DeriveDataTypeable #-}
4+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5+module Hakyll.Web.Template.Internal
6+ ( Template (..)
7+ , TemplateElement (..)
8+ ) where
9+
10+
11+--------------------------------------------------------------------------------
12+import Control.Applicative (pure, (<$>), (<*>))
13+import Data.Binary (Binary, get, getWord8, put, putWord8)
14+import Data.Typeable (Typeable)
15+
16+
17+--------------------------------------------------------------------------------
18+import Hakyll.Core.Writable
19+
20+
21+--------------------------------------------------------------------------------
22+-- | Datatype used for template substitutions.
23+newtype Template = Template
24+ { unTemplate :: [TemplateElement]
25+ } deriving (Show, Eq, Binary, Typeable)
26+
27+
28+--------------------------------------------------------------------------------
29+instance Writable Template where
30+ -- Writing a template is impossible
31+ write _ _ = return ()
32+
33+
34+--------------------------------------------------------------------------------
35+-- | Elements of a template.
36+data TemplateElement
37+ = Chunk String
38+ | Key String
39+ | Escaped
40+ | If String Template (Maybe Template) -- key, then branch, else branch
41+ | For String Template (Maybe Template) -- key, body, separator
42+ | Partial String -- filename
43+ deriving (Show, Eq, Typeable)
44+
45+
46+--------------------------------------------------------------------------------
47+instance Binary TemplateElement where
48+ put (Chunk string) = putWord8 0 >> put string
49+ put (Key key) = putWord8 1 >> put key
50+ put (Escaped) = putWord8 2
51+ put (If key t f) = putWord8 3 >> put key >> put t >> put f
52+ put (For key b s) = putWord8 4 >> put key >> put b >> put s
53+ put (Partial p) = putWord8 5 >> put p
54+
55+ get = getWord8 >>= \tag -> case tag of
56+ 0 -> Chunk <$> get
57+ 1 -> Key <$> get
58+ 2 -> pure Escaped
59+ 3 -> If <$> get <*> get <*> get
60+ 4 -> For <$> get <*> get <*> get
61+ 5 -> Partial <$> get
62+ _ -> error $
63+ "Hakyll.Web.Template.Internal: Error reading cached template"
lib/Hakyll/Web/Template/List.hs → src/Hakyll/Web/Template/List.hsrenamed+3−23
@@ -13,8 +13,6 @@ module Hakyll.Web.Template.List
1313 , applyJoinTemplateList
1414 , chronological
1515 , recentFirst
16− , sortChronological
17− , sortRecentFirst
1816 ) where
1917
2018
@@ -22,12 +20,11 @@ module Hakyll.Web.Template.List
2220import Control.Monad (liftM)
2321import Data.List (intersperse, sortBy)
2422import Data.Ord (comparing)
25−import Data.Time.Locale.Compat (defaultTimeLocale)
23+import System.Locale (defaultTimeLocale)
2624
2725
2826--------------------------------------------------------------------------------
2927import Hakyll.Core.Compiler
30−import Hakyll.Core.Identifier
3128import Hakyll.Core.Item
3229import Hakyll.Core.Metadata
3330import Hakyll.Web.Template
@@ -68,24 +65,7 @@ chronological =
6865 sortByM f xs = liftM (map fst . sortBy (comparing snd)) $
6966 mapM (\x -> liftM (x,) (f x)) xs
7067
71−
7268--------------------------------------------------------------------------------
7369-- | The reverse of 'chronological'
74−recentFirst :: MonadMetadata m => [Item a] -> m [Item a]
75−recentFirst = liftM reverse . chronological
76−
77−
78−--------------------------------------------------------------------------------
79−-- | Version of 'chronological' which doesn't need the actual items.
80−sortChronological
81− :: MonadMetadata m => [Identifier] -> m [Identifier]
82−sortChronological ids =
83− liftM (map itemIdentifier) $ chronological [Item i () | i <- ids]
84−
85−
86−--------------------------------------------------------------------------------
87−-- | Version of 'recentFirst' which doesn't need the actual items.
88−sortRecentFirst
89− :: MonadMetadata m => [Identifier] -> m [Identifier]
90−sortRecentFirst ids =
91− liftM (map itemIdentifier) $ recentFirst [Item i () | i <- ids]
70+recentFirst :: (MonadMetadata m, Functor m) => [Item a] -> m [Item a]
71+recentFirst = fmap reverse . chronological
src/Hakyll/Web/Template/Read.hsnew file+93
@@ -0,0 +1,93 @@
1+--------------------------------------------------------------------------------
2+-- | Read templates in Hakyll's native format
3+module Hakyll.Web.Template.Read
4+ ( readTemplate
5+ ) where
6+
7+
8+--------------------------------------------------------------------------------
9+import Control.Applicative ((<$), (<$>))
10+import Control.Monad (void)
11+import Text.Parsec
12+import Text.Parsec.String
13+
14+
15+--------------------------------------------------------------------------------
16+import Hakyll.Core.Util.Parser
17+import Hakyll.Web.Template.Internal
18+
19+
20+--------------------------------------------------------------------------------
21+readTemplate :: String -> Template
22+readTemplate input = case parse template "" input of
23+ Left err -> error $ "Cannot parse template: " ++ show err
24+ Right t -> t
25+
26+
27+--------------------------------------------------------------------------------
28+template :: Parser Template
29+template = Template <$>
30+ (many1 $ chunk <|> escaped <|> conditional <|> for <|> partial <|> key)
31+
32+
33+--------------------------------------------------------------------------------
34+chunk :: Parser TemplateElement
35+chunk = Chunk <$> (many1 $ noneOf "$")
36+
37+
38+--------------------------------------------------------------------------------
39+escaped :: Parser TemplateElement
40+escaped = Escaped <$ (try $ string "$$")
41+
42+
43+--------------------------------------------------------------------------------
44+conditional :: Parser TemplateElement
45+conditional = try $ do
46+ void $ string "$if("
47+ i <- metadataKey
48+ void $ string ")$"
49+ thenBranch <- template
50+ elseBranch <- optionMaybe $ try (string "$else$") >> template
51+ void $ string "$endif$"
52+ return $ If i thenBranch elseBranch
53+
54+
55+--------------------------------------------------------------------------------
56+for :: Parser TemplateElement
57+for = try $ do
58+ void $ string "$for("
59+ i <- metadataKey
60+ void $ string ")$"
61+ body <- template
62+ sep <- optionMaybe $ try (string "$sep$") >> template
63+ void $ string "$endfor$"
64+ return $ For i body sep
65+
66+
67+--------------------------------------------------------------------------------
68+partial :: Parser TemplateElement
69+partial = try $ do
70+ void $ string "$partial("
71+ i <- stringLiteral
72+ void $ string ")$"
73+ return $ Partial i
74+
75+
76+--------------------------------------------------------------------------------
77+key :: Parser TemplateElement
78+key = try $ do
79+ void $ char '$'
80+ k <- metadataKey
81+ void $ char '$'
82+ return $ Key k
83+
84+
85+--------------------------------------------------------------------------------
86+stringLiteral :: Parser String
87+stringLiteral = do
88+ void $ char '\"'
89+ str <- many $ do
90+ x <- noneOf "\""
91+ if x == '\\' then anyChar else return x
92+ void $ char '\"'
93+ return str
src/Init.hsdeleted−123
@@ -1,123 +0,0 @@
1−--------------------------------------------------------------------------------
2−module Main
3− ( main
4− ) where
5−
6−
7−--------------------------------------------------------------------------------
8−import Control.Arrow (first)
9−import Control.Monad (forM, forM_)
10−import Data.Char (isAlphaNum, isNumber)
11−import Data.List (foldl', intercalate, isPrefixOf)
12−import Data.Version (Version (..))
13−import System.Directory (canonicalizePath, copyFile,
14− doesFileExist)
15−import System.Environment (getArgs, getProgName)
16−import System.Exit (exitFailure)
17−import System.FilePath (splitDirectories, (</>))
18−
19−
20−--------------------------------------------------------------------------------
21−import Hakyll.Core.Util.File
22−import Paths_hakyll
23−
24−
25−--------------------------------------------------------------------------------
26−import Prelude
27−
28−
29−--------------------------------------------------------------------------------
30−main :: IO ()
31−main = do
32− progName <- getProgName
33− args <- getArgs
34− srcDir <- getDataFileName "example"
35− files <- getRecursiveContents (const $ return False) srcDir
36−
37− case args of
38− -- When the argument begins with hyphens, it's more likely that the user
39− -- intends to attempt some arguments like ("--help", "-h", "--version", etc.)
40− -- rather than create directory with that name.
41− -- If dstDir begins with hyphens, the guard will prevent it from creating
42− -- directory with that name so we can fall to the second alternative
43− -- which prints a usage info for user.
44− [dstDir] | not ("-" `isPrefixOf` dstDir) ->
45− createFiles False srcDir files dstDir
46− ["-f", dstDir] ->
47− createFiles True srcDir files dstDir
48− _ -> do
49− putStrLn $ "Usage: " ++ progName ++ "[-f] <directory>"
50− exitFailure
51−
52− where
53− createFiles force srcDir files dstDir = do
54− name <- makeName dstDir
55− let cabalPath = dstDir </> name ++ ".cabal"
56−
57− diff <- if force then return []
58− else existingFiles dstDir (cabalPath : files)
59−
60− case diff of
61− [] -> do
62− forM_ files $ \file -> do
63− let dst = dstDir </> file
64− src = srcDir </> file
65− putStrLn $ "Creating " ++ dst
66− makeDirectories dst
67− copyFile src dst
68−
69− putStrLn $ "Creating " ++ cabalPath
70− createCabal cabalPath name
71− fs -> do
72− putStrLn $ "The following files will be overwritten:"
73− mapM_ putStrLn fs
74− putStrLn $ "Use -f to overwrite them"
75− exitFailure
76−
77−existingFiles :: FilePath -> [FilePath] -> IO [FilePath]
78−existingFiles dstDir files = fmap concat $ forM files $ \file -> do
79− let dst = dstDir </> file
80− exists <- doesFileExist dst
81− return $ if exists then [dst] else []
82−
83−-- | Figure out a good cabal package name from the given (existing) directory
84−-- name
85−makeName :: FilePath -> IO String
86−makeName dstDir = do
87− canonical <- canonicalizePath dstDir
88− return $ case safeLast (splitDirectories canonical) of
89− Nothing -> fallbackName
90− Just "/" -> fallbackName
91− Just x -> repair (fallbackName ++) id x
92− where
93− -- Package name repair code comes from
94− -- cabal-install.Distribution.Client.Init.Heuristics
95− repair invalid valid x = case dropWhile (not . isAlphaNum) x of
96− "" -> repairComponent ""
97− x' -> let (c, r) = first repairComponent $ break (not . isAlphaNum) x'
98− in c ++ repairRest r
99− where repairComponent c | all isNumber c = invalid c
100− | otherwise = valid c
101− repairRest = repair id ('-' :)
102− fallbackName = "site"
103−
104− safeLast = foldl' (\_ x -> Just x) Nothing
105−
106−createCabal :: FilePath -> String -> IO ()
107−createCabal path name =
108− writeFile path $ unlines [
109− "name: " ++ name
110− , "version: 0.1.0.0"
111− , "build-type: Simple"
112− , "cabal-version: >= 1.10"
113− , ""
114− , "executable site"
115− , " main-is: site.hs"
116− , " build-depends: base == 4.*"
117− , " , hakyll == " ++ version' ++ ".*"
118− , " ghc-options: -threaded"
119− , " default-language: Haskell2010"
120− ]
121− where
122− -- Major hakyll version
123− version' = intercalate "." . take 2 . map show $ versionBranch version
stack.yamldeleted−33
@@ -1,33 +0,0 @@
1−resolver: lts-13.29
2−save-hackage-creds: false
3−
4−flags:
5− hakyll:
6− previewServer: true
7− watchServer: true
8− checkExternal: true
9− usePandoc: true
10− buildWebsite: true
11−
12−packages:
13− - '.'
14−
15−nix:
16− enable: false
17− packages:
18− - zlib
19−
20−build:
21− haddock: true
22− haddock-hyperlink-source: true
23− haddock-deps: false
24−
25−extra-deps:
26−- 'cmark-gfm-0.2.0'
27−- 'hslua-module-system-0.2.1'
28−- 'ipynb-0.1'
29−- 'lrucache-1.2.0.1'
30−- 'pandoc-2.7.3'
31−- 'pandoc-citeproc-0.16.2'
32−- 'skylighting-0.8.2'
33−- 'skylighting-core-0.8.2'
test.hsdeleted−30
@@ -1,30 +0,0 @@
1−{-# LANGUAGE BangPatterns #-}
2−import Control.Monad (forM)
3−import qualified Crypto.Hash.SHA256 as SHA256
4−import qualified Data.ByteString.Base16 as Base16
5−import qualified Data.ByteString.Char8 as BS8
6−import qualified Data.ByteString.Lazy as BSL
7−import Data.Map (Map)
8−import qualified Data.Map as Map
9−import Hakyll
10−import System.FilePath ((</>))
11−
12−type FileHashes = Map Identifier String
13−
14−mkFileHashes :: FilePath -> IO FileHashes
15−mkFileHashes dir = do
16− allFiles <- getRecursiveContents (\_ -> return False) dir
17− fmap Map.fromList $ forM allFiles $ \path0 -> do
18− let path1 = dir </> path0
19− !h <- hash path1
20− return (fromFilePath path1, h)
21− where
22− hash :: FilePath -> IO String
23− hash fp = do
24− !h <- SHA256.hashlazy <$> BSL.readFile fp
25− return $! BS8.unpack $! Base16.encode h
26−
27−main :: IO ()
28−main = hakyll $ do
29− fileHashes <- preprocess (mkFileHashes "images")
30− undefined
tests/Hakyll/Core/Dependencies/Tests.hs+4−5
@@ -9,8 +9,8 @@ module Hakyll.Core.Dependencies.Tests
99import Data.List (delete)
1010import qualified Data.Map as M
1111import qualified Data.Set as S
12−import Test.Tasty (TestTree, testGroup)
13−import Test.Tasty.HUnit (Assertion, (@=?))
12+import Test.Framework (Test, testGroup)
13+import Test.HUnit (Assertion, (@=?))
1414
1515
1616--------------------------------------------------------------------------------
@@ -20,7 +20,7 @@ import TestSuite.Util
2020
2121
2222--------------------------------------------------------------------------------
23−tests :: TestTree
23+tests :: Test
2424tests = testGroup "Hakyll.Core.Dependencies.Tests" $
2525 fromAssertions "analyze" [case01, case02, case03]
2626
@@ -38,8 +38,7 @@ oldFacts = M.fromList
3838 , ("posts/02.md",
3939 [])
4040 , ("index.md",
41− [ PatternDependency "posts/*"
42− (S.fromList ["posts/01.md", "posts/02.md"])
41+ [ PatternDependency "posts/*" ["posts/01.md", "posts/02.md"]
4342 , IdentifierDependency "posts/01.md"
4443 , IdentifierDependency "posts/02.md"
4544 ])
tests/Hakyll/Core/Identifier/Tests.hs+5−6
@@ -6,8 +6,8 @@ module Hakyll.Core.Identifier.Tests
66
77
88--------------------------------------------------------------------------------
9−import Test.Tasty (TestTree, testGroup)
10−import Test.Tasty.HUnit ((@=?))
9+import Test.Framework (Test, testGroup)
10+import Test.HUnit ((@=?))
1111
1212
1313--------------------------------------------------------------------------------
@@ -17,7 +17,7 @@ import TestSuite.Util
1717
1818
1919--------------------------------------------------------------------------------
20−tests :: TestTree
20+tests :: Test
2121tests = testGroup "Hakyll.Core.Identifier.Tests" $ concat
2222 [ captureTests
2323 , matchesTests
@@ -25,7 +25,7 @@ tests = testGroup "Hakyll.Core.Identifier.Tests" $ concat
2525
2626
2727--------------------------------------------------------------------------------
28−captureTests :: [TestTree]
28+captureTests :: [Test]
2929captureTests = fromAssertions "capture"
3030 [ Just ["bar"] @=? capture "foo/**" "foo/bar"
3131 , Just ["foo/bar"] @=? capture "**" "foo/bar"
@@ -42,12 +42,11 @@ captureTests = fromAssertions "capture"
4242 , Just ["lol", "fun/large"] @=? capture "*cat/**.jpg" "lolcat/fun/large.jpg"
4343 , Just [] @=? capture "\\*.jpg" "*.jpg"
4444 , Nothing @=? capture "\\*.jpg" "foo.jpg"
45− , Just ["xyz","42"] @=? capture (fromRegex "cat-([a-z]+)/foo([0-9]+).jpg") "cat-xyz/foo42.jpg"
4645 ]
4746
4847
4948--------------------------------------------------------------------------------
50−matchesTests :: [TestTree]
49+matchesTests :: [Test]
5150matchesTests = fromAssertions "matches"
5251 [ True @=? matches (fromList ["foo.markdown"]) "foo.markdown"
5352 , False @=? matches (fromList ["foo"]) (setVersion (Just "x") "foo")
tests/Hakyll/Core/Provider/Metadata/Tests.hs+15−24
@@ -5,27 +5,26 @@ module Hakyll.Core.Provider.Metadata.Tests
55
66
77--------------------------------------------------------------------------------
8−import qualified Data.HashMap.Strict as HMS
9−import qualified Data.Text as T
10−import qualified Data.Yaml as Yaml
11−import Hakyll.Core.Metadata
8+import Test.Framework (Test, testGroup)
9+import Test.HUnit (Assertion, (@=?))
10+import Text.Parsec as P
11+import Text.Parsec.String (Parser)
12+
13+
14+--------------------------------------------------------------------------------
1215import Hakyll.Core.Provider.Metadata
13−import Test.Tasty (TestTree, testGroup)
14−import Test.Tasty.HUnit (Assertion, assertFailure, (@=?))
1516import TestSuite.Util
1617
1718
1819--------------------------------------------------------------------------------
19−tests :: TestTree
20+tests :: Test
2021tests = testGroup "Hakyll.Core.Provider.Metadata.Tests" $
2122 fromAssertions "page" [testPage01, testPage02]
2223
2324
24−
2525--------------------------------------------------------------------------------
2626testPage01 :: Assertion
27−testPage01 =
28− (meta [("foo", "bar")], "qux\n") `expectRight` parsePage
27+testPage01 = testParse page ([("foo", "bar")], "qux\n")
2928 "---\n\
3029 \foo: bar\n\
3130 \---\n\
@@ -34,29 +33,21 @@ testPage01 =
3433
3534--------------------------------------------------------------------------------
3635testPage02 :: Assertion
37−testPage02 =
38− (meta [("description", descr)], "Hello I am dog\n") `expectRight`
39− parsePage
36+testPage02 = testParse page
37+ ([("description", descr)], "Hello I am dog\n")
4038 "---\n\
4139 \description: A long description that would look better if it\n\
4240 \ spanned multiple lines and was indented\n\
4341 \---\n\
4442 \Hello I am dog\n"
4543 where
46− descr :: String
4744 descr =
4845 "A long description that would look better if it \
4946 \spanned multiple lines and was indented"
5047
5148
5249--------------------------------------------------------------------------------
53−meta :: Yaml.ToJSON a => [(String, a)] -> Metadata
54−meta pairs = HMS.fromList [(T.pack k, Yaml.toJSON v) | (k, v) <- pairs]
55−
56−
57−--------------------------------------------------------------------------------
58−-- | This is useful when the 'Left' side of 'Either' doesn't have an 'Eq'
59−-- instance.
60−expectRight :: (Eq b, Show a, Show b) => b -> Either a b -> Assertion
61−expectRight _ (Left err) = assertFailure (show err)
62−expectRight expected (Right res) = expected @=? res
50+testParse :: (Eq a, Show a) => Parser a -> a -> String -> Assertion
51+testParse parser expected input = case P.parse parser "<inline>" input of
52+ Left err -> error $ show err
53+ Right x -> expected @=? x
tests/Hakyll/Core/Provider/Tests.hs+12−8
@@ -6,15 +6,19 @@ module Hakyll.Core.Provider.Tests
66
77
88--------------------------------------------------------------------------------
9−import Hakyll.Core.Metadata
9+import qualified Data.Map as M
10+import Test.Framework (Test, testGroup)
11+import Test.Framework.Providers.HUnit (testCase)
12+import Test.HUnit (Assertion, assert, (@=?))
13+
14+
15+--------------------------------------------------------------------------------
1016import Hakyll.Core.Provider
11−import Test.Tasty (TestTree, testGroup)
12−import Test.Tasty.HUnit (Assertion, testCase, (@=?))
1317import TestSuite.Util
1418
1519
1620--------------------------------------------------------------------------------
17−tests :: TestTree
21+tests :: Test
1822tests = testGroup "Hakyll.Core.Provider.Tests"
1923 [ testCase "case01" case01
2024 ]
@@ -25,12 +29,12 @@ case01 :: Assertion
2529case01 = do
2630 store <- newTestStore
2731 provider <- newTestProvider store
28− True @=? resourceExists provider "example.md"
32+ assert $ resourceExists provider "example.md"
2933
3034 metadata <- resourceMetadata provider "example.md"
31− Just "An example" @=? lookupString "title" metadata
32− Just "External data" @=? lookupString "external" metadata
35+ Just "An example" @=? M.lookup "title" metadata
36+ Just "External data" @=? M.lookup "external" metadata
3337
3438 doesntExist <- resourceMetadata provider "doesntexist.md"
35− mempty @=? doesntExist
39+ M.empty @=? doesntExist
3640 cleanTestEnv
tests/Hakyll/Core/Routes/Tests.hs+8−14
@@ -6,18 +6,18 @@ module Hakyll.Core.Routes.Tests
66
77
88--------------------------------------------------------------------------------
9−import Data.Maybe (fromMaybe)
9+import Test.Framework (Test, testGroup)
10+import Test.HUnit (Assertion, (@=?))
11+
12+
13+--------------------------------------------------------------------------------
1014import Hakyll.Core.Identifier
11−import Hakyll.Core.Metadata
1215import Hakyll.Core.Routes
13−import System.FilePath ((</>))
14−import Test.Tasty (TestTree, testGroup)
15−import Test.Tasty.HUnit (Assertion, (@=?))
1616import TestSuite.Util
1717
1818
1919--------------------------------------------------------------------------------
20−tests :: TestTree
20+tests :: Test
2121tests = testGroup "Hakyll.Core.Routes.Tests" $ fromAssertions "runRoutes"
2222 [ testRoutes "foo.html" (setExtension "html") "foo"
2323 , testRoutes "foo.html" (setExtension ".html") "foo"
@@ -33,18 +33,12 @@ tests = testGroup "Hakyll.Core.Routes.Tests" $ fromAssertions "runRoutes"
3333 , testRoutes "tags/bar.xml"
3434 (gsubRoute "rss/" (const "") `composeRoutes` setExtension "xml")
3535 "tags/rss/bar"
36−
37− , testRoutes "food/example.md" (metadataRoute $ \md -> customRoute $ \id' ->
38− fromMaybe "?" (lookupString "subblog" md) </> toFilePath id')
39− "example.md"
4036 ]
4137
4238
4339--------------------------------------------------------------------------------
4440testRoutes :: FilePath -> Routes -> Identifier -> Assertion
4541testRoutes expected r id' = do
46− store <- newTestStore
47− provider <- newTestProvider store
48− (route, _) <- runRoutes r provider id'
42+ (route, _) <- runRoutes r
43+ (error "Hakyll.Core.Routes.Tests: no provider") id'
4944 Just expected @=? route
50− cleanTestEnv
tests/Hakyll/Core/Rules/Tests.hs+19−29
@@ -9,22 +9,24 @@ module Hakyll.Core.Rules.Tests
99import Data.IORef (IORef, newIORef, readIORef,
1010 writeIORef)
1111import qualified Data.Set as S
12+import Test.Framework (Test, testGroup)
13+import Test.HUnit (Assertion, assert, (@=?))
14+
15+
16+--------------------------------------------------------------------------------
1217import Hakyll.Core.Compiler
1318import Hakyll.Core.File
1419import Hakyll.Core.Identifier
1520import Hakyll.Core.Identifier.Pattern
16−import Hakyll.Core.Metadata
1721import Hakyll.Core.Routes
1822import Hakyll.Core.Rules
1923import Hakyll.Core.Rules.Internal
20−import System.FilePath ((</>))
21−import Test.Tasty (TestTree, testGroup)
22−import Test.Tasty.HUnit (Assertion, (@=?))
24+import Hakyll.Web.Pandoc
2325import TestSuite.Util
2426
2527
2628--------------------------------------------------------------------------------
27−tests :: TestTree
29+tests :: Test
2830tests = testGroup "Hakyll.Core.Rules.Tests" $ fromAssertions "runRules"
2931 [case01]
3032
@@ -42,29 +44,22 @@ case01 = do
4244 runRoutes routes provider i >>= \(r, _) -> Just ex @=? r
4345
4446 -- Test that we have some identifiers and that the routes work out
45− S.fromList expected @=? identifiers
46− checkRoute "example.html" "example.md"
47− checkRoute "example.md" (sv "raw" "example.md")
48− checkRoute "example.md" (sv "nav" "example.md")
49− checkRoute "example.mv1" (sv "mv1" "example.md")
50− checkRoute "example.mv2" (sv "mv2" "example.md")
51− checkRoute "food/example.md" (sv "metadataMatch" "example.md")
52− readIORef ioref >>= (True @=?)
47+ assert $ all (`S.member` identifiers) expected
48+ checkRoute "example.html" "example.md"
49+ checkRoute "example.md" (sv "raw" "example.md")
50+ checkRoute "example.md" (sv "nav" "example.md")
51+ checkRoute "example.mv1" (sv "mv1" "example.md")
52+ checkRoute "example.mv2" (sv "mv2" "example.md")
53+ readIORef ioref >>= assert
5354 cleanTestEnv
5455 where
5556 sv g = setVersion (Just g)
5657 expected =
57− [ "example.md"
58− , sv "raw" "example.md"
59− , sv "metadataMatch" "example.md"
60− , sv "nav" "example.md"
61− , sv "mv1" "example.md"
62− , sv "mv2" "example.md"
63−
64− , "russian.md"
58+ [ "example.md"
59+ , "russian.md"
60+ , sv "raw" "example.md"
6561 , sv "raw" "russian.md"
66− , sv "mv1" "russian.md"
67− , sv "mv2" "russian.md"
62+ , sv "nav" "example.md"
6863 ]
6964
7065
@@ -74,7 +69,7 @@ rules01 ioref = do
7469 -- Compile some posts
7570 match "*.md" $ do
7671 route $ setExtension "html"
77− compile copyFileCompiler
72+ compile pandocCompiler
7873
7974 -- Yeah. I don't know how else to test this stuff?
8075 preprocess $ writeIORef ioref True
@@ -84,11 +79,6 @@ rules01 ioref = do
8479 route idRoute
8580 compile getResourceString
8681
87− version "metadataMatch" $
88− matchMetadata "*.md" (\md -> lookupString "subblog" md == Just "food") $ do
89− route $ customRoute $ \id' -> "food" </> toFilePath id'
90− compile getResourceString
91−
9282 -- Regression test
9383 version "nav" $ match (fromList ["example.md"]) $ do
9484 route idRoute
tests/Hakyll/Core/Runtime/Tests.hs+6−21
@@ -8,8 +8,8 @@ module Hakyll.Core.Runtime.Tests
88--------------------------------------------------------------------------------
99import qualified Data.ByteString as B
1010import System.FilePath ((</>))
11−import Test.Tasty (TestTree, testGroup)
12−import Test.Tasty.HUnit (Assertion, (@?=))
11+import Test.Framework (Test, testGroup)
12+import Test.HUnit (Assertion, (@?=))
1313
1414
1515--------------------------------------------------------------------------------
@@ -20,7 +20,7 @@ import TestSuite.Util
2020
2121
2222--------------------------------------------------------------------------------
23−tests :: TestTree
23+tests :: Test
2424tests = testGroup "Hakyll.Core.Runtime.Tests" $
2525 fromAssertions "run" [case01, case02]
2626
@@ -28,8 +28,7 @@ tests = testGroup "Hakyll.Core.Runtime.Tests" $
2828--------------------------------------------------------------------------------
2929case01 :: Assertion
3030case01 = do
31− logger <- Logger.new Logger.Error
32− _ <- run testConfiguration logger $ do
31+ _ <- run testConfiguration Logger.Error $ do
3332 match "images/*" $ do
3433 route idRoute
3534 compile copyFileCompiler
@@ -39,16 +38,7 @@ case01 = do
3938 compile $ do
4039 getResourceBody
4140 >>= saveSnapshot "raw"
42− >>= renderParagraphs
43−
44− match (fromList ["partial.html", "partial-helper.html"]) $
45− compile templateCompiler
46− create ["partial.html.out"] $ do
47− route idRoute
48− compile $ do
49− example <- loadSnapshotBody "example.md" "raw"
50− makeItem example
51− >>= loadAndApplyTemplate "partial.html" defaultContext
41+ >>= return . renderPandoc
5242
5343 create ["bodies.txt"] $ do
5444 route idRoute
@@ -69,18 +59,13 @@ case01 = do
6959 bodies <- readFile $ destinationDirectory testConfiguration </> "bodies.txt"
7060 head (lines bodies) @?= "This is an example."
7161
72− partial <- readFile $ providerDirectory testConfiguration </> "partial.html.out"
73− partial' <- readFile $ destinationDirectory testConfiguration </> "partial.html.out"
74− partial @?= partial'
75−
7662 cleanTestEnv
7763
7864
7965--------------------------------------------------------------------------------
8066case02 :: Assertion
8167case02 = do
82− logger <- Logger.new Logger.Error
83− _ <- run testConfiguration logger $ do
68+ _ <- run testConfiguration Logger.Error $ do
8469 match "images/favicon.ico" $ do
8570 route $ gsubRoute "images/" (const "")
8671 compile $ makeItem ("Test" :: String)
tests/Hakyll/Core/Store/Tests.hs+16−16
@@ -6,22 +6,22 @@ module Hakyll.Core.Store.Tests
66
77
88--------------------------------------------------------------------------------
9−import Data.Typeable (typeOf)
10−import qualified Test.QuickCheck as Q
11−import qualified Test.QuickCheck.Monadic as Q
12−import Test.Tasty (TestTree, testGroup)
13−import Test.Tasty.HUnit (testCase)
14−import qualified Test.Tasty.HUnit as H
15−import Test.Tasty.QuickCheck (testProperty)
9+import Data.Typeable (typeOf)
10+import Test.Framework (Test, testGroup)
11+import Test.Framework.Providers.HUnit (testCase)
12+import Test.Framework.Providers.QuickCheck2 (testProperty)
13+import qualified Test.HUnit as H
14+import qualified Test.QuickCheck as Q
15+import qualified Test.QuickCheck.Monadic as Q
1616
1717
1818--------------------------------------------------------------------------------
19−import qualified Hakyll.Core.Store as Store
19+import qualified Hakyll.Core.Store as Store
2020import TestSuite.Util
2121
2222
2323--------------------------------------------------------------------------------
24−tests :: TestTree
24+tests :: Test
2525tests = testGroup "Hakyll.Core.Store.Tests"
2626 [ testProperty "simple get . set" simpleSetGet
2727 , testProperty "persistent get . set" persistentSetGet
@@ -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− 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"
66+ H.assert $ case value of
67+ Store.WrongType e t ->
68+ e == typeOf (undefined :: Int) &&
69+ t == typeOf (undefined :: String)
70+ _ -> False
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− True H.@=? good
82− False H.@=? bad
81+ H.assert good
82+ H.assert (not bad)
8383 cleanTestEnv
tests/Hakyll/Core/UnixFilter/Tests.hs+13−23
@@ -6,38 +6,36 @@ module Hakyll.Core.UnixFilter.Tests
66
77
88--------------------------------------------------------------------------------
9−import Test.Tasty (TestTree, testGroup)
10−import Test.Tasty.HUnit (testCase)
11−import qualified Test.Tasty.HUnit as H
9+import Data.List (isInfixOf)
10+import Test.Framework (Test, testGroup)
11+import Test.Framework.Providers.HUnit (testCase)
12+import qualified Test.HUnit as H
1213
1314
1415--------------------------------------------------------------------------------
1516import Hakyll.Core.Compiler
16−import Hakyll.Core.Identifier
17+import Hakyll.Core.Compiler.Internal
1718import Hakyll.Core.Item
1819import Hakyll.Core.UnixFilter
1920import TestSuite.Util
2021
2122
2223--------------------------------------------------------------------------------
23−tests :: TestTree
24+tests :: Test
2425tests = testGroup "Hakyll.Core.UnixFilter.Tests"
2526 [ testCase "unixFilter rev" unixFilterRev
2627 , testCase "unixFilter false" unixFilterFalse
27− , testCase "unixFilter error" unixFilterError
2828 ]
2929
30−testMarkdown :: Identifier
31−testMarkdown = "russian.md"
3230
3331--------------------------------------------------------------------------------
3432unixFilterRev :: H.Assertion
3533unixFilterRev = do
3634 store <- newTestStore
3735 provider <- newTestProvider store
38− output <- testCompilerDone store provider testMarkdown compiler
39− expected <- testCompilerDone store provider testMarkdown getResourceString
40− rev (itemBody expected) H.@=? lines (itemBody output)
36+ output <- testCompilerDone store provider "russian.md" compiler
37+ expected <- testCompilerDone store provider "russian.md" getResourceString
38+ H.assert $ rev (itemBody expected) == lines (itemBody output)
4139 cleanTestEnv
4240 where
4341 compiler = getResourceString >>= withItemBody (unixFilter "rev" [])
@@ -49,18 +47,10 @@ unixFilterFalse :: H.Assertion
4947unixFilterFalse = do
5048 store <- newTestStore
5149 provider <- newTestProvider store
52− testCompilerError store provider testMarkdown compiler "exit code"
50+ result <- testCompiler store provider "russian.md" compiler
51+ H.assert $ case result of
52+ CompilerError es -> any ("exit code" `isInfixOf`) es
53+ _ -> False
5354 cleanTestEnv
5455 where
5556 compiler = getResourceString >>= withItemBody (unixFilter "false" [])
56−
57−
58−--------------------------------------------------------------------------------
59−unixFilterError :: H.Assertion
60−unixFilterError = do
61− store <- newTestStore
62− provider <- newTestProvider store
63− testCompilerError store provider testMarkdown compiler "option"
64− cleanTestEnv
65− where
66− compiler = getResourceString >>= withItemBody (unixFilter "head" ["-#"])
tests/Hakyll/Core/Util/String/Tests.hs+3−3
@@ -5,8 +5,8 @@ module Hakyll.Core.Util.String.Tests
55
66
77--------------------------------------------------------------------------------
8−import Test.Tasty (TestTree, testGroup)
9−import Test.Tasty.HUnit ((@=?))
8+import Test.Framework (Test, testGroup)
9+import Test.HUnit ((@=?))
1010
1111
1212--------------------------------------------------------------------------------
@@ -15,7 +15,7 @@ import TestSuite.Util
1515
1616
1717--------------------------------------------------------------------------------
18−tests :: TestTree
18+tests :: Test
1919tests = testGroup "Hakyll.Core.Util.String.Tests" $ concat
2020 [ fromAssertions "trim"
2121 [ "foo" @=? trim " foo\n\t "
tests/Hakyll/Web/CompressCss/Tests.hsdeleted−64
@@ -1,64 +0,0 @@
1−--------------------------------------------------------------------------------
2−module Hakyll.Web.CompressCss.Tests
3− ( tests
4− ) where
5−
6−
7−--------------------------------------------------------------------------------
8−import Test.Tasty (TestTree, testGroup)
9−import Test.Tasty.HUnit ((@=?))
10−
11−
12−--------------------------------------------------------------------------------
13−import Hakyll.Web.CompressCss
14−import TestSuite.Util
15−
16−
17−--------------------------------------------------------------------------------
18−tests :: TestTree
19−tests = testGroup "Hakyll.Web.CompressCss.Tests" $ concat
20− [ fromAssertions "compressCss"
21− [
22− -- compress whitespace
23− "something something" @=?
24− compressCss " something \n\t\r something "
25− -- do not compress whitespace in string tokens
26− , "abc \" \t\n\r \" xyz" @=?
27− compressCss "abc \" \t\n\r \" xyz"
28− , "abc ' \t\n\r ' xyz" @=?
29− compressCss "abc ' \t\n\r ' xyz"
30−
31− -- strip comments
32− , "before after" @=? compressCss "before /* abc { } ;; \n\t\r */ after"
33− -- don't strip comments inside string tokens
34− , "before \"/* abc { } ;; \n\t\r */\" after"
35− @=? compressCss "before \"/* abc { } ;; \n\t\r */\" after"
36−
37− -- compress separators
38− , "}" @=? compressCss "; }"
39− , ";{};" @=? compressCss " ; { } ; "
40− , "text," @=? compressCss "text , "
41− , "a>b" @=? compressCss "a > b"
42− , "a+b" @=? compressCss "a + b"
43− , "a!b" @=? compressCss "a ! b"
44− -- compress calc()
45− , "calc(1px + 100%/(5 + 3) - (3px + 2px)*5)" @=? compressCss "calc( 1px + 100% / ( 5 + 3) - calc( 3px + 2px ) * 5 )"
46− -- compress whitespace even after this curly brace
47− , "}" @=? compressCss "; } "
48− -- but do not compress separators inside string tokens
49− , "\" { } ; , \"" @=? compressCss "\" { } ; , \""
50− -- don't compress separators at the start or end of string tokens
51− , "\" }\"" @=? compressCss "\" }\""
52− , "\"{ \"" @=? compressCss "\"{ \""
53− -- don't get irritated by the wrong token delimiter
54− , "\" ' \"" @=? compressCss "\" ' \""
55− , "' \" '" @=? compressCss "' \" '"
56− -- don't compress whitespace in the middle of a string
57− , "abc '{ '" @=? compressCss "abc '{ '"
58− , "abc \"{ \"" @=? compressCss "abc \"{ \""
59− -- compress whitespace after colons (but not before)
60− , "abc :xyz" @=? compressCss "abc : xyz"
61− -- compress multiple semicolons
62− , ";" @=? compressCss ";;;;;;;"
63− ]
64− ]
tests/Hakyll/Web/Html/RelativizeUrls/Tests.hs+3−7
@@ -6,9 +6,8 @@ module Hakyll.Web.Html.RelativizeUrls.Tests
66
77
88--------------------------------------------------------------------------------
9−import Test.Tasty (TestTree, testGroup)
10−import Test.Tasty.HUnit ((@=?))
11−
9+import Test.Framework (Test, testGroup)
10+import Test.HUnit ((@=?))
1211
1312--------------------------------------------------------------------------------
1413import Hakyll.Web.Html.RelativizeUrls
@@ -16,16 +15,13 @@ import TestSuite.Util
1615
1716
1817--------------------------------------------------------------------------------
19−tests :: TestTree
18+tests :: Test
2019tests = testGroup "Hakyll.Web.Html.RelativizeUrls.Tests" $
2120 fromAssertions "relativizeUrls"
2221 [ "<a href=\"../foo\">bar</a>" @=?
2322 relativizeUrlsWith ".." "<a href=\"/foo\">bar</a>"
2423 , "<img src=\"../../images/lolcat.png\" />" @=?
2524 relativizeUrlsWith "../.." "<img src=\"/images/lolcat.png\" />"
26− , "<video poster=\"../../images/lolcat.png\"></video>" @=?
27− relativizeUrlsWith "../.."
28− "<video poster=\"/images/lolcat.png\"></video>"
2925 , "<a href=\"http://haskell.org\">Haskell</a>" @=?
3026 relativizeUrlsWith "../.."
3127 "<a href=\"http://haskell.org\">Haskell</a>"
tests/Hakyll/Web/Html/Tests.hs+9−11
@@ -5,9 +5,9 @@ module Hakyll.Web.Html.Tests
55
66
77--------------------------------------------------------------------------------
8−import Data.Char (toUpper)
9−import Test.Tasty (TestTree, testGroup)
10−import Test.Tasty.HUnit ((@=?))
8+import Data.Char (toUpper)
9+import Test.Framework (Test, testGroup)
10+import Test.HUnit (assert, (@=?))
1111
1212
1313--------------------------------------------------------------------------------
@@ -16,7 +16,7 @@ import TestSuite.Util
1616
1717
1818--------------------------------------------------------------------------------
19−tests :: TestTree
19+tests :: Test
2020tests = testGroup "Hakyll.Web.Html.Tests" $ concat
2121 [ fromAssertions "demoteHeaders"
2222 [ "<h2>A h1 title</h2>" @=?
@@ -59,16 +59,14 @@ tests = testGroup "Hakyll.Web.Html.Tests" $ concat
5959 , "." @=? toSiteRoot "index.html"
6060 , "." @=? toSiteRoot "/index.html"
6161 , "../.." @=? toSiteRoot "foo/bar/qux"
62− , ".." @=? toSiteRoot "./foo/bar.html"
63− , ".." @=? toSiteRoot "/foo/./bar.html"
6462 ]
6563
6664 , fromAssertions "isExternal"
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"
65+ [ assert (isExternal "http://reddit.com")
66+ , assert (isExternal "https://mail.google.com")
67+ , assert (isExternal "//ajax.googleapis.com")
68+ , assert (not (isExternal "../header.png"))
69+ , assert (not (isExternal "/foo/index.html"))
7270 ]
7371
7472 , fromAssertions "stripTags"
tests/Hakyll/Web/Pandoc/FileType/Tests.hs+3−3
@@ -6,8 +6,8 @@ module Hakyll.Web.Pandoc.FileType.Tests
66
77
88--------------------------------------------------------------------------------
9−import Test.Tasty (TestTree, testGroup)
10−import Test.Tasty.HUnit ((@=?))
9+import Test.Framework (Test, testGroup)
10+import Test.HUnit ((@=?))
1111
1212
1313--------------------------------------------------------------------------------
@@ -16,7 +16,7 @@ import TestSuite.Util
1616
1717
1818--------------------------------------------------------------------------------
19−tests :: TestTree
19+tests :: Test
2020tests = testGroup "Hakyll.Web.Pandoc.FileType.Tests" $
2121 fromAssertions "fileType"
2222 [ Markdown @=? fileType "index.md"
tests/Hakyll/Web/Template/Context/Tests.hs+9−45
@@ -6,26 +6,24 @@ module Hakyll.Web.Template.Context.Tests
66
77
88--------------------------------------------------------------------------------
9−import Test.Tasty (TestTree, testGroup)
10−import Test.Tasty.HUnit (Assertion, testCase, (@=?))
9+import Test.Framework (Test, testGroup)
10+import Test.Framework.Providers.HUnit (testCase)
11+import Test.HUnit (Assertion, (@=?))
1112
1213
1314--------------------------------------------------------------------------------
1415import Hakyll.Core.Compiler
1516import Hakyll.Core.Identifier
16−import Hakyll.Core.Item
1717import Hakyll.Core.Provider
18−import Hakyll.Core.Store (Store)
18+import Hakyll.Core.Store (Store)
1919import Hakyll.Web.Template.Context
20−import Hakyll.Web.Template.Internal
2120import TestSuite.Util
2221
2322
2423--------------------------------------------------------------------------------
25−tests :: TestTree
26−tests = testGroup "Hakyll.Web.Template.Context.Tests"
24+tests :: Test
25+tests = testGroup "Hakyll.Core.Template.Context.Tests"
2726 [ testCase "testDateField" testDateField
28− , testCase "testOuerLoopContextAccess" testOuerLoopContextAccess
2927 ]
3028
3129
@@ -44,15 +42,6 @@ testDateField = do
4442 dateField "date" "%B %e, %Y"
4543 date2 @=? "August 26, 2010"
4644
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"
5645 cleanTestEnv
5746
5847
@@ -62,34 +51,9 @@ testContextDone :: Store -> Provider -> Identifier -> String
6251testContextDone store provider identifier key context =
6352 testCompilerDone store provider identifier $ do
6453 item <- getResourceBody
65− cf <- unContext context key [] item
54+ cf <- unContext context key item
6655 case cf of
6756 StringField str -> return str
68− _ -> error $
57+ ListField _ _ -> error $
6958 "Hakyll.Web.Template.Context.Tests.testContextDone: " ++
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'
59+ "Didn't expect ListField"
tests/Hakyll/Web/Template/Tests.hs+20−97
@@ -1,23 +1,22 @@
11--------------------------------------------------------------------------------
22{-# LANGUAGE OverloadedStrings #-}
3−{-# LANGUAGE TemplateHaskell #-}
43module Hakyll.Web.Template.Tests
54 ( tests
65 ) where
76
87
98--------------------------------------------------------------------------------
10−import Test.Tasty (TestTree, testGroup)
11−import Test.Tasty.HUnit (Assertion, assertBool, testCase,
12− (@=?), (@?=))
9+import Data.Monoid (mconcat)
10+import Test.Framework (Test, testGroup)
11+import Test.Framework.Providers.HUnit (testCase)
12+import Test.HUnit (Assertion, (@=?), (@?=))
1313
14−import Data.Either (isLeft)
1514
1615--------------------------------------------------------------------------------
1716import Hakyll.Core.Compiler
18−import Hakyll.Core.Identifier
1917import Hakyll.Core.Item
2018import Hakyll.Core.Provider
19+import Hakyll.Web.Pandoc
2120import Hakyll.Web.Template
2221import Hakyll.Web.Template.Context
2322import Hakyll.Web.Template.Internal
@@ -26,83 +25,26 @@ import TestSuite.Util
2625
2726
2827--------------------------------------------------------------------------------
29−tests :: TestTree
30−tests = testGroup "Hakyll.Web.Template.Tests" $ concat
31− [ [ testCase "case01" $ test ("template.html.out", "template.html", "example.md")
32− , testCase "case02" $ test ("strip.html.out", "strip.html", "example.md")
33− , testCase "case03" $ test ("just-meta.html.out", "just-meta.html", "example.md")
34− , testCase "applyJoinTemplateList" testApplyJoinTemplateList
35− ]
36−
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$"
42− -- 'If' trim check.
43− , Right [ TrimL
44− , If (Ident (TemplateKey "body"))
45− [ TrimR
46− , Chunk "\n"
47− , Expr (Ident (TemplateKey "body"))
48− , Chunk "\n"
49− , TrimL
50− ]
51− (Just [ TrimR
52− , Chunk "\n"
53− , Expr (Ident (TemplateKey "body"))
54− , Chunk "\n"
55− , TrimL
56− ])
57− , TrimR
58− ]
59− @=? parse "$-if(body)-$\n$body$\n$-else-$\n$body$\n$-endif-$"
60− -- 'For' trim check.
61− , Right [ TrimL
62− , For (Ident (TemplateKey "authors"))
63− [TrimR, Chunk "\n body \n", TrimL]
64− Nothing
65− , TrimR
66− ]
67− @=? parse "$-for(authors)-$\n body \n$-endfor-$"
68− -- 'Partial' trim check.
69− , Right [ TrimL
70− , Partial (StringLiteral "path")
71− , TrimR
72− ]
73− @=? parse "$-partial(\"path\")-$"
74− -- 'Expr' trim check.
75− , Right [ TrimL
76− , Expr (Ident (TemplateKey "foo"))
77− , TrimR
78− ]
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$"
86− ]
87−
88− , [testCase "embeddedTemplate" testEmbeddedTemplate]
28+tests :: Test
29+tests = testGroup "Hakyll.Core.Template.Tests"
30+ [ testCase "case01" case01
31+ , testCase "applyJoinTemplateList" testApplyJoinTemplateList
8932 ]
90− where
91− parse = parseTemplateElemsFile ""
9233
9334
9435--------------------------------------------------------------------------------
95−test :: (Identifier, Identifier, Identifier) -> Assertion
96−test (outf, tplf, itemf) = do
36+case01 :: Assertion
37+case01 = do
9738 store <- newTestStore
9839 provider <- newTestProvider store
9940
100− out <- resourceString provider outf
101− tpl <- testCompilerDone store provider tplf templateBodyCompiler
102− item <- testCompilerDone store provider itemf $
103− getResourceBody
104− >>= renderParagraphs
105− >>= applyTemplate (itemBody tpl) testContext
41+ out <- resourceString provider "template.html.out"
42+ tpl <- testCompilerDone store provider "template.html" $
43+ templateCompiler
44+ item <- testCompilerDone store provider "example.md" $
45+ pandocCompiler >>= applyTemplate (itemBody tpl) testContext
46+
47+ writeFile "foo" (itemBody item)
10648
10749 out @=? itemBody item
10850 cleanTestEnv
@@ -118,6 +60,7 @@ testContext = mconcat
11860 return [n1, n2]
11961 , functionField "rev" $ \args _ -> return $ unwords $ map reverse args
12062 ]
63+ where
12164
12265
12366--------------------------------------------------------------------------------
@@ -125,8 +68,6 @@ testApplyJoinTemplateList :: Assertion
12568testApplyJoinTemplateList = do
12669 store <- newTestStore
12770 provider <- newTestProvider store
128− tpl <- testCompilerDone store provider "tpl" $
129− compileTemplateItem (Item "tpl" "<b>$body$</b>")
13071 str <- testCompilerDone store provider "item3" $
13172 applyJoinTemplateList ", " tpl defaultContext [i1, i2]
13273
@@ -135,22 +76,4 @@ testApplyJoinTemplateList = do
13576 where
13677 i1 = Item "item1" "Hello"
13778 i2 = Item "item2" "World"
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−
79+ tpl = Template [Chunk "<b>", Key "body", Chunk "</b>"]
tests/TestSuite.hs+2−11
@@ -1,12 +1,11 @@
11--------------------------------------------------------------------------------
2−{-# LANGUAGE CPP #-}
32module Main
43 ( main
54 ) where
65
76
87--------------------------------------------------------------------------------
9−import Test.Tasty (defaultMain, testGroup)
8+import Test.Framework (defaultMain)
109
1110
1211--------------------------------------------------------------------------------
@@ -20,20 +19,16 @@ import qualified Hakyll.Core.Runtime.Tests
2019import qualified Hakyll.Core.Store.Tests
2120import qualified Hakyll.Core.UnixFilter.Tests
2221import qualified Hakyll.Core.Util.String.Tests
23−import qualified Hakyll.Web.CompressCss.Tests
2422import qualified Hakyll.Web.Html.RelativizeUrls.Tests
2523import qualified Hakyll.Web.Html.Tests
26−#ifdef USE_PANDOC
2724import qualified Hakyll.Web.Pandoc.FileType.Tests
28−#endif
2925import qualified Hakyll.Web.Template.Context.Tests
3026import qualified Hakyll.Web.Template.Tests
31−import qualified Hakyll.Web.Tags.Tests
3227
3328
3429--------------------------------------------------------------------------------
3530main :: IO ()
36−main = defaultMain $ testGroup "Hakyll"
31+main = defaultMain
3732 [ Hakyll.Core.Dependencies.Tests.tests
3833 , Hakyll.Core.Identifier.Tests.tests
3934 , Hakyll.Core.Provider.Metadata.Tests.tests
@@ -44,13 +39,9 @@ main = defaultMain $ testGroup "Hakyll"
4439 , Hakyll.Core.Store.Tests.tests
4540 , Hakyll.Core.UnixFilter.Tests.tests
4641 , Hakyll.Core.Util.String.Tests.tests
47− , Hakyll.Web.CompressCss.Tests.tests
4842 , Hakyll.Web.Html.RelativizeUrls.Tests.tests
4943 , Hakyll.Web.Html.Tests.tests
50−#ifdef USE_PANDOC
5144 , Hakyll.Web.Pandoc.FileType.Tests.tests
52−#endif
53− , Hakyll.Web.Tags.Tests.tests
5445 , Hakyll.Web.Template.Context.Tests.tests
5546 , Hakyll.Web.Template.Tests.tests
5647 ]
tests/TestSuite/Util.hs+15−36
@@ -6,38 +6,36 @@ module TestSuite.Util
66 , newTestProvider
77 , testCompiler
88 , testCompilerDone
9− , testCompilerError
109 , testConfiguration
1110 , cleanTestEnv
12− , renderParagraphs
1311 ) where
1412
1513
1614--------------------------------------------------------------------------------
17−import Data.List (intercalate, isInfixOf)
18−import Data.Monoid (mempty)
19−import qualified Data.Set as S
20−import Test.Tasty
21−import Test.Tasty.HUnit
22−import Text.Printf (printf)
15+import Data.List (intercalate)
16+import Data.Monoid (mempty)
17+import qualified Data.Set as S
18+import Test.Framework
19+import Test.Framework.Providers.HUnit
20+import Test.HUnit hiding (Test)
21+import Text.Printf (printf)
2322
2423
2524--------------------------------------------------------------------------------
2625import Hakyll.Core.Compiler.Internal
2726import Hakyll.Core.Configuration
2827import Hakyll.Core.Identifier
29−import qualified Hakyll.Core.Logger as Logger
28+import qualified Hakyll.Core.Logger as Logger
3029import Hakyll.Core.Provider
31−import Hakyll.Core.Store (Store)
32−import qualified Hakyll.Core.Store as Store
30+import Hakyll.Core.Store (Store)
31+import qualified Hakyll.Core.Store as Store
3332import Hakyll.Core.Util.File
34−import Hakyll.Core.Item
3533
3634
3735--------------------------------------------------------------------------------
3836fromAssertions :: String -- ^ Name
3937 -> [Assertion] -- ^ Cases
40− -> [TestTree] -- ^ Result tests
38+ -> [Test] -- ^ Result tests
4139fromAssertions name =
4240 zipWith testCase [printf "[%2d] %s" n name | n <- [1 :: Int ..]]
4341
@@ -79,23 +77,14 @@ testCompilerDone store provider underlying compiler = do
7977 result <- testCompiler store provider underlying compiler
8078 case result of
8179 CompilerDone x _ -> return x
82− CompilerError e -> fail $
80+ CompilerError e -> error $
8381 "TestSuite.Util.testCompilerDone: compiler " ++ show underlying ++
84− " threw: " ++ intercalate "; " (compilerErrorMessages e)
85− CompilerRequire i _ -> fail $
82+ " threw: " ++ intercalate "; " e
83+ CompilerRequire i _ -> error $
8684 "TestSuite.Util.testCompilerDone: compiler " ++ show underlying ++
8785 " requires: " ++ show i
88− CompilerSnapshot _ _ -> fail
89− "TestSuite.Util.testCompilerDone: unexpected CompilerSnapshot"
9086
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"
87+
9988
10089--------------------------------------------------------------------------------
10190testConfiguration :: Configuration
@@ -113,13 +102,3 @@ cleanTestEnv = do
113102 removeDirectory $ destinationDirectory testConfiguration
114103 removeDirectory $ storeDirectory testConfiguration
115104 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.htmldeleted−1
@@ -1 +0,0 @@
1−<p>$body$</p>
tests/data/example.md.metadata−3
@@ -1,5 +1,2 @@
11external: External data
22date: 2012-10-22 14:35:24
3−subblog: food
4−intfield: 42
5−numfield: 3.14
tests/data/just-meta.htmldeleted−7
@@ -1,7 +0,0 @@
1−<pre>
2−external: {$external$}
3−date: {$date$}
4−subblog: {$subblog$}
5−intfield: {$intfield$}
6−numfield: {$numfield$}
7−</pre>
tests/data/just-meta.html.outdeleted−7
@@ -1,7 +0,0 @@
1−<pre>
2−external: {External data}
3−date: {2012-10-22 14:35:24}
4−subblog: {food}
5−intfield: {42}
6−numfield: {3.14}
7−</pre>
tests/data/partial-helper.htmldeleted−3
@@ -1,3 +0,0 @@
1−<p>This is an included partial.</p>
2−
3−$body$
tests/data/partial.htmldeleted−3
@@ -1,3 +0,0 @@
1−<p>This is a file that includes a partial.</p>
2−
3−$partial("partial-helper.html")$
tests/data/partial.html.outdeleted−7
@@ -1,7 +0,0 @@
1−<p>This is a file that includes a partial.</p>
2−
3−<p>This is an included partial.</p>
4−
5−This is an example.
6−
7−
tests/data/posts/2018-09-26.mddeleted−1
@@ -1 +0,0 @@
1−Something happened today.
tests/data/posts/2019/05/10/tomorrow.mddeleted−1
@@ -1 +0,0 @@
1−This day hasn't happened yet (as of writing this).
tests/data/strip.htmldeleted−34
@@ -1,34 +0,0 @@
1−<div>
2− I'm so rich I have $$3.
3−
4− $rev("foo")$
5− $-rev(rev("foo"))$
6−
7− $if(body)-$
8− I have body
9− $else-$
10− or no
11− $-endif-$
12−
13− $if(unbound)$
14− should not be printed
15− $endif$
16−
17− $-if(body)-$
18− should be printed
19− $-endif$
20−
21− <ul>
22− $for(authors)-$
23− <li>$name$</li>
24− $endfor-$
25− </ul>
26−
27− $for(authors)-$
28− $name-$
29− $sep$,
30− $-endfor$
31−
32− $body$
33−</div>
34−
tests/data/strip.html.outdeleted−18
@@ -1,18 +0,0 @@
1−<div>
2− I'm so rich I have $3.
3−
4− ooffoo
5−
6− I have body
7− should be printed
8−
9− <ul>
10− <li>Jan</li>
11− <li>Piet</li>
12− </ul>
13−
14− Jan,Piet
15−
16− <p>This is an example.</p>
17−</div>
18−
tests/data/template.html+1−2
@@ -1,8 +1,7 @@
11<div>
22 I'm so rich I have $$3.
33
4− $rev("foo")$
5− $rev(rev("foo"))$
4+ $rev foo$
65
76 $if(body)$
87 I have body
tests/data/template.html.out−1
@@ -2,7 +2,6 @@
22 I'm so rich I have $3.
33
44 oof
5− foo
65
76
87 I have body
web/css/syntax.css+18−61
@@ -1,61 +1,18 @@
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 */
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+pre.sourceCode span.kw { color: #007020; font-weight: bold; }
7+pre.sourceCode span.dt { color: #902000; }
8+pre.sourceCode span.dv { color: #40a070; }
9+pre.sourceCode span.bn { color: #40a070; }
10+pre.sourceCode span.fl { color: #40a070; }
11+pre.sourceCode span.ch { color: #4070a0; }
12+pre.sourceCode span.st { color: #4070a0; }
13+pre.sourceCode span.co { color: #60a0b0; font-style: italic; }
14+pre.sourceCode span.ot { color: #007020; }
15+pre.sourceCode span.al { color: red; font-weight: bold; }
16+pre.sourceCode span.fu { color: #06287e; }
17+pre.sourceCode span.re { }
18+pre.sourceCode span.er { color: red; font-weight: bold; }
web/examples.markdown+17−128
@@ -9,19 +9,12 @@ interesting resource to learn from as well. If you're using Hakyll for your
99site, and the source code is available, please notify me so I can add you to
1010this list. This list has no particular ordering.
1111
12−Note that there is also a [Hakyll CSS garden] which has some themes you can use
13−directly with the default Hakyll site.
14−
15−[Hakyll CSS garden]: http://katychuang.com/hakyll-cssgarden/gallery/
16−
1712## Hakyll 4
1813
1914- <http://jaspervdj.be/>,
2015 [source](https://github.com/jaspervdj/jaspervdj)
2116- <http://www.imagination-land.org/>,
2217 [source](https://github.com/Keruspe/blog/)
23−- <https://bananasandlenses.net/>,
24− [source](https://github.com/denisshevchenko/bananasandlenses)
2518- <http://blog.clement.delafargue.name/>,
2619 [source](https://github.com/divarvel/blog)
2720- <http://bneijt.nl>,
@@ -36,6 +29,8 @@ directly with the default Hakyll site.
3629 [source](https://github.com/dannysu/hakyll-blog)
3730- <http://meta.plasm.us/>,
3831 [source](https://github.com/travisbrown/metaplasm)
32+- <http://www.web2day-nantes.org/>,
33+ [source](https://github.com/CompanyCampus/web2day2013)
3934- <http://workforpizza.com/>,
4035 [source](https://github.com/irneh/workforpizza)
4136- <http://poleiro.info/>,
@@ -45,7 +40,9 @@ directly with the default Hakyll site.
4540- <http://www.regisfoucault.com/>,
4641 [source](https://github.com/regisfoucault/blog)
4742- <http://www.chaoxuprime.com/>,
48− [source](https://github.com/chaoxu/chaoxu.github.io/tree/develop)
43+ [source](https://github.com/Mgccl/blog)
44+- <https://benjeffrey.com/>,
45+ [source](https://github.com/jeffbr13/benjeffrey.com)
4946- <http://www.skybluetrades.net/>,
5047 [source](https://github.com/ian-ross/blog)
5148- <http://chromaticleaves.com/>,
@@ -56,134 +53,18 @@ directly with the default Hakyll site.
5653 [source](https://github.com/blaenk/blaenk.github.io)
5754- <https://xinitrc.de/>,
5855 [source](https://github.com/xinitrc/xinitrc.de)
56+- <http://darkfox.us.to/>,
57+ [source](http://hub.darcs.net/DarkFox/DarkFox-blog)
5958- <http://nickcharlton.net/>,
6059 [source](https://github.com/nickcharlton/nickcharlton.net)
6160- <http://scr.stunts.hu/>,
6261 [literate source](http://scr.stunts.hu/hakyll.html)
6362- <http://www.eanalytica.com/>,
6463 [literate source](http://www.eanalytica.com/site/)
64+- <http://dikmax.name/>,
65+ [source](https://github.com/dikmax/dikmax.name)
6566- <http://cse.iitk.ac.in/users/ppk>,
6667 [source](https://github.com/piyush-kurur-pages/website)
67−- <http://web.engr.oregonstate.edu/~walkiner/>,
68− [source](https://github.com/walkie/WebPage)
69−- <http://techblog.rosedu.org/>,
70− [source](https://github.com/rosedu/techblog)
71−- <http://freizl.github.io>,
72− [source](https://github.com/freizl/freizl.github.com/tree/master/build)
73−- <http://www.gwern.net/>,
74− [source](https://github.com/gwern/gwern.net)
75−- <http://www.corentindupont.info/>,
76− [source](https://github.com/cdupont/CorentinDupont-WebPage)
77−- <http://jelv.is>,
78− [source](https://github.com/TikhonJelvis/website)
79−- <https://secure.plaimi.net/>,
80− [source](https://github.com/plaimi/www)
81−- <http://duplode.github.io/>,
82− [source](https://github.com/duplode/duplode.github.io/tree/sources)
83−- <http://tzemanovic.github.io/>,
84− [source](https://github.com/tzemanovic/tzemanovic)
85−- <http://listx.github.io>,
86− [source](https://github.com/listx/listx_blog)
87−- <http://kyle.marek-spartz.org>,
88− [source](https://github.com/zeckalpha/kyle.marek-spartz.org)
89−- <http://ladatura-corsets.com/>,
90− [source](https://github.com/dsferruzza/datura-corsets)
91−- <http://reichertbrothers.com/>,
92− [source](https://github.com/rbros/reichertbrothers.com)
93−- <http://alemedeiros.sdf.org>,
94− [source](https://github.com/alemedeiros/homepage)
95−- <http://www.haskell.mn>,
96− [source](https://github.com/HaskellMN/www.haskell.mn)
97−- <https://axiomatic.neophilus.net/>,
98− [source](https://github.com/Libbum/AxiomaticSemantics)
99−- [https://tesser.org/](http://web.archive.org/web/20161022221713/https://tesser.org/),
100− [source](https://github.com/fractalcat/tesser.org)
101−- <https://weien.io/>,
102−  [source](https://github.com/wei2912/blog-src)
103−- <http://www.metasepi.org/>,
104− [source](https://github.com/metasepi/metasepi-website)
105−- <http://gisli.hamstur.is/>,
106− [source](https://github.com/gislik/gisli.hamstur.is)
107−- <http://blog.ssanj.net/>,
108− [source](https://github.com/ssanj/babyloncandle)
109−- <http://ruhaskell.org/>,
110− [source](https://github.com/ruHaskell/ruhaskell)
111−- <http://www.haskell.od.ua/>,
112− [source](https://github.com/odhug/odhug.github.com)
113−- <http://blog.rekahsoft.ca/>,
114− [source](http://git.rekahsoft.ca/blog-rekahsoft-ca)
115−- <http://www.andreascarpino.it>,
116− [source](https://gitlab.com/ilpianista/website)
117−- <http://cascadeofinsights.com/>,
118− [source](https://github.com/agbell/agbell.github.io/tree/hakyll)
119−- <http://www.xoltar.org/>,
120− [source](https://github.com/xoltar/xoltar.org)
121−- <http://sigkill.dk/>
122− [literate source](http://sigkill.dk/programs/sigkill.html)
123−- <http://curry-club-augsburg.de/>,
124− [source](https://github.com/curry-club-aux/curry-club-augsburg.de)
125−- <http://ismailmustafa.com/>,
126− [source](https://github.com/ismailmustafa/ismailmustafa.github.io/tree/hakyll)
127−- <http://lettier.github.io/>,
128− [source](https://github.com/lettier/lettier.github.io)
129−- <http://raindev.io>,
130− [source](https://github.com/raindev/raindev.github.io)
131−- <http://hal2016.haskell.org/>,
132− [source](https://github.com/nomeata/hal2016-website/)
133−- <http://acehack.org/>,
134− [source](https://github.com/sakshamsharma/acehack/)
135−- <https://nek0.eu/>,
136− [source](https://github.com/nek0/nek0.eu)
137−- <http://static-dust.klpn.se/>,
138− [source](https://github.com/klpn/static-dust)
139−- <http://www.kuznero.com/>,
140− [source](https://github.com/kuznero/kuznero.github.io)
141−- <http://nicolerauch.de/>,
142− [source](https://github.com/NicoleRauch/BusinessWebsite)
143−- <http://johnduhamel.io/>,
144− [source](https://github.com/jjduhamel/blog)
145−- <http://alexanderlobov.ru/>,
146− [source](https://github.com/alexanderlobov/homepage)
147−- <http://www.rosanbo.com/>,
148− [source](https://github.com/polo2ro/blog)
149−- <http://limansky.me/>,
150− [source](https://github.com/limansky/limansky_me)
151−- <http://rahul.nu/>,
152− [source](https://github.com/rp/rp.github.io/tree/dev)
153−- <http://haskanything.com/>,
154− [source](https://github.com/beerendlauwers/HaskAnything)
155−- <https://anler.me/>,
156− [source](https://github.com/anler/anler.me)
157−- <http://argumatronic.com/>,
158− [source](https://gitlab.com/GinBaby/argumatronic/tree/master)
159−- <http://sillybytes.net/>,
160− [source](https://github.com/sillybytes/sillybytes)
161−- <https://falconpilot.github.io/>,
162− [source](https://github.com/FalconPilot/falconpilot.github.io)
163−- <http://spyhop.cafe/>,
164− [source](https://github.com/thunky-monk/spyhop-cafe)
165−- <https://bendo.github.io/>,
166− [source](https://github.com/bendo/bendo.github.io)
167−- <http://adarsh.cc/>,
168− [source](https://github.com/adarshp/adarsh.cc)
169−- <http://0a.io>,
170− [source](https://github.com/arrchyy/0a.io)
171−- <https://nasy.moe/>,
172− [source](https://github.com/nasyxx/NasyMoe)
173−- <http://uniprocess.org/>,
174− [source](https://gitlab.com/uniprocess/uniprocess.org)
175−- <https://regularflolloping.com/>,
176− [source](https://github.com/techieAgnostic/rf)
177−- <http://enter-haken.github.io/>,
178− [source](https://github.com/enter-haken/blog)
179−- <https://modular.blind.guru/>,
180− [source](https://github.com/mlang/eurorack-metadata)
181−- <http://www.jonashietala.se/>,
182− [source](https://github.com/treeman/jonashietala)
183−- <https://abhinavsarkar.net/>,
184− [source](https://github.com/abhin4v/abhin4v.github.io)
185−- <https://dailyreadingnotes.com/>,
186− [source](https://github.com/hackeryarn/daily-reading-notes)
18768
18869## Hakyll 3.X
18970
@@ -199,10 +80,14 @@ directly with the default Hakyll site.
19980 [source](https://github.com/wunki/wunki.org)
20081- <http://rs.io>,
20182 [source](https://github.com/robertseaton/rs.io/)
83+- <http://www.gwern.net/>,
84+ source [on Patch-tag](https://patch-tag.com/r/gwern/Gwern/home)
20285- <http://blog.coldflake.com/>,
20386 [source](https://github.com/marcmo/blog.coldflake)
20487- <http://deepak.jois.name>,
20588 [source](https://github.com/deepakjois/website)
89+- <http://sigkill.dk/>,
90+ [literate source](http://sigkill.dk/programs/sigkill.html)
20691- <http://orftz.com>,
20792 [source](https://github.com/orftz/orftz.com)
20893- <http://citationneeded.me/>,
@@ -211,5 +96,9 @@ directly with the default Hakyll site.
21196 [source](http://gitorious.org/shakthimaan-blog)
21297- <http://www.alfredodinapoli.com/>,
21398 [source](https://github.com/CharlesStain/alfredodinapoli.com)
99+- <http://www.rohanjain.in/>,
100+ [source](https://github.com/crodjer/rohanjain.in)
101+- <http://datahackermd.com>,
102+ [source](http://github.com/akshayjshah/datahackermd)
214103- <http://michaelxavier.net>,
215104 [source](https://github.com/michaelxavier/michaelxavier.net)
web/index.markdown+2−4
@@ -31,10 +31,8 @@ and TeX support, including syntax highlighting and other goodies.
3131
3232# Getting Started
3333
34−You can get the latest version from hackage using `cabal install hakyll`, or
35−using [stack] by using `stack install hakyll`. Then, you can:
36−
37−[stack]: http://www.haskellstack.org/
34+You can get the latest version from hackage using `cabal install hakyll`. Then,
35+you can:
3836
3937- read the [tutorials](/tutorials.html);
4038- mail the [google discussion group](http://groups.google.com/group/hakyll);
web/libraries.markdowndeleted−21
@@ -1,21 +0,0 @@
1−---
2−title: Libraries
3−---
4−
5−# Libraries
6−
7−Since some functionality is too specific to be included in the main Hakyll
8−library, people have generously written utility libraries and tools for Hakyll.
9−Here is a list of the ones I know of (I would be happy to hear about others).
10−
11−- [hakyll-contrib-links](http://hackage.haskell.org/package/hakyll-contrib-links),
12− a Hakyll library that helps maintain a separate links database.
13−- [hakyll-agda](https://hackage.haskell.org/package/hakyll-agda),
14− a simple module useful to generate blog posts from literate Agda files.
15−- [hakyll-contrib-hyphenation](http://hackage.haskell.org/package/hakyll-contrib-hyphenation),
16− automatic hyphenation for Hakyll.
17−- [hakyll-convert](https://hackage.haskell.org/package/hakyll-convert),
18− a conversion tool from other blog engines to Hakyll.
19−- [hakyll-elm](https://hackage.haskell.org/package/hakyll-elm),
20− Hakyll wrapper for the Elm compiler.
21−- [hakyll-sass](http://hackage.haskell.org/package/hakyll-sass), a compiler for SASS templates.
web/releases.markdownnew file+164
@@ -0,0 +1,164 @@
1+---
2+title: Releases
3+---
4+
5+# Releases
6+
7+## Hakyll 4.4.0.0
8+
9+- Update to work with Pandoc 1.12. This changes the type of `readPandocBibilio`:
10+ the `CSL` argument is no longer optional.
11+
12+- Fix incorrect output of `toSiteRoot` on windows (contribution by Saeid
13+ Al-Wazzan)
14+
15+- Add a preview port option to `Configuration` (contribution by Jorge Israel
16+ Peña)
17+
18+- Add `watch` command that polls for changes but does not necessarily launch a
19+ server (contribution by Eric Stolten)
20+
21+- Generalise type of `metadataField`
22+
23+- Fix issue where metadata was not correctly loaded when using versions
24+
25+## Hakyll 4.3.3.0
26+
27+- Re-add the `functionField` function
28+
29+## Hakyll 4.3.2.0
30+
31+- Re-add the `mapContext` function
32+
33+- Unescape internal URLs when using `./site check` (contribution by Marc-Antoine
34+ Perennou)
35+
36+## Hakyll 4.3.1.0
37+
38+- Make teasers undefined if no `<!--more-->` comment is found
39+
40+- Sanitize tag URLs (contribution by Simonas Kazlauskas)
41+
42+## Hakyll 4.3.0.0
43+
44+- Add conditionals, partials and for loops to the template system (includes a
45+ contribution by Ivan N. Veselov)
46+
47+- Improvements to the preview functionality on windows (contribution by Jorge
48+ Israel Peña)
49+
50+- Add pagination support (contribution by Anton Dubovik)
51+
52+- Slight speedup for the Hakyll cache (contribution by justnoxx)
53+
54+- Add teaser functionality (contribution by Ivan N. Veselov)
55+
56+- Make `./site check` work with scheme-relative URLs (contribution by Simonas
57+ Kazlauskas)
58+
59+- The `./site deploy` command can now be customized with Haskell code
60+ (contribution by Samuel Tardieu)
61+
62+- Use `hsnotify` for proper polling instead of sleep loop on all platforms
63+ (contribution by Simonas Kazlauskas)
64+
65+- More useful debug info available
66+
67+## Hakyll 4.2.2.0
68+
69+- Fix issue with `Alternative` instance of `Compiler`
70+
71+## Hakyll 4.2.1.1
72+
73+*March 9, 2013*
74+
75+- Make `http-conduit` dependency optional by adding a `checkExternal` cabal flag
76+
77+## Hakyll 4.2.1.0
78+
79+*March 7, 2013*
80+
81+- Fix issue where `copyFileCompiler` ignored `providerDirectory`
82+
83+## Hakyll 4.2.0.0
84+
85+*March 7, 2013*
86+
87+- Read second extension for `.lhs`, e.g. `.md.lhs` or `.tex.lhs` (contribution
88+ by Alexander Vershilov)
89+
90+- Speedup initialization by using modification times instead of hashing files
91+
92+- Speedup initialization with a rewritten resource provider
93+
94+- Fix `./site check` not working with sites that require a user agent (e.g.
95+ <http://www.wikipedia.org/>)
96+
97+- Change `chronological` and `recentFirst` to actually look at the dates of
98+ items. This changes their types from:
99+
100+ chronological, recentFirst :: [Item a] -> [Item a]
101+
102+ to:
103+
104+ chronological, recentFirst
105+ :: MonadMetadata m => [Item a] -> m [Item a]
106+
107+ (contribution by Simonas Kazlauskas)
108+
109+- Add `metadataRoute`, so it is now possible to use metadata when determining
110+ routes
111+
112+- Improve metadata parser for multiline metadata fields (contribution by Peter
113+ Jones)
114+
115+- Add the `getMetadataField` utility
116+
117+## Hakyll 4.1.4.0
118+
119+*January 26, 2013*
120+
121+- Export the flexible `renderTags` function
122+
123+## Hakyll 4.1.3.0
124+
125+*January 26, 2013*
126+
127+- Export the constructor of the `Tags` datatype
128+
129+## Hakyll 4.1.2.0
130+
131+*January 20, 2013*
132+
133+- Fix an issue where a dependency cycle would lead to infinite recursion/stack
134+ overflow
135+
136+## Hakyll 4.1.1.0
137+
138+*January 20, 2013*
139+
140+- Fix an issue regarding `relativizeUrls` expanding `<meta />` to
141+ `<meta></meta>`
142+
143+## Hakyll 4.1.0.0
144+
145+*January 20, 2013*
146+
147+Update to use Pandoc 1.10, this requires changes to your `site.hs` if you're
148+using custom Pandoc options or the `Hakyll.Web.Pandoc.Biblio` module.
149+
150+- `defaultHakyllParserState` renamed to `defaultHakyllReaderOptions`
151+
152+- The type of `readPandocBiblio` changed
153+
154+Because of the many changes, this release is no longer compatible with Pandoc
155+1.9.
156+
157+## Hakyll 4.0.0.0
158+
159+*January 16, 2013*
160+
161+The Initial release of Hakyll 4, see
162+[this blogpost](http://jaspervdj.be/posts/2013-01-16-hakyll-4.0.html) and
163+[the migration guide](/tutorials/hakyll-3-to-hakyll4-migration-guide.html) for
164+an overview of changes.
web/site.hs+32−29
@@ -1,23 +1,20 @@
11--------------------------------------------------------------------------------
22{-# LANGUAGE OverloadedStrings #-}
3−import Control.Arrow (second)
4−import Control.Monad (filterM, forM_)
5−import Data.List (isPrefixOf, sortBy)
6−import Data.Monoid ((<>))
7−import Data.Ord (comparing)
3+import Control.Applicative ((<$>))
4+import Control.Arrow (second)
5+import Control.Monad (forM_)
6+import Data.Char (isDigit)
7+import Data.List (isPrefixOf, partition, sortBy)
8+import Data.Monoid (mappend)
9+import Data.Ord (comparing)
810import Hakyll
9−import System.Directory (copyFile)
10−import System.FilePath (dropTrailingPathSeparator, splitPath)
11+import System.FilePath (dropTrailingPathSeparator, splitPath)
1112import Text.Pandoc
1213
1314
1415--------------------------------------------------------------------------------
1516main :: IO ()
1617main = hakyllWith config $ do
17− -- Copy CHANGELOG.md here.
18− preprocess $ copyFile "../CHANGELOG.md" "releases.markdown"
19−
20− -- CSS
2118 match "css/*" $ do
2219 route idRoute
2320 compile compressCssCompiler
@@ -56,11 +53,23 @@ main = hakyllWith config $ do
5653 create ["tutorials.html"] $ do
5754 route idRoute
5855 compile $ do
59− ctx <- tutorialsCtx <$>
60− sortBy (comparing itemIdentifier) <$> loadAll "tutorials/*"
56+ tutorials <- loadAll "tutorials/*"
57+ itemTpl <- loadBody "templates/tutorial-item.html"
58+ let (series, articles) = partitionTutorials $
59+ sortBy (comparing itemIdentifier) tutorials
60+
61+ series' <- applyTemplateList itemTpl defaultContext series
62+ articles' <- applyTemplateList itemTpl defaultContext articles
63+
64+ let tutorialsCtx =
65+ constField "title" "Tutorials" `mappend`
66+ constField "series" series' `mappend`
67+ constField "articles" articles' `mappend`
68+ defaultContext
69+
6170 makeItem ""
62− >>= loadAndApplyTemplate "templates/tutorials.html" ctx
63− >>= loadAndApplyTemplate "templates/default.html" ctx
71+ >>= loadAndApplyTemplate "templates/tutorials.html" tutorialsCtx
72+ >>= loadAndApplyTemplate "templates/default.html" tutorialsCtx
6473 >>= relativizeUrls
6574
6675 -- Templates
@@ -68,7 +77,8 @@ main = hakyllWith config $ do
6877 where
6978 withToc = defaultHakyllWriterOptions
7079 { writerTableOfContents = True
71− , writerTemplate = Just "$toc$\n$body$"
80+ , writerTemplate = "$toc$\n$body$"
81+ , writerStandalone = True
7282 }
7383
7484
@@ -102,16 +112,9 @@ hackage url
102112
103113
104114--------------------------------------------------------------------------------
105−-- | Partition tutorials into tutorial series, other articles, external articles
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") <>
113− defaultContext
114− where
115− ofType ty = filterM (\item -> do
116− mbType <- getMetadataField (itemIdentifier item) "type"
117− return $ Just ty == mbType) tuts
115+-- | Partition tutorials into tutorial series & other articles
116+partitionTutorials :: [Item a] -> ([Item a], [Item a])
117+partitionTutorials = partition $ \i ->
118+ case splitPath (toFilePath $ itemIdentifier i) of
119+ [_, (x : _)] -> isDigit x
120+ _ -> False
web/templates/default.html+7−2
@@ -29,13 +29,18 @@
2929 <h1>Navigation</h1>
3030 <a href="/index.html">home</a>
3131 <a href="/tutorials.html">tutorials</a>
32− <a href="/libraries.html">libraries</a>
3332 <a href="/reference.html">reference</a>
3433 <a href="/about.html">about</a>
3534 <a href="/tutorials/faq.html">faq</a>
3635 <a href="/examples.html">examples</a>
3736 <a href="/releases.html">releases</a>
38− <a href="https://jaspervdj.be/contact.html#donate" target="_blank">donate</a>
37+ <a href="/hakyll3">Hakyll 3.X</a>
38+
39+ <!-- Flattr button -->
40+ <h1>Donate</h1>
41+ <a href="http://flattr.com/thing/291889/Hakyll" target="_blank">
42+ <img src="http://api.flattr.com/button/flattr-badge-large.png"
43+ alt="Flattr this" title="Flattr this" border="0" /></a>
3944 </div>
4045
4146 <div id="content">
web/templates/tutorial-item.htmlnew file+1
@@ -0,0 +1 @@
1+<li><a href="$url$">$title$</a> by <em>$author$</em></li>
web/templates/tutorials.html+7−50
@@ -1,56 +1,13 @@
11<h1>Tutorials about Hakyll</h1>
2−<h2 id="series">Tutorial series</h2>
3−<ul>
4− $for(main)$
5− <li>
6− <a href="$url$">$title$</a>
7− </li>
8− $endfor$
9−</ul>
10−<h2 id="articles">Other articles</h2>
2+<h2>Tutorial series</h2>
3+<ul>$series$</ul>
4+<h2>Other articles</h2>
115<p>In no particular order:</p>
12−<ul>
13− $for(articles)$
14− <li>
15− <a href="$url$">$title$</a> by <em>$author$</em>
16− </li>
17− $endfor$
18−</ul>
19−<h2 id="external-articles">External articles</h2>
20−<p>
21− These are articles on external blogs. If you wrote a similar article, feel
22− free to <a href="http://jaspervdj.be/contact.html">shoot me an email</a> so
23− I can add it to the list.
24−</p>
25−<p>In no particular order:</p>
26−<ul>
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>
43− $endfor$
44−</ul>
6+<ul>$articles$</ul>
457<p>
468 All these tutorials assume you are using the latest stable version of
479 Hakyll. If this is not the case, you might want to update using:
4810</p>
49−<pre><code>$$ ghc-pkg unregister hakyll
50−$$ cabal update
51−$$ cabal install hakyll</code></pre>
52−
53−<p>
54− Or using stack:
55−</p>
56−<pre><code>$$ stack install hakyll</code></pre>
11+<pre><code>$ ghc-pkg unregister hakyll
12+$ cabal update
13+$ cabal install hakyll</code></pre>
web/tutorials/01-installation.markdown+23−16
@@ -1,25 +1,27 @@
11---
22title: Installation
33author: Jasper Van der Jeugt
4−type: main
54---
65
76Installation
87------------
98
10−Installation is provided via Hackage, and some packages are available for
11−different distributions. For installation from source (i.e. via Hackage),
12−[stack] is recommended:
9+Installation is provided using [cabal], and some packages are available for
10+different distributions.
1311
14− $ stack install hakyll
12+ $ cabal install hakyll
1513
16−[stack]: http://www.haskellstack.org/
14+[cabal]: http://www.haskell.org/cabal/
15+
16+If you have a recent installation of `cabal` and your time is somewhat valuable,
17+use:
18+
19+ $ cabal install -j hakyll
1720
1821Linux distro packages:
1922
23+- [Archlinux (AUR)](https://aur.archlinux.org/packages/haskell-hakyll/)
2024- [Debian unstable](http://packages.debian.org/source/sid/haskell-hakyll)
21−- [Fedora](https://apps.fedoraproject.org/packages/ghc-hakyll)
22−- [Nix](https://nixos.org/nixos/packages.html#hakyll): `$ nix-env -i hakyll`
2325
2426Building the example site
2527-------------------------
@@ -28,27 +30,32 @@ Apart from the main Hakyll library, the cabal package also provides you with an
2830executable `hakyll-init` to create an example site. This is an easy way to get
2931started:
3032
31− $ stack exec hakyll-init my-site
33+ $ hakyll-init my-site
3234
3335This creates a folder `my-site` in the current directory, with some example
3436content and a generic configuration.
3537
36−If `hakyll-init` is not found, you should make sure your stack bin path
37−(usually `$HOME/.local/bin`) is in your `$PATH`. You can check your stack local
38−bin path by running `stack path --local-bin`.
38+If `hakyll-init` is not found, you should make sure `$HOME/.cabal/bin` is in
39+your `$PATH`.
40+
41+(If you're on OS X you may not have a bin directory in `$HOME/.cabal`. In this
42+case, check `$HOME/Library/Haskell/bin` and put it on your path if you find
43+`hakyll-init` there. See [here] for more information on installation paths on
44+OS X.)
45+
46+[here]: http://www.haskell.org/haskellwiki/Mac_OS_X_Common_Installation_Paths
3947
4048The file `site.hs` holds the configuration of your site, as an executable
4149haskell program. We can compile and run it like this:
4250
4351 $ cd my-site
44− $ stack init # creates stack.yaml file based on my-site.cabal
45− $ stack build
46− $ stack exec site build
52+ $ ghc --make site.hs
53+ $ ./site build
4754
4855If you installed `hakyll` with a preview server (this is the default), you can
4956now use
5057
51− $ stack exec site watch
58+ $ ./site preview
5259
5360and have a look at your site at
5461[http://localhost:8000/](http://localhost:8000/).
web/tutorials/02-basics.markdown+11−12
@@ -1,31 +1,30 @@
11---
22title: The basics
33author: Jasper Van der Jeugt
4−type: main
54---
65
76Building and cleaning
87---------------------
98
109If you followed along with the previous tutorial, you should now have the
11−example site up and running. By running `stack exec site build`, you created
12−two directories:
10+example site up and running. By running `./site build`, you created two
11+directories:
1312
1413- `_site`, with your site as HTML files, ready to be deployed;
1514- `_cache`, which Hakyll uses internally.
1615
17−`stack exec site clean` removes these directories, and `stack exec site
18−rebuild` performs a `clean` and then a `build`.
16+`./site clean` removes these directories, and `./site rebuild` performs a
17+`clean` and then a `build`.
1918
20−In general, you want to use `stack exec site build` when you just made changes
21−to the contents of your website. If you made changes to `site.hs`, you need to
22−recompile `site.hs` followed by a rebuild:
19+In general, you want to use `./site build` when you just made changes to the
20+contents of your website. If you made important changes to `site.hs`, you need
21+to recompile `site.hs` followed by a rebuild:
2322
24− stack build
25− stack exec site rebuild
23+ ghc --make site.hs
24+ ./site rebuild
2625
27−At this point, feel free to change some files, `stack exec site build` and see
28−what happens!
26+At this point, feel free to change some files, `./site build` and see what
27+happens!
2928
3029Pages and metadata
3130------------------
web/tutorials/03-rules-routes-compilers.markdown+1−2
@@ -1,7 +1,6 @@
11---
22title: Rules, routes and compilers
33author: Jasper Van der Jeugt
4−type: main
54---
65
76Basic rules
@@ -37,7 +36,7 @@ match "css/*" $ do
3736
3837```
3938
40−This is a declarative DSL: the order in which you write the rules makes little
39+This is a declarative DSL: the order in which you write the rules make little
4140difference, Hakyll will use dependency tracking to determine the correct order.
4241
4342We group the different rules using `match`. The first argument for `match` is a
web/tutorials/04-compilers.markdown+9−26
@@ -1,7 +1,6 @@
11---
2−title: 'More on compilers: load, and templates'
2+title: More on compilers: load, and templates
33author: Jasper Van der Jeugt
4−type: main
54---
65
76Loading items
@@ -54,10 +53,10 @@ when the template is applied. If you want an actual dollar sign in the output,
5453use `$$`.
5554
5655You usually compile the templates from disk using the aptly named
57−`templateBodyCompiler`:
56+`templateCompiler`:
5857
5958```haskell
60−match "templates/*" $ compile templateBodyCompiler
59+match "templates/*" $ compile templateCompiler
6160```
6261
6362Notice the lack of `route` here: this is because we don't need to write the
@@ -93,7 +92,7 @@ And `$title$` like this:
9392titleContext :: Context a
9493titleContext = field "title" $ \item -> do
9594 metadata <- getMetadata (itemIdentifier item)
96− return $ fromMaybe "No title" $ lookupString "title" metadata
95+ return $ fromMaybe "No title" $ M.lookup "title" metadata
9796```
9897
9998And compose them using the `Monoid` instance:
@@ -116,10 +115,8 @@ you to use:
116115- `$path$` for the original filepath of the page;
117116- `$foo$` where foo is specified in the metadata.
118117
119−`$date$` is not provided by default. In the scaffold, we use the convenience
120−context function `dateField`, which will parse an `Item`'s filename to check if
121−it begins with a date. You can see how we add it in the definition of `postCtx`
122−in `site.hs`:
118+`$date$` is not provided by default, you can see how we add it in the definition
119+of `postCtx` in `site.hs`:
123120
124121```haskell
125122postCtx :: Context String
@@ -131,7 +128,7 @@ postCtx =
131128### Loading and applying templates
132129
133130Now we know about templates, context and how to load arbitrary items. This gives
134−us enough background information in order to understand how you can apply a
131+us enough background information in order to understand you can apply a
135132template:
136133
137134```haskell
@@ -152,7 +149,7 @@ Control flow in templates
152149-------------------------
153150
154151Sometimes string interpolation does not suffice, and you want a little more
155−control over how your templates are laid out. Hakyll provides a few control
152+control over how your templates are layed out. Hakyll provides a few control
156153structures for this. The syntax for these structures was based on the syntax
157154used in pandoc templates, since Hakyll already has tight integration with
158155pandoc.
@@ -192,7 +189,7 @@ Note that an if-else form is supported as well:
192189
193190Partials allow you to [DRY] up your templates by putting repetitive actions into
194191separate template files. You can then include them using
195−`$partial("filename.html")$`.
192+`$partial("filename.html")$.
196193
197194[DRY]: http://en.wikipedia.org/wiki/Don%27t_repeat_yourself
198195
@@ -237,20 +234,6 @@ This uses the `$for(foo)$` construct. This construct allows you loop over a
237234list, in this case, `$posts$`. Inside the body of this for loop, all fields
238235refer to the current post, e.g.: `$url$`, `$title$` and `$date$`.
239236
240−You can also add a simple separator with the special `$sep$` field.
241−Everything between `$sep$` and `$endfor$` will be regarded as a separator
242−that will only be shown if there is more than one item in the list.
243−
244−```html
245−<ul>
246− $for(posts)$
247− $x$
248− $sep$,
249− $endfor$
250−</ul>
251−```
252−
253−
254237Of course, $posts$ does not magically appear. We have to specify this in
255238`site.hs`. Let's look at how `archive.html` is generated:
256239
web/tutorials/05-snapshots-feeds.markdown−1
@@ -1,7 +1,6 @@
11---
22title: Snapshots, and how to produce an RSS/Atom feed
33author: Jasper Van der Jeugt
4−type: main
54---
65
76Basic feed configuration
web/tutorials/06-versions.markdown−1
@@ -1,7 +1,6 @@
11---
22title: Producing multiple versions of a single file
33author: Jasper Van der Jeugt
4−type: main
54---
65
76Basics
web/tutorials/a-guide-to-the-hakyll-module-zoo.markdown−1
@@ -1,7 +1,6 @@
11---
22title: A Guide to the Hakyll Module Zoo
33author: Brent Yorgey
4−type: article
54---
65
76The hakyll package [contains a bewildering array](/reference/) of modules, and
web/tutorials/external-add-tags-to-your-hakyll-blog.mddeleted−6
@@ -1,6 +0,0 @@
1−---
2−title: Add tags to your Hakyll blog
3−author: Javran Cheng
4−url: https://javran.github.io/posts/2014-03-01-add-tags-to-your-hakyll-blog.html
5−type: external
6−---
web/tutorials/external-blogger-port.mddeleted−6
@@ -1,6 +0,0 @@
1−---
2−title: Port an existing Blogger blog to Hakyll
3−author: Daniel Campoverde
4−url: 'http://www.sillybytes.net/posts/from_blogger_to_hakyll.html'
5−type: external
6−---
web/tutorials/external-cassius-template.mddeleted−6
@@ -1,6 +0,0 @@
1−---
2−title: Using Cassius templating with Hakyll
3−author: Daniel Campoverde
4−url: 'http://www.sillybytes.net/posts/using_hakyll_with_cassius.html'
5−type: external
6−---
web/tutorials/external-clean-urls-with-hakyll.mddeleted−6
@@ -1,6 +0,0 @@
1−---
2−title: Clean URLs with Hakyll
3−author: Rohan Jain
4−url: http://www.rohanjain.in/hakyll-clean-urls/
5−type: external
6−---
web/tutorials/external-deploying-with-widely.mddeleted−6
@@ -1,6 +0,0 @@
1−---
2−title: Deploying with Widely
3−author: Kyle Marek-Spartz
4−url: http://kyle.marek-spartz.org/posts/2013-12-09-widely-and-hakyll.html
5−type: external
6−---
web/tutorials/external-external-code-inclusion.mddeleted−6
@@ -1,6 +0,0 @@
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.mddeleted−6
@@ -1,6 +0,0 @@
1−---
2−title: Hakyll's functionField
3−author: Beerend Lauwers
4−url: http://beerendlauwers.be/posts/2015-09-21-hakylls-functionfield.html
5−type: external
6−---
web/tutorials/external-github-pages-circle-ci.mddeleted−6
@@ -1,6 +0,0 @@
1−---
2−title: Create a GitHub page with Hakyll and CircleCI
3−author: Juan Pedro Villa
4−url: https://www.stackbuilders.com/news/dr-hakyll-create-a-github-page-with-hakyll-and-circleci
5−type: external
6−---
web/tutorials/external-inlining-and-compressing-css.mddeleted−6
@@ -1,6 +0,0 @@
1−---
2−title: Inlining and compressing CSS
3−author: Kyle Marek-Spartz
4−url: http://kyle.marek-spartz.org/posts/2014-12-09-hakyll-css-template-compiler.html
5−type: external
6−---
web/tutorials/faq.markdown−16
@@ -1,7 +1,6 @@
11---
22title: FAQ
33author: Jasper Van der Jeugt
4−type: article
54---
65
76## "hGetContents: invalid argument" or "commitBuffer: invalid argument"
@@ -25,21 +24,6 @@ using something like:
2524
2625You should also add this to your `.profile`, or whatever config file you use.
2726
28−On Windows, running `chcp 65001` before running your Hakyll executable has been
29−reported to work.
30−
31−Alternatively, you can specify this in your `site.hs`:
32−
33−```haskell
34−import qualified GHC.IO.Encoding as E
35−
36−main :: IO ()
37−main = do
38− E.setLocaleEncoding E.utf8
39− hakyll $ do
40− ...
41−```
42−
4327## "File name does not match module name" on Mac OS
4428
4529 Hakyll.hs:1:1:
web/tutorials/github-pages-tutorial.mddeleted−181
@@ -1,181 +0,0 @@
1−---
2−title: Using Hakyll with GitHub Pages
3−author: Erik Stevenson
4−type: article
5−---
6−
7−## Introduction
8−
9−[GitHub Pages](http://pages.github.com) has become a popular static website hosting solution due to its simplicity. Simply push a couple files to a repository and it's off to the races.
10−
11−Working with Hakyll on a GitHub Pages-hosted website is complicated slightly due to Hakyll outputting files to a ```_site``` subdirectory, our desire to have the source code as well as the compiled site stored in a single repository, and our desire to automate it.
12−
13−This guide will walkthrough the creation and setup of a GitHub site that has two independent branches.
14−
15−1. ```master``` - This is where your site lives. It's what you see when you go to ```https://<your repo>.github.io```. This branch *needs* to be called master.
16−2. ```develop``` - This is where your website's source is. That's all your Haskell code, your posts and templates, etc, and it's where you do work from. This name was chosen arbitrarily and can be freely substituted for any name of your choosing.
17−
18−When you're finished, you will be able to, with one command, refresh your website's contents and send any changes to your GitHub Page.
19−
20−In the interest of keeping this guide as simple as possible, I'll be making a few assumptions.
21−
22−1. Haskell is being used with [Stack](http://docs.haskellstack.org/en/stable/README/).
23−2. Creating a user/organization site (vice a project site).
24−3. You haven't changed Hakyll's default output directory of '_site/'.
25−
26−These instructions should be easy to adapt for any situation though.
27−
28−## GitHub Setup
29−
30−1. If required, create a new repository for your blog.
31−2. If required, create a ```master``` branch.
32−2. If applicable/desired, create/add to your repository any files that your site needs that will not be produced by your Hakyll project. For example, ```CNAME``` as outlined [here](https://help.github.com/articles/setting-up-your-pages-site-repository/).
33−3. Create a ```.gitignore``` file with at a minimum, the following entries:
34−
35−```
36−_cache/
37−_site/
38−.stack-work/
39−```
40−
41−## Local setup
42−
43−1. If required, create a new Hakyll project. If you're a stack user, there is a Hakyll template available that makes this step easy.
44−
45−```stack new myblog hakyll-template```
46−
47−2. Create a ```.gitignore``` file in your blog's directory with at a minimum, the same directories listed as in the GitHub repository.
48−3. Use the following git commands to setup your local repository.
49−
50−```
51−git init
52−# create new branch called develop and switch to it.
53−git checkout -b develop
54−# track all the source files for our blog.
55−git add .
56−# make our first commit
57−git commit -m "initial commit."
58−# and add the GitHub repository as a remote.
59−git remote add origin <URL to your GitHub pages repository>
60−```
61−
62−## Deployment
63−
64−So everything's all setup and we're ready to deploy.
65−
66−> **Note:** Performing the following commands from your ```develop``` branch is recommended since you will end up back in that branch at the end.
67−
68−Temporarily save any uncommitted changes that may exist in the current branch.
69−
70−```
71−git stash
72−```
73−
74−Ensure we are in the correct branch.
75−
76−```
77−git checkout develop
78−```
79−
80−Get a clean build of our site.
81−
82−```
83−stack exec myblog clean
84−stack exec myblog build
85−```
86−
87−Update the local list of remote branches to ensure we're able to checkout the branch we want in the next step.
88−
89−```
90−git fetch --all
91−```
92−
93−Switch to the `master` branch.
94−
95−> **Note:** Checking out this branch does not overwrite the files that Hakyll just produced because we have '_site' listed in both .gitignore files.
96−
97−```
98−git checkout -b master --track origin/master
99−```
100−
101−Next, copy the freshly made contents of '_site' over the old ones.
102−
103−> **Note:** Deleting a file from your site's source will not remove it from your `master` repository if it has already been published. An alternative to `cp` is discussed at the end of this guide.
104−
105−```
106−cp -a _site/. .
107−```
108−
109−Commit our changes.
110−
111−```
112−git add -A
113−git commit -m "Publish."
114−```
115−
116−And send them to GitHub.
117−
118−```
119−git push origin master:master
120−```
121−
122−Final clean up and return to the original state.
123−
124−```
125−git checkout develop
126−git branch -D master
127−git stash pop
128−```
129−
130−## Putting it all together
131−
132−Below is a complete listing of all the commands used to automate deployment to Github Pages. A `deployCommand` can be set as part of Hakyll's configuration options. More information and an example is provided [here](https://jaspervdj.be/hakyll/reference/Hakyll-Core-Configuration.html).
133−
134−```
135−# Temporarily store uncommited changes
136−git stash
137−
138−# Verify correct branch
139−git checkout develop
140−
141−# Build new files
142−stack exec myblog clean
143−stack exec myblog build
144−
145−# Get previous files
146−git fetch --all
147−git checkout -b master --track origin/master
148−
149−# Overwrite existing files with new files
150−cp -a _site/. .
151−
152−# Commit
153−git add -A
154−git commit -m "Publish."
155−
156−# Push
157−git push origin master:master
158−
159−# Restoration
160−git checkout develop
161−git branch -D master
162−git stash pop
163−```
164−
165−*And that's it.*
166−
167−## Removing old files with `rsync`
168−
169−Earlier it was mentioned a flaw is that deleted files will persist in the published site until deleted manually. This is easily overcome by using `rsync` instead of `cp`.
170−
171−```
172−rsync -a --filter='P _site/' \
173− --filter='P _cache/' \
174− --filter='P .git/' \
175− --filter='P .gitignore' \
176− --filter='P .stack-work' \
177− --delete-excluded \
178− _site/ .
179−```
180−
181−The only drawback this approach has is the requirement that *every* file in your site "go through" Hakyll. Fortunately, in many cases this is not an issue.
web/tutorials/hakyll-3-to-hakyll4-migration-guide.markdown+1−2
@@ -1,7 +1,6 @@
11---
22title: Hakyll 3 to Hakyll 4 migration guide
33author: Jasper Van der Jeugt
4−type: article
54---
65
76Introduction
@@ -38,7 +37,7 @@ The `Page` type in Hakyll 3.X has been removed and replaced by an `Item` type.
3837`pageCompiler` no longer exists -- where you previously used this, you probably
3938want to use `pandocCompiler` instead.
4039
41−`Page`s were manipulated using `setField`/`getField` functions in Hakyll 3.X.
40+`Page`s where manipulated using `setField`/`getField` functions in Hakyll 3.X.
4241In Hakyll 4, all metadata is completely immutable, so these functions have been
4342removed. In order to format and add fields, use a `Context` -- see the next
4443section.
web/tutorials/robertwpearce-01-setup-and-initial-customization.htmldeleted−5
@@ -1,5 +0,0 @@
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.htmldeleted−5
@@ -1,5 +0,0 @@
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.htmldeleted−5
@@ -1,5 +0,0 @@
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.htmldeleted−5
@@ -1,5 +0,0 @@
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.htmldeleted−5
@@ -1,5 +0,0 @@
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+2−16
@@ -1,7 +1,6 @@
11---
22title: Using Clay with Hakyll
33author: Jasper Van der Jeugt
4−type: article
54---
65
76[Clay](http://sebastiaanvisser.github.com/clay/) is a nice CSS preprocesser
@@ -14,25 +13,15 @@ the CSS. This would be an example of such a file:
1413``` haskell
1514{-# LANGUAGE OverloadedStrings #-}
1615import Clay
16+import qualified Data.Text.Lazy.IO as T
1717
1818test :: Css
1919test = ...
2020
2121main :: IO ()
22−main = putCss test
22+main = T.putStr $ render test
2323```
2424
25−If you prefer the compact version, you're going to have an extra import:
26−
27−
28−``` haskell
29−import qualified Data.Text.Lazy.IO as T
30−
31−main :: IO ()
32−main = T.putStr $ renderWith compact test
33−```
34−
35−
3625Let's assume such a file is called `css/foo.hs`. In our compiled site, we want
3726to map this to `css/foo.css`. Hence, the route is a simple `setExtension`. For
3827compilation, we simply pass the Clay file through `runghc` with no options.
@@ -41,9 +30,6 @@ compilation, we simply pass the Clay file through `runghc` with no options.
4130match "css/*.hs" $ do
4231 route $ setExtension "css"
4332 compile $ getResourceString >>= withItemBody (unixFilter "runghc" [])
44−
45−-- cabal sandbox users can use (unixFilter "cabal" ["exec", "runghc"])
46−-- given that you've added cabal to your PATH
4733```
4834
4935The major advantage of using this method (as opposed to importing the Clay files
web/tutorials/using-teasers-in-hakyll.markdown−1
@@ -1,7 +1,6 @@
11---
22title: Using teasers in Hakyll
33author: Ivan Veselov
4−type: article
54---
65
76## Introduction